2017-02-02 17:21:23 +01:00
|
|
|
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
|
|
|
/*
|
|
|
|
* This file is part of the LibreOffice project.
|
|
|
|
*
|
|
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
*
|
|
|
|
* This file incorporates work covered by the following license notice:
|
|
|
|
*
|
|
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
|
|
* contributor license agreements. See the NOTICE file distributed
|
|
|
|
* with this work for additional information regarding copyright
|
|
|
|
* ownership. The ASF licenses this file to you under the Apache
|
|
|
|
* License, Version 2.0 (the "License"); you may not use this file
|
|
|
|
* except in compliance with the License. You may obtain a copy of
|
|
|
|
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
|
|
|
|
*/
|
|
|
|
|
|
|
|
#ifndef INCLUDED_VCL_TASK_HXX
|
|
|
|
#define INCLUDED_VCL_TASK_HXX
|
|
|
|
|
|
|
|
#include <vcl/dllapi.h>
|
|
|
|
|
|
|
|
struct ImplSchedulerData;
|
|
|
|
|
|
|
|
enum class TaskPriority
|
|
|
|
{
|
Reorganize Scheduler priority classes
This is based on glibs classification of tasks, but while glib uses
an int for more fine grained priority, we stay with our enum.
1. Timers start with DEFAULT priority, which directly corresponds
with the previous HIGH priority
2. Idles start with DEFAULT_IDLE priority instead of the previous
HIGH priority, so idle default becomes "really run when idle".
As RESIZE and REPAINT are special, and the DEFAULTS are set, there
is just one primary decision for the programmer: should my idle
run before paint (AKA HIGH_IDLE)?
If we really need a more fine-grained classification, we can add it
later, or also switch to a real int. As a result, this drops many
classifications from the code and drastically changes behaviour,
AKA a mail merge from KDE is now as fast as Gtk+ again.
Change-Id: I498a73fd02d5fb6f5d7e9f742f3bce972de9b1f9
2016-08-10 12:00:53 +02:00
|
|
|
HIGHEST, ///< These events should run very fast!
|
|
|
|
DEFAULT, ///< Default priority used, e.g. the default timer priority
|
invoke idle priority timers only when actually idle
The 'Idle' timers are misnamed. They are zero-timeout times, i.e.
they are invoked immediately after returning to the main loop.
But that does not necessarily mean they are invoked when idle,
there may be e.g. user input pending in the system event queue.
In fact, LO events are processed before system events, which means
that 'Idle' timers are normally processed before user input.
Besides being confused, this also leads to poor performance in some
cases, such as when using mouse wheel to zoom in a large document.
This results in several mouse wheel events, each of which will
result in adjusting the zoom and that causing a repaint. Repaints
are internally handled using a TaskPriority::REPAINT 'Idle',
and so what happens is zoom->repaint->zoom->repaint->zoom->repaint
instead of the more efficient zoom->zoom->zoom->repaint.
This change (besides trying to clarify the confusion in the docs)
delays invoking tasks with priorities TaskPriority::HIGH_IDLE
and lower if there is user input or repaint events in the OS queue.
That means that tasks using idle priorities actually will be invoked
only when idle (barring background threads etc.).
I'm reasonably certain this is a safe change, there's no guarantee
when exactly tasks will be invoked (e.g. other tasks with a higher
priority go first) and explicitly specifying such a priority means
asking for it.
I already implemented this once in 06d731428ef6cf93c7333e8228b,
and it was also again done in 87199d3829257420429057336283, but
apparently these have been removed. There was d348035a60361a1b9ba9e
'Drop special idle handling' with the reasoning that 'Idles are just
instant timers'. Which strictly technically speaking is true due to
'Idle' being a misnomer, but the point is that some idles should be
actual idles and that's why they need to be handled specially.
Change-Id: I36c2b02a80ae7e1476b731f878d9b28aa87975f4
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/110538
Tested-by: Jenkins
Reviewed-by: Luboš Luňák <l.lunak@collabora.com>
2021-02-07 16:34:38 +01:00
|
|
|
// Input from the OS event queue is processed before HIGH_IDLE tasks.
|
Reorganize Scheduler priority classes
This is based on glibs classification of tasks, but while glib uses
an int for more fine grained priority, we stay with our enum.
1. Timers start with DEFAULT priority, which directly corresponds
with the previous HIGH priority
2. Idles start with DEFAULT_IDLE priority instead of the previous
HIGH priority, so idle default becomes "really run when idle".
As RESIZE and REPAINT are special, and the DEFAULTS are set, there
is just one primary decision for the programmer: should my idle
run before paint (AKA HIGH_IDLE)?
If we really need a more fine-grained classification, we can add it
later, or also switch to a real int. As a result, this drops many
classifications from the code and drastically changes behaviour,
AKA a mail merge from KDE is now as fast as Gtk+ again.
Change-Id: I498a73fd02d5fb6f5d7e9f742f3bce972de9b1f9
2016-08-10 12:00:53 +02:00
|
|
|
HIGH_IDLE, ///< Important idle events to be run before processing drawing events
|
|
|
|
RESIZE, ///< Resize runs before repaint, so we won't paint twice
|
|
|
|
REPAINT, ///< All repaint events should go in here
|
2025-02-28 22:52:13 -05:00
|
|
|
SKIA_FLUSH, ///< tdf#165277 Skia needs to flush immediately before POST_PAINT tasks on macOS
|
Reorganize Scheduler priority classes
This is based on glibs classification of tasks, but while glib uses
an int for more fine grained priority, we stay with our enum.
1. Timers start with DEFAULT priority, which directly corresponds
with the previous HIGH priority
2. Idles start with DEFAULT_IDLE priority instead of the previous
HIGH priority, so idle default becomes "really run when idle".
As RESIZE and REPAINT are special, and the DEFAULTS are set, there
is just one primary decision for the programmer: should my idle
run before paint (AKA HIGH_IDLE)?
If we really need a more fine-grained classification, we can add it
later, or also switch to a real int. As a result, this drops many
classifications from the code and drastically changes behaviour,
AKA a mail merge from KDE is now as fast as Gtk+ again.
Change-Id: I498a73fd02d5fb6f5d7e9f742f3bce972de9b1f9
2016-08-10 12:00:53 +02:00
|
|
|
POST_PAINT, ///< Everything running directly after painting
|
|
|
|
DEFAULT_IDLE, ///< Default idle priority
|
2024-01-28 20:17:57 +00:00
|
|
|
LOWEST, ///< Low, very idle cleanup tasks
|
|
|
|
TOOLKIT_DEBUG ///< Do not use. Solely for IdleTask::waitUntilIdleDispatched
|
2017-02-02 17:21:23 +01:00
|
|
|
};
|
|
|
|
|
2024-01-28 20:17:57 +00:00
|
|
|
#define PRIO_COUNT (static_cast<int>(TaskPriority::TOOLKIT_DEBUG) + 1)
|
2018-09-16 19:17:31 +00:00
|
|
|
|
2017-02-02 17:21:23 +01:00
|
|
|
class VCL_DLLPUBLIC Task
|
|
|
|
{
|
|
|
|
friend class Scheduler;
|
|
|
|
friend struct ImplSchedulerData;
|
|
|
|
|
|
|
|
ImplSchedulerData *mpSchedulerData; ///< Pointer to the element in scheduler list
|
2019-12-24 10:47:38 +02:00
|
|
|
const char *mpDebugName; ///< Useful for debugging
|
2017-02-02 17:21:23 +01:00
|
|
|
TaskPriority mePriority; ///< Task priority
|
|
|
|
bool mbActive; ///< Currently in the scheduler
|
2017-08-23 16:07:50 +02:00
|
|
|
bool mbStatic; ///< Is a static object
|
2017-02-02 17:21:23 +01:00
|
|
|
|
|
|
|
protected:
|
|
|
|
static void StartTimer( sal_uInt64 nMS );
|
|
|
|
|
|
|
|
const ImplSchedulerData* GetSchedulerData() const { return mpSchedulerData; }
|
|
|
|
|
|
|
|
virtual void SetDeletionFlags();
|
2017-01-29 17:12:25 +01:00
|
|
|
|
2017-02-02 17:21:23 +01:00
|
|
|
/**
|
2017-01-29 17:12:25 +01:00
|
|
|
* How long (in MS) until the Task is ready to be dispatched?
|
|
|
|
*
|
|
|
|
* Simply return Scheduler::ImmediateTimeoutMs if you're ready, like an
|
|
|
|
* Idle. If you have to return Scheduler::InfiniteTimeoutMs, you probably
|
2018-08-15 14:18:38 +02:00
|
|
|
* need another mechanism to wake up the Scheduler or rely on other
|
2017-01-29 17:12:25 +01:00
|
|
|
* Tasks to be scheduled, or simply use a polling Timer.
|
|
|
|
*
|
|
|
|
* @param nTimeNow the current time
|
|
|
|
* @return the sleep time of the Task to become ready
|
2017-02-02 17:21:23 +01:00
|
|
|
*/
|
2019-10-17 16:17:45 +02:00
|
|
|
virtual sal_uInt64 UpdateMinPeriod( sal_uInt64 nTimeNow ) const = 0;
|
2017-02-02 17:21:23 +01:00
|
|
|
|
|
|
|
public:
|
2019-12-24 10:47:38 +02:00
|
|
|
Task( const char *pDebugName );
|
2017-02-02 17:21:23 +01:00
|
|
|
Task( const Task& rTask );
|
|
|
|
virtual ~Task() COVERITY_NOEXCEPT_FALSE;
|
|
|
|
Task& operator=( const Task& rTask );
|
|
|
|
|
2019-03-13 01:23:36 +01:00
|
|
|
void SetPriority(TaskPriority ePriority);
|
2017-02-02 17:21:23 +01:00
|
|
|
TaskPriority GetPriority() const { return mePriority; }
|
|
|
|
|
|
|
|
const char *GetDebugName() const { return mpDebugName; }
|
|
|
|
|
Experimental Emscripten Qt6 --enable-emscripten-proxy-to-pthread ersatz
With Qt5, the (enabled by default) --enable-emscripten-proxy-to-pthread (i.e.,
leveraging Emscripten's -sPROXY_TO_PTHREAD) works reasonably well with the
handful of hacks we added to our Qt5 5.15.2+wasm branch. However, for Qt6 any
necessary hacking there would need to be excessive and looks unrealistic (see
the mailing list thread starting at
<https://lists.qt-project.org/pipermail/development/2024-December/045960.html>
"[Development] Wasm: Support for Emscripten PROXY_TO_PTHREAD?" and the
presentation at
<https://fosdem.org/2025/schedule/event/fosdem-2025-5169-lowa-in-need-of-a-vcl-plug/>).
But when leveraging the upcoming browser support for JSPI (through Emscripten's
-sJSPI, Qt6's -feature-wasm-jspi, and our --enable-emscripten-jspi), it looks
feasible to leave the Qt6 code alone and instead of proxying the complete
application main thread (including the Qt6 event handling) off the browser main
thread to an additional thread (automatically set up through Emscripten
-sPROXY_TO_PTHREAD), to just proxy some of the LO event handling off the browser
main thread to an additional thread (set up through a new
comphelper::emscriptenthreading::setUp). That way, problematic LO code that is
triggered from event handling (like "Tools - Extensions..." wanting to spawn
additional threads, which requires the browser main thread to be unblocked) can
be offloaded to the additional thread and get the browser main thread unblocked.
Thus there is a new "Emscripten Qt6 JSPI/non-PROXY_TO_PTHREAD" mode now (via
--enable-qt6 --enable-emscripten-jspi --disable-emscripten-proxy-to-pthread).
It is still experimental and known to occasionally hang and crash, but has been
seen to generally work at least with recent emsdk 4.0.3 and a recent Qt dev
branch (towards Qt 6.10, built with -feature-wasm-jspi) and running on recent
Chrome 132 with "Experimental WebAssembly JavaScript Promise Integration (JSPI)"
enabled under <chrome://flags>.
There are two places where code is proxied off the browser main thread to the
additional thread: One is QtInstance::ProcessEvent, the other is invoking
certain kinds of tasks (just "vcl::Dialog maLayoutIdle", for now) in
Scheduler::CallbackTaskScheduling (unconditionally proxying off all kinds of
tasks there appeared to cause more issues than it would solve). For the latter,
the concept of "transferability" has been added to class Task; the new
Emscripten mode is the only case making use of that concept.
Also for the latter, it is important that the browser main thread does not get
blocked at the
> SolarMutexGuard aGuard;
in QtTimer::timeoutActivated when such a timer event happens while the
additional thread has the SolarMutex locked and tries to do something that
requires the browser main thread to not be blocked (like spawning additional
threads upon "Tools - Extensions..."). Therefore, as a bad hack, and only for
the new Emscripen mode, that SolarMutexGuard has been moved from
QtTimer::timeoutActivated down to the call of pTask->Invoke() in
Scheduler::CallbackTaskScheduling (see the two TODO comments added to the code).
This appears to work relatively well, but is still a bad hack that should rather
be done properly.
And then there's some code across vcl/qt5 that may now end up running proxied
off the browser main thread, but which actually requires to be run on the
browser main thread (because, e.g., it calls into Qt6 code that accesses JS
entities that are only available there). Those places use a newly introduced
QtInstance::EmscriptenLightweightRunInMainThread that takes some code via a
lambda: For the new Emscripten mode, the code is explicitly proxied back to the
browser main thread, while in all other cases it is run directly in place.
Some further notes:
* -sPTHREAD_POOL_SIZE needed to be bumped by one, to accommodate for the
additional new thread.
* The code requires two additional JSPI "entry points",
_emscripten_check_mailbox (to complement the suspension in
QtInstance::ProcessEvent) and the invocation of the Qt6-internal
qstdweb::EventListener::handleEvent (to complement a Qt6-internal suspension
point hit when e.g. doing "Tools - Extension Manager..." and clicking "Add").
* The new Emscripten mode requires -fexperimental-library (for std::jthread and
std::stop_token) when building at least with emsdk 4.0.3, but it shouldn't
hurt to have that switch enabled unconditionally for Emscripten builds (which
are self-contained, so can't be hit by any compatibility issues that switch
might cause).
Change-Id: Id3b13e2cc3c304b072f388c6d736e7e663b36c58
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/181424
Tested-by: Jenkins
Reviewed-by: Stephan Bergmann <stephan.bergmann@allotropia.de>
Reviewed-by: Michael Weghorn <m.weghorn@posteo.de>
2025-02-11 14:11:25 +01:00
|
|
|
virtual bool DecideTransferredExecution();
|
|
|
|
|
2017-02-02 17:21:23 +01:00
|
|
|
// Call handler
|
|
|
|
virtual void Invoke() = 0;
|
|
|
|
|
2021-06-27 12:55:42 +02:00
|
|
|
/**
|
|
|
|
* Schedules the task for execution
|
|
|
|
*
|
|
|
|
* If the timer is already active, it's reset!
|
|
|
|
* Check with Task::IsActive() to prevent reset.
|
|
|
|
*
|
|
|
|
* If you unset bStartTimer, the Task must call Task::StartTimer(...) to be correctly scheduled!
|
|
|
|
* Otherwise it might just be picked up when the Scheduler runs the next time.
|
|
|
|
*
|
|
|
|
* @param bStartTimer if false, don't schedule the Task by calling Task::StartTimer(0).
|
|
|
|
*/
|
|
|
|
virtual void Start(bool bStartTimer = true);
|
2017-02-02 17:21:23 +01:00
|
|
|
void Stop();
|
|
|
|
|
|
|
|
bool IsActive() const { return mbActive; }
|
2017-08-23 16:07:50 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* This function must be called for static tasks, so the Task destructor
|
2020-12-08 16:04:48 +01:00
|
|
|
* ignores the scheduler mutex, as it may not be available anymore.
|
2017-08-23 16:07:50 +02:00
|
|
|
* The cleanup is still correct, as it has already happened in
|
|
|
|
* DeInitScheduler call well before the static destructor calls.
|
|
|
|
*/
|
|
|
|
void SetStatic() { mbStatic = true; }
|
|
|
|
bool IsStatic() const { return mbStatic; }
|
2017-02-02 17:21:23 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
#endif // INCLUDED_VCL_TASK_HXX
|
|
|
|
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|