looks like I'm trapped with the choice between a convoluted API design
and an braindead and inefficient implementation. I am leaning towards the latter
- polish the text in the TiddlyWiki
- integrate some new pages in the published documentation
Still mostly placeholder text with some indications
- fill in the relevant sections in the overview document
- adjust, expand and update the Doxygen comments
TODO: could convert the TiddlyWiki page to Asciidoc and
publish it mostly as-is. Especially the nice benchmarks
from yesterday :-D
This is essentially the solution we used since start of the Lumiera project.
This solution is not entirely correct in theory, because the assignment to the
instance pointer can be visible prior to releasing the Mutex -- so another thread
might see a partially initialised object
_not_ using the dependency factory, rather direct access
- to a shared object in the enclosing stack frame
- to a heap allocated existing object accessed through uniqe_ptr
This is a tricky problem an an immediate consequence of the dynamic configuration
favoured by this design. We avoid a centralised configuration and thus there
are no automatic rules to enforce consistency. It would thus be possible
to start using a dependency in singleton style, but to switch to service
style later, after the fact.
An attempt was made to prevent such a mismatch by static initialisiation;
basically the presence of any Depend<SRV>::ServiceInstance<X> would disable
any usage of Depend<SRV> in singleton style. However, such a mechanism
was found to be fragile at best. It seems more apropriate just to fail
when establishing a ServiceInstance on a dependency already actively in
use (and to lock usage after destroying the ServiceInstance).
This issue is considered rather an architectural one, which can not be
solved by any mechanism at implementation level ever
Problems:
- using Meyers Singleton plus a ClassLock;
This is wasteful, since the compiler will emit additional synchronisation
and will likely not be able to detect the presence of our explicit locking guard
- what happens if the Meyers Singleton can not even be instantiated, e.g. for
an abstract baseclass? We are required to install an explicit subclass configuration
in that case, but the compiler is not able to see this will happen, when just
compiling the lib::Depend
Most dependencies within Lumiera are singletons and this approach remains adequate.
Singletons are not "EVIL" per se. But in some cases, there is an explicit
lifecycle, managed by some subsystem. E.g. some GUI services are only available
while the GTK event loop is running.
This special case can be integrated transparently into our lib::Depend<TY> front-end,
which defaults to creating a singleton otherwise.
we'll use a typedef to represent the default case
and provide the level within the UI-Tree as template parameter for the generic case
This avoids wrapping each definition into a builder function, which will be
the same function for 99% of the cases, and it looks rather compact and natural
for the default case, while still retaining genericity.
Another alternative would have been to inject the Tree-level at the invocation;
but doing so feels more like magic for me.
This looks like YAGNI, and it would be non trivial to implement.
But since the feature looks important for slick UI behaviour,
I've made a new ticket and leave it for now
several extensions and convenience features are conceivable,
but I'll postpone all of them for later, when actual need arises
Note especially there is one recurring design challenge, when creating
such a demand-driven tree evaluation: more often than not it turns out
that "downstream" will need some information about the nested tree structure,
even while, on the surfice, it looks as if the evaluation could be working
completely "linearised". Often, such a need arises from diagnostic features,
and sometimes we want to invoke another API, which in turn could benefit
from knowing something about the original tree structure, even if just
abstracted.
I have no real solution for this problem, but implementing this pipeline builder
leads to a pragmatic workaround: since the iterator already exposes a expandChildren(),
it may as well expose a depth() call, even while keeping anything beyond that
opaque. This is not the clean solution you'd like, but it comes without any
overhead and does not really break the abstraction.
...and there is a point where to stop with the mere technicalities,
and return to a design in accordance with the inner nature of things.
Monads are a mere technology, without explicatory power as a concept or pattern
For that reason
- discard the second expansion pattern implemented yesterday,
since it just raises the complexity level for no given reason
- write a summary of my findings while investigating the abilities
of Monads during this design excercise.
- the goal remains to abandon IterExplorer and use the now complete
IterTreeEplorer in its place. Which also defines roughly the extent
to wich monadic techniques can be useful for real world applications
...to limit them to the UI-Coordinates themselves,
while declining the possibility to mutate the target environment
through the PathResolver. Better handle changes within the
target environment by dedicated API calls on the target elements,
instead of creating some kind of "universal structure"
..this collection of ideas, terms and conclusions has been shaped
since some time within the TiddlyWiki. Since I've now started even
some supporting implementation regarding these concepts, its time
to publish them in the design documentation section of the Website
After completing the self-contained UICoord data elements,
the next thing to consider might be how to resolve UI coordinates
against an actual window topology. We need to define a suitable
command-and-query interface in order to build and verify this
intricate resolution process separated from the actual UI code.
...we have to face the problem that we need some generic strategy
for access to component views, which possibly will become customisable.
And the allowed patterns of access are quite different for the various
kind of view we know....
this starts work on a new UI global topic (#1004)
- coin a new term: "view component"
- distinction between veiw component and Panel
- consider how to locate view components
- WindowList becomes WindowLocator
This change was caused by investigation of UI event loop dispatch;
since the GTK UI is designed to run single threaded, any invocation
from other threads need to be diepatched explicitly.
A possible way to achieve this is to use Glib::Dispatcher, which
in turn requires that the current thread (which is in this case the UI thread)
already holds a Glib::MainContext
This prompted me to create a tight link between the external facade interfaces
of the UI and the event loop itself. What remains to be settled is how
to hand over arguments to the action in the main loop
After investigation of current GTK and GIO code, I came to the conclusion
that we do *not* want to rely on the shiny new Gtk::Application, which
provides a lot of additional "convenience" functionality we do neither
need nor want. Most notably, we do not want extended desktop integration
like automatically connecting to D-Bus or exposing application actions
as desktop events.
After stripping away all those optional functions and extensions, it turns
out the basic code to operate the GTK main event loop is quite simple.
This changeset extracts this code from the (deprecated) Gtk::Main and
integrates it directly in Lumiera's UI framework object (UiManager).