From 14e0d65468e215fefc8ef360a57a47f6bb11a2ad Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Tue, 20 Dec 2016 03:18:03 +0100 Subject: [PATCH] Looper: idea how to determine "builder dirty" ...just by offloading that task onto the CommandQueue, which happens to know when a new command is being scheduled --- .../proc/control/dispatcher-looper-test.cpp | 95 +++++++++++++++---- wiki/renderengine.html | 4 +- 2 files changed, 81 insertions(+), 18 deletions(-) diff --git a/tests/core/proc/control/dispatcher-looper-test.cpp b/tests/core/proc/control/dispatcher-looper-test.cpp index e908b13fc..0762719b0 100644 --- a/tests/core/proc/control/dispatcher-looper-test.cpp +++ b/tests/core/proc/control/dispatcher-looper-test.cpp @@ -44,6 +44,32 @@ namespace test { namespace { // test fixture... + /** + * @todo this value should be retrieved from configuration ////////////////////////////////TICKET #1052 : access application configuration + * @see Looper::establishWakeTimeout() + */ + const uint EXPECTED_BUILDER_DELAY_ms = 50; + + bool + isFast (uint timeoutDelay_ms) + { + return timeoutDelay_ms < 1.2 * EXPECTED_BUILDER_DELAY_ms + and 0 < timeoutDelay_ms; + } + + bool + isSlow (uint timeoutDelay_ms) + { + return timeoutDelay_ms >= 1.2 * EXPECTED_BUILDER_DELAY_ms; + } + + bool + isDisabled (uint timeoutDelay_ms) + { + return 0 == timeoutDelay_ms; + } + + /** * Setup for testing the Looper. * In operation, the Looper receives its input by invoking closures. @@ -53,6 +79,7 @@ namespace test { struct Setup { bool has_commands_in_queue = false; + bool builder_is_dirty = false; Looper install() @@ -297,6 +324,7 @@ namespace test { CHECK ( looper.isIdle()); setup.has_commands_in_queue = true; // regular command processing + setup.builder_is_dirty = true; // need to re-build after issuing a command CHECK ( looper.requireAction()); CHECK (not looper.isDisabled()); @@ -311,25 +339,42 @@ namespace test { CHECK (not looper.isWorking()); CHECK ( looper.needBuild()); // ...note: still needs build CHECK ( looper.isIdle()); - ///////////////////////////////TODO verify the timeout is the short timeout - ///////////////////////////////TODO how to communicate build dirty state?? // set NOT DIRTY + CHECK (isFast (looper.getTimeout())); + + + setup.builder_is_dirty = false; CHECK (not looper.requireAction()); CHECK (not looper.isDisabled()); CHECK (not looper.isWorking()); CHECK (not looper.needBuild()); // ...note: now done with building CHECK ( looper.isIdle()); - ///////////////////////////////TODO verify the timeout is now the *extended* timeout + + CHECK (isSlow (looper.getTimeout())); + setup.has_commands_in_queue = true; // next command pending + CHECK ( looper.requireAction()); + CHECK (not looper.isDisabled()); + CHECK ( looper.isWorking()); + CHECK (not looper.needBuild()); // ...note: command not yet processed: no need to re-build + CHECK ( looper.isIdle()); + + CHECK (isSlow (looper.getTimeout())); + + + setup.builder_is_dirty = true; // now let's assume one command has been processed + CHECK ( looper.requireAction()); CHECK (not looper.isDisabled()); CHECK ( looper.isWorking()); CHECK ( looper.needBuild()); CHECK (not looper.isIdle()); - ///////////////////////////////TODO verify the timeout is back to the short period + + CHECK (isFast (looper.getTimeout())); + looper.enableProcessing(false); // disable processing @@ -338,7 +383,9 @@ namespace test { CHECK (not looper.isWorking()); CHECK (not looper.needBuild()); // ...note: dirty state hidden by disabled state CHECK (not looper.isIdle()); - ///////////////////////////////TODO verify the timeout is *completely disabled* + + CHECK (isDisabled (looper.getTimeout())); + looper.enableProcessing(true); // enable back @@ -347,7 +394,9 @@ namespace test { CHECK ( looper.isWorking()); CHECK ( looper.needBuild()); // ...note: dirty state revealed again CHECK (not looper.isIdle()); - ///////////////////////////////TODO verify the timeout is back to the short period + + CHECK (isFast (looper.getTimeout())); + setup.has_commands_in_queue = false; // done with the commands @@ -364,7 +413,9 @@ namespace test { CHECK (not looper.isWorking()); CHECK (not looper.needBuild()); // ...note: dirty state hidden CHECK (not looper.isIdle()); - ///////////////////////////////TODO verify the timeout is *completely disabled* + + CHECK (isDisabled (looper.getTimeout())); + looper.enableProcessing(true); // enable back @@ -373,18 +424,21 @@ namespace test { CHECK (not looper.isWorking()); CHECK ( looper.needBuild()); // ...note: dirty state revealed CHECK ( looper.isIdle()); - ///////////////////////////////TODO verify the timeout is back to the short period + + CHECK (isFast (looper.getTimeout())); + looper.enableProcessing(false); // disable processing - ///////////////////////////////TODO how to communicate build dirty state?? // set NOT DIRTY - // let's assume builder was running and is now finished + setup.builder_is_dirty = false; // let's assume builder was running and is now finished CHECK (not looper.requireAction()); CHECK ( looper.isDisabled()); CHECK (not looper.isWorking()); CHECK (not looper.needBuild()); // ...note: dirty state not obvious CHECK (not looper.isIdle()); - ///////////////////////////////TODO verify the timeout is *completely disabled* + + CHECK (isDisabled (looper.getTimeout())); + looper.enableProcessing(true); // enable back @@ -393,9 +447,12 @@ namespace test { CHECK (not looper.isWorking()); CHECK (not looper.needBuild()); // ...note: but now it becomes clear builder is not dirty CHECK ( looper.isIdle()); - ///////////////////////////////TODO verify the timeout is now the *extended* timeout + + CHECK (isSlow (looper.getTimeout())); + setup.has_commands_in_queue = true; // more commands again + setup.builder_is_dirty = true; // ...and let's assume one command has already been processed CHECK ( looper.requireAction()); CHECK (not looper.isDisabled()); @@ -410,7 +467,9 @@ namespace test { CHECK (not looper.isWorking()); CHECK (not looper.needBuild()); // ...note: no need to care for builder anymore CHECK (not looper.isIdle()); - ///////////////////////////////TODO verify the timeout *remains* at the short period (due to shutdown) + + CHECK (isFast (looper.getTimeout())); + setup.has_commands_in_queue = false; // and even when done all commands @@ -419,16 +478,20 @@ namespace test { CHECK (not looper.isWorking()); CHECK (not looper.needBuild()); // ...note: still no need for builder run, since in shutdown CHECK (not looper.isIdle()); - ///////////////////////////////TODO verify the timeout *remains* at the short period (due to shutdown) - ///////////////////////////////TODO how to communicate build dirty state?? // set NOT DIRTY + CHECK (isFast (looper.getTimeout())); + + + setup.builder_is_dirty = false; CHECK ( looper.requireAction()); CHECK (not looper.isDisabled()); CHECK (not looper.isWorking()); CHECK (not looper.needBuild()); CHECK (not looper.isIdle()); - ///////////////////////////////TODO verify the timeout *remains* at the short period (due to shutdown) + + CHECK (isFast (looper.getTimeout())); + } diff --git a/wiki/renderengine.html b/wiki/renderengine.html index 26af18a66..a9c2080b9 100644 --- a/wiki/renderengine.html +++ b/wiki/renderengine.html @@ -5222,7 +5222,7 @@ Besides, they provide an __inward interface__ for the [[ProcNode]]s, enabling th [img[Asset Classess|uml/fig131077.png]] {{red{Note 3/2010}}} it is very unlikely we'll organise the processing nodes as a class hierarchy. Rather it looks like we'll get several submodules/special capabilities configured in within the Builder -
+
//The guard and coordinator of any operation within the session subsystem.//
 The session and related components work effectively single threaded. Any tangible operation on the session data structure has to be enqueued as [[command|CommandHandling]] into the dispatcher. Moreover, the [[Builder]] is triggered from the ProcDispatcher; and while the Builder is running, any command processing is halted. The Builder in turn creates or reshapes the processing nodes network, and the changed network is brought into operation with a //transactional switch// -- while render processes on this processing network operate unaffected and essentially multi-threaded.
 
@@ -5261,7 +5261,7 @@ The ProcDispatcher is a component with //running state.// There is some kind of
 
 Initially the command queue is empty and the ProcDispatcher can be considered idle. Whenever more commands are available in the queue, the dispatcher will handle them one after another, without delay, until the queue is emptied. Yet the Builder run need to be kept in mind. Essentially, the builder models a //dirty state:// whenever a command has touched the session, the corresponding LowLevelModel must be considered out of sync, possibly not reflecting the intended semantics of the session anymore. From a strictly logical view angle, we'd need to trigger the builder after each and every session command -- but it was a very fundamental design decision in Lumiera to allow for a longer running build process, more akin to running a compiler. This decision opens all the possibilities of integrating a knowledge based system and resolution activities to find a solution to match the intended session semantics. For this reason, we decouple the UI actions from session and render engine consistency, and we enqueue session commands, to throttle down the number of builder runs.
 
-So the logic to trigger builder runs has to take some leeway into account. Due to the typical interactive working style of an editing application, session commands might be trickling in in strikes of similar commands, intermingled with tiny pauses. For this reason, the ProcDispatcher implements some //hysteresis,// as far as triggering the builder runs is concerned. The builder is fired in idle state, but only after passing some //latency period.// On the other hand, massive UI activities (especially during a builder run) may have flooded the queue, thus sending the session into an extended period of command processing. From the user's view angle, the application looks non responsive in such a case, albeit not frozen, since the UI can still enqueue further commands and thus retains the ability to react locally on user interaction. To mitigate this problem, the builder should be started anyway after some extended period of command processing, even if the queue is not yet emptied. Each builder run produces a structural diff message sent towards the UI and thus causes user visible changes within the session's UI representation. This somewhat stuttering response conveys to the user a tangible sensation of ongoing activity, while communicating at the same time, at least subconsciously some degree of operational overload.
+So the logic to trigger builder runs has to take some leeway into account. Due to the typical interactive working style of an editing application, session commands might be trickling in in strikes of similar commands, intermingled with tiny pauses. For this reason, the ProcDispatcher implements some //hysteresis,// as far as triggering the builder runs is concerned. The builder is fired in idle state, but only after passing some //latency period.// On the other hand, massive UI activities (especially during a builder run) may have flooded the queue, thus sending the session into an extended period of command processing. From the user's view angle, the application looks non responsive in such a case, albeit not frozen, since the UI can still enqueue further commands and thus retains the ability to react locally on user interaction. To mitigate this problem, the builder should be started anyway after some extended period of command processing, even if the queue is not yet emptied. Each builder run produces a structural diff message sent towards the UI and thus causes user visible changes within the session's UI representation. This somewhat stuttering response conveys to the user a tangible sensation of ongoing activity, while communicating at the same time, at least subconsciously some degree of operational overload. {{red{note 12/2016 builder is not implemented, so consider this planning}}}
 
 Any change to the circumstances determining the ProcDispatcher's behaviour need to be imparted actively through the public interface -- the dispatcher is not designed to be a state listener or observer. Any such state change notifications are synchronised and cause a wakeup notification to the session thread. For this purpose, enqueuing of further commands counts as state change and is lock protected. Beyond that, any other activities, like //processing// of commands or builder runs, are performed within the session thread without blocking other threads; the locking on the ProcDispatcher is only ever short term to ensure consistent internal state. Clients need to be prepared for the effect of actions to appear asynchronously and with some delay. Especially this means that session switch or shutdown has to await completion of any session command or builder run currently in progress.