- the default should be to look for total coverage
- the predicates should reflect the actual state of the path only
- the 'canXXX' predicates test for possible covering mutation
I set out to "discover" what operations we actually need on the LocationQuery
interface, in order to build a "coordinate resolver" on top. It seems like
this set of operations is clear by now.
It comes somewhat as a surprise that this API is so small. This became possible
through the idea of a ''child iterator'' with the additional ability to delve down and
expand one level of children of the current element. Such can be ''implemented''
by relying on techniques similar to the "Monads" from functional programming.
Let's see if this was a good choice. The price to pay is a high level of ''formal precision''
when dealing with the abstraction barrier. We need to stick strictly to the notion of a
''logical path'' into a tree-like topology, and we need to be strong enough never to
give in and indulge with "the concrete, tangible". The concrete reality of a tree
processing algorithm with memory management plus backtracking is just to complex
to be handled mentally. So either stick to the rules or get lost.
...but not yet switched into the main LocationQuery interface,
because that would also break the existing implementation;
recasting this implementation is the next step to do....
...which basically allows us to return any suitable implementation
for the child iterator, even to switch the concrete iteration on each level.
We need this flexibility when implementing navigation through a concrete UI
...at least when using a wrapped Lumiera Iterator as source.
Generally speaking, this is a tricky problem, since real mix-in interfaces
would require the base interface (IterSource) to be declared virtual.
Which incurres a performance penalty on each and every user of IterSource,
even without any mix-in additions. The tricky part with this is to quantify
the relevance of such a performance penalty, since IterSource is meant
to be a generic library facility and is a fundamental building block
on several component interfaces within the architecture.
This is just a temporary solution, until IterSource is properly refactored (#1125)
After that, IterSource is /basically a state core/ and the adaptor will be more or less trivial
- as it stands currently, IterSource has a design problem, (see #1125)
- and due to common problems in C++ with mix-ins and extended super interfaces,
it is surprisingly tricky to build on an extension of IterSource
- thus the idea is to draft a new solution "in green field"
by allowing TreeExplorer to adapt IterSource automatically
- the new sholution should be templated on the concrete sub interface
and ideally even resolve the mix-in-problem by re-linearising the
inheritance line, i.e. replace WrappedLumieraIter by something
able to wrap its source, in a similar vein as TreeExplorer does
...this was a difficult piece of consideration and analysis.
In the end I've settled down on a compromise solution,
with the potential to be extended into the right direction eventually...
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.
...so sad.
The existing implementation was way more elegant,
just it discarded an exahusted parent element right while in expansion,
so effectively the child sequence took its place. Resolved that by
decomposing the iterNext() operation. And to keep it still readable,
I make the invariant of this class explicit and check it (which
caught yet another undsicovered bug. Yay!)
instead of building a very specific collaboration,
rather just pass the tree depth information over the extended iterator API.
This way, "downstream" clients *can* possibly react on nested scope exploration
We get conflicting goals here:
- either the child expansion happens within the opaque source data
and is thus abstracted away
- or the actual algorithm evaluation becomes aware of the tree structure
and is thus able to work with nested evaluation contexts and a local stack
...build on top of the core features of TreeExplorer
- completely encapsulate and abstract the source data structure
- build an backtracking evaluation based on layered evaluation
of this abstracted expandable data source
NOTE: test passes compilation, but doesn't work yet
...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
...it can sensibly only be done within the Expander itself.
Question: is this nice-to-have-feature worth the additional complexity
of essentially loading two quite distinct code paths into a single
implementation object?
As it stands, this looks totally confusing to me...
At that time, our home-made Tuple type was replaced by std::tuple,
and then the command framework was extended to also allow command invocation
with arguments packaged as lib::diff::Record<GenNode>
With changeset 0e10ef09ec
A rebinding from std::tuple<ARGS...> to Types<ARGS> was introduced,
but unfortunately this was patched-in on top of the existing Types<ARGS...>
just as a partial specialisation.
Doing it this way is especially silly, since now this rebinding also kicks
in when std::tuple appears as regular payload type within Types<....>
This is what happened here: We have a Lambda taking a std::tuple<int, int>
as argument, yet when extracting the argument type, this rebinding kicks in
and transforms this argument into Types<int, int>
Oh well.
this leads to either unfolding the full tree depth-first,
or, when expanding eagerly, to delve into each sub-branch down to the leaf nodes
Both patterns should be simple to implement on top of what we've built already...
IterSource should be refactored to have an iteration control API similar to IterStateWrapper.
This would resolve the need to pass that pos-pointer over the abstraction barrier,
which is the root cause for all the problems and complexities incurred here
turns out that -- again -- we miss some kind of refresh after expanding children.
But this case is more tricky; it indicates a design mismatch in IterSource:
we (ab)use the pos-pointer to communicate iteration state. While this might be
a clever trick for iterating a real container, it is more than dangerous when
applied to an opaque source state as in this case. After expanding children,
the pos-pointer still points into the cache buffer of the last transformer.
In fact, we miss an actualisation call, but the IterSource interface does not
support such a call (since it tries to get away with state hidden in the pos pointer)
this was a design decision, but now I myself run into that obvious mistake;
thus not sure if this is a good design, or if we need a dedicated operation
to finish the builder and retrieve the iterable result.
as it turned out, when "inheriting" ctors, C++14 removes the base classes' copy ctors.
C++17 will rectify that. Thus for now we need to define explicitly that
we'll accept the base for initialising the derived. But we need do so
only on one location, namely the most down in the chain.
Since this now requires to import iter-adapter-stl.hpp and iter-source.hpp
at the same time, I decided to drop the convenience imports of the STL adapters
into namespace lib. There is no reason to prefer the IterSource-based adapters
over the iter-adapter-stl.hpp variants of the same functionality.
Thus better always import them explicitly at usage site.
...actual implementation of the planned IterSource packaging is only stubbed.
But I needed to redeclare a lot of ctors, which doesn't seem logical
And I get a bad function invocation from another test case which worked correct beforehand.
due to switching from ADL extension points to member functions,
we now need to detect a "state core" type in a different fashion.
The specific twist is that we can not spell out the full signature
in all cases, since the result type will be formed as a consequence
of this type detection. Thus there are now additional detectors to
probe for the presence of a specific function name only, and the
distinction between members and member functions has been sharpened.
Considering the fact that we are bound to introduce yet another iteration control function,
because there is literally no other way to cause a refresh within the IterTreeExplorer-Layers,
it is indicated to reconsider the way how IterStateWrapper attaches to the
iteration control API.
As it turns out, we'll never need an ADL-free function here;
and it seems fully adequate to require all "state core" objects to expose
the API as argument less member function. Because these reflect precisely
the contract of a "state core", so why not have them as member functions.
And as a nice extra, the implementation becomes way more concise in
all the cases refactored with this changeset!
Yet still, we stick to the basic design, *not* relying on virtual functions.
So this is a typical example of a Type Class (or "Concept" in C++ terminology)
good news: it (almost) works out-of-the-box as expected.
There is only one problem: expandChildren() alters the content of the
data source, yet downstream decorators aren't aware of that fact and
continue to present cached evaluations, until the next iterate() call
is issued. Yet unfortunately this iterate already consumes the first
of the expanded children, which thus gets shadowed by the cached
outcome of parent node already consumed and expanded at that point
See the first example:
"10-8-expand-8-4-2-6-4-2"
should be 6 ^^^
...which happens to be supported out of the box,
due to the generic adaptor magic shared with the explore-operation
Exploiting this feature, some functor could even subvert the layering order
- always layer the TreeExplorer (builder) on top of the stack
- always intersperse an IterableDecorator in between adjacent layers
- consequently...
* each layer implementation is now a "state core"
* and the source is now always a Lumiera Iterator
This greatly simplifies all the type rebindings and avoids the
ambiguities in argument converison. Basically now we can always convert
down, and we just need to pick the result type of the bound functor.
Downside is we have now always an adaptation wrapper in between,
but we can assume the compiler is able to optimise such inline
accessors away without overhead.
...yet this seems like a rather bad idea,
it breeds various problems and requires arcane trickery to make it fly
==> abandon this design
==> always intersperse an IterableDecorator between each pair of Layers
...especially relevant in the context of TreeExplorer,
where the general understanding is that the "Data Source" (whatever it is)
will be piggy-backed into the pipeline builder, and this wrapping is
conceived as being essentially a no-op.
It is quite possible we'll even start using such pipeline builders
in concert with move-only types. Just consider a UI-navigator state
hooked up with a massive implementation internal pointer tree attached
to all of the major widgets in the UI. Nothing you want to copy in passing by.
As it turned out, we had two bugs luring in the code base,
with the happy result of one cancelling out the adverse effects of the other
:-D
- a mistake in the invocation of the Itertools (transform, filter,...)
caused them to move and consume any input passed by forwarding, instead
of consuming only the RValue references.
- but util::join did an extraneous copy on its data source, meaning that
in all relevant cases where a *copy* got passed into the Itertools,
only that spurious temporary was consumed by Bug #1.
(Note that most usages of Itertools rely on RValues anyway, since the whole
point of Itertools is to write concise in-line transformation pipelines...)
*** Added additional testcode to prove util::stringify() behaves correct
now in all cases.
- we do strip references
- we delegate to nested typedefs
Hoever, we do *not* treat const or pointers in any way special --
if the user want to strip or level these, he has to do so explicitly.
Initially it seemed like a good idea to do something clever here, but
on the long run, such "special treatment" is just good for surprises
...automatically whenever those are present.
Up to now, we hat that as base case, which limited usage to those cases
where we already know such nested definitions are actually present
attempt to re-use the same traits as much as possible
NOTE: new code not passing compiler yet, but refactored old code
does, and still passes unit test
...which uncovered an error in the test fixture
plus helped to spot the spurious copy when passing the argument to the expand functor
And my GDB crashed when loading the executable, YAY!
so we'll need to coment out some code from now on,
until we're able to switch to a more recent toolchain (#1118)
but possible only for the iterator -> iterator case
Since we can not "probe" a generic lambda, we get only one shot:
we can try to bind it into a std::function with the assumed signature
This is a consequence of the experiments with generic lambdas.
Up to now, lib::meta::_Fun<F> failed with a compilation error
when passing the decltype of such a generic lambda.
The new behaviour is to pick the empty specialisation (std::false_type) in such cases,
allowing to guard explicit specialisations when no suitable functor type
is passed
this solution makes me feel somewhat queasy..
stacking several adaptors and wrappers and traits on top of each other.
Well, it type checks and passes the test, so let's trust functional programming
The plan is to use a monad-like scheme, but allow for a lot of leeway
with respect to the src and value types of the expand functor.
A key idea is to allow for a *different* state core than used in the source
The key trick is to form an expression with the free function, using a declval of the type to probe.
What is somewhat tricky is the fact that functions can be void, so we need just to pick up
the type and use it in another type expression
Here, the tricky question remains, how to relate this evalutaion scheme
to the well known monadic handling of collections and iterators.
It seems, we can not yet decide upon that question, rather we should
first try to build a concrete implementation of the envisioned algorithm
and then reconsider the question later, to what extent this is "monadic"
This can be seen as a side track, but the hope is
by relying on some kind of monadic evaluation pattern, we'll be
able to to reconcile the IterExplorer draft from 2012 with the requirement
to keep the implementation of "tree position" entirely opaque.
The latter is mandatory in the use case here, since we must not intermingle
the algorithm to resolve UI-coordinates in any way with the code actually
navigating and accessing GTK widgets. Thus, we're forced to build some kind
of abstraction barrier, and this turns out to be surprisingly difficult.
...which can be helpful when a function usually returns a somewhat dressed-up iterator,
but needs to return a specific fixed value under some circumstances
- fix some warnings due to uninitialised members
(no real problem, since these members get assigned anyway)
- use a lambda as example function right in the test
- use move initialisation and the new util::join
this fixes a silly mistake:
obviously we want named sub-nodes, aka. "Attributes",
but we used the anonymous sub-nodes instead, aka. "Children"
Incidentally, this renders the definitions also way more readable;
in fact the strange post-fix naming notation of the original version
was a clear indication of using the system backwards....
obviously, we get a trivial case, when the path is explicit,
and we need a tricky full blown resolution with backtracking
when forced to interpolate wildcards to cover a given UICoord
spec against the actual UI topology.
Do we need it?
* actually not right now
* but already a complete implementation of the ViewSpec concept
requires such a resolution
...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"
It is not possible to inherit through boost operators
and defining them explicitly is not that much fuss either.
Plus we avoid the boost include on widely used header
the usual drill...
once there is one additional non explicit conversion ctor,
lots of preferred conversion paths are opened under various conditions.
The only remedy is to define all ctors explicitly, instead of letting the
compiler infer them (from the imported base class ctors). Because this way
we're able to indicate a yet-more-preferred initialisation path and thus
prevent the compiler from going the conversion route.
In the actual case, the coordinate Builder is the culprit; obviously
we need smooth implicit conversion from builder expressions, and obviously
we also want to restrict Builder's ctors to be used from UICoord solely.
Unfortunately this misleads the compiler to do implement a simple copy construction
from non const reference by going through the prohibited Builder ctor, or to
instantiate the vararg-ctor inherited from PathArray.
Thus better be explicit and noisy...
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.
Explicitly assuming that those functions are called solely from IterAdapter
and that they are implemented in a typical standard style, we're able to elide
two redundant calls to the checkPoint() function. Since checkPoint typically performs
some non-trivial checks, this has the potential of a significant performance improvement
- we check (and throw ITER_EXHAUST) anyway from operator++, so we know that pos is valid
- the iterate() function ensures checkPoint is invoked right after iterNext,
and thus the typical standard implementation of iterNext need not do the same
...under the assumption that the content is normalised,
which means
- leading NULL is changed to Symbol::EMPTY
- missing elements in the middle are marked as "*"
- trailing NULL in extension storage is handled by adjusting nominal extension size
as it turned out, the solution from yesterday works only with uniform argument lists,
but not with arbitrarily mixed types. Moreover the whole trickery with the
indices was shitty -- better use a predicate decision on template argument level.
This simple solution somehow just didn't occur to me...
exploring the idea of a configuration DSL.
As a first step, this could be a simple internal DSL,
implemented as a bunch of static functor objects, which are internally bound
and thus implemented by the ViewLocator within InteractionDirector
...still with lots of diagnostic messages,
and need to fine tune the balance between generator and consumer,
in order to produce more interesting patterns.
Also need to verfiy the results automatically
Problems while building the test fixture: several, most notably again
the dangers when combining lambdas and multithreading. The most glorious
mistake was to capture the notifyGUI function, which led to locking
a corrupted uiDispatcher queue, causing deadlock.
Problems in the actual test subject: seemingly none.
Message passing and diff application works like a charm!
basically DiffMessage has a "take everything" ctor, which happens
to match on type DiffMessage itslef, since the latter is obviously
a Lumiera Forward Operator. Unfortunately the compiler now considers
this "take everyting" ctor as copy constructor. Worse even, such a
template generated ctor qualifies as "best match".
The result was, when just returing a DiffMessage by value form a
function, this erroneous "copy" operation was invoked, thus wrapping
the existing implementation into a WrappedLumieraIterator.
The only tangible symptom of this unwanted storage bloat was the fact
that our already materialised diagnostics where seemingly "gone". Indee
they weren't gone for real, just covered up under yet another layer of
DiffMessage wrapping another Lumiera Forward Iterator
actually I do not know much regarding the actual situation when,
within the Builder run, we're able to detect a change and generate
a diff description. However, as a first step, I'll pick IterSrouce
as a base interface and use a "generation context", which is to be
passed by shared-ptr
the (trivial) implementation turned out to be correct as written,
but it was (again) damn challenging to get the mulithreaded chaotic
test fixture and especially the lambda captures to work correct.
again surprising how such fundamental bugs can hide for years...
Here the reason is that IterAdapter leaves the representation of "NIL" to
its instantiation / users; some users (here in for example the ScopedCollection)
can choose to allow for different representations of "NIL", but the comparison
provided by IterAdapter just compares the embedded pos by face value.
...while tests in the library subdirectory are linked only against
liblumierasupport, which does not provide the multithreading support
In this special case here the actual facility to be tested does not rely
on thread support, only on locking. But the stress test obviously needs
to create several threads. Simple workaround is to move the test into
a test collection linked against all of the application core...
for the simplistic implementation we're using right now this effort might look
exaggerated, but we should consider using a lock-free implementation at some
point in the future, at which point it is good to have a stress test in place
- concept for a first preliminary implementation of dispatch into the UI thread
- define an integration effort to build a complete working communication chain
after extended analysis, it turned out to be a "placeholder concept"
and introduces an indirection, which can be removed altogether
- simple command invocation happens at gui::model::Tangible
- it is based on the command (definition) ID
- instance management happens automatically and transparently
- the extended case of context-bound commands will be treated later,
and is entirely self-contained
while the initial design treated the commands in a strictly top-down manner,
where the ID is known solely to the CommandRegistry, this change and information
duplication became necessary now, since by default we now always enqueue and
dispatch anonymous clone copies from the original command definition (prototype).
This implementation uses the trick to tag this command-ID when a command-hanlde
is activated, which is also the moment when it is tracked in the registry.
due to the refactorings, the instance was moved out prior to checking for
bound arguments. This is ammended now, albeit at the price of passing an
additional flagn and some tricky boolean conditions
in accordance to the design changes concluded yesterday.
- in the standard cases we now check the global registry first
- automatically create anonymous clone copy from global commands
- reorganise code internally to use common tail implementation
as it turns out, we can always trigger commands right away,
the moment all arguments are known. Thus it is sufficient to
send a single argument binding message, which allows us to
get rid of a lot or ugly complexities (payload visitor).
It seems more adequate to push the somewhat intricate mechanics
for the "fall back" onto generic commands down into the implementation
level of CommandInstanceManager. The point is, we know the standard
usage situation is to rely on the instance manager, and thus we want
to avoid redundant table lookups, only to support the rare case of
fallback to global commands. The latter is currently used only from
unit-tests, but might in future also be used by scripts.
Due to thread safety considerations, I have refrained from handing
out a direct reference to the command token sitting in the registry,
even while not doing so incurs a small runtime penalty (accessing
the shared ref-count for creating a copy of the smart-handle).
This is the typical situation where you'd be tempted to sacrifice
sanity for the sake of an imaginary performance benefit, which
in fact is dwarfed by all the machinery of UI-Bus and argument
passing via GenNode.
Up to now, we tolerated null pointers in Literal instances.
But we can not tolerate passing a null cString to Symbol initialisation.
Rather, hereby we introduce a dedicated "bottom" Symbol, a valid "null object"
...which means, from now on identical input strings
will produce the same Symbol object (embedded pointer).
TODO: does not handle null pointers passed in as c-String properly