command processing against the session is not yet implemented,
so to allow for unit testing, we magically recognise all commands
starting with "test." and invoke them directly within the Dispatcher.
With this addition, the basic functionality of the dispatcher works now
...since the session loop will be notified on any change via the
interface, adding a command will activate the loop, and the builder
timeout is handled separately via the dirty state. So there is no
need to spin around the loop in idle state.
As a aside, timeout waiting on a condition variable can be intentional
and should thus not be logged as an error automatically. It is up to the
calling context to decide if a timeout constitutes an exceptional situation.
It is always a trade-off performance vs. readability.
Sometimes a single-threaded implementation of self-contained logic
is preferable to a slightly more performant yet obscure implementation
based on our threadpool and scheduler.
Did a full review of state and locking logic, seems airtight now.
- command processing itself is unimplemented, we log a TODO message for now
- likewise, builder is not implemented
- need to add the deadlock safeguard #1054
Due to object scoping we can conclude reliably that the only one
ever to delete the DispacherLoop object will be the the loop thread
from within this object itself, when invoking the termination callback.
Btw, the lock on the inner object was insufficient and will be
replaced by taking the outer lock
It turns out we *do* support the use of anonymous commands
(while it is not clear yet if we really need this feature).
Basically, client code may either create and register a new
instance from another command used as prototype, by invoking
Command::storeDef(ID). Or, alternatively it may just invoke
newInstance() on the command, which creates a new handle
and a valid new implementation (managed by the handle as
smart-ptr), but never stores this implementation into the
CommandRegistry. In that case, client code may use such a
command just fine, as long as it cares to hold onto that
handle; but it is not possible to retrieve this command
instance later by symbolic ID.
In the light of this (possible) usage pattern, it doesn't
make sense to throw when accessing a command-ID. Rather, we
now return a placeholder-Symbol ("_anonymous_")
after reading some related code, I am leaning towards a design
to mirror the way command messages are sent over the UI-Bus.
Unfortunately this pretty much abandons the possibility to
invoke these operations from a client written in C or any
other hand made language binding. Which pretty much confirms
my initial reservation towards such an excessively open
and generic interface system.
...this means to turn Looper into a state machine.
Yet it seems more feasible, since the DispatcherLoop has a nice
checkpoint after each iteration through the while loop, and we'd
keep that whole builder-dirty business completely confined within
the Looper (with a little help of the DispatcherLoop)
Let's see if the state transition logic can actually be implemented
based just on such a checkpoint....?
....if by some weird coincidence, a command dispatched into the session
happens to trigger session shutdown or re-loading, this will cause a deadlock,
since decommissioning of session data structures must wait for the
ProcDispatcher to disable command processing -- and this will obviously
never happen when in a callstack below some command execution!
After some consideration, it became clear that this service implementation
is closely tied to the DispatcherLoop -- which will consequently be
responsible to run and expose this service implementation
need to keep state variables on both levels,
since the session manager (lifecycle) "opens" the session
for external access by starting the dispatcher; it may well happen
thus that the session starts up, while the *session subsystem*
is not(yet) started
"command dispatching" == the public session interface
so we'll better implement this important causal link directly,
instead of some obscure trickery with lifecycle events.
mark TODOs in code to make that happen.
Actually, it is not hard to do so, it just requires to combine
all the existing building blocks. When this is done, we can define
the "Session" subsystem as prerequisite for "GUI" in main.cpp
Unless I've made some (copy-n-paste) mistake with defining the facades,
this should be sufficient to pull up "the Session" and automatically
let the Gui-Plugin connect against the SessionCommandService
...the sheer amount of mechanical replacements scattered all over these
files might be a vivid indication, that the design of the interface system
is subobptimal ;-)