diff --git a/admin/scons/Platform.py b/admin/scons/Platform.py index 41d983ab3..8060c4630 100644 --- a/admin/scons/Platform.py +++ b/admin/scons/Platform.py @@ -82,8 +82,6 @@ def configure(env): if not conf.CheckCXXHeader('boost/config.hpp'): problems.append('We need the C++ boost-libraries.') else: - if not conf.CheckCXXHeader('boost/noncopyable.hpp'): - problems.append('We need boost::noncopyable') if not conf.CheckCXXHeader('boost/lexical_cast.hpp'): problems.append('We need boost::lexical_cast') if not conf.CheckCXXHeader('boost/format.hpp'): diff --git a/data/icons/prerendered/16x16/panel-infobox.png b/data/icons/prerendered/16x16/panel-infobox.png new file mode 100644 index 000000000..adb1517f5 Binary files /dev/null and b/data/icons/prerendered/16x16/panel-infobox.png differ diff --git a/doc/design/architecture/Subsystems.txt b/doc/design/architecture/Subsystems.txt new file mode 100644 index 000000000..f57d2d85b --- /dev/null +++ b/doc/design/architecture/Subsystems.txt @@ -0,0 +1,258 @@ +Layers -- Subsystems -- Lifecycle +================================= +:Author: Hermann Voßeler +:Email: +:Date: 2018 +:Toc: + +WARNING: under construction -- [red]#some parts to be filled in# + + +Terminology +----------- + +Layer:: + A conceptual realm within the application to group related concerns and define an ordering. + Layers are above/below each other and may depend _solely_ on lower layers. The Application + may be operated in a partial layer configuration with only some lower layers. Each layer + deals with distinct topics and has its own style. In Lumiera, we distinguish three layers ++ +* User Interface -> Interaction +* Proc-Layer -> Processing +* Backend -> Data manipulation + +Subsystem:: + A runtime entity which acts as anchor point and framework to maintain a well defined lifecycle. + While layers are conceptual realms, subsystems can actually be started and stopped and their + dependencies are represented as data structure. A subsystem typically starts one or several + primary components, which might spawn a dedicated thread and instantiate further components + and services. + +Service:: + A component within some subsystem is termed _Service_ ++ +-- + * when it exposes an interfaces with an associated contract + (informal rules about usage pattern and expectations) + * when it accepts invocations from arbitrary other components + without specific wiring or hard coded knowledge +-- ++ +The service lifecycle is tied to the lifecycle of the related subsystem; whenever the subsystem is ``up and running'', +any contained services can be accessed and used. Within Lumiera, there is no _service broker_ or any similar kind +of elaborate _service discovery_ -- rather, services are accessed *by name*, where ``name'' is the _type name_ +of the service interface. + +Dependency:: + A relation at implementation level and thus a local property of an individual component. A dependency + is something we need in order to complete the task at hand, yet a dependency lies beyond that task and + relates to concerns outside the scope and theme of this actual task. Which means, a dependency is not + introduced by the task or part of the task, rather the task is the reason why some entity dealing with + it needs to _pull_ the dependency, in order to be able to handle the task. So essentially, dependencies + are accessed on-demand. Dependencies might be other components or services, and typically the user + (consumer) of a dependency just relies on the corresponding interface and remains agnostic with respect + to the dependency's actual implementation or lifecycle details. + +Subsystems +---------- +As a coherent part of the application, a subsystem can be started into running state. Several subsystems +can reside within a single layer, which leads to rather tight coupling. We do not define boundaries between +subsystems in a strict way (as we do with layers) -- rather some component is associated with a subsystem +when it relies on services of the subsystem to be ``just available''. However, the grouping into subsystems +is often also a thematic one, and related to a specific abstraction level. To give an example, the Player +deals with _calculation streams,_ while the engine handles individual _render jobs,_ which together form +a calculation stream. So there is a considerable grey area in between. Any code related with defining and +then dispatching frame jobs needs at least some knowledge about the presence of calculation streams; yet +it depends and relies on the scheduling service of the engine. In the end, it remains a question of +architecture to keep those dependency chains ordered in a way to form a one-way route: when we start +the engine, it must not instantiate a component which _requires the player_ in order to be operative. +Yet we can not start the player without having started the engine beforehand; if we do, its services +will throw exceptions due to missing dependencies on first use. + +However, subsystems as such are not dynamically configured. This was a clear cut design decision (and the +result of a heated debate within the Lumiera team at that time). We do _not expect_ to load just some plug-in +dynamically, inserted via an UI-action at runtime, which then installs a new subsystem and hooks it into the +existing architecture. The flexibility lies in what we can do with the _contents_ of the session -- yet the +application itself is not conceived as set of Lego(TM) bricks. Rather, we identify a small number of coherent +parts of the application, each with its own theme, style, relations and contingencies. + +Engine +~~~~~~ +_tbw_ + +Player +~~~~~~ +_tbw_ + +Session +~~~~~~~ +_tbw_ + +User Interface +~~~~~~~~~~~~~~ +_tbw_ + +Script Runner +~~~~~~~~~~~~~ +_tbw_ + +Net Node +~~~~~~~~ +_tbw_ + + +Lifecycle +--------- +Dependencies and abstraction through interfaces are ways to deal with complexity getting out of hand. +When done well, we can avoid adding _accidental complexity_ -- yet essential complexity as such can not +be removed, but with the help of abstractions it can be raised to another level.footnote:[Irony tags here. +There is a lot of hostility towards abstractions, because it is quite natural to conflate the abstraction +with the essential complexity it has to deal with. It seems compelling to kill the abstraction, in the +hope to kill the complexities as well -- an attitude rather effective, in practice...]. +When components express their external needs in the form of dependency on an interface, the immediate tangling +at the code level is resolved, however, someone needs to implement that interface, and this other entity needs +to be _available_. It is now an architecture challenge to get those dependency chains ordered. A way to +circumvent this problem is to rely on a _lifecycle_ with several _phases._ +This is the idea behind the subsystems and the subsystem runner. + +. First we define an ordering between the subsystems. The most basic subsystem (the Engine) is started first. +. Within a subsystem, components may be mutually dependent. However, we establish a new rule, dictating that + during the _startup phase_ only local operations within a single component are allowed. The component need + to be written in a way that it does not need the help of anything ``remote'' in order to get its inner + workings up and ready. The component may rely on its members and on other services it created, _owns and + manages._ And sometimes we do need to rely on a more low-level service in another subsystem or in the + application core.footnote:[A typical example would be the reliance on threading, locking or application + configuration.] -- which then creates a hard dependency on _architecture level_ +. Moreover, we ensure that all operational activity is generated by actual work tasks, and that such tasks + in turn may be initiated _solely through official interfaces._ Such interfaces are to be _opened explicitly_ + at the end of the startup phase. +. In operational mode, any part of the system can now assume for its dependencies to be ``just available''. +. And finally we establish a further rule to _disallow extended clean-up._ Everything must be written in a + way such that when a task is done, it is _really done_ and settled. (Obviously there are some fine points + to consider here, like caching or elaborate buffer and I/O management). The rationale is that after leaving + the operational phase at the end of `main()` the application is able to unwind in any arbitrary way. + +The problem with emergencies +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +This concept has a weak spot however: A catastrophic failure might cause any subsystem to break down immediately. +The handler within the subsystem's primary component will hopefully detect the corresponding exception and signal +emergency to the subsystem runner. However, the working services of that subsystem are already gone at that point. +And even while other subsystems get the (emergency) shutdown trigger, some working parts may be already failing +catastrophically due to their dependencies being dragged away suddenly. + +Lumiera is not written for exceptional resilience or high availability. Our attitude towards such failures can +be summarised as ``Let it crash''. And this is another rationale for the ruling against extended clean-up. +Any valuable work done by the user should be accepted and recorded persistently right away. Actions on the +session are logged, like in a database. The user may still save snapshots, but basically any actual change +is immediately recorded persistently. And thus we may crash without remorse. + +Static initialisation and shutdown +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +A lot of fine points can be made about when precisely static objects in C\++ will be initialised or destroyed. +However, anything beyond the scope of `main()` is not meant to be used for regular application code. Extended +initialisation, dependency management and decommissioning -- when actually necessary -- should be part of the +application code proper.footnote:[this is established ``best practice'' for good reasons. The interplay of +static lifespan, various translation units and even dynamically loaded libraries together with shared access +becomes intricate and insidious quite easily. And since in theory any static function could use some static +variable residing in another translation unit, it is always possible to construct a situation where objects +are accessed after being destroyed. Typically such objects do not even look especially ``dead'', since the +static storage remains in place and still holds possibly sane values. Static (global) variables, like raw +pointers, allow to subvert the deterministic automatic memory management, which otherwise is one of the +greatest strengths of C++. Whenever we find ourselves developing extended collaborative logic based on +several statics, we should consider to transform this logic into regular objects, which are easier to +test and better to reason about. If it really can not be avoided to use such units of logic from a +static context, it should at least be packaged as a single object, plus we should ensure this logic +can only be accessed through a regular (non static) object as front-end. Packaged this way, the +most common and dangerous pitfalls with statics can be avoided.] And since Lumiera indeed allows +for link:{ldoc}/technical/library/Dependencies.html[[lazily initialised dependencies], we +establish the policy that *destructors must not rely on dependencies*. In fact, they should +not do any tangible work at all, beyond releasing other resources. + +Lifecycle Events +~~~~~~~~~~~~~~~~ +The Application as a whole conducts a well defined lifecycle; whenever transitioning to the next phase, +a _Lifecycle Event_ is issued. Components may register a notification hook with the central _Lifecycle Manager_ +(see 'include/lifecycle.h') to be invoked whenever a specific event is emitted. The process of registration +can be simplified by planting a static variable of type `lumiera::LifecycleHook`. + +WARNING: A callback enrolled this way needs to be callable at the respective point in the lifecycle, + otherwise the application will crash. + +`ON_BASIC_INIT`:: + Invoked as early as possible, somewhere in the static initialisation phase prior to entering `main()`. + In order to install a callback hook for this event, the client must plant a static variable somewhere. + +`AppState`:: + This is the Lumiera »Application Object«. It is a singleton, and should be used by `main()` solely. + While not a lifecycle event in itself, it serves to bring up some very fundamental application services: ++ +-- +- the plug-in loader +- the application configuration +-- ++ +After starting those services within the `AppState::init()` function, +the event `ON_GLOBAL_INIT` is emitted. + +`ON_GLOBAL_INIT`:: + When this event occurs, the start-up phase of the application has commenced. The command line was already + parsed and the basic application configuration is loaded, but the subsystems are not yet initialised. + +`Subsys::start()`:: + By evaluation of the command line, the application object determines what subsystems actually need to + be started; those will receive the `start()` call, prompting them to enter their startup phase, to + instantiate all service objects and open their business façade when ready + +`ON_SESSION_START`:: + When this hook is activated, the session implementation facilities are available and the corresponding + interfaces are already opened and accessible, but the session itself is completely pristine and empty. + Basic setup of the session can be performed at that point. Afterwards, the session contents will be + populated. + +`ON_SESSION_INIT`:: + At this point, all specific session content and configuration has already be loaded. Any subsystems + in need to build some indices or to establish additional wiring to keep track of the session's content + should register here. + +`ON_SESSION_READY`:: + Lifecycle hook to perform post loading tasks, which require an already completely usable and configured + session to be in place. When activated, the session is completely restored according to the defaulted or + persisted definition, and any access interfaces are already opened and enabled. Scripts and the GUI might + even be accessing the session in parallel already. Subsystems intending to perform additional processing + should register here, when requiring fully functional client side APIs. Examples would be statistics gathering, + validation or auto-correction of the session's contents. + +`ON_SESSION_CLOSE`:: + This event indicates to commence any activity relying on an opened and fully operative session. + When invoked, the session is still in fully operative state, all interfaces are open and the render engine + is available. However, after issuing this event, the session shutdown sequence will be initiated, by detaching + the engine interfaces and signalling the scheduler to cease running render jobs. + +`ON_SESSION_END`:: + This is the point to perform any state saving, deregistration or de-activation necessary before + an existing session can be brought down. When invoked, the session is still fully valid and functional, + but the GUI/external access has already been closed. Rendering tasks might be running beyond this point, + since the low-level session data is maintained by reference count. + +`Subsys::triggerShutdown()`:: + While not a clear cut lifecycle event, this call prompts any subsystem to close external interfaces + and cease any activity. Especially the GUI will signal the UI toolkit set to end the event loop and + then to destroy all windows and widgets. + +`ON_GLOBAL_SHUTDOWN`:: + Issued when the control flow is about to leave `main()` regularly to proceed into the shutdown and + unwinding phase. All subsystems have already signalled termination at that point. So this is the right + point to perform any non-trivial clean-up, since, on a language level, all service objects (especially + the singletons) are still alive, but all actual application activity has ceased. + +`ON_EMERGENCY_EXIT`:: + As notification of emergency shutdown, this event is issued _instead of_ `ON_GLOBAL_SHUTDOWN`, whenever + some subsystem collapsed irregularly with a top-level exception. + +NOTE: all lifecycle hooks installed on those events are _blocking_. This is intentionally so, since any + lifecycle event is a breaking point, after which some assumptions can or can not be made further on. + However, care should be taken not to block unconditionally from within such a callback, since this + would freeze the whole application. Moreover, implementers should be careful not to make too much + assumptions regarding the actual thread of invocation; we only affirm that it will be _that specific_ + thread responsible for bringing the global lifecycle ahead at this point. + diff --git a/doc/design/architecture/index.txt b/doc/design/architecture/index.txt index b061aa74c..83dadaa02 100644 --- a/doc/design/architecture/index.txt +++ b/doc/design/architecture/index.txt @@ -1,6 +1,12 @@ Design Documents: Lumiera Architecture ====================================== +// Menu : sort children +// Menu : prepend child Subsystems +// Menu : prepend child ArchitectureSummary + +// Menu : attach child TimeQuant after TimeUsage + This section will feature some rather high-level considerations regarding the architecture of the application. diff --git a/doc/design/gui/InteractionControl.txt b/doc/design/gui/InteractionControl.txt new file mode 100644 index 000000000..e0123aca4 --- /dev/null +++ b/doc/design/gui/InteractionControl.txt @@ -0,0 +1,137 @@ +Interaction Control +=================== +:Author: Hermann Voßeler +:Email: +:Date: Summer 2017 + +This overarching topic is where the arrangement of our interface components meets considerations about +https://en.wikipedia.org/wiki/Interaction_design[Interaction Design]. The interface programming allows us +to react on events and trigger behaviour, and it allows us to arrange building blocks within a layout framework. +And beyond that, there needs to be some kind of coherency in the way matters are arranged -- this is the realm +of conventions and guidelines. Yet in any more than trivial UI application, there is an intermediate and implicit +level of understanding, where things just ``happen to happen'', which can not fully be derived from first principles. + +It is fine to have a convention to put the »OK« button right -- but how do we get at trimming a clip? +How are we assumed to get at trimming a clip? if we work with the mouse? or the keyboard? or with a pen? +or with a hardware controller we don't even know yet? We could deal with such on a case-by-case base +(as the so called reasonable people do) or we could aim at an abstract intermediary space, with the ability +to assimilate the practical situation yet to come. + +interface has a spatial quality:: + the elements within an user interface are arranged in a way that parallels our experience when working + in real world space. With the addition of some minor dose of __ ``hyper'' __ -- allowing for cross connections + and shortcuts beyond spatial logic... + +locality of work spaces:: + but the arrangement of the interface interactions is not amorphous, + rather it is segregated into cohesive clusters of closely interrelated actions. + We move between these clusters of activity the same way as we move between several + clearly segregated rooms confined within a building. + +context and focus of activity:: + most of what we could do _in theory,_ is not relevant _right now_ most of the time. + Yet only when the inner logic of what we're about to do coincides with the things at hand _right now,_ + only then we might feel _enabled_ to perform our work. + +shift of perspective:: + and while we work, the focus moves along. + + Some things are closer, other things are remote and require us to move and re-orient and reshape our perspective, + should we choose to turn towards them. + +the ability to arrange what is relevant:: + all day long, we do the same stuff again and again, and this makes us observe and gradually understand matters. + As we reveal the inner nature of what we're doing, we desire to arrange _close at hand_ what belongs together, + and to expunge the superficial and distracting. + + +Foundation Concepts +------------------- + + +The primary insight is _that we build upon a spatial metaphor_ -- +and thus we start out by defining various kinds of _locations._ +We express interactions as _happening somewhere..._ + +Work Site:: + a distinct, coherent place where some ongoing work is done + + the [blue]#Work Site# might move along with the work, but we also may leave it temporarily + to visit some other [blue]#Work Site# + +the Spot:: + the [blue]#Spot# is _where we currently are_ -- taken both in the sense of a location and a spotlight. + Thus a [blue]#Spot# is potentially at some [blue]#Work Site#, but it can be navigated to another one + +Focus:: + the concrete realisation of the [blue]#Spot# within a given [blue]#Control System# + +Control System:: + a practical technical realisation of an human-computer-interface, + like keyboard input/navigation, mouse, pen, hardware controller, touch + +Focus Goal:: + an _order_ or _instruction_ to bring something _into focus,_ + which also means to move the [blue]#Spot# to the designated location. + +UI Frame:: + the overall interface is arranged into independent top-level segments of equal importance. + Practically speaking, we may have multiple top-level windows residing on multiple desktops... + +Perspective:: + a set of concrete configuration parameters defining the contents and arrangement within one [blue]#UI Frame#. + The [blue]#Perspective# defines which views are opened and arranged at what position and within which docking panel + +Focus Path:: + concrete coordinates to reach a specific [blue]#Work Site#. + + The [blue]#Focus Path# specifies the [blue]#UI Frame# (top-level window), the perspective, and then some canonical + path to navigate down a component hierarchy in order to reach the anchor point of the new [blue]#Work Site# + +the Spot Locator:: + navigating means to move the [blue]#Spot Locator#, in order to move the spot from [blue]#Work Site# to + [blue]#Work Site#. + The [blue]#Spot Locator# is relocated by loading a new [blue]#Focus Path# leading to another [blue]#Work Site# + +The concept of a __[blue]#Focus Goal#__ has several ramifications: +For one it implies that there is something akin the __ ``current Control System'',__ which also could be the +_currently active Control System(s)._ Simply because Focus, as the realisation of the _abstract notion_ of the Spot, +is always tied to a Control System able to implement it. And when we're able to define generic location coordinates +and then __ ``move there'',__ with the help of the `SpotLocator`, we draw the conclusion that there must be a Focus +(implementation), somehow getting shifted towards that location. +Like e.g. the desired entity to gain the keyboard focus. +And, beyond that, the second thing we may conclude is that there need to be some degree of leeway in the way +such a Focus Goal can be reached. Since the inner logic of Control Systems can be quite drastically different +from each other, we are well advised to leave it to the actual Control System how _actually to fulfil the focus goal._ + +To point out an obvious example: it is not a good idea to move the mouse pointer forcibly onto a screen element. +Rather, we must use the established mechanisms of switching, scrolling and unfolding to bring the desired +target element into the visible area, leaving the last step to the user, which must actively move the mouse +onto the target. And we must give good visual clues as to what happened, and what we expect from the user +(namely to direct her attention onto the element brought into focus). + + +Building the framework +---------------------- + +To create such a system is an ambitious goal for sure. +We can not reach it in a single step, since it entails the formation of a whole intermediary layer, +on top of the _usual UI mechanics,_ yet below the concrete UI interactions. +Especially, we'd need to clarify the meaning of _Perspective,_ we need to decide on the relation of top level frame, +individual view, layout, focus and _current location within the UI._ +On a second thought, building such a system implies we'll have to live with an intermediary state of evolution, +where parts of the new framework are already in place without interfering with common conventional usage +of the interface as-is. + +UI coordinates +~~~~~~~~~~~~~~ + +Especially the focus navigation entails the use of some kind of ubiquitous coordinate system within the user interface. +In fact this is more of a topological navigation, since these coordinates describe the decisions and forks taken on +navigation down the _Focus Path._ + +* [optional] top-level Window (UI frame) +* [optional] Perspective +* Panel +* View-ID +* [optional] Group or Tab +* local path: `component.component.component`... + + diff --git a/doc/technical/build/Dependencies.txt b/doc/technical/build/Dependencies.txt index 0dc06f3e2..29b6b16c1 100644 --- a/doc/technical/build/Dependencies.txt +++ b/doc/technical/build/Dependencies.txt @@ -58,7 +58,8 @@ Languages and Tools occasionally we check using Clang] - GCC *4.9* or Clang *3.5* should be fine footnote:[basically we try to use just the stock language. On rare occasions in the past, we _did_ use some GCC extensions, like `typeof()`, but we care for - workarounds, in case this becomes a problem]. + workarounds, in case this becomes a problem. Incidentally, `typeof()` is obsoleted by the new + C++ standard, which provides `decltype()` for this purpose.]. * BOOST (listed below are the Debian package names) - libboost-dev (at least *1.55*) diff --git a/doc/technical/code/c++11.txt b/doc/technical/code/c++11.txt index e504b51c4..dad754c0e 100644 --- a/doc/technical/code/c++11.txt +++ b/doc/technical/code/c++11.txt @@ -163,7 +163,11 @@ of arbitrary type to the constructor of type `TY`. Note the following details that a rvalue reference can only bind to _something unnamed_. This is a safety feature; moving destroys the contents at the source location. Thus if we really want to move something known by name (like e.g. the function arguments in this example), we need to indicate so by an explicit call. -- `std::forward` needs an explicit type parameter to be able to deduce the right target type in any case +- `std::forward` needs an explicit type parameter to be able to deduce the right target type in any case. + It is _crucial to pass this type parameter in the correct way,_ as demonstrated above. Because, if we + invoke this function for _copying_ (i.e. with a lvalue reference), this type parameter will _carry on_ + this refrerence. Only a rvalue reference is stripped. This is the only way ``downstream'' receivers can + tell the copy or move case apart. - note how we're allowed to ``wrap'' a function call around the unpacking of the _argument pack_ (`args`): the three dots `...` are _outside_ the parenthesis, and thus the `std::forward` is applied to each of the arguments individually @@ -221,3 +225,70 @@ Thus -- as of 9/2014 -- the _rules of the game_ are as folows to variadic templates and `std::tuple` + +New Capabilities +---------------- +As the support for the new language standard matures, we're able to extend our capabilities to write code +more succinctly and clearer to read. Sometimes we have to be careful in order to use these new powers the right way... + +Variadic Templates +~~~~~~~~~~~~~~~~~~ +Programming with variadic template arguments can be surprisingly tricky and the resulting code design tends to be confusing. +This is caused by the fact that a _»variadic argument pack«_ is not a type proper. Rather, it is a syntactic (not lexical) +macro. It is thus not possible to ``grab'' the type from variadic arguments and pass it directly to some metaprogramming +helper or metafunction. The only way to dissect and evaluate such types is kind of _going backwards_: pass them to some +helper function or template with explicit specialisations, thereby performing a pattern match. + +A typical example: Let's assume we're about to write a function or constructor to accept flexible arguments, but we +want to ensure those arguments to conform to some standards. Maybe we can handle only a small selection of types +sensibly within the given context -- and thus we want to check and produce a compilation failure otherwise. + +Unfortunately, this is not possible, at least not directly. If we want to get at the argument types one by one, we need +to fabricate some variadic template helper type, and then add an explicit specialisation to it, for the sole purpose of +writing a pattern match ``. Another common technique is to fabricate a `std::tuple` and then +to use the `std::get (std::tuple)` to grab individual types. Such might be bewildering for the casual +reader, since we never intend to create a storage tuple anywhere. + +NOTE: we might consider to reshape our typelist to be a real `lib::meta::Types` and outfit it with some + of the most frequently needed accessors. At least the name ``Types'' is somewhat more indicative than ``tuple''. + + +Type inference +~~~~~~~~~~~~~~ +Generally speaking, the use of `auto` and type inference has the potential to significantly improve readability of +the code. Indeed, this new language feature is what removed most of the clumsiness regarding use of the Lumiera +Forward Iterator concept. There are some notable pitfalls to consider, though + +- the `decltype(...)` operator is kind-of ``overloaded'', insofar it supports two distinct usages + + * to find out the type of a given _name_ + * to deduce the type of a given _expression_ + +- and the `auto` type inference does not just pick up what `decltype` would see -- rather, it applies its own + preferences and transformations (which does make much sense from a pragmatic perspective) + + * a definition with `auto` type specification always prefers to create an object or data element. This is very much + in contrast to the usual behaviour of the C++ language, which by default prefers a reference as result type of + expression evaluation. + * return type deduction always _decays_ the inferred type (similar as `std::decay::type` does). Which means, + functions and arrays become pointers, and a _reference_ becomes a _value_. Given the danger of returning a + dangling reference to local storage, the latter is a sensible default, but certainly not what you'd expect + when just passing the result from a delegate function (or lambda!). + + +Generic Lambdas +~~~~~~~~~~~~~~~ +A generic lambda (i.e. using `auto` on some argument) produces _a template_, not a regular `operator()` function. +Whenever such a lambda is invoked, this template is instantiated. This has several ramifications. For one, the +notorious techniques for detecting a function work well on conventional lambdas, but _fail on generic lambdas_. +And it is not possible to ``probe'' a template with SFINAE: If you instantiate a template, and the result is not +valid, you've produced a _compilation failure_, not a _substitution failure._ Which means, the compilation aborts, +instead of just trying the next argument substitution (as you'd might expect). Another consequence is that you +can not _just accept a generic lambda as argument and put it into a `std::function` (which otherwise is the +idiomatic way of ``just accepting anything function-like''). In order to place a generic lambda into a `std::function`, +you must decide upon the concrete argument and return type(s) and thus instantiate the template at that point. + +The only remedy to get around these serious limitations will be when C++ starts to support *Concepts* -- which +can be expected to replace the usage of unbounded type inference in many situations: for example, it would then +be possible to accept just ``an Iterator'' or ``a STL container''... + diff --git a/doc/technical/howto/HashFunctions.txt b/doc/technical/howto/HashFunctions.txt index b30e23989..d7a848880 100644 --- a/doc/technical/howto/HashFunctions.txt +++ b/doc/technical/howto/HashFunctions.txt @@ -48,7 +48,7 @@ The Boost library *functional-hash* provided the foundation for the definition n into the new C++ standard. Yet the boost library provides some additional facilities not part of the standard. Thus we're bound to choose -* either including ++ and +using std::tr1::hash+ +* either including ++ and +using std::hash+ * or including ++ and +using boost::hash+ The boost version additionally provides pre defined hash functors for STL containers holding diff --git a/doc/technical/howto/UsingBoost.txt b/doc/technical/howto/UsingBoost.txt index 23c8de128..3c5e70ec5 100644 --- a/doc/technical/howto/UsingBoost.txt +++ b/doc/technical/howto/UsingBoost.txt @@ -54,41 +54,6 @@ source code comment near the static assertion statement to help solving the actu Relevant Bosst extensions ~~~~~~~~~~~~~~~~~~~~~~~~~ -.noncopyable -Inheriting from `boost::noncoypable` inhibits any copy, assignment and copy construction. It's a highly -recommended practice _by default to use that for every new class you create_ -- unless you know for sure -your class is going to have _value semantics_. The C++ language has kind of a ``fixation'' on value -semantics, passing objects by value, and the language adds a lot of magic on that behalf. Which might lead -to surprising results if you aren't aware of the fine details. - -.type traits -Boost provides a very elaborate collection of type trait templates, allowing to ``detect'' several -properties of a given type at compile time. Since C++ has no reflection and only a very weak introspection -feature (RTTI, run time type information), using these type traits is often indispensable. - -.enable_if -a simple but ingenious metaprogramming trick, allowing to control precisely in which cases the compiler -shall pick a specific class or function template specialisation. Basically this allows to control the -code generation, based on some type traits or other (metaprogramming) predicates you provide. Again, -since C++ is lacking introspection features, we're frequently forced to resort to metaprogramming -techniques, i.e to influence the way the compiler translates our code from within that very code. - -.metaprogramming library -A very elaborate, and sometimes mind-bending library and framework. While heavily used within -Boost to build the more advanced features, it seems too complicated and esoteric for general purpose -and everyday use. Code written using the MPL tends to be very abstract and almost unreadable for -people without math background. In Lumiera, we _try to avoid using MPL directly._ Instead, we -supplement some metaprogramming helpers (type lists and tuples), written in a simple LISP style, -which -- hopefully -- should be decipherable without having to learn an abstract academic -terminology and framework. - -.lambda -In a similar vein, the `boost::lambda` library might be worth exploring a bit, yet doesn't add -much value in practical use. It is stunning to see how this library adds the capability to define -real _lambda expressions_ on-the-fly, but since C++ was never designed for language extensions of -that kind, using lambda in practice is surprisingly tricky, sometimes even obscure and rarely -not worth the hassle. (An notable exception might be when it comes to defining parser combinators) - .operators The `boost::operators` library allows to build families of types/objects with consistent algebraic properties. Especially, it eases building _equality comparable_, _totally ordered_, @@ -109,6 +74,15 @@ quite some compilation overhead and size impact (-> see our own link:http://git.lumiera.org/gitweb?p=lumiera/ichthyo;a=blob;f=src/lib/format-string.hpp;h=716aa0e3d23f09269973b7659910d74b3ee334ea;hb=37384f1b681f5bbfa7dc4d50b8588ed801fbddb3[formatting front-end] to reduce this overhead) +.metaprogramming library +A very elaborate, and sometimes mind-bending library and framework. While heavily used within +Boost to build the more advanced features, it seems too complicated and esoteric for general purpose +and everyday use. Code written using the MPL tends to be very abstract and almost unreadable for +people without math background. In Lumiera, we _try to avoid using MPL directly._ Instead, we +supplement some metaprogramming helpers (type lists and tuples), written in a simple LISP style, +which -- hopefully -- should be decipherable without having to learn an abstract academic +terminology and framework. + .variant and any These library provide a nice option for building data structures able to hold a mixture of multiple types, especially types not directly related to each other. `boost::variant` is a diff --git a/doc/technical/library/Dependencies.txt b/doc/technical/library/Dependencies.txt new file mode 100644 index 000000000..6379c450e --- /dev/null +++ b/doc/technical/library/Dependencies.txt @@ -0,0 +1,226 @@ +Singletons and Dependency Handling +================================== +:Date: 2018 +:Toc: + +We encounter _dependencies as an issue at implementation level:_ In order to deal with some task at hand, +sometimes we need to arrange matters way beyond the scope of that task. We could just thoughtlessly reach out and +settle those extraneous concerns -- yet this kind of pragmatism has a price tag: we are now mutually dependent +with internals of some other part of the system we do not even care much about. A more prudent choice would be +to let ``that other part'' provide a service for us, focussed to what we actually need right here to get _our_ +work done. In essence, we create a dependency to resolve issues of coupling and to reduce complexity +(»divide et impera«). + +Unfortunately this solution created a new problem: how do we get at our dependencies? We can not just step ahead +and create them or manage them, because then we'd be ``back to square one''. Rather someone else has to care. +Someone _needs to connect us with those dependencies,_ so we can use them. This is a special meta-service known +as _Dependency Injection_. A dedicated part of the application wires all other components, so each component +can focus on its specific concern and abstract away everything else. Dependency Injection can be seen as +application of the principle »Inversion Of Control«: each part is sovereign within its own realm, but becomes +a client (asks for help) for anything beyond that. + +However, in the Lumiera code base, we refrain from building or using a full-blown Dependency Injection Container. +A lot of FUD has been spread regarding Dependency Injection and Singletons, to the point that a majority of developers +confuses and conflates the Inversion-of-Control principle (which is essential) with the use of a DI-Container. Nowadays, +you can not even utter the word ``Singleton'' without everyone yelling out ``Evil! Evil!'' -- while most of these people +at the same time feel just comfortable living in the metadata hell. + +Not Singletons as such are problematic -- rather, the _coupling_ of the Singleton class _itself_ with the instantiation +and lifecycle mechanism is what creates the problems. This situation is similar to the use of _global variables,_ which +likewise are not evil as such; the problems arise from an imperative, operation driven and data centric mindset, +combined with hostility towards any abstraction. In C++ such problems can be mitigated by use of a generic +_Singleton Factory_ -- which can be augmented into a _Dependency Factory_ for those rare cases where we actually need +more instance and lifecycle management beyond lazy initialisation. Client code indicates the dependence on some other +service by planting an instance of that Dependency Factory (for Lumiera this is `lib::Depend`) and remains unaware +if the instance is created lazily in singleton style (which is the default) or has been reconfigured to expose +a service instance explicitly created by some subsystem lifecycle. The __essence of a ``dependency'' __ of this kind is +that we **access a service _by name_**. And this service name or service ID is in our case a _type name._ + + +Requirements +------------ +Our *DependencyFactory* satisfies the following requirements + +- client code is able to access some service _by-name_ -- where the name is actually + the _type name_ of the service interface. +- client code remains agnostic with regard to the lifecycle or backing context of the service it relies on. +- in the simplest (and most prominent case), _nothing_ has to be done at all by anyone to manage that lifecycle. + + By default, the Dependency Factory creates a *singleton* instance lazily (heap allocated) on demand and it ensures + thread-safe initialisation and access. +- we establish a policy to *disallow any significant functionality during application shutdown*. + After leaving `main()`, only trivial dtors are invoked and possibly a few resource handles are dropped. + No filesystem writes, no clean-up and reorganisation, not even any logging is allowed. For this reason, + we established a link:{ldoc}/design/architecture/Subsystems.html[Subsystem] concept with explicit shutdown hooks, + which are invoked beforehand. +- the Dependency Factory can be re-configured for individual services (type names) to refer to an explicitly installed + service instance. In those cases, access while the service is not available will raise an exception. + There is a simple one-shot mechanism to reconfigure Dependency Factory and create a link to an actual + service implementation, including automatic deregistration. + + +Configuration +~~~~~~~~~~~~~ +The DependencyFactory and thus the behaviour of dependency injection can be reconfigured, ad hoc, at runtime. + +Deliberately, we do not enforce global consistency statically (since that would lead to one central static configuration). +However, a runtime sanity check is performed to ensure configuration actually happens prior to any use, which means any +invocation to retrieve (and thus lazily create) the service instance. The following flavours can be configured: + +default:: + a singleton instance of the designated type is created lazily, on first access + + - define an instance for access (preferably static): `Depend theBla;` + - access the singleton instance as `theBla().doIt()` + +singleton subclass:: + `DependInject::useSingleton();` + + causes the dependency factory `Depend` to create a `SubBlah` singleton instance from now on + +attach to service:: + `DependInject::ServiceInstance service{p1, p2, p3};` + + - build and manage an instance of `SubBlah` in heap memory immediately (not lazily) + - configure the dependency factory to return a reference _to this instance_ + - the instantiated `ServiceInstance` object itself acts as lifecycle handle (and managing smart-ptr) + - when it is destroyed, the dependency factory is automatically cleared, and further access will trigger an error + +support for test mocking:: + `DependInject::Local mock;` + + + - temporarily shadows whatever configuration resides within the dependency factory + - the next access will create a (non singleton) `SubBlah` instance in heap memory and return a `Blah&` + - the instantiated mock handle object again acts as lifecycle handle and smart-ptr + to access the `SubBlah` instance like `mock->doItSpecial()` + - when this handle goes out of scope, the original configuration of the dependency factory is restored + +custom constructors:: + both the subclass singleton configuration and the test mock support optionally accept a functor + or lambda argument with signature `SubBlah*()`. The contract is for this construction functor + to return a heap allocated object, which will be owned and managed by the DependencyFactory. + Especially this enables use of subclasses with non default ctor and / or binding to some + additional hidden context. Please note _that this closure will be invoked later, on-demand._ + +We consider the usage pattern of dependencies a question of architecture rather -- +such can not be solved by any mechanism at implementation level. For this reason, +Lumiera's Dependency Factory prevents reconfiguration after use, but does nothing beyond such basic sanity checks. + + +Performance considerations +~~~~~~~~~~~~~~~~~~~~~~~~~~ +We acknowledge that such a dependency or service will be accessed frequently and even from rather performance critical +parts of the application. We have to optimise for low overhead on access, while initialisation happens only once and +can be arbitrarily expensive. It is more important that configuration, setup and initialisation code remains readable. +And it is important to place such configuration at a location within the code where the related concerns are treated -- +which is not at the usage site, and which is likewise not within some global central core application setup. At which +point precisely initialisation happens is a question of architecture -- lazy initialisation can be used to avoid +expensive setup of rarely used services, or it can be employed to simplify the bootstrap of complex subsystems, +or to break service dependency cycles. All of this builds on the assumption that the global application structure +is fixed and finite and well-known -- we assume we are in full control about when and how parts of the application +start and stop. + +Our requirements on (optional) reconfigurability have some impact on the implementation technique though, +since we need access to the instance pointer for individual service types. This basically rules out +_Meyers Singleton_ -- and so the adequate implementation technique for our usage pattern is _Double Checked Locking._ +In the past, there was much debate about DCL being +link:http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html[broken] -- which indeed was true when +_assuming full portability and arbitrary target platform._ Since our focus is primarily on PC-with-Linux systems, +this argument seems to lean more to the theoretical side though, since the x86/64 platform is known to employ rather +strong memory and cache coherency constraints. With the recent advent of ARM systems, the situation has changed however. +Anyway, since C++11 there +link:http://preshing.com/20130930/double-checked-locking-is-fixed-in-cpp11[is now a portable solution available] +for writing a correct DCL implementation, based on `std::atomic`. + +The idea underlying Double Checked Locking is to optimise for the access path, which is achieved by moving the +expensive locking entirely out of that path. However, any kind of concurrent consistency assertion requires us +to establish a »happens before« relation between two events of information exchange. Both traditional locking +and lock-free concurrency implement this relation by establishing a *synchronises-with* relation between two actions +on a common *guard* entity -- for traditional locking, this would be a Lock, Mutex, Monitor or Semaphore, while +lock-free concurrency uses the notion of a _fence_ connected with some well defined action on a userspace guard variable. +In modern C++, typically we use _Atomic variables_ as guard. In addition to well defined semantics regarding concurrent +visibility of changes, these link:http://en.cppreference.com/w/cpp/atomic["atomics"] offer indivisible access and +exchange operations. A correct concurrent interaction must involve some kind of well defined handshake to establish +the aforementioned _synchronises-with_ relation -- otherwise we just can not assume anything. Herein lies the problem +with Double Checked Locking: when we move all concurrency precautions away from the optimised access path, we get +performance close to a direct local memory access, but we can not give any correctness assertions in this setup. +If we are lucky (and the underlying hardware does much to yield predictable behaviour), everything works as expected, +but we can never be sure about that. A correct solution thus inevitably needs to take away some of the performance +from the optimised access path. Fortunately, with properly used atomics this price tag is known to be low. +At the end of the day, correctness is more important than some superficially performance boost. + +To gain insight into the rough proportions of performance impact, in 2018 we conducted some micro benchmarks +(using a 8 core AMD FX-8350 64bit CPU running Debian/Jessie and GCC 4.9 compiler) +The following table lists averaged results _in relative numbers,_ +in relation to a single threaded optimised direct non virtual member function invocation (≈ 0.3ns) + +[width="80%",cols="4e,4*>",frame="topbot",options="header"] +|========================== +| Access Technique 2+^| development 2+^| optimised +||[small]#singlethreaded# +|[small]#multithreaded# +|[small]#singlethreaded# +|[small]#multithreaded# +|direct invoke on shared local object | 15.13| 16.30| *1.00*| 1.59 +|invoke existing object through unique_ptr | 60.76| 63.20| 1.20| 1.64 +|lazy init unprotected (not threadsafe) | 27.29| 26.57| 2.37| 3.58 +|lazy init always mutex protected | 179.62| 10917.18| 86.40| 6661.23 +|Double Checked Locking with mutex | 27.37| 26.27| 2.04| 3.26 +|DCL with std::atomic and mutex for init | 44.06| 52.27| 2.79| 4.04 +|========================== + +These benchmarks used a dummy service class holding a `volatile int`, initialised to a random value. +The complete code was visible to the compiler and thus eligible for inlining. Repeatedly the benchmarked code +accessed this dummy object through the means listed in the table, then retrieved the (actually constant) value +from the private volatile variable within the service and compared it to zero. +This setup ensures the optimiser can not remove the code altogether, while the access to the service dominates +the measured time. The concurrent measurement used 8 threads (number of cores), each performing the same timing loop +on a local instance. The number of invocations within each thread was high enough (several millions) to amortise +the actual costs of object allocation. +Some observations: + +- The numbers obtained pretty much confirm + link:http://www.modernescpp.com/index.php/thread-safe-initialization-of-a-singleton[other people's measurments]. +- Synchronisation is indeed necessary; + the unprotected lazy init crashed several times randomly during multithreaded tests. +- Contention on concurrent access is very tangible; + even for unguarded access the cache and memory hardware has to perform additional work +- However, the concurrency situation in this example is rather extreme and deliberately provokes collisions; + in practice we'd be closer to the single threaded case +- Double Checked Locking is a very effective implementation strategy and results in timings + within the same order of magnitude as direct unprotected access +- Unprotected lazy initialisation performs spurious duplicate initialisations, which can be avoided by DCL +- Naïve Mutex locking is slow even with non-recursive Mutex without contention +- Optimisation achieves access times around ≈ 1ns + + + +Architecture +------------ +Dependency management does not define the architecture, nor can it solve architecture problems. +Rather, its purpose is to _enact_ the architecture. A dependency is something we need in order to perform +the task at hand, yet essence of a dependency lies outside the scope and relates to concerns beyond and theme +of this actual task. A naïve functional approach -- pass everything you need as argument -- would be as harmful +as thoughtlessly manipulating some off-site data to fit current needs. The local function would be splendid, +strict and referentially transparent -- yet anyone using it would be infected with issues of tangling and +tight coupling. As remedy, a _global context_ can be introduced, which works well as long as this global +context does not exhibit any other state than ``being available''. The root of those problems however +lies in the drive to conceive matters simpler as they are. + +- collaboration typically leads to indirect mutual dependency. + We can only define precisely _what is required locally,_ and then _pull our requirements_ on demand. +- a given local action can be part of a process, or a conversation or interaction chain, which in turn + might originate from various, quite distinct contexts. At _that level,_ we might find a simpler structure + to hinge questions of lifecycle on. + +In Lumiera we encounter both these kinds of circumstances. On a global level, we have a simple and well defined +order of dependencies, cast into link:{ldoc}/design/architecture/Subsystems.html[Subsystem relations]. +We know e.g. that mutating changes to the session can originate from scripts or from UI interactions. +It suffices thus, when the _leading subsystem_ (the UI or the script runner) refrains from emitting any further +external activities, _prior_ to reaching that point in the lifecycle where everything is ``basically set''. +Yet however self evident this insight might be, it yields some unsettling and challenging consequences: +The UI _must not assume_ the presence of specific data structures within the lower layers, nor is it allowed to +``pull'' session contents as a dependency while starting up. Rather the UI-Layer is bound to bootstrap itself into +completely usable and operative state, without the ability to attach anything onto existing tangible content structures. +This runs completely counter common practice of UI programming, where it is customary to wire most of the +application internals somehow directly below the UI ``shell''. Rather, in Lumiera the UI must be conceived +as a _collection of services_ -- and when running, a _population request_ can be issued to fill the prepared +UI framework with content. This is Inversion-of-Control at work. + diff --git a/doc/technical/overview.txt b/doc/technical/overview.txt index 5ad3d6c23..ac3f5b21c 100644 --- a/doc/technical/overview.txt +++ b/doc/technical/overview.txt @@ -401,11 +401,39 @@ Common Services Subsystem runner ~~~~~~~~~~~~~~~~ -_tbw_ +Conceptually, the application is structured into layers. However, as actual runtime +entities we rely on a small number of link:{ldoc}/design/architecture/Subsystems.html[Subsystems], +which can be started and stopped and have well defined dependencies: + +- Engine +- Session and Builder +- Play and Render Subsystem +- Lumiera GUI +- Script runner [yellow-background small]#planned# +- Renderfarm node [yellow-background small]#planned# + +Lumiera's `main()` function defines an _Application Object_, which in turn operates +the _Subsystem Runner_. Each Subsystem implements a descriptor with the necessary +hooks callbacks to be started and stopped when necessary. Lifecycle events ~~~~~~~~~~~~~~~~ -_tbw_ +Beyond parsing the commandline and triggering start of the subsystems, the Application +Object also issues _Lifecycle Events_. Individual components can register a callback +to be invoked when this happens. Currently (2018) the following events are known +(see 'include/lifecycle.h'): + +- ON_BASIC_INIT +- ON_GLOBAL_INIT +- ON_GLOBAL_SHUTDOWN +- ON_EMERGENCY_EXIT + +-> see here for +link:{ldoc}/design/application/SubsystemLifecycle.html#_initialisation_and_lifecycle[explanation in detail] +[small]#(somewhat preliminary and messy, yet still valid as of [yellow-background]##2018##)# + +-> more +link:{ldoc}/design/architecture/Subsystems.html#_lifecycle[documentation] +[small]#(likewise incomplete, but we're getting there, eventually [yellow-background]##2018##)# Interface system ~~~~~~~~~~~~~~~~ @@ -448,9 +476,6 @@ Config loader ~~~~~~~~~~~~~ _tbw_ -Lua Scripting -~~~~~~~~~~~~~ -_tbw_ @@ -656,18 +681,22 @@ mayfail() -Singletons -~~~~~~~~~~ +Singletons and Dependencies +~~~~~~~~~~~~~~~~~~~~~~~~~~~ Deliberately, Lumiera stays below the level of utilising a dependency injection framework. Consequently, we access most services _by type name_, pulling up a single implementation -instance on demand. Rather than placing this singleton lifecycle logic into the individual -implementation classes, we use a _singleton factory_, managing the lifecycle through static -variables and placing the singleton object into a static memory block. Singleton initialisation -is protected by a monitor per singleton type, while shutdown is triggered by the clean-up of -a static variable. This results in the general policy that within Lumiera, performing any -``business'' code in the application shutdown phase (after exiting +main()+) is _strictly -prohibited._ Generally speaking, destructors _must not perform any significant work_ and -are are expected to be failsafe. +instance on demand. Yet some aspects of the *Singleton Pattern* are known to be problematic. +Rather than placing this singleton lifecycle logic into the individual implementation classes, +we use a _singleton factory_, to manage the instance creation and lifecycle of the singleton +or service instance separate from the singleton class itself. Singleton initialisation happens +lazily with this approach, and thus needs to be protected in a multithreaded environment; we +use Double Checked Locking with std::atomic for this purpose. + +Singletons need to be written in a way to cope with that somewhat nondeterministic environment. +They will be created on-demand, and be destroyed _eventually,_ in the application shutdown phase. +This results in the general policy that within Lumiera, performing any ``business'' code in the +application shutdown phase (after exiting +main()+) is _strictly prohibited._ Generally speaking, +destructors _must not perform any significant work_ and are are expected to be failsafe. .accessing the singleton instance By convention, when clients are expected actively to access the singleton instance, @@ -675,16 +704,21 @@ the interface class holds the singleton factory as a public static member with t name +instance+. This allows clients to write `SomeService::instance()` to get a reference to the implementation. -.subclasses and mock testing support -There is a mechanism to ``push aside'' the existing singleton instance and shadow -it temporarily with a mock implementation. Besides, there is a variation of the -general singleton factory, allowing to fabricate a specific subclass of the -exposed interface. Both of these facilities are rather technically involved -and require some specific set-up -- fortunately it turned out that they are -used only occasionally and rarely required. (Probably this is a result of -Lumiera being developed _test-driven_ -- things are being written mostly -in a unit test friendly fashion). +.subclasses and services +Actually, what appears as a mere ``singleton'' here is a form of _Dependency Injection,_ +and can easily be extended to cover other forms of dependencies and other types of lifecycle. +Our experience indicates that this is rarely necessary, but when it happens, it's essential. +Lumiera's Dependency-Factory thus offers a configuration mechanism, which allows to switch +those modes of operation for individual types; the client consuming the ``singleton'' remains +unaware of such special setup. +.mock testing support +Moreover, the Dependency-Factory also offers a mechanism to ``push aside'' the existing singleton +or serviec instance and shadow it temporarily with a mock implementation. Again, it turned out that +such mocking mechanisms are used only occasionally and rarely required. Likely this is a result of +Lumiera being developed _test-driven_ -- things are being written mostly in a unit test friendly way. + +-> more about link:{ldoc}/technical/library/Dependencies.html[dependency handling] Extensible Factory ~~~~~~~~~~~~~~~~~~ @@ -749,6 +783,16 @@ only _unique values,_ to _take a snapshot on-the-fly_ etc. There are convenience builder functions for those operations, figuring out the actual source and destination types by template metaprogramming. +Pipeline Builder +^^^^^^^^^^^^^^^^ +As an extension to the iterator framework, the pipeline builder is able +to wrap arbitrary source iterators, decorate them with filters and transformations +provided as Lambda, and finally package all together into a self contained new +iterator (value object). This is a rather new feature [small]#(as of 2017)# and +we still need to determine its viability, yet it proved helpful when implementing +certain kinds of search algorithms on top of an abstracted data source exposed +as iterator. -> 'lib/iter-tree-explorer.hpp' + Front-end for boost::format ~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/research/try.cpp b/research/try.cpp index 5be3b928d..a034e6bdf 100644 --- a/research/try.cpp +++ b/research/try.cpp @@ -10,7 +10,7 @@ // 4/08 - comparison operators on shared_ptr // 4/08 - conversions on the value_type used for boost::any // 5/08 - how to guard a downcasting access, so it is compiled in only if the involved types are convertible -// 7/08 - combining partial specialisation and subclasses +// 7/08 - combining partial specialisation and subclasses // 10/8 - abusing the STL containers to hold noncopyable values // 6/09 - investigating how to build a mixin template providing an operator bool() // 12/9 - tracking down a strange "warning: type qualifiers ignored on function return type" @@ -32,194 +32,86 @@ // 1/16 - generic to-string conversion for ostream // 1/16 - build tuple from runtime-typed variant container // 3/17 - generic function signature traits, including support for Lambdas +// 9/17 - manipulate variadic templates to treat varargs in several chunks +// 11/17 - metaprogramming to detect the presence of extension points +// 11/17 - detect generic lambda +// 12/17 - investigate SFINAE failure. Reason was indirect use while in template instantiation +// 03/18 - Dependency Injection / Singleton initialisation / double checked locking /** @file try.cpp - ** Metaprogramming: unified treatment of functors, function references and lambdas. - ** Rework our existing function signature trait to also support lambdas, which forces us - ** to investigate and in the end to change the handling of function member pointers. - ** - ** This investigation is a partial step towards #994 and became necessary to support - ** Command definition by Lambda - ** + ** Rework of the template lib::Depend for singleton and service access. + ** The (now for the third time rewritten) dependency factory can be configured to yield + ** a subclass singleton, or to bind to an external service. Lazy initialisation relies on + ** Double Checked Locking, which we need switch to C++11 Atomics in order to be correct + ** (in theory). The impact of this initialisation guard should be investigated by benchmark. */ typedef unsigned int uint; #include "lib/format-cout.hpp" -#include "lib/format-util.hpp" -#include "lib/meta/function.hpp" - -#include -#include - -using lib::meta::_Fun; - -using std::function; -using std::placeholders::_1; -using std::bind; -using std::string; -using std::tuple; -using std::move; - - - - -int -funny (uint i) -{ - return -i+1; -} - -struct Funky - { - int ii = 2; - - int - fun (uint i2) - { - return ii + funny(i2); - } - - int - operator() (uint i2) - { - return 2*ii - fun(i2); - } - - static int - notfunny (uint i) - { - return 2*funny (i); - } - }; +#include "lib/depend.hpp" +#include "lib/depend-inject.hpp" +//#include "lib/meta/util.hpp" +#include "lib/test/test-helper.hpp" +#include "lib/util.hpp" #define SHOW_TYPE(_TY_) \ cout << "typeof( " << STRINGIFY(_TY_) << " )= " << lib::meta::typeStr<_TY_>() <::Sig; + volatile int theHole_ = rand() % 1000; - SHOW_TYPE (F); - SHOW_TYPE (Sig); - } + public: + int readMe() { return theHole_; } + }; -template -void -showRef (F&) - { - using Sig = typename _Fun::Sig; - - SHOW_TYPE (F); - SHOW_TYPE (Sig); - } - -template -void -showCRef (F&) - { - using Sig = typename _Fun::Sig; - - SHOW_TYPE (F); - SHOW_TYPE (Sig); - } - -template -void -showRRef (F&&) - { - using Sig = typename _Fun::Sig; - - SHOW_TYPE (F); - SHOW_TYPE (Sig); - } - - -using Fun = function; -using Fuk = function; int main (int, char**) { - Fun f1{funny}; - Fun f2{&funny}; + std::srand(std::time(nullptr)); + LifecycleHook::trigger (ON_GLOBAL_INIT); - Fun f3{Funky::notfunny}; - Fun f4{&Funky::notfunny}; + Depend mystery; - auto memfunP = &Funky::fun; + thread_local int64_t cnt = 0; - Fuk f5{memfunP}; - - Funky funk; - - Fun f6{bind (f5, funk, _1)}; - - auto lambda = [&](uint ii) { return funk.fun(ii); }; - - Fun f7{lambda}; - - showType (funny); - showType (&funny); - showType (Funky::notfunny); - - showType (memfunP); - showType (lambda); - showType (f7); - - cout << "\n\n-------\n"; - - showRef (funny); - showRef (lambda); - showRef (f7); - - showCRef (funny); - showCRef (lambda); - showCRef (f7); - - showRRef (move(lambda)); - showRRef (move(f7)); - - showType (move(&funny)); - showType (move(lambda)); - showType (move(f7)); - - Fun& funRef = f1; - Funky& funkyRef = funk; - Fun const& funCRef = f1; - Funky const& funkyCRef = funk; - showType (funRef); - showType (funkyRef); - showType (funCRef); - showType (funkyCRef); - - cout << "\n\n-------\n"; - - SHOW_TYPE (decltype(&Funky::operator())); - SHOW_TYPE (decltype(lambda)); - - SHOW_TYPE (_Fun::Sig); - SHOW_TYPE (_Fun::Sig); - SHOW_TYPE (_Fun::Sig); - SHOW_TYPE (_Fun::Sig); - SHOW_TYPE (_Fun::Sig); - SHOW_TYPE (_Fun::Sig); - SHOW_TYPE (_Fun::Sig); - - using Siggy = _Fun::Sig; - SHOW_TYPE (_Fun::Sig); - SHOW_TYPE (_Fun::Sig); - SHOW_TYPE (_Fun::Sig); + cout << microbenchmark<8> ([&]() + { + cnt += mystery().readMe(); + } + ,50000000) + << endl; + LifecycleHook::trigger (ON_GLOBAL_SHUTDOWN); cout << "\n.gulp.\n"; return 0; diff --git a/src/backend/engine/engine-config.h b/src/backend/engine/engine-config.h index 89df108a9..4560c5556 100644 --- a/src/backend/engine/engine-config.h +++ b/src/backend/engine/engine-config.h @@ -69,7 +69,7 @@ namespace engine { EngineConfig(); ~EngineConfig(); - friend class lib::DependencyFactory; + friend class lib::DependencyFactory; public: /** access point to the Engine Interface. diff --git a/src/backend/engine/job.h b/src/backend/engine/job.h index cd77fcb7f..d294878d3 100644 --- a/src/backend/engine/job.h +++ b/src/backend/engine/job.h @@ -213,7 +213,7 @@ namespace engine { */ class JobClosure : public lumiera_jobClosure - , boost::noncopyable // ....has distinct identity and stable address + , util::NonCopyable // ....has distinct identity and stable address { public: virtual ~JobClosure(); ///< this is an interface diff --git a/src/backend/engine/scheduler-diagnostics.hpp b/src/backend/engine/scheduler-diagnostics.hpp index efa01a85d..2e9b83708 100644 --- a/src/backend/engine/scheduler-diagnostics.hpp +++ b/src/backend/engine/scheduler-diagnostics.hpp @@ -38,13 +38,11 @@ #include "lib/error.hpp" +#include "lib/nocopy.hpp" #include "lib/hash-value.h" #include "lib/time/timevalue.hpp" #include "backend/engine/scheduler-frontend.hpp" -#include -//#include - namespace backend{ namespace engine { @@ -68,7 +66,7 @@ namespace engine { * of SchedulerDiagnostics may be used. */ class SchedulerDiagnostics - : boost::noncopyable + : util::NonCopyable { SchedulerFrontend& scheduler_; diff --git a/src/backend/real-clock.hpp b/src/backend/real-clock.hpp index b17efd4ae..a227aa6ae 100644 --- a/src/backend/real-clock.hpp +++ b/src/backend/real-clock.hpp @@ -49,7 +49,6 @@ //#include "lib/iter-source.hpp" //#include "lib/sync.hpp" -//#include //#include //#include //#include diff --git a/src/backend/thread-wrapper.hpp b/src/backend/thread-wrapper.hpp index 37f1d834b..9595927cc 100644 --- a/src/backend/thread-wrapper.hpp +++ b/src/backend/thread-wrapper.hpp @@ -41,7 +41,9 @@ #include "lib/error.hpp" +#include "lib/nocopy.hpp" #include "include/logging.h" +#include "lib/meta/function.hpp" #include "lib/result.hpp" extern "C" { @@ -49,18 +51,16 @@ extern "C" { } #include "backend/threadpool-init.hpp" -#include -#include +#include +#include namespace backend { - using std::bind; - using std::function; using lib::Literal; namespace error = lumiera::error; - using error::LUMIERA_ERROR_STATE; - using error::LUMIERA_ERROR_EXTERNAL; + using error::LERR_(STATE); + using error::LERR_(EXTERNAL); typedef struct nobug_flag* NoBugFlag; @@ -108,94 +108,95 @@ namespace backend { * If this doesn't happen, you'll block forever. */ class Thread - : boost::noncopyable + : util::MoveOnly { + /** @internal perfect forwarding through a C-style `void*` */ + template + static FUN&& + forwardInitialiser (void* rawPtr) noexcept + { + REQUIRE (rawPtr); + FUN& initialiser = *reinterpret_cast (rawPtr); + return static_cast (initialiser); + } + + + template + static void + threadMain (void* arg) + { + using Fun= typename lib::meta::_Fun::Functor; + Fun _doIt_{forwardInitialiser (arg)}; + + lumiera_thread_sync (); // sync point: arguments handed over + + try { + _doIt_(); // execute the actual operation in the new thread + } + + catch (std::exception& failure) + { + if (!lumiera_error_peek()) + LUMIERA_ERROR_SET (sync, STATE + ,failure.what()); + } + catch (...) + { + LUMIERA_ERROR_SET_ALERT (sync, EXTERNAL + , "Thread terminated abnormally"); + } + } + protected: - typedef function Operation; + LumieraThread threadHandle_; + + /** @internal derived classes may create an inactive thread */ + Thread() : threadHandle_(0) { } - struct ThreadStartContext - : boost::noncopyable + /** @internal use the Lumiera thread manager to start a new thread and hand over the operation */ + template + void + launchThread (Literal purpose, FUN&& operation, NoBugFlag logging_flag, uint additionalFlags =0) { + REQUIRE (!lumiera_error(), "Error pending at thread start"); + using Functor = typename std::remove_reference::type; + threadHandle_ = + lumiera_thread_run ( LUMIERA_THREADCLASS_INTERACTIVE | additionalFlags + , &threadMain + , reinterpret_cast (&operation) + , purpose.c() + , logging_flag + ); + if (!threadHandle_) + throw error::State ("Failed to start a new Thread for \"+purpose+\"" + , lumiera_error()); - Operation const& operation_; - - static void - run (void* arg) - { - REQUIRE (arg); - ThreadStartContext* ctx = reinterpret_cast(arg); - Operation _doIt_(ctx->operation_); - - lumiera_thread_sync (); // sync point: arguments handed over - - try { - _doIt_(); // execute the actual operation in the new thread - } - - catch (std::exception& failure) - { - if (!lumiera_error_peek()) - LUMIERA_ERROR_SET (sync, STATE - ,failure.what()); - } - catch (...) - { - LUMIERA_ERROR_SET_ALERT (sync, EXTERNAL - , "Thread terminated abnormally"); - } - } - - - public: - ThreadStartContext (LumieraThread& handle - ,Operation const& operation_to_execute - ,Literal& purpose - ,NoBugFlag logging_flag - ,uint additionalFlags =0 - ) - : operation_(operation_to_execute) - { - REQUIRE (!lumiera_error(), "Error pending at thread start"); - handle = - lumiera_thread_run ( LUMIERA_THREADCLASS_INTERACTIVE | additionalFlags - , &run // invoking the run helper and.. - , this // passing this start context as parameter - , purpose.c() - , logging_flag - ); - if (!handle) - throw error::State ("Failed to start a new Thread for \"+purpose+\"" - , lumiera_error()); - - // make sure the new thread had the opportunity to take the Operation - // prior to leaving and thereby possibly destroying this local context - lumiera_thread_sync_other (handle); - } - }; + // make sure the new thread had the opportunity to take the Operation + // prior to leaving and thereby possibly destroying this local context + lumiera_thread_sync_other (threadHandle_); + } - LumieraThread thread_; - - Thread() : thread_(0) { } - - public: /** Create a new thread to execute the given operation. - * The new thread starts up synchronously, it can't - * be cancelled and it can't be joined. + * The new thread starts up synchronously, can't be cancelled and it can't be joined. * @param purpose fixed char string used to denote the thread for diagnostics * @param logging_flag NoBug flag to receive diagnostics regarding the new thread - * @param operation defining what to execute within the new thread. Any functor - * which can be bound to function. Note this functor will be - * copied onto the stack of the new thread, thus it can be transient. + * @param operation a functor holding the code to execute within the new thread. + * Any function-like entity with signature `void(void)` is acceptable. + * @warning The operation functor will be forwarded to create a copy residing + * on the stack of the new thread; thus it can be transient, however + * anything referred through a lambda closure here must stay alive + * until the new thread terminates. */ - Thread (Literal purpose, Operation const& operation, NoBugFlag logging_flag = &NOBUG_FLAG(thread)) - : thread_(0) + template + Thread (Literal purpose, FUN&& operation, NoBugFlag logging_flag = &NOBUG_FLAG(thread)) + : threadHandle_{nullptr} { - ThreadStartContext (thread_, operation, purpose, logging_flag); + launchThread (purpose, std::forward (operation), logging_flag); } @@ -207,7 +208,7 @@ namespace backend { bool isValid() const { - return thread_; + return threadHandle_; } @@ -219,7 +220,7 @@ namespace backend { sync() { REQUIRE (isValid(), "Thread not running"); - if (!lumiera_thread_sync_other (thread_)) + if (!lumiera_thread_sync_other (threadHandle_)) lumiera::throwOnError(); } @@ -242,7 +243,7 @@ namespace backend { REQUIRE (isValid(), "Thread not running"); LumieraThread current = lumiera_thread_self (); return current - and current == this->thread_; + and current == this->threadHandle_; } }; @@ -259,11 +260,13 @@ namespace backend { : public Thread { public: - ThreadJoinable (Literal purpose, Operation const& operation, + template + ThreadJoinable (Literal purpose, FUN&& operation, NoBugFlag logging_flag = &NOBUG_FLAG(thread)) + : Thread{} { - ThreadStartContext (thread_, operation, purpose, logging_flag, - LUMIERA_THREAD_JOINABLE); + launchThread (purpose, std::forward (operation), logging_flag, + LUMIERA_THREAD_JOINABLE); } @@ -279,8 +282,8 @@ namespace backend { throw error::Logic ("joining on an already terminated thread"); lumiera_err errorInOtherThread = - lumiera_thread_join (thread_); - thread_ = 0; + lumiera_thread_join (threadHandle_); + threadHandle_ = 0; if (errorInOtherThread) return error::State ("Thread terminated with error", errorInOtherThread); diff --git a/src/common/advice.hpp b/src/common/advice.hpp index a188b6610..b8e31541e 100644 --- a/src/common/advice.hpp +++ b/src/common/advice.hpp @@ -95,14 +95,13 @@ #include "lib/error.hpp" +#include "lib/nocopy.hpp" #include "lib/null-value.hpp" #include "lib/symbol.hpp" #include "lib/util.hpp" #include "common/advice/binding.hpp" -#include - namespace lumiera{ namespace advice { @@ -315,7 +314,7 @@ namespace advice { template class ActiveProvision : public PointOfAdvice - , boost::noncopyable + , util::NonCopyable { AD theAdvice_; diff --git a/src/common/advice/advice.cpp b/src/common/advice/advice.cpp index 23e10bf1a..8e1543cd9 100644 --- a/src/common/advice/advice.cpp +++ b/src/common/advice/advice.cpp @@ -90,6 +90,7 @@ #include "lib/error.hpp" +#include "lib/nocopy.hpp" #include "lib/del-stash.hpp" #include "lib/depend.hpp" #include "lib/symbol.hpp" @@ -99,7 +100,6 @@ #include "common/advice.hpp" #include "common/advice/index.hpp" -#include using lib::Literal; using lib::DelStash; @@ -121,7 +121,7 @@ namespace advice { */ class AdviceSystem : public lib::Sync<> - , boost::noncopyable + , util::NonCopyable { DelStash adviceDataRegistry_; diff --git a/src/common/advice/index.hpp b/src/common/advice/index.hpp index 0bbdc27d3..6be0a2aa5 100644 --- a/src/common/advice/index.hpp +++ b/src/common/advice/index.hpp @@ -509,7 +509,7 @@ namespace advice { { SelfCheckFailure (Literal failure) : error::Fatal (string("Failed test: ")+failure - ,LUMIERA_ERROR_INDEX_CORRUPTED) + ,LERR_(INDEX_CORRUPTED)) { } }; } diff --git a/src/common/appstate.cpp b/src/common/appstate.cpp index 125eb31ce..a0a6294d5 100644 --- a/src/common/appstate.cpp +++ b/src/common/appstate.cpp @@ -33,7 +33,6 @@ #include "lib/error.hpp" -#include "include/lifecycle.h" #include "common/appstate.hpp" #include "common/subsystem-runner.hpp" @@ -65,20 +64,12 @@ namespace lumiera { if (lumiera_err errorstate = lumiera_error ()) ALERT (common, "*** Unexpected error: %s\n Triggering emergency exit.", errorstate); } - - void - createAppStateInstance(){ - AppState::instance(); - } - - LifecycleHook schedule_ (ON_BASIC_INIT, &createAppStateInstance); - } - /** perform initialisation triggered on first access. + /** perform initialisation triggered on first access. * Will execute BasicSetup sequence to determine the location * of the executable and read in \c setup.ini -- * Since above a LifecycleHook is installed ON_BASIC_INIT, @@ -94,17 +85,8 @@ namespace lumiera { , core_up_{false} { } - - - - AppState& - AppState::instance() // Meyer's singleton - { - static unique_ptr theApp_; - if (!theApp_) theApp_.reset (new AppState ()); - return *theApp_; - } - + /** storage for the Appstate Singleton instance */ + lib::Depend AppState::instance; string @@ -120,7 +102,7 @@ namespace lumiera { // ===== Implementation startup and shutdown sequence for main() ======== -#define _THROW_IF \ +#define _MAYBE_THROW_ \ maybeThrow ("internal failure while initialising the "\ "Lumiera application framework"); @@ -132,17 +114,17 @@ namespace lumiera { TRACE (common, "initialising application core..."); lumiera_interfaceregistry_init (); - _THROW_IF + _MAYBE_THROW_ lumiera_plugin_discover (lumiera_plugin_load, lumiera_plugin_register); - _THROW_IF + _MAYBE_THROW_ lumiera_config_interface_init (); - _THROW_IF + _MAYBE_THROW_ core_up_= true; LifecycleHook::trigger (ON_GLOBAL_INIT); - _THROW_IF + _MAYBE_THROW_ subsystems_.reset (new SubsystemRunner (options)); @@ -174,7 +156,7 @@ namespace lumiera { * the termination of all other subsystems is initiated; when detecting this case, * the emergency exit sequence is called. Any error which can't be handled within * this scheme, should be thrown as exception, in which case the abort handler - * is activated. + * is activated. */ ExitCode AppState::maybeWait() @@ -269,6 +251,4 @@ namespace lumiera { - - } // namespace lumiera diff --git a/src/common/appstate.hpp b/src/common/appstate.hpp index 2f0d6f6a0..6c2346d28 100644 --- a/src/common/appstate.hpp +++ b/src/common/appstate.hpp @@ -41,11 +41,12 @@ #define LUMIERA_APPSTATE_H #include "lib/symbol.hpp" +#include "lib/nocopy.hpp" +#include "lib/depend.hpp" #include "common/option.hpp" #include "common/subsys.hpp" #include "common/basic-setup.hpp" -#include #include #include #include @@ -55,7 +56,6 @@ namespace lumiera { using std::string; - using boost::noncopyable; class SubsystemRunner; @@ -63,24 +63,24 @@ namespace lumiera { /** * The Lumiera Application state and basic initialisation. * Singleton to hold global flags directing the overall application behaviour, - * for triggering lifecycle events and performing early initialisation tasks. + * responsible for triggering lifecycle events and performing early initialisation tasks. * AppState services are available already from static initialisation code. * @warning don't use AppState in destructors. */ class AppState - : private noncopyable + : util::NonCopyable { private: AppState (); ~AppState (); - friend class std::default_delete; + friend class lib::DependencyFactory; public: - /** get the (single) AppState instance. + /** get the (single) AppState instance. * @warning don't use it after the end of main()! */ - static AppState& instance(); + static lib::Depend instance; /** evaluate the result of option parsing and maybe additional configuration @@ -112,7 +112,7 @@ namespace lumiera { * On termination of one of those components, tear down the remaining * components and initiate a normal or emergency shutdown of the * application, depending on the triggering component's - * mode of termination (exit or exception). + * mode of termination (exit or exception). * @return global application exit code */ ExitCode maybeWait(); @@ -128,7 +128,7 @@ namespace lumiera { ExitCode abort () noexcept; - + private: using PSub = std::unique_ptr; diff --git a/src/common/basic-setup.cpp b/src/common/basic-setup.cpp index 16b9e2fe5..434c55086 100644 --- a/src/common/basic-setup.cpp +++ b/src/common/basic-setup.cpp @@ -75,9 +75,9 @@ namespace lumiera { /** * Creating the BasicSetup object performs the * initial self-configuration of the Lumiera Application. - * For this, the \c setup.ini file is located relative to the + * For this, the `setup.ini` file is located relative to the * current application executable, read in and parsed into a - * map of setup variables. + * map of setup variables. */ BasicSetup::BasicSetup (string bootstrapIni) : syntax("Lumiera installation and platform configuration") @@ -118,7 +118,7 @@ namespace lumiera { opt::parsed_options parsed = opt::parse_config_file (configIn, syntax); opt::store (parsed, settings); - opt::notify(settings); + opt::notify(settings); } diff --git a/src/common/basic-setup.hpp b/src/common/basic-setup.hpp index c018e2389..5dbdb2622 100644 --- a/src/common/basic-setup.hpp +++ b/src/common/basic-setup.hpp @@ -55,10 +55,10 @@ #include "lib/error.hpp" #include "lib/symbol.hpp" +#include "lib/nocopy.hpp" #include "lib/util.hpp" #include -#include #include @@ -95,7 +95,7 @@ namespace lumiera { * @see AppState */ class BasicSetup - : boost::noncopyable + : util::NonCopyable { opt::options_description syntax; opt::variables_map settings; diff --git a/src/common/configfacade.cpp b/src/common/configfacade.cpp index ac7500be5..3841f14eb 100644 --- a/src/common/configfacade.cpp +++ b/src/common/configfacade.cpp @@ -46,7 +46,7 @@ #include "lib/searchpath.hpp" #include "lib/util.hpp" -extern "C" { +extern "C" { #include "common/config.h" } @@ -61,7 +61,7 @@ extern "C" { /** Similarly, this key is used to fetch the configured default * plugin/module search path from the basic setup.ini * This patch is used by the plugin-loader to discover - * lumiera plugins and extensions. + * lumiera plugins and extensions. */ #define KEY_PLUGIN_PATH "Lumiera.modulepath" @@ -81,8 +81,8 @@ namespace lumiera { namespace { - void - pull_up_ConfigSystem () + void + pull_up_ConfigSystem () { TRACE (common, "booting up config system"); Config::instance(); @@ -139,7 +139,7 @@ extern "C" { /* ==== implementation C interface for accessing setup.ini ======= - const char* + const char* lumiera_get_plugin_path_default () { static string pathSpec; @@ -147,7 +147,7 @@ extern "C" { /* ==== implementation C interface for accessing setup.ini ======= { pathSpec += "plugin.path="; // syntax expected by lumiera_config_setdefault - // fetch plugin search path from setup.ini and expand any $ORIGIN token + // fetch plugin search path from setup.ini and expand any $ORIGIN token SearchPathSplitter pathElement(Config::get (KEY_PLUGIN_PATH)); while (pathElement) pathSpec += pathElement.next() +":"; diff --git a/src/gui/display-interface-proxy.hpp b/src/common/display-interface-proxy.cpp similarity index 71% rename from src/gui/display-interface-proxy.hpp rename to src/common/display-interface-proxy.cpp index 2b0130a37..ac8cbdc93 100644 --- a/src/gui/display-interface-proxy.hpp +++ b/src/common/display-interface-proxy.cpp @@ -22,35 +22,29 @@ -/** @file display-interface-proxy.hpp - ** This is an implementation fragment, intended to be included into common/interfaceproxy.cpp - ** +/** @file display-interface-proxy.cpp + ** Interface-Proxy for the lumiera::Display facade (experimental/deprecated). ** The purpose is to define a proxy implementation of lumiera::Display, in order to ** redirect any calls through the associated C Language Interface "lumieraorg_Display" ** ** @see display-facade.hpp ** @see display-service.hpp actual implementation within the GUI + ** @deprecated obsolete early design draft from 2009; + ** as of 2018 only kept in source to compile some likewise obsolete UI widgets. */ - - - - -/* ==================== gui::Display ====================================== */ - #include "include/display-facade.h" #include "include/interfaceproxy.hpp" namespace lumiera { - /** storage for the facade proxy factory - * used by client code to invoke through the interface */ - lumiera::facade::Accessor Display::facade; - /// emit the vtable here into this translation unit within liblumieracommon.so... Display::~Display() { } + /** static storage for the facade access front-end */ + lib::Depend Display::facade; + } // namespace lumiera @@ -58,14 +52,19 @@ namespace lumiera { namespace lumiera { namespace facade { - typedef InstanceHandle< LUMIERA_INTERFACE_INAME(lumieraorg_Display, 0) - , lumiera::Display - > IHandle_Display; + + + /* ==================== gui::Display ====================================== */ + + using Interface = LUMIERA_INTERFACE_INAME(lumieraorg_Display, 0); + using Facade = lumiera::Display; + + using IHandle = InstanceHandle; template<> - class Proxy - : public Holder + class Proxy + : public Binding { //----Proxy-Implementation-of-lumiera::Display-------- @@ -83,11 +82,12 @@ namespace facade { public: - Proxy (IHandle const& iha) : THolder(iha) {} + using Binding::Binding; }; - template void openProxy (IHandle_Display const&); - template void closeProxy (void); + /** emit proxy code here... */ + template + class Link; -}} // namespace lumiera::facade +}} //namespace facade::lumiera diff --git a/src/common/guifacade.cpp b/src/common/guifacade.cpp index ec69b2c6c..bde729f1e 100644 --- a/src/common/guifacade.cpp +++ b/src/common/guifacade.cpp @@ -57,7 +57,7 @@ namespace gui { /** load and start the GUI as a plugin */ struct GuiRunner - : boost::noncopyable + : util::NonCopyable { typedef InstanceHandle GuiHandle; diff --git a/src/common/instancehandle.hpp b/src/common/instancehandle.hpp index 08f26e7b5..c61be7d3a 100644 --- a/src/common/instancehandle.hpp +++ b/src/common/instancehandle.hpp @@ -1,5 +1,5 @@ /* - INSTANCEHANDLE.hpp - automatically handling interface lifecycle + INSTANCEHANDLE.hpp - automatically handling interface lifecycle Copyright (C) Lumiera.org 2008, Hermann Vosseler @@ -32,7 +32,7 @@ ** @see gui::GuiFacade usage example ** @see interface.h ** @see interfaceproxy.hpp (more explanations) - ** @see interfaceproxy.cpp (Implementation of the proxies) + ** @see session-command-interface-proxy.cpp (Proxy implementation example) ** */ @@ -43,14 +43,14 @@ #include "include/logging.h" #include "lib/error.hpp" -#include "include/interfaceproxy.hpp" +#include "lib/nocopy.hpp" +#include "lib/depend-inject.hpp" extern "C" { #include "common/interface.h" #include "common/interfaceregistry.h" } -#include #include @@ -66,17 +66,17 @@ namespace lumiera { namespace { // implementation details void - throwIfError() + throwIfError() { if (lumiera_error_peek()) throw lumiera::error::Config("failed to open interface or plugin.",lumiera_error()); } - + /** takes a (single) instance definitions, as typically created * when defining interfaces for external use, and registers it * with the InterfaceSystem. Then uses the data found in the * \em given instance descriptor to open an instance handle. - * @throws error::Config when the registration process fails + * @throws error::Config when the registration process fails */ LumieraInterface register_and_open (LumieraInterface descriptor) @@ -97,15 +97,32 @@ namespace lumiera { verify_validity (LumieraInterface ifa) { REQUIRE (ifa); - return (ifa == lumiera_interfaceregistry_interface_find (ifa->interface, - ifa->version, + return (ifa == lumiera_interfaceregistry_interface_find (ifa->interface, + ifa->version, ifa->name)); } - - } // (End) impl details + } // (End) impl details + + + namespace facade { + + /** + * to be specialised and implemented for each + * individual interface and facade interface. + * The actual proxy implements the facade interface + * and reroutes each call to the corresponding function + * on the CL-Interface for the Lumiera interface system. + */ + template + class Proxy; + + /** The ServiceHandle automatically creates and manages the Proxy instance */ + template + using ServiceHandle = typename lib::DependInject::template ServiceInstance>>; + /** * @internal Helper/Adapter for establishing a link @@ -118,39 +135,37 @@ namespace lumiera { */ template struct Link - : boost::noncopyable + : ServiceHandle { - typedef InstanceHandle IH; + using IH = InstanceHandle; - Link (IH const& iha) { facade::openProxy(iha); } - ~Link() { facade::closeProxy(); } + Link (IH const& iha); + ~Link (); - FA& - operator() (IH const&) const - { - return facade::Accessor()(); - } + FA* operator->() const; }; /** * @internal when the InstanceHandle isn't associated with a - * facade interface, then this specialisation switches + * facade interface, then this specialisation switches * the facade::Link into "NOP" mode. */ template struct Link - : boost::noncopyable + : util::NonCopyable { - typedef InstanceHandle IH; + using IH = InstanceHandle; + IH& ih_; - Link (IH const&) { /* NOP */ } - ~Link() { /* NOP */ } - - I& - operator() (IH const& handle) const + Link (IH& ih) + : ih_{ih} + { } + + I* + operator->() const { - return handle.get(); + return & ih_.get(); } }; @@ -174,8 +189,8 @@ namespace lumiera { , class FA = I ///< facade interface type to be used by clients > class InstanceHandle - : private boost::noncopyable - { + : util::NonCopyable + { LumieraInterface desc_; I* instance_; facade::Link facadeLink_; @@ -190,14 +205,14 @@ namespace lumiera { */ InstanceHandle (string const& iName, uint version, size_t minminor, string const& impName) : desc_(0) - , instance_(reinterpret_cast + , instance_(reinterpret_cast (lumiera_interface_open (iName.c_str(), version, minminor, impName.c_str()))) , facadeLink_(*this) - { + { throwIfError(); } - /** Set up an InstanceHandle managing the + /** Set up an InstanceHandle managing the * registration and deregistration of interface(s). * Should be placed at the service providing side. * @param a (single) interface descriptor, which can be created with @@ -207,7 +222,7 @@ namespace lumiera { : desc_(descriptor) , instance_(reinterpret_cast (register_and_open (desc_))) , facadeLink_(*this) - { + { throwIfError(); } @@ -220,24 +235,41 @@ namespace lumiera { - /** act as smart pointer providing access through the facade. - * @note we don't provide operator* */ - FA * operator-> () const { return &(facadeLink_(*this)); } + /** act as smart pointer to allow access through the facade. + * @note we don't provide `operator*` + */ + FA* + operator-> () const + { + return facadeLink_.operator ->(); + } /** directly access the instance via the CL interface */ - I& get () const { ENSURE(instance_); return *instance_; } + I& + get () const + { + ENSURE(instance_); + return *instance_; + } - - explicit operator bool() const { return isValid(); } - bool operator! () const { return not isValid();} + explicit + operator bool() const + { + return isValid(); + } + bool + operator!() const + { + return not isValid(); + } private: - bool + bool isValid() const - { - return instance_ + { + return instance_ && verify_validity (&instance_->interface_header_); } }; diff --git a/src/common/interface-facade-link.hpp b/src/common/interface-facade-link.hpp deleted file mode 100644 index 2811043ac..000000000 --- a/src/common/interface-facade-link.hpp +++ /dev/null @@ -1,124 +0,0 @@ -/* - INTERFACE-FACADE-LINK - a switchable link from interface to service implementation - - Copyright (C) Lumiera.org - 2011, Hermann Vosseler - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of - the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - -*/ - -/** @file interface-facade-link.hpp - ** Opening, accessing and closing the service access through a facade interface. - ** Client code is assumed to access an application level service through an facade - ** interface, while the actual implementation object remains an opaque internal detail. - ** Moreover, services may come up and shut down, so the implementation might change - ** during the Lifecycle. The facility defined here in this header provides a basic - ** implementation for this access mechanism, but without any adaptation, binding - ** or plugin access layer. It works only under the assumption that both the - ** interface and the actual service implementation coexist in the same - ** executable and are written in C++, so any invocation of an - ** interface method boils down to a language-level call. - ** - ** Usually, client code doesn't need to include this header. Clients are assumed - ** to use the facade interface of the service in question. This facade interface - ** contains a static member of type \c lumiera::facade::Accessor (where I is - ** the type of the facade interface). The Accessor baseclass is defined in - ** interfaceproxy.hpp and typically included through the facade header. - ** - ** @note there is a way more elaborate implementation of the same mechanism - ** for use with the Lumiera Interface/Plugin system. - ** - ** @see interfaceproxy.hpp description of the more general use case - ** @see PlayService example for the simple use case - */ - - -#ifndef LUMIERA_FACADE_INTERFACE_FACADE_LINK_H -#define LUMIERA_FACADE_INTERFACE_FACADE_LINK_H - - -#include "lib/error.hpp" -#include "lib/meta/util.hpp" -#include "include/interfaceproxy.hpp" -#include "lib/symbol.hpp" - -#include - - - -namespace lumiera { -namespace facade { - - using lib::Literal; - - - /********************************************************************//** - * simple access-frontend to the implementation of a service (C++ only). - * Usually, an instance of Accessor is placed as static member right into - * the facade interface used to access the service. This implementation - * of the access mechanism handles the simple case that both the facade - * and the service implementation are written in C++ and calls happen - * within the main executable as direct language calls, without an - * binding layer and without involving the Interface/Plugin system. - * - * Typically, the InterfaceFacadeLink becomes a member of the service - * implementation class and is directly tied into the constructor of - * the latter. Being a subclass of lumiera::facade::Accessor, it is - * allowed to "open" the facade access just by setting the static - * protected pointer Accessor::implProxy_ - */ - template - class InterfaceFacadeLink - : protected Accessor - , boost::noncopyable - { - string displayName_; - - void - __checkLifecycle () - { - if (Accessor::implProxy_) - throw error::State("Attempt to open an already opened Facade interface." - , error::LUMIERA_ERROR_LIFECYCLE); - } - - public: - InterfaceFacadeLink(FA& serviceImpl, Literal interfaceName_for_Log=0) - : displayName_(interfaceName_for_Log? string(interfaceName_for_Log) - : util::typeStr()) - { - __checkLifecycle(); - Accessor::implProxy_ = &serviceImpl; - INFO (interface, "interface %s opened", displayName_.c_str()); - } - - ~InterfaceFacadeLink() - { - INFO (interface, "closing interface %s...", displayName_.c_str()); - Accessor::implProxy_ = 0; - } - }; - - - - /** storage for the static access pointer */ - template - FA* Accessor::implProxy_; - - -}} // namespace lumiera::facade - -#endif diff --git a/src/common/interfaceproxy.cpp b/src/common/interfaceproxy.cpp deleted file mode 100644 index 6128842b9..000000000 --- a/src/common/interfaceproxy.cpp +++ /dev/null @@ -1,162 +0,0 @@ -/* - InterfaceProxy - definition of forwarding proxies for the facade interfaces - - Copyright (C) Lumiera.org - 2008, Hermann Vosseler - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of - the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - -* *****************************************************/ - - -/** @file interfaceproxy.cpp - ** Implementation of C++ binding proxies on top of the (plain-C based) - ** interface system. This is an implementation facility within the application core, - ** which allows to embody just an ["interface instance handle"](\ref instancehandle.hpp), - ** in order to get RAII-style registration of interfaces and loading of plug-ins. - ** - ** A *crucial requirement* for this approach to work is, that any relevant interface - ** to be bound and exposed as C++ object needs to set up a concrete specialisation of - ** lumiera::facade::Proxy to drive instantiation of the actual binding proxy. - ** The relevant specialisations _need to be included explicitly_ into this - ** compilation unit! - ** - ** The result of this setup is that clients can just invoke `SomeInterface::facade()` - ** and thus call through proper C++ bindings with type safety and automatic - ** lifecycle management. - ** - */ - - -#include "include/interfaceproxy.hpp" -#include "common/instancehandle.hpp" -#include "lib/error.hpp" -#include "lib/util.hpp" - -using util::cStr; - -namespace lumiera{ -namespace facade { - - - LUMIERA_ERROR_DEFINE (FACADE_LIFECYCLE, "facade is closed; service currently not accessible"); - - - /** - * Implementation Base - * for building Facade Proxy implementations. - * Typically the purpose of such a proxy is to route - * any calls through the C-Bindings of the Lumiera Interface system. - * The actual storage for the concrete proxy object is embedded, - * inline within the #open() function. For access by the clients, - * a frontend-object of type \c Accessor may be placed into - * the facade interface; this accessor-frontend is basically - * a concealed static pointer to the proxy, and will be set, - * when the interface is opened. This opening and closing - * of the interface itself is controlled by the - * InstanceHandle, which in turn is typically - * created and managed within the context - * of the service implementation. - */ - template - class Holder; - - template - class Holder> - : Accessor - , protected FA - { - protected: - typedef InstanceHandle IHandle; - typedef Holder THolder; - typedef Proxy TProxy; - typedef Accessor Access; - - I& _i_; - - Holder (IHandle const& iha) - : _i_(iha.get()) - { } - - public: - static TProxy& open(IHandle const& iha) - { - static char buff[sizeof(TProxy)]; - TProxy* p = new(buff) TProxy(iha); - Access::implProxy_ = p; - return *p; - } - - static void close() - { - if (!Access::implProxy_) return; - TProxy* p = static_cast (Access::implProxy_); - Access::implProxy_ = 0; - p->~TProxy(); - } - }; - - - template - FA* Accessor::implProxy_; - - - template - void - openProxy (IHA const& iha) - { - Proxy::open(iha); - } - - template - void - closeProxy () - { - Proxy::close(); - } - -}} // namespace lumiera::facade - - - - - - - /* ===================================================================== */ - /* including explicit Proxy-Definitions for all facade interfaces */ - /* ===================================================================== */ - - - - /* ==================== SessionCommand ================================= */ - -#include "proc/control/session-command-interface-proxy.hpp" - - - /* ==================== GuiNotification ================================ */ - -#include "gui/notification-interface-proxy.hpp" - - - /* ==================== gui::Display =================================== */ - -#include "gui/display-interface-proxy.hpp" - - - /* ==================== DummyPlayer ==================================== */ - -#include "proc/play/dummy-player-interface-proxy.hpp" - - diff --git a/src/gui/notification-interface-proxy.hpp b/src/common/notification-interface-proxy.cpp similarity index 70% rename from src/gui/notification-interface-proxy.hpp rename to src/common/notification-interface-proxy.cpp index c6fa6cd2e..cb8951562 100644 --- a/src/gui/notification-interface-proxy.hpp +++ b/src/common/notification-interface-proxy.cpp @@ -22,9 +22,8 @@ -/** @file notification-interface-proxy.hpp - ** This is an implementation fragment, intended to be included into common/interfaceproxy.cpp - ** +/** @file notification-interface-proxy.cpp + ** Interface-Proxy for the gui::GuiNotification facade. ** The purpose is to define a proxy implementation of gui::GuiNotification, in order to ** redirect any calls through the associated C Language Interface "lumieraorg_GuiNotification" ** @@ -33,37 +32,31 @@ */ - - - -/* ==================== GuiNotification =================================== */ - #include "include/gui-notification-facade.h" #include "include/interfaceproxy.hpp" namespace gui { - - /** storage for the facade proxy factory - * used by client code to invoke through the interface */ - lumiera::facade::Accessor GuiNotification::facade; - -} // namespace gui - - - + /** static storage for the facade access front-end */ + lib::Depend GuiNotification::facade; +} namespace lumiera { namespace facade { using gui::ID; using lib::diff::MutationMessage; - typedef InstanceHandle< LUMIERA_INTERFACE_INAME(lumieraorg_GuiNotification, 0) - , gui::GuiNotification - > IHandle_GuiNotification; + + + /* ==================== SessionCommand =================================== */ + + using Interface = LUMIERA_INTERFACE_INAME(lumieraorg_GuiNotification, 0); + using Facade = gui::GuiNotification; + + using IHandle = InstanceHandle; template<> - class Proxy - : public Holder + class Proxy + : public Binding { using Level = gui::NotifyLevel; @@ -77,12 +70,12 @@ namespace facade { public: - Proxy (IHandle const& iha) : THolder(iha) {} + using Binding::Binding; }; - template void openProxy (IHandle_GuiNotification const&); - template void closeProxy (void); + /** emit proxy code here... */ + template + class Link; - -}} // namespace lumiera::facade +}} //namespace facade::lumiera diff --git a/src/common/option.hpp b/src/common/option.hpp index e03f4e19d..432a41821 100644 --- a/src/common/option.hpp +++ b/src/common/option.hpp @@ -74,7 +74,7 @@ namespace lumiera { * unrecognised parts. */ class Option - : boost::noncopyable + : util::NonCopyable { public: Option (lib::Cmdline& cmdline); diff --git a/src/common/query.hpp b/src/common/query.hpp index 55347b3a3..34196fdfa 100644 --- a/src/common/query.hpp +++ b/src/common/query.hpp @@ -123,7 +123,7 @@ namespace lumiera { * of the type of query. */ class Goal - : util::no_copy_by_client + : util::Cloneable { public: virtual ~Goal(); ///< this is a marker baseclass diff --git a/src/common/query/defs-manager-impl.hpp b/src/common/query/defs-manager-impl.hpp index 154651db7..6e95796ef 100644 --- a/src/common/query/defs-manager-impl.hpp +++ b/src/common/query/defs-manager-impl.hpp @@ -53,7 +53,7 @@ using util::_Fmt; using proc::ConfigResolver; using lumiera::query::QueryHandler; ///////TODO preliminary interface defined in config-rules.hpp -using lumiera::query::LUMIERA_ERROR_CAPABILITY_QUERY; +using lumiera::query::LERR_(CAPABILITY_QUERY); namespace lumiera{ @@ -66,7 +66,8 @@ namespace query { DefsManager::DefsManager () noexcept : defsRegistry_(new DefsRegistry) { - TODO ("setup basic technical defaults of the session?"); + INFO (session, "Configure technical defaults of the session."); + } diff --git a/src/common/query/defs-manager.hpp b/src/common/query/defs-manager.hpp index 88e89bf89..00bc7fd4a 100644 --- a/src/common/query/defs-manager.hpp +++ b/src/common/query/defs-manager.hpp @@ -85,6 +85,14 @@ ** - for sake of completeness, we can also require a specific element to ** be purged from knowledge ** + ** ## Fake implementation + ** As of 2018, the Lumiera project still has to reach the goal of a complete + ** running engine; we are proceeding with partial integrations for the time being. + ** And we postpone advanced topics, like integration of an actual rules solver + ** to future milestones. Meanwhile, we use a [fake implementation](\ref MockConfigRules) + ** with preconfigured, hard-wired "answers" to some frequently encountered standard queries. + ** This Fake implementation is configured and instantiated by the [Defaults Manager](\ref DefsManager) + ** ** @see DefsManager_test ** @see DefsManagerImpl_test ** @@ -96,9 +104,9 @@ #include "lib/p.hpp" +#include "lib/nocopy.hpp" #include "common/query.hpp" -#include #include @@ -127,7 +135,8 @@ namespace query { * roughly final, as of 12/09 most of the actual object * handling is placeholder code. */ - class DefsManager : private boost::noncopyable + class DefsManager + : util::NonCopyable { unique_ptr defsRegistry_; diff --git a/src/common/query/defs-registry.hpp b/src/common/query/defs-registry.hpp index dbc9cbde7..e694636df 100644 --- a/src/common/query/defs-registry.hpp +++ b/src/common/query/defs-registry.hpp @@ -55,11 +55,11 @@ #include "lib/format-string.hpp" #include "lib/query-util.hpp" #include "common/query.hpp" +#include "lib/nocopy.hpp" #include #include #include -#include namespace lumiera{ @@ -193,7 +193,7 @@ namespace query { * exact behaviour has to be defined. */ class DefsRegistry - : boost::noncopyable + : util::NonCopyable { Table table_; diff --git a/src/common/query/query-resolver.hpp b/src/common/query/query-resolver.hpp index 288cde2b2..52ea1f231 100644 --- a/src/common/query/query-resolver.hpp +++ b/src/common/query/query-resolver.hpp @@ -37,8 +37,8 @@ #include "lib/iter-adapter.hpp" #include "common/query.hpp" +#include "lib/nocopy.hpp" -#include #include #include #include @@ -47,7 +47,6 @@ using std::function; namespace lumiera { - using boost::noncopyable; using std::unique_ptr; using std::string; @@ -65,7 +64,7 @@ namespace lumiera { * of an individual query resolution */ class Resolution - : boost::noncopyable + : util::NonCopyable { public: typedef Goal::Result Result; @@ -106,7 +105,7 @@ namespace lumiera { * Thus the implementation might downcast query and resultset. */ class QueryResolver - : noncopyable + : util::NonCopyable { unique_ptr dispatcher_; diff --git a/src/common/subsys.hpp b/src/common/subsys.hpp index aabad653b..33fe237ce 100644 --- a/src/common/subsys.hpp +++ b/src/common/subsys.hpp @@ -44,8 +44,8 @@ #define LUMIERA_SUBSYS_H #include "lib/error.hpp" +#include "lib/nocopy.hpp" -#include #include #include #include @@ -55,7 +55,6 @@ namespace lumiera { using std::string; - using boost::noncopyable; using std::function; class Option; @@ -69,7 +68,7 @@ namespace lumiera { * @note synchronisation is up to the implementor. */ class Subsys - : private noncopyable + : util::NonCopyable { public: typedef function SigTerm; diff --git a/src/common/subsystem-runner.hpp b/src/common/subsystem-runner.hpp index c3fbfba8b..e933737f2 100644 --- a/src/common/subsystem-runner.hpp +++ b/src/common/subsystem-runner.hpp @@ -94,7 +94,7 @@ namespace lumiera { * correspond to a single component, interface or plugin. It may well be a * complete layer of the application (e.g. the GUI). * - * \par Protocol of operation + * ## Protocol of operation * The SubsystemRunner is to be configured with a lumiera::Option object first. * Then, primary subsystems are \link #maybeRun provided \endlink for eventual * startup, which may depend on conditions defined by the subsystem. When @@ -112,7 +112,7 @@ namespace lumiera { * will be triggered. The #wait() function returns after shutdown of all subsystems, * signalling an emergency exit (caused by an exception) with its return value. * - * @todo maybe use my refArray (see builder) to use Subsys& instead of Subsys* ?? + * @todo 2018 this component works well but could be (re)written in a cleaner way * * @see lumiera::AppState * @see lumiera::Subsys diff --git a/src/gui/config-keys.hpp b/src/gui/config-keys.hpp index a5c395381..13443bb56 100644 --- a/src/gui/config-keys.hpp +++ b/src/gui/config-keys.hpp @@ -25,7 +25,8 @@ /** @file config-keys.hpp ** Hard wired key constants to access the global application configuration. ** - ** \par configuration and resource search + ** # configuration and resource search + ** ** The UI layer retrieves the necessary configuration values from lumiera::Config, ** the config facade in the application core. Currently as of 2/2011 these values are ** loaded from setup.ini, because the full-blown config system is not yet implemented. diff --git a/src/gui/ctrl/actions.hpp b/src/gui/ctrl/actions.hpp index acde2fa3a..1cf4d3539 100644 --- a/src/gui/ctrl/actions.hpp +++ b/src/gui/ctrl/actions.hpp @@ -43,12 +43,12 @@ #include "gui/gtk-base.hpp" #include "gui/ctrl/global-ctx.hpp" -#include "gui/ctrl/window-list.hpp" +#include "gui/ctrl/window-locator.hpp" #include "gui/workspace/workspace-window.hpp" #include "gui/workspace/panel-manager.hpp" #include "lib/format-string.hpp" +#include "lib/nocopy.hpp" -#include #include @@ -74,7 +74,7 @@ namespace ctrl { * user action events. */ class Actions - : boost::noncopyable + : util::NonCopyable { GlobalCtx& globalCtx_; @@ -133,8 +133,8 @@ namespace ctrl { menu("WindowMenu", _("_Window")); - entry ([&]() { globalCtx_.windowList_.newWindow(); } , "WindowNewWindow", StockID("new_window")); - entry ([&]() { globalCtx_.windowList_.closeWindow();}, "WindowCloseWindow", _("Close Window")); + entry ([&]() { globalCtx_.windowLoc_.newWindow(); } , "WindowNewWindow", StockID("new_window")); + entry ([&]() { globalCtx_.windowLoc_.closeWindow();} , "WindowCloseWindow", _("Close Window")); actionGroup->add(Action::create("WindowShowPanel", _("_Show Panel"))); @@ -143,6 +143,10 @@ namespace ctrl { assetsPanelAction->signal_toggled().connect ( [&]() { onMenu_view_assets(); }); actionGroup->add(assetsPanelAction); + infoboxPanelAction = ToggleAction::create("ViewInfoBox", StockID("panel_infobox")); + infoboxPanelAction->signal_toggled().connect ( [&]() { onMenu_view_infobox(); }); + actionGroup->add(infoboxPanelAction); + timelinePanelAction = ToggleAction::create("ViewTimeline", StockID("panel_timeline")); timelinePanelAction->signal_toggled().connect( [&]() { onMenu_view_timeline(); }); actionGroup->add(timelinePanelAction); @@ -181,6 +185,7 @@ namespace ctrl { + @@ -282,9 +287,9 @@ namespace ctrl { cuString panelName = ustring::compose("Panel%1", i); actionGroup->add(Action::create(panelName, StockID(stock_id)), [i,this]() { - globalCtx_.windowList_.findActiveWindow() - .getPanelManager() - .showPanel (i); + globalCtx_.windowLoc_.findActiveWindow() + .getPanelManager() + .showPanel (i); }); } @@ -314,6 +319,16 @@ namespace ctrl { unimplemented ("view assets"); } + void + onMenu_view_infobox() + { + /////////////////////////////////////////////////////////////////////////////////////TODO defunct since GTK-3 transition + //if(!is_updating_action_state) + // workspaceWindow.infoboxPanel->show( + // infoboxPanelAction->get_active()); //////global -> InteractionDirector + unimplemented ("view infobox"); + } + void onMenu_view_timeline() { @@ -347,6 +362,7 @@ namespace ctrl { Glib::RefPtr actionGroup; Glib::RefPtr assetsPanelAction; + Glib::RefPtr infoboxPanelAction; Glib::RefPtr timelinePanelAction; Glib::RefPtr viewerPanelAction; diff --git a/src/gui/ctrl/bus-term.hpp b/src/gui/ctrl/bus-term.hpp index 9aae4415d..b2deb406d 100644 --- a/src/gui/ctrl/bus-term.hpp +++ b/src/gui/ctrl/bus-term.hpp @@ -60,10 +60,10 @@ #include "lib/error.hpp" +#include "lib/nocopy.hpp" #include "lib/idi/entry-id.hpp" #include "lib/diff/gen-node.hpp" -#include #include #include @@ -103,6 +103,7 @@ namespace ctrl{ * some element. */ class BusTerm + : util::MoveOnly { protected: using EntryID = lib::idi::BareEntryID; @@ -135,9 +136,6 @@ namespace ctrl{ /** may be moved, but not copied, * due to the embedded identity */ BusTerm(BusTerm&&) = default; - BusTerm(BusTerm const&) = delete; - - BusTerm& operator= (BusTerm const&) = delete; protected: /** diff --git a/src/gui/ctrl/core-service.hpp b/src/gui/ctrl/core-service.hpp index 19b39d86b..61cb7775a 100644 --- a/src/gui/ctrl/core-service.hpp +++ b/src/gui/ctrl/core-service.hpp @@ -81,6 +81,7 @@ #include "lib/error.hpp" +#include "lib/nocopy.hpp" #include "lib/idi/entry-id.hpp" #include "lib/diff/gen-node.hpp" #include "include/session-command-facade.h" @@ -88,8 +89,6 @@ #include "gui/ctrl/bus-term.hpp" #include "gui/ctrl/nexus.hpp" -#include - namespace gui { namespace ctrl{ @@ -107,7 +106,7 @@ namespace ctrl{ */ class CoreService : public BusTerm - , boost::noncopyable + , util::NonCopyable { Nexus uiBusBackbone_; diff --git a/src/gui/ctrl/facade.hpp b/src/gui/ctrl/facade.hpp index 173cd8b62..6d6093d2d 100644 --- a/src/gui/ctrl/facade.hpp +++ b/src/gui/ctrl/facade.hpp @@ -43,8 +43,10 @@ #define GUI_CTRL_FACADE_H #include "gui/notification-service.hpp" +#include "gui/display-service.hpp" +#include "lib/depend-inject.hpp" +#include "lib/nocopy.hpp" -#include namespace gui { @@ -59,9 +61,13 @@ namespace ctrl { * @remark the UiManager is responsible to activate and deactivate those interfaces */ class Facade - : boost::noncopyable + : util::NonCopyable { - NotificationService notificationService_; + using Instance_Notification = lib::DependInject::ServiceInstance<>; + using Instance_DisplayService = lib::DependInject::ServiceInstance<>; + + Instance_Notification notificationService_; + Instance_DisplayService displayService_; ///////////////////////////////////////////////////////TICKET #82 obsolete and will go away once we have a real OutputSlot offered by the UI public: @@ -69,7 +75,8 @@ namespace ctrl { * Activate all external UI facade interfaces. */ Facade (UiBus& bus, UiManager& manager) - : notificationService_(bus.getAccessPoint(), manager) // opens the GuiNotificationService instance + : notificationService_{bus.getAccessPoint(), manager} // opens the GuiNotificationService instance + , displayService_{} // opens the DisplayService instance ////////TICKET #82 obsolete { INFO (gui, "UI-Facade Interfaces activated."); } diff --git a/src/gui/ctrl/global-ctx.hpp b/src/gui/ctrl/global-ctx.hpp index ef1499c8c..36f728f79 100644 --- a/src/gui/ctrl/global-ctx.hpp +++ b/src/gui/ctrl/global-ctx.hpp @@ -40,7 +40,7 @@ ** - connection to the [UI-Bus](\ref ui-bus.hpp) ** - the UiManager ** - the InteractionDirector - ** - the WindowList + ** - the WindowLocator ** - the Wizard ** ** @see gtk-lumiera.hpp @@ -56,11 +56,11 @@ #include "gui/gtk-base.hpp" #include "gui/ui-bus.hpp" #include "gui/ctrl/ui-manager.hpp" -#include "gui/ctrl/window-list.hpp" +#include "gui/ctrl/window-locator.hpp" #include "gui/interact/wizard.hpp" #include "gui/interact/interaction-director.hpp" +#include "lib/nocopy.hpp" -#include //#include //#include @@ -80,14 +80,14 @@ namespace ctrl { * @remark the UiManager is responsible to install this top-level context */ class GlobalCtx - : boost::noncopyable + : util::NonCopyable { public: UiBus& uiBus_; UiManager& uiManager_; - WindowList windowList_; + WindowLocator windowLoc_; InteractionDirector director_; interact::Wizard wizard_; @@ -99,7 +99,7 @@ namespace ctrl { GlobalCtx (UiBus& bus, UiManager& manager) : uiBus_{bus} , uiManager_{manager} - , windowList_{*this} + , windowLoc_{*this} , director_{*this} , wizard_{*this} { } diff --git a/src/gui/ctrl/nexus.hpp b/src/gui/ctrl/nexus.hpp index d76a4e23a..3d6a93a9f 100644 --- a/src/gui/ctrl/nexus.hpp +++ b/src/gui/ctrl/nexus.hpp @@ -41,6 +41,7 @@ #include "lib/error.hpp" +#include "lib/nocopy.hpp" #include "lib/idi/genfunc.hpp" #include "lib/diff/mutation-message.hpp" #include "lib/diff/tree-diff-application.hpp" @@ -48,7 +49,6 @@ #include "gui/model/tangible.hpp" #include "lib/idi/entry-id.hpp" -#include #include @@ -75,7 +75,7 @@ namespace ctrl{ */ class Nexus : public BusTerm - , boost::noncopyable + , util::NonCopyable { typedef std::unordered_map RoutingTable; diff --git a/src/gui/ctrl/panel-locator.hpp b/src/gui/ctrl/panel-locator.hpp new file mode 100644 index 000000000..a618e4901 --- /dev/null +++ b/src/gui/ctrl/panel-locator.hpp @@ -0,0 +1,82 @@ +/* + PANEL-LOCATOR.hpp - manage all top level windows + + Copyright (C) Lumiera.org + 2017, Hermann Vosseler + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + + +/** @file panel-locator.hpp + ** Access and query front-end to locate, access and place docking panels. + ** This service is an implementation detail of the global WindowLocator and + ** allows to abstract from the concrete top-level window when dealing with + ** docking panels on a conceptual level + ** + ** @see view-locator.hpp + ** @see interaction-director.hpp + */ + + +#ifndef GUI_CTRL_PANEL_LOCATOR_H +#define GUI_CTRL_PANEL_LOCATOR_H + +#include "gui/gtk-base.hpp" +#include "lib/nocopy.hpp" + +#include +#include + + +namespace gui { +namespace workspace { class WorkspaceWindow; } +namespace ctrl { + + + using std::list; + + + + /** + * Service to access, locate or place panels within top-level windows + * Access- and query front-end to the individual panel managers, which are + * located within the top-level windows. + */ + class PanelLocator + : util::NonCopyable + { + using PWindow = std::shared_ptr; + using WindowList = list; + + WindowList& windowList_; + + + public: + PanelLocator (WindowList&); + + private: + + }; + + + + /** */ + + + +}}// namespace gui::ctrl +#endif /*GUI_CTRL_PANEL_LOCATOR_H*/ diff --git a/src/gui/ctrl/playback-controller.cpp b/src/gui/ctrl/playback-controller.cpp index 9152f1943..376600435 100644 --- a/src/gui/ctrl/playback-controller.cpp +++ b/src/gui/ctrl/playback-controller.cpp @@ -62,7 +62,7 @@ namespace ctrl { { if (not instance) throw error::Logic ("GTK UI is not in running state" - , error::LUMIERA_ERROR_LIFECYCLE); + , error::LERR_(LIFECYCLE)); return *instance; } diff --git a/src/gui/ctrl/playback-controller.hpp b/src/gui/ctrl/playback-controller.hpp index c55a1e733..e745875a4 100644 --- a/src/gui/ctrl/playback-controller.hpp +++ b/src/gui/ctrl/playback-controller.hpp @@ -42,8 +42,8 @@ #include "gui/gtk-base.hpp" #include "include/dummy-player-facade.h" #include "include/display-facade.h" +#include "lib/nocopy.hpp" -#include namespace gui { @@ -53,7 +53,7 @@ namespace ctrl { /** @deprecated we need a durable design for the playback process */ class PlaybackController - : boost::noncopyable + : util::NonCopyable { volatile bool playing_; diff --git a/src/gui/ctrl/state-manager.hpp b/src/gui/ctrl/state-manager.hpp index 0362fd7d3..7f2546a58 100644 --- a/src/gui/ctrl/state-manager.hpp +++ b/src/gui/ctrl/state-manager.hpp @@ -55,8 +55,8 @@ #include "lib/idi/entry-id.hpp" #include "lib/diff/gen-node.hpp" +#include "lib/nocopy.hpp" -#include #include @@ -77,7 +77,7 @@ namespace ctrl { * element. */ class StateManager - : boost::noncopyable + : util::NonCopyable { protected: virtual ~StateManager(); ///< this is an interface diff --git a/src/gui/ctrl/state-map-grouping-storage.hpp b/src/gui/ctrl/state-map-grouping-storage.hpp index c20184a9a..31025ae74 100644 --- a/src/gui/ctrl/state-map-grouping-storage.hpp +++ b/src/gui/ctrl/state-map-grouping-storage.hpp @@ -38,11 +38,11 @@ #include "lib/error.hpp" +#include "lib/nocopy.hpp" #include "lib/idi/entry-id.hpp" #include "lib/diff/gen-node.hpp" #include "lib/util.hpp" -#include #include #include #include @@ -72,7 +72,7 @@ namespace ctrl { * @see StateMapGroupingStorage_test */ class StateMapGroupingStorage - : boost::noncopyable + : util::NonCopyable { using StateData = std::set; using Storage = std::unordered_map; diff --git a/src/gui/ctrl/ui-dispatcher.hpp b/src/gui/ctrl/ui-dispatcher.hpp index 63f92d8f5..38eb70e23 100644 --- a/src/gui/ctrl/ui-dispatcher.hpp +++ b/src/gui/ctrl/ui-dispatcher.hpp @@ -77,8 +77,9 @@ #include "gui/gtk-base.hpp" #include "lib/call-queue.hpp" +#include "lib/format-string.hpp" +#include "lib/nocopy.hpp" -#include #include @@ -86,7 +87,19 @@ namespace gui { namespace ctrl { using std::move; + using ::util::_Fmt; + namespace { + /** @note reads and clears the lumiera error flag */ + inline string + generateErrorResponse (const char* problem = "unexpected problem") + { + static _Fmt messageTemplate{"asynchronous UI response failed: %s (error flag was: %s)"}; + string response{messageTemplate % problem % lumiera_error()}; + WARN (gui, "%s", response.c_str()); + return response; + } + } /** @@ -96,7 +109,7 @@ namespace ctrl { * it outlives the GTK event loop */ class UiDispatcher - : boost::noncopyable + : util::NonCopyable { lib::CallQueue queue_; Glib::Dispatcher dispatcher_; @@ -104,13 +117,23 @@ namespace ctrl { using Operation = lib::CallQueue::Operation; public: - UiDispatcher() + template + UiDispatcher(FUN notification) : queue_{} , dispatcher_{} { dispatcher_.connect( - [this]() { - queue_.invoke(); /////////////////////TICKET #1098 : ensure no exception escapes from here!! + [=]() {try { + queue_.invoke(); + } + catch (std::exception& problem) + { + notification (generateErrorResponse(problem.what())); + } + catch (...) + { + notification (generateErrorResponse()); + } }); } diff --git a/src/gui/ctrl/ui-manager.cpp b/src/gui/ctrl/ui-manager.cpp index 3a7fe218b..7a1f2f8eb 100644 --- a/src/gui/ctrl/ui-manager.cpp +++ b/src/gui/ctrl/ui-manager.cpp @@ -116,8 +116,8 @@ namespace ctrl { void UiManager::createApplicationWindow() { - if (globals_->windowList_.empty()) - globals_->windowList_.newWindow(); + if (globals_->windowLoc_.empty()) + globals_->windowLoc_.newWindow(); } @@ -158,7 +158,7 @@ namespace ctrl { UiManager::updateWindowFocusRelatedActions() { ///////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1076 find out how to handle this properly - actions_->updateActionState (globals_->windowList_.findActiveWindow()); + actions_->updateActionState (globals_->windowLoc_.findActiveWindow()); } diff --git a/src/gui/ctrl/ui-manager.hpp b/src/gui/ctrl/ui-manager.hpp index 13db171d0..4a17512ef 100644 --- a/src/gui/ctrl/ui-manager.hpp +++ b/src/gui/ctrl/ui-manager.hpp @@ -37,7 +37,7 @@ ** - the global Actions available though the menu ** - the InteractionDirector (top-level controller) ** - the StyleManager - ** - the WindowList + ** - the WindowLocator ** ** @see gtk-lumiera.hpp ** @see ui-bus.hpp @@ -48,8 +48,8 @@ #define GUI_CTRL_UI_MANAGER_H #include "gui/gtk-base.hpp" +#include "lib/nocopy.hpp" -#include #include #include @@ -72,7 +72,7 @@ namespace ctrl { /** Framework initialisation base */ class ApplicationBase - : boost::noncopyable + : util::NonCopyable { protected: ApplicationBase(); diff --git a/src/gui/ctrl/ui-state.hpp b/src/gui/ctrl/ui-state.hpp index 87ee14a71..2cbb6859f 100644 --- a/src/gui/ctrl/ui-state.hpp +++ b/src/gui/ctrl/ui-state.hpp @@ -55,8 +55,8 @@ #define GUI__H #include "gui/gtk-base.hpp" +#include "lib/nocopy.hpp" -#include //#include //#include @@ -82,7 +82,7 @@ namespace ctrl { * @todo initial draft as of 2/2017 -- actual implementation has to be filled in */ class UiState - : boost::noncopyable + : util::NonCopyable { StateManager& stateManager_; interact::FocusTracker& tracker_; diff --git a/src/gui/ctrl/window-list.cpp b/src/gui/ctrl/window-locator.cpp similarity index 81% rename from src/gui/ctrl/window-list.cpp rename to src/gui/ctrl/window-locator.cpp index f20ac112e..9dc8d7dda 100644 --- a/src/gui/ctrl/window-list.cpp +++ b/src/gui/ctrl/window-locator.cpp @@ -1,5 +1,5 @@ /* - WindowList - manage all top level windows + WindowLocator - manage all top level windows Copyright (C) Lumiera.org 2008, Joel Holdsworth @@ -21,14 +21,16 @@ * *****************************************************/ -/** @file window-list.cpp - ** Implementation parts of the list to manage all top level windows. +/** @file window-locator.cpp + ** Implementation details of management and access to all top level windows and docking panels. ** @see ui-manager.hpp */ #include "gui/ctrl/global-ctx.hpp" -#include "gui/ctrl/window-list.hpp" +#include "gui/ctrl/panel-locator.hpp" +#include "gui/ctrl/window-locator.hpp" +#include "gui/interact/elem-access-dir.hpp" #include "gui/workspace/workspace-window.hpp" #include "lib/util.hpp" @@ -47,20 +49,31 @@ namespace ctrl { - WindowList::WindowList (GlobalCtx& globals) - : globalCtx_{globals} - , windowList_{} + PanelLocator::PanelLocator (WindowList& all_top_level_wndows) + : windowList_{all_top_level_wndows} { } + + WindowLocator::WindowLocator (GlobalCtx& globals) + : globalCtx_{globals} + , windowList_{} + , panelLoc_{windowList_} + , elementAccess_{*this, panelLoc_} + { } + + WindowLocator::~WindowLocator () { } + + + void - WindowList::newWindow () + WindowLocator::newWindow () { PWindow window (new WorkspaceWindow{globalCtx_.uiManager_}); REQUIRE(window); window->signal_delete_event().connect(sigc::mem_fun( - this, &WindowList::on_window_closed)); + this, &WindowLocator::on_window_closed)); windowList_.push_back(window); @@ -76,7 +89,7 @@ namespace ctrl { * the fist one in list will be killed */ void - WindowList::closeWindow() + WindowLocator::closeWindow() { findActiveWindow().hide(); } @@ -90,7 +103,7 @@ namespace ctrl { * called when there is at least one Lumiera window. */ WorkspaceWindow& - WindowList::findActiveWindow() + WindowLocator::findActiveWindow() { REQUIRE (not isnil (windowList_)); @@ -108,7 +121,7 @@ namespace ctrl { * has keyboard focus. This may very well be the case. */ WorkspaceWindow& - WindowList::findFocusWindow() + WindowLocator::findFocusWindow() { REQUIRE (not isnil (windowList_)); @@ -122,7 +135,7 @@ namespace ctrl { bool - WindowList::on_window_closed (GdkEventAny* event) + WindowLocator::on_window_closed (GdkEventAny* event) { REQUIRE(event); REQUIRE(event->window); @@ -157,7 +170,7 @@ namespace ctrl { void - WindowList::updateCloseWindowInMenus() + WindowLocator::updateCloseWindowInMenus() { globalCtx_.uiManager_.allowCloseWindow ( 1 < windowList_.size()); } diff --git a/src/gui/ctrl/window-list.hpp b/src/gui/ctrl/window-locator.hpp similarity index 73% rename from src/gui/ctrl/window-list.hpp rename to src/gui/ctrl/window-locator.hpp index dca2e37a9..c00d36083 100644 --- a/src/gui/ctrl/window-list.hpp +++ b/src/gui/ctrl/window-locator.hpp @@ -1,5 +1,5 @@ /* - WINDOW-LIST.hpp - manage all top level windows + WINDOW-LOCATOR.hpp - manage all top level windows Copyright (C) Lumiera.org 2008, Joel Holdsworth @@ -21,10 +21,10 @@ */ -/** @file window-list.hpp +/** @file window-locator.hpp ** Manager for all top level application windows. - ** The central WindowList instance is owned by the GtkLumiera object and - ** initialised in GTK-main. The WindowList allows to create new windows + ** The central WindowLocator is part of the [UI global context](\ref GlobalCtx) and thus + ** initialised on startup of the UI. The WindowLocator allows to create new windows ** integrated with the application framework. ** ** @see gtk-lumiera.hpp @@ -32,12 +32,14 @@ */ -#ifndef GUI_CTRL_WINDOW_LIST_H -#define GUI_CTRL_WINDOW_LIST_H +#ifndef GUI_CTRL_WINDOW_LOCATOR_H +#define GUI_CTRL_WINDOW_LOCATOR_H #include "gui/gtk-base.hpp" +#include "gui/ctrl/panel-locator.hpp" +#include "lib/depend-inject.hpp" +#include "lib/nocopy.hpp" -#include #include #include @@ -48,10 +50,13 @@ namespace gui { namespace controller { class Controller; } ////////////////////////////////////////////////////TICKET #1048 : rectify UI lifecycle namespace workspace { class WorkspaceWindow; } +namespace model { + class ElementAccess; +} namespace ctrl { class GlobalCtx; - + class ElemAccessDir; using std::list; @@ -59,17 +64,22 @@ namespace ctrl { /** * A centralised manager of all top level application windows. */ - class WindowList - : boost::noncopyable + class WindowLocator + : ::util::NonCopyable { using PWindow = std::shared_ptr; + using Service_ElementAccess = lib::DependInject::ServiceInstance; GlobalCtx& globalCtx_; list windowList_; + PanelLocator panelLoc_; + + Service_ElementAccess elementAccess_; public: - WindowList (GlobalCtx&); + WindowLocator (GlobalCtx&); + ~WindowLocator (); bool empty() const; @@ -79,6 +89,8 @@ namespace ctrl { workspace::WorkspaceWindow& findActiveWindow(); workspace::WorkspaceWindow& findFocusWindow(); + PanelLocator& locatePanel() { return panelLoc_; } + private: @@ -104,7 +116,7 @@ namespace ctrl { inline bool - WindowList::empty() const + WindowLocator::empty() const { return windowList_.empty(); } @@ -112,4 +124,4 @@ namespace ctrl { }}// namespace gui::ctrl -#endif /*GUI_CTRL_WINDOW_LIST_H*/ +#endif /*GUI_CTRL_WINDOW_LOCATOR_H*/ diff --git a/src/gui/demo-gui-roundtrip.cpp b/src/gui/demo-gui-roundtrip.cpp index 77c217dc6..f22834533 100644 --- a/src/gui/demo-gui-roundtrip.cpp +++ b/src/gui/demo-gui-roundtrip.cpp @@ -30,7 +30,7 @@ ** - and sends a "mark" message towards some UI demo widget ** - which in turn displays the mark text ** - ** @todo WIP as of 8/20176 ////////////////////////////////////////////////////////////////////////TICKET #1099 + ** @todo WIP as of 8/2017 ////////////////////////////////////////////////////////////////////////TICKET #1099 ** ** @see CallQueue_test ** @see notification-service.hpp @@ -43,8 +43,8 @@ #include "lib/depend.hpp" #include "gui/ui-bus.hpp" #include "gui/ctrl/ui-manager.hpp" +#include "lib/nocopy.hpp" -#include #include @@ -77,14 +77,14 @@ namespace gui { * @see CallQueue_test */ class DemoGuiRoundtrip - : boost::noncopyable + : util::NonCopyable { string nothing_; DemoGuiRoundtrip(); ~DemoGuiRoundtrip(); - friend class lib::DependencyFactory; + friend class lib::DependencyFactory; public: /** access point to set up the scaffolding. diff --git a/src/gui/display-service.cpp b/src/gui/display-service.cpp index 7a6e07211..0aa975c59 100644 --- a/src/gui/display-service.cpp +++ b/src/gui/display-service.cpp @@ -33,6 +33,7 @@ #include "gui/display-service.hpp" +#include "lib/depend.hpp" extern "C" { #include "common/interface-descriptor.h" @@ -119,10 +120,9 @@ namespace gui { - using lumiera::facade::LUMIERA_ERROR_FACADE_LIFECYCLE; - typedef lib::SingletonRef::Accessor InstanceRef; + using lumiera::error::LERR_(LIFECYCLE); - InstanceRef _instance; ///< a backdoor for the C Language impl to access the actual DisplayService implementation... + lib::Depend _instance; ///< a backdoor for the C Language impl to access the actual SessionCommand implementation... @@ -136,14 +136,14 @@ namespace gui { { if (!_instance) { - lumiera_error_set(LUMIERA_ERROR_FACADE_LIFECYCLE, 0); + lumiera_error_set (LUMIERA_ERROR_LIFECYCLE, 0); return; } REQUIRE (slotHandle); try { - _instance->allocate (slotHandle,true); + _instance().allocate (slotHandle,true); } catch (lumiera::Error&){ /* error state remains set */ } } @@ -153,12 +153,12 @@ namespace gui { { if (!_instance) { - lumiera_error_set(LUMIERA_ERROR_FACADE_LIFECYCLE, 0); + lumiera_error_set (LUMIERA_ERROR_LIFECYCLE, 0); return; } REQUIRE (slotHandle); - _instance->allocate (slotHandle,false); + _instance().allocate (slotHandle,false); } ) , LUMIERA_INTERFACE_INLINE (put, @@ -168,7 +168,7 @@ namespace gui { REQUIRE (_instance && !lumiera_error_peek()); REQUIRE (slotHandle); - DisplayerSlot& slot = _instance->resolve (slotHandle); + DisplayerSlot& slot = _instance().resolve (slotHandle); slot.put (frame); } ) @@ -183,19 +183,18 @@ namespace gui { DisplayService::DisplayService() - : error_("") - , implInstance_(this,_instance) + : error_{} , serviceInstance_( LUMIERA_INTERFACE_REF (lumieraorg_Display, 0, lumieraorg_DisplayService)) - { - INFO (progress, "Display Facade opened."); - } + { + INFO (progress, "Display Facade opened."); + } LumieraDisplaySlot DisplayService::setUp (FrameDestination const& outputDestination) { - DisplayerTab& slots (_instance->slots_); + DisplayerTab& slots (_instance().slots_); return &slots.manage (new DisplayerSlot (outputDestination)); } diff --git a/src/gui/display-service.hpp b/src/gui/display-service.hpp index e7750bb3f..c0e168930 100644 --- a/src/gui/display-service.hpp +++ b/src/gui/display-service.hpp @@ -52,13 +52,12 @@ #include "include/display-facade.h" #include "common/instancehandle.hpp" -#include "lib/singleton-ref.hpp" #include "lib/scoped-ptrvect.hpp" #include "include/logging.h" +#include "lib/nocopy.hpp" #include #include -#include #include #include @@ -84,7 +83,7 @@ namespace gui { */ class DisplayerSlot : public lumiera_displaySlot, - boost::noncopyable + util::NonCopyable { Dispatcher dispatcher_; FrameSignal hasFrame_; @@ -128,7 +127,7 @@ namespace gui { * course of the play process for outputting frames. */ class DisplayService - : boost::noncopyable + : util::NonCopyable { string error_; @@ -140,17 +139,12 @@ namespace gui { typedef lumiera::InstanceHandle< LUMIERA_INTERFACE_INAME(lumieraorg_Display, 0) , lumiera::Display > ServiceInstanceHandle; - - lib::SingletonRef implInstance_; ServiceInstanceHandle serviceInstance_; public: DisplayService(); - ~DisplayService() { - INFO (proc_dbg, "Display service dying..."); - - } + ~DisplayService() { INFO (proc_dbg, "Display service dying..."); } /** open a new display, sending frames to the given output destination diff --git a/src/gui/util/cairo-util.cpp b/src/gui/draw/cairo-util.cpp similarity index 96% rename from src/gui/util/cairo-util.cpp rename to src/gui/draw/cairo-util.cpp index 4165c6317..227b7acb6 100644 --- a/src/gui/util/cairo-util.cpp +++ b/src/gui/draw/cairo-util.cpp @@ -26,10 +26,10 @@ */ -#include "gui/util/cairo-util.hpp" +#include "gui/draw/cairo-util.hpp" namespace gui { -namespace util { +namespace draw { RefPtr CairoUtil::pattern_set_red (const RefPtr color, double red) @@ -83,4 +83,4 @@ namespace util { return Cairo::SolidPattern::create_rgba (red, blue, green, alpha); } -}}// namespace gui::util +}}// namespace gui::draw diff --git a/src/gui/util/cairo-util.hpp b/src/gui/draw/cairo-util.hpp similarity index 95% rename from src/gui/util/cairo-util.hpp rename to src/gui/draw/cairo-util.hpp index 788c5bc06..a3e0ff935 100644 --- a/src/gui/util/cairo-util.hpp +++ b/src/gui/draw/cairo-util.hpp @@ -28,15 +28,15 @@ #include -#ifndef GUI_UTIL_CAIRO_H -#define GUI_UTIL_CAIRO_H +#ifndef GUI_DRAW_CAIRO_H +#define GUI_DRAW_CAIRO_H using Cairo::RefPtr; using Cairo::SolidPattern; namespace gui { -namespace util { +namespace draw { /** * @todo this is not a real entity; consider to turn this into a set of free functions! @@ -78,5 +78,5 @@ namespace util { }; -}}// namespace gui::util -#endif /*GUI_UTIL_CAIRO_H*/ +}}// namespace gui::draw +#endif /*GUI_DRAW_CAIRO_H*/ diff --git a/src/gui/util/rectangle.cpp b/src/gui/draw/rectangle.cpp similarity index 95% rename from src/gui/util/rectangle.cpp rename to src/gui/draw/rectangle.cpp index 9a1dfcf0f..4f2215efc 100644 --- a/src/gui/util/rectangle.cpp +++ b/src/gui/draw/rectangle.cpp @@ -25,7 +25,7 @@ ** Implementation of functions to work with rectangular screen ranges */ -#include "gui/util/rectangle.hpp" +#include "gui/draw/rectangle.hpp" #include @@ -33,7 +33,7 @@ using std::max; using std::min; namespace gui { -namespace util{ +namespace draw{ bool pt_in_rect(const Gdk::Point &point, const Gdk::Rectangle &rect) @@ -57,4 +57,4 @@ namespace util{ } -}}// namespace gui::util +}}// namespace gui::draw diff --git a/src/gui/util/rectangle.hpp b/src/gui/draw/rectangle.hpp similarity index 90% rename from src/gui/util/rectangle.hpp rename to src/gui/draw/rectangle.hpp index 89f91d2a2..5d8fb4ef8 100644 --- a/src/gui/util/rectangle.hpp +++ b/src/gui/draw/rectangle.hpp @@ -26,13 +26,13 @@ */ -#ifndef GUI_UTIL_RECTANGLE_H -#define GUI_UTIL_RECTANGLE_H +#ifndef GUI_DRAW_RECTANGLE_H +#define GUI_DRAW_RECTANGLE_H #include "gui/gtk-base.hpp" namespace gui { -namespace util { +namespace draw { /** determine whether point is within the bounding box of a rectangle */ bool pt_in_rect(Gdk::Point const& point, Gdk::Rectangle const& rect); @@ -42,5 +42,5 @@ namespace util { bool rects_overlap(Gdk::Rectangle const& a, Gdk::Rectangle const& b); -}}// namespace gui::util -#endif // RECTANGLE_HPP +}}// namespace gui::draw +#endif /*GUI_DRAW_RECTANGLE_H*/ diff --git a/src/gui/gtk-base.hpp b/src/gui/gtk-base.hpp index b981bbfc6..e4c5a48b1 100644 --- a/src/gui/gtk-base.hpp +++ b/src/gui/gtk-base.hpp @@ -149,8 +149,13 @@ namespace gui { /** Lumiera custom widgets. */ namespace widget {} - /** GUI helpers, utility functions and classes. */ - namespace util {} + /** Display and drawing helpers and utilities. */ + namespace draw {} + + /** generic definitions for element ID and access. + * @see id-scheme.hpp + */ + namespace idi {} }// namespace gui diff --git a/src/gui/gtk-lumiera.cpp b/src/gui/gtk-lumiera.cpp index a566b3de1..674ef7f27 100644 --- a/src/gui/gtk-lumiera.cpp +++ b/src/gui/gtk-lumiera.cpp @@ -52,16 +52,15 @@ #include "gui/ui-bus.hpp" #include "gui/guifacade.hpp" #include "gui/ctrl/ui-manager.hpp" -#include "gui/display-service.hpp" #include "backend/thread-wrapper.hpp" #include "common/subsys.hpp" +#include "lib/nocopy.hpp" extern "C" { #include "common/interface.h" #include "common/interface-descriptor.h" } -#include #include @@ -84,22 +83,19 @@ namespace gui { /**************************************************************************//** * Implement the necessary steps for actually making the Lumiera Gui available. * Establish the UI backbone services and start up the GTK GUI main event loop. - * @todo to ensure invocation of the termination signal, any members - * should be failsafe on initialisation (that means, we must not - * open other interfaces here...) ///////////////////////////TICKET #82 + * @warning to ensure reliable invocation of the termination signal, + * any members should be failsafe on initialisation */ class GtkLumiera - : boost::noncopyable + : util::NonCopyable { UiBus uiBus_; UiManager uiManager_; - DisplayService activateDisplayService_; ///////////////////////////TICKET #82 will go away once we have a real OutputSlot offered by the UI public: GtkLumiera () : uiBus_{} , uiManager_{uiBus_} - , activateDisplayService_() ///////////////////////////TICKET #82 obsolete (and incurs a race) { } diff --git a/src/gui/guifacade.hpp b/src/gui/guifacade.hpp index ce8318088..67958a343 100644 --- a/src/gui/guifacade.hpp +++ b/src/gui/guifacade.hpp @@ -39,12 +39,12 @@ #include "common/subsys.hpp" +#include "lib/nocopy.hpp" extern "C" { #include "common/interface.h" } -#include namespace gui { @@ -77,7 +77,7 @@ namespace gui { * */ class GuiFacade - : boost::noncopyable + : util::NonCopyable { public: diff --git a/src/gui/id-scheme.hpp b/src/gui/id-scheme.hpp new file mode 100644 index 000000000..a22bc9a97 --- /dev/null +++ b/src/gui/id-scheme.hpp @@ -0,0 +1,175 @@ +/* + ID-SCHEME.hpp - naming and ID scheme definitions for the GUI + + Copyright (C) Lumiera.org + 2017, Hermann Vosseler + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + + +/** @file id-scheme.hpp + ** Hard wired definitions for the object identification system used within the UI. + ** + ** # Identities and element addressing + ** + ** Within the UI-Layer, all components of global relevance are connected to the [UI-Bus](\ref ui-bus.hpp) + ** and can thus be reached just by knowing their ID. This allows to address such UI components as + ** [generic UI-Element](\ref model::Tangible), relying on a generic element manipulation protocol. + ** Beyond this rather unspecific manipulation scheme, the UI relies on direct ownership relations. + ** Typically, some element is created as result of another action and managed as child of some + ** maintaining entity; generally speaking, UI elements live _free floating_ and are mostly + ** interconnected by signals to react on user interaction. There is a rather limited + ** [global UI-Context](\ref GlobalCtx) of directly interconnected backbone services, + ** which allow to achieve activities cross-cutting the ownership relationship. + ** + ** Element IDs are always formed as EntryID, typed to the corresponding type in the Session model. + ** Thus, starting from a given model element, it is always possible to "cast" some message towards + ** the corresponding UI view element, without actually knowing much about that element's implementation. + ** + ** @note This header provides the basic definitions for easily accessing relevant UI elements. + ** + ** @see interaction-director.hpp + ** @see ui-bus.hpp + ** @see tangible.hpp + ** @see bus-term.hpp + ** @see entry-id.hpp + ** + */ + +#ifndef GUI_ID_SCHEME_H +#define GUI_ID_SCHEME_H + + +#include "lib/symbol.hpp" +#include "lib/idi/entry-id.hpp" +#include "gui/interact/view-spec-dsl.hpp" + + +/* === forward declarations === */ +namespace proc { + namespace asset { + class Timeline; + } +} + +namespace gui { + namespace timeline { + class TimelineController; + } + namespace widget { + class ErrorLogWidget; + } + namespace idi { + + using lib::Literal; + using lib::idi::EntryID; + using interact::UICoord; + + + using ID = lib::idi::BareEntryID const&; + + /* === primary component view types === */ + using TimelineView = timeline::TimelineController; + using ErrorLogView = widget::ErrorLogWidget; + + + /** Generic Component View descriptors */ + template + struct Descriptor + { + static_assert (not sizeof(V), "unknown generic view type"); + }; + + + /** + * Descriptor for the Timeline UI + */ + template<> + struct Descriptor + { + using Model = proc::asset::Timeline; + }; + + + /** + * Descriptor for error log display within the UI + */ + template<> + struct Descriptor + { + ///////////////////////////////////////////////////////////////////////////////////////////TICKET #1105 : need a model placeholder to represent UI specific global entities + + ///////////////////////////////////////////////////////////////////////////////////////////TICKET #1105 : consider use of a DSL to configure component view access + // + // alloc = onePerWindow + // locate = within(InfoBoxPanel) + }; + +/* +///////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1105 : DSL design draft... +// Timeline +// add to group of timelines within the timelinePanel + +alloc = unlimited +locate = perspective(edit).panel(timeline) + or panel(timeline) + or currentWindow().panel(timeline).create() + +// Viewer +// here multiple alternatives are conceivable +// - allow only a single view instance in the whole application + +alloc = onlyOne +locate = external(beamer) + or view(viewer) + or perspective(mediaView).panel(viewer) + or panel(viewer) + or firstWindow().panel(viewer).view(viewer).create() + +// - allow two viewer panels (the standard layout of editing applications) + +alloc = limitPerWindow(2) +locate = perspective(edit).panel(viewer) + or currentWindow().panel(viewer) + or panel(viewer) + or currentWindow().panel(viewer).create() + +// (Asset)Bin +// within the dedicated asset panel, add to the appropriate group for the kind of asset + +alloc = unlimited +locate = currentWindow().perspective(edit).tab(assetType()) + or perspective(asset).view(asset) + or tab(assetType()) + or view(asset).tab(assetType()).create() + or firstWindow().panel(asset).view(asset).create() + +// Error-Log +// use the current `InfoBoxPanel` if such exists, fall back to using a single view on the primary window + +alloc = limitPerWindow(1) +locate = currentWindow().panel(infobox) + or view(error) + or panel(infobox) + or firstWindow().panel(infobox).view(error).create() + +*/ + + + }//namespace gui::idi +} // namespace gui +#endif /*GUI_ID_SCHEME_H*/ diff --git a/src/gui/interact/cmd-context.cpp b/src/gui/interact/cmd-context.cpp index c9f9c7f88..b95c7bece 100644 --- a/src/gui/interact/cmd-context.cpp +++ b/src/gui/interact/cmd-context.cpp @@ -34,7 +34,6 @@ //#include "include/logging.h" #include "gui/interact/cmd-context.hpp" -//#include //#include //#include diff --git a/src/gui/interact/cmd-context.hpp b/src/gui/interact/cmd-context.hpp index 99c443177..5adbdb279 100644 --- a/src/gui/interact/cmd-context.hpp +++ b/src/gui/interact/cmd-context.hpp @@ -42,6 +42,7 @@ ** context-bound command becomes executable by picking up suitable parameter values from this context. ** ** @todo as of 3/2017 this is a early design draft and WIP-WIP-WIP + ** @todo after initial design draft, postponed as of 10/2017 ** ** @see CmdContext_test ** @see ////TODO_test usage example @@ -54,13 +55,13 @@ #include "lib/error.hpp" +#include "lib/nocopy.hpp" //#include "gui/ctrl/bus-term.hpp" //#include "lib/idi/entry-id.hpp" #include "lib/hash-indexed.hpp" #include "lib/symbol.hpp" //#include "lib/util.hpp" -#include #include @@ -80,7 +81,7 @@ namespace interact { * @todo write type comment... */ class CmdContext - : boost::noncopyable + : util::NonCopyable { public: diff --git a/src/gui/interact/elem-access-dir.hpp b/src/gui/interact/elem-access-dir.hpp new file mode 100644 index 000000000..32b1baa42 --- /dev/null +++ b/src/gui/interact/elem-access-dir.hpp @@ -0,0 +1,114 @@ +/* + ELEM-ACCESS-DIR.hpp - service to access generic elements in the UI + + Copyright (C) Lumiera.org + 2018, Hermann Vosseler + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + + +/** @file elem-access-directory.hpp + ** A service to discover and access raw UI elements in a cross cutting way. + ** This is the actual low-level implementation of the model::ElementAccess interface + ** Tightly coupled to the internals of Lumiera's GTK UI, this implementation embodies + ** all the inside knowledge necessary to navigate over the various levels (Windows, + ** panels, views, specific components) to get at those elements abstracted as + ** UI-Coordinates. + ** + ** @note as of 4/2018 this is a first draft and will remain unimplemented for the time being + ** @todo WIP-WIP-WIP need to learn more about the concrete UI implementation ///////////////////////TICKET #1134 + ** + ** @see view-locator.hpp + ** @see navigator.hpp + ** + */ + + +#ifndef GUI_CTRL_ELEM_ACCESS_DIR_H +#define GUI_CTRL_ELEM_ACCESS_DIR_H + + +#include "lib/error.hpp" +#include "gui/model/element-access.hpp" +#include "gui/ctrl/window-locator.hpp" +#include "gui/ctrl/panel-locator.hpp" +//#include "lib/symbol.hpp" +//#include "lib/util.hpp" + +//#include + + + +namespace gui { +namespace ctrl{ + +// using util::isnil; +// using std::string; + using interact::UICoord; + + + /** + * Low-level service to navigate the internals of the Lumiera GTK UI. + * @todo mostly not yet implemented as of 4/2018 -- need to learn more about aforementioned internals. + */ + class ElemAccessDir + : public model::ElementAccess + { + WindowLocator& windowLoc_; + PanelLocator& panelLoc_; + + public: + explicit + ElemAccessDir (WindowLocator& windowLocatorService, PanelLocator& panelLocatorService) + : windowLoc_{windowLocatorService} + , panelLoc_{panelLocatorService} + { } + + + + /* == ElementAccess interface == */ + + RawResult + performAccessTo (UICoord const&, size_t limitCreation) override + { + UNIMPLEMENTED ("implementation of access function based on real UI widgets"); + } + + +#if false /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #1134 + if (isDeaf()) + this->transmogrify (solution); +#endif /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #1134 + + + protected: + }; + + + + + + + /** @internal in case + */ + + + + + +}} // namespace gui::ctrl +#endif /*GUI_CTRL_ELEM_ACCESS_DIR_H*/ diff --git a/src/gui/interact/focus-tracker.hpp b/src/gui/interact/focus-tracker.hpp index dd1dd1c08..862194e6c 100644 --- a/src/gui/interact/focus-tracker.hpp +++ b/src/gui/interact/focus-tracker.hpp @@ -55,8 +55,8 @@ #define GUI_INTERACT_FOCUS_TRACKER_H #include "gui/gtk-base.hpp" +#include "lib/nocopy.hpp" -#include //#include //#include @@ -81,7 +81,7 @@ namespace interact { * @todo initial draft as of 2/2017 -- actual implementation has to be filled in */ class FocusTracker - : boost::noncopyable + : util::NonCopyable { Navigator& navigator_; diff --git a/src/gui/interact/interaction-director.cpp b/src/gui/interact/interaction-director.cpp index e8605ad38..b8a66a50e 100644 --- a/src/gui/interact/interaction-director.cpp +++ b/src/gui/interact/interaction-director.cpp @@ -31,9 +31,11 @@ #include "gui/ctrl/bus-term.hpp" #include "gui/ctrl/global-ctx.hpp" #include "gui/interact/interaction-director.hpp" +#include "gui/interact/view-locator.hpp" #include "gui/interact/spot-locator.hpp" #include "gui/interact/navigator.hpp" #include "gui/interact/focus-tracker.hpp" +#include "gui/interact/ui-coord-resolver.hpp" #include "gui/dialog/preferences-dialog.hpp" #include "gui/dialog/render.hpp" #include "gui/workspace/workspace-window.hpp" @@ -82,8 +84,9 @@ namespace interact { InteractionDirector::InteractionDirector (GlobalCtx& globals) : model::Controller(session::Root::getID(), globals.uiBus_.getAccessPoint()) , globalCtx_(globals) + , viewLocator_{new ViewLocator} , spotLocator_{new SpotLocator} - , navigator_{new Navigator{*spotLocator_}} + , navigator_{*spotLocator_, *viewLocator_} // Service exposed as Depend , tracker_{new FocusTracker{*navigator_}} , uiState_{new UiState{globals.uiBus_.getStateManager(), *tracker_}} , assets_{new AssetController{session::Root::getAssetID(), this->uiBus_}} @@ -261,7 +264,7 @@ namespace interact { workspace::WorkspaceWindow& InteractionDirector::getWorkspaceWindow() { - return globalCtx_.windowList_.findActiveWindow(); + return globalCtx_.windowLoc_.findActiveWindow(); } diff --git a/src/gui/interact/interaction-director.hpp b/src/gui/interact/interaction-director.hpp index b169c7e3a..952e1285c 100644 --- a/src/gui/interact/interaction-director.hpp +++ b/src/gui/interact/interaction-director.hpp @@ -55,8 +55,8 @@ //#include "gui/gtk-base.hpp" #include "gui/model/controller.hpp" +#include "lib/depend-inject.hpp" -//#include //#include //#include #include @@ -86,9 +86,12 @@ namespace interact { using std::unique_ptr; //class Actions; - class SpotLocator; + class LocationQuery; + class Navigator; + class SpotLocator; class FocusTracker; + class ViewLocator; @@ -101,9 +104,14 @@ namespace interact { { ctrl::GlobalCtx& globalCtx_; + // == exposed to Depend on == + using Service_LocationQuery = lib::DependInject::ServiceInstance; + + // == global Services == + unique_ptr viewLocator_; unique_ptr spotLocator_; - unique_ptr navigator_; + Service_LocationQuery navigator_; unique_ptr tracker_; // == Model globals == diff --git a/src/gui/interact/interaction-state.cpp b/src/gui/interact/interaction-state.cpp index 427ac6b20..1f70fc529 100644 --- a/src/gui/interact/interaction-state.cpp +++ b/src/gui/interact/interaction-state.cpp @@ -34,7 +34,6 @@ //#include "include/logging.h" #include "gui/interact/interaction-state.hpp" -//#include //#include //#include diff --git a/src/gui/interact/interaction-state.hpp b/src/gui/interact/interaction-state.hpp index 2c176b2d7..0c287d336 100644 --- a/src/gui/interact/interaction-state.hpp +++ b/src/gui/interact/interaction-state.hpp @@ -39,12 +39,12 @@ #include "lib/error.hpp" +#include "lib/nocopy.hpp" #include "gui/ctrl/bus-term.hpp" #include "lib/idi/entry-id.hpp" //#include "lib/symbol.hpp" //#include "lib/util.hpp" -#include #include @@ -59,9 +59,10 @@ namespace interact { /** * Abstract foundation of UI state tracking components. * @todo write type comment... + * ///////////////////////////////////TODO do we need a translation unit interaction-state.cpp (otherwise delete it!) */ class InteractionState - : boost::noncopyable + : util::NonCopyable { protected: diff --git a/src/gui/interact/navigator.cpp b/src/gui/interact/navigator.cpp index e8e05a22c..dce9e238f 100644 --- a/src/gui/interact/navigator.cpp +++ b/src/gui/interact/navigator.cpp @@ -23,8 +23,14 @@ /** @file navigator.cpp ** Implementation of global interface navigation mechanisms. + ** Especially we implement the LocationQuery interface, which exposes the structures + ** of the UI as an abstracted, tree-shaped topology. This task adds up to levelling all the + ** specifics of accessing individual components and to assemble them into a virtual component tree. + ** The actual details of component access are thereby delegated to the ViewLocator, which is a sibling + ** service maintained by the InteractionDirector. ** ** @todo WIP 2/2017 early draft / foundations of "interaction control" + ** @todo WIP 1/2018 integrating the concept of UI-Coordinate navigation and resolution. Still WIP-WIP-WIP... */ @@ -47,11 +53,36 @@ namespace interact { { } - Navigator::Navigator (SpotLocator& spotLocator) - : spotLocator_{spotLocator} + Navigator::Navigator (SpotLocator& spotLoc, ViewLocator& viewLoc) + : spotLocator_{spotLoc} + , viewLocator_{viewLoc} { } + /* ==== implementing the LocationQuery API ==== */ + + Literal + Navigator::determineAnchor (UICoord const& path) + { + UNIMPLEMENTED ("LocationQuery in real UI: resolve anchor point of given UI-Coordinates"); + } + + + size_t + Navigator::determineCoverage (UICoord const& path) + { + UNIMPLEMENTED ("LocationQuery in real UI: determine explicit coverage of given UI-Coordinates"); + } + + + LocationQuery::ChildIter + Navigator::getChildren (UICoord const& path, size_t pos) + { + UNIMPLEMENTED ("LocationQuery in real UI: build child iterator rooted at given point in the UI tree"); + } + + + /** */ diff --git a/src/gui/interact/navigator.hpp b/src/gui/interact/navigator.hpp index dae0599ac..93b80603f 100644 --- a/src/gui/interact/navigator.hpp +++ b/src/gui/interact/navigator.hpp @@ -39,8 +39,9 @@ #define GUI_INTERACT_NAVIGATOR_H #include "gui/gtk-base.hpp" +#include "gui/interact/ui-coord-resolver.hpp" +#include "lib/nocopy.hpp" -#include //#include //#include @@ -53,23 +54,32 @@ namespace interact { // class GlobalCtx; class SpotLocator; + class ViewLocator; /** - * Global cross-cutting navigation in interface space + * Global cross-cutting navigation within user interface space * * @todo initial draft as of 2/2017 -- actual implementation has to be filled in + * @see UiCoordResolver */ class Navigator - : boost::noncopyable + : public LocationQuery + , util::NonCopyable { SpotLocator& spotLocator_; + ViewLocator& viewLocator_; public: - Navigator (SpotLocator&); + Navigator (SpotLocator&, ViewLocator&); ~Navigator (); + /* === LocationQuery API === */ + Literal determineAnchor (UICoord const& path) override final; + size_t determineCoverage (UICoord const& path) override final; + ChildIter getChildren (UICoord const& path, size_t pos) override final; + private: }; diff --git a/src/gui/interact/spot-locator.hpp b/src/gui/interact/spot-locator.hpp index 98f92f967..fe5ccbef9 100644 --- a/src/gui/interact/spot-locator.hpp +++ b/src/gui/interact/spot-locator.hpp @@ -40,8 +40,8 @@ #define GUI_INTERACT_SPOT_LOCATOR_H #include "gui/gtk-base.hpp" +#include "lib/nocopy.hpp" -#include //#include //#include @@ -62,7 +62,7 @@ namespace interact { * @todo initial draft as of 2/2017 -- actual implementation has to be filled in */ class SpotLocator - : boost::noncopyable + : util::NonCopyable { public: diff --git a/src/gui/interact/ui-coord-resolver.cpp b/src/gui/interact/ui-coord-resolver.cpp new file mode 100644 index 000000000..9ce33e6ff --- /dev/null +++ b/src/gui/interact/ui-coord-resolver.cpp @@ -0,0 +1,208 @@ +/* + UICoordResolver - resolve UI coordinate spec against actual window topology + + Copyright (C) Lumiera.org + 2017, Hermann Vosseler + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +* *****************************************************/ + + +/** @file ui-coord-resolver.cpp + ** Implementation details of resolving a UICoord path against the actual UI topology. + ** Algorithm to match a UI-Coord spec, possibly with wildcards, against the current actual tree of UI widgets. + ** + */ + + +#include "gui/interact/ui-coord-resolver.hpp" +#include "lib/util.hpp" + +using util::isnil; +using lib::Symbol; +using lib::treeExplore; + + +namespace gui { +namespace interact { + + // emit typeinfo and VTables here.... + TreeStructureNavigator::~TreeStructureNavigator() { } + + LocationQuery::~LocationQuery() { } + + /** storage for the global LocationQuery service access point */ + lib::Depend LocationQuery::service; + + + + + namespace { // Helpers for patch matching algorithm + + /** + * Special UI-Coordinate builder + * to define the solution path step by step + * while we recurse down into the reference tree + * @note when backtracking, an existing solution will + * be partially rewritten starting from lower depth + */ + class PathManipulator + : public UICoord + { + size_t currDepth_; + + public: + PathManipulator () + : UICoord{} + , currDepth_{0} + { } + // inherited copy operations + + UICoord const& + retrieveResult() + { + PathArray::truncateTo(currDepth_); + return *this; + } + + void + setAt (size_t depth, Literal newSpec) + { + Literal* storage = PathArray::expandPosition (depth); + PathArray::setContent (storage, newSpec); + currDepth_ = depth+1; + } + }; + + + size_t + find_wildcardFree_suffix (UICoord const& uic) + { + size_t pos = uic.size(); + for ( ; 0 < pos; --pos) + { + Literal const& elm = uic[pos-1]; + if (elm == Symbol::ANY or elm == Symbol::EMPTY) + break; + } + return pos; + } + + + }//(End) implementation details + + + + /** + * Since UICoord path specifications may contain gaps and wildcards, we may attempt + * to fill in these missing parts by matching against the topological structure of an actual UI. + * In the general case, finding a solution requires a depth-first exponential brute-force search + * over the whole structure tree, since we have to try every possible branch until we can disprove + * the possibility of a match. Implemented as depth-first search with backtracking, this scanning + * pass produces a list of possible matches, from which we pick the first one with maximum + * coverage, to yield a single solution. + * @remark the search and matching is based on an iterator pipeline builder, with the special ability + * to expand and recurse into the children of the current element on demand: when `expandChildren()` + * was invoked, the next iteration will continue with the first child element; there is a stack of + * such "child expansions" -- meaning that the search will backtrack and explore further possibilities + * later on. Each position where the pattern matches the actual tree is marked as possible solution. + * As a sideeffect, a new coordinate spec to reflect the actual coverage is built and re-written, + * while the algorithm proceeds. Thus, at any point marked as solution, the current (partial) + * solution can be retrieved and copied from this PathManipulator buffer. + * An additional filter layer discriminates the first maximal solutions seen thus far. + */ + bool + UICoordResolver::pathResolution() + { + // Helper to detect a wildcard match + auto wildMatch = [&](Literal patt, Literal curr, size_t depth) + { + return patt == Symbol::ANY + or patt == Symbol::EMPTY + or patt == UIC_ELIDED // "existentially quantified" + or (isAnchored() and curr == res_.anchor and depth == UIC_WINDOW); + }; // transitive argument: assuming res_.anchor was computed for + // the same coordinate pattern used here for patch resolution + // algorithm state + size_t maxDepth = 0; + PathManipulator coverage; + const size_t coordDepth = this->uic_.size(); + const size_t minSolutionDepth = find_wildcardFree_suffix (uic_); + + auto searchAlgo = query_.getChildren (uic_, 0) + .expandOnIteration() + .filter ([&](auto& iter) + { + size_t depth = iter.depth(); // we are at that depth in target tree + if (depth >= coordDepth) // search pattern exhausted without match... + return false; + Literal patt = uic_[depth]; // pick search pattern component at that depth + Literal curr = *iter; // iterator points at current tree position (ID) + if (patt == curr or // if either direct match + wildMatch(patt,curr,depth)) // or wildcard match + { + coverage.setAt (depth,curr); // record match rsp. interpolate wildcard into output + iter.expandChildren(); // next iteration will match one level down into the tree + } + return patt == curr // direct match counts as (partial) solution + or patt == UIC_ELIDED; // existentially quantified elements also accepted + }) + .filter ([&](auto& iter) + { + if (iter.depth() < minSolutionDepth) + return false; // filter solutions which did not bind all wildcards + if (iter.depth()+1 <= maxDepth) // filter for maximum solution length + return false; + maxDepth = 1 + iter.depth(); + return true; + }) + .transform ([&](auto&) -> UICoord const& + { + return coverage.retrieveResult(); + }); + // is (partial) coverage possible? + // search computes definitive answer! + res_.isResolved = true; + + // perform the matching + if (isnil (searchAlgo)) + return false; // no solution found + + while (searchAlgo) // pull first maximal solution + { + if (not res_.covfefe) + res_.covfefe.reset (new UICoord {*searchAlgo}); + else + *res_.covfefe = *searchAlgo; + ++searchAlgo; + } + + ENSURE (res_.covfefe and res_.covfefe->size() >= 1); + res_.anchor = res_.covfefe->getWindow(); + + // but depth reflects only that part coverable without wildcards + if (res_.depth == 0) + res_.depth = query_.determineCoverage(uic_); + if (res_.depth == 0 and res_.anchor) + res_.depth = 1; + + // signal success only when total coverage is possible + return res_.covfefe->size() == uic_.size(); + } + + + +}}// namespace gui::interact diff --git a/src/gui/interact/ui-coord-resolver.hpp b/src/gui/interact/ui-coord-resolver.hpp new file mode 100644 index 000000000..36686f954 --- /dev/null +++ b/src/gui/interact/ui-coord-resolver.hpp @@ -0,0 +1,601 @@ +/* + UI-COORD-RESOLVER.hpp - resolve UI coordinate spec against actual window topology + + Copyright (C) Lumiera.org + 2017, Hermann Vosseler + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + + +/** @file ui-coord-resolver.hpp + ** Evaluation of UI coordinates against a concrete window topology. + ** [UI-Coordinates](\ref UICoord) allow to describe and locate an interface component within the + ** Lumiera GUI through a topological access path. As such these coordinate specifications are abstract, + ** and need to be related, attached or resolved against the actual configuration of widgets in the UI. + ** Through this relation it becomes possible to pose meaningful queries over these coordinates, or to + ** build, rebuild and remould a coordinate specification. + ** + ** We need to avoid tainting with the intrinsics of the actual UI toolkit though -- which indicates + ** the UICoordResolver should be designed as an abstract intermediary, built on top of a command and + ** query interface, provided by the \ref Navigator and backed by the actual UI configuration. + ** + ** # Abstraction + ** + ** The abstraction used to found this interface is twofold. For one, we rely on the notion of logical, + ** topological [Coordinates in User Interface space](\ref UICoord). And secondly, we rely on a very + ** limited form of navigation: we navigate a tree-shaped (abstracted) structure just by + ** - iteration over siblings, which are children of our previous starting point + ** - the ability, _on this iterator,_ to expand the "current child" and inject + ** the next level of child iteration at its place, similar to the `flatMap` + ** operation known from functional programming. + ** Together, these two capabilities allow us to build exploring and backtracking evaluations, + ** which is enough to build a secondary helper component on top, the gui::interact::UICoordResolver + ** + ** + ** # UI coordinate path evaluation + ** + ** Such a _resolver_ can be used to relate and match a given, incomplete UI coordinate specification + ** (a "pattern") against the actual UI topology. Evaluation is accomplished by first constituting an anchoring, + ** followed by traversal of the coordinate spec and matching against a navigation path within the actual UI window + ** configuration. This process might involve interpretation of some meta-symbols and interpolation of wildcards. + ** + ** As indicated above, the coordinate resolver internally relies on a [context query interface](\ref LocationQuery), + ** to find out about existing windows, panels, views and tabs and to navigate the real UI structure. The actual + ** implementation of this context query interface is backed by the \ref Navigator component exposed through the + ** \ref InteractionDirector. + ** + ** ## Query operations + ** In addition to the _locally decidable properties_ of a [coordinate spec](\ref UICoord), which are the explicitness + ** and the presence of some component, several contextual predications may be queried: + ** + ** - *anchorage* + ** ** the way the given coordinate spec is or can be anchored + ** *** it is already _explicitly anchored_ by referring either to a specific window or by generic specification + ** *** it _can be a anchored_ by interpolation of some wildcards + ** *** it is _incomplete_ and need to be extended to allow anchoring + ** *** it is _impossible to anchor_ in the current UI configuration + ** + ** - *coverage* + ** ** the extent to which a given coordinate spec is backed by the actual UI configuration + ** ** _please note_: to determine the coverage, the spec needs to be anchored, either explicitly, + ** or by interpolation, or by extension of an incomplete spec + ** *** it is _completely covered_ + ** *** it is _partially covered_ with an remaining, uncovered extension part + ** *** it is _possible to cover completely_ + ** *** it is _impossible to cover_ related to the current UI topology + ** + ** \par Some fine points to note + ** Anchorage and coverage are not the same thing, but coverage implies anchorage. Only when a path is complete + ** (i.e. it starts with the window spec) and explicit (has no wildcards), then anchorage implies also partial coverage + ** (namely at least to depth 1). To determine the possibility of coverage means to perform a resolution with backtracking + ** to pick the maximal solution. Moreover, since "covered" means that the path specification _is at least partially supported + ** by the real UI,_ we establish an additional constraint to ensure this resolution did not just match some arbitrary wildcards. + ** Rather we demand that behind rsp. below the last wildcard there is at least one further explicit component in the path spec, + ** which is supported by the real UI. As a consequence, the coverage resolution may fail altogether, while still providing at + ** least a possible anchor point. + ** + ** ## Mutations + ** In addition to querying the interpretation of a given coordinate spec with respect to the current UI environment, + ** it is also possible to rewrite or extend the spec based on this environment + ** + ** - *anchoring* + ** ** in correspondence to the possible states of anchorage, we may derive an explicitly anchored spec + ** *** by interpolating the given spec + ** *** by interpretation and extension of the given spec + ** + ** - *covering* + ** ** we may construct the covered part of a given spec, which includes automatic anchoring + ** + ** - *extending* + ** ** a given UI coordinate pattern is covered... + ** ** and _truncated_ to the covered part + ** ** the given _extension suffix_ is then attached behind + ** + ** @see UICoordResolver_test + ** @see UICoord_test + ** @see navigator.hpp + ** @see view-locator.hpp + */ + + +#ifndef GUI_INTERACT_UI_COORD_RESOLVER_H +#define GUI_INTERACT_UI_COORD_RESOLVER_H + +#include "lib/error.hpp" +#include "lib/symbol.hpp" +#include "lib/format-string.hpp" +#include "gui/interact/ui-coord.hpp" +#include "lib/iter-tree-explorer.hpp" +#include "lib/iter-source.hpp" +#include "lib/depend.hpp" +#include "lib/util.hpp" + +#include +#include + + +namespace gui { +namespace interact { + + namespace error = lumiera::error; + + using std::unique_ptr; + using util::unConst; + using lib::Literal; + using lib::Symbol; + + + + + /** + * Interface to locate and move within a tree shaped structure. + * The actual nature of this structure is to be kept abstracted through this interface. + * The purpose of this construct is to build evaluations and matching operations on top. + */ + class TreeStructureNavigator + : public lib::IterSource + { + public: + virtual ~TreeStructureNavigator(); ///< this is an interface + + /** expand into exploration of child elements at "current position". + * At any point, a TreeStructureNavicator instance indicates and represents a position + * within a tree-like structure. At the same time, it is part of a sequence of siblings, + * which is accessible through iteration. This operation now allows to extend visitation + * of siblings by consuming the current element and replacing it with the sequence of its + * immediate child elements, exposing the first one as the _"current position"_. + * @return pointer to a new heap allocated TreeStructureNavigator implementation, which + * represents the sequence of children. The object `this` will not be affected. + * @note it is the caller's responsibility to own and manage the generated navigator. + * The typical (and recommended) way to achieve this is to rely on the embedded + * type #iterator, which exposes an appropriately wired iterator::expandChildren() + */ + virtual TreeStructureNavigator* expandChildren() const =0; + + + /** build a Lumiera Forward Iterator as front-end and managing Handle for a TreeStructureNavigator + * or subclass. The provided pointer is assumed to point to heap allocated storage. + * @return copyable iterator front-end handle, which allows to retrieve once all values + * yielded by this IterSource. The front-end _takes ownership_ of the given object. + * @note the generated iterator is preconfigured to allow for _"child expansion"_, thereby + * calling through the virtual API function expandChildren() + */ + static auto + buildIterator (TreeStructureNavigator* source) + { + return lib::treeExplore (source) + .expand([](TreeStructureNavigator& parent){ return parent.expandChildren(); }); + } + }; + + + + + + /** + * Interface to discover a backing structure for the purpose of path navigation and resolution. + * UICoord are meant to designate a position within the logical structure of an UI -- yet in fact + * they may be resolved against any tree-like topological structure, which can be queried through + * this interface. + * @see Navigator the implementation used in the Lumiera UI, as backed by actual GUI components + * @see InteractionDirector manages the Navigator and exposes it via lib::Depend + * @see GenNodeLocationQuery a dummy/test implementation, where the "UI topology" is hard wired + * as a tree of GenNode elements. This serves the purpose of unit testing, without having + * to rely on an actual UI. + */ + class LocationQuery + { + public: + virtual ~LocationQuery(); ///< this is an interface + + /** access point to global LocationQuery service implementation */ + static lib::Depend service; + + + using ChildIter = decltype (TreeStructureNavigator::buildIterator(0)); + + + /** make the real anchor point explicit. + * @param path an explicit UICoord spec to be anchored in the actual UI + * @return _explicit_ literal window name, where the path can be anchored + * Symbol::BOTTOM in case the given path can not be anchored currently. + * @remark here "to anchor" means to match and thus "attach" the starting point + * of the UIcoord path, i.e. the window spec, with an actual top-level + * window existing in the current UI configuration and state. This operation + * either confirms existence of a window given by explicit ID, or it supplies + * the current meaning of the meta specs `currentWindow` and `firstWindow`, + * again in the form of an explicit window name + */ + virtual Literal determineAnchor (UICoord const& path) =0; + + + /** evaluate to what extent a UIcoord spec matches the actual UI + * @return the depth to which the given spec is _"covered"_ by the actual UI. + * Can be zero, in which case the given coordinates can not be resolved + * and addressed within the currently existing windows, panes and views. + * @remark a depth > 0 also implies that the path can be _anchored._ + * @note this operation does not perform any _resolution_ or interpolation of wildcards, + * it just matches explicit UI component names. + * @see UICoordResolver for a facility to perform such a resolution and to navigate paths. + */ + virtual size_t determineCoverage (UICoord const& path) =0; + + /** get the sequence of child components at a designated position in the actual UI + * @param path an explicit UIcoord spec, expected to be anchored and at least partially + * covered within the current configuration and state of the UI + * @param pos depth where the given path shall be evaluated, starting with 0 at window level + * @return an iterator to enumerate all child components actually existing in the current + * UI below the location designated by path and pos. + * @remark the path is only evaluated up to (not including) the given depth. Especially + * when `pos == 0`, then the path is not evaluated and matched at all, rather + * just the current list of top-level windows is returned. + * @throw error::State when navigating the given path touches a non-existing element + */ + virtual ChildIter getChildren (UICoord const& path, size_t pos) =0; + }; + + + + + + + /** + * Query and mutate UICoord specifications in relation to actual UI topology. + * This specialised builder uses a _location query facility_ to retrieve information about + * the current actual UI topology. The UI coordinate spec given as initialisation acts as a + * _pattern_, to be *queried and resolved* against that actual UI topology in various ways + * - determine the _anchor point_, where this UI coordinate pattern will be rooted in the UI + * - determine to which extent this UI coordinate pattern can be _"covered"_ (= supported) + * by the exiting UI. To resolve this query, it is necessary to perform a matching search + * with backtracking, in order to find the best possible coverage + * ** the coverage can be _complete_, which means that all components mentioned explicitly + * within the pattern in fact exist in the actual UI + * ** a _partial coverage_ means that there is a prefix actually supported, while some + * extraneous tailing components do not (yet) exist in the UI. + * Moreover, the pattern can be *mutated to conform* with the existing UI topology: + * - the anchor point can be made explicit, which means to replace the _meta specifications_ + * ** `firstWindow` + * ** `currentWindow` + * - the calculated _coverage solution_ can be interpolated into the pattern, thereby binding + * and replacing any placeholders ("wildcards", i.e. components designated as `"*"`) + * - we may _extend_ the pattern by attaching further elements, _behind_ the covered part. + * @note this is a _coordinate builder,_ which means that it works on a copy of the UI coordinate + * pattern provided at initialisation. The result, a possibly reworked UI coordinate spec + * can be _moved out_ into a new target UI coordinate (which is then immutable). The + * various query and binding operations work by side-effect on the internal state + * encapsulated within this builder. + * @warning computing a coverage solution for a sparsely defined pattern against a large + * UI topology can be expensive, since in worst case we have to perform a depth-first + * scan of the whole tree structure + */ + class UICoordResolver + : public UICoord::Builder + { + + struct Resolution + { + const char* anchor = nullptr; + size_t depth = 0; + unique_ptr covfefe{}; + bool isResolved = false; + }; + + LocationQuery& query_; + Resolution res_; + + public: + UICoordResolver (UICoord const& uic, LocationQuery& queryAPI) + : Builder{uic} + , query_{queryAPI} + , res_{} + { + attempt_trivialResolution(); + } + + UICoordResolver (UICoord && uic, LocationQuery& queryAPI) + : Builder{std::move(uic)} + , query_{queryAPI} + , res_{} + { + attempt_trivialResolution(); + } + + + /* === query functions === */ + + /** is this path explicitly anchored at an existing window? + * @remarks this also implies the path is complete and explicit (no wildcards). + */ + bool + isAnchored() const + { + return res_.anchor + and res_.anchor != Symbol::BOTTOM; + } + + /** determine if a mutation is possible to anchor the path explicitly + * @remarks basically this either means the path isAnchored(), or we're able + * to calculate a path resolution, interpolating any wildcards. + * And while the path resolution as such might fail, it was at least + * successful to determine an anchor point. The existence of such an + * anchor point implies the path is not totally in contradiction to the + * existing UI */ + bool + canAnchor() const + { + return isAnchored() + or (res_.isResolved and res_.covfefe) + or unConst(this)->pathResolution() + or isAnchored(); // resolution failed, but computed at least an anchor + } + + /** is this path at least _partially_ covered? + * A covered path describes an access path through widgets actually existing in the UI. + * @remark this also implies the path is anchored, complete and explicit. + * @note this predicate tests for _partial_ coverage, which means, there might + * be some extraneous suffix in this path descending beyond existing UI + */ + bool + isCoveredPartially() const + { + return res_.isResolved + and res_.depth > 0; + } + + /** this path is completely covered by the currently existing UI structure; + * @remark there is no extraneous uncovered suffix in this path spec; + * moreover, the path is anchored, complete and explicit + */ + bool + isCovered() const + { + return res_.isResolved + and res_.depth == this->uic_.size(); + } + + /** synonymous to #isCovered() */ + bool + isCoveredTotally() const + { + return isCovered(); + } + + + /** determine if a mutation is possible to get the path (partially) covered. + * @remarks in order to be successful, a path resolution must interpolate any gaps in the + * path spec _and_ reach a point behind / below the gap (wildcards), where an existing + * explicitly stated component in the path can be confirmed (covered) by the existing UI. + * The idea behind this definition is that we do not want just some interpolated wildcards, + * rather we really want to _confirm_ the essence of the path specification. Yet we accept + * an extraneous suffix _in the explicitly given part_ of the path spec to extend beyond or + * below what exists currently within the UI. + */ + bool + canCover() const + { + return isCovered() // either explicit coverage known + or (res_.isResolved and res_.covfefe) // or previous matching run found (partial) solution + or unConst(this)->pathResolution() // perform matching run now to find total coverage + or (res_.covfefe); // or at least partial coverage was found + } + + + + /* === mutation functions === */ + + /** mutate the path to get it totally covered + * - make the anchorage explicit + * - possibly match and expand any wildcards + * - then truncate the UI-Coordinate spec to that part + * actually covered by the UI + * @note if the coordinate spec can not be covered at all, + * it will be truncated to zero size + */ + UICoordResolver&& + cover() + { + if (isCoveredPartially() and not res_.covfefe) + { + ASSERT (res_.anchor); // depth > 0 implies anchorage + window (res_.anchor); // thus make this anchor explicit + truncateTo (res_.depth); + } + else if (canCover()) + { + ASSERT (res_.isResolved); + REQUIRE (res_.covfefe); + res_.depth = res_.covfefe->size(); + this->uic_ = std::move (*res_.covfefe); + res_.covfefe.reset(); + } + else + { + ASSERT (res_.isResolved); + REQUIRE (res_.depth == 0); + REQUIRE (not res_.covfefe); + truncateTo (0); + } + ENSURE (isCovered()); + return std::move (*this); + } + + + /** mutate the path by resolving all wildcards to achieve partial coverage + * - anchorage and all wildcards will be resolved against current UI + * - but an extraneous, uncovered, explicit suffix is retained + * @note if the coordinate spec can not be (partially) covered at all, + * it will be truncated to zero size + */ + UICoordResolver&& + coverPartially() + { + if (isCoveredPartially() and not res_.covfefe) + { + ASSERT (res_.anchor); + window (res_.anchor); // just ensure the anchor info is explicit, + } // the rest is already in place and explicit + else if (canCover()) + { + ASSERT (res_.isResolved); + REQUIRE (res_.covfefe); + REQUIRE (uic_.size() >= res_.covfefe->size()); + res_.depth = res_.covfefe->size(); + // possibly overwrite placeholders by explicitly resolved info... + for (size_t pos = 0; pos < res_.depth; ++pos ) + overwrite (pos, (*res_.covfefe)[pos]); + res_.covfefe.reset(); + } + else + { + ASSERT (res_.isResolved); + REQUIRE (res_.depth == 0); + REQUIRE (not res_.covfefe); + truncateTo (0); + } + ENSURE (empty() or (isCoveredPartially() and uic_.isExplicit())); + return std::move (*this); // no wildcards remain + } + + + /** mutate the window part of the path such as + * to make the anchorage explicit, if possible + * @remark if the path starts with meta specs like + * `firstWindow` or `currentWindow`, they will be + * replaced by their current meaning. If the path + * is incomplete, but can somehow be resolved, we + * use the anchorage as indicated by that resolution, + * without interpolating the rest of the path. + */ + UICoordResolver&& + anchor() + { + if (canAnchor()) + { + window (res_.anchor); + normalise(); + } + return std::move (*this); + } + + + + /** mutate the path to extend it while keeping it partially covered + * @param pathExtension a literal specification, which is extended immediately + * behind the actually covered part of the path, irrespective of the depth + * @note the extension may contain '/', which are treated as component separators + */ + UICoordResolver&& + extend (Literal pathExtension) + { + if (not isCovered()) + cover(); + ENSURE (isCovered()); + append (pathExtension); + res_.depth = query_.determineCoverage (this->uic_); // coverage may grow + return std::move (*this); + } + + /** mutate the path and extend it with components at fixed positions + * @param partialExtensionSpec UI coordinates used as extension + * @throw if the extension would overwrite the covered part. + * @remark this is the typical use case, where we want to place some component + * explicitly at a given depth (e.g. a new view or tab) + */ + UICoordResolver&& + extend (UICoord const& partialExtensionSpec) + { + if (not canCover()) + uic_ = partialExtensionSpec; + else + { + REQUIRE (res_.isResolved); + size_t coverable = res_.covfefe? res_.covfefe->size() : res_.depth; + auto newContent = partialExtensionSpec.begin(); + size_t extensionPos = newContent? partialExtensionSpec.indexOf(*newContent) : coverable; + if (coverable > extensionPos) + throw error::Invalid (util::_Fmt{"Attempt to extend covered path %s with %s " + "would overwrite positions %d to %d (incl)"} + % (res_.covfefe? *res_.covfefe : UICoord{uic_.rebuild().truncateTo(res_.depth)}) + % partialExtensionSpec + % extensionPos + % (coverable-1)); + cover(); + for ( ; newContent; ++newContent, ++extensionPos ) + overwrite (extensionPos, *newContent); + normalise(); + } + res_ = Resolution{}; // start over with pristine resolution state + attempt_trivialResolution(); + canCover(); + return std::move (*this); + } + + + /** mutate to turn a wildcard into _existentially quantified._ + * This means to assume (or require) that an element actually exists at the given position, + * without knowing or caring about its actual name. This becomes relevant when matching for + * _partially covered_ path; normal wildcards are only accepted to build a solution, when + * the matching can be confirmed by an explicit element after (below) the wildcard part. + * While this restriction serves to prevent ambiguous and surprising arbitrary matches, + * it can sometimes defeat any solution, e.g. when creating a new panel on top of an + * existing but irrelevant perspective. + */ + UICoordResolver&& + existentiallyQuantify (size_t pos) + { + if (pos < uic_.size() and not uic_.isPresent(pos)) + overwrite (pos, UIC_ELIDED); + return std::move (*this); + } + + + + /** diagnostics */ + operator string() const { return string(this->uic_); } + size_t coverDepth() const { return res_.depth; } + + + + private: + /** establish a trivial anchorage and coverage, if possible. + * @note when the UICoord contains wildcards or is incomplete, + * a full resolution with backtracking is necessary to + * determine anchorage and coverage. The same is true + * when the pattern uses an elided element, since such + * an existentially quantified item must be interpolated. + */ + void + attempt_trivialResolution() + { + res_.anchor = query_.determineAnchor (this->uic_); + if (not uic_.isExplicit()) return; + res_.depth = query_.determineCoverage(this->uic_); + if (util::contains (this->uic_, UIC_ELIDED)) return; // existentially quantified + res_.isResolved = true; + } + + /** @internal algorithm to resolve this UICoord path against the actual UI topology. + * @return true if total coverage is possible (by interpolating wildcards) + * @remark after invoking this function, res_.isResolved and possible coverage are set. + */ + bool pathResolution(); + }; + + + +}}// namespace gui::interact +#endif /*GUI_INTERACT_UI_COORD_RESOLVER_H*/ diff --git a/src/gui/interact/ui-coord.hpp b/src/gui/interact/ui-coord.hpp new file mode 100644 index 000000000..6d3f4fabb --- /dev/null +++ b/src/gui/interact/ui-coord.hpp @@ -0,0 +1,763 @@ +/* + UI-COORD.hpp - generic topological location addressing scheme within the UI + + Copyright (C) Lumiera.org + 2017, Hermann Vosseler + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + + +/** @file ui-coord.hpp + ** A topological addressing scheme to designate structural locations within the UI. + ** Contrary to screen pixel coordinates, we aim at a topological description of the UI structure. + ** This foundation allows us + ** - to refer to some "place" or "space" within the interface + ** - to remember and return to such a location + ** - to move a work focus structurally within the UI + ** - to describe and configure the pattern view access and arrangement + ** + ** As starting point, we'll pick the notion of an access path within a hierarchical structure + ** - the top-level window + ** - the perspective used within that window + ** - the panel within this window + ** - a view group within the panel + ** - plus a locally defined access path further down to the actual UI element + ** + ** @todo WIP 9/2017 first draft ////////////////////////////////////////////////////////////////////////////TICKET #1106 generic UI coordinate system + ** + ** @note UICoord is designed with immutability in mind; possibly we may decide to disallow assignment. + ** + ** # Coordinate Builder + ** + ** Since UI-Coordinates are conceived as immutable values, we provide a distinct _Builder notation_ + ** for the purpose of defining and assembling coordinate specifications. This notation also helps + ** to keep such specifications more readable, since each component is "tagged" with its intended + ** position within the tree, and thus the order of definition is irrelevant. Relying on that + ** notation, it is also possible to write _sparse_ or _incomplete_ coordinate specifications, + ** since each component _not explicitly given_ is assumed to be a _wildcard_ (`*`). E.g. + ** + ** UICoord::currentWindow() + ** .path("a/b/c") + ** .tab(3) + ** .view("Timeline") + ** + ** is translated into the following UI-Coordinate specification + ** + ** UI::currentWindow[*]-*.Timeline.#3/a/b/c + ** + ** which indicates the currently active main window, disregarding the perspective, just some + ** panel, holding the view "Timeline", selecting the third tab/group within this view and there + ** to descend the path of local sub-components "c" nested within "b" nested within "a" + ** + ** @warning beware, all the builder functions offer a *move* reference! + ** These builder expressions are intended for typical inline constant expressions + ** according to the *builder pattern*; any other use, like assignment of the builder + ** itself to a variable might be dangerous, since, after using the builder result + ** _just once_, the result has been moved away. Even worse, this fact may remain + ** concealed, since short Coordinate specs use inline storage, which the compiler + ** may choose to copy instead of actually moving; only the heap allocated extension + ** will typically be moved. + ** + ** @see UICoord_test + ** @see id-scheme.hpp + ** @see view-spec-dsl.hpp + ** @see view-locator.hpp + */ + + +#ifndef GUI_INTERACT_UI_COORD_H +#define GUI_INTERACT_UI_COORD_H + +#include "lib/error.hpp" +#include "lib/symbol.hpp" +#include "lib/path-array.hpp" +#include "lib/nocopy.hpp" +#include "lib/util.hpp" + +#include +#include +#include +#include + + +namespace gui { +namespace interact { + + namespace error = lumiera::error; + + using std::string; + using lib::Literal; + using lib::Symbol; + using util::unConst; + using util::isnil; + using util::min; + + enum { + UIC_INLINE_SIZE = 8 + }; + + /* === predefined DSL symbols === */ + + enum UIPathElm + { + UIC_WINDOW, + UIC_PERSP, + UIC_PANEL, + UIC_VIEW, + UIC_TAB, + UIC_PATH + }; + + + extern const Symbol UIC_CURRENT_WINDOW; ///< window spec to refer to the _current window_ @see view-locator.cpp + extern const Symbol UIC_FIRST_WINDOW; ///< window spec to refer to the _first window_ of the application + extern const Symbol UIC_ELIDED; ///< indicate that a component is elided or irrelevant here + + + + /** + * Describe a location within the UI through structural/topological coordinates. + * A UICoord specification is a sequence of Literal tokens, elaborating a path descending + * through the hierarchy of UI elements down to the specific UI element to refer. + * @see UICoord_test + */ + class UICoord + : public lib::PathArray + { + + public: + /** + * UI-Coordinates can be created explicitly by specifying a sequence of Literal tokens, + * which will be used to initialise and then normalise the underlying PathArray. + * @warning Literal means _"literal"_ with guaranteed storage during the whole execution. + * @remarks - in case you need to construct some part, then use \ref Symbol to _intern_ + * the resulting string into the global static SymbolTable. + * - usually the Builder API leads to more readable definitions, + * explicitly indicating the meaning of the coordinate's parts. + */ + template + explicit + UICoord (ARGS&& ...args) : PathArray(std::forward (args)...) { } + + UICoord (UICoord&&) = default; + UICoord (UICoord const&) = default; + UICoord (UICoord& o) : UICoord((UICoord const&)o) { } + + UICoord& operator= (UICoord const&) = default; + UICoord& operator= (UICoord &&) = default; + + + + /* === Builder API === */ + + class Builder; + + /** shortcut to allow init from builder expression */ + UICoord (Builder&& builder); + + /** Builder: start definition of UI-Coordinates rooted in the `firstWindow` */ + static Builder firstWindow(); + + /** Builder: start definition of UI-Coordinates rooted in the `currentWindow` */ + static Builder currentWindow(); + + /** Builder: start definition of UI-Coordinates rooted in given window */ + static Builder window (Literal windowID); + + //----- convenience shortcuts to start a copy-builder.... + Builder persp (Literal perspectiveID) const; + Builder panel (Literal panelID)const; + Builder view (Literal viewID) const; + Builder tab (Literal tabID) const; + Builder tab (uint tabIdx) const; + Builder noTab () const; + + //----- convenience shortcuts to start mutation on a copy... + Builder path (Literal pathDefinition) const; + Builder append (Literal elmID) const; + Builder prepend (Literal elmID) const; + Builder rebuild() const; + + + + /* === named component access === */ + + Literal getWindow() const { return accesComponent (UIC_WINDOW);} + Literal getPersp() const { return accesComponent (UIC_PERSP); } + Literal getPanel() const { return accesComponent (UIC_PANEL); } + Literal getView() const { return accesComponent (UIC_VIEW); } + Literal getTab() const { return accesComponent (UIC_TAB); } + + + + /* === query functions === */ + + /** + * @remark _incomplete_ UI-Coordinates have some fragment of the path + * defined, but lacks the definition of an anchor point, + * i.e. it has no window ID + */ + bool + isIncomplete() const + { + return not empty() + and isnil (getWindow()); + } + + bool + isComplete() const + { + return not empty() + and not isnil (getWindow()); + } + + + /** + * @remark an _explicit_ coordinate spec does not use wildcards + * and is anchored in a window spec + */ + bool + isExplicit() const + { + return isComplete() + and not util::contains (*this, Symbol::ANY); + } + + + bool + isPresent (size_t idx) const + { + Literal* elm = unConst(this)->getPosition(idx); + return not isnil(elm) + and *elm != Symbol::ANY; + } + + + bool + isWildcard (size_t idx) const + { + Literal* elm = unConst(this)->getPosition(idx); + return elm + and *elm == Symbol::ANY; + } + + + /** + * Check if this coordinate spec can be seen as an extension + * of the given parent coordinates and thus reaches further down + * towards specific UI elements in comparison to the parent path + * This constitutes a _partial order_, since some paths might just + * be totally unrelated to each other and thus not comparable. + * @note we tolerate (but not demand) expansion/interpolation + * of the given parent, i.e. parent may be incomplete + * or contain `'*'` placeholders. + * @todo 10/2017 have to verify suitability of this definition + */ + bool + isExtendedBelow (UICoord const& parent) const + { + size_t subSiz = this->size(), + parSiz = parent.size(), + idx = 0; + + if (parSiz >= subSiz) + return false; + + while (idx < parSiz + and ( (*this)[idx]== parent[idx] + or Symbol::ANY == parent[idx] + or isnil (parent[idx]))) + ++idx; + + ENSURE (idx < subSiz); + return idx == parSiz; + } // meaning: this goes further down + + + + /* === String representation === */ + + explicit + operator string() const + { + if (isnil (*this)) + return "UI:?"; + + string component = getComp(); + string path = getPath(); + + if (isnil (component)) + return "UI:?/" + path; + + if (isnil (path)) + return "UI:" + component; + else + return "UI:" + component + "/" + path; + } + + string + getComp() const + { + if (empty()) return ""; + + size_t end = min (size(), UIC_PATH); + size_t pos = indexOf (*begin()); + + if (pos >= end) + return ""; // empty or path information only + + string buff; + buff.reserve(80); + + if (0 < pos) // incomplete UI-Coordinates (not anchored) + buff += "?"; + + for ( ; posbegin(); + buff += "?/"; + } + + for ( ; elm; ++elm ) + buff += *elm + "/"; + + // chop off last delimiter + size_t len = buff.length(); + ASSERT (len >= 1); + buff.resize(len-1); + return buff; + } + + /** iterative access to the path sequence section */ + iterator + pathSeq() const + { + return size()<= UIC_PATH? end() + : iterator{this, unConst(this)->getPosition(UIC_PATH)}; + } + + + private: + /** @note Builder allowed to manipulate stored data */ + friend class Builder; + + size_t + findStartIdx() const + { + REQUIRE (not empty()); + return indexOf (*begin()); + } + + Literal + accesComponent (UIPathElm idx) const + { + Literal* elm = unConst(this)->getPosition(idx); + return elm? *elm : Symbol::EMPTY; + } + + void + setComponent (size_t idx, Literal newContent) + { + Literal* storage = expandPosition (idx); + setContent (storage, newContent); + } + + + /** replace / overwrite existing content starting at given index. + * @param idx where to start adding content; storage will be expanded to accommodate + * @param newContent either a single element, or several elements delimited by `'/'` + * @note - a path sequence will be split at `'/'` and the components _interned_ + * - any excess elements will be cleared + * @warning need to invoke PathArray::normalise() afterwards + */ + void + setTailSequence (size_t idx, Literal newContent) + { + std::vector elms; + if (not isnil (newContent)) + { + if (not std::strchr (newContent, '/')) + { + // single element: just place it as-is + // and remove any further content behind + elms.emplace_back (newContent); + } + else + { // it is actually a sequence of elements, + // which need to be split first, and then + // interned into the global symbol table + string sequence{newContent}; + size_t pos = 0; + size_t last = 0; + while (string::npos != (last = sequence.find ('/', pos))) + { + elms.emplace_back (Symbol{sequence.substr(pos, last - pos)}); + pos = last + 1; // delimiter stripped + } + sequence = sequence.substr(pos); + if (not isnil (sequence)) + elms.emplace_back (Symbol{sequence}); + } } + + setTailSequence (idx, elms); + } + + /** replace the existing path information with the given elements + * @note - storage will possibly be expanded to accommodate + * - the individual path elements are required to persist + * - any excess elements will be cleared + * - the pathElms can be _empty_ in which case just + * any content starting from `idx` will be cleared + * @warning need to invoke PathArray::normalise() afterwards + */ + void + setTailSequence (size_t idx, std::vector& pathElms) + { + size_t cnt = pathElms.size(); + expandPosition (idx + cnt); // preallocate + for (size_t i=0 ; i < cnt; ++i) + setContent (expandPosition(idx + i), pathElms[i]); + size_t end = size(); + for (size_t i = idx+cnt; i (l) == static_cast (r); + } + + friend bool + operator< (UICoord const& l, UICoord const& r) + { + return l.isExtendedBelow (r); + } + + friend bool operator> (UICoord const& l, UICoord const& r) { return (r < l); } + friend bool operator<= (UICoord const& l, UICoord const& r) { return (l < r) or (l == r); } + friend bool operator>= (UICoord const& l, UICoord const& r) { return (r < l) or (l == r); } + friend bool operator!= (UICoord const& l, UICoord const& r) { return not (l == r); } + }; + + + + + + /* === Builder API === */ + + class LocationClause; + + class UICoord::Builder + : util::MoveOnly + { + protected: + UICoord uic_; + + template + explicit + Builder (ARGS&& ...args) : uic_{std::forward (args)...} { } + Builder (UICoord && anonRef) : uic_{std::move(anonRef)} { } + Builder (UICoord const& base) : uic_{base} { } + + /** builder instances created by UICoord */ + friend class UICoord; + + public: + /** @remark moving a builder instance is acceptable */ + Builder (Builder &&) = default; + + size_t size() const { return uic_.size(); } + bool empty() const { return uic_.empty();} + + + /* == Builder functions == */ + + /** change UI coordinate spec to define it to be rooted within the given window + * @note this function allows to _undefine_ the window, thus creating an incomplete spec */ + Builder&& + window (Literal windowID) + { + uic_.setComponent (UIC_WINDOW, windowID); + return std::move (*this); + } + + /** augment UI coordinates to mandate a specific perspective to be active within the window */ + Builder&& + persp (Literal perspectiveID) + { + uic_.setComponent (UIC_PERSP, perspectiveID); + return std::move (*this); + } + + /** augment UI coordinates to indicate a specific view to be used */ + Builder&& + panel (Literal panelID) + { + uic_.setComponent (UIC_PANEL, panelID); + return std::move (*this); + } + + /** augment UI coordinates to indicate a specific view to be used */ + Builder&& + view (Literal viewID) + { + uic_.setComponent (UIC_VIEW, viewID); + return std::move (*this); + } + + /** augment UI coordinates to indicate a specific tab within the view" */ + Builder&& + tab (Literal tabID) + { + uic_.setComponent (UIC_TAB, tabID); + return std::move (*this); + } + + /** augment UI coordinates to indicate a tab specified by index number */ + Builder&& + tab (uint tabIdx) + { + uic_.setComponent (UIC_TAB, Symbol{"#"+util::toString (tabIdx)}); + return std::move (*this); + } + + /** augment UI coordinates to indicate that no tab specification is necessary + * @remarks typically this happens when a panel just holds a simple view */ + Builder&& + noTab() + { + uic_.setComponent (UIC_TAB, UIC_ELIDED); + return std::move (*this); + } + + + /** augment UI coordinates by appending a further component at the end. + * @note the element might define a sequence of components separated by `'/'`, + * in which case several elements will be appended. + */ + Builder&& + append (Literal elm) + { + if (not isnil(elm)) + uic_.setTailSequence (uic_.size(), elm); + return std::move (*this); + } + + /** augment partially defined UI coordinates by extending them towards the root */ + Builder&& + prepend (Literal elmID) + { + if (not uic_.isIncomplete()) + throw error::Logic ("Attempt to prepend "+elmID + +" to the complete rooted path "+string(uic_)); + + uic_.setComponent (uic_.findStartIdx() - 1, elmID); + return std::move (*this); + } + + /** + * augment UI coordinates to define a complete local path + * @param pathDef a path, possibly with multiple components separated by `'/'` + * @note any existing path definition is completely replaced by the new path + */ + Builder&& + path (Literal pathDef) + { + uic_.setTailSequence (UIC_PATH, pathDef); + return std::move (*this); + } + + /** possibly shorten this path specification to a limited depth */ + Builder&& + truncateTo (size_t depth) + { + uic_.truncateTo (depth); + return std::move (*this); + } + + /** support for use in the [ViewSpec-DSL](\ref ui-location-solver.hpp) */ + operator LocationClause(); + LocationClause create(); + + protected: + Builder&& + normalise() + { + uic_.normalise(); + return std::move (*this); + } + + Builder&& + overwrite (size_t depth, Literal newSpec) + { + Literal* storage = uic_.expandPosition (depth); + uic_.setContent (storage, newSpec); + return std::move (*this); + } + }; + + + + /** + * @note this ctor is used to "fix" and normalise + * the contents established in the Builder thus far. + */ + inline + UICoord::UICoord (Builder&& builder) + : UICoord{std::move (builder.uic_)} + { + PathArray::normalise(); + } + + + /** @return a minimally defined Builder, allowing to define further parts; + * to finish the definition, cast / store it into UICoord, which itself is immutable. + */ + inline UICoord::Builder + UICoord::currentWindow() + { + return window (UIC_CURRENT_WINDOW); + } + + inline UICoord::Builder + UICoord::firstWindow() + { + return window (UIC_FIRST_WINDOW); + } + + /** @return a Builder with just the windowID defined */ + inline UICoord::Builder + UICoord::window (Literal windowID) + { + return Builder{windowID}; + } + + + /** @return a Builder holding a clone copy of the original UICoord, + * with the perspective information set to a new value. + * @remarks This Builder can then be used do set further parts + * independently of the original. When done, store it + * as new UICoord object. To achieve real mutation, + * assign it to the original variable. */ + inline UICoord::Builder + UICoord::persp (Literal perspectiveID) const + { + return Builder(*this).persp (perspectiveID); + } + + inline UICoord::Builder + UICoord::panel (Literal panelID) const + { + return Builder(*this).panel (panelID); + } + + inline UICoord::Builder + UICoord::view (Literal viewID) const + { + return Builder(*this).view (viewID); + } + + inline UICoord::Builder + UICoord::tab (Literal tabID) const + { + return Builder(*this).tab (tabID); + } + + inline UICoord::Builder + UICoord::tab (uint tabIdx) const + { + return Builder(*this).tab (tabIdx); + } + + inline UICoord::Builder + UICoord::noTab () const + { + return Builder(*this).noTab(); + } + + /** + * convenience builder function so set a full path definition + * @note the given path string will be split at `'/'` and the + * resulting components will be stored/retrieved as Symbol + */ + inline UICoord::Builder + UICoord::path (Literal pathDefinition) const + { + return Builder(*this).path (pathDefinition); + } + + inline UICoord::Builder + UICoord::append (Literal elmID) const + { + return Builder(*this).append (elmID); + } + + inline UICoord::Builder + UICoord::prepend (Literal elmID) const + { + return Builder(*this).prepend (elmID); + } + + inline UICoord::Builder + UICoord::rebuild () const + { + return Builder(*this); + } + + + +}}// namespace gui::interact +#endif /*GUI_INTERACT_UI_COORD_H*/ diff --git a/src/gui/interact/ui-location-solver.hpp b/src/gui/interact/ui-location-solver.hpp new file mode 100644 index 000000000..cf869d896 --- /dev/null +++ b/src/gui/interact/ui-location-solver.hpp @@ -0,0 +1,327 @@ +/* + UI-LOCATION-SOLVER.hpp - decide upon a possible location for some UI component + + Copyright (C) Lumiera.org + 2018, Hermann Vosseler + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + + +/** @file ui-location-solver.hpp + ** A solver to match incomplete coordinate specifications against the actual UI topology. + ** Within the Lumiera UI, a _component view_ is typically _created or retrieved_ to live at some position + ** within the tree-like topology of the interface. Such happens as a consequence of interaction or other + ** events, and the logic as to where and how to place a new UI element shall not be intermingled with the + ** actual event handling code. Rather, the ViewLocator, as a service related to the InteractionDirector, + ** can be invoked to draw on some default configuration plus the actual UI topology present at this time. + ** + ** ## Pattern matching against the actual UI-tree + ** + ** This Location solver is based on the pattern matching algorithm [embedded](\ref ui-coord-resolver.cpp) + ** within the UICoordResolver component. This mechanism allows to treat (typically) incomplete coordinate + ** specifications as rules for locating an element. Several such coordinate _clauses_ can be combined into + ** a _disjunctive_ LocationRule, which is evaluated by matching the clauses one by one, in given order, + ** against the currently existing UI tree (topology). Each clause is evaluated individually from scratch + ** (there is no common variable binding); the first clause to produce a successful match is used as + ** solution -- with any placeholders replaced by the actually matching UI elements. + ** + ** ## Default View location configuration DSL + ** + ** Whenever a new UI element of a given kind is to be created, we query a standard location configuration + ** to determine it's actual location within the interface. This standard configuration is known as + ** ["ViewSpec DSL"](\ref id-scheme.hpp) and hard-wired into the UI code. Based on the aforementioned + ** pattern matching rules, it allows to express placement rules dependent on the already existing UI. + ** There are two kinds of location clauses + ** - the standard rules describe an element required to exist. Typically this is the _parent element_ + ** of the UI widget in question. But it is also possible to write clauses directly mentioning this + ** element, in which case such an element must already exist in the UI and will be retrieved as result. + ** - the more relaxed _create clauses_ describe a new location / path within the UI-tree, meaning that + ** any (parent) elements not yet present are to be created. A _create clause_ is defined within the + ** DSL by ending a UI coordinate specification with the term `.create()`. It may still be incomplete + ** (i.e. contain wildcards), which means, that the first explicitly given element after (below) the + ** wildcards must exist in the tree, to allow for an unambiguous pattern match. Otherwise, for + ** creating a new path completely from scratch, all elements have to be given explicitly. + ** As a minimum requirement, each LocationRule should be concluded with such a "catch-all" explicit + ** create clause, which describes the standard location of the element in question. + ** + ** @see UILocationSolver_test + ** @see ViewSpecDSL_test + ** @see UICoordResolver + ** @see view-locator.hpp + */ + + +#ifndef GUI_INTERACT_UI_LOCATION_SOLVER_H +#define GUI_INTERACT_UI_LOCATION_SOLVER_H + +#include "lib/error.hpp" +#include "lib/symbol.hpp" +#include "lib/format-util.hpp" +#include "gui/interact/ui-coord.hpp" +#include "gui/interact/ui-coord-resolver.hpp" +#include "lib/nocopy.hpp" + +#include +#include +#include + + +namespace gui { +namespace interact { + + using lib::Literal; + using std::string; + using std::move; + + + class LocationQuery; + using LocationQueryAccess = std::function; + + + + + /** + * A single location specification to be matched and fulfilled. + * It is created from a -- typically incomplete -- UICoord spec, + * which in turn can be built through a DSL notation. + * @todo maybe add a flag to require the current query goal to exist in tree //////////////////////////////TICKET #1130 + */ + struct LocationClause + : util::NonCopyable + { + UICoord pattern; + bool createParents; + + + LocationClause (UICoord && locationPattern, bool allowCreate =false) + : pattern{move (locationPattern)} + , createParents{allowCreate} + { } + LocationClause (LocationClause && rr) + : pattern{move (rr.pattern)} + , createParents{rr.createParents} + { } + + operator string() const; + }; + + + /** + * A rule to determine some location by matching against the UI-tree. + * It is comprised of one or several disjunctive [clauses](\ref LocationClause), + * each of which is a coordinate pattern to be matched. The clauses are tried + * in order and the first successfully matched clause wins. + */ + class LocationRule + : util::NonCopyable + { + using Clauses = std::vector; + + Clauses clauses_; + + public: + LocationRule (LocationClause && firstRule) + : clauses_{} + { + this->append (move (firstRule)); + } + LocationRule (LocationRule && rr) + : clauses_{move (rr.clauses_)} + { } + + + LocationRule&& + append (LocationClause && furtherRule) + { + clauses_.emplace_back (move (furtherRule)); + return move (*this); + } + + + using iterator = lib::RangeIter; + iterator begin() const { return iterator{clauses_.begin(), clauses_.end()}; } + iterator end() const { return iterator(); } + + operator string() const; + }; + + + + + /* ==== Support of UI-Coordinate notation within the ViewSpec-DSL ==== */ + + /** interprets the current (inline) contents of an UICoord builder expression + * as a standard LocationClause, which has the meaning of "when an element + * exists at the location XYZ in the real UI" + * @warning like all the UICoord::Builder functions, the contents are moved. + * Thus, after using this conversion path _once_, the Builder _is defunct_ + */ + inline + UICoord::Builder::operator LocationClause() + { + return LocationClause{move(*this), false}; + } + + /** interprets the current (inline) builder contents as _create clause_, + * which has the meaning "create a new element XYZ when possible" */ + inline LocationClause + UICoord::Builder::create() + { + return LocationClause{move(*this), true}; + } + + + /** DSL operator to assemble a sequence of clauses. + * Introduced solely for the purpose of writing location specifications within the + * [ViewSpec-DSL](\ref id-scheme.hpp), this operator acts on several UI-Coordinate specs + * to create a sequence of clauses, to be checked against the currently existing UI topology, + * in the given order, ranging from more specific to more general patterns. + */ + inline LocationRule + operator or (UICoord::Builder && firstRule, UICoord secondRule) + { + return LocationRule{move (firstRule)} + .append (move (secondRule)); + } + + inline LocationRule&& + operator or (LocationRule && ruleSet, UICoord furtherRule) + { + ruleSet.append (move (furtherRule)); + return move(ruleSet); + } + + + + /* === diagnostics === */ + + inline + LocationClause::operator string() const + { + return string{pattern} + + (createParents? " create!":""); + } + inline + LocationRule::operator string() const + { + return "=~\t.. " + + util::join(clauses_, "\n\tOR "); + } + + + + + /** + * Service to determine the location of an UI component view. + * @see LocationRule + * @see UILocationSolver_test::simple_usage_example() + */ + class UILocationSolver + : util::NonCopyable + { + LocationQueryAccess getLocationQuery; + + public: + explicit + UILocationSolver (LocationQueryAccess accessor) + : getLocationQuery{accessor} + { } + + explicit + UILocationSolver (LocationQuery& locationQueryService) + : getLocationQuery{[&]() -> LocationQuery& { return locationQueryService; }} + { } + + + /** + * Solve for a location according to the given location rule. + * @param depth desired kind of UI element (and thus the depth in the UI topology tree) + * @param elementType designator of the specific element to be created at that level + * @return an explicit location, resolved against the current UI topology. May be empty + * @remarks the returned path is either empty (no solution exists), or it is "partially covered" + * by the existing UI; here, the "covered" part are the already existing UI elements, + * while the remaining, uncovered extension describes additional elements to be created. + * When the resolution process found an already existing UI element, the returned path + * is completely covered. The degree of coverage of a path can be found out with the + * help of a UICoordResolver, which also needs a LocationQuery (service) to discover + * the currently existing UI topology. + */ + UICoord + solve (LocationRule const& rule, size_t depth, Literal elementTypeID) + { + for (auto& clause : rule) + { + // Clauses which do not at least describe an element at parent level + // will never lead to a solution and can thus be skipped + if (depth+1 < clause.pattern.size() + or depth > clause.pattern.size()) + continue; + + // try to solve the current Clause by matching against real UI topology + UICoordResolver resolver{clause.pattern, getLocationQuery()}; + preprocess (resolver, clause); + resolver.coverPartially(); // now either holds a solution or is empty + + if (not isnil(resolver) // Solution found! + and (clause.createParents // The "create" case requires only some part to exist, + or resolver.isCoveredTotally())) // while in the default case we demand complete coverage + { + if (depth == clause.pattern.size()) + // append ID of the new element to be created + // unless it's already there (and thus exists) + resolver.append (elementTypeID); + return move (resolver); + // use the first suitable solution and exit + } + else + if (clause.createParents and clause.pattern.isExplicit()) + // allow creation of a totally new path from scratch + // as long as it is complete and explicitly given + { + if (depth == clause.pattern.size()) + return clause.pattern.append (elementTypeID); + else + return clause.pattern; + } + } + //all clauses tried without success... + return UICoord(); + } + + private: + /** perform adjustments on the current pattern to support some very specific situations + * - when we want to create a new panel (or re-use an existing of the same name), on top + * of an existing (but irrelevant) perspective, we mark this perspective as "just there". + * This allows to get a new path as solution, which is just covered up to and including + * that perspective; without special treatment, such a match would otherwise be rejected. + */ + void + preprocess (UICoordResolver& builder, LocationClause const& clause) + { + if (clause.createParents + and clause.pattern.isComplete()) + { + builder.existentiallyQuantify (UIC_PERSP); + } + } + }; + + + + +}}// namespace gui::interact +#endif /*GUI_INTERACT_UI_LOCATION_SOLVER_H*/ diff --git a/src/gui/interact/view-locator.cpp b/src/gui/interact/view-locator.cpp new file mode 100644 index 000000000..01835ae10 --- /dev/null +++ b/src/gui/interact/view-locator.cpp @@ -0,0 +1,111 @@ +/* + ViewLocator - access and allocation of UI component views + + Copyright (C) Lumiera.org + 2017, Hermann Vosseler + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +* *****************************************************/ + + +/** @file view-locator.cpp + ** Implementation details of a machinery to allocate UI global component views. + ** Essentially this compilation unit hosts the implementation backing the [ViewSpec-DSL](view-spec-dsl.hpp) + ** and the [Location Solver](ui-location-solver.hpp) to drive evaluation of the standard location defaults, + ** which are [hard-wired into the UI](\ref id-scheme.hpp). For this integration of various facilities and + ** frameworks we rely on actual definitions for the DSL-tokens, which themselves are functors and tied by + ** lambda-binding into the implementation side of ViewLocator; which both acts as a front-end to access + ** the service to "get or create" a component and also holds the UILocationSolver component to perform + ** the actual location solving mechanism. The DSL thus sits between both poles, since _resolving a view_ + ** draws upon the default locating configuration encoded as DSL tokens, which in turn call back into + ** the implementation services within ViewLocator to resolve those partial location specifications + ** against the currently existing UI topology and then to access or create new UI components as + ** laid out through those definitions. + ** + ** @todo WIP 9/2017 early draft ////////////////////////////////////////////////////////////TICKET #1104 + */ + + +#include "gui/gtk-base.hpp" +#include "gui/interact/view-locator.hpp" +#include "gui/interact/view-spec-dsl.hpp" +#include "gui/interact/ui-location-solver.hpp" +#include "gui/ctrl/panel-locator.hpp" +#include "gui/model/element-access.hpp" +#include "gui/interact/ui-coord-resolver.hpp" +#include "lib/depend.hpp" +#include "lib/symbol.hpp" +//#include "lib/util.hpp" + +//using util::cStr; +//using util::isnil; +using lib::Symbol; +//using gui::ctrl::PanelLocator; +//using gui::ctrl::WindowLocator; +using gui::model::ElementAccess; + + +namespace gui { + namespace error = lumiera::error; + + + + /* ==== definitions and concrete bindings for the View-Spec-DSL ==== */ + + namespace idi { // Namespace for the actual ViewSpec DSL tokens + + + AllocSpec limitAllocation =[&](UICoord target, uint limit) + { + UNIMPLEMENTED ("Actual DSL token to cause a (limited) view allocation"); + return target; /////////////////////////////////////////////////////////TICKET #1129 : need at least a working draft implementation here + }; + + } + namespace interact { + + const Symbol UIC_CURRENT_WINDOW{"currentWindow"}; + const Symbol UIC_FIRST_WINDOW {"firstWindow"}; + const Symbol UIC_ELIDED {"."}; + + + + + ViewLocator::ViewLocator () + : locResolver_{LocationQuery::service} + { } + + // dtors via smart-ptr invoked from here... + ViewLocator::~ViewLocator() { } + + + + /* === Service accessors within global context === */ + + // PanelLocator& + // ViewLocator::panelLocator() + // { + // return windowLoc_.locatePanel(); + // } + + + + + /** */ + + + +}}// namespace gui::interact diff --git a/src/gui/interact/view-locator.hpp b/src/gui/interact/view-locator.hpp new file mode 100644 index 000000000..44d47e56e --- /dev/null +++ b/src/gui/interact/view-locator.hpp @@ -0,0 +1,134 @@ +/* + VIEW-LOCATOR.hpp - access and allocation of UI component views + + Copyright (C) Lumiera.org + 2017, Hermann Vosseler + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + + +/** @file view-locator.hpp + ** Access and allocation of UI component views. + ** Within the Lumiera UI, a _component view_ is a building block to deal with some component + ** of relevance to _»the model«. As such, all component views exhibit some distinctive traits: + ** - they conform to a built-in fixed list of view types, each of which is unique and dedicated + ** to a very specific purpose: *Timeline*, *Viewer*, (Asset)*Bin*, *Infobox*, *Playcontrol*,... + ** - each component view has a distinguishable identity and is connected to and addressable + ** through the [UI-Bus](\ref ui-bus.hpp). + ** - it can be hosted only at a dedicated location within one or several specific [docking panels](\ref panel.hpp). + ** - multiplicity (one, one per window, many) depends on the type of view and needs to be managed. + ** - such a view is not just _created_ -- rather it needs to be _allocated_ + ** + ** ## LocationQuery and the View-Spec DSL + ** + ** Implementation wise, there is a tight connection between the ViewLocator service, the Navigator service + ** and the configuration how and where to create standard view elements (the "View-Spec DSL"). Several interactions + ** require the UI to access or create some specific view widget by side-effect. And the logic how and where to create + ** views can be quite intricate and is closely related to global, overarching topics of interaction design. Lumiera + ** thus relies on a framework for default configuration, and a matching mechanism to determine the location and + ** creation modes of such views. This matching mechanism in turn requires an abstracted view on the UI seen as + ** a topological tree structure of relevant entities (windows, panels, views,...) -- which is basically the + ** service provided by the Navigator; yet this Navigator service can be abstracted into the gui::interact::LocationQuery + ** API, and this abstraction allows to keep all the intricacies of navigating concrete UI widgets confined within the + ** implementation of the Navigator service. + ** + ** @todo WIP 9/2017 early draft ////////////////////////////////////////////////////////////TICKET #1104 + ** + ** @see interaction-director.hpp + ** @see panel-locator.hpp + */ + + +#ifndef GUI_INTERACT_VIEW_LOCATOR_H +#define GUI_INTERACT_VIEW_LOCATOR_H + +#include "lib/depend-inject.hpp" +#include "gui/id-scheme.hpp" +#include "gui/interact/view-spec-dsl.hpp" +#include "gui/model/element-access.hpp" +#include "lib/idi/genfunc.hpp" +#include "lib/symbol.hpp" +#include "lib/nocopy.hpp" + +#include +#include +#include + + +namespace gui { +namespace ctrl{ + class PanelLocator; +} +namespace interact { + + using std::unique_ptr; + using std::string; + using lib::Symbol; + + class UILocationSolver; + + + + /** + * Access or allocate a UI component view + * + * @todo initial draft as of 9/2017 -- actual implementation need to be filled in + */ + class ViewLocator + : ::util::NonCopyable + { + using Service_LocationSolver = lib::DependInject::ServiceInstance<>; + + lib::Depend elementAccess; + Service_LocationSolver locResolver_; + + + public: + ViewLocator (); + ~ViewLocator(); + + + /** + * Access and possibly create _just some_ component view of the desired type + */ + template + V& get(); + + private: + /* === accessors to sibling global services === */ + + }; + + + + template + inline V& + ViewLocator::get() + { + using ViewSpec = idi::Descriptor; + ViewSpec viewSpec; /////////////////////////////////////////////////////////TICKET #1129 : can't create instances all the time. Need some kind of Factory + Symbol viewID{lib::idi::typeSymbol()}; + + UICoord targetLocation = viewSpec.locate(viewID); + UICoord realView = viewSpec.alloc (targetLocation); + + return elementAccess().access (realView); + } + + +}}// namespace gui::interact +#endif /*GUI_INTERACT_VIEW_LOCATOR_H*/ diff --git a/src/gui/interact/view-spec-dsl.hpp b/src/gui/interact/view-spec-dsl.hpp new file mode 100644 index 000000000..d5bac7fa1 --- /dev/null +++ b/src/gui/interact/view-spec-dsl.hpp @@ -0,0 +1,265 @@ +/* + VIEW-SPEC-DSL.hpp - configuration of view allocation patterns + + Copyright (C) Lumiera.org + 2017, Hermann Vosseler + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + + +/** @file view-spec-dsl.hpp + ** A framework for configuration of view access and allocation patterns. + ** Component views are building blocks of the Lumiera UI, and may, depending on their + ** type, be instantiated or allocated according to specific rules and patterns. And these + ** might vary in accordance to the desired working style. To give a typical example, at times + ** it might be mandatory to use a single, external output for all kind of media playback, while + ** other users prefer the classical editing application layout with two media viewers side by + ** side. And yet another working style would be to use a stack of media viewers allocated on + ** demand in MRU-fashion. + ** + ** To specify those standard behaviour patterns, we provide a small internal DSL to spell out + ** the default configuration in a (hopefully) self explanatory way. + ** @todo as of 9/2017, we confirm the intention to open this configuration DSL for some kind + ** of user provided flexible configuration of screen layouts eventually; yet right now, + ** only the foundation for this flexible configuration is provided while the defaults + ** are to be compiled into the UI as hard wired constants. + ** + ** + ** # Allocation of UI component views + ** + ** Within this context, _Allocation_ means... + ** - to constitute the desired element's identity + ** - to consider multiplicity and possibly retrieve an existing instance + ** - to determine the hosting location + ** - possibly to instantiate and register a new instance + ** - and finally to configure that instance for the desired role + ** + ** ## View access + ** + ** The global access point to component views is the ViewLocator within InteractionDirector, + ** which exposes a generic access- and management API to + ** - get (possibly create) some view of given type + ** - get (possibly create) a view with specific identity + ** - destroy a specific view + ** Together these operations expose a strictly typed direct reference to the respective view component + ** and thus allow to manage them like child objects. Operating on top of these primitive operations, + ** the _configuration of view access patterns_ creates a flexible binding layer, which isolates the + ** users of component views (typically other parts of the UI) from the actual mechanics of locating. + ** While the client just retrieves a view instance, a dedicated _allocation logic_ ensures this view + ** instance is actually placed at the desired place within the UI, and manages active view instances. + ** + ** ## Configuration DSL + ** + ** Access to component views typically happens through the ViewLocator provided by the InteractionDirector. + ** The implementation of view access in turn redirects access through the configured patterns. + ** - at [definition site](\ref id-scheme.hpp), explicit specialisations are given for the relevant types + ** of component view + ** - each of those general view configurations + ** * defines the multiplicity allowed for this kind of view + ** * defines how to locate this view + ** ... and that location definition is given as a list of alternatives in order of precedence. + ** That is, the system tries each pattern of location and uses the first one applicable. + ** + ** Actually, the definitions and tokens used within the configuration DSL are implemented as _functors._ + ** At implementation level, the basic specification tokens are bound to the corresponding operations + ** within ViewLocator and PanelLocator. Thus, the "value" retrieved from the configuration is actually + ** a functor, which provides the desired behaviour pattern + ** - the *locate* spec is evaluated to yield [user interface coordinates](\ref UICoord) representing the + ** desired location of the view + ** - the *allocate* spec is then evaluated as a second step, while providing the coordinates from first step. + ** This results in calling a dedicated _view allocator_ to create a new view or re-use an existing one + ** as appropriate. + ** + ** @note the actual binding for the DSL tokens can be found in the [ViewLocator implementation](view-locator.cpp) + ** + ** @see ViewSpecDSL_test + ** @see id-scheme.hpp + ** @see view-locator.hpp + ** @see view-locator.cpp + ** @see interaction-director.hpp + */ + + +#ifndef GUI_INTERACT_VIEW_SPEC_DSL_H +#define GUI_INTERACT_VIEW_SPEC_DSL_H + +#include "lib/error.hpp" +#include "lib/symbol.hpp" +#include "lib/depend.hpp" +#include "lib/meta/function.hpp" +#include "lib/meta/tuple-helper.hpp" +#include "lib/meta/function-closure.hpp" +#include "gui/interact/ui-location-solver.hpp" +#include "gui/interact/ui-coord.hpp" + +#include +#include +#include + + +namespace gui { +namespace interact { + + using std::forward; + using std::string; + + + + /** + * Locator is a functor to resolve to a topological location in the UI-tree. + * It takes a _viewID_ as argument, which actually is more of a typeID to designate + * the kind of view or UI widget, which shall be attached at or retrieved from the + * location resolved through the LocationRule. The latter is essentially what is + * embedded within the Locator functor. + */ + using Locator = std::function; + + + /** + * A specification to describe the desired location of a component view within the Lumiera UI. + * LocatorSpec is basically a set of [UI coordinates](\ref UICoord), with the additional possibility + * of specifying several alternatives, with the intention to pick the first applicable one. + * @tparam depth the level in the tree addressed by this locator + * @remarks Locator is built from a DSL expression, which is basically a UICoord::Builder. + * This coordinate spec describes a sequence of several places where to locate + * the UI-Element in question. The template parameter clarifies if we're talking + * about windows here, or panels or views. The latter is the [default case](\ref ViewSpec). + */ + template + class LocatorSpec + : public Locator + { + LocationRule rules_; + + public: + LocatorSpec(LocationRule && ruleToDetermineLocation) + : Locator([&](Literal componentID) -> UICoord + { + lib::Depend locationSolver; + return locationSolver().solve (rules_, depth, componentID); + }) + , rules_{move (ruleToDetermineLocation)} + { } + + /** shortcut to allow initialisation from UI-Coordinate builder expression */ + LocatorSpec(UICoord::Builder&& simpleLocationSpec) + : LocatorSpec{ + LocationRule{ + LocationClause{ + UICoord{std::move (simpleLocationSpec)}}}} + { } + + operator string() const + { + return string{rules_}; + } + }; + + /** + * A specification to describe the desired location of a component view within the Lumiera UI. + * ViewSpec is basically a set of [UI coordinates](\ref UICoord), with the additional possibility + * of specifying several alternatives, with the intention to pick the first applicable one. + */ + using ViewSpec = LocatorSpec; + + + /** + * Allocator is a functor to resolve a given, desired location of a view within the UI, resulting + * in creation or allocation of the view. The result of this invocation are the UI coordinates + * of an existing or newly created view. + */ + using Allocator = std::function; + + + /** + * A specification to describe the strategy for allocating (placing, retrieving) a component view. + * On a DSL-technical level, AllocSpec is a _function generator_: it produces Allocator entities, + * which in turn are functions to perform the actual allocation. + * @note AllocSpec relies on a specific *convention* how to specify the actual allocation operation: + * - the operation takes one main argument and additional parameters + * - its first argument is the _work triggering argument_, namely the concrete UI coordinates + * passed to the Allocator, requesting to create or retrieve or claim the view at that location + * - the additional argument(s) serves for parametrisation or specialisation of the strategy; + * they will be "baked" into the generated allocator. + * @remark to give a typical example: an allocation operation might want to limit the number of + * generated views per window and take this instance limit as additional parameter. But for the DSL + * we want a allocation spec of the form `limitPerWindow(cnt)` -- which is a function accepting the + * concrete limit and producing the actual allocator function `UICoord -> UICoord`, which has this + * actual limit "baked in". + */ + template + class AllocSpec + : public std::function + { + /** @internal analyse and dissect the given allocation operation + * and fabricate a builder to accept the additional arguments + * and produce a partially closed allocation functor + * @note the generated lambda captures by value */ + template + static auto + buildPartialApplicator (FUN&& fun) + { + using lib::meta::_Fun; + using lib::meta::Split; + using lib::meta::Tuple; + using lib::meta::Types; + using lib::meta::func::_Sig; + using lib::meta::func::PApply; + + typedef typename _Fun::Ret Ret; + typedef typename _Fun::Args Args; + typedef typename Split::Head Arg1; + typedef typename Split::Tail FurtherArgs; + + static_assert (std::is_convertible::value, + "Allocator function must accept UICoordinates (where to create/locate) as first argument"); + static_assert (std::is_convertible::value, + "Allocator function must produce UICoordinates (of the actually allocated UI element)"); + static_assert (std::is_convertible>::value, + "Additional parameters of the allocator function must match the AllocSpec template parameters"); + + + using ArgTuple = Tuple; + + return [=](auto&&... args) -> Allocator + { + ArgTuple params {forward (args)...}; + return PApply::bindBack (fun, params); + }; + } + + + public: + template + AllocSpec(FUN&& fun) + : std::function {buildPartialApplicator (forward (fun))} + { } + }; + + +} // namespace interact +namespace idi { +//bring definitions into scope for DSL use... + using interact::ViewSpec; + using interact::Allocator; + + using interact::AllocSpec; + + extern AllocSpec limitAllocation; + +}}// namespace gui::idi +#endif /*GUI_INTERACT_VIEW_SPEC_DSL_H*/ diff --git a/src/gui/interact/wizard.cpp b/src/gui/interact/wizard.cpp index ea5181e3e..8585092fa 100644 --- a/src/gui/interact/wizard.cpp +++ b/src/gui/interact/wizard.cpp @@ -96,7 +96,7 @@ namespace interact { dialog.set_website(Config::get (KEY_WEBSITE)); dialog.set_authors(authorsList); - WorkspaceWindow& currentWindow = globalCtx_.windowList_.findActiveWindow(); + WorkspaceWindow& currentWindow = globalCtx_.windowLoc_.findActiveWindow(); dialog.set_transient_for (currentWindow); // Show the about dialog diff --git a/src/gui/interact/wizard.hpp b/src/gui/interact/wizard.hpp index 8d7fed8c8..f134d62ef 100644 --- a/src/gui/interact/wizard.hpp +++ b/src/gui/interact/wizard.hpp @@ -43,8 +43,8 @@ #define GUI_INTERACT_WIZARD_H #include "gui/gtk-base.hpp" +#include "lib/nocopy.hpp" -#include //#include //#include @@ -69,7 +69,7 @@ namespace interact { * @todo initial draft as of 2/2017 -- actual implementation has to be filled in */ class Wizard - : boost::noncopyable + : util::NonCopyable { ctrl::GlobalCtx& globalCtx_; diff --git a/src/gui/interact/work-site-trail.hpp b/src/gui/interact/work-site-trail.hpp index 698dee13f..c136696a4 100644 --- a/src/gui/interact/work-site-trail.hpp +++ b/src/gui/interact/work-site-trail.hpp @@ -41,8 +41,8 @@ #define GUI_INTERACT_WORK_SITE_TRAIL_H #include "gui/gtk-base.hpp" +#include "lib/nocopy.hpp" -#include //#include //#include @@ -63,7 +63,7 @@ namespace interact { * @todo initial draft as of 2/2017 -- actual implementation has to be filled in */ class WorkSiteTrail - : boost::noncopyable + : util::NonCopyable { public: diff --git a/src/gui/interact/work-site.hpp b/src/gui/interact/work-site.hpp index 7d06546ec..ac7a69da3 100644 --- a/src/gui/interact/work-site.hpp +++ b/src/gui/interact/work-site.hpp @@ -46,8 +46,8 @@ #define GUI_INTERACT_WORK_SITE_H #include "gui/gtk-base.hpp" +#include "lib/nocopy.hpp" -#include //#include //#include @@ -70,7 +70,7 @@ namespace interact { * @todo initial draft as of 2/2017 -- actual implementation has to be filled in */ class WorkSite - : boost::noncopyable + : util::NonCopyable { public: diff --git a/src/gui/model/diagnostics.hpp b/src/gui/model/diagnostics.hpp index 7019501d1..c2f359bc1 100644 --- a/src/gui/model/diagnostics.hpp +++ b/src/gui/model/diagnostics.hpp @@ -39,10 +39,10 @@ #include "lib/error.hpp" +#include "lib/nocopy.hpp" //#include "lib/symbol.hpp" //#include "lib/util.hpp" -#include //#include @@ -64,7 +64,7 @@ namespace model { * @see NA_test */ class Diagnostics - : boost::noncopyable + : util::NonCopyable { public: diff --git a/src/gui/model/element-access.hpp b/src/gui/model/element-access.hpp new file mode 100644 index 000000000..174f3fb5d --- /dev/null +++ b/src/gui/model/element-access.hpp @@ -0,0 +1,205 @@ +/* + ELEMENT-ACCESS.hpp - access to generic elements in the UI + + Copyright (C) Lumiera.org + 2015, Hermann Vosseler + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + + +/** @file element-access.hpp + ** Interface to discover and access raw UI elements in a cross cutting way. + ** We have several orthogonal identification and access schemes within the UI. + ** A naively written UI application just attaches the core logic below some widgets and + ** controllers -- not only does this lead to a hard to maintain codebase, this approach + ** is even outright impossible for Lumiera, since the core is able to run standalone and + ** the UI is loaded as plug-in, which places us into the situation to connect a self + ** contained core with a self contained UI. This is a binding, which, as a sideline, also + ** generates a control structure of its own. An another kind of generic access happens + ** when we _navigate_ the topological UI structure for focus management. + ** + ** This interface defines an abstract service to translate a generic element designation + ** into a (language level) access to internal structures of the UI toolkit (GTK in our case). + ** This access to low-level structure proceeds in two stages: + ** - navigate down the UI topology. Optionally, this may involve a mutation (create element) + ** - evaluate the result (found, not found, element created) and access the target, + ** possibly with conversion (which might fail) + ** + ** @note as of 4/2018 this is a first draft and WIP-WIP-WIP + ** @todo WIP ///////////////////////TICKET #1134 + ** + ** @see ElementAccess_test + ** @see elem-access-dir.hpp implementation + ** + */ + + +#ifndef GUI_MODEL_ELEMENT_ACCESS_H +#define GUI_MODEL_ELEMENT_ACCESS_H + + +#include "lib/error.hpp" +#include "lib/nocopy.hpp" +#include "lib/result.hpp" +#include "include/limits.h" +#include "lib/variant.hpp" +#include "lib/meta/typelist-manip.hpp" +#include "lib/access-casted.hpp" +#include "gui/interact/ui-coord.hpp" +//#include "lib/format-string.hpp" +//#include "lib/symbol.hpp" +//#include "lib/util.hpp" +#include "lib/test/test-helper.hpp" ////////TOD-o + +#include + + +namespace Gtk { + class Widget; +} +namespace gui { +namespace model { + namespace error = lumiera::error; + + using interact::UICoord; + using lib::meta::Types; +// using util::isnil; + using std::string; + + class Tangible; + + /** + * Interface: access UI elements by navigating the UI topology. + * + * @see gui::interact::Navigator + * @see ElementAccess_test + */ + class ElementAccess + : util::NonCopyable + { + + public: + virtual ~ElementAccess () { } ///< this is an interface + + + template + using Result = lib::Result; + + /* == Access by Location == */ + + template + Result access (UICoord const& destination); + + template + Result access_or_create (UICoord const& destination, size_t limitCreation = LUMIERA_MAX_ORDINAL_NUMBER); + + + protected: + using RawResult = lib::Variant>; + + /** @internal drill down according to coordinates, maybe create element */ + virtual RawResult performAccessTo (UICoord const&, size_t limitCreation) =0; + + private: + template + struct TypeConverter; + }; + + + + + /** @internal helper to perform conversion to the desired result type. + * This is a bit of tricky metaprogramming to deal with the problem that we can not + * assume a single base interface for all the UI elements or widgets accessible through + * UI-Coordinates. Rather we have to deal with a small set of possible base interfaces, + * and thus the actual [access function](\ref #performAccessTo) returns a _variant record_ + * holding a pointer, and internally tagged with the base interface type to apply. Now the + * public API functions are templated to the _result type as expected by the invoking clinent_ + * and thus we get a matrix of possible cases; when the expected result type is _reachable by + * dynamic downcast_ from the actual base interface type returned by the internal access function, + * we can perform this dynamic_cast. Otherwise the returned result proxy object is marked as empty. + * @remark technically this solution relies on the lib::Variant::Visitor to provide a NOP default + * implementation. The TypeConverter template is instantiated with the desired target type + * and thus only overwrites _the first case where an conversion is possible._ In all other + * cases the default implementation does nothing and thus retains the embedded result proxy + * in empty state. + */ + template + struct ElementAccess::TypeConverter + : RawResult::Visitor + { + Result result{"not convertible"}; + + template // note the "backward" use. We pick that base interface + using canUpcast = std::is_convertible; // into which our desired result type can be upcast, because + // we know then the following dynamic_cast (downcast) can succeed + using Base = typename RawResult::FirstMatching::Type; + + virtual void + handle (Base& pb) override + { + if (pb) + result = *dynamic_cast (pb); + else + result = Result{"access returns empty answer"}; + } + }; + + + + + + + /** Navigate the UI topology to access the designated component. + * @tparam TAR type of result element expected at the designated location + * @return suitably converted direct (language) reference to the desired element + * wrapped as _result proxy_ + * @note when access was not possible because either the element does not exist, + * or is not convertible to the desired target type, the result proxy is empty + * and convertible to `bool(false)` + */ + template + inline ElementAccess::Result + ElementAccess::access (UICoord const& destination) + { + return access_or_create (destination, 0); + } + + + /** Navigate to the designated component, possibly create the element and parents + * @return suitably converted direct (language) reference to the desired element + * wrapped as _result proxy_ + * @note when access was not possible because the element could not been created, + * or is not convertible to the desired target type, the result proxy is empty, + * and convertible to `bool(false)` (or raises an exception on attempted access) + */ + template + inline ElementAccess::Result + ElementAccess::access_or_create (UICoord const& destination, size_t limitCreation) + { + TypeConverter converter; + RawResult targetElm = performAccessTo (destination, limitCreation); + targetElm.accept (converter); + return converter.result; + } + + + + + +}} // namespace gui::model +#endif /*GUI_MODEL_ELEMENT_ACCESS_H*/ diff --git a/src/gui/model/element.hpp b/src/gui/model/element.hpp deleted file mode 100644 index 24a7cdb06..000000000 --- a/src/gui/model/element.hpp +++ /dev/null @@ -1,127 +0,0 @@ -/* - ELEMENT.hpp - a generic element in the UI-Model - - Copyright (C) Lumiera.org - 2015, Hermann Vosseler - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of - the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - -*/ - - -/** @file element.hpp - ** Generic building block in the Lumiera GUI model. - ** A model::Element has a unique identifier, which is tied to the - ** identification scheme used in the "real" model in Proc-Layer. - ** Model elements can be addressed receive mutations caused by changes - ** and rebuilding of elements within the Session; moreover, a generic - ** representation of attributes is provided. - ** - ** @note as of 1/2015 this is a first draft and WIP-WIP-WIP - ** @todo WIP ///////////////////////TICKET #959 - ** - ** @see ////TODO_test usage example - ** @see element.cpp implementation - ** - */ - - -#ifndef GUI_MODEL_ELEMENT_H -#define GUI_MODEL_ELEMENT_H - - -#include "lib/error.hpp" -#include "lib/hash-value.h" -//#include "lib/symbol.hpp" -#include "lib/util.hpp" - -#include -#include - - - -namespace gui { -namespace model { - - using lib::HashVal; - using util::isnil; - using std::string; - - - /** - * Basic (abstracted) view of... - * - * @see SomeSystem - * @see NA_test - */ - template - class Element - { - string nothing_; - - public: - explicit - Element (string const& b) - : nothing_(b) - { } - - // using default copy/assignment - - - - /* == Adapter interface for == */ - - void - setSolution (string const& solution ="") - { - UNIMPLEMENTED ("tbw"); -#if false /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #888 - if (isDeaf()) - this->transmogrify (solution); -#endif /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #888 - } - - - protected: - void maybe () const; - - - friend HashVal - hash_value (Element const& entry) - { - return hash_value (entry.nothing_); - } - }; - - - - - - - /** @internal in case - */ - template - inline void - Element::maybe () const - { - UNIMPLEMENTED ("tbw"); - } - - - - - -}} // namespace gui::model -#endif /*GUI_MODEL_ELEMENT_H*/ diff --git a/src/gui/model/session-facade.cpp b/src/gui/model/session-facade.cpp index 2a457ccf7..d8b6d332c 100644 --- a/src/gui/model/session-facade.cpp +++ b/src/gui/model/session-facade.cpp @@ -41,7 +41,6 @@ #include "gui/model/session-facade.hpp" #include "lib/depend.hpp" -//#include //#include //#include diff --git a/src/gui/model/session-facade.hpp b/src/gui/model/session-facade.hpp index 7df641c2c..0bb26229d 100644 --- a/src/gui/model/session-facade.hpp +++ b/src/gui/model/session-facade.hpp @@ -39,10 +39,10 @@ #include "lib/error.hpp" +#include "lib/nocopy.hpp" //#include "lib/symbol.hpp" //#include "lib/util.hpp" -#include #include @@ -61,7 +61,7 @@ namespace model { * @see NA_test */ class SessionFacade - : boost::noncopyable + : util::NonCopyable { string nothing_; diff --git a/src/gui/model/tangible.hpp b/src/gui/model/tangible.hpp index 4331035d3..fcaf2f1f9 100644 --- a/src/gui/model/tangible.hpp +++ b/src/gui/model/tangible.hpp @@ -131,12 +131,12 @@ #include "lib/error.hpp" +#include "lib/nocopy.hpp" #include "gui/ctrl/bus-term.hpp" #include "lib/diff/diff-mutable.hpp" #include "lib/idi/entry-id.hpp" #include "lib/symbol.hpp" -#include #include #include #include @@ -156,7 +156,7 @@ namespace model { * which is the [UI-Bus](ui-bus.hpp). Any tangible element acquires a distinct identity * and has to be formed starting from an already existing bus nexus. * @see [explanation of the basic interactions](tangible.hpp) - * @warning Tangible is `noncopyable` for good reason: the UI-Bus Nexus adds a direct + * @warning Tangible is `NonCopyable` for good reason: the UI-Bus Nexus adds a direct * reference into the routing table, tied to the given Tangible's ID (identity. * Consequently you must not store tangibles in STL containers, since these * might re-allocate and thus change the location in memory. @@ -164,7 +164,7 @@ namespace model { class Tangible : public sigc::trackable , public lib::diff::DiffMutable - , boost::noncopyable + , util::NonCopyable { public: using ID = ctrl::BusTerm::ID; diff --git a/src/gui/notification-service.cpp b/src/gui/notification-service.cpp index 400d16497..4a30fcdf3 100644 --- a/src/gui/notification-service.cpp +++ b/src/gui/notification-service.cpp @@ -51,6 +51,7 @@ #include "lib/diff/mutation-message.hpp" #include "lib/diff/gen-node.hpp" #include "include/logging.h" +#include "lib/depend.hpp" #include "lib/util.hpp" extern "C" { @@ -91,6 +92,7 @@ namespace gui { { INFO (gui, "@GUI: display '%s' as notification message.", cStr(text)); ///////////////////////////////////TICKET #1102 : build a message display box in the UI ////////////////////////TODO actually push the information to the GUI ///////////////////////////////////TICKET #1098 : use a suitable Dispatcher + ////////////////////////////////////////////////TICKET #1047 : as a temporary solution, use the InfoBox panel... } @@ -124,7 +126,7 @@ namespace gui { { NOTICE (gui, "@GUI: shutdown triggered with explanation '%s'....", cStr(cause)); displayInfo (NOTE_ERROR, cause); - dispatch_->event ([=]() + dispatch_->event ([this]() { uiManager_.terminateUI(); }); @@ -207,10 +209,9 @@ namespace gui { - using lumiera::facade::LUMIERA_ERROR_FACADE_LIFECYCLE; - typedef lib::SingletonRef::Accessor InstanceRef; + using lumiera::error::LERR_(LIFECYCLE); - InstanceRef _instance; ///< a backdoor for the C Language impl to access the actual GuiNotification implementation... + lib::Depend _instance; ///< a backdoor for the C Language impl to access the actual SessionCommand implementation... @@ -222,41 +223,41 @@ namespace gui { , LUMIERA_INTERFACE_INLINE (displayInfo, void, (uint severity, const char* text), { - if (!_instance) lumiera_error_set(LUMIERA_ERROR_FACADE_LIFECYCLE, text); + if (!_instance) lumiera_error_set (LUMIERA_ERROR_LIFECYCLE, text); else - _instance->displayInfo (NotifyLevel(severity), text); + _instance().displayInfo (NotifyLevel(severity), text); } ) , LUMIERA_INTERFACE_INLINE (markError, void, (LumieraUid element, const char* text), { - if (!_instance) lumiera_error_set(LUMIERA_ERROR_FACADE_LIFECYCLE, text); + if (!_instance) lumiera_error_set (LUMIERA_ERROR_LIFECYCLE, text); else - _instance->markError (reinterpret_cast (*element), text); + _instance().markError (reinterpret_cast (*element), text); } ) , LUMIERA_INTERFACE_INLINE (markNote, void, (LumieraUid element, const char* text), { - if (!_instance) lumiera_error_set(LUMIERA_ERROR_FACADE_LIFECYCLE, text); + if (!_instance) lumiera_error_set (LUMIERA_ERROR_LIFECYCLE, text); else - _instance->markNote (reinterpret_cast (*element), text); + _instance().markNote (reinterpret_cast (*element), text); } ) , LUMIERA_INTERFACE_INLINE (mutate, void, (LumieraUid element, void* diff), { - if (!_instance) lumiera_error_set(LUMIERA_ERROR_FACADE_LIFECYCLE, "passing diff message"); + if (!_instance) lumiera_error_set (LUMIERA_ERROR_LIFECYCLE, "passing diff message"); else - _instance->mutate (reinterpret_cast (*element), move(*reinterpret_cast (diff))); + _instance().mutate (reinterpret_cast (*element), move(*reinterpret_cast (diff))); } ) , LUMIERA_INTERFACE_INLINE (triggerGuiShutdown, void, (const char* cause), { - if (!_instance) lumiera_error_set(LUMIERA_ERROR_FACADE_LIFECYCLE, cause); + if (!_instance) lumiera_error_set (LUMIERA_ERROR_LIFECYCLE, cause); else - _instance->triggerGuiShutdown (cause); + _instance().triggerGuiShutdown (cause); } ) ); @@ -269,21 +270,21 @@ namespace gui { /** - * When started, NotificationService connects to the [UI-Bus](ui-bus.hpp) via - * the provided connection. This is a simple, unidirectional up-link connection, - * without actively adding NotificationService into the routing tables in [Nexus]. - * Yet this simple connection is sufficient to implement this service by talking - * to other facilities within the UI layer. + * When started, NotificationService connects to the [UI-Bus](ui-bus.hpp) via the provided connection. + * This is a simple, unidirectional up-link connection, without actively adding NotificationService + * into the routing tables in [Nexus]. Yet this simple connection is sufficient to implement this + * service by talking to other facilities within the UI layer. + * @remark internally this service relies on a UiDispatcher queue to hand over any invocations + * into the GTK event loop thread. */ NotificationService::NotificationService (ctrl::BusTerm& upLink, ctrl::UiManager& uiManager) : BusTerm{lib::idi::EntryID{}, upLink} - , dispatch_{new UiDispatcher{}} + , dispatch_{new UiDispatcher{[this](string msg){ displayInfo (NOTE_ERROR, msg); }}} , uiManager_{uiManager} - , implInstance_(this,_instance) , serviceInstance_( LUMIERA_INTERFACE_REF (lumieraorg_GuiNotification, 0,lumieraorg_GuiNotificationService)) - { - INFO (gui, "GuiNotification Facade opened."); - } + { + INFO (gui, "GuiNotification Facade opened."); + } NotificationService::~NotificationService() { } // emit dtors of embedded objects here... diff --git a/src/gui/notification-service.hpp b/src/gui/notification-service.hpp index 12e4f6c76..93dd82666 100644 --- a/src/gui/notification-service.hpp +++ b/src/gui/notification-service.hpp @@ -42,7 +42,6 @@ #include "include/gui-notification-facade.h" #include "common/instancehandle.hpp" #include "gui/ctrl/bus-term.hpp" -#include "lib/singleton-ref.hpp" #include @@ -78,19 +77,10 @@ namespace gui { , public ctrl::BusTerm { - /* === Implementation of the Facade Interface === */ - - void displayInfo (NotifyLevel,string const& text) override; - void markError (ID uiElement, string const& text) override; - void markNote (ID uiElement, string const& text) override; - void mutate (ID uiElement, MutationMessage&&) override; - void triggerGuiShutdown (string const& cause) override; - - private: std::unique_ptr dispatch_; ctrl::UiManager& uiManager_; - void dispatchMsg(ID, lib::diff::GenNode&&); + void dispatchMsg (ID, lib::diff::GenNode&&); /* === Interface Lifecycle === */ @@ -98,15 +88,20 @@ namespace gui { using ServiceInstanceHandle = lumiera::InstanceHandle< LUMIERA_INTERFACE_INAME(lumieraorg_GuiNotification, 0) , GuiNotification > ; - lib::SingletonRef implInstance_; ServiceInstanceHandle serviceInstance_; - public: ~NotificationService(); NotificationService (ctrl::BusTerm& upLink, ctrl::UiManager& uiManager); + /* === Implementation of the Facade Interface === */ + + void displayInfo (NotifyLevel,string const& text) override; + void markError (ID uiElement, string const& text) override; + void markNote (ID uiElement, string const& text) override; + void mutate (ID uiElement, MutationMessage&&) override; + void triggerGuiShutdown (string const& cause) override; }; diff --git a/src/gui/panel/infobox-panel.cpp b/src/gui/panel/infobox-panel.cpp new file mode 100644 index 000000000..0f4330e50 --- /dev/null +++ b/src/gui/panel/infobox-panel.cpp @@ -0,0 +1,93 @@ +/* + InfoBoxPanel - A dockable panel to expose information and parameters + + Copyright (C) Lumiera.org + 2017, Hermann Vosseler + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +* *****************************************************/ + + +/** @file infobox-panel.cpp + ** Implementation of a (dockable) panel to display and manipulate parameters. + */ + +#include "gui/gtk-base.hpp" +#include "gui/panel/infobox-panel.hpp" +#include "lib/format-string.hpp" + +using util::_Fmt; +using Glib::ustring; + +namespace gui { +namespace panel{ + + InfoBoxPanel::InfoBoxPanel (workspace::PanelManager& panelManager, Gdl::DockItem& dockItem) + : Panel(panelManager, dockItem, getTitle(), getStockID()) + , twoParts_{Gtk::ORIENTATION_VERTICAL} + , buttons_{} + , frame_{"UI Integration Experiments"} + , errorLog_{} + { + twoParts_.pack_start(frame_); + twoParts_.pack_start(buttons_, Gtk::PACK_SHRINK); + + buttons_.set_layout (Gtk::BUTTONBOX_START); + + // buttons to trigger experiments + button_1_.set_label ("_bang"); + button_1_.set_use_underline(); + button_1_.set_tooltip_markup ("Experiment 1:\ntrigger Proc-GUI roundtrip"); + button_1_.signal_clicked().connect( + mem_fun(*this, &InfoBoxPanel::experiment_1)); + buttons_.add (button_1_); + //(End)buttons... + + frame_.set_border_width (5); + frame_.add (errorLog_); + + // show everything.... + this->add (twoParts_); + this->show_all(); + } + + const char* + InfoBoxPanel::getTitle() + { + return _("InfoBox"); + } + + const gchar* + InfoBoxPanel::getStockID() + { + return "panel_infobox"; + } + + + + void + InfoBoxPanel::experiment_1() + { + frame_.set_label("Experiment 1... BANG"); + + static uint bangNo{0}; + static _Fmt msgTemplate{"Bang #%d\n"}; + + errorLog_.showMsg(NOTE_WARN, msgTemplate % ++bangNo); + } + + +}}// namespace gui::panel diff --git a/src/gui/panel/infobox-panel.hpp b/src/gui/panel/infobox-panel.hpp new file mode 100644 index 000000000..3b508bcba --- /dev/null +++ b/src/gui/panel/infobox-panel.hpp @@ -0,0 +1,72 @@ +/* + infobox-panel.hpp - A dockable panel to expose information and parameters + + Copyright (C) Lumiera.org + 2017, Hermann Vosseler + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + + +/** @file infobox-panel.hpp + ** A (dockable) panel to display and manage information and parameters. + ** Such an »Info Box« typically exposes detail settings from some other component + ** currently selected, and allows to access those in a non modal fashion. + ** + ** @todo as of 8/2017 this is (ab)used as space for UI / Proc-Layer integration experiments + */ + + + +#ifndef GUI_PANEL_INFOBOX_PANEL_H +#define GUI_PANEL_INFOBOX_PANEL_H + + +#include "gui/panel/panel.hpp" +#include "gui/widget/error-log-widget.hpp" + +namespace gui { +namespace panel{ + + class InfoBoxPanel + : public Panel + { + public: + /** Build a new InfoBox-Panel + * @param PanelManager The owner panel manager widget. + * @param DockItem The GdlDockItem that will host this panel. + * + * @todo as of 8/2017 this is placeholder code for UI experiments... + */ + InfoBoxPanel (workspace::PanelManager&, Gdl::DockItem&); + + + static const char* getTitle(); ///< @deprecated need better design of the PanelManager /////TICKET #1026 + static const gchar* getStockID(); + + private: + Gtk::Box twoParts_; + Gtk::ButtonBox buttons_; + Gtk::Button button_1_; + Gtk::Frame frame_; + widget::ErrorLogWidget errorLog_; + + void experiment_1(); + }; + + +}}// namespace gui::panel +#endif /*GUI_PANEL_INFOBOX_PANEL_H*/ diff --git a/src/gui/setting/asset-controller.hpp b/src/gui/setting/asset-controller.hpp index ca3b04cd8..fce280526 100644 --- a/src/gui/setting/asset-controller.hpp +++ b/src/gui/setting/asset-controller.hpp @@ -54,7 +54,6 @@ //#include "gui/gtk-base.hpp" #include "gui/model/controller.hpp" -//#include //#include //#include diff --git a/src/gui/ui-bus.hpp b/src/gui/ui-bus.hpp index f56c7f479..6455b9cfb 100644 --- a/src/gui/ui-bus.hpp +++ b/src/gui/ui-bus.hpp @@ -105,8 +105,8 @@ #include "gui/gtk-base.hpp" //////////////////////////////////////////////////////TODO remove any GTK dependency if possible #include "gui/ctrl/playback-controller.hpp" +#include "lib/nocopy.hpp" -#include #include @@ -157,7 +157,7 @@ namespace gui { * and must not be accessed from anywhere else. */ class UiBus - : boost::noncopyable + : util::NonCopyable { unique_ptr coreService_; diff --git a/src/gui/widget/error-log-widget.hpp b/src/gui/widget/error-log-widget.hpp new file mode 100644 index 000000000..4533e8add --- /dev/null +++ b/src/gui/widget/error-log-widget.hpp @@ -0,0 +1,121 @@ +/* + ERROR-LOG-WIDGET.hpp - display of error messages in a text box + + Copyright (C) Lumiera.org + 2017, Hermann Vosseler + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + + +/** @file error-log-widget.hpp + ** Widget to display error messages to the use in non-modal way. + ** Notifications are shown in a simple text window with scrollbars; new + ** entries can be added with a severity level, causing the widget to scroll + ** down to the last line of the content buffer. + ** + ** @todo WIP-WIP-WIP as of 9/2017 this is a first draft of a widget to be + ** used as receiver by the GuiNotificationService. + ** + */ + + +#ifndef GUI_WIDGET_ERROR_LOG_WIDGET_H +#define GUI_WIDGET_ERROR_LOG_WIDGET_H + +#include "gui/gtk-base.hpp" +#include "include/gui-notification-facade.h" + +//#include "lib/util.hpp" + +//#include +//#include + + + +namespace gui { +namespace widget { + + + /** + * @todo WIP-WIP as of 9/2017 + * Just a text display box with scrollbars. + * Need to add formatting etc. + */ + class ErrorLogWidget + : public Gtk::ScrolledWindow + { + public: + ~ErrorLogWidget() { }; + + ErrorLogWidget() + : Gtk::ScrolledWindow() + , textLog_{} + { + set_size_request (200, 50); + set_border_width (10); + set_shadow_type (Gtk::SHADOW_NONE); + + // the vertical scrollbar will always be necessary.... + set_policy (Gtk::POLICY_AUTOMATIC, Gtk::POLICY_ALWAYS); + this->add (textLog_); + textLog_.set_editable (false); + + } + + + void + showMsg (NotifyLevel severity, string const& text) + { + //////////////////////////////////////////////////TICKET #1102 : add formatting according to the error level + switch (severity) { + case NOTE_ERROR: + addEntry ("ERROR: "+text); + break; + case NOTE_WARN: + addEntry ("WARN: "+text); + break; + default: + addEntry (text); + break; + } + } + + + private:/* ===== Internals ===== */ + + Gtk::TextView textLog_; + + /** add message entry to the (ever growing) text buffer. + * @remark According to the Gtkmm tutorial, TextView::scroll_to(iter) is not reliable; + * rather we need to use a text mark and set that text mark to the insert position. + * Actually, there is always one predefined text mark called "insert", which corresponds + * to the text cursor. Thus it suffices to navigate to text end, insert and scroll into view. + */ + void + addEntry (string const& text) + { + auto buff = textLog_.get_buffer(); + auto cursor = buff->get_insert(); + buff->move_mark (cursor, buff->end()); + buff->insert (buff->end(), text); + textLog_.scroll_to (cursor); + } + }; + + +}}// namespace gui::widget +#endif /*GUI_WIDGET_ERROR_LOG_WIDGET_H*/ diff --git a/src/gui/widget/panel-bar.cpp b/src/gui/widget/panel-bar.cpp index 003aadd00..7f8c870df 100644 --- a/src/gui/widget/panel-bar.cpp +++ b/src/gui/widget/panel-bar.cpp @@ -31,7 +31,7 @@ #include "gui/workspace/workspace-window.hpp" #include "gui/workspace/panel-manager.hpp" #include "gui/panel/panel.hpp" -#include "gui/util/rectangle.hpp" +#include "gui/draw/rectangle.hpp" #include "include/logging.h" #include diff --git a/src/gui/widget/timeline/draw-strategy.hpp b/src/gui/widget/timeline/draw-strategy.hpp index 2fe308d5e..7cbb77d10 100644 --- a/src/gui/widget/timeline/draw-strategy.hpp +++ b/src/gui/widget/timeline/draw-strategy.hpp @@ -43,7 +43,7 @@ namespace timeline { /////////TODO some questions: ///////// 1) who is allowed to destroy DrawStrategy objects - ///////// 2) shouldn't DrawStragegy be boost::noncopyable? + ///////// 2) shouldn't DrawStragegy be util::NonCopyable? /** diff --git a/src/gui/widget/timeline/timeline-body.cpp b/src/gui/widget/timeline/timeline-body.cpp index 35696ad1a..f0b88623e 100644 --- a/src/gui/widget/timeline/timeline-body.cpp +++ b/src/gui/widget/timeline/timeline-body.cpp @@ -29,7 +29,7 @@ #include "gui/widget/timeline/timeline-body.hpp" #include "gui/widget/timeline-widget.hpp" #include "gui/workspace/style-manager.hpp" -#include "gui/util/cairo-util.hpp" +#include "gui/draw/cairo-util.hpp" #include "timeline-arrow-tool.hpp" #include "timeline-ibeam-tool.hpp" @@ -42,7 +42,7 @@ using namespace Gtk; /////////////////////////////////////////////////// using namespace std; ///////////////////////////////////////////////////////////////////////////////TICKET #1071 no wildcard includes please! using namespace lumiera; ///////////////////////////////////////////////////////////////////////////////TICKET #1071 no wildcard includes please! -using gui::util::CairoUtil; +using gui::draw::CairoUtil; using std::shared_ptr; namespace gui { diff --git a/src/gui/widget/timeline/timeline-header-container.cpp b/src/gui/widget/timeline/timeline-header-container.cpp index 7a98c5353..6a463d462 100644 --- a/src/gui/widget/timeline/timeline-header-container.cpp +++ b/src/gui/widget/timeline/timeline-header-container.cpp @@ -29,7 +29,7 @@ #include "gui/widget/timeline/timeline-header-container.hpp" #include "gui/widget/timeline/timeline-track.hpp" #include "gui/widget/timeline-widget.hpp" -#include "gui/util/rectangle.hpp" +#include "gui/draw/rectangle.hpp" #include #include diff --git a/src/gui/widget/timeline/timeline-header-widget.cpp b/src/gui/widget/timeline/timeline-header-widget.cpp index fab001e9d..cd54a6e0c 100644 --- a/src/gui/widget/timeline/timeline-header-widget.cpp +++ b/src/gui/widget/timeline/timeline-header-widget.cpp @@ -27,7 +27,7 @@ #include "gui/widget/timeline-widget.hpp" -#include "gui/util/rectangle.hpp" +#include "gui/draw/rectangle.hpp" #include @@ -239,10 +239,10 @@ namespace timeline { bool hovering = false; const Gdk::Rectangle box = get_allocation(); - if (util::pt_in_rect(Gdk::Point(event->x, event->y), - Gdk::Rectangle(margin, margin, - expand_button_size, - box.get_height() - margin * 2))) + if (gui::draw::pt_in_rect (Gdk::Point(event->x, event->y), + Gdk::Rectangle(margin, margin, + expand_button_size, + box.get_height() - margin * 2))) hovering = true; if(hovering != hoveringExpander) diff --git a/src/gui/widget/timeline/timeline-layout-helper.cpp b/src/gui/widget/timeline/timeline-layout-helper.cpp index 139542036..2e8c9bac5 100644 --- a/src/gui/widget/timeline/timeline-layout-helper.cpp +++ b/src/gui/widget/timeline/timeline-layout-helper.cpp @@ -30,7 +30,7 @@ #include "gui/widget/timeline/timeline-layout-helper.hpp" #include "gui/widget/timeline-widget.hpp" #include "gui/model/sequence.hpp" -#include "gui/util/rectangle.hpp" +#include "gui/draw/rectangle.hpp" #include #include @@ -45,7 +45,7 @@ using std::dynamic_pointer_cast; using namespace Gtk; ///////////////////////////////////////////////////////////////////////////////TICKET #1071 no wildcard includes please! using namespace lumiera; ///////////////////////////////////////////////////////////////////////////////TICKET #1071 no wildcard includes please! using namespace util; ///////////////////////////////////////////////////////////////////////////////TICKET #1071 no wildcard includes please! -using namespace gui::util; /////////////////////////////////////////////////////////////////////////////TICKET #1071 no wildcard includes please! +using namespace gui::draw; ///////////////////////////////////////////////////////////////////////////////TICKET #1071 no wildcard includes please! namespace gui { namespace widget { @@ -119,7 +119,7 @@ namespace timeline { BOOST_FOREACH( pair, headerBoxes ) { // Hit test the rectangle - if(util::pt_in_rect(point, pair.second)) + if (gui::draw::pt_in_rect (point, pair.second)) return shared_ptr(pair.first); } diff --git a/src/gui/widget/timeline/timeline-layout-helper.hpp b/src/gui/widget/timeline/timeline-layout-helper.hpp index a094e1da3..b9c9fe514 100644 --- a/src/gui/widget/timeline/timeline-layout-helper.hpp +++ b/src/gui/widget/timeline/timeline-layout-helper.hpp @@ -61,7 +61,7 @@ namespace gui { * @see gui::widget::TimelineWidget */ class TimelineLayoutHelper - : public boost::noncopyable + : util::NonCopyable { public: /** Definition of the layout track tree type.*/ diff --git a/src/gui/widget/timeline/timeline-ruler.cpp b/src/gui/widget/timeline/timeline-ruler.cpp index 5324add22..9e5431b57 100644 --- a/src/gui/widget/timeline/timeline-ruler.cpp +++ b/src/gui/widget/timeline/timeline-ruler.cpp @@ -30,7 +30,7 @@ #include "gui/widget/timeline/timeline-ruler.hpp" #include "gui/widget/timeline-widget.hpp" #include "gui/workspace/style-manager.hpp" -#include "gui/util/cairo-util.hpp" +#include "gui/draw/cairo-util.hpp" #include "lib/time/timevalue.hpp" #include "lib/time.h" @@ -43,7 +43,7 @@ using namespace gui::widget; /////////////////////////////////////////////// using namespace gui::widget::timeline; ///////////////////////////////////////////////////////////////////////////////TICKET #1071 no wildcard includes please! using std::shared_ptr; -using gui::util::CairoUtil; +using gui::draw::CairoUtil; using lib::time::Time; using lib::time::TimeVar; diff --git a/src/gui/widget/timeline/timeline-state.hpp b/src/gui/widget/timeline/timeline-state.hpp index 35f60aab2..1cd2e0d3c 100644 --- a/src/gui/widget/timeline/timeline-state.hpp +++ b/src/gui/widget/timeline/timeline-state.hpp @@ -66,7 +66,7 @@ namespace gui { */ template class SelectionListener - : boost::noncopyable + : util::NonCopyable { sigc::signal valueChangedSignal_; diff --git a/src/gui/widget/video-display-widget.cpp b/src/gui/widget/video-display-widget.cpp index 00664f139..cb0beb506 100644 --- a/src/gui/widget/video-display-widget.cpp +++ b/src/gui/widget/video-display-widget.cpp @@ -23,6 +23,7 @@ /** @file video-display-widget.cpp ** Implementation of video display, embedded into the UI. + ** @deprecated defunct since the transition to GTK-3 */ diff --git a/src/gui/widget/video-display-widget.hpp b/src/gui/widget/video-display-widget.hpp index 451bc4074..4cc35d33a 100644 --- a/src/gui/widget/video-display-widget.hpp +++ b/src/gui/widget/video-display-widget.hpp @@ -23,6 +23,7 @@ /** @file video-display-widget.hpp ** Widget to create a video display embedded into the UI + ** @deprecated defunct since the transition to GTK-3 */ @@ -39,7 +40,13 @@ using namespace gui::output; ////////////////////////////////////////////////// namespace gui { namespace widget { - + + /** + * @todo the first UI draft included a video displayer widget library implementation, + * Unfortunately, this became defunct with the switch to GTK-3. And a fun fact is, + * even while Lumiera is a video editing application, we did not yet reach the state + * as to care for video display ourselves. Someone (TM) need to care for this! + */ class VideoDisplayWidget : public Gtk::DrawingArea { diff --git a/src/gui/workspace/panel-manager.cpp b/src/gui/workspace/panel-manager.cpp index 9a027b323..9d2cd1481 100644 --- a/src/gui/workspace/panel-manager.cpp +++ b/src/gui/workspace/panel-manager.cpp @@ -31,6 +31,7 @@ #include "gui/panel/assets-panel.hpp" #include "gui/panel/viewer-panel.hpp" +#include "gui/panel/infobox-panel.hpp" #include "gui/panel/timeline-panel.hpp" #include "gui/panel/timeline-panel-obsolete.hpp" @@ -47,6 +48,7 @@ namespace workspace { PanelManager::panelDescriptionList[] = { PanelManager::Panel(), PanelManager::Panel(), + PanelManager::Panel(), PanelManager::Panel(), PanelManager::Panel() }; @@ -236,7 +238,7 @@ namespace workspace { { ///////////////////////////////TICKET #1026 : code smell, use types directly instead panel::Panel* assetsPanel = createPanel_by_name("AssetsPanel"); - panel::Panel* viewerPanel = createPanel_by_name("ViewerPanel"); + panel::Panel* viewerPanel = createPanel_by_name("InfoBoxPanel"); panel::Panel* timelinePanel = createPanel_by_name("TimelinePanel"); dock_.add_item(assetsPanel->getDockItem(),Gdl::DOCK_LEFT); diff --git a/src/gui/workspace/style-manager.cpp b/src/gui/workspace/style-manager.cpp index 80f2a1f6f..81b044ce9 100644 --- a/src/gui/workspace/style-manager.cpp +++ b/src/gui/workspace/style-manager.cpp @@ -152,6 +152,7 @@ namespace workspace { addStockIconSet(factory, "panel-assets", "panel_assets", _("_Assets")); addStockIconSet(factory, "panel-viewer", "panel_viewer", _("_Viewer")); + addStockIconSet(factory, "panel-infobox", "panel_infobox", _("_InfoBox")); addStockIconSet(factory, "panel-timeline", "panel_timeline",_("_Timeline")); addStockIconSet(factory, "panel-timeline", "panel_timeline_obsolete",_("_ZombieTimeline")); diff --git a/src/gui/workspace/style-manager.hpp b/src/gui/workspace/style-manager.hpp index 14ab21c15..3330c64e5 100644 --- a/src/gui/workspace/style-manager.hpp +++ b/src/gui/workspace/style-manager.hpp @@ -37,8 +37,8 @@ #define GUI_WORKSPACE_STYLE_MANAGER_H #include "gui/gtk-base.hpp" +#include "lib/nocopy.hpp" -#include #include #include @@ -64,7 +64,7 @@ namespace workspace { */ class StyleManager : public Gtk::UIManager - , boost::noncopyable + , util::NonCopyable { string iconSearchPath_; diff --git a/src/include/config-facade.h b/src/include/config-facade.h index 173af49d0..b00b645f0 100644 --- a/src/include/config-facade.h +++ b/src/include/config-facade.h @@ -81,7 +81,7 @@ namespace lumiera { private: Config(); ~Config(); - friend class lib::DependencyFactory; + friend class lib::DependencyFactory; }; diff --git a/src/include/display-facade.h b/src/include/display-facade.h index 13e14eb90..270dc6172 100644 --- a/src/include/display-facade.h +++ b/src/include/display-facade.h @@ -51,10 +51,9 @@ #ifdef __cplusplus /* ============== C++ Interface ================= */ -#include "include/interfaceproxy.hpp" +#include "lib/depend.hpp" #include "lib/handle.hpp" -#include @@ -88,7 +87,7 @@ namespace lumiera { { public: /** get an implementation instance of this service */ - static lumiera::facade::Accessor facade; + static lib::Depend facade; /** @@ -119,8 +118,7 @@ namespace lumiera { virtual Sink getHandle(LumieraDisplaySlot) =0; - protected: - virtual ~Display(); + virtual ~Display(); ///< this is an interface }; diff --git a/src/include/dummy-player-facade.h b/src/include/dummy-player-facade.h index 6a11672d4..1fc72eec2 100644 --- a/src/include/dummy-player-facade.h +++ b/src/include/dummy-player-facade.h @@ -22,6 +22,7 @@ /** @file dummy-player-facade.h + * @deprecated left-over from an early design draft (2009) */ @@ -36,7 +37,7 @@ #ifdef __cplusplus /* ============== C++ Interface ================= */ #include "common/subsys.hpp" -#include "include/interfaceproxy.hpp" +#include "lib/depend.hpp" #include "lib/handle.hpp" @@ -65,7 +66,8 @@ namespace lumiera { * with our interface layout and the way we create bindings to the * implementation. The moment we attempt to use other abstractions * within an interface (as we do here with the Process interface), - * we're running into serious maintenance and library dependency problems. + * we're running into serious maintenance and library dependency problems. + * @deprecated obsoleted design from 2009 and not operative anymore (2018) */ class DummyPlayer { @@ -76,7 +78,7 @@ namespace lumiera { static lumiera::Subsys& getDescriptor(); /** get an implementation instance of this service */ - static lumiera::facade::Accessor facade; + static lib::Depend facade; class ProcessImplementationLink; @@ -129,6 +131,7 @@ namespace lumiera { protected: virtual ~DummyPlayer(); + friend class lib::DependencyFactory; }; diff --git a/src/include/gui-notification-facade.h b/src/include/gui-notification-facade.h index 759548f9d..4e06607e7 100644 --- a/src/include/gui-notification-facade.h +++ b/src/include/gui-notification-facade.h @@ -43,7 +43,7 @@ #ifdef __cplusplus /* ============== C++ Interface ================= */ -#include "include/interfaceproxy.hpp" +#include "lib/depend.hpp" #include "lib/diff/mutation-message.hpp" #include "lib/idi/entry-id.hpp" @@ -76,8 +76,12 @@ namespace gui { */ class GuiNotification { + protected: + virtual ~GuiNotification() {} ///< this is an interface + friend class lib::DependencyFactory; + public: - static lumiera::facade::Accessor facade; + static lib::Depend facade; /** push a user visible notification text */ @@ -108,9 +112,6 @@ namespace gui { */ virtual void triggerGuiShutdown (string const& cause) =0; - - protected: - virtual ~GuiNotification() {} }; @@ -136,6 +137,6 @@ LUMIERA_INTERFACE_DECLARE (lumieraorg_GuiNotification, 0, #ifdef __cplusplus -} +}// extern "C" #endif #endif /*GUI_GUI_NOTIFICATION_H*/ diff --git a/src/include/interfaceproxy.hpp b/src/include/interfaceproxy.hpp index 78ef4b9ee..f9b315a3a 100644 --- a/src/include/interfaceproxy.hpp +++ b/src/include/interfaceproxy.hpp @@ -21,56 +21,102 @@ */ /** @file interfaceproxy.hpp - ** Facade Interfaces Lifecycle. Communication between the Layers within Lumiera - ** usually is routed through *Layer Separation Interfaces*. These are comprised - ** of a Facade interface and a equivalent rendering as C Language interface defined - ** with the help of the Interface/Plugin system. But in order to be able to actually - ** access a service via this Facade, you need an instance of the interface. + ** Implementation of C++ binding proxies on top of the (plain-C based) interface system. + ** This is an implementation facility within the application core, which allows to embody + ** just an ["interface instance handle"](\ref instancehandle.hpp) into the implementation + ** os some service, in order to get RAII-style registration of interfaces and loading of + ** plug-ins. ** - ** lumiera::facade::Proxy and InstanceHandle together are used to create such an concrete - ** instance of the Facade interface. It is implemented such as to route each call - ** through the corresponding C Language function defined in the Interface/Plugin system. - ** Typically there is another subclass of the Facade interfaces sitting "on the other side" - ** of the interface barrier and actually implementing the functionality. The template - ** facade::Accessor can be thought of as a factory creating such a proxy instance of the - ** facade interface for the client code to use. Typically, an instance of the _factory_ - ** is embedded (as a static functor member object) right within the otherwise abstract - ** facade interface, this way allowing the client code to write e.g. `XYZInterface::facade()` + ** A *crucial requirement* for this approach to work is that any relevant interface + ** to be bound and exposed as C++ object needs to set up a concrete specialisation of + ** lumiera::facade::Proxy to drive instantiation of the actual binding proxy. + ** The result of this setup is that clients can just invoke `SomeInterface::facade()` + ** and thus call through proper C++ bindings with type safety and automatic + ** lifecycle management. + ** + ** # Interface, Plug-in, Facade interface, Instance Handle and Proxy + ** + ** These are all terms related to the Interface- and Plug-in system for Lumiera. + ** Communication between the Layers within the architecture is usually routed through + ** *Layer Separation Interfaces*. Here we have to distinguish two different flavours + ** of an "interface" + ** - A Façade interface is written in C++ and is what you's usually + ** denote with the term "interface": it defines a contract in terms of + ** some abstract entities, without exposing implementation details. + ** Ideally, the interface holds all you need to use a given service. + ** - A C Language interface defined with the help of the Interface/Plugin system. + ** It is a collection of functions and supports only the primitive types of the + ** bare C Language. Objects need to be emulated by pointers to a struct type, + ** and functors must be represented as static function pointers. In many cases + ** you need to fall back to untyped `void*` unfortunately. + ** + ** @todo 2018 as it stands (since 2008), the Interface/Plug-in system fulfils the basic task + ** it was created for, but is rather cumbersome to use in practice. We should investigate + ** to use SWIG or something similar to generate the bindings and the low-level interfaces. + ** + ** The Interface/Plug-in system offers two basic usage scenarios + ** - a CL-Interface can be published (from the service provider side). + ** From that point on, clients can "open" that interface and talk to it. + ** - a _client_ can use the CL-Interface of a Plug-in to _load_ a plug-in instance. + ** From that point on, clients can talk through a interface handle to the plug-in. + ** + ** An Attempt was made to simplify and unify this process with the help of an InstanceHandle. + ** This is an RAII-style handle object, which automates the registration and instance management. + ** + ** But in order to be able to actually access some service via a high-level façade interface, + ** we still need a way to get a callable instance of the façade interface. This is where the + ** proxy implementation comes into play. The binding proxy implements the façade and maps each + ** high-level call into an invocation of the corresponding low-level function on the CL-interface. + ** + ** Whenever InstanceHandle was created with a second template parameter defining a façade interface, + ** it automatically attempts to instantiate a lumiera::facade::Proxy templated to the actual type + ** of the InstanceHandle. This proxy instance is then exposed via `lib::Depend` + ** This way, any call will be routed through the corresponding C Language function defined within + ** the Interface/Plugin system. Moreover, there will be another subclass of the Facade interface + ** sitting "on the other side" of the interface barrier to _actually implement_ the functionality. + ** + ** As a convention, each façade interface should hold a static accessor member named "facade" of + ** type `lib::Depend`, so client code can write e.g. `XYZInterface::facade()` ** to yield a reference to a proxy object implementing `XYZInterface`. ** + ** ** # Interface Lifecycle ** ** Instances of an Interface are either directly provided by some facility within the core, ** or they are loaded from a shared module (plugin). In either case this means the interface ** isn't accessible all the time, rather it comes up at a defined point in the application ** lifecycle and similarly will be shut down deliberately at some point. Beyond this time - ** window of availability, any access through the proxy factory throws an lumiera::error::State. + ** window of availability, any access through the proxy factory throws an lumiera::error::Fatal. ** Any sort of dependency management is outside the scope of the InstanceHandle (for the core ** services, it is handled by the dependency of subsystems, while the plugin loader cares ** for dependency issues regarding loadable modules, thereby building on the deployment ** descriptors.) ** ** For the Layer separation interfaces, the process of loading and opening is abstracted as - ** an InstanceHandle object. When creating such an InstanceHandle using the appropriate - ** template and ctor parameters, in addition to the registration with the Interface/Plugin - ** system, the corresponding facade::Proxy factory is addressed and the interface instance - ** is "opened" by creating the appropriate proxy object instance. Similarly, when the - ** InstanceHandle object goes out of scope, prior to detaching from the Interface/Proxy - ** system, the corresponding lumiera::facade::Accessor frontend is "closed", which - ** additionally means destroying the proxy object instance and switching any - ** further access to throwing and exception. + ** an InstanceHandle object. A service exposing an interface defines an InstanceHandle + ** member using the appropriate template and ctor parameters; this causes registration with + ** the Interface/Plugin and instantiates the corresponding facade::Proxy, which is then + ** exposed through the lib::Depend front-end. Similarly, when the service implementation + ** object is destroyed, the InstanceHandle goes out of scope, thereby detaching from the + ** Interface/Proxy and deregistering and destroying the proxy object. Any further + ** access beyond that point will raise an exception. ** - ** While client code just includes the interface header (including interfaceproxy.hpp - ** in turn), there needs to be an actual implementation of each proxy object located in - ** some translation unit. The usual place is interfaceproxy.cpp, which gets linked into - ** `liblumieracommon.so` and contains actual specialisations and literal forwarding - ** code _for each individual facade._ - ** - ** @see interface.h - ** @see plugin.h - ** @see lumiera::Subsys - ** @see guinotification.h usage example (facade interface) - ** @see guinotification-facade.cpp corresponding implementation within the GUI + ** # Usage + ** + ** While client code just includes the interface header (including lib/depend.hpp), + ** there needs to be an actual implementation of each proxy object located in some translation + ** unit, linked into the application core or `liblumieracommon.so`. This translation unit + ** needs to specialise lumiera::facade::Proxy and then create an instance of that template. + ** And, most importantly, such translation units (and _only such translation units_) must + ** include this header `interfaceproxy.hpp` -- because it defines the concrete ctor + ** and dtor of the facade::Link template and thus creates the missing "link" between + ** the InstanceHandle and the actual proxy instantiation. + ** + ** @see instancehandle.hpp + ** @see gui-notification-facade.h usage example (facade interface) + ** @see notification-service.hpp + ** @see notification-service.cpp (service implementation) + ** @see notification-interface-proxy.cpp */ @@ -78,69 +124,62 @@ #define LUMIERA_INTERFACE_PROXY_H +#include "common/instancehandle.hpp" #include "lib/error.hpp" +#include "lib/util.hpp" - +using util::cStr; namespace lumiera { namespace facade { - /** error-ID for accessing a (currently) closed facade */ - LUMIERA_ERROR_DECLARE(FACADE_LIFECYCLE); - - - /*****************************************************************//** - * access-frontend to the implementation of a service. - * Usually, an instance of Accessor is placed as static member - * right into the facade interface used to access the service. - * This allows clients to get the current actual implementation - * of that service, just by invoking the function operator on - * that member, e.g. \c lumiera::Play::facade() - * - * The reason for this rather indirect access technique is Lifecycle: - * Service implementations may come up and go down; moreover, a service - * might be implemented through a plugin component and thus the actual - * invocation needs to be passed through a binding layer. In this case, - * clients rather access a proxy object, which then passes on any call - * through that binding layer to the actual implementation located - * "somewhere". - * - * @note the pointer to the actual implementation is a static variable. - * This has two consequences. For one, we're dealing with kind of - * singleton service here. And, secondly, the implementation or - * proxy accessor can inherit from Accessor where FA is the - * facade interface. Being a subclass, allows the implementation - * to set that pointer when the service comes up, and to clear - * it when the service goes down and the access needs to - * be closed. + /** + * Implementation Base + * for building Facade Proxy implementations. + * Typically the purpose of such a proxy is to route + * any calls through the C-Bindings of the Lumiera Interface system. + * The actual instance and thus the storage for the concrete proxy object + * is controlled via lib::DependInject::ServiceInstance, which in turn is + * embedded into and thus linked to the lifetime of a InstanceHandle to + * connect via Lumiera's Interface/Plug-in system. Typically the actual + * Service implementation object will hold an instance of that InstanceHandle + * and thus tie the opening/closing of the interface and access mechanism + * to the service lifecycle. */ - template - class Accessor + template + struct Binding; + + template + struct Binding> + : public FA { - protected: - static FA* implProxy_; + using IHandle = InstanceHandle; + I& _i_; - public: - FA& - operator() () - { - if (implProxy_) - return *implProxy_; - else - throw error::State("Facade interface currently closed." - , LUMIERA_ERROR_FACADE_LIFECYCLE); - } + Binding (IHandle const& iha) + : _i_(iha.get()) + { } }; - template - void openProxy (IHA const&); - template - void closeProxy (); - template - class Proxy; + template + Link::Link (IH const& iha) + : ServiceHandle{iha} + { } + + template + Link::~Link() { } + + + template + FA* + Link::operator->() const + { + return ServiceHandle::operator->(); + } + }} // namespace lumiera::facade diff --git a/src/include/lifecycle.h b/src/include/lifecycle.h index da4c526b3..342e07207 100644 --- a/src/include/lifecycle.h +++ b/src/include/lifecycle.h @@ -41,7 +41,7 @@ #ifdef __cplusplus #include "lib/symbol.hpp" -#include +#include "lib/nocopy.hpp" @@ -74,7 +74,7 @@ namespace lumiera { * @note duplicate or repeated calls with the same callback are NOP */ class LifecycleHook - : private boost::noncopyable + : util::NonCopyable { public: typedef void (*Hook)(void); diff --git a/src/include/play-facade.h b/src/include/play-facade.hpp similarity index 86% rename from src/include/play-facade.h rename to src/include/play-facade.hpp index ecfc536a7..abf8188d2 100644 --- a/src/include/play-facade.h +++ b/src/include/play-facade.hpp @@ -21,8 +21,13 @@ */ -/** @file play-facade.h +/** @file play-facade.hpp ** Public access point to the _playback service_ provided by the »play-out subsystem« + ** @todo in theory this could be an external interface mapped via Lumiera's interface system. + ** This would be much work however, and not serve any tangible goal in the current stage + ** of development (2018). I take this as just another confirmation that turning everything + ** into a plug-in does not quality as good architecture: if you want to do it right, it + ** creates a lot of additional cost. And to do it just superficially would be like cheating. */ @@ -31,15 +36,11 @@ - -#ifdef __cplusplus /* ============== C++ Interface ================= */ - -//#include "include/interfaceproxy.hpp" +#include "lib/depend.hpp" #include "lib/handle.hpp" -#include "lib/iter-source.hpp" +#include "lib/iter-source.hpp" ////////////////////TICKET #493 : only using the IterSource base interface here #include "lib/time/control.hpp" #include "lib/time/timevalue.hpp" -#include "include/interfaceproxy.hpp" #include "proc/mobject/model-port.hpp" #include "proc/mobject/output-designation.hpp" #include "proc/mobject/session/clip.hpp" @@ -80,10 +81,13 @@ namespace lumiera { */ class Play { + protected: + virtual ~Play(); + friend class lib::DependencyFactory; public: /** get an implementation instance of this service */ - static lumiera::facade::Accessor facade; + static lib::Depend facade; /** @@ -144,8 +148,6 @@ namespace lumiera { Controller perform(Clip); protected: - virtual ~Play(); - /** core operation: create a new playback process * outputting to the given viewer/display */ virtual Controller connect(ModelPorts, Output) =0; @@ -153,19 +155,4 @@ namespace lumiera { } // namespace lumiera - - - - -extern "C" { -#endif /* =========================== CL Interface ===================== */ - - -// #include "common/interface.h" -////////////////////////////////////TODO define a C binding for the Interface system here - - -#ifdef __cplusplus -} -#endif -#endif +#endif /*PROC_INTERFACE_PLAY_H*/ diff --git a/src/include/session-command-facade.h b/src/include/session-command-facade.h index 973f9df10..5c1205e8d 100644 --- a/src/include/session-command-facade.h +++ b/src/include/session-command-facade.h @@ -51,7 +51,7 @@ #ifdef __cplusplus /* ============== C++ Interface ================= */ -#include "include/interfaceproxy.hpp" +#include "lib/depend.hpp" #include "lib/diff/gen-node.hpp" #include "lib/symbol.hpp" @@ -93,7 +93,7 @@ namespace control { class SessionCommand { public: - static lumiera::facade::Accessor facade; + static lib::Depend facade; /** start next command cycle and "open" a new anonymous command instance */ virtual Symbol cycle (Symbol cmdID, string const& invocationID) =0; @@ -109,7 +109,8 @@ namespace control { protected: - virtual ~SessionCommand() {} + virtual ~SessionCommand() {} ///< this is an interface + friend class lib::DependencyFactory; }; @@ -132,6 +133,6 @@ LUMIERA_INTERFACE_DECLARE (lumieraorg_SessionCommand, 0, #ifdef __cplusplus -} +}// extern "C" #endif #endif /*PROC_CONTROL_SESSION_COMMAND_H*/ diff --git a/src/lib/allocation-cluster.hpp b/src/lib/allocation-cluster.hpp index 1f393aef6..bf9fb1872 100644 --- a/src/lib/allocation-cluster.hpp +++ b/src/lib/allocation-cluster.hpp @@ -48,11 +48,11 @@ #define LIB_ALLOCATION_CLUSTER_H #include "lib/error.hpp" +#include "lib/nocopy.hpp" #include "lib/sync-classlock.hpp" #include "lib/scoped-holder.hpp" #include "lib/scoped-holder-transfer.hpp" -#include #include @@ -88,7 +88,7 @@ namespace lib { * Is this issue worth the hassle? //////////////////////////////TICKET #169 */ class AllocationCluster - : boost::noncopyable + : util::NonCopyable { public: diff --git a/src/lib/call-queue.hpp b/src/lib/call-queue.hpp index 9be6e9483..1b20ee381 100644 --- a/src/lib/call-queue.hpp +++ b/src/lib/call-queue.hpp @@ -40,8 +40,8 @@ #include "lib/error.hpp" #include "lib/sync.hpp" #include "lib/iter-stack.hpp" +#include "lib/nocopy.hpp" -#include #include @@ -56,7 +56,7 @@ namespace lib { * their concrete parameters into another thread for invocation. */ class CallQueue - : boost::noncopyable + : util::NonCopyable , public Sync<> { public: diff --git a/src/lib/del-stash.hpp b/src/lib/del-stash.hpp index 629b5da49..fac9523e1 100644 --- a/src/lib/del-stash.hpp +++ b/src/lib/del-stash.hpp @@ -45,18 +45,14 @@ #include "lib/error.hpp" +#include "lib/nocopy.hpp" -#include #include -#include -#include -#include +#include namespace lib { - using boost::disable_if_c; - using boost::is_same; /** * Manage a collection of deleter functions. @@ -66,7 +62,7 @@ namespace lib { * @warning clients must not add a given object more than once */ class DelStash - : boost::noncopyable + : util::NonCopyable { typedef void KillFun(void*); @@ -82,14 +78,14 @@ namespace lib { public: Killer(KillFun* f, void* t) - : target_(t) - , killIt_(f) + : target_{t} + , killIt_{f} { REQUIRE(f); } void - trigger () + trigger() { if (target_) killIt_(target_); @@ -115,7 +111,7 @@ namespace lib { public: DelStash (size_t elms_to_reserve =0) : killers_() - { + { if (elms_to_reserve) { killers_.reserve (elms_to_reserve); @@ -144,14 +140,10 @@ namespace lib { } -#define __DONT_USE_THIS_OVERLOAD_FOR_VOID_POINTER_ \ - typename disable_if_c< is_same::value \ - ||is_same::value>::type* =0 - template void - manage (TY* obj, __DONT_USE_THIS_OVERLOAD_FOR_VOID_POINTER_) + manage (TY* obj)//, __disable_if_voidPtr =0) { if (!obj) return; REQUIRE (!isRegistered (obj)); @@ -160,21 +152,12 @@ namespace lib { template void - manage (TY& obj, __DONT_USE_THIS_OVERLOAD_FOR_VOID_POINTER_) + manage (TY& obj)//, __disable_if_voidPtr =0) { REQUIRE (!isRegistered (&obj)); killers_.push_back (Killer (how_to_kill, &obj)); } - template - void - manage (void* obj) - { - if (!obj) return; - REQUIRE (!isRegistered (obj)); - killers_.push_back (Killer (how_to_kill, obj)); - } - void manage (void* obj, KillFun* customDeleter) { @@ -231,7 +214,7 @@ namespace lib { findEntry (const void* obj) { REQUIRE (obj); - return std::find(killers_.begin(),killers_.end(), obj); + return std::find (killers_.begin(),killers_.end(), obj); } void diff --git a/src/lib/depend-inject.hpp b/src/lib/depend-inject.hpp new file mode 100644 index 000000000..a7a99ed43 --- /dev/null +++ b/src/lib/depend-inject.hpp @@ -0,0 +1,467 @@ +/* + DEPEND-INJECT.hpp - managing the lifecycle of singletons and dependencies + + Copyright (C) Lumiera.org + 2018, Hermann Vosseler + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + + +/** @file depend-inject.hpp + ** Per type specific configuration of instances created as service dependencies. + ** This is the _"Backstage Area"_ of lib::Depend, where the actual form and details + ** of instance creation can be configured in various ways. Client code typically + ** plants an instance of lib::Depend, templated to the actual type of the dependency. + ** This is a way to express the dependency on some interface or service, while not + ** expanding on any details as to when and how this dependency is created. Without + ** an explicit configuration, lib::Depend will automatically create and manage a + ** singleton instance of the type given as type parameter. + ** + ** ## Architecture + ** A _dependency_ is understood as something we need to perform the task at hand, + ** yet a dependency referres beyond that task and relates to concerns outside the + ** scope and theme of this actual task. The usage site of the dependency is only + ** bound and coupled to the _interface_ exposed as dependency, and the associated + ** _contract_ of a service. Initially, such a dependency is _dormant_ and will + ** be activated on first access. This simplifies the bootstrap of complexly + ** interwoven structures; it suffices to ensure that none of the participating + ** entities actually starts its work before all of the setup and wiring is done. + ** + ** For that reason, lib::DependInject is meant to be used at the site providing + ** the _actual_ service or implementation subtype -- not at the site consuming a + ** dependency (through lib::Depend). This choice also means that the actual + ** configuration of services is not centralised, and can not be static; it need + ** to happen prior to any service access (on violation error::Logic is raised) + ** + ** The public configuration mechanisms offered by DependInject address various concerns: + ** + ** \paragraph Wiring + ** Even when relying on lazy on-demand initialisation, a concrete service implementation + ** typically needs to connect to further services, and maybe even decide upon the actual + ** subclass to be instantiated. By invoking the DependInject::useSingleton(FUN) + ** function, a functor or lambda can be installed into the static factory of lib::Depend. + ** Especially, a lambda could be bound into the internal context of the service provider. + ** This function is expected to deliver a heap allocated instance on invocation, which + ** will be owned and managed by lib::Depend::factory (A DependencyFactory). + ** + ** \paragraph Service Lifecycle + ** Whenever a module or subsystem can be started and stopped, several interconnected + ** services become operational together, with dependent lifecycle. It is possible to + ** expose such services through a lib::Depend front-end; this way, the actual + ** usage context remains agnostic with respect to details of the lifecycle. Any + ** access while the service is _not_ available will just raise an error::Logic. + ** This kind of configuration can be achieved by planting a smart-handle of type + ** DependInject::ServiceInstance + ** + ** \paragraph Unit Testing with Mock Services + ** Dependencies tend to hamper unit testing, but global variables and actively + ** linked and collaborating implementations are worse and often prevent test coverage + ** altogether. Preferably dependencies are limited to an interface and a focused topic, + ** and then it might be possible to inject a _mock implementation_ locally within the + ** unit test. Such a mock instance temporarily shadows any previously existing state, + ** instance or configuration for this dependency; the mock object can be rigged and + ** instrumented by the test code to probe or observe the subject's behaviour. This + ** concept can only work when the test subject does not cache any state and really + ** pulls the dependency whenever necessary. + ** + ** + ** ## Performance, concurrency and sanity + ** + ** The lib::Depend front-end is optimised for the access path. It uses an + ** std::atomic to hold the instance pointer and a class-level Mutex to protect + ** the initialisation phase. On the other hand, initialisation happens only once + ** and will be expensive anyway. And, most importantly, for any non-standard + ** configuration we assume, that -- by architecture -- there is _no contention_ + ** between usage and configuration. Services are to be started in a dedicated bootstrap + ** phase, and unit tests operated within a controlled, single threaded environment. + ** For this reason, any configuration grabs the lock, and publishes via the default + ** memory order of std::atomic (which is std::memory_order_seq_cst). Any spotted + ** collision or inconsistency raises an exception, which typically should not + ** be absorbed, but rather trigger component, subsystem or application shutdown. + ** @warning there is a known coherency breach in "emergency shutdown": When a + ** subsystem collapses unexpectedly, its root handler signals the subsystem + ** runner to initiate emergency shutdown. However, the collapsed subsystem + ** is already defunct at that point, which breaks the general contract of + ** prerequisite subsystems to be available in operative mode. Lumiera + ** is not built as a resilient service in that respect, but we also + ** mandate for any parts not to cache essential work results in + ** transient memory; actions are logged and performed for + ** persistent history and UNDO. + ** + ** @see DependencyConfiguration_test + ** @see SingletonSubclass_test + ** @see subsys.hpp + */ + + + + +#ifndef LIB_DEPEND_INJECT_H +#define LIB_DEPEND_INJECT_H + + + +#include "lib/error.hpp" +#include "lib/nocopy.hpp" +#include "lib/depend.hpp" +#include "lib/meta/trait.hpp" +#include "lib/meta/function.hpp" +#include "lib/sync-classlock.hpp" + +#include +#include +#include + + +namespace lib { + namespace error = lumiera::error; + + using std::forward; + using std::move; + + + + + /** + * This framework allows to (re)configure the lib::Depend front-end for dependency-injection. + * By default, `Depend` will create a singleton instance of `TY` lazily, on demand. + * When instantiating one of the configuration handles provided here -- _prior_ to anyone + * retrieving the instance through `Depend` -- this default (singleton) behaviour + * can be reconfigured in various ways, without the client being aware of it: + * - instead of a singleton, a service instance with well defined lifecycle can be + * exposed through the `Depend` front-end. When the service is shut down, + * clients will receive an exception on access. + * - instead of the interface type `TY` mentioned in `Depend`, an implementation subclass + * can be specified, optionally with a closure for the actual creation of this subclass + * singleton type, which still happens lazily, on demand + * - the current state and configuration can be shadowed temporarily by a test mock instance, + * which is managed automatically and removed when leaving the scope of the test. + * @note DependInject is declared fried by Depend to reconfigure the latter's internals + */ + template + class DependInject + : util::NoInstance + { + using Factory = typename Depend::Factory; + using Lock = typename Depend::Lock; + + public: + /** configure dependency-injection for type SRV to build a subclass singleton. + * @note actually a delegation to `Depend` is installed into `Depend` + * @tparam SUB concrete subclass type to build on demand when invoking `Depend` + * @throws error::Logic (LUMIERA_ERROR_LIFECYCLE) when the default factory has already + * been invoked at the point when calling this (re)configuration function. + */ + template + static void + useSingleton() + { + __assert_compatible(); + installFactory(); + } + + /** configure dependency-injection for type SRV to manage a subclass singleton, + * which is created lazily on demand by invoking the given builder function. + * @note actual Subclass type is determined from the given functor and then + * a delegation to `Depend` is installed into `Depend` + * @param ctor functor to create a heap allocated instance of the subclass + * @throws error::Logic (LUMIERA_ERROR_LIFECYCLE) when the default factory has already + * been invoked at the point when calling this (re)configuration function. + */ + template + static void + useSingleton (FUN&& ctor) + { + using Sub = typename SubclassFactoryType::Subclass; + using Fun = typename SubclassFactoryType::Functor; + + __assert_compatible(); + installFactory (forward (ctor)); + } + + + /** + * Configuration handle to expose a service implementation through the `Depend` front-end. + * This non-copyable (but movable) handle shall be planted within the context operating the service + * to be exposed. It will immediately create (in RAII style) and manage a heap-allocated instance + * of the subclass `IMP` and expose a baseclass pointer to this specific instance through `Depend`. + * Moreover, the implementation subclass can be accessed through this handle, which acts as smart-ptr. + * When the handle goes out of scope, the implementation instance is destroyed and the access through + * `Depend` is closed and inhibited, to prevent on-demand creation of a baseclass `SRV` singleton. + * @tparam IMP concrete service implementation subclass to build, manage and expose. + * @throws error::Logic (LUMIERA_ERROR_LIFECYCLE) when the default factory has already + * been invoked at the point when calling this (re)configuration function. + */ + template + class ServiceInstance + : util::MoveOnly + { + std::unique_ptr instance_; + + public: + template + ServiceInstance(ARGS&& ...ctorArgs) + : instance_{new IMP(forward (ctorArgs)...)} + { + __assert_compatible(); + activateServiceAccess (*instance_); + } + + enum StartMode { NOT_YET_STARTED }; + + /** create in deactivated state. Can be [activated](\ref #createInstance) later */ + ServiceInstance(StartMode) + : instance_{} + { + __assert_compatible(); + } + + ~ServiceInstance() + { + shutdown(); + } + + template + IMP& + createInstance(ARGS&& ...ctorArgs) + { + instance_.reset (new IMP(forward (ctorArgs)...)); + activateServiceAccess (*instance_); + return *instance_; + } + + void + shutdown() noexcept + { + if (instance_) + { + deactivateServiceAccess(); + instance_.reset(); + } + } + + + explicit + operator bool() const + { + return bool(instance_); + } + + IMP& + operator* () const + { + ENSURE (instance_); + return *instance_; + } + + IMP* + operator->() const + { + ENSURE (instance_); + return instance_.get(); + } + }; + + + /** + * Configuration handle for temporarily shadowing a dependency by a test mock instance. + * This non-copyable (but movable) handle shall be planted within the immediate test context. + * It immediately stashes away the existing state and configuration from `Depend`, but + * waits for actual invocation of the `Depend`-front-end to create a heap-allocated + * instance of the `MOC` subclass, which it manages and exposes like a smart-ptr. + * When the handle goes out of scope, the original state and configuration is restored + */ + template + class Local + : util::MoveOnly + { + std::unique_ptr mock_; + + SRV* origInstance_; + Factory origFactory_; + + public: + Local() + : Local([]{ return new MOC{}; }) + { } + + template + explicit + Local (FUN&& buildInstance) + { + __assert_compatible(); + __assert_compatible::Subclass>(); + + temporarilyInstallAlternateFactory (origInstance_, origFactory_ + ,[=]() + { + mock_.reset (buildInstance()); + return mock_.get(); + }); + } + ~Local() + { + restoreOriginalFactory (origInstance_, move(origFactory_)); + } + + explicit + operator bool() const + { + return bool(mock_); + } + + /** trigger lazy service object instantiation */ + MOC& + triggerCreate() + { + Depend{}.operator()(); + ENSURE (mock_); + return *mock_; + } + + MOC& + operator* () const + { + REQUIRE (mock_); + return *mock_; + } + + MOC* + operator-> () const + { + REQUIRE (mock_); + return mock_.get(); + } + }; + + + + protected: /* ======= internal access-API for those configurations to manipulate Depend ======= */ + template + friend class ServiceInstance; + template + friend class Local; + + + template + static void + __assert_compatible() + { + static_assert (meta::is_Subclass() + ,"Installed implementation class must be compatible to the interface."); + } + + static void + __ensure_pristine() + { + if (Depend::instance) + throw error::Logic("Attempt to reconfigure dependency injection after the fact. " + "The previously installed factory (typically Singleton) was already used." + , error::LUMIERA_ERROR_LIFECYCLE); + } + + template + struct SubclassFactoryType + { + static_assert (meta::_Fun(), + "Need a Lambda or Function object to create a heap allocated instance"); + + using Functor = typename meta::_Fun::Functor; // suitable type to store for later invocation + using ResultVal = typename meta::_Fun::Ret; + using Subclass = typename meta::Strip::TypePlain; + + static_assert (std::is_pointer::value, + "Function must yield a pointer to a heap allocated instance"); + }; + + + template + static void + installFactory (FUN&& ctor) + { + Lock guard; + if (std::is_same()) + { + __ensure_pristine(); + Depend::factory.defineCreatorAndManage (forward (ctor)); + } + else + { + __ensure_pristine(); + Depend::factory.defineCreator ([]{ return & Depend{}(); }); + DependInject::useSingleton (forward (ctor)); + } // delegate actual instance creation to Depend + } + + template + static void + installFactory () + { + if (not std::is_same()) + { + Lock guard; + __ensure_pristine(); + Depend::factory.defineCreator ([]{ return & Depend{}(); }); + } + // note: we do not install an actual factory; rather we use the default for SUB + } + + + template + static void + temporarilyInstallAlternateFactory (SRV*& stashInstance, Factory& stashFac, FUN&& newFac) + { + Lock guard; + stashFac.transferDefinition (move (Depend::factory)); + stashInstance = Depend::instance; + Depend::factory.defineCreator (forward(newFac)); + Depend::instance = nullptr; + } + + static void + restoreOriginalFactory (SRV*& stashInstance, Factory&& stashFac) + { + Lock guard; + Depend::factory.transferDefinition (move (stashFac)); + Depend::instance = stashInstance; + } + + static void + activateServiceAccess (SRV& newInstance) + { + Lock guard; + if (Depend::instance) + throw error::Logic("Attempt to activate an external service implementation, " + "but another instance has already been dependency-injected." + , error::LUMIERA_ERROR_LIFECYCLE); + Depend::instance = &newInstance; + Depend::factory.disable(); + } + + static void + deactivateServiceAccess() + { + Lock guard; + Depend::instance = nullptr; + Depend::factory.disable(); + } + }; + + + +} // namespace lib +#endif /*LIB_DEPEND_INJECT_H*/ diff --git a/src/lib/depend.hpp b/src/lib/depend.hpp index 68f05cad3..9e44a1d8a 100644 --- a/src/lib/depend.hpp +++ b/src/lib/depend.hpp @@ -3,6 +3,7 @@ Copyright (C) Lumiera.org 2013, Hermann Vosseler + 2018, Hermann Vosseler This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as @@ -18,27 +19,17 @@ along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -==================================================================== -This code is heavily inspired by - The Loki Library (loki-lib/trunk/include/loki/Singleton.h) - Copyright (c) 2001 by Andrei Alexandrescu - Loki code accompanies the book: - Alexandrescu, Andrei. "Modern C++ Design: Generic Programming - and Design Patterns Applied". - Copyright (c) 2001. Addison-Wesley. ISBN 0201704315 - */ - /** @file depend.hpp ** Singleton services and Dependency Injection. ** The Singleton Pattern provides a single access point to a class or ** service and exploits this ubiquitous access point to limit the number of objects ** of this type to a single shared instance. Within Lumiera, we mostly employ a ** factory template for this purpose; the intention is to use on-demand initialisation - ** and a standardised lifecycle. In the default configuration, this \c Depend factory - ** maintains a singleton instance of type TY. The possibility to install other factory + ** and a standardised lifecycle. In the default configuration, this `Depend` factory + ** maintains a singleton instance of type `TY`. The possibility to install other factory ** functions allows for subclass creation and various other kinds of service management. ** ** @@ -63,15 +54,34 @@ This code is heavily inspired by ** as a static variable within the interface class describing the service or facade. ** As a rule, everything accessible as Singleton is sufficiently self-contained to come ** up any time -- even prior to `main()`. But at shutdown, any deregistration must be done - ** explicitly using a lifecycle hook. Destructors aren't allowed to do _any significant work_ - ** beyond releasing references, and we acknowledge that singletons can be released - ** in _arbitrary order_. + ** explicitly using a lifecycle hook. In Lumiera, destructors aren't allowed to do + ** _any significant work_ beyond releasing references, and we acknowledge that + ** singletons can be released in _arbitrary order_. ** - ** @see lib::Depend - ** @see lib::DependencyFactory - ** @see lib::test::Depend4Test - ** @see singleton-test.cpp - ** @see dependency-factory-test.cpp + ** Lifecycle and management of dependencies is beyond the scope of this access mechanism + ** exposed here. However, the actual product to be created or exposed lazily can be + ** configured behind the scenes, as long as this configuration is performed _prior_ + ** to the first access. This configuration is achieved with the help of the "sibling" + ** template lib::DependInject, which is declared friend within `Depend` for type `T` + ** - a service with distinct lifecycle can be exposed through the `Depend` front-end + ** - it is possible to create a mock instance, which temporarily shadows what + ** Depend delivers on access. + ** + ** ## Implementation and performance + ** + ** Due to this option for flexible configuration, the implementation can not be built + ** as Meyer's Singleton. Rather, Double Checked Locking of a Mutex is combined with + ** an std::atomic to work around the known (rather theoretical) concurrency problems. + ** Microbenchmarks indicate that this implementation technique ranges close to the + ** speed of a direct access to an already existing object; in the fully optimised + ** variant it was found to be roughly at ≈ 1ns and thus about 3 to 4 times slower + ** than the comparable unprotected direct access without lazy initialisation. + ** This is orders of magnitude better than any flavour of conventional locking. + ** + ** @see depend-inject.hpp + ** @see lib::DependInject + ** @see Singleton_test + ** @see DependencyConfiguration_test */ @@ -79,137 +89,254 @@ This code is heavily inspired by #define LIB_DEPEND_H +#include "lib/error.hpp" +#include "lib/nocopy.hpp" +#include "lib/nobug-init.hpp" #include "lib/sync-classlock.hpp" -#include "lib/dependency-factory.hpp" +#include "lib/zombie-check.hpp" +#include "lib/meta/util.hpp" + +#include +#include +#include +#include namespace lib { + namespace error = lumiera::error; + /** - * Access point to singletons and other kinds of dependencies. - * Actually this is a Factory object, which is typically placed into a - * static field of the Singleton (target) class or some otherwise suitable interface. - * @param SI the class of the Singleton instance - * @note uses static fields internally, so all factory configuration is shared per type - * @remark there is an ongoing discussion regarding the viability of the - * Double Checked Locking pattern, which requires either the context of a clearly defined - * language memory model (as in Java), or needs to be supplemented by memory barriers. - * In our case, this debate boils down to the question: does \c pthread_mutex_lock/unlock - * constitute a memory barrier, such as to force any memory writes happening \em within - * the singleton ctor to be flushed and visible to other threads when releasing the lock? - * To my understanding, the answer is yes. See - * [POSIX](http://www.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap04.html#tag_04_10) - * @remark we could consider to rely on a _Meyers Singleton_, where the compiler automatically - * generates the necessary code and guard variable to ensure single-threaded initialisation - * of the instance variable. But the downside of this approach is that we'd loose access - * to the singleton instance variable, which then resides within the scope of a single - * access function. Such would counterfeit the ability to exchange the instance to - * inject a mock for unit testing. - * @todo as of 2016, the design could still be improved //////////////////////////////////////////TICKET #1086 - * @param SI the class of the Singleton instance + * Helper to abstract creation and lifecycle of a dependency. + * Holds a configurable constructor function and optionally + * an automatically invoked deleter function. + * @note DependencyFactory can be declared friend to indicate + * the expected way to invoke an otherwise private ctor. + * This is a classical idiom for singletons. + * @see lib::Depend + * @see lib::DependInject */ - template + template + class DependencyFactory + : util::NonCopyable + { + using Creator = std::function; + using Deleter = std::function; + + Creator creator_; + Deleter deleter_; + + public: + ZombieCheck zombieCheck; + + DependencyFactory() = default; + ~DependencyFactory() + { + if (deleter_) + deleter_(); + } + + OBJ* + operator() () + { + return creator_? creator_() + : buildAndManage(); + } + + template + void + defineCreator (FUN&& ctor) + { + creator_ = std::forward (ctor); + } + + template + void + defineCreatorAndManage (FUN&& ctor) + { + creator_ = [this,ctor] + { + OBJ* obj = ctor(); + atDestruction ([obj]{ delete obj; }); + return obj; + }; + } + + void + disable() + { + creator_ = []() -> OBJ* + { + throw error::Fatal("Service not available at this point of the Application Lifecycle" + ,error::LERR_(LIFECYCLE)); + }; + } + + template + void + atDestruction (FUN&& additionalAction) + { + if (deleter_) + { + Deleter oldDeleter{std::move (deleter_)}; + deleter_ = [oldDeleter, additionalAction] + { + oldDeleter(); + additionalAction(); + }; + } + else + deleter_ = std::forward (additionalAction); + } + + void + transferDefinition (DependencyFactory&& source) + { + creator_ = std::move (source.creator_); + deleter_ = std::move (source.deleter_); + source.creator_ = Creator(); + source.deleter_ = Deleter(); // clear possible leftover deleter + } + + private: + OBJ* + buildAndManage() + { + OBJ* obj = buildInstance(); + atDestruction ([obj]{ delete obj; }); + return obj; + } + + // try to instantiate the default ctor + template + static std::true_type __try_instantiate(int); + template + static std::false_type __try_instantiate(...); + + /** metafunction: can we instantiate the desired object here? + * @remark need to perform the check right here in this scope, because + * default ctor can be private with DependencyFactory declared friend + */ + template + struct canDefaultConstruct + : decltype(__try_instantiate(0)) + { }; + + + template + static meta::enable_if, + TAR* > + buildInstance() + { + return new TAR; + } + + template + static meta::enable_if, + ABS* > + buildInstance() + { + throw error::Fatal("Attempt to create a singleton instance of an abstract class. " + "Application architecture or lifecycle is seriously broken."); + } + template + static meta::disable_if,canDefaultConstruct>, + ABS* > + buildInstance() + { + throw error::Fatal("Desired singleton class is not default constructible. " + "Application architecture or lifecycle is seriously broken."); + } + }; + + + + /** + * @internal access point to reconfigure dependency injection on a per type base + * @see depend-inject.hpp + */ + template + class DependInject; + + + /** + * Access point to singletons and other kinds of dependencies designated *by type*. + * Actually this is a Factory object, which is typically placed into a static field + * of the Singleton (target) class or some otherwise suitable interface. + * @tparam SRV the class of the Service or Singleton instance + * @note uses static fields internally, so all factory configuration is shared per type + * @remarks + * - threadsafe lazy instantiation implemented by Double Checked Locking with std::atomic. + * - by default, without any explicit configuration, this template creates a singleton. + * - a per-type factory function can be configured with the help of lib::DependInject + * - singletons will be destroyed when the embedded static InstanceHolder is destroyed. + */ + template class Depend { - typedef ClassLock SyncLock; + using Instance = std::atomic; + using Factory = DependencyFactory; + using Lock = ClassLock; - static SI* volatile instance; - static DependencyFactory factory; + /* === shared per type === */ + static Instance instance; + static Factory factory; + + friend class DependInject; public: - /** Interface to be used by clients for retrieving the service instance. - * Manages the instance creation, lifecycle and access in multithreaded context. - * @return instance of class SI. When used in default configuration, - * this service instance is a singleton + /** + * Interface to be used by clients for retrieving the service instance. + * Manages the instance creation, lifecycle and access in multithreaded context. + * @return instance of class `SRV`. When used in default configuration, + * the returned service instance is a singleton. */ - SI& + SRV& operator() () { - if (!instance) + SRV* object = instance.load (std::memory_order_acquire); + if (!object) { - SyncLock guard; + factory.zombieCheck(); + Lock guard; - if (!instance) - instance = static_cast (factory.buildInstance()); + object = instance.load (std::memory_order_relaxed); + if (!object) + { + object = factory(); + factory.disable(); + factory.atDestruction([]{ instance = nullptr; }); + } + instance.store (object, std::memory_order_release); } - ENSURE (instance); - return *instance; + ENSURE (object); + return *object; } - - typedef DependencyFactory::InstanceConstructor Constructor; - - - /** default configuration of the dependency factory - * is to build a singleton instance on demand */ - Depend() - { - factory.ensureInitialisation (buildSingleton()); - } - /** - * optionally, the instance creation process can be configured explicitly - * \em once per type. By default, a singleton instance will be created. - * Installing another factory function enables other kinds of dependency injection; - * this configuration must be done \em prior to any use the dependency factory. - * @param ctor a constructor function, which will be invoked on first usage. - * @note basically a custom constructor function is responsible to manage any - * created service instances. - * @remark typically the \c Depend factory will be placed into a static variable, - * embedded into another type or interface. In this case, actual storage for - * this static variable needs to be allocated within some translation unit. - * And this is the point where this ctor will be invoked, in the static - * initialisation phase of the respective translation unit (*.cpp) + * allow to "peek" if a dependency is already available and exposed. + * @remark relevant when C code relies on a service with lifecycle. */ - Depend (Constructor ctor) + explicit + operator bool() const { - factory.installConstructorFunction (ctor); - } - - // standard copy operations applicable - - - - /* === Management / Test support interface === */ - - /** temporarily replace the service instance. - * The purpose of this operation is to support unit testing. - * @param mock reference to an existing service instance (mock). - * @return reference to the currently active service instance. - * @warning this is a dangerous operation and not threadsafe. - * Concurrent accesses might still get the old reference; - * the only way to prevent this would be to synchronise - * \em any access (which is too expensive). - * This feature should only be used for unit tests thusly. - * @remark the replacement is not actively managed by the DependencyFactory, - * it remains in ownership of the calling client (unit test). Typically - * this test will keep the returned original service reference and - * care for restoring the original state when done. - * @see Depend4Test scoped object for automated test mock injection - */ - static SI* - injectReplacement (SI* mock) - { - SyncLock guard; - SI* currentInstance = instance; - instance = mock; - return currentInstance; + return instance.load (std::memory_order_acquire); } }; - // Storage for static per type instance management... - template - SI* volatile Depend::instance; + /* === allocate Storage for static per type instance management === */ + template + std::atomic Depend::instance; - template - DependencyFactory Depend::factory; + template + DependencyFactory Depend::factory; } // namespace lib -#endif +#endif /*LIB_DEPEND_H*/ diff --git a/src/lib/dependency-factory.hpp b/src/lib/dependency-factory.hpp deleted file mode 100644 index 36297a6f5..000000000 --- a/src/lib/dependency-factory.hpp +++ /dev/null @@ -1,221 +0,0 @@ -/* - DEPENDENCY-FACTORY.hpp - managing the lifecycle of singletons and dependencies - - Copyright (C) Lumiera.org - 2013, Hermann Vosseler - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of - the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - -*/ - - -/** @file dependency-factory.hpp - ** Implementation of a singleton factory used to bring up services as dependency. - ** @internal this implementation header belongs to our framework to deal with - ** [service dependencies](\ref depend.hpp) and should not be used directly. - */ - - - - -#ifndef LIB_DEPENDENCY_FACTORY_H -#define LIB_DEPENDENCY_FACTORY_H - - - -#include "lib/nobug-init.hpp" -#include "lib/error.hpp" - - -namespace lib { - - namespace error = lumiera::error; - - - /** - * @internal Factory to generate and manage service objects classified by type. - * An instance of this factory is placed once for each type for use by - * the lib::Depend front-end for dependency management. While the latter - * provides the singleton-style initialisation patter, the DependencyFacotry - * maintains a customisable factory function for instance creation. Moreover, - * the embedded helper template DependencyFactory::InstanceHolder actually - * creates and manages the singleton instances in default configuration; - * it is placed into a function-scope static variable; consequently - * the singleton instances are placed into static memory by default. - */ - class DependencyFactory - { - public: - typedef void* (*InstanceConstructor)(void); - - /** ensure initialisation by installing a default constructor function, - * but don't change an explicitly installed different constructor function. - * @remark deliberately this DependencyFactory has no constructor to - * initialise the object field \c ctorFunction_ to zero. - * The reason is, in the intended usage scenario, the - * DependencyFactory lives within a static variable, - * which might be constructed in no defined order - * in relation to the Depend instance. - */ - void - ensureInitialisation (InstanceConstructor defaultCtor) - { - if (!ctorFunction_) - this->ctorFunction_ = defaultCtor; - ENSURE (ctorFunction_); - } - - - /** explicitly set up constructor function, unless already configured - * In the default configuration, the template \c Depend installs a - * builder function to create a singleton instance in static memory. - * But specific instances might install e.g. a factory to create a - * implementation defined subclass; this might also be the place - * to hook in some kind of centralised service manager in future. - * @param ctor a function to be invoked to create a new service instance - * @throw error::Fatal when attempting to change an existing configuration. - */ - void - installConstructorFunction (InstanceConstructor ctor) - { - if (ctorFunction_ && ctor != ctorFunction_) - throw error::Fatal ("DependencyFactory: attempt to change the instance builder function " - "after-the-fact. Before this call, a different function was installed " - "and possibly also used already. Hint: visit all code locations, which " - "actually create an instance of the Depend template." - ,error::LUMIERA_ERROR_LIFECYCLE); - this->ctorFunction_ = ctor; - } - - - - /** invoke the installed ctor function */ - void* - buildInstance() - { - if (!ctorFunction_) - throw error::Fatal ("lib::Depend: attempt to retrieve a service object prior to initialisation " - "of the DependencyFactory. Typically, this happens due to a misconfiguration " - "regarding static initialisation order. When lib::Depend is placed into " - "a class static variable, then the definition and initialisation of that " - "variable must happen prior to the call which caused this exception." - ,error::LUMIERA_ERROR_LIFECYCLE); - return ctorFunction_(); - } - - - - - - private: - /** pointer to the concrete function - * used for building new service instances */ - InstanceConstructor ctorFunction_; - - - - /** function to build service instances. - * A service class with private ctor can declare DependencyFactory as friend, - * to indicate this is the expected way to create instances */ - template - static TAR* - create_in_buffer (void* buffer) - { - return new(buffer) TAR; - } - - - /** - * @internal Helper to manage a service instance within an embedded buffer. - * This helper and thus the service instance will be allocated into static memory. - */ - template - class InstanceHolder - : boost::noncopyable - { - /** storage for the service instance */ - char buff_[sizeof(TAR)]; - uint lifecycle_; - - - public: - InstanceHolder() - : lifecycle_(0) - { } - - ~InstanceHolder() - { - lifecycle_ |= 4; - if (1 & lifecycle_) - { - reinterpret_cast (buff_). ~TAR(); - --lifecycle_; - } } - - - TAR* - buildInstance () - { - if (0 < lifecycle_) - throw error::Fatal("Attempt to double-create a singleton service. " - "Either the application logic, or the compiler " - "or runtime system is seriously broken" - ,error::LUMIERA_ERROR_LIFECYCLE); - - // place new instance into embedded buffer - TAR* newInstance = create_in_buffer(buff_); - ++lifecycle_; - return newInstance; - } - }; - - - - template - static void* - createSingletonInstance() - { - static InstanceHolder storage; // note: the singleton(s) live here - return storage.buildInstance(); - } - - - template - friend InstanceConstructor buildSingleton(); - - }; - - - - /** - * DSL-style marker function for client code - * to configure the usage of a specific subclass. - * Typically this function is used right within the - * Constructor call for lib::Depend; this allows to - * confine the actual service implementation class - * to a single compilation unit, without the need - * for clients of the respective service to know - * the actual concrete implementation class - */ - template - inline DependencyFactory::InstanceConstructor - buildSingleton() - { - return & DependencyFactory::createSingletonInstance; - } - - -} // namespace lib -#endif diff --git a/src/lib/diagnostic-context.hpp b/src/lib/diagnostic-context.hpp index 5d3167fee..0a0ba9045 100644 --- a/src/lib/diagnostic-context.hpp +++ b/src/lib/diagnostic-context.hpp @@ -46,9 +46,9 @@ #include "lib/error.hpp" +#include "lib/nocopy.hpp" #include "lib/thread-local.hpp" -#include #include @@ -68,7 +68,7 @@ namespace lib { */ template class DiagnosticContext - : boost::noncopyable + : util::NonCopyable { typedef ThreadLocalPtr ThreadLocalAccess; typedef std::vector ValSequence; diff --git a/src/lib/diff/diff-language.hpp b/src/lib/diff/diff-language.hpp index 6229cb401..f4683c2a5 100644 --- a/src/lib/diff/diff-language.hpp +++ b/src/lib/diff/diff-language.hpp @@ -85,10 +85,10 @@ #include "lib/error.hpp" +#include "lib/nocopy.hpp" #include "lib/verb-token.hpp" #include "lib/util.hpp" -#include #include @@ -285,7 +285,7 @@ namespace diff{ */ template class DiffApplicator - : boost::noncopyable + : util::NonCopyable { using Interpreter = DiffApplicationStrategy; diff --git a/src/lib/diff/gen-node.hpp b/src/lib/diff/gen-node.hpp index e14944585..6b77c0f3f 100644 --- a/src/lib/diff/gen-node.hpp +++ b/src/lib/diff/gen-node.hpp @@ -521,26 +521,26 @@ namespace diff{ /* === Iteration control API for IterStateWrapper == */ - friend bool - checkPoint (Locator const& loc) - { - return loc.get(); - } + bool + checkPoint() const + { + return this->get(); + } - friend GenNode const& - yield (Locator const& loc) - { - return *loc.get(); - } + GenNode const& + yield() const + { + return *get(); + } - friend void - iterNext (Locator & loc) - { - if (loc.node_) - loc.node_ = nullptr; - else - ++loc.scope_; - } + void + iterNext() + { + if (node_) + node_ = nullptr; + else + ++scope_; + } }; @@ -570,28 +570,28 @@ namespace diff{ /* === Iteration control API for IterStateWrapper == */ - friend bool - checkPoint (ScopeExplorer const& explorer) - { - return not explorer.scopes_.empty() - && bool(explorer.scopes_.back()); - } + bool + checkPoint() const + { + return not scopes_.empty() + and bool(scopes_.back()); + } - friend GenNode const& - yield (ScopeExplorer const& explorer) - { - return * (explorer.scopes_.back()); - } + GenNode const& + yield() const + { + return *(scopes_.back()); + } - friend void - iterNext (ScopeExplorer & explorer) - { - ScopeIter& current = explorer.scopes_.back(); - explorer.scopes_.emplace_back (current->data.expand()); - ++current; - while (!explorer.scopes_.empty() && !explorer.scopes_.back()) - explorer.scopes_.pop_back(); - } + void + iterNext() + { + ScopeIter& current = scopes_.back(); + scopes_.emplace_back (current->data.expand()); + ++current; + while (not scopes_.empty() and not scopes_.back()) + scopes_.pop_back(); + } friend bool operator== (ScopeExplorer const& s1, ScopeExplorer const& s2) @@ -599,7 +599,7 @@ namespace diff{ return not s1.scopes_.empty() && not s2.scopes_.empty() && s1.scopes_.size() == s2.scopes_.size() - && yield(s1) == yield(s2); + && s1.yield() == s2.yield(); } }; @@ -844,10 +844,26 @@ namespace diff{ inline GenNode Rec::buildAttribute (string const& key, X&& payload) { - return GenNode(key, payload); + return GenNode(key, std::forward(payload)); } -}} // namespace lib::diff +}// namespace lib::diff + +namespace variant { + using diff::Rec; + + /** + * specialisation allowing to build a GenNode with a + * nested record from a Record::Mutator (aka `MakeRec`) + */ + template + struct CanBuildFrom> + : std::true_type + { + using Type = Rec; + }; + +}} // namespace lib::variant #endif /*LIB_DIFF_GEN_NODE_H*/ diff --git a/src/lib/diff/list-diff-application.hpp b/src/lib/diff/list-diff-application.hpp index 0d4bb1aa6..a3bb04f38 100644 --- a/src/lib/diff/list-diff-application.hpp +++ b/src/lib/diff/list-diff-application.hpp @@ -91,12 +91,12 @@ namespace diff{ if (end_of_target()) throw error::State(_Fmt("Unable to %s element %s from target as demanded; " "no (further) elements in target sequence") % oper % elm - , LUMIERA_ERROR_DIFF_CONFLICT); + , LERR_(DIFF_CONFLICT)); if (*pos_ != elm) throw error::State(_Fmt("Unable to %s element %s from target as demanded; " "found element %s on current target position instead") % oper % elm % *pos_ - , LUMIERA_ERROR_DIFF_CONFLICT); + , LERR_(DIFF_CONFLICT)); } void @@ -105,7 +105,7 @@ namespace diff{ if (end_of_target()) throw error::State(_Fmt("Premature end of target sequence, still expecting element %s; " "unable to apply diff further.") % elm - , LUMIERA_ERROR_DIFF_CONFLICT); + , LERR_(DIFF_CONFLICT)); } void @@ -114,7 +114,7 @@ namespace diff{ if (targetPos == orig_.end()) throw error::State(_Fmt("Premature end of sequence; unable to locate " "element %s in the remainder of the target.") % elm - , LUMIERA_ERROR_DIFF_CONFLICT); + , LERR_(DIFF_CONFLICT)); } diff --git a/src/lib/diff/list-diff-detector.hpp b/src/lib/diff/list-diff-detector.hpp index 845e4857e..5e0bc394b 100644 --- a/src/lib/diff/list-diff-detector.hpp +++ b/src/lib/diff/list-diff-detector.hpp @@ -61,8 +61,8 @@ #include "lib/diff/list-diff.hpp" #include "lib/diff/index-table.hpp" #include "lib/iter-adapter.hpp" +#include "lib/nocopy.hpp" -#include #include @@ -85,7 +85,7 @@ namespace diff{ */ template class DiffDetector - : boost::noncopyable + : util::NonCopyable { using Val = typename SEQ::value_type; using Idx = IndexTable; @@ -187,24 +187,24 @@ namespace diff{ /* === Iteration control API for IterStateWrapper === */ - friend bool - checkPoint (DiffFrame const& frame) - { - return token.NIL != frame.currentStep_; - } + bool + checkPoint() const + { + return token.NIL != currentStep_; + } - friend DiffStep& - yield (DiffFrame const& frame) - { - REQUIRE (checkPoint (frame)); - return unConst(frame).currentStep_; - } + DiffStep& + yield() const + { + REQUIRE (checkPoint()); + return unConst(this)->currentStep_; + } - friend void - iterNext (DiffFrame & frame) - { - frame.currentStep_ = frame.establishNextState(); - } + void + iterNext() + { + currentStep_ = this->establishNextState(); + } private: DiffStep diff --git a/src/lib/diff/record.hpp b/src/lib/diff/record.hpp index 638648c8b..0e2f8ce03 100644 --- a/src/lib/diff/record.hpp +++ b/src/lib/diff/record.hpp @@ -33,30 +33,31 @@ ** ** ## design decisions ** The Record type is shaped from its intended use: It serves to symbolically represent - ** \em objects in the "external tree description". Here, \em objects means objects for - ** real, i.e. with types, fields and an enclosed scope. But \em external means that we - ** do not work on these objects right here, we only represent them, for later referral, - ** \em symbolically. + ** _objects_ in the "external tree description". Here, "objects" means objects _for real_, + ** i.e. with types, fields and an enclosed scope. But \em external means that we do not work + ** on these objects right here, we only represent them, for later referral, _symbolically_. + ** + ** \par rationale + ** The underlying theme and motivation of this design is negative: we do not want + ** to build _yet another object system_. The object model of C++ is deemed adequate. ** ** This leads to the following decisions ** - the Record entity is itself an object and thus has an inner side, privately. ** The entrails of the Record can be reworked and tuned for performance ** - yet the Record has an external appearance, which makes it look flat and passive. ** This is to say, a Record has no visible functionality. - ** - the parts or \em realms within this symbolic representation are distinguished + ** - the parts or _realms_ within this symbolic representation are distinguished ** by convention solely ** ** * metadata is very limited and boils down to a type attribute known by name - ** * children (scope contents) can be recognised by \em not bearing a name + ** * children (scope contents) can be recognised by _not_ bearing a name ** ** Record entities are meant to be immutable. The proper way to alter a Record is - ** to apply a \link tree-diff.hpp diff \endlink. Yet for the \em implementation - ** of this diff handling, a Record::Mutator is provided, to allow controlled - ** partial re-building of a given data element. - ** - ** \par rationale - ** The underlying theme of this design is negative, dialectical: we do not want to - ** build yet another object system. The object model of C++ is deemed adequate. + ** to apply a [diff](\ref tree-diff.hpp). Yet for the _implementation_ of this + ** diff handling, a Record::Mutator is provided, to allow controlled partial + ** re-building of a given data element. Moreover, especially for Record, + ** this mutator can be used for a DSL-style compact notation of a complete + ** "object tree" -- a feature intended for writing unit tests. ** ** @remarks ** - the implementation is focused on the intended primary use case, @@ -86,6 +87,7 @@ #include "lib/error.hpp" +#include "lib/nocopy.hpp" #include "lib/iter-adapter.hpp" #include "lib/iter-adapter-stl.hpp" #include "lib/itertools.hpp" @@ -93,8 +95,6 @@ #include "lib/util.hpp" -#include - #include #include #include @@ -279,6 +279,14 @@ namespace diff{ * copy-initialise (or convert) from the given Mutator instance. * @remarks need to code this explicitly, otherwise Record's * build-from sequence templated ctor would kick in. + * @warning beware of initialiser lists. Record has a dedicated + * ctor to accept an initialiser list of GenNode elements, + * and GenNode's ctor is deliberately _not explicit,_ to ease + * building argument lists wrapped in GenNodes. When writing + * initialisation expressions inline, the compiler might pick + * the conversion path through initialiser list, which means + * the contents of the Mutator end up wrapped into a GenNode, + * which in turn becomes the only child of the new Record. */ Record (Mutator const& mut) : Record((Record const&) mut) @@ -312,10 +320,9 @@ namespace diff{ /** Implementation of Iteration-logic: pull next element. */ friend void - iterNext (const Record* src, ElmIter& pos) + iterNext (const Record*, ElmIter& pos) { ++pos; - checkPoint (src,pos); } /** Implementation of Iteration-logic: detect iteration end. @@ -391,7 +398,7 @@ namespace diff{ template class Record::Mutator - : boost::noncopyable + : util::NonCopyable { using Rec = Record; diff --git a/src/lib/diff/test-mutation-target.hpp b/src/lib/diff/test-mutation-target.hpp index aacfbc61d..0b97a3896 100644 --- a/src/lib/diff/test-mutation-target.hpp +++ b/src/lib/diff/test-mutation-target.hpp @@ -53,9 +53,9 @@ #include "lib/format-string.hpp" #include "lib/format-util.hpp" #include "lib/test/event-log.hpp" +#include "lib/nocopy.hpp" #include "lib/util.hpp" -#include #include #include #include @@ -193,7 +193,7 @@ namespace diff{ * @see TreeMutatorBinding_test::mutateDummy() */ class TestMutationTarget - : boost::noncopyable + : util::NonCopyable { using VecG = std::vector; diff --git a/src/lib/diff/tree-diff-application.hpp b/src/lib/diff/tree-diff-application.hpp index 642cd9d09..e5e275495 100644 --- a/src/lib/diff/tree-diff-application.hpp +++ b/src/lib/diff/tree-diff-application.hpp @@ -153,7 +153,7 @@ namespace diff{ * TreeMutators dedicated to nested scopes */ class ScopeManager - : boost::noncopyable + : util::NonCopyable { public: virtual ~ScopeManager(); ///< this is an interface diff --git a/src/lib/diff/tree-mutator-collection-binding.hpp b/src/lib/diff/tree-mutator-collection-binding.hpp index 9b4a5bcc5..268effc16 100644 --- a/src/lib/diff/tree-mutator-collection-binding.hpp +++ b/src/lib/diff/tree-mutator-collection-binding.hpp @@ -52,6 +52,7 @@ #include "lib/error.hpp" +#include "lib/nocopy.hpp" #include "lib/meta/trait.hpp" #include "lib/diff/gen-node.hpp" #include "lib/diff/tree-mutator.hpp" @@ -96,6 +97,7 @@ namespace diff{ */ template struct CollectionBinding + : util::MoveOnly { using Coll = typename Strip::TypeReferred; using Elm = typename Coll::value_type; @@ -128,10 +130,8 @@ namespace diff{ , openSub(u) { } - // allow move construction only, + // only move construction allowed, // to enable use of unique_ptr in collections - CollectionBinding(CollectionBinding&&) = default; - CollectionBinding(CollectionBinding&) = delete; @@ -172,7 +172,7 @@ namespace diff{ } - private: /* === technicallities of container access === */ + private: /* === Technicalities of container access === */ /** @internal technicality * Our iterator is actually a Lumiera RangeIter, and thus we need @@ -362,7 +362,7 @@ namespace diff{ { binding_.inject (move(*found)); } - return found; + return bool(found); } else return PAR::findSrc (refSpec); diff --git a/src/lib/diff/tree-mutator.hpp b/src/lib/diff/tree-mutator.hpp index f03ade540..4a5c0df61 100644 --- a/src/lib/diff/tree-mutator.hpp +++ b/src/lib/diff/tree-mutator.hpp @@ -94,6 +94,7 @@ #include "lib/error.hpp" +#include "lib/nocopy.hpp" #include "lib/symbol.hpp" #include "lib/meta/trait.hpp" #include "lib/diff/gen-node.hpp" @@ -139,18 +140,15 @@ namespace diff{ * activities to concrete manipulations known within target scope. */ class TreeMutator + : util::MoveOnly { public: virtual ~TreeMutator(); ///< this is an interface - /** only allow default and move construction */ + // only default and move construction allowed TreeMutator () =default; TreeMutator (TreeMutator&&) =default; - TreeMutator (TreeMutator const&) =delete; - - TreeMutator& operator= (TreeMutator const&) =delete; - TreeMutator& operator= (TreeMutator&&) =delete; diff --git a/src/lib/error-exception.cpp b/src/lib/error-exception.cpp index 810ba9e6f..a1e4c64ce 100644 --- a/src/lib/error-exception.cpp +++ b/src/lib/error-exception.cpp @@ -40,40 +40,30 @@ #include #include +using util::cStr; using util::isnil; using std::exception; namespace lumiera { - - typedef const char* CStr; - typedef const char* const CCStr; - - namespace error { /** the message shown to the user per default * if an exception reaches one of the top-level * catch clauses. * @todo to be localised + * @todo develop a framework to set more specific yet friendly messages */ inline const string - default_usermsg (Error* exception_obj) noexcept + default_usermsg (Error*) noexcept { - return string("Sorry, Lumiera encountered an internal error. (") - + util::typeStr(*exception_obj) + ")"; - } - - inline CStr - default_or_given (CCStr id) - { - return id? id : LUMIERA_ERROR_STATE; + return "Sorry, Lumiera encountered an internal error."; } CStr detailInfo () { - CCStr detailinfo = lumiera_error_extra(); + CStr detailinfo = lumiera_error_extra(); return isnil (detailinfo)? "Lumiera errorstate detected" : detailinfo; } @@ -90,16 +80,14 @@ namespace lumiera { LUMIERA_ERROR_DEFINE (ASSERTION, "assertion failure"); /* some further generic error situations */ - LUMIERA_ERROR_DEFINE (LIFECYCLE, "Lifecycle assumptions violated"); - LUMIERA_ERROR_DEFINE (WRONG_TYPE, "runtime type mismatch"); + LUMIERA_ERROR_DEFINE (LIFECYCLE, "Lifecycle assumptions violated"); + LUMIERA_ERROR_DEFINE (WRONG_TYPE, "runtime type mismatch"); LUMIERA_ERROR_DEFINE (ITER_EXHAUST, "end of sequence reached"); LUMIERA_ERROR_DEFINE (CAPACITY, "predefined fixed storage capacity"); LUMIERA_ERROR_DEFINE (INDEX_BOUNDS, "index out of bounds"); LUMIERA_ERROR_DEFINE (BOTTOM_VALUE, "invalid or NIL value"); - LUMIERA_ERROR_DEFINE (UNCONNECTED, "missing connection"); - LUMIERA_ERROR_DEFINE (UNIMPLEMENTED, "using a feature not yet implemented...."); - - + LUMIERA_ERROR_DEFINE (UNCONNECTED, "missing connection"); + LUMIERA_ERROR_DEFINE (UNIMPLEMENTED,"using a feature not yet implemented...."); } // namespace error @@ -109,38 +97,29 @@ namespace lumiera { /** @note we set the C-style errorstate as a side effect */ - Error::Error (string description, CCStr id) noexcept - : std::exception (), - id_ (error::default_or_given (id)), - msg_ (error::default_usermsg (this)), - desc_ (description), - cause_ ("") - { - lumiera_error_set (this->id_, description.c_str ()); - } + Error::Error (string description, lumiera_err const id) noexcept + : std::exception{} + , id_{id} + , msg_{error::default_usermsg (this)} + , desc_{description} + , cause_{} + { + lumiera_error_set (this->id_, description.c_str()); + } Error::Error (std::exception const& cause, - string description, CCStr id) noexcept - : std::exception (), - id_ (error::default_or_given (id)), - msg_ (error::default_usermsg (this)), - desc_ (description), - cause_ (extractCauseMsg(cause)) - { - lumiera_error_set (this->id_, description.c_str ()); - } + string description, lumiera_err const id) noexcept + : std::exception{} + , id_{id} + , msg_{error::default_usermsg (this)} + , desc_{description} + , cause_{extractCauseMsg(cause)} + { + lumiera_error_set (this->id_, description.c_str()); + } - /** @note copy ctor behaves like chaining, i.e setting the cause_. */ - Error::Error (const Error& ref) noexcept - : std::exception (), - id_ (ref.id_), - msg_ (ref.msg_), - desc_ (ref.desc_), - cause_ (extractCauseMsg(ref)) - { } - /** Description of the problem, including the internal char constant @@ -149,7 +128,7 @@ namespace lumiera { * generated output as well. */ CStr - Error::what() const noexcept + Error::what() const noexcept { if (isnil (this->what_)) { @@ -167,7 +146,7 @@ namespace lumiera { const string Error::extractCauseMsg (const exception& cause) noexcept { - const Error* err=dynamic_cast (&cause); + const Error* err = dynamic_cast (&cause); if (err) { if (isnil (err->cause_)) @@ -175,7 +154,6 @@ namespace lumiera { else return err->cause_; // cause was caused by another exception } - // unknown other exception type return cause.what (); } @@ -186,23 +164,59 @@ namespace lumiera { - namespace error - { + namespace error { + namespace { + void install_unexpectedException_handler () + { + std::set_terminate (lumiera_unexpectedException); + } + LifecycleHook schedule_ (ON_BASIC_INIT, &install_unexpectedException_handler); + + std::terminate_handler nextHandler = nullptr; + } + void lumiera_unexpectedException () noexcept { - CCStr is_halted + CStr is_halted = "### Lumiera halted due to an unexpected Error ###"; - std::cerr << "\n" << is_halted << "\n\n"; ERROR (NOBUG_ON, "%s", is_halted); + std::cerr << "\n" << is_halted << "\n\n"; - if (CCStr errorstate = lumiera_error ()) + + try { // -----find-out-about-any-Exceptions-------- + auto lastException = std::current_exception(); + if (lastException) { + std::rethrow_exception (lastException); + } + } catch(const lumiera::Error& lerr) { + std::cout << "\n+++ Caught Exception " << lerr.getID() << "\n\n"; + ERROR (NOBUG_ON, "+++ caught %s\n+++ messg: %s\n+++ descr: %s" + , cStr(util::typeStr(lerr)) + , cStr(lerr.getUsermsg()) + , cStr(lerr.what()) + ); + if (not isnil(lerr.rootCause())) + ERROR (NOBUG_ON, "+++ cause: %s",cStr(lerr.rootCause())); + + } catch(const std::exception& e) { + ERROR (NOBUG_ON, "Generic Exception: %s", e.what()); + std::cout << "+++ Caught Exception \"" << e.what() << "\"\n"; + } catch(...) { + ERROR (NOBUG_ON, "FATAL -- unknown exception"); + } + + if (CStr errorstate = lumiera_error ()) ERROR (NOBUG_ON, "last registered error was....\n%s", errorstate); - std::terminate(); + if (nextHandler) + nextHandler(); + else + std::abort(); } + void assertion_terminate (const string& location) { throw Fatal (location, LUMIERA_ERROR_ASSERTION) @@ -210,17 +224,6 @@ namespace lumiera { "an internal consistency check."); } - - void install_unexpectedException_handler () - { - std::set_unexpected (lumiera_unexpectedException); - } - - namespace { - LifecycleHook schedule_ (ON_BASIC_INIT, &install_unexpectedException_handler); - } - - } // namespace error } // namespace lumiera diff --git a/src/lib/error.h b/src/lib/error.h index f4fbc0857..6d1bf2485 100644 --- a/src/lib/error.h +++ b/src/lib/error.h @@ -69,16 +69,16 @@ typedef const char* lumiera_err; * @param err name of the error without the 'LUMIERA_ERROR_' prefix (example: NO_MEMORY) */ #define LUMIERA_ERROR_DECLARE(err) \ -extern lumiera_err LUMIERA_ERROR_##err +extern lumiera_err const LUMIERA_ERROR_##err /** - * Definition and initialization of an error constant. + * Definition and initialisation of an error constant. * This macro eases the error definition in implementation files * @param err name of the error without the 'LUMIERA_ERROR_' prefix (example: NO_MEMORY) - * @param msg message describing the error in plain english (example: "memory allocation failed") + * @param msg message describing the error in plain English (example: "memory allocation failed") */ #define LUMIERA_ERROR_DEFINE(err, msg) \ -lumiera_err LUMIERA_ERROR_##err = "LUMIERA_ERROR_" #err ":" msg +lumiera_err const LUMIERA_ERROR_##err = "LUMIERA_ERROR_" #err ":" msg /** * Helper macro to raise an error for the current thread. diff --git a/src/lib/error.hpp b/src/lib/error.hpp index b0ac5ef0f..e949f64aa 100644 --- a/src/lib/error.hpp +++ b/src/lib/error.hpp @@ -28,7 +28,7 @@ ** Within Lumiera, C-style error states and C++-style exceptions ** are tightly integrated. Creating an exception sets the error flag, ** and there are helpers available to throw an exception automatically - ** when a non-cleare error state is detected. + ** when a unclear error state is detected. ** ** @see error-state.c ** @see error.hpp @@ -39,70 +39,111 @@ #ifndef LUMIERA_ERROR_HPP_ #define LUMIERA_ERROR_HPP_ +#define _STDBOOL_H // prevent from including stdbool.h + #include "include/logging.h" #include "include/lifecycle.h" #include "lib/error.h" + #include #include + namespace lumiera { using std::string; + using CStr = const char*; +#define LERR_(_NAME_) LUMIERA_ERROR_##_NAME_ /** error-ID for unspecified exceptions */ - LUMIERA_ERROR_DECLARE(EXCEPTION); + LUMIERA_ERROR_DECLARE(EXCEPTION); /** - * Interface and Base class of all Exceptions thrown - * from within Lumiera (C++) code. Common operations - * for getting an diagnostic message and for obtaining - * the root cause, i.e. the first exception encountered - * in a chain of exceptions. + * Interface and Base definition for all Lumiera Exceptions. + * Provides common operations for getting an diagnostic message + * and to obtaining the _root cause_ message, i.e. the message + * from the first exception encountered in a chain of exceptions. */ - class Error : public std::exception + class Error + : public std::exception { + lumiera_err const id_; ///< an LUMIERA_ERROR id, which is set as errorstate on construction + string msg_; ///< friendly message intended for users (to be localised) + string desc_; ///< detailed description of the error situation for the developers + mutable string what_; ///< buffer for generating the detailed description on demand + const string cause_; ///< description of first exception encountered in the chain + + public: - Error (string description="", const char* const id=LUMIERA_ERROR_EXCEPTION) noexcept; - Error (std::exception const& cause, - string description="", const char* const id=LUMIERA_ERROR_EXCEPTION) noexcept; + virtual ~Error () noexcept { }; ///< this is an interface - Error (const Error&) noexcept; - virtual ~Error () noexcept {}; - /** yield a diagnostic message characterising the problem */ - virtual const char* what () const noexcept; + Error (string description="" + ,lumiera_err const id =LERR_(EXCEPTION)) noexcept; + Error (std::exception const& cause + ,string description="" + ,lumiera_err const id =LERR_(EXCEPTION)) noexcept; - /** the internal Lumiera-error-ID (was set as C-errorstate in ctor) */ - const char* getID () const noexcept { return this->id_; } + Error (Error &&) = default; + Error (Error const&) = default; + Error& operator= (Error &&) = delete; + Error& operator= (Error const&) = delete; + + /** std::exception interface : yield a diagnostic message */ + virtual CStr + what () const noexcept override; + + /** the internal Lumiera-error-ID + * (was set as C-errorstate in ctor) */ + lumiera_err + getID () const noexcept + { + return id_; + } /** extract the message to be displayed for the user */ - const string& getUsermsg () const noexcept; + string const& + getUsermsg () const noexcept + { + return msg_; + } /** If this exception was caused by a chain of further exceptions, * return the description of the first one registered in this throw sequence. - * This works only if every exceptions thrown as a consequence of another exception + * This works only if every exceptions thrown as a consequence of another exception * is properly constructed by passing the original exception to the constructor * @return the description string, maybe empty (if there is no known root cause) */ - const string& rootCause () const noexcept { return this->cause_; } + string const& + rootCause () const noexcept + { + return cause_; + } - /** replace the previous or default friendly message for the user. To be localised. */ - Error& setUsermsg (const string& newMsg) noexcept { this->msg_ = newMsg; return *this; } + /** replace the previous or default friendly message for the user. + * @note to be localised / translated. + */ + Error& + setUsermsg (string const& newMsg) noexcept + { + msg_ = newMsg; + return *this; + } /** give additional developer info. Typically used at intermediate handlers to add context. */ - Error& prependInfo (const string& text) noexcept { this->desc_.insert (0,text); return *this; } + Error& + prependInfo (string const& text) noexcept + { + desc_.insert (0, text); + return *this; + } private: - const char* id_; ///< an LUMIERA_ERROR id, which is set as errorstate on construction - string msg_; ///< friendly message intended for users (to be localised) - string desc_; ///< detailed description of the error situation for the developers - mutable string what_; ///< buffer for generating the detailed description on demand - const string cause_; ///< description of first exception encountered in the chain - - static const string extractCauseMsg (std::exception const&) noexcept; + static const string + extractCauseMsg (std::exception const&) noexcept; }; @@ -115,7 +156,7 @@ namespace lumiera { namespace error { /** global function for handling unknown exceptions - * encountered at functions declaring not to throw + * encountered at functions declaring not to throw * this kind of exception. Basically, any such event * can be considered a severe design flaw; we can just * add some diagnostics prior to halting. @@ -148,43 +189,41 @@ namespace lumiera { -/** Macro for creating derived exception classes properly - * integrated into Lumiera's exception hierarchy. Using - * this macro assures that the new class will get the full - * set of constructors and behaviour common to all exception - * classes, so it should be used when creating an derived - * exception type for more then strictly local purposes - */ -#define LUMIERA_EXCEPTION_DECLARE(CLASS, PARENT, _ID_) \ - class CLASS : public PARENT \ - { \ - public: \ - CLASS (std::string description="", \ - const char* id=_ID_) noexcept \ - : PARENT (description, id) {} \ - \ - CLASS (std::exception const& cause, \ - std::string description="", \ - const char* id=_ID_) noexcept \ - : PARENT (cause, description, id) {} \ + /** + * Derived specific exceptions within Lumiera's exception hierarchy. + */ + template + class LumieraError + : public PAR + { + public: + LumieraError (std::string description="" + ,lumiera_err const id=eID) noexcept + : PAR{description, id? id:eID} + { } + LumieraError (std::exception const& cause + ,std::string description="" + ,lumiera_err const id=eID) noexcept + : PAR{cause, description, id? id:eID} + { } }; - //-------------------------CLASS-----PARENT--ID---------------------- - LUMIERA_EXCEPTION_DECLARE (Logic, Error, LUMIERA_ERROR_LOGIC); - LUMIERA_EXCEPTION_DECLARE (Fatal, Logic, LUMIERA_ERROR_FATAL); - LUMIERA_EXCEPTION_DECLARE (Config, Error, LUMIERA_ERROR_CONFIG); - LUMIERA_EXCEPTION_DECLARE (State, Error, LUMIERA_ERROR_STATE); - LUMIERA_EXCEPTION_DECLARE (Flag, State, LUMIERA_ERROR_FLAG); - LUMIERA_EXCEPTION_DECLARE (Invalid, Error, LUMIERA_ERROR_INVALID); - LUMIERA_EXCEPTION_DECLARE (External, Error, LUMIERA_ERROR_EXTERNAL); + //----CLASS-------------------ID--------------PARENT------ + using Logic = LumieraError; + using Fatal = LumieraError; + using State = LumieraError; + using Flag = LumieraError; + using Invalid = LumieraError; + using Config = LumieraError; + using External = LumieraError; /** install our own handler for undeclared exceptions. Will be * called automatically ON_BASIC_INIT when linking exception.cpp */ - void install_unexpectedException_handler (); + void install_unexpectedException_handler(); /** @return error detail-info if currently set, a default message else */ - const char* detailInfo (); + CStr detailInfo(); } // namespace error @@ -213,22 +252,14 @@ namespace lumiera { */ template inline void - maybeThrow(string description) + maybeThrow (string description ="") { if (lumiera_err errorFlag =lumiera_error()) { - throw EX (error::Flag( error::detailInfo() - , errorFlag) + throw EX (error::Flag{error::detailInfo(), errorFlag} ,description); } } - template - inline void - maybeThrow() - { - maybeThrow(""); - } - } // namespace lumiera @@ -256,7 +287,7 @@ namespace lumiera { /**************************************************//** - * if NoBug is used, redefine some macros + * if NoBug is used, redefine some macros * to rather throw Lumiera Errors instead of aborting */ #if 0 ///////////////////////////////////TODO disabled for now. NoBug aborts are hard and may hold some locks. There are hooks to allow throwing from NoBug TODO use them.... diff --git a/src/lib/format-obj.cpp b/src/lib/format-obj.cpp index e3cd29fcb..f2cdc0216 100644 --- a/src/lib/format-obj.cpp +++ b/src/lib/format-obj.cpp @@ -95,7 +95,8 @@ namespace meta { #ifdef __GNUG__ /** - * \par Implementation notes + * # Implementation notes + * * GCC / G++ subscribes to a cross-vendor ABI for C++, sometimes called the IA64 ABI * because it happens to be the native ABI for that platform. It is summarised at * \link http://www.codesourcery.com/cxx-abi/ mentor-embedded \endlink @@ -305,7 +306,7 @@ apologies for that." static regex identifierChars {"[A-Za-z]\\w*", regex::ECMAScript | regex::optimize}; return regex_replace (text, identifierChars, "$&", std::regex_constants::format_no_copy); - } + } // don't copy what does not match diff --git a/src/lib/format-string.hpp b/src/lib/format-string.hpp index a781f9ee3..079ba8067 100644 --- a/src/lib/format-string.hpp +++ b/src/lib/format-string.hpp @@ -106,10 +106,10 @@ #define UTIL_FORMAT_STRING_H #include "lib/error.hpp" +#include "lib/nocopy.hpp" #include "lib/meta/util.hpp" #include "lib/meta/size-trait.hpp" -#include #include @@ -152,7 +152,7 @@ namespace util { * @see FormatString_test */ class _Fmt - : boost::noncopyable + : util::NonCopyable { /** size of an opaque implementation Buffer */ enum{ FORMATTER_SIZE = lib::meta::SizeTrait::BOOST_FORMAT }; diff --git a/src/lib/format-util.hpp b/src/lib/format-util.hpp index 8760a8b63..eebf22638 100644 --- a/src/lib/format-util.hpp +++ b/src/lib/format-util.hpp @@ -111,6 +111,7 @@ namespace util { /** convert a sequence of elements to string * @param elms sequence of arbitrary elements + * @tparam CON the container type to collect the results * @return a collection of type CON, initialised by the * string representation of the given elements */ @@ -193,12 +194,12 @@ namespace util { using Coll = typename lib::meta::Strip::TypePlain; _RangeIter range(std::forward(coll)); - auto strings = stringify (range.iter); + auto strings = stringify (std::move (range.iter)); if (!strings) return ""; std::ostringstream buffer; - for (string const& elm : strings) - buffer << elm << delim; + for ( ; strings; ++strings) + buffer << *strings << delim; // chop off last delimiter size_t len = buffer.str().length(); diff --git a/src/lib/functor-util.hpp b/src/lib/functor-util.hpp index c2561e7a7..4512d54d5 100644 --- a/src/lib/functor-util.hpp +++ b/src/lib/functor-util.hpp @@ -25,6 +25,7 @@ ** Collection of small helpers and utilities related to function objects. ** ** @todo combine with meta/function-closure.hpp and reorganise + ** @todo 2017 find out to what extent we still need that (looks obsolete) ** ** @see GuiSubsysDescriptor#start (guifacade.cpp) ** @see MementoTie @@ -72,7 +73,8 @@ namespace util { ////////////TODO: refactor namespace. But probably not directly * we use a conservative approach, by requiring * the concrete invoker, the storage manager and * the actual function and argument data pointers - * to be the same. + * to be the same. + * @todo 2017 is this still necessary today? */ class HijackedFunction : std::_Function_base diff --git a/src/lib/hash-indexed.hpp b/src/lib/hash-indexed.hpp index dfac2e537..e0b94b101 100644 --- a/src/lib/hash-indexed.hpp +++ b/src/lib/hash-indexed.hpp @@ -41,7 +41,7 @@ ** and the IDs denoting specific subclasses, such that the latter can stand-in ** for the generic ID. ** - providing a Mixin, which allows any hierarchy to use this facility without - ** much code duplication, including an adapter for tr1::unordered_map + ** much code duplication, including an adapter for std::unordered_map ** - equality comparison ** ** @see HashIndexed_test @@ -68,7 +68,7 @@ extern "C" { namespace lib { /** Hash implementations usable for the HashIndexed mixin - * as well as key within tr1::unordered_map */ + * as well as key within std::unordered_map */ namespace hash { /** @@ -156,7 +156,7 @@ namespace lib { }; - /** enables use of BA objects as keys within tr1::unordered_map */ + /** enables use of BA objects as keys within std::unordered_map */ struct UseEmbeddedHash : public std::unary_function { diff --git a/src/lib/idi/entry-id.hpp b/src/lib/idi/entry-id.hpp index 54b5debf4..8a860a9c5 100644 --- a/src/lib/idi/entry-id.hpp +++ b/src/lib/idi/entry-id.hpp @@ -176,7 +176,7 @@ namespace idi { operator string() const; - /** using BareEntryID derived objects as keys within tr1::unordered_map */ + /** using BareEntryID derived objects as keys within std::unordered_map */ struct UseEmbeddedHash : public std::unary_function { diff --git a/src/lib/iter-adapter-stl.hpp b/src/lib/iter-adapter-stl.hpp index ed04ca37c..e412973e8 100644 --- a/src/lib/iter-adapter-stl.hpp +++ b/src/lib/iter-adapter-stl.hpp @@ -44,6 +44,7 @@ #include "lib/iter-adapter.hpp" #include "lib/iter-adapter-ptr-deref.hpp" +#include namespace lib { diff --git a/src/lib/iter-adapter.hpp b/src/lib/iter-adapter.hpp index 0575f8fb7..8834b166c 100644 --- a/src/lib/iter-adapter.hpp +++ b/src/lib/iter-adapter.hpp @@ -1,5 +1,5 @@ /* - ITER-ADAPTER.hpp - helpers for building simple forward iterators + ITER-ADAPTER.hpp - helpers for building simple forward iterators Copyright (C) Lumiera.org 2009, Hermann Vosseler @@ -21,11 +21,11 @@ */ /** @file iter-adapter.hpp - ** Helper template(s) for creating lumiera forward iterators. + ** Helper template(s) for creating *Lumiera Forward Iterators*. ** These are the foundation to build up iterator like types from scratch. ** Usually, these templates will be created and provided by a custom ** container type and accessed by the client through a typedef name - ** "iterator" (similar to the usage within the STL). For more advanced + ** "`iterator`" (similar to the usage within the STL). For more advanced ** usage, the providing container might want to subclass these iterators, ** e.g. to provide an additional, specialised API. ** @@ -48,20 +48,20 @@ ** contents of a stable and unmodifiable data structure through pointers. ** The AddressExposingIter wraps another Lumiera Forward Iterator and ** exposes addresses -- assuming the used source iterator is exposing - ** references to pre-existing storage locations (not temporaries). + ** references to pre-existing storage locations (not temporaries). ** - ** There are many further ways of yielding a Lumiera forward iterator. - ** For example, lib::IterSource builds a "iterable" source of data elements, + ** There are many further ways of building a Lumiera Forward Iterator. + ** For example, lib::IterSource exposes a "iterable" source of data elements, ** while hiding the actual container or generator implementation behind a - ** vtable call. Besides, there are adapters for the most common usages + ** VTable call. Besides, there are adapters for the most common usages ** with STL containers, and such iterators can also be combined and - ** extended with the help of itertools.hpp + ** extended with the help of \ref itertools.hpp ** ** Basically every class in compliance with our specific iterator concept ** can be used as a building block within this framework. ** ** - ** # Lumiera forward iterator concept + ** # Lumiera Forward Iterator concept ** ** Similar to the STL, instead of using a common "Iterator" base class, ** we rather define a common set of functions and behaviour which can @@ -73,7 +73,7 @@ ** support the various extended iterator concepts from STL and boost ** (random access iterators, output iterators, arithmetics, difference ** between iterators and the like). According to this concept, - ** an iterator is a promise for pulling values, + ** _an iterator is a promise for pulling values,_ ** and nothing beyond that. ** ** - Any Lumiera forward iterator can be in a "exhausted" (invalid) state, @@ -82,14 +82,14 @@ ** state is final and can't be reset, meaning that any iterator is ** a disposable one-way-off object. ** - iterators are copyable and equality comparable - ** - when an iterator is \em not in the exhausted state, it may be - ** \em dereferenced to yield the "current" value. + ** - when an iterator is _not_ in the exhausted state, it may be + ** _dereferenced_ to yield the "current" value. ** - moreover, iterators may be incremented until exhaustion. ** ** @see iter-adapter-test.cpp ** @see itertools.hpp ** @see IterSource (completely opaque iterator) - ** @see iter-type-binding.hpp + ** @see value-type-binding.hpp ** */ @@ -99,7 +99,9 @@ #include "lib/error.hpp" -#include "lib/iter-type-binding.hpp" +#include "lib/meta/value-type-binding.hpp" + +#include namespace lib { @@ -118,12 +120,14 @@ namespace lib { #define ENABLE_USE_IN_STD_RANGE_FOR_LOOPS(ITER) \ friend ITER begin (ITER const& it){ return it; } \ friend ITER&& begin (ITER&& it) { return static_cast (it); } \ - friend ITER end (ITER const&) { return ITER(); } + friend ITER end (ITER const&) { return ITER(); } \ + using iterator_category = std::input_iterator_tag; \ + using difference_type = size_t; /** - * Adapter for building an implementation of the lumiera forward iterator concept. + * Adapter for building an implementation of the »Lumiera Forward Iterator« concept. * The "current position" is represented as an opaque element (usually a nested iterator), * with callbacks into the controlling container instance to manage this position. * This allows to influence and customise the iteration process to a large extent. @@ -132,27 +136,37 @@ namespace lib { * - it checks validity on every operation and may throw * - it has a distinct back-link to the source container * - the source container needs to provide hasNext() and iterNext() free functions. - * - we may need friendship to implement those extension points on the container + * - we may need friendship to implement those extension points on the container * - the end-of-iteration can be detected by bool check * @note it is possible to "hide" a smart-ptr within the CON template parameter. * + * @tparam POS pointer or similar mutable link to the _current value_. + * Will be `bool()` checked to detect iteration end + * @tparam CON type of the backing container, which needs to implement two + * extension point functions for iteration control + * * \par Stipulations * - POS refers to the current position within the data source of this iterator. * -# it should be default constructible * -# it should be copy constructible * -# when IterAdapter is supposed to be assignable, then POS should be * -# it should provide embedded typedefs for pointer, reference and value_type, - * or alternatively resolve these types through specialisation of iter::TypeBinding. + * or alternatively resolve these types through specialisation of meta::TypeBinding. * -# it should be convertible to the pointer type it declares * -# dereferencing should yield a type that is convertible to the reference type * - CON points to the data source of this iterator (typically a data container type) * We store a pointer-like backlink to invoke a special iteration control API: * -# \c checkPoint yields true iff the source has yet more result values to yield - * -# \c iterNext advances the POS to the next element + * -# \c iterNext advances the POS to the next element + * + * @note + * - when POS is just a pointer, we use the pointee as value type + * - but when POS is a class, we expect the usual STL style nested typedefs + * `value_type`, `reference` and `pointer` * * @see scoped-ptrvect.hpp usage example - * @see iter-type-binding.hpp - * @see iter-adaptor-test.cpp + * @see value-type-binding.hpp + * @see iter-adapter-test.cpp */ template class IterAdapter @@ -160,15 +174,18 @@ namespace lib { CON source_; mutable POS pos_; + using _ValTrait = meta::TypeBinding>; + public: - typedef typename iter::TypeBinding::pointer pointer; - typedef typename iter::TypeBinding::reference reference; - typedef typename iter::TypeBinding::value_type value_type; + using value_type = typename _ValTrait::value_type; + using reference = typename _ValTrait::reference; + using pointer = typename _ValTrait::pointer; + IterAdapter (CON src, POS const& startpos) : source_(src) , pos_(startpos) - { + { check(); } @@ -223,7 +240,7 @@ namespace lib { * @note this function is called before any operation, * thus the container may adjust the position value, * for example setting it to a "stop iteration" mark. - */ + */ bool check() const { @@ -238,8 +255,7 @@ namespace lib { void iterate() { - if (check()) - iterNext (source_,pos_); // extension point: free function iterNext(...) + iterNext (source_,pos_); // extension point: free function iterNext(...) check(); } // checkPoint() might mark end condition // for comparison with IterAdapter{} @@ -247,12 +263,19 @@ namespace lib { protected: - using ConRef = typename iter::TypeBinding::reference; + using ConRef = typename meta::TypeBinding::reference; /** allow derived classes to access backing container */ ConRef source() { return source_; } const ConRef source() const { return unConst(this)->source_; } + void + resetPos (POS otherPos) ////////////////////////////////////////////TICKET #1125 : get rid of this function! it should not be there; rectify IterSource! + { + pos_ = otherPos; + check(); + } + private: void _maybe_throw() const @@ -262,6 +285,7 @@ namespace lib { } + public: ENABLE_USE_IN_STD_RANGE_FOR_LOOPS (IterAdapter); /// comparison is allowed to access impl iterator @@ -281,7 +305,7 @@ namespace lib { /** - * Another Lumiera Forward Iterator building block, based on incorporating a state type + * Another Lumiera Forward Iterator building block, based on incorporating a state type * right into the iterator. Contrast this to IterAdapter, which refers to a managing * container behind the scenes. Here, all of the state is assumed to live in the * custom type embedded into this iterator, accessed and manipulated through @@ -290,18 +314,18 @@ namespace lib { * \par Assumptions when building iterators based on IterStateWrapper * There is a custom state representation type ST. * - default constructible - * - this default state represents the \em bottom (invalid) state. + * - this default state represents the _bottom_ (invalid) state. * - copyable, because iterators are passed by value - * - this type needs to provide an iteration control API through free functions + * - this type needs to provide an *iteration control API* with the following operations * -# \c checkPoint establishes if the given state element represents a valid state * -# \c iterNext evolves this state by one step (sideeffect) - * -# \c yield realises the given state, yielding an element of result type T - * @param T nominal result type (maybe const, but without reference). - * The resulting iterator will yield a reference to this type T - * @param ST type of the "state core", defaults to T. - * The resulting iterator will hold an instance of ST, which thus - * needs to be copyable and default constructible to the extent - * this is required for the iterator as such. + * -# \c yield realises the given state, yielding an element of result type `T&` + * @tparam T nominal result type (maybe const, but without reference). + * The resulting iterator will yield a reference to this type T + * @tparam ST type of the "state core", defaults to T. + * The resulting iterator will hold an instance of ST, which thus + * needs to be copyable and default constructible to the extent + * this is required for the iterator as such. * @see IterExplorer an iterator monad built on top of IterStateWrapper * @see iter-explorer-test.hpp * @see iter-adaptor-test.cpp @@ -318,15 +342,11 @@ namespace lib { IterStateWrapper (ST&& initialState) : core_(std::forward(initialState)) - { - checkPoint (core_); // extension point: checkPoint - } + { } IterStateWrapper (ST const& initialState) : core_(initialState) - { - checkPoint (core_); // extension point: checkPoint - } + { } IterStateWrapper () : core_() @@ -341,28 +361,28 @@ namespace lib { operator*() const { __throw_if_empty(); - return yield (core_); // extension point: yield + return core_.yield(); // core interface: yield } pointer operator->() const { __throw_if_empty(); - return & yield(core_); // extension point: yield + return & core_.yield(); // core interface: yield } IterStateWrapper& operator++() { __throw_if_empty(); - iterNext (core_); // extension point: iterNext + core_.iterNext(); // core interface: iterNext return *this; } bool isValid () const { - return checkPoint(core_); // extension point: checkPoint + return core_.checkPoint(); // core interface: checkPoint } bool @@ -390,6 +410,7 @@ namespace lib { + public: ENABLE_USE_IN_STD_RANGE_FOR_LOOPS (IterStateWrapper); /// comparison is allowed to access state implementation core @@ -411,7 +432,7 @@ namespace lib { template inline bool operator!= (IterStateWrapper const& il, IterStateWrapper const& ir) - { + { return not (il == ir); } @@ -422,14 +443,19 @@ namespace lib { - /** + /** * Accessing a STL element range through a Lumiera forward iterator, * An instance of this iterator adapter is completely self-contained - * and allows to iterate once over the range of elements, until - * \c pos==end . Thus, a custom container may expose a range of + * and allows to iterate once over the range of elements, until + * `pos==end`. Thus, a custom container may expose a range of * elements of an embedded STL container, without controlling * the details of the iteration (as is possible using the * more generic IterAdapter). + * + * @note + * - when IT is just a pointer, we use the pointee as value type + * - but when IT is a class, we expect the usual STL style nested typedefs + * `value_type`, `reference` and `pointer` */ template class RangeIter @@ -437,10 +463,13 @@ namespace lib { IT p_; IT e_; + using _ValTrait = meta::TypeBinding>; + public: - typedef typename iter::TypeBinding::pointer pointer; - typedef typename iter::TypeBinding::reference reference; - typedef typename iter::TypeBinding::value_type value_type; + using value_type = typename _ValTrait::value_type; + using reference = typename _ValTrait::reference; + using pointer = typename _ValTrait::pointer; + RangeIter (IT const& start, IT const& end) : p_(start) @@ -525,7 +554,7 @@ namespace lib { /// Supporting equality comparisons... template inline bool operator== (RangeIter const& il, RangeIter const& ir) { return (!il && !ir) || (il.getPos() == ir.getPos()); } - + template inline bool operator!= (RangeIter const& il, RangeIter const& ir) { return !(il == ir); } @@ -633,7 +662,7 @@ namespace lib { /// Supporting equality comparisons... template inline bool operator== (NumIter const& il, NumIter const& ir) { return (!il && !ir) || (il.getPos() == ir.getPos()); } - + template inline bool operator!= (NumIter const& il, NumIter const& ir) { return !(il == ir); } @@ -653,7 +682,7 @@ namespace lib { - /** + /** * Helper for type rewritings: * get the element type for an iterator like entity */ @@ -687,7 +716,7 @@ namespace lib { - /** wrapper to declare exposed values const */ + /** wrapper to expose values as const */ template class ConstIter { @@ -747,6 +776,9 @@ namespace lib { { return i_; } + + + ENABLE_USE_IN_STD_RANGE_FOR_LOOPS (ConstIter); }; diff --git a/src/lib/iter-cursor.hpp b/src/lib/iter-cursor.hpp index a069e7840..66b81523e 100644 --- a/src/lib/iter-cursor.hpp +++ b/src/lib/iter-cursor.hpp @@ -65,8 +65,8 @@ namespace lib { public: - typedef typename iter::TypeBinding::pointer pointer; - typedef typename iter::TypeBinding::reference reference; + typedef typename meta::TypeBinding::pointer pointer; + typedef typename meta::TypeBinding::reference reference; typedef typename std::remove_reference::type value_type; ///< @note will be const for const iterators @@ -109,34 +109,34 @@ namespace lib { /* === Iteration control API for IterStateWrapper == */ - friend bool - checkPoint (CursorGear const& gear) - { - return gear.backwards_? gear.pos_ != gear.start_ - : gear.pos_ != gear.end_; - } + bool + checkPoint() const + { + return backwards_? pos_ != start_ + : pos_ != end_; + } - friend reference - yield (CursorGear const& gear) - { - return gear.backwards_? *(gear.pos_-1) - : *(gear.pos_); - } + reference + yield() const + { + return backwards_? *(pos_-1) + : *(pos_); + } - friend void - iterNext (CursorGear & gear) - { - if (gear.backwards_) - --gear.pos_; - else - ++gear.pos_; - } + void + iterNext() + { + if (backwards_) + --pos_; + else + ++pos_; + } friend bool operator== (CursorGear const& g1, CursorGear const& g2) { - return (not checkPoint(g1) and not checkPoint(g2)) // note: all exhausted iters are equal + return (not g1.checkPoint() and not g2.checkPoint()) // note: all exhausted iters are equal or ( g1.pos_ == g2.pos_ and g1.backwards_ == g2.backwards_ and g1.start_ == g2.start_ diff --git a/src/lib/iter-explorer.hpp b/src/lib/iter-explorer.hpp index 800cec49d..153521da3 100644 --- a/src/lib/iter-explorer.hpp +++ b/src/lib/iter-explorer.hpp @@ -73,6 +73,8 @@ ** All the other preconfigured variants defined here where created as proof-of-concept, to document ** and verify this implementation technique as such. ** + ** @todo as of 2017, this framework is deemed incomplete and requires more design work. ////////////////////TICKET #1116 + ** ** @warning preferably use value semantics for the elements to be processed. Recall, C++ is not ** really a functional programming language, and there is no garbage collector. It might be ** tempting just to pass pointers through a whole evaluation chain. Indeed, you can do so, @@ -362,25 +364,25 @@ namespace lib { return bool(results_); } - /* === Iteration control API for IterStateWrapper== */ - friend bool - checkPoint (CombinedIteratorEvaluation const& seq) - { - return unConst(seq).findNextResultElement(); - } + public: /* === Iteration control API for IterStateWrapper== */ + bool + checkPoint() const + { + return unConst(this)->findNextResultElement(); + } - friend reference - yield (CombinedIteratorEvaluation const& seq) - { - return *(seq.results_); - } + reference + yield() const + { + return *results_; + } - friend void - iterNext (CombinedIteratorEvaluation & seq) - { - ++(seq.results_); - } + void + iterNext() + { + ++results_; + } }; /** @@ -593,36 +595,29 @@ namespace lib { return unConst(this)->resultBuf_.getFeed(); } + + public: /* === Iteration control API for IterStateWrapper== */ + + bool + checkPoint() const + { + return bool(feed()); + } + + reference + yield() const + { + return *feed(); + } + void - iterate () + iterNext() { REQUIRE (feed()); ResultIter nextStep = explore_(*feed()); ++ feed(); resultBuf_.feedBack (nextStep); } - - - /* === Iteration control API for IterStateWrapper== */ - - friend bool - checkPoint (RecursiveExhaustingEvaluation const& seq) - { - return bool(seq.feed()); - } - - friend reference - yield (RecursiveExhaustingEvaluation const& seq) - { - reference result = *(seq.feed()); - return result; - } - - friend void - iterNext (RecursiveExhaustingEvaluation & seq) - { - seq.iterate(); - } }; @@ -842,25 +837,25 @@ namespace lib { } - /* === Iteration control API for IterStateWrapper== */ + public: /* === Iteration control API for IterStateWrapper== */ - friend bool - checkPoint (RecursiveSelfIntegration const& seq) - { - return unConst(seq).findNextResultElement(); - } + bool + checkPoint() const + { + return unConst(this)->findNextResultElement(); + } - friend reference - yield (RecursiveSelfIntegration const& seq) - { - return *(seq.outSeq_); - } + reference + yield() const + { + return *outSeq_; + } - friend void - iterNext (RecursiveSelfIntegration & seq) - { - seq.iterate(); - } + void + iterNext() + { + this->iterate(); + } }; @@ -891,23 +886,23 @@ namespace lib { /* === Iteration control API for IterStateWrapper == */ - friend bool - checkPoint (WrappedSequence const& sequence) - { - return bool(sequence); - } + bool + checkPoint() const + { + return bool(*this); + } - friend typename IT::reference - yield (WrappedSequence const& sequence) - { - return *sequence; - } + typename IT::reference + yield() const + { + return *(*this); + } - friend void - iterNext (WrappedSequence & sequence) - { - ++sequence; - } + void + iterNext() + { + ++(*this); + } }; diff --git a/src/lib/iter-source.hpp b/src/lib/iter-source.hpp index 28d02d29e..e3239aa4f 100644 --- a/src/lib/iter-source.hpp +++ b/src/lib/iter-source.hpp @@ -29,7 +29,11 @@ ** a simple data source type, without needing to disclose details of ** the implementation. ** - ** \par Standard Adapters + ** @todo the design used for the "iteration control API" is misaligned + ** with the purpose of this adapter. Rather, it should be shaped + ** similar to IterStateWrapper with three control functions //////////////////////////////////////TICKET #1125 + ** + ** ## Standard Adapters ** As a complement, this header contains a generic implementation ** of the IterSource interface by wrapping an existing Lumiera Forward Iterator. ** Using this WrappedLumieraIter, the details of this wrapped source iterator @@ -51,8 +55,8 @@ #include "lib/meta/util.hpp" #include "lib/iter-adapter.hpp" #include "lib/itertools.hpp" +#include "lib/nocopy.hpp" -#include #include #include #include @@ -89,14 +93,14 @@ namespace lib { typedef TY* Pos; typedef shared_ptr DataHandle; - + ///////////////////////////////////////////////TICKET #1125 : this API should use three control functions, similar to IterStateWrapper /** iteration start: prepare the first element. * may return NULL in case of empty data source */ virtual Pos firstResult () =0; /** iteration step: switch on to the next element. - * The pos pointer may be set to NULL to report + * The pos pointer should be set to NULL to report * iteration end */ virtual void nextResult(Pos& pos) =0; @@ -123,7 +127,7 @@ namespace lib { /* == Iteration control API for IterAdapter frontend == */ - friend bool + friend bool ////////////////////////////////////////TICKET #1125 : this API should use three control functions, similar to IterStateWrapper checkPoint (DataHandle const&, Pos const& pos) { return bool(pos); @@ -141,10 +145,10 @@ namespace lib { struct iterator : IterAdapter - { - using _I = IterAdapter - ; + { ////////////////////////////////////////////////////TICKET #1125 : should be build on top of IterStateWrapper rather than IterAdapter! + using _I = IterAdapter; using _I::IterAdapter; + operator string() const {return _I::source()? string(*_I::source()) : "⟂"; } }; @@ -174,6 +178,10 @@ namespace lib { static iterator EMPTY_SOURCE; + using value_type = TY; + using reference = TY&; + using pointer = TY*; + private: @@ -221,17 +229,17 @@ namespace lib { * is passed to one of the IterSource's builder functions, thereby * erasing the specific type information of the template parameter IT */ - template + template> class WrappedLumieraIter - : public IterSource - , boost::noncopyable + : public ISO + , util::NonCopyable { - using _Base = IterSource; - using Pos = typename _Base::Pos; - IT src_; - Pos + protected: + using Pos = typename ISO::Pos; + + Pos ////////////////////////////////////////////////////TICKET #1125 : this API should use three control functions, similar to IterStateWrapper firstResult () { if (!src_) @@ -261,6 +269,11 @@ namespace lib { WrappedLumieraIter (IT&& orig) : src_(forward(orig)) { } + + + protected: + IT& wrappedIter() { return src_; } + IT const& wrappedIter() const { return src_; } }; @@ -364,6 +377,22 @@ namespace lib { } + /** an IterSource frontend to return just a single value once. + * @warning behind the scenes, a heap allocation is managed by shared_ptr, + * to maintain a copy of the wrapped element. When passing a reference, + * only a reference will be wrapped, but a heap allocation happens nonetheless + */ + template + auto + singleVal (VAL&& something) + { + using Src = decltype(singleValIterator (forward(something))); + using Val = typename _IterT::Val; + + return IterSource::build (new WrappedLumieraIter{singleValIterator (forward(something))}); + } + + /** pipes a given Lumiera Forward Iterator through * a transformation function and wraps the resulting * transforming Iterator, exposing just an IterSource. @@ -461,11 +490,11 @@ namespace lib { using Range = RangeIter; Range contents (container.begin(), container.end()); - return IterSource::build (new WrappedLumieraIter(contents)); + return IterSource::build (new WrappedLumieraIter(contents)); } - - /** @return a Lumiera Forward Iterator yielding all values + + /** @return a Lumiera Forward Iterator to yield all values * defined by a classical Iterator range. */ template @@ -478,16 +507,7 @@ namespace lib { Range contents (begin, end); return IterSource::build (new WrappedLumieraIter(contents)); } - - } - using iter_source::wrapIter; - using iter_source::transform; - using iter_source::eachMapKey; - using iter_source::eachDistinctKey; - using iter_source::eachValForKey; - using iter_source::eachMapVal; - using iter_source::eachEntry; - -} // namespace lib + +}} // namespace lib::iter_source #endif diff --git a/src/lib/iter-stack.hpp b/src/lib/iter-stack.hpp index dec50b321..39220f291 100644 --- a/src/lib/iter-stack.hpp +++ b/src/lib/iter-stack.hpp @@ -82,25 +82,25 @@ namespace lib { { /* === Iteration control API for IterStateWrapper == */ - friend bool - checkPoint (IterDequeStorage const& elements) - { - return not elements.empty(); - } + bool + checkPoint() const + { + return not this->empty(); + } - friend TY & - yield (IterDequeStorage const& elements) - { - REQUIRE (not elements.empty()); - return unConst(elements).back(); - } + TY & + yield() const + { + REQUIRE (not this->empty()); + return unConst(this)->back(); + } - friend void - iterNext (IterDequeStorage & elements) - { - REQUIRE (not elements.empty()); - elements.pop_back(); - } + void + iterNext() + { + REQUIRE (not this->empty()); + this->pop_back(); + } }; } // namespace lib::iter @@ -245,7 +245,7 @@ namespace lib { * to prepare and pre-fill a sequence */ struct Builder - : util::no_copy_by_client + : util::Cloneable { Builder(IterQueue& initialElements) : queue_(initialElements) diff --git a/src/lib/iter-tree-explorer.hpp b/src/lib/iter-tree-explorer.hpp new file mode 100644 index 000000000..6aa6ea8a4 --- /dev/null +++ b/src/lib/iter-tree-explorer.hpp @@ -0,0 +1,1272 @@ +/* + ITER-TREE-EXPLORER.hpp - building blocks for iterator evaluation strategies + + Copyright (C) Lumiera.org + 2017, Hermann Vosseler + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file iter-tree-explorer.hpp + ** Building tree expanding and backtracking evaluations within hierarchical scopes. + ** Based on the *Lumiera Forward Iterator* concept and using the basic IterAdaptor templates, + ** these components allow to implement typical evaluation strategies, like conditional expanding + ** or depth-first exploration of a hierarchical structure. Since the access to this structure is + ** abstracted through the underlying iterator, what we effectively get is a functional datastructure. + ** The implementation is based on the idea of a "state core", which is wrapped right into the iterator + ** itself (value semantics) -- similar to the IterStateWrapper, which is one of the basic helper templates + ** provided by iter-adapter.hpp. + ** + ** @remark as of 2017, this template, as well as the initial IterExplorer (draft from 2012) can be + ** seen as steps towards designing a framework of building blocks for tree expanding and + ** backtracking algorithms. Due to the nature of Lumiera's design, we repeatedly encounter + ** this kind of computation pattern, when it comes to matching flexible configuration against + ** a likewise hierarchical and rules based model. To keep the code base maintainable, + ** we deem it crucial to reduce the inherent complexity in such algorithms by clearly + ** separate the _mechanics of evaluation_ from the actual logic of the target domain. + ** + ** # Iterators as Monad + ** The fundamental idea behind the implementation technique used here is the _Monad pattern_ + ** known from functional programming. A Monad is a container holding some arbitrarily typed base value; the monad can + ** be seen as "amplifying" and enhancing the contained base value by attaching additional properties or capabilities + ** This is a rather detached concept with a wide variety of applications (things like IO state, parsers, combinators, + ** calculations with exception handling but also simple data structures like lists or trees). The key point with any + ** monad is the ability to _bind a function_ into the monad; this function will work on the _contained base values_ + ** and produce a modified new monad instance. In the simple case of a list, "binding" a function basically means + ** to _map the function onto_ the elements in the list. + ** + ** ## Rationale + ** The primary benefit of using the monad pattern is to separate the transforming operation completely from + ** the mechanics of applying that operation and combining the results. More specifically, we rely on an iterator + ** to represent an abstracted source of data and we expose the combined and transformed results again as such + ** an abstracted data sequence. While the transformation to apply can be selected at runtime (as a functor), + ** the monad pattern defines a sane way to represent partial evaluation state without requiring a container + ** for intermediary results. This is especially helpful when + ** - a flexible and unspecific source data structure needs to be processed + ** - and this evaluation needs to be done asynchronously and in parallel (no locking, immutable data) + ** - and a partial evaluation needs to be stored as continuation (not relying on the stack for partial results) + ** + ** # A Pipeline builder + ** Based on such concepts, structures and evaluation patterns, the TreeExplorer serves the purpose to provide + ** building blocks to assemble a _processing pipeline_, where processing will happen _on demand,_ while iterating. + ** TreeExplorer itself is both a Lumiera Forward Iterator based on some wrapped data source, and at the same time + ** it is a builder to chain up processing steps to work on the data pulled from that source. These processing steps + ** are attached as _decorators_ wrapping the source, in the order the corresponding builder functions were invoked. + ** - the *expand operation* installs a function to consume one element and replace it by the sequence of elements + ** (``children'') produced by that _»expansion functor«_. But this expansion does not happen automatically and + ** on each element, rather it is triggered by issuing a dedicated `expandChildren()` call on the processing + ** pipeline. Thus, binding the expansion functor has augmented the data source with the ability to explore + ** some part in more detail _when required_. + ** - the *transform operation* installs a function to be mapped onto each element retrieved from the underlying source + ** - in a similar vein, the *filter operation* binds a predicate to decide about using or discarding data + ** - in concert, expand- and transform operation allow to build hierarchy evaluation algorithms without exposing + ** any knowledge regarding the concrete hierarchy used and explored as data source. + ** + ** In itself, the TreeExplorer is an iterator with implementation defined type (all operations being inlined). + ** But it is possible to package this structure behind a conventional iteration interface with virtual functions. + ** By invoking the terminal builder function TreeExplorer::asIterSource(), the iterator compound type, as created + ** thus far, will be moved into a heap allocation, returning a front-end based on IterSource. In addition, the + ** actually returned type, IterExplorerSource, exposes the `expandChildren()` operation as discussed above. + ** + ** ## some implementation details + ** The builder operations assemble a heavily nested type, each builder call thereby adding yet another layer of + ** subclassing. The templates involved into this build process are _specialised_, as driven by the actual functor + ** type bound into each builder step; this functor is investigated and possibly adapted, according to its input + ** type, while its output type determines the value type used in the pipeline "downstream". A functor with an + ** input (argument) type incompatible or unsuitable to the existing pipeline will produce that endless sway + ** of template error messages we all love so much. When this happens, please look at the static assertion + ** error message typically to be found below the first template-instantiation stack sequence of messages. + ** + ** @warning all builder operations work by _moving_ the existing pipeline built thus far into the parent + ** of the newly built subclass object. The previously existing pipeline is defunct after that + ** move; if you captured it into a variable, be sure to capture the _result_ of the new + ** builder operation as well and don't use the old variable anymore. Moreover, it should + ** be ensured that any "state core" used within TreeExplorer has an efficient move ctor; + ** including RVO, the compiler is typically able to optimise such move calls away altogether. + ** + ** @see IterTreeExplorer_test + ** @see iter-adapter.hpp + ** @see itertools.hpp + ** @see IterSource (completely opaque iterator) + ** + */ + + +#ifndef LIB_ITER_TREE_EXPLORER_H +#define LIB_ITER_TREE_EXPLORER_H + + +#include "lib/error.hpp" +#include "lib/meta/duck-detector.hpp" +#include "lib/meta/function.hpp" +#include "lib/meta/trait.hpp" +#include "lib/wrapper.hpp" ////////////TODO : could be more lightweight by splitting FunctionResult into separate header. Relevant? +#include "lib/iter-adapter.hpp" +#include "lib/iter-source.hpp" /////////////TICKET #493 : only using the IterSource base feature / interface here. Should really split the iter-source.hpp +#include "lib/iter-stack.hpp" +#include "lib/util.hpp" + +#include +#include + + +namespace lib { + + using std::move; + using std::forward; + using std::function; + using util::isnil; + + namespace error = lumiera::error; + + namespace iter_explorer { // basic iterator wrappers... + + template + using iterator = typename meta::Strip::TypeReferred::iterator; + template + using const_iterator = typename meta::Strip::TypeReferred::const_iterator; + + /** + * Adapt STL compliant container. + * @note the container itself is _not_ included in the resulting iterator, + * it is just assumed to stay alive during the entire iteration. + */ + template + struct StlRange + : RangeIter> + { + StlRange() =default; + StlRange (CON& container) + : RangeIter> {begin(container), end(container)} + { } + // standard copy operations acceptable + }; + + template + struct StlRange + : RangeIter> + { + StlRange() =default; + StlRange (CON const& container) + : RangeIter> {begin(container), end(container)} + { } + // standard copy operations acceptable + }; + + + /** + * Decorate a state or logic core to treat it as Lumiera Forward Iterator. + * This Adapter does essentially the same as \ref IterStateWrapper, but here + * the state core is not encapsulated opaque, but rather inherited, and thus + * the full interface of the core remains publicly accessible. + */ + template + class IterableDecorator + : public COR + { + COR & _core() { return static_cast (*this); } + COR const& _core() const { return static_cast (*this); } + + void + __throw_if_empty() const + { + if (not isValid()) + throw lumiera::error::Invalid ("Can't iterate further", + lumiera::error::LUMIERA_ERROR_ITER_EXHAUST); + } + + + public: + typedef T* pointer; + typedef T& reference; + typedef T value_type; + + /** by default, pass anything down for initialisation of the core. + * @note especially this allows move-initialisation from an existing core. + * @remarks to prevent this rule from "eating" the standard copy operations, + * and the no-op default ctor, we need to declare them explicitly below. + */ + template + IterableDecorator (ARGS&& ...init) + : COR(std::forward(init)...) + { } + + IterableDecorator() =default; + IterableDecorator (IterableDecorator&&) =default; + IterableDecorator (IterableDecorator const&) =default; + IterableDecorator& operator= (IterableDecorator&&) =default; + IterableDecorator& operator= (IterableDecorator const&) =default; + + + /* === lumiera forward iterator concept === */ + + explicit operator bool() const { return isValid(); } + + reference + operator*() const + { + __throw_if_empty(); + return _core().yield (); // core interface: yield + } + + pointer + operator->() const + { + __throw_if_empty(); + return & _core().yield(); // core interface: yield + } + + IterableDecorator& + operator++() + { + __throw_if_empty(); + _core().iterNext(); // core interface: iterNext + return *this; + } + + bool + isValid () const + { + return _core().checkPoint(); // core interface: checkPoint + } + + bool + empty () const + { + return not isValid(); + } + + + + ENABLE_USE_IN_STD_RANGE_FOR_LOOPS (IterableDecorator); + + + /// Supporting equality comparisons of equivalent iterators (same state core)... + template + friend bool + operator== (IterableDecorator const& il, IterableDecorator const& ir) + { + return (il.empty() and ir.empty()) + or (il.isValid() and ir.isValid() and il._core() == ir._core()); + } + + template + friend bool + operator!= (IterableDecorator const& il, IterableDecorator const& ir) + { + return not (il == ir); + } + }; + + + /** + * Adapt an IterSource to make it iterable. As such, lib::IterSource is meant + * to be iterable, while only exposing a conventional VTable-based _iteration interface_. + * To support this usage, the library offers some builders to attach an iterator adapter. + * Two flavours need to be distinguished: + * - we get a _reference_ to something living elsewhere; all we know is it's iterable. + * - we get a pointer, indicating that we must take ownership and manage the lifetime. + * The iterable entity in this case can be assumed to be heap allocated, featuring a + * virtual destructor. + * The generated front-end has identical type in both cases; it is based on a shared_ptr, + * just a different deleter function is used in both cases. This design makes sense, + * since the protocol requires us to invoke virtual function IterSource::disconnect() + * when use count goes to zero. + */ + template + class IterSourceIter + : public ISO::iterator + { + using Iterator = typename ISO::iterator; + + public: + IterSourceIter() =default; + // standard copy operations + + /** link to existing IterSource (without memory management) */ + IterSourceIter (ISO& externalSource) + : Iterator{ISO::build (externalSource)} + { } + + /** own and manage a heap allocated IterSource */ + IterSourceIter (ISO* heapObject) + : Iterator{heapObject? ISO::build (heapObject) + : Iterator()} + { } + + using Source = ISO; + + Source& + source() + { + REQUIRE (ISO::iterator::isValid()); + return static_cast (*ISO::iterator::source()); + } + }; + + }//(End) namespace iter_explorer : basic iterator wrappers + + + + + namespace { // TreeExplorer traits + + using meta::enable_if; + using meta::disable_if; + using meta::Yes_t; + using meta::No_t; + using meta::_Fun; + using std::__and_; + using std::__not_; + using std::is_base_of; + using std::is_convertible; + using std::remove_reference_t; + using meta::can_IterForEach; + using meta::can_STL_ForEach; + + META_DETECT_FUNCTION_ARGLESS(checkPoint); + META_DETECT_FUNCTION_ARGLESS(iterNext); + META_DETECT_FUNCTION_ARGLESS(yield); + + template + struct is_StateCore + : __and_< HasArglessFun_checkPoint + , HasArglessFun_iterNext + , HasArglessFun_yield + > + { }; + + template + struct shall_wrap_STL_Iter + : __and_ + ,__not_> + > + { }; + + template + struct shall_use_StateCore + : __and_<__not_> + ,is_StateCore + > + { }; + + template + struct shall_use_Lumiera_Iter + : __and_ + ,__not_> + > + { }; + + + /** the _value type_ yielded by a »state core« */ + template + struct CoreYield + { + using Res = remove_reference_t().yield())>; + + using value_type = typename meta::TypeBinding::value_type; + using reference = typename meta::TypeBinding::reference; + using pointer = typename meta::TypeBinding::pointer; + }; + + + /** decide how to adapt and embed the source sequence into the resulting TreeExplorer */ + template + struct _DecoratorTraits + { + static_assert (!sizeof(SRC), "Can not build TreeExplorer: Unable to figure out how to iterate the given SRC type."); + }; + + template + struct _DecoratorTraits>> + { + using SrcVal = typename CoreYield::value_type; + using SrcIter = iter_explorer::IterableDecorator; + }; + + template + struct _DecoratorTraits>> + { + using SrcIter = remove_reference_t; + using SrcVal = typename SrcIter::value_type; + }; + + template + struct _DecoratorTraits>> + { + static_assert (not std::is_rvalue_reference::value, + "container needs to exist elsewhere during the lifetime of the iteration"); + using SrcIter = iter_explorer::StlRange; + using SrcVal = typename SrcIter::value_type; + }; + + template + struct _DecoratorTraits, ISO>>> + { + using SrcIter = iter_explorer::IterSourceIter; + using SrcVal = typename ISO::value_type; + }; + + template + struct _DecoratorTraits, ISO>>> + : _DecoratorTraits + { }; + + template + struct _DecoratorTraits, ISO>>> + : _DecoratorTraits + { }; + + }//(End) TreeExplorer traits + + + + + + namespace iter_explorer { // Implementation of Iterator decorating layers... + + /** + * @internal technical details of binding a functor into the TreeExplorer. + * Notably, this happens when adapting an _"expansion functor"_ to allow expanding a given element + * from the TreeExploer (iterator) into a sequence of child elements. A quite similar situation + * arises when binding a _transformation function_ to be mapped onto each result element. + * + * The TreeExplorer::expand() operation accepts various flavours of functors, and depending on + * the signature of such a functor, an appropriate adapter will be constructed here, allowing to + * write a generic Expander::expandChildren() operation. The following details are handled here: + * - detect if the passed functor is generic, or a regular "function-like" entity. + * - in case it is generic (generic lambda), we assume it actually accepts a reference to + * the source iterator type `SRC`. Thus we instantiate a templated functor with this + * argument type to find out about its result type (and this instantiation may fail) + * - moreover, we try to determine, if an explicitly typed functor accepts a value as yielded + * by the embedded source iterator (this is the "monadic" usage pattern), or if it rather + * accepts the iterator or state core itself (the "opaque state manipulation" usage pattern). + * - we generate a suitable argument accessor function and build the function composition + * of this accessor and the provided _expansion functor_. + * - the resulting, combined functor is stored into a std::function, but wired in a way to + * keep the argument-accepting front-end still generic (templated `operator()`). This + * special adapter supports the case when the _expansion functor_ yields a child sequence + * type different but compatible to the original source sequence embedded in TreeExplorer. + * @tparam FUN something _"function-like"_ passed as functor to be bound + * @tparam SRC the source iterator type to apply when attempting to use a generic lambda as functor + */ + template + struct _BoundFunctor + { + /** handle all regular "function-like" entities */ + template + struct FunDetector + { + using Sig = typename _Fun::Sig; + }; + + /** handle a generic lambda, accepting a reference to the `SRC` iterator */ + template + struct FunDetector> > + { + using Arg = typename std::add_lvalue_reference::type; + using Ret = decltype(std::declval() (std::declval())); + using Sig = Ret(Arg); + }; + + + using Sig = typename FunDetector::Sig; + using Arg = typename _Fun::Args::List::Head; // assuming function with a single argument + using Res = typename _Fun::Ret; + + + + /** adapt to a functor, which accesses the source iterator or embedded "state core" */ + template + struct ArgAccessor + { + using FunArgType = remove_reference_t; + static_assert (std::is_convertible::value, + "the bound functor must accept the source iterator or state core as parameter"); + + static auto build() { return [](ARG& arg) -> ARG& { return arg; }; } + }; + + /** adapt to a functor, which accepts the value type of the source sequence ("monadic" usage pattern) */ + template + struct ArgAccessor>> + { + static auto build() { return [](auto& iter) { return *iter; }; } + }; + + /** adapt to a functor collaborating with an IterSource based iterator pipeline */ + template + struct ArgAccessor, typename IT::Source> + , is_base_of, remove_reference_t> + > >> + { + using Source = typename IT::Source; + + static auto build() { return [](auto& iter) -> Source& { return iter.source(); }; } + }; + + + /** holder for the suitably adapted _expansion functor_ */ + struct Functor + { + function boundFunction; + + template + Res + operator() (ARG& arg) + { + auto accessArg = ArgAccessor::build(); + + return boundFunction (accessArg (arg)); + } + }; + }; + + + + /** + * @internal Base of pipe processing decorator chain. + * TreeExplorer allows to create a stack out of various decorating processors + * - each decorator is itself a _"state core"_, adding some on-demand processing + * - each wraps and adapts a source iterator, attaching to and passing-on the iteration logic + * Typically each such layer is configured with actual functionality provided as lambda or functor. + * Yet in addition to forming an iteration pipeline, there is kind of an internal interconnection + * protocol, allowing the layers to collaborate; notably this allows to handle an expandChildren() + * call, where some "expansion layer" consumes the current element and replaces it by an expanded + * series of new elements. Other layers might need to sync to this operation, and thus it is passed + * down the chain. For that reason, we need a dedicated BaseAdapter to adsorb such chained calls. + * @remark when building the TreeExplorer, the to-be-wrapped source is fed down into its place + * within BaseAdapter. For that reason, we need to lift the copy ctors of the base. + * Just inheriting the base class ctors won't do that, at least not in C++14. + */ + template + struct BaseAdapter + : SRC + { + BaseAdapter() = default; + BaseAdapter(SRC const& src) : SRC(src) { } + BaseAdapter(SRC && src) : SRC(src) { } + + void expandChildren() { } + size_t depth() const { return 0; } + }; + + + /** + * @internal Decorator for TreeExplorer adding the ability to "expand children". + * The expandChildren() operation is the key element of a depth-first evaluation: it consumes + * one element and performs a preconfigured _expansion functor_ on that element to yield + * its "children". These are given in the form of another iterator, which needs to be + * compatible to the source iterator ("compatibility" boils down to both iterators + * yielding a compatible value type). Now, this _sequence of children_ effectively + * replaces the expanded source element in the overall resulting sequence; which + * means, the nested sequence was _flattened_ into the results. Since this expand() + * operation can again invoked on the results, the implementation of such an evaluation + * requires a stack datastructure, so the nested iterator from each expand() invocation + * can be pushed to become the new active source for iteration. Thus the primary purpose + * of this Expander (decorator) is to integrate those "nested child iterators" seamlessly + * into the overall iteration process; once a child iterator is exhausted, it will be + * popped and iteration continues with the previous child iterator or finally with + * the source iterator wrapped by this decorator. + * @remark since we allow a lot of leeway regarding the actual form and definition of the + * _expansion functor_, there is a lot of minute technical details, mostly confined + * within the _BoundFunctorTraits. + * @tparam SRC the wrapped source iterator, typically a TreeExplorer or nested decorator. + * @tparam FUN the concrete type of the functor passed. Will be dissected to find the signature + */ + template + class Expander + : public SRC + { + static_assert(can_IterForEach::value, "Lumiera Iterator required as source"); + using _Traits = _BoundFunctor; + using ExpandFunctor = typename _Traits::Functor; + + using ResIter = typename _DecoratorTraits::SrcIter; + static_assert (std::is_convertible::value, + "the iterator from the expansion must yield compatible values"); + + ExpandFunctor expandChildren_; + IterStack expansions_; + + public: + Expander() =default; + // inherited default copy operations + + Expander (SRC&& parentExplorer, FUN&& expandFunctor) + : SRC{move (parentExplorer)} // NOTE: slicing move to strip TreeExplorer (Builder) + , expandChildren_{forward (expandFunctor)} + , expansions_{} + { } + + + /** core operation: expand current head element */ + void + expandChildren() + { + REQUIRE (this->checkPoint(), "attempt to expand an empty explorer"); + + ResIter expanded{ hasChildren()? expandChildren_(*expansions_) + : expandChildren_(*this)}; + incrementCurrent(); // consume current head element (but don't clean-up) + if (not isnil(expanded)) + expansions_.push (move(expanded)); + else + dropExhaustedChildren(); // clean-up only here to preserve the logical depth + + ENSURE (invariant()); + } + + /** diagnostics: current level of nested child expansion */ + size_t + depth() const + { + return expansions_.size(); + } + + + public: /* === Iteration control API for IterableDecorator === */ + + bool + checkPoint() const + { + ENSURE (invariant()); + + return hasChildren() + or SRC::isValid(); + } + + typename SRC::reference + yield() const + { + return hasChildren()? **expansions_ + : **this; + } + + void + iterNext() + { + incrementCurrent(); + dropExhaustedChildren(); + ENSURE (invariant()); + } + + private: + bool + invariant() const + { + return not hasChildren() + or *expansions_; + } + + bool + hasChildren() const + { + return 0 < depth(); + } + + void + incrementCurrent() + { + if (hasChildren()) + ++(*expansions_); + else + ++(*this); + } + + void + dropExhaustedChildren() + { + while (not invariant()) + ++expansions_; + } + }; + + + + /** + * @internal extension to the Expander decorator to perform expansion automatically on each iteration step. + * @todo as of 12/2017, this is more like a proof-of concept and can be seen as indication, that there might + * be several flavours of child expansion. Unfortunately, most of these conceivable extensions would + * require a flexibilisation of Expander's internals and thus increase the complexity of the code. + * Thus, if we ever encounter the need of anything beyond the basic expansion pattern, we should + * rework the design of Expander and introduce building blocks to define the evaluation strategy. + */ + template + class AutoExpander + : public SRC + { + static_assert(is_StateCore::value, "need wrapped state core as predecessor in pipeline"); + + public: + /** pass through ctor */ + using SRC::SRC; + + void + iterNext() + { + SRC::expandChildren(); + } + }; + + + + /** + * @internal extension to the Expander decorator to perform expansion delayed on next iteration. + */ + template + class ScheduledExpander + : public SRC + { + static_assert(is_StateCore::value, "need wrapped state core as predecessor in pipeline"); + + bool shallExpand_ = false; + + public: + /** pass through ctor */ + using SRC::SRC; + + void + expandChildren() + { + shallExpand_ = true; + } + + void + iterNext() + { + if (shallExpand_) + { + SRC::expandChildren(); + shallExpand_ = false; + } + else + SRC::iterNext(); + } + }; + + + + /** + * @internal Decorator for TreeExplorer to map a transformation function on all results. + * The transformation function is invoked on demand, and only once per item to be treated, + * storing the treated result into an universal value holder buffer. The given functor + * is adapted in a similar way as the "expand functor", so to detect and convert the + * expected input on invocation. + */ + template + class Transformer + : public SRC + { + static_assert(can_IterForEach::value, "Lumiera Iterator required as source"); + using _Traits = _BoundFunctor; + using Res = typename _Traits::Res; + + using TransformFunctor = typename _Traits::Functor; + using TransformedItem = wrapper::ItemWrapper; + + TransformFunctor trafo_; + TransformedItem treated_; + + public: + using value_type = typename meta::TypeBinding::value_type; + using reference = typename meta::TypeBinding::reference; + using pointer = typename meta::TypeBinding::pointer; + + + Transformer() =default; + // inherited default copy operations + + Transformer (SRC&& dataSrc, FUN&& transformFunctor) + : SRC{move (dataSrc)} + , trafo_{forward (transformFunctor)} + { } + + + /** refresh state when other layers manipulate the source sequence + * @remark expansion replaces the current element by a sequence of + * "child" elements. Since we cache our transformation, we + * need to ensure possibly new source elements get processed + */ + void + expandChildren() + { + treated_.reset(); + SRC::expandChildren(); + } + + public: /* === Iteration control API for IterableDecorator === */ + + bool + checkPoint() const + { + return bool(srcIter()); + } + + reference + yield() const + { + return unConst(this)->invokeTransformation(); + } + + void + iterNext() + { + ++ srcIter(); + treated_.reset(); + } + + private: + SRC& + srcIter() const + { + return unConst(*this); + } + + reference + invokeTransformation () + { + if (not treated_) // invoke transform function once per src item + treated_ = trafo_(srcIter()); + return *treated_; + } + }; + + + + /** + * @internal Decorator for TreeExplorer to filter elements based on a predicate. + * Similar to the Transformer, the given functor is adapted as appropriate. However, + * we require the functor's result type to be convertible to bool, to serve as approval test. + * The filter predicate and thus the source iterator is evaluated _eagerly_, to establish the + * *invariant* of this class: _if a "current element" exists, it has already been approved._ + */ + template + class Filter + : public SRC + { + static_assert(can_IterForEach::value, "Lumiera Iterator required as source"); + using _Traits = _BoundFunctor; + using Res = typename _Traits::Res; + static_assert(std::is_constructible::value, "Functor must be a predicate"); + + using FilterPredicate = typename _Traits::Functor; + + FilterPredicate predicate_; + + public: + + Filter() =default; + // inherited default copy operations + + Filter (SRC&& dataSrc, FUN&& filterFun) + : SRC{move (dataSrc)} + , predicate_{forward (filterFun)} + { + pullFilter(); // initially pull to establish the invariant + } + + + /** refresh state when other layers manipulate the source sequence. + * @note possibly pulls to re-establish the invariant */ + void + expandChildren() + { + pullFilter(); + SRC::expandChildren(); + } + + public: /* === Iteration control API for IterableDecorator === */ + + bool + checkPoint() const + { + return bool(srcIter()); + } + + typename SRC::reference + yield() const + { + return *srcIter(); + } + + void + iterNext() + { + ++ srcIter(); + pullFilter(); + } + + private: + SRC& + srcIter() const + { + return unConst(*this); + } + + /** @note establishes the invariant: + * whatever the source yields as current element, + * has already been approved by our predicate */ + void + pullFilter () + { + while (srcIter() and not predicate_(srcIter())) + ++srcIter(); + } + }; + + + /** + * Interface to indicate and expose the ability for _child expansion_. + * This interface is used when packaging a TreeExplorer pipeline opaquely into IterSource. + * @remark the depth() call indicates the depth of the child expansion tree. This information + * can be used by a "downstream" consumer to react according to a nested scope structure. + * @todo expandChildren() should not return the value pointer. + * This is just a workaround to cope with the design mismatch in IterSource; + * the fact that latter just passes around a pointer into the implementation is + * ugly, dangerous and plain silly. ////////////////////////////////////////////////////////////TICKET #1125 + * Incidentally, this is also the sole reason why this interface is templated with `VAL` + */ + template + class ChildExpandableSource + { + protected: + ~ChildExpandableSource() { } ///< @note mix-in interface, not meant to handle objects + public: + virtual VAL* expandChildren() =0; + virtual size_t depth() const =0; + }; + + /** + * @internal Decorator to package a whole TreeExplorer pipeline suitably to be handled through + * an IterSource based front-end. Such packaging is performed by the TreeExplorer::asIterSource() + * terminal builder function. In addition to [wrapping the iterator](\ref WrappedLumieraIter), + * the `expandChildren()` operation is exposed as virtual function, to allow invocation through + * the type-erased front-end, without any knowledge about the concrete implementation type + * of the wrapped TreeIterator pipeline. + */ + template + class PackagedTreeExplorerSource + : public WrappedLumieraIter + , public ChildExpandableSource + { + using Parent = WrappedLumieraIter; + using Val = typename SRC::value_type; ///////////////////////////////////TICKET #1125 : get rid of Val + + public: + using Parent::Parent; + + virtual Val* + expandChildren() override + { + Parent::wrappedIter().expandChildren(); + return Parent::wrappedIter()? & *Parent::wrappedIter() ///////////////////////////////////TICKET #1125 : trickery to cope with the misaligned IterSource design + : nullptr; + } + + virtual size_t + depth() const override + { + return Parent::wrappedIter().depth(); + } + }; + + }//(End)Iterator decorating layer implementation + + + + + + /** + * Iterator front-end to manage and operate a TreeExplorer pipeline opaquely. + * In addition to the usual iterator functions, this front-end also exposes an + * `expandChildren()`-function, to activate the _expansion functor_ installed + * through TreeExplorer::expand(). + * @remarks A iterator pipeline is assembled through invocation of the builder functions + * on TreeExplorer -- this way creating a complex implementation defined iterator type. + * This front-end manages such a pipeline in heap allocated storage (by shared_ptr), while + * exposing only a simple conventional interface (templated to the resulting value type `VAL`). + * This allows to pass it over interfaces as "unspecified data source", without disclosing + * the details of the implementation. + * @warning this lightweight front-end handle in itself is copyable and default constructible, + * but any copies will hold onto the same implementation back-end. The effect of competing + * manipulations through such copies is _undefined_ (it depends on arbitrary intrinsics of + * the implementation). Recommendation is, at any time, to use only one single instance + * for iteration and discard it when done. + */ + template + struct IterExploreSource + : IterSource::iterator + { + using Expandable = iter_explorer::ChildExpandableSource; + + + IterExploreSource() =default; + // inherited default copy operations + + + void + expandChildren() + { + VAL* changedResult = expandableSource().expandChildren(); + this->resetPos (changedResult); ///////////////////////////////////TICKET #1125 : trickery to cope with the misaligned IterSource design + } + + size_t + depth() const + { + return expandableSource().depth(); + } + + + private: + template + friend class TreeExplorer; + + template + IterExploreSource (IT&& opaqueSrcPipeline) + : IterSource::iterator { + IterSource::build ( + new iter_explorer::PackagedTreeExplorerSource { + move (opaqueSrcPipeline)})} + { } + + Expandable& + expandableSource() const + { + if (not this->source()) + throw error::State ("operating on a disabled default constructed TreeExplorer" + ,error::LUMIERA_ERROR_BOTTOM_VALUE); + + auto source = unConst(this)->source().get(); + return dynamic_cast (*source); + } + }; + + + + + + /* ======= TreeExplorer pipeline builder and iterator ======= */ + + /** + * Adapter to build a demand-driven tree expanding and exploring computation + * based on a custom opaque _state core_. TreeExploer adheres to the _Monad_ + * pattern known from functional programming, insofar the _expansion step_ is + * tied into the basic template by means of a function provided at usage site. + * This allows to separate the mechanics of evaluation and result combination + * from the actual processing and thus to define tree structured computations + * based on an opaque source data structure not further disclosed. + * + * \param usage + * - to build a TreeExplorer, use the treeExplore() free function, + * which cares to pick up an possibly adapt the given iteration source + * - to add processing layers, invoke the builder operations on TreeExplorer + * in a chained fashion, thereby binding functors or lambdas. Capture the + * final result with an auto variable. + * - the result is iterable in accordance to »Lumiera Forward Iterator« + * + * @warning deliberately, the builder functions exposed on TreeExplorer will + * _move_ the old object into the new, augmented iterator. This is + * possibly dangerous, since one might be tempted to invoke such a + * builder function on an existing iterator variable captured by auto. + * @todo if this turns out as a problem on the long run, we'll need to block + * the iterator operations on the builder (by inheriting protected) + * and provide an explicit `build()`-function, which removes the + * builder API and unleashes or slices down to the iterator instead. + */ + template + class TreeExplorer + : public SRC + { + static_assert(can_IterForEach::value, "Lumiera Iterator required as source"); + + + public: + using value_type = typename meta::TypeBinding::value_type; + using reference = typename meta::TypeBinding::reference; + using pointer = typename meta::TypeBinding::pointer; + + /** pass-through ctor */ + using SRC::SRC; + + + + /* ==== Builder functions ==== */ + + /** preconfigure this TreeExplorer to allow for _"expansion of children"_. + * The resulting iterator exposes an `expand()` function, which consumes + * the current head element of this iterator and feeds it through the + * _expansion functor_, which was provided to this builder function here. + * The _expansion functor_ is expected to yield a sequence of "child" elements, + * which will be integrated into the overall result sequence instead of the + * consumed source element. Thus, repeatedly invoking `expand()` until exhaustion + * generates a _depth-first evaluation_, since every child will be expanded until + * reaching the leaf nodes of a tree like structure. + * + * @param expandFunctor a "function-like" entity to perform the actual "expansion". + * There are two distinct usage patterns, as determined by the signature + * of the provided function or functor: + * - _"monad style"_: the functor takes a _value_ from the sequence and + * produces a new sequence, iterator or collection of compatible values + * - _"opaque state manipulation"_: the functor accepts the concrete source + * iterator type, or even a "state core" type embedded therein. It yields + * a new sequence, state core or collection representing the "children". + * Obviously, the intention here is to allow hidden collaboration between + * the expansion functor and the embedded opaque "data source". For that + * reason, the functor may take its argument by reference, and a produced + * new "child state core" may likewise collaborate with that original + * data source or state core behind the scenes; the latter is guaranteed + * to exist during the whole lifetime of this TreeExplorer. + * @note there is limited support for generic lambdas, but only for the second case. + * The reason is, we can not "probe" a template or generic lambda for possible + * argument and result types. Thus, if you provide a generic lambda, TreeExplorer + * tries to pass it a `SrcIter &` (reference to the embedded original iterator). + * For any other cases, please provide a lambda or functor with a single, explicitly + * typed argument. Obviously, argument and result type should also make sense for + * the desired evaluation pattern, otherwise you'll get all kinds of nasty + * template compilation failures (have fun!) + */ + template + auto + expand (FUN&& expandFunctor) + { + using ResCore = iter_explorer::Expander; + using ResIter = typename _DecoratorTraits::SrcIter; + + return TreeExplorer (ResCore {move(*this), forward(expandFunctor)}); + } + + + /** extension functionality to be used on top of expand(), to perform expansion automatically. + * When configured, child elements will be expanded on each iteration step; it is thus not + * necessary to invoke `expandChildren()` (and doing so would have no further effect than + * just iterating). Thus, on iteration, each current element will be fed to the _expand functor_ + * and the results will be integrated depth first. + * @warning iteration will be infinite, unless the _expand functor_ provides some built-in + * termination condition, returning an empty child sequence at that point. This would + * then be the signal for the internal combination mechanism to return to visiting the + * results of preceding expansion steps, eventually exhausting all data source(s). + */ + auto + expandAll() + { + using ResCore = iter_explorer::AutoExpander; + using ResIter = typename _DecoratorTraits::SrcIter; + + return TreeExplorer (ResCore {move(*this)}); + } + + + /** extension functionality to be used on top of expand(), to perform expansion on next iteration. + * When configured, an expandChildren() call will not happen immediately, but rather in place of + * the next iteration step. Basically child expansion _is kind of a special iteration step,_ and + * thus all we need to do is add another layer with a boolean state flag, which catches the + * expandChildren() and iterNext() calls and redirects appropriately. + * @warning expandAll and expandOnIteration are not meant to be used at the same time. + * Recommendation is to use expandOnIteration() right above (after) the expand() + * definition, since interplay with intermingled layers can be complex. + */ + auto + expandOnIteration() + { + using ResCore = iter_explorer::ScheduledExpander; + using ResIter = typename _DecoratorTraits::SrcIter; + + return TreeExplorer (ResCore {move(*this)}); + } + + + /** adapt this TreeExplorer to pipe each result value through a transformation function. + * Several "layers" of mapping can be piled on top of each other, possibly mixed with the + * other types of adaptation, like the child-expanding operation, or a filter. Obviously, + * when building such processing pipelines, the input and output types of the functors + * bound into the pipeline need to be compatible or convertible. The transformation + * functor supports the same definition styles as described for #expand + * - it can be pure functional, src -> res + * - it can accept the underlying source iterator and exploit side-effects + */ + template + auto + transform (FUN&& transformFunctor) + { + using ResCore = iter_explorer::Transformer; + using ResIter = typename _DecoratorTraits::SrcIter; + + return TreeExplorer (ResCore {move(*this), forward(transformFunctor)}); + } + + + /** adapt this TreeExplorer to filter results, by invoking the given functor to approve them. + * The previously created source layers will be "pulled" to fast-forward immediately to the + * next element confirmed this way by the bound functor. If none of the source elements + * is acceptable, the iterator will transition to exhausted state immediately. + */ + template + auto + filter (FUN&& filterPredicate) + { + using ResCore = iter_explorer::Filter; + using ResIter = typename _DecoratorTraits::SrcIter; + + return TreeExplorer (ResCore {move(*this), forward(filterPredicate)}); + } + + + /** _terminal builder_ to package the processing pipeline as IterSource. + * Invoking this function moves the whole iterator compound, as assembled by the preceding + * builder calls, into heap allocated memory and returns a [iterator front-end](\ref IterExploreSource). + * Any iteration and manipulation on that front-end is passed through virtual function calls into + * the back-end, thereby concealing all details of the processing pipeline. + */ + IterExploreSource + asIterSource() + { + return IterExploreSource {move(*this)}; + } + }; + + + + + + + + + /* ==== convenient builder free functions ==== */ + + /** start building a TreeExplorer + * by suitably wrapping the given iterable source. + * @return a TreeEplorer, which is an Iterator to yield all the source elements, + * but may also be used to build an processing pipeline. + * @warning if you capture the result of this call by an auto variable, + * be sure to understand that invoking any further builder operation on + * TreeExplorer will invalidate that variable (by moving it into the + * augmented iterator returned from such builder call). + */ + template + inline auto + treeExplore (IT&& srcSeq) + { + using SrcIter = typename _DecoratorTraits::SrcIter; + using Base = iter_explorer::BaseAdapter; + + return TreeExplorer (std::forward (srcSeq)); + } + + +} // namespace lib +#endif /* LIB_ITER_TREE_EXPLORER_H */ diff --git a/src/lib/iter-type-binding.hpp b/src/lib/iter-type-binding.hpp deleted file mode 100644 index 7ca5c87e3..000000000 --- a/src/lib/iter-type-binding.hpp +++ /dev/null @@ -1,113 +0,0 @@ -/* - ITER-TYPE-BINDING.hpp - control type variations for IterAdapter - - Copyright (C) Lumiera.org - 2010, Hermann Vosseler - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of - the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - -*/ - -/** @file iter-type-binding.hpp - ** Type re-binding helper template for IterAdapter and friends. - ** This header defines a trait template which is used by the Iterator - ** adapters to figure out the value-, pointer- and reference types - ** when wrapping iterators or containers. For extending the use of - ** Iterator adapters and Iter-tools to situations involving const - ** iterators or custom containers, explicit specialisations - ** might be injected prior to instantiating the Iterator adapter - ** template. - ** - ** @see iter-adapter.hpp - ** @see scope-path.hpp usage example (explicit specialisation) - */ - - -#ifndef LIB_ITER_TYPE_BINDING_H -#define LIB_ITER_TYPE_BINDING_H - - -#include "lib/error.hpp" - - - -namespace std { - template - class shared_ptr; -} - -namespace lib { -namespace iter { - - /** - * Type re-binding helper template for creating nested typedefs - * for use by IterAdapter or similar "Lumiera Forward Iterators". - * This trait provides a value-, reference- and pointer type, - * similar to what the STL does. - * @note client code might define specialisations - * to handle tricky situations (like const_reverse_iter) - */ - template - struct TypeBinding - { - typedef typename TY::pointer pointer; - typedef typename TY::reference reference; - typedef typename TY::value_type value_type; - }; - - template - struct TypeBinding - { - typedef TY value_type; - typedef TY& reference; - typedef TY* pointer; - }; - - template - struct TypeBinding - { - typedef TY value_type; - typedef const TY& reference; - typedef const TY* pointer; - }; - - template - struct TypeBinding - { - typedef TY value_type; - typedef TY& reference; - typedef TY* pointer; - }; - - template - struct TypeBinding - { - typedef TY value_type; - typedef const TY& reference; - typedef const TY* pointer; - }; - - template - struct TypeBinding> - { - typedef std::shared_ptr value_type; - typedef std::shared_ptr& reference; - typedef std::shared_ptr* pointer; - }; - - - -}} // namespace lib -#endif diff --git a/src/lib/itertools.hpp b/src/lib/itertools.hpp index dcd37c300..e95fdd654 100644 --- a/src/lib/itertools.hpp +++ b/src/lib/itertools.hpp @@ -42,12 +42,24 @@ ** The FilterIter template can be used to build a filter into a pipeline, ** as it forwards only those elements from its source iterator, which pass ** the predicate evaluation. Anything acceptable as ctor parameter for a - ** tr1::function object can be passed in as predicate, but of course the + ** std::function object can be passed in as predicate, but of course the ** signature must be sensible. Please note, that -- depending on the - ** predicate -- already the ctor or even a simple \c bool test might + ** predicate -- already the ctor or even a simple `bool` test might ** pull and exhaust the source iterator completely, in an attempt ** to find the first element passing the predicate test. ** + ** \par extensible Filter + ** Based on the FilterIter, this facility allows to elaborate the filter + ** function while in the middle of iteration. The new augmented filter + ** will be in effect starting with the current element, which might even + ** be filtered away now due to a more restrictive condition. However, + ** since this is still an iterator, any "past" elements are already + ** extracted and gone and can thus not be subject to changed filtering. + ** The ExtensibleFilterIter template provides several _builder functions_ + ** to elaborate the initial filter condition, like adding conjunctive or + ** disjunctive clauses, flip the filter's meaning or even replace it + ** altogether by a completely different filter function. + ** ** \par processing Iterator ** the TransformIter template can be used as processing (or transforming) ** step within the pipeline. It is created with a functor, which, when @@ -55,11 +67,9 @@ ** source iterator. The signature of the functor must match the ** desired value (output) type. ** - ** @todo some more building blocks are planned, see Ticket #347 - ** ** @see iter-adapter.hpp ** @see itertools-test.cpp - ** @see contents-query.hpp + ** @see event-log.hpp */ @@ -311,7 +321,7 @@ namespace lib { : Raw{forward(source)} , predicate_(prediDef) // induces a signature check , cached_(false) // not yet cached - , isOK_() // some value + , isOK_(false) // not yet relevant { } template @@ -319,7 +329,7 @@ namespace lib { : Raw{source} , predicate_(prediDef) , cached_(false) - , isOK_() + , isOK_(false) { } }; @@ -373,7 +383,7 @@ namespace lib { filterIterator (IT&& src, PRED filterPredicate) { using SrcIT = typename std::remove_reference::type; - return FilterIter{forward(src), filterPredicate}; + return FilterIter{forward(src), filterPredicate}; } @@ -393,7 +403,7 @@ namespace lib { * for the added clause. * @warning the addition of disjunctive and negated clauses might * actually weaken the filter condition. Yet still there is - * \em no reset of the source iterator, i.e. we don't + * _no reset of the source iterator,_ i.e. we don't * re-evaluate from start, but just from current head. * Which means we might miss elements in the already consumed * part of the source sequence, which theoretically would @@ -565,6 +575,102 @@ namespace lib { + /** + * Implementation of a _singleton value_ holder, + * which discards the contained value once "iterated" + */ + template + class SingleValCore + { + typedef wrapper::ItemWrapper Item; + + Item theValue_; + + public: + SingleValCore() { } ///< passive and empty + + SingleValCore (VAL&& something) + : theValue_{forward (something)} + { } + + Item const& + pipe () const + { + return theValue_; + } + + void + advance () + { + theValue_.reset(); + } + + bool + evaluate () const + { + return theValue_.isValid(); + } + + typedef typename std::remove_reference::type * pointer; + typedef typename std::remove_reference::type & reference; + typedef typename std::remove_reference::type value_type; + }; + + + /** + * Pseudo-Iterator to yield just a single value. + * When incremented, the value is destroyed and + * the Iterator transitions to _exhausted state_. + * @remark as such might look nonsensical, but proves + * useful when a function yields an iterator, while + * producing an explicit value in some special case. + * @tparam VAL anything, value or reference to store + */ + template + class SingleValIter + : public IterTool> + { + using _ValHolder = SingleValCore; + using _IteratorImpl = IterTool<_ValHolder> ; + + public: + SingleValIter () + : _IteratorImpl{_ValHolder{}} + { } + + SingleValIter (VAL&& something) + : _IteratorImpl{_ValHolder{forward(something)}} + { } + + ENABLE_USE_IN_STD_RANGE_FOR_LOOPS (SingleValIter) + }; + + + + /** Build a SingleValIter: convenience free function shortcut, + * to pick up just any value and wrap it as Lumiera Forward Iterator. + * @return Iterator to yield the value once + * @warning be sure to understand that we literally pick up and wrap anything + * provided as argument. If you pass a reference, we wrap a reference. + * If you want to wrap a copy, you have to do the copy yourself inline + */ + template + inline auto + singleValIterator (VAL&& something) + { + return SingleValIter{forward(something)}; + } + + template + inline auto + singleValIterator (VAL const& ref) + { + return SingleValIter{ref}; + } + + + + /** * Implementation of custom processing logic. @@ -643,8 +749,8 @@ namespace lib { /** * Iterator tool treating pulled data by a custom transformation (function) - * @param IT source iterator - * @param VAL result (output) type + * @tparam IT source iterator + * @tparam VAL result (output) type */ template class TransformIter @@ -676,7 +782,7 @@ namespace lib { /** Build a TransformIter: convenience free function shortcut, * picking up the involved types automatically. - * @param processingFunc to be invoked for each source element + * @tparam processingFunc to be invoked for each source element * @return Iterator processing the source feed */ template @@ -693,7 +799,7 @@ namespace lib { { using SrcIT = typename std::remove_reference::type; using OutVal = typename lib::meta::_Fun::Ret; - return TransformIter{forward(src), processingFunc}; + return TransformIter{forward(src), processingFunc}; } @@ -751,7 +857,7 @@ namespace lib { { using SrcIT = typename std::remove_reference::type; using Val = typename SrcIT::value_type; - return filterIterator (forward(source), SkipRepetition() ); + return filterIterator (forward(source), SkipRepetition() ); } diff --git a/src/lib/lifecycle.cpp b/src/lib/lifecycle.cpp index 1ac3f07ca..0a05d5eeb 100644 --- a/src/lib/lifecycle.cpp +++ b/src/lib/lifecycle.cpp @@ -40,15 +40,20 @@ namespace lumiera { // ==== implementation Lifecycle Registry ======= /** @remark since the LifecycleRegistry is used to implement - * the most basic initialisation, we need to ensure it is fully - * initialised and gets up on demand as early as possible. */ + * the most basic initialisation, we need to ensure it is fully initialised + * as early as possible. Especially we can not use lib::Depend for that purpose, + * since the latter needs the LifecycleRegistry to pull up the NoBug-Library. + * More specifically, if we `#include "lib/depend.hpp"`, on static initialisation + * the handle planted within that include would invoke `DependencyFactory`, + * which is defined later in the same header and thus not yet initialised. + */ LifecycleRegistry& LifecycleRegistry::instance() { static LifecycleRegistry theRegistry; return theRegistry; // Meyer's singleton } - + // ==== implementation LifecycleHook class ======= @@ -102,7 +107,7 @@ extern "C" { /* ==== implementation C interface for lifecycle hooks ======= */ - void + void lumiera_LifecycleHook_add (const char* eventLabel, void callbackFun(void)) { lumiera::LifecycleHook (eventLabel, callbackFun); diff --git a/src/lib/lifecycleregistry.hpp b/src/lib/lifecycleregistry.hpp index 50f8947c3..5b9be4a10 100644 --- a/src/lib/lifecycleregistry.hpp +++ b/src/lib/lifecycleregistry.hpp @@ -40,8 +40,8 @@ #include "lib/util.hpp" +#include "lib/nocopy.hpp" -#include #include #include #include @@ -50,7 +50,6 @@ namespace lumiera { - using boost::noncopyable; using util::contains; using std::function; using std::string; @@ -64,7 +63,7 @@ namespace lumiera { * to implement the lumiera lifecycle (init, shutdown) hooks. */ class LifecycleRegistry - : private noncopyable + : util::NonCopyable { public: typedef void (*Hook)(void); diff --git a/src/lib/linked-elements.hpp b/src/lib/linked-elements.hpp index 0e654c65e..fbf8f6a64 100644 --- a/src/lib/linked-elements.hpp +++ b/src/lib/linked-elements.hpp @@ -61,10 +61,10 @@ #include "lib/error.hpp" +#include "lib/nocopy.hpp" #include "lib/iter-adapter.hpp" #include "lib/util.hpp" -#include #include @@ -72,7 +72,7 @@ namespace lib { namespace error = lumiera::error; - using error::LUMIERA_ERROR_INDEX_BOUNDS; + using error::LERR_(INDEX_BOUNDS); using util::unConst; @@ -315,7 +315,7 @@ namespace lib { , class ALO = linked_elements::OwningHeapAllocated > class LinkedElements - : boost::noncopyable + : util::NonCopyable , ALO { N* head_; @@ -495,7 +495,7 @@ namespace lib { if (!p || index) throw error::Logic ("Attempt to access element beyond the end of LinkedElements list" - , LUMIERA_ERROR_INDEX_BOUNDS); + , LERR_(INDEX_BOUNDS)); else return *p; } @@ -537,6 +537,33 @@ namespace lib { : node(p) { } + /* ==== internal callback API for the iterator ==== */ + + /** Iteration-logic: switch to next position + * @warning assuming the given node pointer belongs to this collection. + * actually this is never checked; also the given node might + * have been deallocated in the meantime. + */ + void + iterNext() + { + node = node->next; + } + + /** Iteration-logic: detect iteration end. */ + bool + checkPoint() const + { + return bool(node); + } + + N& + yield() const + { + REQUIRE (node); + return * unConst(this)->node; + } + friend bool operator== (IterationState const& il, IterationState const& ir) { @@ -556,38 +583,6 @@ namespace lib { - private: /* ==== internal callback API for the iterator ==== */ - - /** Iteration-logic: switch to next position - * @warning assuming the given node pointer belongs to this collection. - * actually this is never checked; also the given node might - * have been deallocated in the meantime. - */ - friend void - iterNext (IterationState & pos) - { - pos.node = pos.node->next; - } - - friend void - iterNext (IterationState const& pos) - { - pos.node = pos.node->next; - } - - /** Iteration-logic: detect iteration end. */ - friend bool - checkPoint (IterationState const& pos) - { - return bool(pos.node); - } - - friend N& - yield (IterationState const& pos) - { - REQUIRE (pos.node); - return * unConst(pos).node; - } }; diff --git a/src/lib/meta/duck-detector.hpp b/src/lib/meta/duck-detector.hpp index 6e71de863..8b9157be1 100644 --- a/src/lib/meta/duck-detector.hpp +++ b/src/lib/meta/duck-detector.hpp @@ -33,15 +33,16 @@ ** to enter a given API function, provided this object supports some specific operations. ** ** While C++ certainly isn't a dynamic language and does not provide any kind of run time introspection, - ** doing such check-and branch at compile time allows even to combine such a flexible approach with - ** static type safety, which is compelling. (The downside is the danger of code bloat, as is with all - ** template based techniques). + ** doing such check-and branch at compile time allows to combine flexibility as known from dynamic + ** languages with static type safety, which is compelling. We can generate similar implementation + ** for types not further related by inheritance. Building on this, we're able to emulate some + ** of the features enabled by type classes (or "concepts"). ** ** # how the implementation works ** ** Most of these trait templates rely on a creative use of function overloading. The C++ standard - ** requires the compiler silently to drop any candidate of overload resolution which has - ** gotten an invalid function signature as a result of instantiating a template (type). This allows + ** requires the compiler _silently to drop_ any candidate of overload resolution which has gotten + ** an invalid function signature as a result of instantiating a template (type). This rule allows ** us to set up kind of a "honey pot" for the compiler: we present two overloaded candidate functions ** with a different return type; by investigating the resulting return type we're able to figure ** out the overload actually picked by the compiler. @@ -61,7 +62,7 @@ ** to determine if a member function of a type in question has the desired signature. ** ** All these detection building blocks are written such as to provide a bool member `::value`, - ** which is in accordance to the conventions of boost metaprogramming. I.e. you can immediately + ** which is in accordance to the conventions of C++11 metaprogramming. I.e. you can immediately ** use them within `std::enable_if` ** ** # some pitfalls to consider @@ -70,18 +71,30 @@ ** Effectively this means that an error in the test expression might go unnoticed; ** you'd be better off explicitly checking the detection result by an unit test. ** - ** There are several typical problems to care about + ** There are several *typical problems* to care about + ** - none of these tests can detect any private members + ** - the name-only detectors will fail if the name is ambiguous ** - a member can be both a variable or a function of that name ** - function signatures need to match precisely, including const modifiers ** - the generated metafunction (template) uses a type parameter 'TY', which could ** shadow or conflict with an type parameter in the enclosing scope + ** - some of the detectors _require a complete type_ to work properly. They create a + ** pointer-to-member or invoke `sizeof()`. In regular code, doing such on an incomplete + ** type would provoke a _compilation failure_ -- however, here this code gets evaluated + ** in a SFINAE context, which means, it will fail silently and thus produce a wrong + ** detection result. This can be quite *insidious* when relying on the proper detection + ** to pick the right implementation/specialisation; especially when instantiating + ** _mutually dependent_ templates, the distinction between "complete" and "incomplete" + ** can be rather arbitrary while in the process of instantiation. ** - the member and function checks rely on member pointers, which generally refer to ** the explicit static type. These checks won't see any inherited members / functions. ** - obviously, all those checks are never able to detect anything depending on runtime ** types or RTTI ** ** @see util-foreach.hpp usage example + ** @see iter-tree-explorer.hpp (example: is_StateCore) ** @see duck-detector-test.cpp + ** @see duck-detector-extension-test.cpp ** */ @@ -102,6 +115,7 @@ * with the given name. To answer this question, instantiate * resulting HasNested_XXX template with the type in question * and check the static bool value field. + * @warning none of these checks can not detect private members */ #define META_DETECT_NESTED(_TYPE_) \ template \ @@ -123,22 +137,25 @@ * Defines a metafunction (template), allowing to detect * the presence of a member with the given name within * a type in question. + * @note this check will likely fail if the name is ambiguous. + * @warning none of these checks can not detect private members */ #define META_DETECT_MEMBER(_NAME_) \ template \ class HasMember_##_NAME_ \ { \ - template \ - struct Probe \ - { }; \ - \ - template \ - static Yes_t check(Probe * ); \ - template \ - static No_t check(...); \ - \ - public: \ - static const bool value = (sizeof(Yes_t)==sizeof(check(0))); \ + template \ + struct Probe \ + { }; \ + \ + template \ + static Yes_t check(Probe * ); \ + template \ + static No_t check(...); \ + \ + public: \ + static const bool value = (sizeof(Yes_t)==sizeof(check(0))); \ }; @@ -146,8 +163,9 @@ /** Detector for a specific member function. * Defines a metafunction (template), allowing to detect * the presence of a member function with the specific - * signature, as defined by the parameters. Note this - * check will probably fail if there are overloads + * signature, as defined by the parameters. + * @note this check is not sensible to overloads, + * due to the explicitly given argument types */ #define META_DETECT_FUNCTION(_RET_TYPE_,_FUN_NAME_,_ARGS_) \ template \ @@ -167,6 +185,94 @@ }; +/** Detector for a member function with the given name. + * Defines a metafunction (template), allowing to detect + * the presence of a member function with a specific name, + * but without imposing any additional constraints on arguments + * and return type. Yet a non-function member will not trigger this detector. + * @note this check will fail if there are overloads or similar ambiguity + */ +#define META_DETECT_FUNCTION_NAME(_FUN_NAME_) \ + template \ + class HasFunName_##_FUN_NAME_ \ + { \ + template \ + struct Probe; \ + template \ + struct Probe \ + { \ + using Match = void; \ + }; \ + template \ + struct Probe \ + { \ + using Match = void; \ + }; \ + \ + template \ + static Yes_t check(typename Probe::Match * ); \ + template \ + static No_t check(...); \ + \ + public: \ + static const bool value = (sizeof(Yes_t)==sizeof(check(0))); \ + }; + + +/** Detector for an argument-less member function with the given name. + * Defines a metafunction (template), allowing to detect a member function + * taking no arguments, and with arbitrary return type. + * @remarks the presence of overloads is irrelevant, since we explicitly + * from an invocation to that function (within `decltype`) + */ +#define META_DETECT_FUNCTION_ARGLESS(_FUN_) \ + template \ + class HasArglessFun_##_FUN_ \ + { \ + template()._FUN_())>\ + struct Probe \ + { }; \ + \ + template \ + static Yes_t check(Probe * ); \ + template \ + static No_t check(...); \ + \ + public: \ + static const bool value = (sizeof(Yes_t)==sizeof(check(0))); \ + }; + + + +/** Detector for support of a free-function extension point. + * Defines a metafunction (template), allowing to probe if the type + * in question supports a specific extension point function. Typically + * such functions are injected by some type in a way to be picked up by ADL. + * The detection test works by forming an expression to invoke the extension point, + * passing the type given as template parameter as function argument. If this expression + * type checks, the extension point is assumed to be supported. + * @warning beware of implicit type conversions + */ +#define META_DETECT_EXTENSION_POINT(_FUN_) \ + template \ + class HasExtensionPoint_##_FUN_ \ + { \ + template()))>\ + struct Probe \ + { }; \ + \ + template \ + static Yes_t check(Probe * ); \ + template \ + static No_t check(...); \ + \ + public: \ + static const bool value = (sizeof(Yes_t)==sizeof(check(0))); \ + }; + + /** Detector for a dereferentiation operator. Works like member detection */ #define META_DETECT_OPERATOR_DEREF() \ diff --git a/src/lib/meta/function-closure.hpp b/src/lib/meta/function-closure.hpp index 0493b68d6..564c77de2 100644 --- a/src/lib/meta/function-closure.hpp +++ b/src/lib/meta/function-closure.hpp @@ -504,7 +504,7 @@ namespace func{ /** * Closing a function over its arguments. * This is a small usage example or spin-off, - * having almost the same effect than invoking tr1::bind. + * having almost the same effect than invoking `std::bind()`. * The notable difference is that the function arguments for * creating the closure are passed in as one tuple compound. */ @@ -545,7 +545,7 @@ namespace func{ * @tparam VAL type sequence describing the tuple of values * used for closing arguments * @note the construction of this helper template does not verify or - * match types to to the signature. In case of mismatch, you'll get + * match types to the signature. In case of mismatch, you'll get * a compilation failure from `std::bind` (which can be confusing) */ template @@ -620,7 +620,7 @@ namespace func{ * closed arguments; on invocation, only the remaining arguments need to be supplied. */ static LeftReducedFunc - bindFront (SIG& f, Tuple const& arg) + bindFront (SIG const& f, Tuple const& arg) { LeftReplacedArgs params {BuildL(arg)}; return func::Apply::template bind (f, params); @@ -635,7 +635,7 @@ namespace func{ * closed arguments; on invocation, only the remaining arguments need to be supplied. */ static RightReducedFunc - bindBack (SIG& f, Tuple const& arg) + bindBack (SIG const& f, Tuple const& arg) { RightReplacedArgs params {BuildR(arg)}; return func::Apply::template bind (f, params); diff --git a/src/lib/meta/function-erasure.hpp b/src/lib/meta/function-erasure.hpp index 736ce4f80..47764a6ea 100644 --- a/src/lib/meta/function-erasure.hpp +++ b/src/lib/meta/function-erasure.hpp @@ -30,7 +30,7 @@ ** storage for the concrete functor object, which might become problematic when ** dealing with lots of functor objects. ** - ** Especially when dealing with tr1::function objects, all of the type differences + ** Especially when dealing with std::function objects, all of the type differences ** are actually encoded into 3 internal pointers, thus yielding the same size for ** all various types of functors. Building on this observation, we can create an ** common container object to store the varying functors inline, while hiding the diff --git a/src/lib/meta/function.hpp b/src/lib/meta/function.hpp index 1d63fb65f..1075c197c 100644 --- a/src/lib/meta/function.hpp +++ b/src/lib/meta/function.hpp @@ -52,6 +52,7 @@ #define LIB_META_FUNCTION_H #include "lib/meta/typelist.hpp" +#include "lib/meta/util.hpp" #include @@ -66,14 +67,21 @@ namespace meta{ /** * Helper for uniform access to function signature types. * Extract the type information contained in a function or functor type, - * so it can be manipulated by metaprogramming. The embedded typedefs - * allow to pick up the return type, the sequence of argument types - * and the bare function signature type. This template works on + * so it can be manipulated by metaprogramming. This template works on * anything _function like_, irrespective if the parameter is given * as function reference, function pointer, member function pointer, - * functor object, `std::function` or lambda. + * functor object, `std::function` or lambda. The embedded typedefs + * allow to pick up + * - `Ret` : the return type + * - `Args`: the sequence of argument types as type sequence `Types` + * - `Sig` : the bare function signature type + * - `Functor` : corresponding Functor type which can be instantiated or copied. * - * The base case assumes a functor object, i.e. anything with an `operator()`. + * This template can also be used in metaprogramming with `enable_if` to enable + * some definition or specialisation only if a function-like type was detected; thus + * the base case holds no nested type definitions and inherits from std::false_type. + * The primary, catch-all case gets activated whenever on functor objects, i.e. anything + * with an `operator()`. * The following explicit specialisations handle the other cases, which are * not objects, but primitive types (function (member) pointers and references). * @remarks The key trick of this solution is to rely on `decltype` of `operator()` @@ -82,23 +90,44 @@ namespace meta{ * function signature are reflected. But if you bind such a member * pointer into a `std::function`, an additional first parameter * will show up to take the `this` pointer of the class instance. + * @warning this detection scheme fails when the signature of a function call + * operator is ambiguous, which is especially the case + * - when there are several overloads of `operator()` + * - when the function call operator is templated + * - on *generic lambdas* + * All these cases will activate the base (false) case, as if the + * tested subject was not a function at all. Generally speaking, + * it is _not possible_ to probe a generic lambda or templated function, + * unless you bind it beforehand into a std::function with correct signature. * @see FunctionSignature_test * * [kennytm]: http://stackoverflow.com/users/224671/kennytm * [stackoverflow] : http://stackoverflow.com/questions/7943525/is-it-possible-to-figure-out-the-parameter-type-and-return-type-of-a-lambda/7943765#7943765 "answer on stackoverflow" */ - template + template struct _Fun + : std::false_type + { + using Functor = FUN; + }; + + /** Specialisation for function objects and lambdas */ + template + struct _Fun> > : _Fun - { }; + { + using Functor = FUN; + }; /** Specialisation for a bare function signature */ template struct _Fun + : std::true_type { using Ret = RET; using Args = Types; using Sig = RET(ARGS...); + using Functor = std::function; }; /** Specialisation for using a function pointer */ template @@ -134,12 +163,6 @@ namespace meta{ - template - struct is_Functor { static const bool value = false; }; - template - struct is_Functor > { static const bool value = true; }; - - diff --git a/src/lib/meta/trait.hpp b/src/lib/meta/trait.hpp index 707ac46bf..d82364655 100644 --- a/src/lib/meta/trait.hpp +++ b/src/lib/meta/trait.hpp @@ -94,12 +94,15 @@ namespace meta { using std::remove_cv; using std::remove_pointer; using std::remove_reference; + using std::is_pointer; + using std::is_base_of; using std::is_convertible; using std::is_constructible; using std::is_floating_point; using std::is_arithmetic; using std::is_unsigned; using std::is_signed; + using std::is_class; using std::is_same; using std::__not_; using std::__and_; @@ -266,8 +269,19 @@ namespace meta { /** compare unadorned types, disregarding const and references */ template struct is_basically - : is_same ::TypePlain - ,typename Strip::TypePlain> + : is_same ::TypeReferred + ,typename Strip::TypeReferred> + { }; + + /** verify compliance to an interface by subtype check */ + template + struct is_Subclass + : __or_< __and_< is_class + , is_class + , is_base_of + > + , is_same + > { }; /** detect various flavours of string / text data */ @@ -311,8 +325,8 @@ namespace meta { /** when to use custom string conversions for output streams */ template struct use_StringConversion4Stream - : __and_::TypePlain> - ,__not_> + : __and_::TypePlain> + ,__not_> ,__not_> > { }; @@ -435,7 +449,7 @@ namespace meta { META_DETECT_OPERATOR_INC(); public: - enum{ value = std::is_convertible::value + enum{ value = std::is_constructible::value && HasNested_value_type::value && HasOperator_deref::value && HasOperator_inc::value diff --git a/src/lib/meta/tuple-helper.hpp b/src/lib/meta/tuple-helper.hpp index 66337c970..18f92f3ef 100644 --- a/src/lib/meta/tuple-helper.hpp +++ b/src/lib/meta/tuple-helper.hpp @@ -27,6 +27,17 @@ ** some additional manipulations on type sequences, especially to integrate ** with the Tuples provided by the standard library. ** + ** @warning the metaprogramming part of Lumiera to deal with type sequences is in a + ** state of transition, since C++11 now offers direct language support for + ** processing of flexible template parameter sequences ("parameter packs"). + ** It is planned to regroup and simplify our homemade type sequence framework + ** to rely on variadic templates and integrate better with std::tuple. + ** It is clear that we will _retain some parts_ of our own framework, + ** since programming with _Loki-style typelists_ is way more obvious + ** and straight forward than handling of template parameter packs, + ** since the latter can only be rebound through pattern matching. + ** @todo transition lib::meta::Types to variadic parameters /////////////////////////////////TICKET #987 + ** ** @see control::CommandDef usage example ** @see TupleHelper_test ** @see typelist.hpp @@ -42,6 +53,7 @@ #include "lib/meta/typelist.hpp" #include "lib/meta/typelist-util.hpp" #include "lib/meta/typeseq-util.hpp" +#include "lib/meta/variadic-helper.hpp" #include "lib/meta/util.hpp" #include @@ -58,67 +70,6 @@ namespace util { // forward declaration namespace lib { namespace meta { - /** - * temporary workaround: - * alternative definition of "type sequence", - * already using variadic template parameters. - * @remarks the problem with our existing type sequence type - * is that it fills the end of each sequence with NullType, - * which was the only way to get a flexible type sequence - * prior to C++11. Unfortunately these trailing NullType - * entries do not play well with other variadic defs. - * @deprecated when we switch our primary type sequence type - * to variadic parameters, this type will be superfluous. - */ - template - struct TySeq - { - using Seq = TySeq; - using List = typename Types::List; - }; - - - /** - * temporary workaround: additional specialisation for the template - * `Prepend` to work also with the (alternative) variadic TySeq. - * @see typeseq-util.hpp - */ - template - struct Prepend> - { - using Seq = TySeq; - using List = typename Types::List; - }; - - - /** - * temporary workaround: strip trailing NullType entries from a - * type sequence, to make it compatible with new-style variadic - * template definitions. - * @note the result type is a TySec, to keep it apart from our - * legacy (non-variadic) lib::meta::Types - * @deprecated necessary for the transition to variadic sequences - */ - template - struct StripNullType; - - template - struct StripNullType> - { - using TailSeq = typename StripNullType>::Seq; - - using Seq = typename Prepend::Seq; - }; - - template - struct StripNullType> - { - using Seq = TySeq<>; // NOTE: this causes the result to be a TySeq - }; - - - - namespace { // rebinding helper to create std::tuple from a type sequence template @@ -176,9 +127,15 @@ namespace meta { - /** match and rebind the type sequence from a tuple */ + /** temporary workaround: match and rebind the type sequence from a tuple */ template - struct Types> + struct RebindTySeq + { + using Seq = typename Types::Seq; + using List = typename Seq::List; + }; + template + struct RebindTySeq> { using Seq = typename Types::Seq; using List = typename Seq::List; @@ -200,76 +157,27 @@ namespace meta { - /** Hold a sequence of index numbers as template parameters */ - template - struct IndexSeq - { - template - using AppendElm = IndexSeq; - }; - - /** build an `IndexSeq<0, 1, 2, ..., n-1>` */ - template - struct BuildIndexSeq - { - using Ascending = typename BuildIndexSeq::Ascending::template AppendElm; - - template - using FilledWith = typename BuildIndexSeq::template FilledWith::template AppendElm; - }; - - template<> - struct BuildIndexSeq<0> - { - using Ascending = IndexSeq<>; - - template - using FilledWith = IndexSeq<>;; - }; - - - - /** build an index number sequence from a structured reference type */ - template - struct IndexIter; - - /** build an index number sequence from a type sequence */ - template - struct IndexIter> - { - /////TODO as long as Types is not variadic (#987), we need to strip NullType here (instead of just using sizeof...(TYPES) - enum {SIZ = lib::meta::count::List>::value }; - - using Seq = typename BuildIndexSeq::Ascending; - }; - - - - - /** - * Extensible Adapter to construct a distinct tuple - * from some arbitrary source type. This includes the - * possibility to re-map elements or element positions. + * Extensible Adapter to construct a distinct tuple from some arbitrary source type. + * This includes the possibility to re-map elements or element positions. * @tparam TYPES sequence of types to use for the tuple * @tparam _ElmMapper_ a _template_ to extract each * constructor argument from the source value. - * On invocation, we'll pick up the source type from the - * actual ctor argument, and then invoke this helper template - * iteratively for each component of the tuple, with arguments + * On invocation, we'll pick up the source type from the actual ctor argument, + * and then invoke this helper template iteratively for each component of the + * tuple, passing as template arguments * - the source type, as picked up from the constructor * - the target tuple type, i.e. `Tuple` * - the actual index position of the tuple element * to be initialised through this concrete instantiation. - * @remarks this design has several extension points. Pretty much - * any conceivable initialisation logic can be embodied in the - * `_ElmMapper_` template. The sole requirement is that the - * concrete instance is _assignable_ by the source type and - * _convertible_ to the individual member type of the target - * tuple it is invoked for. Moreover, it is possible to build - * a generic _element extractor_, which will be specialised - * on base of the source type accepted. See \ref ExtractArg + * @remarks this design has several extension points. Pretty much any conceivable + * initialisation logic can be embodied in the `_ElmMapper_` template. The sole + * requirement is that the concrete instance is _assignable_ by the source type + * and _convertible_ to the individual member type of the target tuple it is + * invoked for. Moreover, it is possible to build a generic _element extractor_, + * which will be specialised on base of the source type accepted. + * @see ExtractArg */ template< typename TYPES , template class _ElmMapper_ @@ -277,11 +185,12 @@ namespace meta { struct TupleConstructor : Tuple { - using TypeIdxIterator = typename IndexIter::Seq; + /** meta-sequence to drive instantiation of the ElmMapper */ + using SequenceIterator = typename BuildIdxIter::Ascending; protected: template - TupleConstructor (SRC values, IndexSeq*) + TupleConstructor (SRC values, IndexSeq) : Tuple (_ElmMapper_, idx>{values}...) { } @@ -289,7 +198,7 @@ namespace meta { public: template TupleConstructor (SRC values) - : TupleConstructor (values, (TypeIdxIterator*)nullptr) + : TupleConstructor (values, SequenceIterator()) { } }; diff --git a/src/lib/meta/tuple-record-init.hpp b/src/lib/meta/tuple-record-init.hpp index 28d0f0fe1..5ee885508 100644 --- a/src/lib/meta/tuple-record-init.hpp +++ b/src/lib/meta/tuple-record-init.hpp @@ -98,7 +98,7 @@ namespace meta { */ template struct GenNodeAccessor - : boost::noncopyable + : util::NonCopyable { template diff --git a/src/lib/meta/typelist.hpp b/src/lib/meta/typelist.hpp index 53474572e..ddb0c6f21 100644 --- a/src/lib/meta/typelist.hpp +++ b/src/lib/meta/typelist.hpp @@ -55,6 +55,17 @@ This code is heavily inspired by ** effectively this is a flavour of functional programming. Just the ** "execution environment" is the compiler, during compilation. ** + ** @warning the metaprogramming part of Lumiera to deal with type sequences is in a + ** state of transition, since C++11 now offers direct language support for + ** processing of flexible template parameter sequences ("parameter packs"). + ** It is planned to regroup and simplify our homemade type sequence framework + ** to rely on variadic templates and integrate better with std::tuple. + ** It is clear that we will _retain some parts_ of our own framework, + ** since programming with _Loki-style typelists_ is way more obvious + ** and straight forward than handling of template parameter packs, + ** since the latter can only be rebound through pattern matching. + ** @todo transition lib::meta::Types to variadic parameters /////////////////////////////////TICKET #987 + ** ** @see lib::visitor::Applicable usage example ** @see control::CommandSignature more elaborate usage example (dissecting a functor signature) ** @see TypeList_test diff --git a/src/lib/meta/typeseq-util.hpp b/src/lib/meta/typeseq-util.hpp index 88f4d9ba1..f6c9effc2 100644 --- a/src/lib/meta/typeseq-util.hpp +++ b/src/lib/meta/typeseq-util.hpp @@ -31,12 +31,23 @@ ** - shifting a type sequence ** - re-generating a type sequence from a typelist. ** + ** @warning the metaprogramming part of Lumiera to deal with type sequences is in a + ** state of transition, since C++11 now offers direct language support for + ** processing of flexible template parameter sequences ("parameter packs"). + ** It is planned to regroup and simplify our homemade type sequence framework + ** to rely on variadic templates and integrate better with std::tuple. + ** It is clear that we will _retain some parts_ of our own framework, + ** since programming with _Loki-style typelists_ is way more obvious + ** and straight forward than handling of template parameter packs, + ** since the latter can only be rebound through pattern matching. + ** @todo transition lib::meta::Types to variadic parameters /////////////////////////////////TICKET #987 + ** ** @see typeseq-manip-test.cpp ** @see typelist.hpp ** @see typelist-util.hpp + ** @see tuple-helper.hpp ** @see function.hpp ** @see generator.hpp - ** @see tuple.hpp ** */ diff --git a/src/lib/meta/util.hpp b/src/lib/meta/util.hpp index 3dd474a51..5df0dad53 100644 --- a/src/lib/meta/util.hpp +++ b/src/lib/meta/util.hpp @@ -25,7 +25,7 @@ ** Simple and lightweight helpers for metaprogramming and type detection. ** This header is a collection of very basic type detection and metaprogramming utilities. ** @warning indirectly, this header gets included into the majority of compilation units. - ** Avoid anything here which increases compilation times or adds much debugging info. + ** Avoid anything here which increases compilation times or adds much debugging info. ** ** @see MetaUtils_test ** @see trait.hpp @@ -173,11 +173,34 @@ namespace meta { template static No_t check(...); - public: + public: static const bool value = (sizeof(Yes_t)==sizeof(check(0))); }; + /** Trait template to detect presence of a simple function call operator + * @note this metafunction fails to detect an overloaded or templated `operator()` + */ + template + class has_FunctionOperator + { + template + struct Probe + { }; + + template + static Yes_t check(Probe * ); + template + static No_t check(...); + + public: + static const bool value = (sizeof(Yes_t)==sizeof(check(0))); + }; + + + + @@ -240,7 +263,7 @@ namespace meta { * do want the pointer itself (and not a pointer to the pointer). We then * pass the "object" as so called "glvalue" to the `typeid()` function, * so to get the evaluation of RTTI, when applicable. - * @warning this function does string transformations behind the scenes, + * @warning this function does string transformations behind the scene, * and thus should not be used in performance critical context. Moreover, * the returned type string is not necessarily exact and re-parsable. */ diff --git a/src/lib/meta/value-type-binding.hpp b/src/lib/meta/value-type-binding.hpp new file mode 100644 index 000000000..ce5f4b12a --- /dev/null +++ b/src/lib/meta/value-type-binding.hpp @@ -0,0 +1,150 @@ +/* + VALUE-TYPE-BINDING.hpp - control type variations for custom containers + + Copyright (C) Lumiera.org + 2010, Hermann Vosseler + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file value-type-binding.hpp + ** Type re-binding helper template for custom containers and adapters. + ** This header defines a trait template which is used by the Iterator + ** adapters and similar custom containers to figure out the value-, + ** pointer- and reference types when wrapping iterators or containers. + ** + ** When writing a generic container or adapter, there is typically a point + ** where you'll need some variation of the payload type: you may want to expose + ** a reference, or you might need a pointer to the type, to implement a forwarding + ** `operator->()`. On a technical level, this turns out surprisingly tricky, since + ** we often don't know the exact "incantation" of the payload type and might thus + ** end up forming a pointer to a rvalue reference or likewise illegal constructs. + ** + ** Within the STL, there is a convention to provide nested typedefs to indicate + ** type variations in relation to the basic payload type of the container. We + ** follow this convention and support especially the + ** - `value_type` + ** - a simple (LValue) reference to the payload + ** - a pointer at the payload. + ** + ** A custom container should likewise provide such type definitions, and the + ** type rebinding helper template defined in this header makes it easy to + ** provide such nested type definitions in a flexible way. This usage also + ** creates an *Extension Point*: when some payload type requires special + ** treatment, an explicit specialisation to this rebinding trait may be + ** injected alongside with the definition of the payload type. + ** + ** @see iter-adapter.hpp + ** @see scope-path.hpp usage example (explicit specialisation) + */ + + +#ifndef LIB_META_VALUE_TYPE_BINDING_H +#define LIB_META_VALUE_TYPE_BINDING_H + + +#include "lib/error.hpp" +#include "lib/meta/trait.hpp" + + + + +namespace lib { +namespace meta { + + namespace { // Helper trait to detect nested value_type binding definitions + + template + class has_nested_ValueTypeBindings + { + template + struct Probe + { }; + + template + static Yes_t check(Probe * ); + template + static No_t check(...); + + public: + static const bool value = (sizeof(Yes_t)==sizeof(check(0))); + }; + + template + struct use_ValueTypebindings + : __and_ + ,__not_ + > + > + { }; + } + + + /** + * @internal helper template to pick up nested value type definitions + */ + template + struct ValueTypeBinding + { + typedef TY value_type; + typedef TY& reference; + typedef TY* pointer; + }; + + /** specialisation for classes providing STL style type binding definitions */ + template + struct ValueTypeBinding> > + { + typedef typename TY::value_type value_type; + typedef typename TY::reference reference; + typedef typename TY::pointer pointer; + }; + + + + /** + * Type re-binding helper template for creating nested typedefs + * for use by custom containers and iterator adapters or similar. + * - this trait provides a value-, reference- and pointer type, + * similar to what the STL does. + * - references are stripped, otherwise the base type is passed through + * - _but_ when the base type in turn provides such nested typedefs, + * they are picked up and retrieved as result. + * @note client code might define specialisations + * to handle tricky situations (like e.g. const_reverse_iter) + */ + template + struct TypeBinding + : ValueTypeBinding + { }; + + template + struct TypeBinding + : TypeBinding + { }; + + template + struct TypeBinding + : TypeBinding + { }; + + + +}} // namespace lib::meta +#endif /*LIB_META_VALUE_TYPE_BINDING_H*/ diff --git a/src/lib/meta/variadic-helper.hpp b/src/lib/meta/variadic-helper.hpp new file mode 100644 index 000000000..23bdda67b --- /dev/null +++ b/src/lib/meta/variadic-helper.hpp @@ -0,0 +1,345 @@ +/* + VARIADIC-HELPER.hpp - metaprogramming utilities for parameter- and type sequences + + Copyright (C) Lumiera.org + 2016, Hermann Vosseler + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + + +/** @file variadic-helper.hpp + ** Metaprogramming with type sequences based on variadic template parameters. + ** The type rebinding- and helper templates in this header allow to perform + ** simple sequence manipulations on sequences of template parameters extracted + ** from variadic parameter packs. The goal is to (pre)process flexible argument + ** lists _at compile time,_ driven by template instantiation, allowing to specialise + ** and react specifically on some concrete pattern of argument types. + ** + ** @warning the metaprogramming part of Lumiera to deal with type sequences is in a + ** state of transition, since C++11 now offers direct language support for + ** processing of flexible template parameter sequences ("parameter packs"). + ** It is planned to regroup and simplify our homemade type sequence framework + ** to rely on variadic templates and integrate better with std::tuple. + ** It is clear that we will _retain some parts_ of our own framework, + ** since programming with _Loki-style typelists_ is way more obvious + ** and straight forward than handling of template parameter packs, + ** since the latter can only be rebound through pattern matching. + ** @todo transition lib::meta::Types to variadic parameters /////////////////////////////////TICKET #987 + ** + ** @see control::CommandDef usage example + ** @see TupleHelper_test + ** @see typelist.hpp + ** @see function.hpp + ** @see generator.hpp + ** + */ + + +#ifndef LIB_META_VARIADIC_HELPER_H +#define LIB_META_VARIADIC_HELPER_H + +#include "lib/meta/typelist.hpp" +#include "lib/meta/typelist-util.hpp" +#include "lib/meta/typeseq-util.hpp" +#include "lib/meta/util.hpp" + +namespace lib { +namespace meta { + + /** + * temporary workaround: + * alternative definition of "type sequence", + * already using variadic template parameters. + * @remarks the problem with our existing type sequence type + * is that it fills the end of each sequence with NullType, + * which was the only way to get a flexible type sequence + * prior to C++11. Unfortunately these trailing NullType + * entries do not play well with other variadic defs. + * @deprecated when we switch our primary type sequence type + * to variadic parameters, this type will be obsoleted. + */ + template + struct TySeq + { + using Seq = TySeq; + using List = typename Types::List; + }; + + + /** + * temporary workaround: additional specialisation for the template + * `Prepend` to work also with the (alternative) variadic TySeq. + * @see typeseq-util.hpp + */ + template + struct Prepend> + { + using Seq = TySeq; + using List = typename Types::List; + }; + + + /** + * temporary workaround: strip trailing NullType entries from a + * type sequence, to make it compatible with new-style variadic + * template definitions. + * @note the result type is a TySec, to keep it apart from our + * legacy (non-variadic) lib::meta::Types + * @deprecated necessary for the transition to variadic sequences + */ + template + struct StripNullType; + + template + struct StripNullType> + { + using TailSeq = typename StripNullType>::Seq; + + using Seq = typename Prepend::Seq; + }; + + template + struct StripNullType> + { + using Seq = TySeq<>; // NOTE: this causes the result to be a TySeq + }; + + + + + + +//////////////////////////TICKET #943 : temporary workaround until full C++14 compliance (need constexpr) + template + constexpr inline X const& + max (X const& a, X const& b) + { + return a < b? b : a; + } + + template + constexpr inline X const& + min (X const& a, X const& b) + { + return b < a? b : a; + } +//////////////////////////TICKET #943 : temporary workaround until full C++14 compliance (need constexpr) + + + + + + + /** Hold a sequence of index numbers as template parameters */ + template + struct IndexSeq + { + template + using AppendElm = IndexSeq; + + template + using PrependElm = IndexSeq; + }; + + /** + * build regular sequences of index number + * e.g. `IndexSeq<0, 1, 2, ..., n-1>` + */ + template + struct BuildIndexSeq + { + using Ascending = typename BuildIndexSeq::Ascending::template AppendElm; + using Descending = typename BuildIndexSeq::Descending::template PrependElm; + + template + using OffsetBy = typename BuildIndexSeq::template OffsetBy::template AppendElm; + + template + using FilledWith = typename BuildIndexSeq::template FilledWith::template AppendElm; + + template + using First = typename BuildIndexSeq::Ascending; + + template + using After = typename BuildIndexSeq< (n>c)? n-c : 0>::template OffsetBy; + }; + + template<> + struct BuildIndexSeq<0> + { + using Empty = IndexSeq<>; + + using Ascending = Empty; + using Descending = Empty; + + template + using OffsetBy = Empty; + + template + using FilledWith = Empty; + + template + using First = Empty; + + template + using After = Empty; + }; + + + + /** + * build a sequence of index numbers based on a type sequence + */ + template + struct BuildIdxIter + { + enum {SIZ = sizeof...(TYPES) }; + using Builder = BuildIndexSeq; + + using Ascending = typename Builder::Ascending; + using Descending = typename Builder::Descending; + + template + using OffsetBy = typename Builder::template OffsetBy; + + template + using FilledWith = typename Builder::template FilledWith; + + template + using First = typename Builder::template First; + + template + using After = typename Builder::template After; + }; + + /** build an index number sequence from a type sequence */ + template + struct BuildIdxIter> + { + /////TODO as long as Types is not variadic (#987), we need to strip NullType here (instead of just using sizeof...(TYPES) + enum {SIZ = lib::meta::count::List>::value }; + using Builder = BuildIndexSeq; + + using Ascending = typename Builder::Ascending; + using Descending = typename Builder::Descending; + + template + using OffsetBy = typename Builder::template OffsetBy; + + template + using FilledWith = typename Builder::template FilledWith; + + template + using First = typename Builder::template First; + + template + using After = typename Builder::template After; + }; + + + + + + + /* ==== Manipulation of variadic arguments ==== **/ + + namespace { // Implementation delegate template... + /** + * @internal pick a single argument from a variadic parameter pack + * @tparam i the index number (zero based) of the argument to select + * @warning i must be smaller than the number of arguments available + */ + template + struct SelectVararg + { + template + static auto + get (ARG, ARGS&& ...args) + { + return SelectVararg::get (std::forward (args)...); + } + }; + + template<> + struct SelectVararg<0> + { + template + static auto + get (ARG&& a, ARGS...) + { + return std::forward(a); + } + }; + + /** + * @internal helper to decide if SelectVararg shall be applied. + * When the boolean condition does not hold, then, instead of selecting + * from the argument list, an element of type DEFAULT is created as fallback. + */ + template + struct SelectOrInit + : SelectVararg + { }; + + template + struct SelectOrInit + { + template + static DEFAULT + get (ARGS&&...) + { + return DEFAULT{}; + } + }; + }//(End)Implementation + + + /** + * Helper to single out one argument from a variadic argument pack. + * @tparam idx the index number (zero based) of the argument to select + * @remark typically this function is used "driven" by an likewise variadic index sequence, + * where the index sequence itself is picked up by a pattern match; this usage pattern + * allows arbitrarily to handle some of the arguments of a variable argument list, + * as determined by the index sequence passed in. + */ + template + constexpr inline auto + pickArg (ARGS&&... args) + { + static_assert (idx < sizeof...(args), "insufficient number of arguments"); + + return SelectVararg::get (std::forward (args)...); + } + + /** + * Helper to pick one initialisation argument from a variadic argument pack, + * falling back to a default constructed element of type `DEFAULT` in case of + * insufficient number of variadic arguments. + * @tparam idx the index number (zero based) of the argument to select + * @tparam DEFALUT type of the default element to construct as fallback + */ + template + constexpr inline auto + pickInit (ARGS&&... args) + { + return SelectOrInit<(idx < sizeof...(args)), DEFAULT, idx>::get (std::forward (args)...); + } + + + +}} // namespace lib::meta +#endif /*LIB_META_VARIADIC_HELPER_H*/ diff --git a/src/lib/meta/virtual-copy-support.hpp b/src/lib/meta/virtual-copy-support.hpp index 47720630f..ff3ae4877 100644 --- a/src/lib/meta/virtual-copy-support.hpp +++ b/src/lib/meta/virtual-copy-support.hpp @@ -45,7 +45,7 @@ ** - full copy support ** - copy construction but no assignment ** - only move construction allowed - ** - noncopyable type + ** - non-copyable type ** - we use type traits and a policy template to pick the correct implementation ** for a given data type. Any assignment or copy operations not supported by the ** target type will be replaced by an implementation which raises a runtime error diff --git a/src/lib/nobug-resource-handle-context.hpp b/src/lib/nobug-resource-handle-context.hpp index ac4ccca52..8ad11549c 100644 --- a/src/lib/nobug-resource-handle-context.hpp +++ b/src/lib/nobug-resource-handle-context.hpp @@ -45,9 +45,9 @@ #include "lib/error.hpp" +#include "lib/diagnostic-context.hpp" #include "lib/thread-local.hpp" -#include #include @@ -77,7 +77,7 @@ namespace lib { * Disabled placeholder for the Diagnostic context, not used in release builds. */ class NobugResourceHandleContext - : boost::noncopyable + : util::NonCopyable { typedef nobug_resource_user* Handle; diff --git a/src/lib/nocopy.hpp b/src/lib/nocopy.hpp index e916306a3..3ce888b39 100644 --- a/src/lib/nocopy.hpp +++ b/src/lib/nocopy.hpp @@ -23,45 +23,94 @@ /** @file nocopy.hpp ** Mix-Ins to allow or prohibit various degrees of copying and cloning. - ** @todo 2016 this could be organised way better. Also C++11 offers a way more - ** elegant way of expressing the intention. We could get rid of `boost::noncopyable` - ** The basic idea of using a marker mixin seems very reasonable though. ////////////////////////////TICKET #1084 + ** Whenever a class is conceived as entity with a well-defined "identity", + ** or whenever a service has to manage resources, we consider it good practice + ** to define it by default as "non copyable". This rules out a lot of complexities + ** with mutable state and confusion regarding equality. + ** @remark inspired by [Boost-Noncopyable] + ** + ** [Boost-Noncopyable]: http://www.boost.org/doc/libs/1_55_0/libs/utility/utility.htm#Class_noncopyable */ #ifndef LIB_NOCOPY_H #define LIB_NOCOPY_H -#include - namespace util { /** - * any copy and copy construction prohibited + * Any copy and copy construction prohibited */ - class no_copy - : boost::noncopyable - { }; + class NonCopyable + { + protected: + ~NonCopyable() = default; + NonCopyable() = default; + NonCopyable (NonCopyable const&) = delete; + NonCopyable& operator= (NonCopyable const&) = delete; + }; /** - * classes inheriting from this mixin - * may be created by copy-construction, - * but any copy-assignment is prohibited. - * @note especially this allows returning - * by-value from a builder function, - * while prohibiting any further copy + * Types marked with this mix-in may be moved but not copied */ - class no_copy_by_client + class MoveOnly { - protected: - ~no_copy_by_client() {} - no_copy_by_client() {} - no_copy_by_client (no_copy_by_client const&) {} - no_copy_by_client const& - operator=(no_copy_by_client const&) { return *this; } + protected: + ~MoveOnly() = default; + MoveOnly() = default; + MoveOnly (MoveOnly&&) = default; + MoveOnly (MoveOnly const&) = delete; + MoveOnly& operator= (MoveOnly&&) = delete; + MoveOnly& operator= (MoveOnly const&) = delete; }; + /** + * Types marked with this mix-in may be moved and move-assigned + */ + class MoveAssign + { + protected: + ~MoveAssign() = default; + MoveAssign() = default; + MoveAssign (MoveAssign&&) = default; + MoveAssign (MoveAssign const&) = delete; + MoveAssign& operator= (MoveAssign&&) = default; + MoveAssign& operator= (MoveAssign const&) = delete; + }; + + /** + * Types marked with this mix-in may be created by + * copy-construction (or move construction), + * but may be not reassigned thereafter. + * @remark especially this allows returning + * by-value from a builder function, + * while prohibiting any further copy + */ + class Cloneable + { + protected: + ~Cloneable() = default; + Cloneable() = default; + Cloneable (Cloneable&&) = default; + Cloneable (Cloneable const&) = default; + Cloneable& operator= (Cloneable&&) = delete; + Cloneable& operator= (Cloneable const&) = delete; + }; + + /** + * Not meant to be instantiated in any way. + * Types marked this way are typically used for metaprogramming + * or expose static factory methods to some related sibling + * based on a template parameter or similar configuration. + */ + class NoInstance + { + protected: + NoInstance() = delete; + }; + + } // namespace util #endif /*LIB_NOCOPY_H*/ diff --git a/src/lib/opaque-holder.hpp b/src/lib/opaque-holder.hpp index 72cf16f92..e6badc185 100644 --- a/src/lib/opaque-holder.hpp +++ b/src/lib/opaque-holder.hpp @@ -68,19 +68,22 @@ #include "lib/error.hpp" +#include "lib/nocopy.hpp" #include "lib/access-casted.hpp" #include "lib/meta/util.hpp" #include "lib/util.hpp" -#include -#include -#include #include +#include +#include + namespace lib { namespace error = lumiera::error; + using error::LERR_(BOTTOM_VALUE); + using error::LERR_(WRONG_TYPE); using util::isSameObject; using util::unConst; @@ -135,7 +138,7 @@ namespace lib { return asBase; throw error::Logic ("Unable to convert concrete object to Base interface" - , error::LUMIERA_ERROR_WRONG_TYPE + , LERR_(WRONG_TYPE) ); } }; @@ -145,7 +148,7 @@ namespace lib { * a common interface; use this policy if the intention is * to use OpaqueHolder with a family of similar classes, * \em without requiring all of them to be derived from - * a common base class. (E.g. tr1::function objects). + * a common base class. (E.g. std::function objects). * In this case, the "Base" type will be defined to void* * As a consequence, we loose all type information and * no conversions are possible on re-access. You need @@ -220,7 +223,7 @@ namespace lib { getBase() const { throw error::Invalid("accessing empty holder" - , error::LUMIERA_ERROR_BOTTOM_VALUE); + , LERR_(BOTTOM_VALUE)); } virtual void @@ -453,11 +456,11 @@ namespace lib { if (this->empty()) throw error::Invalid("accessing empty holder" - , error::LUMIERA_ERROR_BOTTOM_VALUE); + ,LERR_(BOTTOM_VALUE)); else throw error::Logic ("Attempt to access OpaqueHolder's contents " "specifying incompatible target type" - , error::LUMIERA_ERROR_WRONG_TYPE + , LERR_(WRONG_TYPE) ); } @@ -594,7 +597,7 @@ namespace lib { , class DEFAULT = BA > class InPlaceBuffer - : boost::noncopyable + : util::NonCopyable { mutable char buf_[siz]; diff --git a/src/lib/optional-ref.hpp b/src/lib/optional-ref.hpp index e4ff6ca15..23e6827c1 100644 --- a/src/lib/optional-ref.hpp +++ b/src/lib/optional-ref.hpp @@ -36,7 +36,7 @@ namespace lib { - using lumiera::error::LUMIERA_ERROR_BOTTOM_VALUE; + using lumiera::error::LERR_(BOTTOM_VALUE); @@ -88,7 +88,7 @@ namespace lib { { if (!isValid()) throw lumiera::error::Logic ("access to this object is (not/yet) enabled" - , LUMIERA_ERROR_BOTTOM_VALUE); + , LERR_(BOTTOM_VALUE)); return *ref_; } diff --git a/src/lib/path-array.hpp b/src/lib/path-array.hpp new file mode 100644 index 000000000..dc1ec8f37 --- /dev/null +++ b/src/lib/path-array.hpp @@ -0,0 +1,605 @@ +/* + PATH-ARRAY.hpp - sequence of path-like component-IDs in fixed storage + + Copyright (C) Lumiera.org + 2017, Hermann Vosseler + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + + +/** @file path-array.hpp + ** Foundation abstraction to implement path-like component sequences. + ** This library class can be used to build a path abstraction for data structure access + ** or some similar topological coordinate system, like e.g. [UI coordinates](\ref ui-coord.hpp) + ** A PathArray is an iterable sequence of literal component IDs, implemented as tuple of lib::Literal + ** held in fixed inline storage with possible heap allocated (and thus unlimited) extension storage. + ** It offers range checks, standard iteration and array-like indexed component access; as a whole + ** it is copyable, while actual components are immutable after construction. PathArray can be + ** bulk initialised from an explicit sequence of literals; it is normalised on construction + ** to trim and fill interspersed missing elements. Iteration and equality comparison are + ** built on top of the normalisation; iteration starts with the first nonempty element. + ** + ** @remark the choice of implementation layout is arbitrary and not based on evidence. + ** A delegate structure with fixed inline storage looked like an interesting programming challenge. + ** Using just a heap based array storage would have been viable likewise. + ** @todo when UICoord is in widespread use, collect performance statistics and revisit this design. + ** + ** @see PathArray_test + ** @see UICoord_test + ** @see gui::interact::UICoord + ** @see view-spec-dsl.hpp + */ + + +#ifndef LIB_PATH_ARRAY_H +#define LIB_PATH_ARRAY_H + +#include "lib/error.hpp" +#include "lib/symbol.hpp" +#include "lib/iter-adapter.hpp" +#include "lib/meta/variadic-helper.hpp" +#include "lib/format-obj.hpp" +#include "lib/util.hpp" + +#include +#include +#include +#include +#include + + +namespace lib { + namespace error = lumiera::error; + + using std::string; + using std::forward; + using lib::Literal; + using util::unConst; + using util::isnil; + + namespace con { // Implementation helper: flexible heap based extension storage.... + + /** + * Heap-allocated extension storage for an immutable sequence of literal strings. + * The size of the allocation is determined and fixed once, at construction time, + * derived from the number of initialisers. The first slot within the allocation + * stores this length. Extension can be _empty_ (default constructed), + * in which case no heap allocation is performed. + */ + class Extension + { + using PStorage = Literal*; + + PStorage storage_; + + + static size_t& + size (PStorage& p) + { + REQUIRE (p); + return reinterpret_cast (p[0]); + } + + /** allocate a copy. + * @note caller has to manage the allocated memory + * @warning call to Literal's ctor deliberately elided + */ + PStorage + newCopy() const + { + size_t siz = 1 + size (unConst(this)->storage_); + const char** alloc = new const char*[siz]; + std::copy (storage_, storage_+siz, alloc); + return reinterpret_cast (alloc); + } + + + public: + ~Extension() + { + if (storage_) + delete[] storage_; + } + + Extension() noexcept + : storage_{nullptr} + { } + + template + explicit + Extension (ELMS&& ...elms) + : storage_{new Literal[1 + sizeof...(ELMS)]} + { + size(storage_) = sizeof...(ELMS); + new(storage_+1) Literal[sizeof...(ELMS)] {forward(elms)...}; + } + + Extension (Extension const& r) + : storage_{r.storage_? r.newCopy() : nullptr} + { } + + Extension (Extension&& rr) noexcept + : storage_{nullptr} + { + if (rr.storage_) + std::swap (storage_, rr.storage_); + } + + Extension& operator= (Extension const& o) + { + if (this != &o) + { + std::unique_ptr cp; + if (o.storage_) + cp.reset (o.newCopy()); + if (storage_) + delete[] storage_; + storage_ = cp.release(); + } + return *this; + } + + Extension& operator= (Extension&& rr) noexcept + { + if (this != &rr) + { + std::swap (storage_, rr.storage_); + } + return *this; + } + + + + operator bool() const { return not empty(); } + bool empty() const { return not storage_;} + + size_t + size() const + { + return storage_? size(unConst(this)->storage_) + : 0; + } + + Literal const& + operator[] (size_t idx) const + { + REQUIRE (storage_ and idx < size()); + return storage_[1+idx]; + } + + size_t + indexOf (Literal const* pos) const + { + REQUIRE (isValid (pos)); + return pos - (storage_+1); + } + + + bool + isValid (Literal const* pos) const + { + return storage_ + and storage_ < pos + and pos < storage_ + (1 + size (unConst(this)->storage_)); + } + + void + resizeTo (size_t cnt) + { + if (cnt > size()) + { // need more storage + if (storage_) + { // copy content to new expanded storage + auto target = new const char* [cnt+1]; + auto pos = std::copy (storage_, storage_+1+size(), target); + for ( ; pos < target+1+cnt; ++pos) + *pos = nullptr; + delete[] storage_; + storage_ = reinterpret_cast (target); + } + else // allocate and init empty + storage_ = new Literal [cnt+1]; + + size (storage_) = cnt; + return; + } + if (not storage_) return; + if (cnt == 0) + { + delete[] storage_; + storage_ = nullptr; + } + else + size(storage_) = cnt; + // excess elements now unreachable + // note: delete[] knows original size + } + }; + }//(End)Implementation helper + + + + using meta::pickArg; + using meta::pickInit; + using meta::IndexSeq; + + /** + * Abstraction for path-like topological coordinates. + * A sequence of Literal strings, with array-like access and + * standard iteration. Implemented as fixed size inline tuple + * with heap allocated unlimited extension space. + * @note contents are normalised on initialisation + * - leading empty elements are filled with Symbol::EMPTY + * - empty elements in the middle are replaced by "*" + * - trailing empty elements and "*" are trimmed + */ + template + class PathArray + { + static_assert (0 < chunk_size, "PathArray chunk_size must be nonempty"); + + using CcP = const char*; + using LiteralArray = std::array; + + LiteralArray elms_; + con::Extension tail_; + + /** + * @internal delegate ctor to place the initialiser arguments appropriately + * @remarks the two index sequences passed by pattern match determine which + * arguments go to the inline array, and which go to heap allocated extension. + * The inline array has fixed size an is thus filled with trailing `NULL` ptrs, + * which is achieved with the help of meta::pickInit(). The con::Extension + * is an embedded smart-ptr, which, when receiving additional tail arguments, + * will place and manage them within a heap allocated array. + */ + template + PathArray (IndexSeq + ,IndexSeq + ,ARGS&& ...args) + : elms_{pickInit (forward(args)...) ...} + , tail_{pickArg (forward(args)...) ...} + { + this->normalise(); + } + + /** + * @internal rebinding helper for building sequences of index numbers, + * to route the initialiser arguments into the corresponding storage + * - the leading (`chunk_size`) arguments go into the LiteralArray inline + * - all the remaining arguments go into heap allocated extension storage + */ + template + struct Split + { + using Prefix = typename meta::BuildIndexSeq::Ascending; + using Rest = typename meta::BuildIdxIter::template After; + }; + + public: + template + explicit + PathArray (ARGS&& ...args) + : PathArray(typename Split::Prefix() + ,typename Split::Rest() + ,forward (args)...) + { } + + PathArray(PathArray&&) = default; + PathArray(PathArray const&) = default; + PathArray(PathArray& o) : PathArray((PathArray const&)o) { } + PathArray& operator= (PathArray const&) = default; + PathArray& operator= (PathArray &&) = default; + ////////////////////////TICKET #963 Forwarding shadows copy operations + + + size_t + size() const + { + return tail_? chunk_size + tail_.size() + : findInlineEnd() - elms_.begin(); + } + + bool + empty() const + { + return not elms_[0]; // normalise() ensures nonnull unless completely empty + } + + /** joins nonempty content, separated by slash */ + operator string() const; + + + /** Array style indexed access. + * @throws error::Invalid on bound violation + * @return reference to the normalised content + * @note returns Symbol::EMPTY for leading empty elements, + * even while iteration will skip such entries. + */ + Literal const& + operator[] (size_t idx) const + { + Literal* elm = unConst(this)->getPosition (idx); + if (not elm) + throw error::Invalid ("Accessing index "+util::toString(idx) + +" on PathArray of size "+ util::toString(size()) + ,error::LUMIERA_ERROR_INDEX_BOUNDS); + return *elm; + } + + /** reverse lookup of actual path content + * @param content reference to actual content _residing within the path_ + * @return index position of the content within the path + * @throws error::Invalid when the given storage location + * is outside the data content storage of this path + */ + size_t + indexOf (Literal const& content) const + { + if (elms_.begin() <= &content and &content < elms_.end()) + return &content - elms_.begin(); + if (tail_.isValid (&content)) + return chunk_size + tail_.indexOf (&content); + + throw error::Invalid ("Referred content "+util::toString(&content) + +" is not located within the storage of PathArray " + +string(*this)); + } + + + protected: /* ==== Iteration control API for IterAdapter ==== */ + + /** Implementation of Iteration-logic: pull next element. */ + friend void + iterNext (const PathArray*, const Literal*& pos) + { + ++pos; + } + + /** Implementation of Iteration-logic: detect iteration end. */ + friend bool + checkPoint (const PathArray* src, const Literal*& pos) + { + REQUIRE (src); + if (pos == src->elms_.end() and src->tail_) + pos = &src->tail_[0]; + else + if (not src->isValid (pos)) + { + pos = nullptr; + return false; + } + ENSURE ( (src->elms_.begin() <= pos and pos < src->elms_.end()) + or src->tail_.isValid(pos)); + return true; + } + + public: + using const_iterator = lib::IterAdapter; + using iterator = const_iterator; + + using value_type = Literal; + using reference = Literal&; + using const_reference = Literal const&; + + /** @remark iteration is defined to begin with real content */ + iterator begin() const { return firstNonempty(); } + iterator end() const { return iterator{}; } + + friend iterator begin(PathArray const& pa) { return pa.begin();} + friend iterator end (PathArray const& pa) { return pa.end(); } + + + + private: /* ===== implementation details ===== */ + + bool + isValid (Literal const* pos) const + { + return pos + and (tail_.isValid(pos) + or (elms_.begin() <= pos and pos < elms_.end() + and *pos)); + } + + iterator + firstNonempty () const + { + iterator startPos{this, elms_.begin()}; + while (startPos && isnil (*startPos)) + ++startPos; + return startPos; + } + + /** + * find _effective end_ of data in the inline array, + * i.e. the position _behind_ the last usable content + */ + Literal const* + findInlineEnd() const + { + Literal const* lastPos = elms_.begin() + chunk_size-1; + Literal const* beforeStart = elms_.begin() - 1; + while (lastPos != beforeStart and not *lastPos) + --lastPos; + return ++lastPos; // at start if empty, else one behind the last + } + + + /* ===== manipulation by UICoord and Builder subclasses ===== */ + protected: + /** + * @internal access content element by index + * @return pointer to storage, `null` if out of bounds + */ + Literal* + getPosition (size_t idx) + { + Literal const* elm =nullptr; + if (idx < chunk_size) + elm = elms_[idx]? &elms_[idx] : nullptr; + else + if (idx-chunk_size < tail_.size()) + elm = &tail_[idx-chunk_size]; + + return const_cast (elm); + } + + /** + * @internal ensure storage for the indicated position exists + * @return pointer to the storage, either existing or reallocated + */ + Literal* + expandPosition (size_t idx) + { + if (chunk_size <= idx and size() <= idx) + tail_.resizeTo(idx+1 - chunk_size); + if (idx < chunk_size) + return elms_.begin() + idx; + + ENSURE (idx-chunk_size < tail_.size()); + return const_cast (&tail_[idx-chunk_size]); + } + + /** @internal force new content into the given entry */ + void + setContent (Literal* pos, const char* val) + { + REQUIRE (pos); + *reinterpret_cast (pos) = val; + } + + void + truncateTo (size_t newSize) + { + if (newSize < chunk_size) + { + Literal* end = elms_.end(); + Literal* pos = getPosition(newSize); + if (pos) + for ( ; pos!=end; ++pos) + setContent (pos, nullptr); + tail_.resizeTo (0); + } + else + if (newSize-chunk_size < tail_.size()) + tail_.resizeTo (newSize - chunk_size); + // otherwise: keep current size, don't grow + } + + /** + * establish the _contract_ of PathArray + * - no null pointer within the valid storage range + * - storage is precisely trimmed at the end + * - missing leading elements are filled with Symbol::EMPTY + * - missing inner elements are interpolated as "*" + */ + void + normalise() + { + if (size() == 0) return; + const char* fill = Symbol::EMPTY; + + Literal* end = elms_.end(); + Literal* pos = elms_.begin(); + for ( ; pos!=end; ++pos) + if (isnil (*pos)) + setContent (pos, fill); + else + if (fill==Symbol::EMPTY) + fill = Symbol::ANY; + + if (tail_) + { + // normalise data in extension storage + pos = getPosition (chunk_size); + end = pos + tail_.size(); + for ( ; pos!=end; ++pos) + if (isnil (*pos)) + setContent (pos, fill); + else + if (fill==Symbol::EMPTY) + fill = Symbol::ANY; + } + + size_t idx = size(); + // trim trailing fill + while (idx and fill == *getPosition (--idx)) + setContent (getPosition(idx), nullptr); + + if (idx >= chunk_size-1) + tail_.resizeTo (idx+1 - chunk_size); + else + tail_.resizeTo (0); + } + }; + + + + + template + inline + PathArray::operator string() const + { + if (this->empty()) return ""; + + string buff; + size_t expectedLen = this->size() * 10; + buff.reserve (expectedLen); // heuristic + for (Literal elm : *this) + buff += elm + "/"; + + // chop off last delimiter + size_t len = buff.length(); + ASSERT (len >= 1); + buff.resize(len-1); + return buff; + } + + + + /** + * Equality comparison of arbitrary PathArray objects + */ + template + bool + operator== (PathArray const& l, PathArray const& r) + { + if (l.size() != r.size()) return false; + + typename PathArray::iterator lp = l.begin(); + typename PathArray::iterator rp = r.begin(); + while (lp and rp) + { + if (*lp != *rp) return false; + ++lp; + ++rp; + } + return isnil(lp) and isnil(rp); + } + + template + bool + operator!= (PathArray const& l, PathArray const& r) + { + return not (l == r); + } + + + +}// namespace lib +#endif /*LIB_PATH_ARRAY_H*/ diff --git a/src/lib/ref-array.hpp b/src/lib/ref-array.hpp index 32084f3a2..5e70c768f 100644 --- a/src/lib/ref-array.hpp +++ b/src/lib/ref-array.hpp @@ -32,7 +32,7 @@ #define LIB_REF_ARRAY_H -#include +#include "lib/nocopy.hpp" namespace lib { @@ -46,7 +46,7 @@ namespace lib { */ template class RefArray - : boost::noncopyable + : util::NonCopyable { public: virtual ~RefArray() {} ///< this is an interface diff --git a/src/lib/result.hpp b/src/lib/result.hpp index 38b3ba123..7060c39ad 100644 --- a/src/lib/result.hpp +++ b/src/lib/result.hpp @@ -24,15 +24,13 @@ /** @file result.hpp ** Intermediary value object to represent the result of an operation. ** This operation might have produced a value result or failed with an exception. - ** Typically, the Result token is used \em inline -- immediately either invoking + ** Typically, the Result token is used _inline_ -- immediately either invoking ** one of the member function or employing the built-in result type conversion. ** It will be copyable iff the result value is copyable. There is an implicit ** valid or failure state, which can be tested. Any attempt to get the value - ** of an invalid result token will cause in an exception to be thrown. + ** of an invalid result token will cause an exception to be thrown. ** - ** @todo WIP and rather brainstorming as of 2/10 - ** - ** @see backend::ThreadJob usage example + ** @see backend::ThreadJoinable usage example */ @@ -44,6 +42,7 @@ #include "lib/wrapper.hpp" #include "lib/util.hpp" +#include #include @@ -58,7 +57,7 @@ namespace lib { /** * Optional Result value or status of some operation. - * It can be created for passing a result produced by the operation, or the + * It can be created for passing a result produced by the operation, or the * failure to do so. The value can be retrieved by implicit or explicit conversion. * @throw error::State on any attempt to access the value in case of failure * @warning this class has a lot of implicit conversions; @@ -73,19 +72,19 @@ namespace lib { public: /** mark an invalid/failed result */ - Result () - : failureLog_("no result") + Result (string const& failureReason ="no result") + : failureLog_{failureReason} { } /** failed result, with reason given.*/ Result (lumiera::Error const& reason) - : failureLog_(reason.what()) - { } + : failureLog_{reason.what()} + { } /** standard case: valid result */ - Result (RES const& value) - : failureLog_("") - , value_(value) + Result (RES&& value) + : failureLog_{} + , value_{std::forward (value)} { } @@ -99,7 +98,7 @@ namespace lib { void maybeThrow() const { - if (!isValid()) + if (not isValid()) throw error::State (failureLog_, lumiera_error_peek()); } @@ -131,12 +130,12 @@ namespace lib { public: /** mark either failure (default) or success */ Result (bool success =false) - : failureLog_(success? "": "operation failed") + : failureLog_{success? "": "operation failed"} { } /** failed result, with reason given.*/ Result (lumiera::Error const& reason) - : failureLog_(reason.what()) + : failureLog_{reason.what()} { } @@ -150,7 +149,7 @@ namespace lib { void maybeThrow() const { - if (!isValid()) + if (not isValid()) throw error::State (failureLog_, lumiera_error_peek()); } }; diff --git a/src/lib/scoped-collection.hpp b/src/lib/scoped-collection.hpp index 3cfc985a6..5623cb836 100644 --- a/src/lib/scoped-collection.hpp +++ b/src/lib/scoped-collection.hpp @@ -21,7 +21,7 @@ */ /** @file scoped-collection.hpp - ** Managing a collection of noncopyable polymorphic objects in compact storage. + ** Managing a collection of non-copyable polymorphic objects in compact storage. ** This helper supports the frequently encountered situation where a service ** implementation internally manages a collection of implementation related ** sub-components with reference semantics. Typically, those objects are @@ -70,22 +70,23 @@ #include "lib/error.hpp" +#include "lib/nocopy.hpp" +#include "lib/meta/trait.hpp" #include "lib/iter-adapter.hpp" -#include #include namespace lib { namespace error = lumiera::error; - using error::LUMIERA_ERROR_CAPACITY; - using error::LUMIERA_ERROR_INDEX_BOUNDS; + using error::LERR_(CAPACITY); + using error::LERR_(INDEX_BOUNDS); /** - * A fixed collection of noncopyable polymorphic objects. + * A fixed collection of non-copyable polymorphic objects. * * All child objects reside in a common chunk of storage * and are owned and managed by this collection holder. @@ -98,7 +99,7 @@ namespace lib { , size_t siz = sizeof(I) > class ScopedCollection - : boost::noncopyable + : util::NonCopyable { public: @@ -109,7 +110,7 @@ namespace lib { * @note doesn't manage the Child */ class ElementHolder - : boost::noncopyable + : util::NonCopyable { mutable char buf_[siz]; @@ -136,8 +137,7 @@ namespace lib { TY& create (ARGS&& ...args) { - static_assert ( (std::is_same::value - ||std::is_base_of::value) + static_assert ( meta::is_Subclass() && sizeof(TY) <= siz, "ElementHolder buffer to small"); @@ -329,7 +329,7 @@ namespace lib { return elements_[index].accessObj(); throw error::Logic ("Attempt to access not (yet) existing object in ScopedCollection" - , LUMIERA_ERROR_INDEX_BOUNDS); + , LERR_(INDEX_BOUNDS)); } @@ -366,7 +366,7 @@ namespace lib { { if (level_ >= capacity_) throw error::State ("ScopedCollection exceeding the initially defined capacity" - , LUMIERA_ERROR_CAPACITY); + , LERR_(CAPACITY)); } @@ -462,7 +462,7 @@ namespace lib { { IT iter_; - typedef typename iter::TypeBinding::value_type ElementType; + typedef typename meta::TypeBinding::value_type ElementType; public: PullFrom (IT source) diff --git a/src/lib/scoped-ptrvect.hpp b/src/lib/scoped-ptrvect.hpp index ce3ab1abc..f14267f93 100644 --- a/src/lib/scoped-ptrvect.hpp +++ b/src/lib/scoped-ptrvect.hpp @@ -49,12 +49,12 @@ #include "include/logging.h" #include "lib/iter-adapter-ptr-deref.hpp" +#include "lib/nocopy.hpp" #include "lib/error.hpp" #include "lib/util.hpp" #include #include -#include namespace lib { @@ -70,7 +70,7 @@ namespace lib { template class ScopedPtrVect : std::vector, - boost::noncopyable + util::NonCopyable { typedef std::vector _Vec; typedef typename _Vec::iterator VIter; diff --git a/src/lib/searchpath.hpp b/src/lib/searchpath.hpp index 822ae1b1e..617552b63 100644 --- a/src/lib/searchpath.hpp +++ b/src/lib/searchpath.hpp @@ -34,8 +34,8 @@ #define COMMON_SEARCHPATH_H #include "lib/error.hpp" +#include "lib/nocopy.hpp" -#include #include #include #include @@ -55,7 +55,7 @@ namespace lib { namespace fsys = boost::filesystem; LUMIERA_ERROR_DECLARE (FILE_NOT_DIRECTORY); ///< path element points at a file instead of a directory - using error::LUMIERA_ERROR_ITER_EXHAUST; + using error::LERR_(ITER_EXHAUST); /** retrieve the location of the executable */ @@ -74,7 +74,7 @@ namespace lib { * @note #next picks the current component and advances the iteration. */ class SearchPathSplitter - : boost::noncopyable + : util::NonCopyable { string pathSpec_; sregex_iterator pos_, @@ -102,7 +102,7 @@ namespace lib { { if (!isValid()) throw error::Logic ("Search path exhausted." - ,LUMIERA_ERROR_ITER_EXHAUST); + ,LERR_(ITER_EXHAUST)); string currentPathElement = pos_->str(); ++pos_; diff --git a/src/lib/singleton-ref.hpp b/src/lib/singleton-ref.hpp deleted file mode 100644 index 35fba715c..000000000 --- a/src/lib/singleton-ref.hpp +++ /dev/null @@ -1,134 +0,0 @@ -/* - SINGLETON-REF.hpp - helper template providing singleton-like access for implementation code - - Copyright (C) Lumiera.org - 2008, Hermann Vosseler - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of - the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - -*/ - -/** @file singleton-ref.hpp - ** Helper for singleton-kind access without managing object creation and lifecycle. - ** A typical usage scenario is when implementing C Language Interfaces without any - ** canonical access to some "this" pointer. In this case - ** - an instance of the \c Access policy class is created "somewhere" (usually as - ** global variable). Calls from within the C code would enter through this accessor. - ** - when the actual service implementation class comes up, it creates an SingletonRef - ** instance and thereby wires the (already existing) accessor up with \c *this - ** - when the service goes down, access is closed automatically and reliably. - ** Client code (from within C) should check the accessor (by \c bool check) - ** prior to any access, because accessing a closed connection will throw - ** (that's unfortunate, but at least throwing is better than segfaulting) - ** - ** @see gui::NotificationService usage example - */ - - -#ifndef LIB_SINGLETON_REF_H -#define LIB_SINGLETON_REF_H - - -#include "lib/error.hpp" - -#include - - -namespace lib { - - namespace singleton { - - /***********************************//** - * Detail/Policy class specifying - * how the SingletonRef can be accessed - */ - template - class AccessAsReference - : boost::noncopyable - { - TY* obj_; - - typedef AccessAsReference _ThisType; - - public: - void - open (TY* instance) - { - ASSERT (!obj_, "Lifecycle error"); - obj_ = instance; - } - - void - close () - { - ASSERT (obj_, "Lifecycle error"); - obj_ = nullptr; - } - - TY* - operator-> () const - { - if (!isValid()) - throw lumiera::error::State("Target currently not available."); - return obj_; - } - - explicit operator bool() const { return obj_; } - bool isValid() const { return obj_; } - }; - - } // namespace Singleton - - - - - /*********************************************************//** - * Helper template providing singleton access without managing - * object creation and lifecycle. Just the access to the - * implementation instance is handled: on creation, access is - * enabled, and disabled on destruction. Client code is assumed - * to invoke through the Access (template template param), - * handed in as ctor parameter. - * @param TY the actual type to be made accessible - * @param B a base class to inherit from; defaults to noncopyable - * @param Accessor how to implement the static instance access - */ - template< class TY - , class B = boost::noncopyable - , template class Access = singleton::AccessAsReference - > - struct SingletonRef - : public B - { - - typedef Access Accessor; - Accessor& accessor_; - - SingletonRef(TY * instance, Accessor& acc) - : accessor_(acc) - { - accessor_.open (instance); - } - - ~SingletonRef() - { - accessor_.close (); - } - }; - - - -} // namespace lib -#endif diff --git a/src/lib/symbol-impl.cpp b/src/lib/symbol-impl.cpp index c616ce7c2..a8c7676b9 100644 --- a/src/lib/symbol-impl.cpp +++ b/src/lib/symbol-impl.cpp @@ -82,6 +82,8 @@ namespace lib { /* == predefined marker Symbols == */ + Symbol Symbol::ANY = "*"; + Symbol Symbol::EMPTY = ""; Symbol Symbol::BOTTOM = "⟂"; Symbol Symbol::FAILURE = "↯"; diff --git a/src/lib/symbol-table.hpp b/src/lib/symbol-table.hpp index 39ffb3f36..e252ffb68 100644 --- a/src/lib/symbol-table.hpp +++ b/src/lib/symbol-table.hpp @@ -25,8 +25,8 @@ ** The implementation of the lib::Symbol token relies on unique string pointers, ** such as to create one distinct identity for each distinct "symbol string". The idea ** is to generate unique and distinct numeric token IDs, while still holding onto a human readable - ** string. Which in turn requires us to manage a registry of already known symbol strings; when - ** a Symbol object with such an already known string is created, it will thus connect internally + ** string. Which in turn requires us to manage a registry of already known symbol strings; when a + ** Symbol object with such an already known string is created, it will thus connect internally ** to the known token ID. ** ** @todo as of this writing (4/2017), it is neither clear if we really need such a facility, nor @@ -50,8 +50,8 @@ #include "lib/sync.hpp" #include "lib/symbol.hpp" +#include "lib/nocopy.hpp" -#include #include #include #include @@ -72,7 +72,7 @@ namespace lib { */ class SymbolTable : public Sync<> - , boost::noncopyable + , util::NonCopyable { std::unordered_set table_; diff --git a/src/lib/symbol.hpp b/src/lib/symbol.hpp index c6abfd856..f021d3d37 100644 --- a/src/lib/symbol.hpp +++ b/src/lib/symbol.hpp @@ -21,16 +21,30 @@ */ /** @file symbol.hpp - ** WIP placeholder definition for a planned Symbol datatype. + ** Marker types to indicate a literal string and a Symbol. + ** Instead of working just with pointers, which could represent pretty much anything, + ** it is prudent to express the meaning at interfaces and for variables and members explicitly. ** - ** @todo for the (currently just planned as of 11/08) rules based configuration - ** in the Proc-Layer a explicit Symbol datatype will probably very helpful. - ** For now just a typedef is sufficient. A real Symbol datatype should - ** - be definable by string constant - ** - integrate smoothly with std::string - ** - provide a unique numeric index for each distinct Symbol - ** - automatically maintain a symbol table at runtime to support this - ** - provide some magic (macros) allowing to build distinct types based on symbols. + ** On concept level, while a string is just some sequence of characters and nothing can be said + ** about mutability or lifetime, a Literal on the contrary is meant to be _static._ It is fixed + ** and assumed to exist literally as is during the whole lifetime of the execution. The concept + ** of a Symbol is related, yet slightly different: it is meant to be a distinguishable fixed, + ** unique token. _Identical sequence_ of characters means we have exactly the _same Symbol._ + ** + ** These concepts can be fused by treating Symbol as a specialisation of Literal, additionally + ** maintaining an automatically populated, static [symbol table](\ref symbol-table.hpp), and + ** we close the circle by allowing Symbol instances to be created from strings at runtime. + ** + ** @remark this started on occasion 11/2008, just with a typedef to mark assumption on interfaces + ** for rules based configuration in the Proc-Layer. Over time, conversions, comparison and + ** hashcode implementation were added. It turned out that the most smooth integration in + ** coding practice is achieved when allowing transparent conversion for Literal, but not + ** for Symbol or std::string. + ** @todo 9/2017 consider this mostly as settled, but might require some finishing touches + ** - maybe improve interoperation of Symbol and std::string + ** - investigate performance of the automatic symbol table + ** - improve Lifecycle in startup and shutdown phase + ** - maybe some metaprogramming magic to build distinct types based on symbols. ** ** @see symbol-impl.cpp ** @see configrules.hpp @@ -50,18 +64,27 @@ namespace lib { /** inline string literal - * This is a marker type to handle literally given C-Strings. + * This is a _marker type_ to indicate that + * - the string was given literally + * - storage is _somewhere_, not managed by Literal, + * yet guaranteed to exist during the whole lifetime + * - it is transparently convertible to/from C-string + * - defaults to the empty string + * - can not be altered */ class Literal { const char * str_; public: - Literal (const char* literal) + /** empty string by default */ + Literal() noexcept; + + Literal (const char* literal) noexcept : str_(literal) { } - Literal (Literal const& o) + Literal (Literal const& o) noexcept : str_(o.str_) { } @@ -77,8 +100,8 @@ namespace lib { bool operator== (const char* cString) const; protected: - /** Assignment prohibited */ - Literal& operator= (const char* newStr) + /** Assignment generally prohibited */ + Literal& operator= (const char* newStr) noexcept { str_ = newStr; return *this; @@ -86,13 +109,16 @@ namespace lib { }; - /** Token or Atom with distinct identity + /** Token or Atom with distinct identity. + * It can be created from arbitrary strings, yet not altered * @note same literal string==same pointer representation */ class Symbol : public Literal { public: + static Symbol ANY; + static Symbol EMPTY; static Symbol BOTTOM; static Symbol FAILURE; @@ -137,6 +163,9 @@ namespace lib { * with raw char ptrs (typically literal values) */ extern const size_t STRING_MAX_RELEVANT; + /** @note storage guaranteed to exist */ + inline Literal::Literal() noexcept : str_(Symbol::EMPTY) { } + /* ===== to be picked up by ADL ===== */ diff --git a/src/lib/sync-classlock.hpp b/src/lib/sync-classlock.hpp index 60ffba9bd..22acbaec8 100644 --- a/src/lib/sync-classlock.hpp +++ b/src/lib/sync-classlock.hpp @@ -29,7 +29,8 @@ ** ** @note simply using the ClassLock may cause a Monitor object (with a mutex) to be ** created at static initialisation and destroyed on application shutdown. - ** @see singleton-factory.hpp usage example + ** @see depend.hpp usage example + ** @see SyncClasslock_test */ @@ -37,51 +38,12 @@ #define LIB_SYNC_CLASSLOCK_H #include "lib/nobug-init.hpp" +#include "lib/zombie-check.hpp" #include "lib/sync.hpp" namespace lib { - namespace nifty { // implementation details - - template - struct Holder - { - static uint accessed_; - static char content_[sizeof(X)]; - - Holder() - { - if (!accessed_) - new(content_) X(); - ++accessed_; - } - - ~Holder() - { - --accessed_; - if (0==accessed_) - get().~X(); - } - - X& - get() - { - X* obj = reinterpret_cast (&content_); - ASSERT (obj, "Logic of Schwartz counter broken."); - return *obj; - } - }; - - template - uint Holder::accessed_; - - template - char Holder::content_[sizeof(X)]; - - } // (End) nifty implementation details - - /** * A synchronisation protection guard employing a lock scoped @@ -109,18 +71,17 @@ namespace lib { Monitor& getPerClassMonitor() { - static nifty::Holder __used_here; - ASSERT (1==use_count(), "static init broken"); + static PerClassMonitor classMonitor; + static ZombieCheck zombieCheck; - return __used_here.get(); + zombieCheck(); + return classMonitor; } public: ClassLock() : Lock (getPerClassMonitor()) { } - - uint use_count() { return nifty::Holder::accessed_; } }; } // namespace lib -#endif +#endif /*LIB_SYNC_CLASSLOCK_H*/ diff --git a/src/lib/sync.hpp b/src/lib/sync.hpp index 3d4584fc6..add2d7ec6 100644 --- a/src/lib/sync.hpp +++ b/src/lib/sync.hpp @@ -68,13 +68,13 @@ #define LIB_SYNC_H #include "lib/error.hpp" +#include "lib/nocopy.hpp" #include "lib/util.hpp" extern "C" { #include "lib/lockerror.h" } -#include #include #include #include @@ -284,13 +284,13 @@ namespace lib { wait (BF& predicate, Timeout& waitEndTime) { bool ok = true; - while (ok && !predicate()) + while (ok and !predicate()) if (waitEndTime) ok = Cond::timedwait (&waitEndTime); else ok = Cond::wait (); - if (!ok && lumiera_error_expect(LUMIERA_ERROR_LOCK_TIMEOUT)) return false; + if (not ok and lumiera_error_expect(LUMIERA_ERROR_LOCK_TIMEOUT)) return false; lumiera::throwOnError(); // any other error throws return true; @@ -440,7 +440,7 @@ namespace lib { * scoped object to control the actual locking. */ class Lock - : boost::noncopyable + : util::NonCopyable { Monitor& mon_; diff --git a/src/lib/test/depend-4test.hpp b/src/lib/test/depend-4test.hpp deleted file mode 100644 index 628725c49..000000000 --- a/src/lib/test/depend-4test.hpp +++ /dev/null @@ -1,124 +0,0 @@ -/* - DEPEND4TEST.hpp - inject test mock singletons and dependencies - - - Copyright (C) Lumiera.org - 2013, Hermann Vosseler - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of - the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - -*/ - - -/** @file depend-4test.hpp - ** test support code to inject mock variants of depencencies - */ - - - -#ifndef LIB_TEST_DEPEND_4TEST_H -#define LIB_TEST_DEPEND_4TEST_H - - -#include "lib/depend.hpp" -#include "lib/meta/duck-detector.hpp" - -#include -#include - - -namespace lib { -namespace test{ - - namespace { ///< details: inject a mock automatically in place of a singleton - - using lib::meta::enable_if; - using lib::meta::Yes_t; - using lib::meta::No_t; - - /** - * Metafunction: does the Type in question - * give us a clue about what service interface to use? - */ - template - class defines_ServiceInterface - { - META_DETECT_NESTED (ServiceInterface); - - public: - enum{ value = HasNested_ServiceInterface::value - }; - }; - - - /** - * Policy-Trait: determine the access point to install the mock. - * @note either the mock service implementation needs to provide - * explicitly a typedef for the ServiceInterface, or we're - * just creating a separate new instance of the singleton service, - * while shadowing (but not destroying) the original instance. - */ - template - struct ServiceInterface - { - typedef I Type; - }; - - - template - struct ServiceInterface >> - { - typedef typename MOCK::ServiceInterface Type; - }; - - }//(End) mock injection details - - - - - - /** - * Scoped object for installing/deinstalling a mocked service automatically. - * Placing a suitably specialised instance of this template into a local scope - * will inject the corresponding mock installation and remove it when the - * control flow leaves this scope. - * @param TYPE the concrete mock implementation type to inject. It needs to - * be default constructible. If TYPE is a subclass of the service interface, - * it needs to expose a typedef \c ServiceInterface - */ - template - struct Depend4Test - : boost::noncopyable - { - typedef typename ServiceInterface::Type Interface; - - std::unique_ptr mock_; - Interface* shadowedOriginal_; - - Depend4Test() - : mock_{new TYPE} - , shadowedOriginal_{Depend::injectReplacement (mock_.get())} - { } - - ~Depend4Test() - { - ENSURE (mock_); - Depend::injectReplacement (shadowedOriginal_); - } - }; - - -}} // namespace lib::test -#endif diff --git a/src/lib/test/microbenchmark.hpp b/src/lib/test/microbenchmark.hpp new file mode 100644 index 000000000..463bbb8bf --- /dev/null +++ b/src/lib/test/microbenchmark.hpp @@ -0,0 +1,134 @@ +/* + MICROBENCHMARK.hpp - multithreaded timing measurement + + Copyright (C) Lumiera.org + 2018, Hermann Vosseler + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + + +/** @file microbenchmark.hpp + ** A function to perform multithreaded timing measurement on a given functor. + ** This helper simplifies micro benchmarks of isolated implementation details. + ** The test subject, given as function object or lambda, is copied into N threads + ** and invoked numerous times within a tight loop. After waiting on termination of + ** the test threads, results are summed up and then averaged into milliseconds + ** per single invocation. The actual timing measurement relies on `chrono::duration`, + ** which means to count micro ticks of the OS. + ** @warning care has to bee taken when optimisation is involved! + ** Optimisation usually has quite some impact on the results, but since + ** this function is inline, the lambda can typically be inlined and the + ** loop possibly be optimised away entirely. A simple workaround is to + ** define a _volatile_ variable in the call context, close the lambda + ** by reference, and perform a comparison with that volatile variable + ** in each invocation. The compiler is required actually to access the + ** value of the volatile each time. + ** @remarks some interesting observations (in my setup, 8 core AMD FX-8350) + ** - if we replace the global volatile by a local variable within the + ** test subject, the initialisation of that local typically costs +5ns + ** per invocation. + ** - incrementing the volatile costs +10ns + ** - multithreaded (unlocked) incrementing of the _global_ volatile + ** creates massive overhead and increases the running time by factor 100. + ** This nicely confirms that the x86_64 platform has strong cache coherence. + ** + */ + + +#ifndef LIB_TEST_MICROBENCHMARK_H +#define LIB_TEST_MICROBENCHMARK_H + + +#include "backend/thread-wrapper.hpp" + +#include +#include + + + +namespace lib { +namespace test{ + + namespace { + constexpr size_t DEFAULT_RUNS = 10000000; + constexpr double SCALE = 1e6; // Results are in µ sec + } + + + /** perform a multithreaded microbenchmark. + * This function fires up a number of threads + * and invokes the given test subject repeatedly. + * @tparam number of threads to run in parallel + * @param subject `void(void)` function to be timed + * @return the averaged invocation time in _mircroseconds_ + * @remarks - the subject function will be _copied_ into each thread + * - so `nThreads` copies of this function will run in parallel + * - consider locking if this function accesses a shared closure. + * - if you pass a lambda, it is eligible for inlining followed + * by loop optimisation -- be sure to include some action, like + * e.g. accessing a volatile variable, to prevent the compiler + * from optimising it away entirely. + */ + template + inline double + microbenchmark(FUN const& subject, const size_t nRepeat = DEFAULT_RUNS) + { + using backend::ThreadJoinable; + using std::chrono::system_clock; + + using Dur = std::chrono::duration; + + struct Thread + : ThreadJoinable + { + Thread(FUN const& subject, size_t loopCnt) + : ThreadJoinable("Micro-Benchmark" + ,[=]() // local copy of the test-subject-Functor + { + syncPoint(); // block until all threads are ready + auto start = system_clock::now(); + for (size_t i=0; i < loopCnt; ++i) + subject(); + duration = system_clock::now () - start; + }) + { } + /** measured time within thread */ + Dur duration{}; + }; + + std::vector threads; + threads.reserve(nThreads); + for (size_t n=0; n + struct TypeDebugger + { + static_assert (not sizeof(X), "### Type Debugging ###"); + }; + + + + namespace { // helper for printing type diagnostics + + template + struct TypeDiagnostics + { + using Type = X; + static constexpr auto prefix = ""; + static constexpr auto postfix = ""; + }; + template + struct TypeDiagnostics + { + using Type = X; + static constexpr auto prefix = ""; + static constexpr auto postfix = "&"; + }; + template + struct TypeDiagnostics + { + using Type = X; + static constexpr auto prefix = ""; + static constexpr auto postfix = " &&"; + }; + template + struct TypeDiagnostics + { + using Type = X; + static constexpr auto prefix = ""; + static constexpr auto postfix = " const&"; + }; + template + struct TypeDiagnostics + { + using Type = X; + static constexpr auto prefix = "const "; + static constexpr auto postfix = " &&"; + }; + template + struct TypeDiagnostics + { + using Type = X; + static constexpr auto prefix = ""; + static constexpr auto postfix = " *"; + }; + template + struct TypeDiagnostics + { + using Type = X; + static constexpr auto prefix = "const "; + static constexpr auto postfix = " *"; + }; + template + struct TypeDiagnostics + { + using Type = X; + static constexpr auto prefix = "const "; + static constexpr auto postfix = " * const"; + }; + template + struct TypeDiagnostics + { + using Type = X; + static constexpr auto prefix = ""; + static constexpr auto postfix = " * const"; + }; + template + struct TypeDiagnostics + { + using Type = X; + static constexpr auto prefix = ""; + static constexpr auto postfix = " * const *"; + }; + } + + /** diagnostic type output, including const and similar adornments + * @warning operates after-the-fact and relies on mangled type names + * plus several heuristics. Output might thus not be entirely correct, + * especially when several levels of const, pointer and references are involved. + * If in doubt, place the TypeDebugger to reveal the type as the compiler sees it. + * @remarks the function lib::meta::typeStr removes adornments and does not work on all kinds + * of reference. This helper attempts to work around those limitations. + */ + template + inline string + showType() + { + using Case = TypeDiagnostics; + using Type = typename Case::Type; + + return Case::prefix + + meta::humanReadableTypeID (typeid(Type).name()) + + Case::postfix; + } + + + /** create a random but not insane Time value */ diff --git a/src/lib/test/testdummy.hpp b/src/lib/test/testdummy.hpp index 5a6744c25..9de21a0c8 100644 --- a/src/lib/test/testdummy.hpp +++ b/src/lib/test/testdummy.hpp @@ -29,8 +29,8 @@ */ +#include "lib/nocopy.hpp" -#include #include @@ -39,7 +39,7 @@ namespace test{ class Dummy - : boost::noncopyable + : util::NonCopyable { int val_; diff --git a/src/lib/test/testoption.hpp b/src/lib/test/testoption.hpp index 0963ca6b7..cb6427378 100644 --- a/src/lib/test/testoption.hpp +++ b/src/lib/test/testoption.hpp @@ -30,6 +30,7 @@ #define TESTHELPER_TESTOPTION_H #include "lib/cmdline.hpp" +#include "lib/nocopy.hpp" #include #include @@ -55,7 +56,8 @@ namespace test { * vector will contain only the remaining * unrecognised parts. */ - class TestOption : private boost::noncopyable + class TestOption + : util::NonCopyable { public: TestOption (lib::Cmdline& cmdline); diff --git a/src/lib/thread-local.hpp b/src/lib/thread-local.hpp index 530da6038..98ef3832a 100644 --- a/src/lib/thread-local.hpp +++ b/src/lib/thread-local.hpp @@ -28,6 +28,7 @@ ** ** @todo care for unit test coverage ** @todo WIP-WIP. Maybe add facilities similar to boost::specific_ptr + ** @deprecated C++11 has a `thread_local` storage class... **/ @@ -36,8 +37,8 @@ #include "lib/error.hpp" +#include "lib/nocopy.hpp" -#include #include @@ -54,7 +55,7 @@ namespace lib { */ template class ThreadLocalPtr - : boost::noncopyable + : util::NonCopyable { pthread_key_t key_; @@ -100,7 +101,7 @@ namespace lib { TAR *p(get()); if (!p) throw lumiera::error::State ("dereferencing a thread local NULL pointer" - ,lumiera::error::LUMIERA_ERROR_BOTTOM_VALUE); + ,lumiera::error::LERR_(BOTTOM_VALUE)); return p; } diff --git a/src/lib/time/mutation.hpp b/src/lib/time/mutation.hpp index 81b57c454..872f7976d 100644 --- a/src/lib/time/mutation.hpp +++ b/src/lib/time/mutation.hpp @@ -62,11 +62,11 @@ #define LIB_TIME_MUTATION_H #include "lib/error.hpp" +#include "lib/nocopy.hpp" #include "lib/time/timevalue.hpp" #include "lib/polymorphic-value.hpp" #include "lib/symbol.hpp" -#include namespace lib { diff --git a/src/lib/time/timecode.cpp b/src/lib/time/timecode.cpp index 8460516a5..90e853bfd 100644 --- a/src/lib/time/timecode.cpp +++ b/src/lib/time/timecode.cpp @@ -77,7 +77,7 @@ namespace time { return frameGrid.timeOf (lexical_cast (match[1])); else throw error::Invalid ("unable to parse framecount \""+frameNumber+"\"" - , LUMIERA_ERROR_INVALID_TIMECODE); + , LERR_(INVALID_TIMECODE)); } @@ -145,7 +145,7 @@ namespace time { } else throw error::Invalid ("unable to parse \""+seconds+"\" as (fractional)seconds" - , LUMIERA_ERROR_INVALID_TIMECODE); + , LERR_(INVALID_TIMECODE)); } diff --git a/src/lib/time/timevalue.hpp b/src/lib/time/timevalue.hpp index ee9e4a03e..ed5ba97f1 100644 --- a/src/lib/time/timevalue.hpp +++ b/src/lib/time/timevalue.hpp @@ -582,7 +582,7 @@ namespace time { { if (n == 0) throw error::Logic ("Degenerated frame grid not allowed" - , error::LUMIERA_ERROR_BOTTOM_VALUE); + , error::LERR_(BOTTOM_VALUE)); return n; } }//(End) implementation helpers diff --git a/src/lib/util.hpp b/src/lib/util.hpp index 7f68a26a1..4d35ff019 100644 --- a/src/lib/util.hpp +++ b/src/lib/util.hpp @@ -210,7 +210,7 @@ namespace util { /** shortcut for set value containment test */ template inline bool - contains (std::set& set, T const& val) + contains (std::set const& set, T const& val) { return set.end() != set.find (val); } diff --git a/src/lib/variant-o.hpp b/src/lib/variant-o.hpp index 137a69c7d..11fdc5d41 100644 --- a/src/lib/variant-o.hpp +++ b/src/lib/variant-o.hpp @@ -54,8 +54,8 @@ #include "lib/meta/typelist-util.hpp" #include "lib/meta/generator.hpp" +#include "lib/nocopy.hpp" -#include @@ -251,7 +251,7 @@ namespace lib { , template class Access > class VariantO - : boost::noncopyable + : util::NonCopyable { typedef variant::Holder Holder; diff --git a/src/lib/variant.hpp b/src/lib/variant.hpp index 16199594d..87621e14b 100644 --- a/src/lib/variant.hpp +++ b/src/lib/variant.hpp @@ -28,7 +28,7 @@ ** but also doesn't deal with alignment issues and is not threadsafe. ** ** Deliberately, the design rules out re-binding of the contained type. Thus, - ** once created, a variant \em must hold a valid element and always an element + ** once created, a variant _must hold a valid element_ and always an element ** of the same type. Beyond that, variant elements are copyable and mutable. ** Direct access requires knowledge of the embedded type (no switch-on type). ** Type mismatch is checked at runtime. As a fallback, we provide a visitor @@ -39,10 +39,11 @@ ** Likewise, we do not want to support mutations of the variant type at runtime. Basically, ** using a variant record is recommended only if either the receiving context has structural ** knowledge about the type to expect, or when a visitor implementation can supply a sensible - ** handling for \em all the possible types. As an alternative, you might consider the + ** handling for _all the possible types._ As an alternative, you might consider the ** lib::PolymorphicValue to hold types implementing a common interface. ** - ** \par implementation notes + ** ## implementation notes + ** ** We use a "double capsule" implementation technique similar to lib::OpaqueHolder. ** In fact, Variant is almost identical to the latter, just omitting unnecessary flexibility. ** The outer capsule exposes the public handling interface, while the inner, private capsule @@ -61,9 +62,9 @@ ** ** @note we use a Visitor interface generated through metaprogramming. ** This may generate a lot of warnings "-Woverloaded-virtual", - ** since one \c handle(TX) function may shadow other \c handle(..) functions + ** since one `handle(TX)` function may shadow other `handle(..)` functions ** from the inherited (generated) Visitor interface. These warnings are besides - ** the point, since not the \em client uses these functions, but the Variant does, + ** the point, since not the _client_ uses these functions, but the Variant does, ** after upcasting to the interface. Make sure you define your specialisations with ** the override modifier; when done so, it is safe to disable this warning here. ** @@ -102,10 +103,11 @@ namespace lib { namespace error = lumiera::error; - namespace variant { // implementation helpers + namespace variant { // implementation metaprogramming helpers using std::remove_reference; using meta::NullType; + using meta::Types; using meta::Node; @@ -148,6 +150,33 @@ namespace lib { { }; + template + struct Identity { using Type = T; }; + + /** + * Helper to pick the first type from a type sequence, + * which fulfils the predicate (meta function) given as template + * @tparam TYPES a type sequence or type list + * @tparam a predicate template or type trait + * @note result as embedded typedef `Type` + */ + template class _P_> + struct FirstMatchingType + { + static_assert(not sizeof(TYPES), "None of the possible Types fulfils the condition"); + }; + + template class _P_> + struct FirstMatchingType, _P_> + : FirstMatchingType::List, _P_> + { }; + + template class _P_> + struct FirstMatchingType, _P_> + : std::conditional_t<_P_::value, Identity, FirstMatchingType> + { }; + + template struct VFunc @@ -188,7 +217,7 @@ namespace lib { * @todo we need to define all copy operations explicitly, due to the * templated one-arg ctor to wrap the actual values. * There might be a generic solution for that ////////////////////////TICKET #963 Forwarding shadows copy operations -- generic solution?? - * But -- Beware of unverifiable generic solutions! + * But -- Beware of unverifiable generic solutions! */ template class Variant @@ -219,6 +248,14 @@ namespace lib { virtual ~Predicate() { } ///< this is an interface }; + /** + * Metafunction to pick the first of the variant's types, + * which satisfies the given trait or predicate template + * @note result is the embedded typedef `FirstMatching

::Type` + */ + template class _P_> + using FirstMatching = variant::FirstMatchingType; + private: /** Inner capsule managing the contained object (interface) */ @@ -313,7 +350,7 @@ namespace lib { throw error::Logic("Variant type mismatch: " "the given variant record does not hold " "a value of the type requested here" - ,error::LUMIERA_ERROR_WRONG_TYPE); + ,error::LERR_(WRONG_TYPE)); else return *buff; } diff --git a/src/lib/wrapper.hpp b/src/lib/wrapper.hpp index 9c73d02f0..aa66db2fe 100644 --- a/src/lib/wrapper.hpp +++ b/src/lib/wrapper.hpp @@ -34,6 +34,8 @@ ** ** @see lib::TransformIter ** + ** @todo 2017 consider to split off the FunctionResult into a dedicated header to reduce includes + ** */ @@ -41,12 +43,12 @@ #define LIB_WRAPPER_H #include "lib/error.hpp" +#include "lib/nocopy.hpp" #include "lib/meta/function.hpp" #include "lib/meta/function-closure.hpp" #include "lib/meta/util.hpp" #include "lib/util.hpp" -#include #include @@ -56,16 +58,16 @@ namespace wrapper { using util::unConst; using util::isSameObject; using lib::meta::_Fun; - using lumiera::error::LUMIERA_ERROR_BOTTOM_VALUE; + using lumiera::error::LERR_(BOTTOM_VALUE); using std::function; - /** - * Extension to std::reference_wrapper: + /** + * Extension to std::reference_wrapper: * Allows additionally to re-bind to another reference, * almost like a pointer. Helpful for building templates. - * @warning potentially dangerous + * @warning potentially dangerous */ template class AssignableRefWrapper @@ -95,7 +97,7 @@ namespace wrapper { - /** + /** * Reference wrapper implemented as constant function, * returning the (fixed) reference on invocation */ @@ -145,21 +147,15 @@ namespace wrapper { using TY_unconst = typename meta::UnConst::Type ; - mutable + mutable char content_[sizeof(TY)]; bool created_; + template void - build (TY const& ref) + build (REF&& ref) { - new(&content_) TY{ref}; - created_ = true; - } - - void - build (TY&& rref) - { - new(&content_) TY{std::forward (rref)}; + new(&content_) TY{std::forward (ref)}; created_ = true; } @@ -179,7 +175,7 @@ namespace wrapper { TY_unconst& access_unconst() const ///< used to assign new buffer contents { - return const_cast (access()); + return const_cast (access()); } public: @@ -192,11 +188,10 @@ namespace wrapper { { build (o); } - explicit - ItemWrapper(TY&& rref) + ItemWrapper(TY && ro) { - build (std::forward (rref)); + build (std::move(ro)); } ~ItemWrapper() @@ -206,52 +201,62 @@ namespace wrapper { /* == copy and assignment == */ - + ItemWrapper (ItemWrapper const& ref) : created_(false) { if (ref.isValid()) build (*ref); } - ItemWrapper (ItemWrapper&& rref) + ItemWrapper (ItemWrapper && rref) : created_(false) { if (rref.isValid()) - build (std::forward (*rref)); + { + build (std::move (*rref)); + rref.discard(); + } } ItemWrapper& - operator= (ItemWrapper const& ref) + operator= (ItemWrapper const& cref) { - if (!ref.isValid()) + if (!cref.isValid()) discard(); else - this->operator= (*ref); + this->operator= (*cref); return *this; } - ItemWrapper& - operator= (ItemWrapper&& rref) + operator= (ItemWrapper & ref) + { + return *this = (ItemWrapper const&)ref; + } + ItemWrapper& + operator= (ItemWrapper && rref) { if (!rref.isValid()) discard(); else - this->operator= (std::forward (*rref)); + { + this->operator= (std::move(*rref)); + rref.discard(); + } return *this; } template ItemWrapper& - operator= (X something) ///< move in anything assignable to TY + operator= (X&& something) ///< accept anything assignable to TY { if (!isSameObject (something, access() )) { if (created_) - access() = std::forward (something); + access() = std::forward(something); else - build (std::forward (something)); + build (std::forward(something)); } return *this; @@ -269,14 +274,20 @@ namespace wrapper { { if (!created_) throw lumiera::error::State ("accessing uninitialised value/ref wrapper" - , LUMIERA_ERROR_BOTTOM_VALUE); + , LERR_(BOTTOM_VALUE)); return access(); } + TY* + operator-> () const + { + return & **this; + } + bool isValid () const { - return created_; + return created_; } void @@ -285,12 +296,12 @@ namespace wrapper { discard(); } }; - + /** * Specialisation of the ItemWrapper to deal with references, * as if they were pointer values. Allows the reference value - * to be default constructed to \c NULL and to be re-assigned. + * to be default constructed to `NULL` and to be re-assigned. */ template class ItemWrapper @@ -330,14 +341,14 @@ namespace wrapper { { if (!content_) throw lumiera::error::State ("accessing uninitialised reference wrapper" - , LUMIERA_ERROR_BOTTOM_VALUE); + , LERR_(BOTTOM_VALUE)); return *content_; } bool isValid () const { - return bool(content_); + return bool(content_); } void @@ -366,12 +377,13 @@ namespace wrapper { - /** + + /** * Extension of ItemWrapper: a function remembering * the result of the last invocation. Initially, the "value" - * is bottom (undefined, NIL), until the function is invoked - * for the first time. After that, the result of the last - * invocation can be accessed by \c operator*() + * is bottom (undefined, NIL), until the function is invoked + * for the first time. After that, the result of the last + * invocation can be accessed by `operator* ()` * * @note non-copyable. (removing this limitation would * require a much more expensive implementation, @@ -380,7 +392,7 @@ namespace wrapper { template class FunctionResult : public function - , boost::noncopyable + , util::NonCopyable { using Res = typename _Fun::Ret; using ResWrapper = ItemWrapper; @@ -402,7 +414,7 @@ namespace wrapper { /** Create result-remembering functor * by binding the given function. Explanation: - * - *this is a \em function + * - `*this` is a _function_ * - initially it is defined as invalid * - then we build the function composition of * the target function, and a function storing @@ -418,7 +430,7 @@ namespace wrapper { using lib::meta::func::chained; // note: binding "this" mandates noncopyable function doCaptureResult = bind (&FunctionResult::captureResult, this, _1 ); - function chainedWithResCapture = chained (targetFunction, doCaptureResult); + function chainedWithResCapture = chained (targetFunction, doCaptureResult); function::operator= (chainedWithResCapture); // define the function (baseclass) } diff --git a/src/lib/zombie-check.hpp b/src/lib/zombie-check.hpp new file mode 100644 index 000000000..81ffa4fff --- /dev/null +++ b/src/lib/zombie-check.hpp @@ -0,0 +1,80 @@ +/* + ZOMBIE-CHECK.hpp - flatliner self-detection + + Copyright (C) Lumiera.org + 2018, Hermann Vosseler + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file dependable-base.hpp + ** Detector to set off alarm when (re)using deceased objects. + ** When implementing services based on static fields or objects, + ** an invocation after static shutdown can not be precluded -- be it by + ** re-entrance, be through indirect reference to some dependency within a + ** static function residing in another translation unit. Since typically the + ** values in static storage are not overwritten after invoking the destructor, + ** we may plant an automatic "zombie detector" to give a clear indication of + ** such a policy violation (Lumiera forbids to use dependencies from dtors). + ** + ** @see sync-classlock.hpp + ** @see depend.hpp + */ + + +#ifndef LIB_ZOMBIE_CHECK_H +#define LIB_ZOMBIE_CHECK_H + +#include "lib/error.hpp" + + + +namespace lib { + namespace error = lumiera::error; + + + /** + * Automatic lifecycle tracker, to produce an alarm when accessing objects after deletion. + */ + class ZombieCheck + { + bool deceased_ = false; + + public: + ZombieCheck() = default; + ~ZombieCheck() + { + deceased_ = true; + } + + operator bool() const + { + return deceased_; + } + + void + operator() () const + { + if (deceased_) + throw error::Fatal("Already deceased object called out of order during Application shutdown. " + "Lumiera Policy violated: Dependencies must not be used from destructors." + ,error::LUMIERA_ERROR_LIFECYCLE); + } + }; + + +} // namespace lib +#endif /*LIB_ZOMBIE_CHECK_H*/ diff --git a/src/proc/asset.hpp b/src/proc/asset.hpp index b148a7e0a..5e1cec4c2 100644 --- a/src/proc/asset.hpp +++ b/src/proc/asset.hpp @@ -57,6 +57,7 @@ #include "lib/error.hpp" +#include "lib/nocopy.hpp" #include "include/logging.h" #include "proc/asset/category.hpp" #include "lib/hash-value.h" @@ -64,7 +65,6 @@ #include #include -#include #include #include @@ -144,8 +144,8 @@ namespace asset { * @author Ichthyo */ class Asset - : boost::totally_ordered1< Asset, - boost::noncopyable> + : public boost::totally_ordered1< Asset + , util::NonCopyable > { public: diff --git a/src/proc/asset/db.hpp b/src/proc/asset/db.hpp index b0f097605..22a729db4 100644 --- a/src/proc/asset/db.hpp +++ b/src/proc/asset/db.hpp @@ -35,6 +35,7 @@ #include "lib/sync.hpp" #include "lib/error.hpp" #include "proc/asset.hpp" +#include "lib/depend-inject.hpp" #include #include @@ -95,8 +96,8 @@ namespace asset { * As of 8/2007 implemented by a hashtable. */ class DB - : private boost::noncopyable, - public Sync + : util::NonCopyable + , public Sync { IdHashtable table; @@ -109,7 +110,7 @@ namespace asset { clear(); } - friend class lib::DependencyFactory; + friend class lib::DependencyFactory; public: diff --git a/src/proc/asset/media.cpp b/src/proc/asset/media.cpp index f1d236bcf..be5c5ccfd 100644 --- a/src/proc/asset/media.cpp +++ b/src/proc/asset/media.cpp @@ -242,7 +242,7 @@ namespace asset { "(multichannel) media. Found parent Media %s.") % mediaref % *mediaref.checkCompound() - ,LUMIERA_ERROR_PART_OF_COMPOUND); + ,LERR_(PART_OF_COMPOUND)); Clip* pC = new Clip (mediaref); return AssetManager::instance().wrap (*pC); } diff --git a/src/proc/asset/media.hpp b/src/proc/asset/media.hpp index a39b8647e..d1bac7333 100644 --- a/src/proc/asset/media.hpp +++ b/src/proc/asset/media.hpp @@ -40,8 +40,8 @@ #include "lib/time/timevalue.hpp" #include "proc/mobject/mobject.hpp" #include "proc/mobject/session/clip.hpp" +#include "lib/nocopy.hpp" -#include @@ -141,7 +141,7 @@ namespace asset { * Factory specialised for creating Media Asset objects. */ class MediaFactory - : boost::noncopyable + : util::NonCopyable { public: typedef lib::P PType; diff --git a/src/proc/asset/meta.hpp b/src/proc/asset/meta.hpp index bfb30628d..f701a665f 100644 --- a/src/proc/asset/meta.hpp +++ b/src/proc/asset/meta.hpp @@ -61,8 +61,7 @@ #include "proc/asset.hpp" #include "lib/idi/entry-id.hpp" - -#include +#include "lib/nocopy.hpp" namespace proc { @@ -147,7 +146,7 @@ namespace asset { * Factory specialised for creating Metadata Asset objects. */ class MetaFactory - : boost::noncopyable + : util::NonCopyable { public: typedef lib::P PType; diff --git a/src/proc/asset/proc.hpp b/src/proc/asset/proc.hpp index 796fd730b..30c08e821 100644 --- a/src/proc/asset/proc.hpp +++ b/src/proc/asset/proc.hpp @@ -38,8 +38,7 @@ #include "proc/asset.hpp" #include "proc/streamtype.hpp" - -#include +#include "lib/nocopy.hpp" @@ -118,7 +117,7 @@ namespace asset { * Factory specialised for creating Processor Asset objects. */ class ProcFactory - : boost::noncopyable + : util::NonCopyable { public: typedef lib::P PType; diff --git a/src/proc/asset/struct-factory-impl.hpp b/src/proc/asset/struct-factory-impl.hpp index 1f4d574c1..16239874c 100644 --- a/src/proc/asset/struct-factory-impl.hpp +++ b/src/proc/asset/struct-factory-impl.hpp @@ -70,7 +70,7 @@ namespace asset { using util::isnil; using util::contains; using lumiera::Query; - using lumiera::query::LUMIERA_ERROR_CAPABILITY_QUERY; + using lumiera::query::LERR_(CAPABILITY_QUERY); using lib::query::extractID; using proc::mobject::Session; diff --git a/src/proc/asset/struct.hpp b/src/proc/asset/struct.hpp index 4ec18a145..4ed21312e 100644 --- a/src/proc/asset/struct.hpp +++ b/src/proc/asset/struct.hpp @@ -64,8 +64,8 @@ #include "lib/symbol.hpp" #include "proc/asset.hpp" #include "common/query.hpp" +#include "lib/nocopy.hpp" -#include #include #include @@ -142,7 +142,7 @@ namespace asset { * Factory specialised for creating Structural Asset objects. */ class StructFactory - : boost::noncopyable + : util::NonCopyable { unique_ptr impl_; diff --git a/src/proc/assetmanager.cpp b/src/proc/assetmanager.cpp index 9b7c8a2fa..509b6b98c 100644 --- a/src/proc/assetmanager.cpp +++ b/src/proc/assetmanager.cpp @@ -70,7 +70,7 @@ namespace asset { UnknownID (ID aID) : IDErr(_Fmt("Query for Asset with ID=%d, which up to now " "hasn't been created or encountered.") % aID - ,LUMIERA_ERROR_UNKNOWN_ASSET_ID) + ,LERR_(UNKNOWN_ASSET_ID)) { } }; @@ -80,7 +80,7 @@ namespace asset { : IDErr (_Fmt("Request for Asset(%s), specifying an Asset kind, " "that doesn't match the actual type (and can't be " "casted either).") % idi - ,LUMIERA_ERROR_WRONG_ASSET_KIND) + ,LERR_(WRONG_ASSET_KIND)) { } }; diff --git a/src/proc/assetmanager.hpp b/src/proc/assetmanager.hpp index 5d9c7fa04..08f6e6244 100644 --- a/src/proc/assetmanager.hpp +++ b/src/proc/assetmanager.hpp @@ -68,7 +68,7 @@ namespace asset { * Facade for the Asset subsystem */ class AssetManager - : private boost::noncopyable + : util::NonCopyable { asset::DB & registry; @@ -100,7 +100,7 @@ namespace asset { /** deregister and evict all known Assets. * @note the actual object instances are managed by reference count, - * i.e. typically the Assets will be kept alive by MObjects from the sesison + * i.e. typically the Assets will be kept alive by MObjects from the session * @warning unsure if this design is sane. Asset subsystem needs a rework ////////////////////////////TICKET #691 * @todo verify this actually works, especially with session shutdown ////////////////////////////TICKET #154 */ @@ -124,9 +124,9 @@ namespace asset { friend Asset::Asset (Asset::Ident const& idi); - AssetManager (); + AssetManager(); - friend class lib::DependencyFactory; + friend class lib::DependencyFactory; }; diff --git a/src/proc/cmd.hpp b/src/proc/cmd.hpp index 96fa9a37a..00716f0b9 100644 --- a/src/proc/cmd.hpp +++ b/src/proc/cmd.hpp @@ -42,7 +42,6 @@ //#include "lib/symbol.hpp" //#include "proc/common.hpp" -//#include //#include diff --git a/src/proc/common.hpp b/src/proc/common.hpp index 35109badd..5acea10b0 100644 --- a/src/proc/common.hpp +++ b/src/proc/common.hpp @@ -45,6 +45,7 @@ #include "lib/p.hpp" #include "lib/util.hpp" +#include "lib/nocopy.hpp" #include "lib/time/timevalue.hpp" #include "lib/symbol.hpp" #include "lib/error.hpp" // pulls in NoBug via loggging.h diff --git a/src/proc/config-resolver.cpp b/src/proc/config-resolver.cpp index ffd865fb3..410c4b80f 100644 --- a/src/proc/config-resolver.cpp +++ b/src/proc/config-resolver.cpp @@ -30,17 +30,41 @@ #include "lib/error.hpp" +#include "include/lifecycle.h" #include "proc/config-resolver.hpp" #include "proc/mobject/session/query/fake-configrules.hpp" +#include "lib/depend-inject.hpp" +using lumiera::LifecycleHook; +using lumiera::ON_GLOBAL_INIT; namespace proc { + namespace { + + /** + * Install the actual ConfigResolver implementation. + * The ConfigResolver answers queries about configuration and default configuration + * of various aspects of the session, based on configuration rules. + * @todo PLANNED: use an embedded Prolog-System or similar rules engine. + * For the time being (as of 2008-2018), + * we use preconfigured fake answers for some common Config-Queries + */ + void + configure_ConfigResolver() + { + lib::DependInject::useSingleton(); + } + + LifecycleHook schedule_ (ON_GLOBAL_INIT, &configure_ConfigResolver); + } - using lib::buildSingleton; - /** Singleton factory instance, configured with the actual implementation type. */ - lib::Depend ConfigResolver::instance (buildSingleton()); + /** Singleton factory instance, configured with the actual implementation type. + * @see DefsManager::DefsManager() + * @see defs-manager-impl.hpp + */ + lib::Depend ConfigResolver::instance; } // namespace proc diff --git a/src/proc/control/command-def.hpp b/src/proc/control/command-def.hpp index d878ac531..133c2330b 100644 --- a/src/proc/control/command-def.hpp +++ b/src/proc/control/command-def.hpp @@ -54,6 +54,7 @@ #define CONTROL_COMMAND_DEF_H #include "lib/error.hpp" +#include "lib/nocopy.hpp" #include "include/logging.h" #include "lib/symbol.hpp" #include "proc/control/command.hpp" @@ -70,7 +71,6 @@ #include #include -#include @@ -276,7 +276,7 @@ namespace control { * */ class CommandDef - : boost::noncopyable + : util::NonCopyable { Symbol id_; Command prototype_; diff --git a/src/proc/control/command-impl-clone-builder.hpp b/src/proc/control/command-impl-clone-builder.hpp index ce18a24a2..6c8fe2565 100644 --- a/src/proc/control/command-impl-clone-builder.hpp +++ b/src/proc/control/command-impl-clone-builder.hpp @@ -49,8 +49,7 @@ #include "proc/control/command-mutation.hpp" #include "lib/typed-allocation-manager.hpp" #include "lib/opaque-holder.hpp" - -#include +#include "lib/nocopy.hpp" namespace proc { @@ -122,7 +121,7 @@ namespace control { * and the memento storage within the cloned parts. */ class CommandImplCloneBuilder - : public boost::noncopyable + : util::NonCopyable { typedef InPlaceBuffer ContextHolder; diff --git a/src/proc/control/command-impl.hpp b/src/proc/control/command-impl.hpp index cddabe1bd..c13e898a3 100644 --- a/src/proc/control/command-impl.hpp +++ b/src/proc/control/command-impl.hpp @@ -46,8 +46,8 @@ #include "proc/control/command-closure.hpp" #include "proc/control/command-mutation.hpp" #include "lib/format-string.hpp" +#include "lib/nocopy.hpp" -#include #include #include @@ -77,7 +77,7 @@ namespace control { * @see Mutation */ class CommandImpl - : boost::noncopyable + : util::NonCopyable { Mutation do_; UndoMutation undo_; diff --git a/src/proc/control/command-instance-manager.hpp b/src/proc/control/command-instance-manager.hpp index d44523ceb..1134d9d1b 100644 --- a/src/proc/control/command-instance-manager.hpp +++ b/src/proc/control/command-instance-manager.hpp @@ -58,12 +58,12 @@ #define CONTROL_COMMAND_INSTANCE_MANAGER_H #include "lib/error.hpp" +#include "lib/nocopy.hpp" #include "proc/control/command.hpp" #include "proc/control/command-dispatch.hpp" #include "lib/diff/gen-node.hpp" #include "lib/symbol.hpp" -#include #include #include @@ -97,7 +97,7 @@ namespace control { * @warning CommandInstanceManager is *not threadsafe* */ class CommandInstanceManager - : boost::noncopyable + : util::NonCopyable { CommandDispatch& dispatcher_; std::unordered_map table_; diff --git a/src/proc/control/command-mutation.hpp b/src/proc/control/command-mutation.hpp index 6cc9f5336..23f517cc8 100644 --- a/src/proc/control/command-mutation.hpp +++ b/src/proc/control/command-mutation.hpp @@ -58,7 +58,7 @@ namespace control { /** * Unspecific command functor for implementing Proc-Layer Command. - * To be created from an tr1::function object, which later on gets + * To be created from an std::function object, which later on gets * any necessary arguments from a closure passed in on invocation. * The concrete type of the function and the arguments is * concealed (erased) on the interface. @@ -80,7 +80,7 @@ namespace control { { if (!clo) throw lumiera::error::State ("Lifecycle error: function arguments not ready", - LUMIERA_ERROR_UNBOUND_ARGUMENTS); + LERR_(UNBOUND_ARGUMENTS)); clo.invoke (func_); } @@ -138,7 +138,7 @@ namespace control { { if (!clo) throw lumiera::error::State ("need additional function arguments to be able to capture UNDO state", - LUMIERA_ERROR_UNBOUND_ARGUMENTS); + LERR_(UNBOUND_ARGUMENTS)); captureMemento_(clo); return *this; diff --git a/src/proc/control/command-queue.hpp b/src/proc/control/command-queue.hpp index a5b54ec3a..60150966a 100644 --- a/src/proc/control/command-queue.hpp +++ b/src/proc/control/command-queue.hpp @@ -76,7 +76,7 @@ namespace control { if (not cmd.canExec()) throw error::Logic(_Fmt("Reject '%s'. Not suitably prepared for invocation: %s") % cmd.getID() % cmd - , LUMIERA_ERROR_UNBOUND_ARGUMENTS); + , LERR_(UNBOUND_ARGUMENTS)); lib::IterQueue::feed (move(cmd)); return *this; diff --git a/src/proc/control/command-registry.hpp b/src/proc/control/command-registry.hpp index 0830198af..ac54fe8f7 100644 --- a/src/proc/control/command-registry.hpp +++ b/src/proc/control/command-registry.hpp @@ -62,6 +62,7 @@ #include "lib/depend.hpp" #include "lib/sync.hpp" #include "include/logging.h" +#include "lib/nocopy.hpp" #include "lib/util.hpp" #include "proc/control/command.hpp" @@ -70,7 +71,6 @@ #include "lib/typed-allocation-manager.hpp" #include -#include #include #include #include @@ -82,7 +82,6 @@ namespace proc { namespace control { using boost::hash; - using boost::noncopyable; using std::shared_ptr; using std::unordered_map; using lib::TypedAllocationManager; @@ -118,7 +117,7 @@ namespace control { */ class CommandRegistry : public lib::Sync<> - , noncopyable + , util::NonCopyable { // using a hashtable to implement the index typedef unordered_map> CmdIndex; diff --git a/src/proc/control/command-setup.cpp b/src/proc/control/command-setup.cpp index 4ad3e4d06..e592325e1 100644 --- a/src/proc/control/command-setup.cpp +++ b/src/proc/control/command-setup.cpp @@ -122,7 +122,7 @@ namespace control { { if (not definitionBlock) throw error::Invalid ("unbound function/closure provided for CommandSetup" - , error::LUMIERA_ERROR_BOTTOM_VALUE); + , error::LERR_(BOTTOM_VALUE)); pendingCmdDefinitions().emplace_front (cmdID_, move(definitionBlock)); return *this; @@ -189,7 +189,7 @@ namespace control { "is currently open for parametrisation and " "not yet dispatched for execution."} % instanceID % invocationID - , LUMIERA_ERROR_DUPLICATE_COMMAND + , LERR_(DUPLICATE_COMMAND) ); // create new clone from the prototype table_[instanceID] = move (Command::get(prototypeID).newInstance()); @@ -219,7 +219,7 @@ namespace control { if (not entry->second) throw error::Logic (_Fmt{"Command instance '%s' is not (yet/anymore) active"} % instanceID - , error::LUMIERA_ERROR_LIFECYCLE); + , error::LERR_(LIFECYCLE)); return entry->second; } @@ -241,11 +241,11 @@ namespace control { "globally registered command definition, " "nor to an previously opened command instance") % instanceID - , LUMIERA_ERROR_INVALID_COMMAND); + , LERR_(INVALID_COMMAND)); if (not entry->second.isValid()) throw error::Logic (_Fmt{"Command instance '%s' is not (yet/anymore) active"} % instanceID - , error::LUMIERA_ERROR_LIFECYCLE); + , error::LERR_(LIFECYCLE)); if (not must_be_bound or entry->second.canExec()) instance = move(entry->second); } @@ -253,7 +253,7 @@ namespace control { throw error::State (_Fmt{"attempt to dispatch command instance '%s' " "without binding all arguments properly beforehand"} % instanceID - , LUMIERA_ERROR_UNBOUND_ARGUMENTS); + , LERR_(UNBOUND_ARGUMENTS)); ENSURE (instance.isValid() and (instance.canExec() or not must_be_bound)); diff --git a/src/proc/control/command-setup.hpp b/src/proc/control/command-setup.hpp index c77f5c8c8..fedcf7871 100644 --- a/src/proc/control/command-setup.hpp +++ b/src/proc/control/command-setup.hpp @@ -53,6 +53,7 @@ #define CONTROL_COMMAND_SETUP_H #include "lib/error.hpp" +#include "lib/nocopy.hpp" #include "proc/control/command.hpp" #include "lib/symbol.hpp" @@ -94,15 +95,13 @@ namespace control { * to be defined and configured for use with the session subsystem. */ class CommandSetup + : util::Cloneable // copy-construction allowed, but no assignment { Symbol cmdID_; public: ~CommandSetup(); CommandSetup (Symbol cmdID); - CommandSetup (CommandSetup const&) = default; - CommandSetup (CommandSetup &&) = default; - CommandSetup& operator= (CommandSetup const&) = delete; operator Symbol const&() const { diff --git a/src/proc/control/command-simple-closure.hpp b/src/proc/control/command-simple-closure.hpp index 5b7e78d17..317da7ad4 100644 --- a/src/proc/control/command-simple-closure.hpp +++ b/src/proc/control/command-simple-closure.hpp @@ -80,7 +80,7 @@ namespace control { using ArgumentBuff = InPlaceBuffer; using ArgTuple = typename ArgHolder::ArgTuple; - using Args = typename Types::Seq; + using Args = typename lib::meta::RebindTySeq::Seq; // std::tuple to Types /* ====== in-place argument storage ====== */ @@ -138,7 +138,7 @@ namespace control { if (!isValid()) throw lumiera::error::State ("Lifecycle error: can't bind functor, " "command arguments not yet provided", - LUMIERA_ERROR_UNBOUND_ARGUMENTS); + LERR_(UNBOUND_ARGUMENTS)); arguments_->invoke(func); } diff --git a/src/proc/control/command-storage-holder.hpp b/src/proc/control/command-storage-holder.hpp index 7a3221bbf..6a03ae060 100644 --- a/src/proc/control/command-storage-holder.hpp +++ b/src/proc/control/command-storage-holder.hpp @@ -86,7 +86,7 @@ namespace control { using MementoBuff = InPlaceBuffer; using ArgTuple = typename ArgHolder::ArgTuple; - using Args = typename Types::Seq; + using Args = typename lib::meta::RebindTySeq::Seq; // std::tuple to Types /* ====== in-place storage buffers ====== */ @@ -145,7 +145,7 @@ namespace control { if (!isValid()) throw lumiera::error::State ("Lifecycle error: can't bind functor, " "command arguments not yet provided", - LUMIERA_ERROR_UNBOUND_ARGUMENTS); + LERR_(UNBOUND_ARGUMENTS)); arguments_->invoke(func); } diff --git a/src/proc/control/command.cpp b/src/proc/control/command.cpp index e266da1aa..ffd4518c0 100644 --- a/src/proc/control/command.cpp +++ b/src/proc/control/command.cpp @@ -80,7 +80,7 @@ namespace control { REQUIRE (handle); if (!handle->isValid()) throw error::Invalid (operation_descr+" an undefined command" - , LUMIERA_ERROR_INVALID_COMMAND); + , LERR_(INVALID_COMMAND)); } void @@ -89,7 +89,7 @@ namespace control { REQUIRE (handle); if (!handle->canExec()) throw error::State ("Lifecycle error: command arguments not bound" - , LUMIERA_ERROR_UNBOUND_ARGUMENTS); + , LERR_(UNBOUND_ARGUMENTS)); } void @@ -98,7 +98,7 @@ namespace control { REQUIRE (handle); if (!handle->canUndo()) throw error::State ("Lifecycle error: command has not yet captured UNDO information" - , LUMIERA_ERROR_UNBOUND_ARGUMENTS); + , LERR_(UNBOUND_ARGUMENTS)); } } @@ -129,7 +129,7 @@ namespace control { Command cmd = CommandRegistry::instance().queryIndex (cmdID); if (!cmd) throw error::Invalid(_Fmt("Command \"%s\" not found") % cmdID - , LUMIERA_ERROR_INVALID_COMMAND); + , LERR_(INVALID_COMMAND)); ENSURE (cmdID == CommandRegistry::instance().findDefinition(cmd)); return cmd; @@ -240,7 +240,7 @@ namespace control { "ID \"%s\" is already in use") % *this % newCmdID - , LUMIERA_ERROR_DUPLICATE_COMMAND); + , LERR_(DUPLICATE_COMMAND)); } diff --git a/src/proc/control/memento-tie.hpp b/src/proc/control/memento-tie.hpp index 91d619617..dfa290fd7 100644 --- a/src/proc/control/memento-tie.hpp +++ b/src/proc/control/memento-tie.hpp @@ -178,7 +178,7 @@ namespace control { { if (!isCaptured_) throw lumiera::error::State ("need to invoke memento state capturing beforehand", - LUMIERA_ERROR_MISSING_MEMENTO); + LERR_(MISSING_MEMENTO)); return memento_; } diff --git a/src/proc/control/proc-dispatcher.cpp b/src/proc/control/proc-dispatcher.cpp index 5554cce84..f63fd6415 100644 --- a/src/proc/control/proc-dispatcher.cpp +++ b/src/proc/control/proc-dispatcher.cpp @@ -89,6 +89,7 @@ #include "proc/control/session-command-service.hpp" #include "proc/mobject/session.hpp" #include "backend/thread-wrapper.hpp" +#include "lib/depend-inject.hpp" #include "lib/util.hpp" ///////////////TODO for test command invocation #include @@ -116,9 +117,10 @@ namespace control { , public CommandDispatch , public Sync { + using ServiceHandle = lib::DependInject::ServiceInstance<>; /** manage the primary public Session interface */ - unique_ptr commandService_; + ServiceHandle commandService_; CommandQueue queue_; Looper looper_; @@ -135,8 +137,8 @@ namespace control { */ DispatcherLoop (Subsys::SigTerm notification) : Thread{"Lumiera Session", bind (&DispatcherLoop::runSessionThread, this, notification)} - , commandService_() - , queue_() + , commandService_{ServiceHandle::NOT_YET_STARTED} + , queue_{} , looper_([&]() -> bool { return not queue_.empty(); @@ -145,15 +147,15 @@ namespace control { Thread::sync(); // done with setup; loop may run now.... INFO (session, "Proc-Dispatcher running..."); { - Lock(this); // open public session interface: - commandService_.reset(new SessionCommandService(*this)); + Lock(this); // open public session interface: + commandService_.createInstance(*this); } } ~DispatcherLoop() { try { - commandService_.reset(); // redundant call, to ensure session interface is closed reliably + commandService_.shutdown(); // redundant call, to ensure session interface is closed reliably INFO (session, "Proc-Dispatcher stopped."); } ERROR_LOG_AND_IGNORE(session, "Stopping the Proc-Dispatcher"); @@ -181,7 +183,7 @@ namespace control { requestStop() noexcept { Lock sync(this); - commandService_.reset(); // closes Session interface + commandService_.shutdown(); // closes Session interface looper_.triggerShutdown(); sync.notifyAll(); } @@ -326,6 +328,9 @@ namespace control { /* ======== ProcDispatcher implementation ======== */ + ProcDispatcher::ProcDispatcher() { } + ProcDispatcher::~ProcDispatcher() { } + /** starting the ProcDispatcher means to start the session subsystem. * @return `false` when _starting_ failed since it is already running... * @remark this function implements the start operation for the »session subsystem«. diff --git a/src/proc/control/proc-dispatcher.hpp b/src/proc/control/proc-dispatcher.hpp index 7ffbfc209..66381e2b9 100644 --- a/src/proc/control/proc-dispatcher.hpp +++ b/src/proc/control/proc-dispatcher.hpp @@ -87,7 +87,6 @@ namespace control { unique_ptr runningLoop_; bool active_{false}; - public: static lib::Depend instance; @@ -104,6 +103,10 @@ namespace control { private: void endRunningLoopState(); + + ~ProcDispatcher(); + ProcDispatcher(); + friend class lib::DependencyFactory; }; diff --git a/src/proc/control/session-command-interface-proxy.hpp b/src/proc/control/session-command-interface-proxy.cpp similarity index 66% rename from src/proc/control/session-command-interface-proxy.hpp rename to src/proc/control/session-command-interface-proxy.cpp index a8658ebdf..35ffdb106 100644 --- a/src/proc/control/session-command-interface-proxy.hpp +++ b/src/proc/control/session-command-interface-proxy.cpp @@ -22,9 +22,8 @@ -/** @file session-command-interface-proxy.hpp - ** This is an implementation fragment, intended to be included into common/interfaceproxy.cpp - ** +/** @file session-command-interface-proxy.cpp + ** Interface-Proxy for the proc::control::SessionCommand facade. ** The purpose is to define a proxy implementation of proc::control::SessionCommand, in order to ** redirect any calls through the associated C Language Interface "lumieraorg_SessionCommand" ** @@ -33,35 +32,30 @@ */ - - - -/* ==================== SessionCommand =================================== */ - #include "include/session-command-facade.h" +#include "include/interfaceproxy.hpp" namespace proc { -namespace control { - - /** storage for the facade proxy factory - * used by client code to invoke through the interface */ - lumiera::facade::Accessor SessionCommand::facade; - -}} // namespace proc::control - - - +namespace control{ + /** static storage for the facade access front-end */ + lib::Depend SessionCommand::facade; +}} namespace lumiera { namespace facade { - typedef InstanceHandle< LUMIERA_INTERFACE_INAME(lumieraorg_SessionCommand, 0) - , proc::control::SessionCommand - > IHandle_SessionCommand; + + + /* ==================== SessionCommand =================================== */ + + using Interface = LUMIERA_INTERFACE_INAME(lumieraorg_SessionCommand, 0); + using Facade = proc::control::SessionCommand; + + using IHandle = InstanceHandle; template<> - class Proxy - : public Holder + class Proxy + : public Binding { //----Proxy-Implementation-of-SessionCommand-------- @@ -72,12 +66,12 @@ namespace facade { public: - Proxy (IHandle const& iha) : THolder(iha) {} + using Binding::Binding; }; - template void openProxy (IHandle_SessionCommand const&); - template void closeProxy (void); + /** emit proxy code here... */ + template + class Link; - -}} // namespace lumiera::facade +}} //namespace facade::lumiera diff --git a/src/proc/control/session-command-service.cpp b/src/proc/control/session-command-service.cpp index 196c1d477..b2d8141a7 100644 --- a/src/proc/control/session-command-service.cpp +++ b/src/proc/control/session-command-service.cpp @@ -188,10 +188,9 @@ namespace control { - using lumiera::facade::LUMIERA_ERROR_FACADE_LIFECYCLE; - typedef lib::SingletonRef::Accessor InstanceRef; + using lumiera::error::LERR_(LIFECYCLE); - InstanceRef _instance; ///< a backdoor for the C Language impl to access the actual SessionCommand implementation... + lib::Depend _instance; ///< a backdoor for the C Language impl to access the actual SessionCommand implementation... @@ -204,33 +203,33 @@ namespace control { const char*, (const char* cmdID, const char* invocationID), { if (!_instance) - return lumiera_error_set(LUMIERA_ERROR_FACADE_LIFECYCLE, cmdID); + return lumiera_error_set (LUMIERA_ERROR_LIFECYCLE, cmdID); else - return _instance->cycle(cmdID, invocationID); + return _instance().cycle(cmdID, invocationID); } ) , LUMIERA_INTERFACE_INLINE (trigger, void, (const char* cmdID, const void* args), { - if (!_instance) lumiera_error_set(LUMIERA_ERROR_FACADE_LIFECYCLE, cmdID); + if (!_instance) lumiera_error_set (LUMIERA_ERROR_LIFECYCLE, cmdID); else - _instance->trigger(cmdID, *static_cast (args)); + _instance().trigger(cmdID, *static_cast (args)); } ) , LUMIERA_INTERFACE_INLINE (bindArg, void, (const char* cmdID, const void* args), { - if (!_instance) lumiera_error_set(LUMIERA_ERROR_FACADE_LIFECYCLE, cmdID); + if (!_instance) lumiera_error_set (LUMIERA_ERROR_LIFECYCLE, cmdID); else - _instance->bindArg(cmdID, *static_cast (args)); + _instance().bindArg(cmdID, *static_cast (args)); } ) , LUMIERA_INTERFACE_INLINE (invoke, void, (const char* cmdID), { - if (!_instance) lumiera_error_set(LUMIERA_ERROR_FACADE_LIFECYCLE, cmdID); + if (!_instance) lumiera_error_set (LUMIERA_ERROR_LIFECYCLE, cmdID); else - _instance->invoke(cmdID); + _instance().invoke(cmdID); } ) ); @@ -241,15 +240,14 @@ namespace control { - - SessionCommandService::SessionCommandService (CommandDispatch& dispatcherLoopInterface) + /** @remark actual instance created from within ProcDispatcher */ + SessionCommandService::SessionCommandService (CommandDispatch& dispatcherLoopInterface) : dispatcher_{dispatcherLoopInterface} , instanceManager_{dispatcher_} - , implInstance_{this,_instance} , serviceInstance_{ LUMIERA_INTERFACE_REF (lumieraorg_SessionCommand, 0, lumieraorg_SessionCommandService)} - { - INFO (gui, "SessionCommand Facade opened."); - } + { + INFO (gui, "SessionCommand Facade opened."); + } diff --git a/src/proc/control/session-command-service.hpp b/src/proc/control/session-command-service.hpp index 73651f05f..f8522c114 100644 --- a/src/proc/control/session-command-service.hpp +++ b/src/proc/control/session-command-service.hpp @@ -46,11 +46,10 @@ #include "proc/control/command-dispatch.hpp" #include "proc/control/command-instance-manager.hpp" #include "common/instancehandle.hpp" -#include "lib/singleton-ref.hpp" #include "lib/diff/gen-node.hpp" +#include "lib/nocopy.hpp" #include "lib/symbol.hpp" -#include @@ -79,32 +78,29 @@ namespace control { */ class SessionCommandService : public SessionCommand - , boost::noncopyable + , util::NonCopyable { CommandDispatch& dispatcher_; CommandInstanceManager instanceManager_; + /* === Interface Lifecycle === */ + + using ServiceInstanceHandle = lumiera::InstanceHandle< LUMIERA_INTERFACE_INAME(lumieraorg_SessionCommand, 0) + , SessionCommand + >; + ServiceInstanceHandle serviceInstance_; + + public: + SessionCommandService (CommandDispatch& dispatcherLoopInterface); + + /* === Implementation of the Facade Interface === */ Symbol cycle (Symbol cmdID, string const& invocationID) override; void trigger (Symbol cmdID, Rec const& args) override; void bindArg (Symbol cmdID, Rec const& args) override; void invoke (Symbol cmdID) override; - - - /* === Interface Lifecycle === */ - - typedef lumiera::InstanceHandle< LUMIERA_INTERFACE_INAME(lumieraorg_SessionCommand, 0) - , SessionCommand - > ServiceInstanceHandle; - - lib::SingletonRef implInstance_; - ServiceInstanceHandle serviceInstance_; - - public: - SessionCommandService (CommandDispatch& dispatcherLoopInterface); - }; diff --git a/src/proc/control/stypemanager.hpp b/src/proc/control/stypemanager.hpp index 1706aece5..67d812fd9 100644 --- a/src/proc/control/stypemanager.hpp +++ b/src/proc/control/stypemanager.hpp @@ -86,11 +86,11 @@ namespace control { STypeManager() ; ~STypeManager(); - friend class lib::DependencyFactory; + friend class lib::DependencyFactory; /** Lifecycle: reset all type registration information * to the generic pristine default state. This includes - * hard wired defaults and defauls provided by type plugins, but + * hard wired defaults and defaults provided by type plugins, but * excludes everything added by the session */ void reset() ; diff --git a/src/proc/control/styperegistry.hpp b/src/proc/control/styperegistry.hpp index ba5e6f24c..3c1963bff 100644 --- a/src/proc/control/styperegistry.hpp +++ b/src/proc/control/styperegistry.hpp @@ -38,6 +38,7 @@ #include "proc/control/stypemanager.hpp" +#include "lib/nocopy.hpp" //#include "common/query.hpp" //#include "lib/util.hpp" //#include "lib/p.hpp" @@ -45,7 +46,6 @@ //#include //#include //#include -#include namespace proc { @@ -72,7 +72,7 @@ namespace control { * exact behaviour has to be defined. */ class STypeManager::Registry - : boost::noncopyable + : util::NonCopyable { public: diff --git a/src/proc/engine/buffer-metadata.hpp b/src/proc/engine/buffer-metadata.hpp index fa735e0e8..90a5589df 100644 --- a/src/proc/engine/buffer-metadata.hpp +++ b/src/proc/engine/buffer-metadata.hpp @@ -61,9 +61,9 @@ #include "include/logging.h" #include "proc/engine/type-handler.hpp" #include "proc/engine/buffer-local-key.hpp" +#include "lib/nocopy.hpp" #include -#include namespace proc { @@ -126,8 +126,8 @@ namespace engine { namespace metadata { - using error::LUMIERA_ERROR_LIFECYCLE; - using error::LUMIERA_ERROR_BOTTOM_VALUE; + using error::LERR_(LIFECYCLE); + using error::LERR_(BOTTOM_VALUE); namespace { // details of hash calculation template @@ -242,7 +242,7 @@ namespace engine { { if (nontrivial(this->instanceFunc_)) throw error::Logic ("unable to supersede an already attached TypeHandler" - , LUMIERA_ERROR_LIFECYCLE); + , LERR_(LIFECYCLE)); instanceFunc_ = ref.instanceFunc_; } @@ -399,7 +399,7 @@ namespace engine { if (NIL == state_) throw error::Fatal ("Buffer metadata entry with state==NIL encountered." "State transition logic broken (programming error)" - , LUMIERA_ERROR_LIFECYCLE); + , LERR_(LIFECYCLE)); } void @@ -409,7 +409,7 @@ namespace engine { throw error::Logic ("Buffer is inaccessible (marked as free). " "Need a new buffer pointer in order to lock an entry. " "You should invoke markLocked(buffer) prior to access." - , LUMIERA_ERROR_LIFECYCLE ); + , LERR_(LIFECYCLE)); } void @@ -417,7 +417,7 @@ namespace engine { { if (FREE != state_) throw error::Logic ("Buffer already in use" - , LUMIERA_ERROR_LIFECYCLE ); + , LERR_(LIFECYCLE)); REQUIRE (!buffer_, "Buffer marked as free, " "but buffer pointer is set."); } @@ -540,7 +540,7 @@ namespace engine { * when \em locking or \em freeing the corresponding buffer. */ class BufferMetadata - : boost::noncopyable + : util::NonCopyable { Literal id_; HashVal family_; diff --git a/src/proc/engine/buffer-provider.cpp b/src/proc/engine/buffer-provider.cpp index 9a25711a8..59bb5c215 100644 --- a/src/proc/engine/buffer-provider.cpp +++ b/src/proc/engine/buffer-provider.cpp @@ -211,7 +211,7 @@ namespace engine { REQUIRE (!metaEntry.isTypeKey()); if (!metaEntry.isLocked()) throw error::Logic ("unable to attach an object because buffer isn't locked for use" - , LUMIERA_ERROR_LIFECYCLE); + , LERR_(LIFECYCLE)); metaEntry.useTypeHandlerFrom (refEntry); // EX_STRONG } @@ -307,8 +307,7 @@ namespace engine { BuffHandle::takeOwnershipFor(BufferDescriptor const& type) { if (!this->isValid()) - throw error::Logic ("attaching an object requires an buffer in locked state" - , LUMIERA_ERROR_LIFECYCLE); + throw error::Logic ("attaching an object requires an buffer in locked state", LERR_(LIFECYCLE)); if (this->size() < type.determineBufferSize()) throw error::Logic ("insufficient buffer size to hold an instance of that type"); diff --git a/src/proc/engine/buffer-provider.hpp b/src/proc/engine/buffer-provider.hpp index fd4a4db5f..610573473 100644 --- a/src/proc/engine/buffer-provider.hpp +++ b/src/proc/engine/buffer-provider.hpp @@ -48,8 +48,8 @@ #include "proc/engine/buffhandle.hpp" #include "proc/engine/type-handler.hpp" #include "proc/engine/buffer-local-key.hpp" +#include "lib/nocopy.hpp" -#include #include @@ -79,7 +79,7 @@ namespace engine { * @todo as of 11/11 thread safety within the engine remains to be clarified ///////////////////////////TICKET #854 */ class BufferProvider - : boost::noncopyable + : util::NonCopyable { unique_ptr meta_; diff --git a/src/proc/engine/buffhandle-attach.hpp b/src/proc/engine/buffhandle-attach.hpp index f0447bcad..9a26aad06 100644 --- a/src/proc/engine/buffhandle-attach.hpp +++ b/src/proc/engine/buffhandle-attach.hpp @@ -120,7 +120,7 @@ namespace engine { { if (!pBuffer_) throw error::Logic ("buffer not (yet) locked for access by clients" - , LUMIERA_ERROR_LIFECYCLE); + , LERR_(LIFECYCLE)); return *reinterpret_cast (pBuffer_); } diff --git a/src/proc/engine/buffhandle.hpp b/src/proc/engine/buffhandle.hpp index 6a631d5ca..fb437f2db 100644 --- a/src/proc/engine/buffhandle.hpp +++ b/src/proc/engine/buffhandle.hpp @@ -60,7 +60,7 @@ namespace proc { namespace engine { namespace error = lumiera::error; - using error::LUMIERA_ERROR_LIFECYCLE; + using error::LERR_(LIFECYCLE); using lib::HashVal; diff --git a/src/proc/engine/bufftable-obsolete.hpp b/src/proc/engine/bufftable-obsolete.hpp index 7cb1b17ba..fad9c3a80 100644 --- a/src/proc/engine/bufftable-obsolete.hpp +++ b/src/proc/engine/bufftable-obsolete.hpp @@ -32,10 +32,10 @@ #include "lib/error.hpp" +#include "lib/nocopy.hpp" #include "proc/engine/channel-descriptor.hpp" #include "proc/engine/procnode.hpp" -#include #include #include @@ -157,7 +157,7 @@ namespace engine { */ class BuffTableChunk : public BuffTable, - boost::noncopyable + util::NonCopyable { const uint siz_; BuffTable::Chunk tab_; diff --git a/src/proc/engine/bufftable.hpp b/src/proc/engine/bufftable.hpp index a0f1289f8..6199f662c 100644 --- a/src/proc/engine/bufftable.hpp +++ b/src/proc/engine/bufftable.hpp @@ -39,7 +39,6 @@ #include "proc/engine/procnode.hpp" #include "lib/iter-adapter.hpp" -#include #include #include diff --git a/src/proc/engine/calc-plan-continuation.hpp b/src/proc/engine/calc-plan-continuation.hpp index ba45ca6c1..dda85c2df 100644 --- a/src/proc/engine/calc-plan-continuation.hpp +++ b/src/proc/engine/calc-plan-continuation.hpp @@ -43,8 +43,6 @@ #include "proc/play/timings.hpp" #include "backend/engine/job.h" -#include - namespace proc { namespace engine { diff --git a/src/proc/engine/calc-stream.hpp b/src/proc/engine/calc-stream.hpp index b39b89a75..7b64bb137 100644 --- a/src/proc/engine/calc-stream.hpp +++ b/src/proc/engine/calc-stream.hpp @@ -54,7 +54,6 @@ //#include "common/instancehandle.hpp" //#include "lib/singleton-ref.hpp" // -//#include //#include #include diff --git a/src/proc/engine/channel-descriptor.hpp b/src/proc/engine/channel-descriptor.hpp index a252f8da9..4b155a153 100644 --- a/src/proc/engine/channel-descriptor.hpp +++ b/src/proc/engine/channel-descriptor.hpp @@ -45,7 +45,7 @@ namespace proc { namespace engine { namespace error = lumiera::error; - using error::LUMIERA_ERROR_LIFECYCLE; + using error::LERR_(LIFECYCLE); using lib::HashVal; diff --git a/src/proc/engine/diagnostic-buffer-provider.hpp b/src/proc/engine/diagnostic-buffer-provider.hpp index f38d4bf6f..7e74c77da 100644 --- a/src/proc/engine/diagnostic-buffer-provider.hpp +++ b/src/proc/engine/diagnostic-buffer-provider.hpp @@ -35,8 +35,8 @@ #include "lib/util.hpp" #include "proc/engine/type-handler.hpp" #include "proc/engine/buffer-provider.hpp" +#include "lib/nocopy.hpp" -#include #include @@ -59,7 +59,7 @@ namespace engine { * @todo write type comment */ class DiagnosticBufferProvider - : boost::noncopyable + : util::NonCopyable { std::unique_ptr pImpl_; @@ -73,7 +73,7 @@ namespace engine { DiagnosticBufferProvider(); ~DiagnosticBufferProvider(); - friend class lib::DependencyFactory; + friend class lib::DependencyFactory; public: /** build a new Diagnostic Buffer Provider instance, diff --git a/src/proc/engine/dispatcher.hpp b/src/proc/engine/dispatcher.hpp index 9065ad0c4..2b61ce5af 100644 --- a/src/proc/engine/dispatcher.hpp +++ b/src/proc/engine/dispatcher.hpp @@ -43,7 +43,6 @@ #include "proc/engine/job-planning.hpp" #include "lib/time/timevalue.hpp" -#include #include diff --git a/src/proc/engine/engine-diagnostics.hpp b/src/proc/engine/engine-diagnostics.hpp index f53d460cd..1f0020ac3 100644 --- a/src/proc/engine/engine-diagnostics.hpp +++ b/src/proc/engine/engine-diagnostics.hpp @@ -38,6 +38,7 @@ #include "lib/error.hpp" +#include "lib/nocopy.hpp" #include "proc/engine/engine-service.hpp" //#include "include/dummy-player-facade.h" //#include "include/display-facade.h" @@ -50,7 +51,6 @@ //#include "lib/polymorphic-value.hpp" //#include "lib/depend.hpp" // -#include //#include @@ -79,7 +79,7 @@ namespace engine{ * of EngineDiagnostics may be used. */ class EngineDiagnostics - : boost::noncopyable + : util::NonCopyable { EngineService& engine_; diff --git a/src/proc/engine/engine-service-mock.hpp b/src/proc/engine/engine-service-mock.hpp index 85cc461bf..b04223790 100644 --- a/src/proc/engine/engine-service-mock.hpp +++ b/src/proc/engine/engine-service-mock.hpp @@ -52,8 +52,6 @@ //#include "lib/singleton-ref.hpp" #include "proc/engine/engine-service.hpp" #include "lib/scoped-ptrvect.hpp" -// -#include namespace proc { diff --git a/src/proc/engine/engine-service.cpp b/src/proc/engine/engine-service.cpp index 7269e541e..56a7e8362 100644 --- a/src/proc/engine/engine-service.cpp +++ b/src/proc/engine/engine-service.cpp @@ -43,7 +43,7 @@ namespace engine{ using std::function; using std::bind; using std::ref; - using lib::transform; + using lib::iter_source::transform; using lib::append_all; diff --git a/src/proc/engine/engine-service.hpp b/src/proc/engine/engine-service.hpp index 41d965339..f27cbd218 100644 --- a/src/proc/engine/engine-service.hpp +++ b/src/proc/engine/engine-service.hpp @@ -53,6 +53,7 @@ #include "lib/error.hpp" +#include "lib/nocopy.hpp" //#include "include/dummy-player-facade.h" //#include "include/display-facade.h" #include "proc/engine/calc-stream.hpp" @@ -63,8 +64,7 @@ //#include "lib/singleton-ref.hpp" #include "lib/polymorphic-value.hpp" #include "lib/depend.hpp" -// -#include + #include //#include @@ -98,7 +98,7 @@ namespace engine{ * @ingroup engine */ class EngineService - : boost::noncopyable + : util::NonCopyable { diff --git a/src/proc/engine/frame-coord.hpp b/src/proc/engine/frame-coord.hpp index cc6f5a940..57c1723cc 100644 --- a/src/proc/engine/frame-coord.hpp +++ b/src/proc/engine/frame-coord.hpp @@ -106,7 +106,7 @@ namespace engine { * backed by the Segmentation (i.e. the render nodes network). */ class FrameSequencer - : boost::noncopyable + : util::NonCopyable { public: diff --git a/src/proc/engine/job-planning.hpp b/src/proc/engine/job-planning.hpp index 8634d650d..011a0e0ff 100644 --- a/src/proc/engine/job-planning.hpp +++ b/src/proc/engine/job-planning.hpp @@ -27,7 +27,7 @@ ** into a sequence of planning steps. The net result is to present a sequence of job planing ** to the user, while actually encapsulating a depth-first tree exploration, which proceeds on demand. ** - ** \par participating elements + ** ## participating elements ** All of these job planning operations are implemented on top of the JobTicket. This is where to look ** for "actual" implementation code. Here, within this header, the following entities cooperate to ** create a simple sequence out of this implementation level tasks: @@ -37,20 +37,20 @@ ** - proc::engine::expandPrerequisites(JobPlanning const&) is the operation to explore further prerequisite Jobs recursively ** - PlanningStepGenerator yields the underlying "master beat": a sequence of frame locations to be planned ** - ** \par how the PlanningState (sequence) is advanced + ** ## how the PlanningState (sequence) is advanced ** PlanningState is an iterator to expose a sequence of JobPlanning elements. On the implementation level, ** there is always just a single JobPlanning element, which represents the \em current element; this element ** lives as "state core" within the PlanningState object. Advancing to the next JobPlanning element (i.e. to ** consider the next job or prerequisite job to be planned for scheduling) is performed through the iteration - ** control API exposed by JobPlanning (the free functions \c checkPoint, \c yield and \c iterNext. Actually, + ** control API exposed by JobPlanning (the functions `checkPoint()`, `yield()` and `iterNext()`. Actually, ** these functions are invoked through the depth-first tree exploration performed by JobPlaningSequence. ** The implementation of these invocations can be found within the IterExplorer strategy ** lib::iter_explorer::RecursiveSelfIntegration. The net result is - ** - the current element is always accessed through \c yield + ** - the current element is always accessed through `yield()` ** - advancing to the next element happens \em either ** - ** - by invoking \c iterNext (when processing a sequence of sibling job prerequisites) - ** - by invoking \c integrate (when starting to explore the next level of children) + ** - by invoking `iterNext()` (when processing a sequence of sibling job prerequisites) + ** - by invoking `integrate()` (when starting to explore the next level of children) ** ** ** @see DispatcherInterface_test simplified usage examples @@ -175,25 +175,25 @@ namespace engine { /* === Iteration control API for IterStateWrapper== */ - friend bool - checkPoint (JobPlanning const& plan) - { - return not isnil (plan.plannedOperations_); - } + bool + checkPoint () const + { + return not isnil (plannedOperations_); + } - friend JobPlanning& - yield (JobPlanning const& plan) - { - REQUIRE (checkPoint (plan)); - return unConst(plan); - } + JobPlanning& + yield () const + { + REQUIRE (checkPoint()); + return unConst(*this); + } - friend void - iterNext (JobPlanning & plan) - { - plan.plannedOperations_.pullNext(); - plan.plannedOperations_.markTreeLocation(); - } + void + iterNext () + { + plannedOperations_.pullNext(); + plannedOperations_.markTreeLocation(); + } }; @@ -363,35 +363,35 @@ namespace engine { /* === Iteration control API for IterStateWrapper== */ - friend bool - checkPoint (PlanningStepGenerator const& gen) - { - return gen.currentLocation_.isDefined(); - } // might indicate end of this planning chunk (or of playback altogether) + bool + checkPoint () const + { + return currentLocation_.isDefined(); + } // might indicate end of this planning chunk (or of playback altogether) - friend JobPlanning& - yield (PlanningStepGenerator const& gen) - { - ENSURE (checkPoint (gen)); - return unConst(gen).nextEvaluation_; - } + JobPlanning& + yield () const + { + ENSURE (checkPoint()); + return unConst(this)->nextEvaluation_; + } - friend void - iterNext (PlanningStepGenerator & gen) - { - if (gen.locationGenerator_->canContinue (gen.currentLocation_)) - { - gen.currentLocation_ = gen.locationGenerator_->getNextFrame (gen.currentLocation_); - gen.use_current_location_as_starting_point_for_planning(); - ENSURE (checkPoint (gen)); - } - else - { // indicate end-of playback or a jump to another playback position - gen.currentLocation_ = FrameCoord(); - } - } + void + iterNext () + { + if (locationGenerator_->canContinue (currentLocation_)) + { + currentLocation_ = locationGenerator_->getNextFrame (currentLocation_); + this->use_current_location_as_starting_point_for_planning(); + ENSURE (this->checkPoint()); + } + else + { // indicate end-of playback or a jump to another playback position + currentLocation_ = FrameCoord(); + } + } }; diff --git a/src/proc/engine/job-ticket.hpp b/src/proc/engine/job-ticket.hpp index 25330979a..80d23f4d1 100644 --- a/src/proc/engine/job-ticket.hpp +++ b/src/proc/engine/job-ticket.hpp @@ -43,7 +43,6 @@ #include "lib/iter-adapter.hpp" #include "lib/util.hpp" -#include #include @@ -83,7 +82,7 @@ using util::isnil; * @todo 1/12 WIP-WIP-WIP defining the invocation sequence and render jobs */ class JobTicket - : boost::noncopyable + : util::NonCopyable { struct Provision { diff --git a/src/proc/engine/nodewiring-def.hpp b/src/proc/engine/nodewiring-def.hpp index cdd4f5f6d..eea639136 100644 --- a/src/proc/engine/nodewiring-def.hpp +++ b/src/proc/engine/nodewiring-def.hpp @@ -43,8 +43,7 @@ #include "proc/engine/procnode.hpp" #include "lib/ref-array.hpp" #include "lib/util-foreach.hpp" - -#include +#include "lib/nocopy.hpp" @@ -70,7 +69,7 @@ namespace engine { * should be used, without adding any connection information. */ class WiringSituation - : boost::noncopyable + : util::NonCopyable { long flags_; asset::Proc::ProcFunc* function_; diff --git a/src/proc/engine/nodewiring.hpp b/src/proc/engine/nodewiring.hpp index ef3692e4b..e493e6ca8 100644 --- a/src/proc/engine/nodewiring.hpp +++ b/src/proc/engine/nodewiring.hpp @@ -35,9 +35,7 @@ #include "lib/allocation-cluster.hpp" #include "proc/engine/nodewiring-def.hpp" -//#include #include -//#include diff --git a/src/proc/engine/procnode.hpp b/src/proc/engine/procnode.hpp index 5e0e4167b..48f99a153 100644 --- a/src/proc/engine/procnode.hpp +++ b/src/proc/engine/procnode.hpp @@ -50,7 +50,6 @@ #include "lib/ref-array.hpp" #include -#include @@ -134,7 +133,7 @@ namespace engine { * ProcNode as a frontend entirely. */ class ProcNode - : boost::noncopyable + : util::NonCopyable { typedef mobject::Parameter Param; //////TODO: just a placeholder for automation as of 6/2008 vector params; diff --git a/src/proc/engine/tracking-heap-block-provider.cpp b/src/proc/engine/tracking-heap-block-provider.cpp index a0d4d34dc..5b88f995e 100644 --- a/src/proc/engine/tracking-heap-block-provider.cpp +++ b/src/proc/engine/tracking-heap-block-provider.cpp @@ -28,18 +28,15 @@ #include "lib/error.hpp" #include "include/logging.h" -//#include "lib/meta/function.hpp" #include "lib/scoped-ptrvect.hpp" #include "lib/scoped-holder.hpp" #include "lib/util-foreach.hpp" #include "proc/engine/tracking-heap-block-provider.hpp" -#include #include #include -//using util::for_each; using util::and_all; using std::vector; using lib::ScopedHolder; diff --git a/src/proc/engine/tracking-heap-block-provider.hpp b/src/proc/engine/tracking-heap-block-provider.hpp index e352ce604..8ad98ef6f 100644 --- a/src/proc/engine/tracking-heap-block-provider.hpp +++ b/src/proc/engine/tracking-heap-block-provider.hpp @@ -71,7 +71,7 @@ namespace engine { * to store some additional tracking information. */ class Block - : boost::noncopyable + : util::NonCopyable { unique_ptr storage_; diff --git a/src/proc/engine/type-handler.hpp b/src/proc/engine/type-handler.hpp index 4ceb80abb..29cef8017 100644 --- a/src/proc/engine/type-handler.hpp +++ b/src/proc/engine/type-handler.hpp @@ -98,7 +98,7 @@ namespace engine { * will be prepared on locking and cleanup will be invoked * automatically when releasing. * @warning comparison and hash values rely on internals of the - * tr1::function implementation and might not be 100% accurate + * std::function implementation and might not be 100% accurate */ struct TypeHandler { diff --git a/src/proc/engine/worker/tick-service.hpp b/src/proc/engine/worker/tick-service.hpp index 5ee6e3926..837b20167 100644 --- a/src/proc/engine/worker/tick-service.hpp +++ b/src/proc/engine/worker/tick-service.hpp @@ -41,6 +41,8 @@ #include "lib/error.hpp" #include "backend/thread-wrapper.hpp" +#include // for usleep() + #include #include diff --git a/src/proc/mobject/builder/fixture-change-detector.hpp b/src/proc/mobject/builder/fixture-change-detector.hpp index 6c1065396..e1800d02c 100644 --- a/src/proc/mobject/builder/fixture-change-detector.hpp +++ b/src/proc/mobject/builder/fixture-change-detector.hpp @@ -66,7 +66,7 @@ namespace builder { * TODO type comment */ class FixtureChangeDetector - : boost::noncopyable + : util::NonCopyable { typedef ID PID; diff --git a/src/proc/mobject/builder/model-port-registry.hpp b/src/proc/mobject/builder/model-port-registry.hpp index e836c2289..bf913be38 100644 --- a/src/proc/mobject/builder/model-port-registry.hpp +++ b/src/proc/mobject/builder/model-port-registry.hpp @@ -78,7 +78,7 @@ namespace builder { * new model is complete and ready for use. */ class ModelPortRegistry - : boost::noncopyable + : util::NonCopyable { typedef ID PID; diff --git a/src/proc/mobject/builder/toolfactory.cpp b/src/proc/mobject/builder/toolfactory.cpp index 81215d154..ed9815667 100644 --- a/src/proc/mobject/builder/toolfactory.cpp +++ b/src/proc/mobject/builder/toolfactory.cpp @@ -30,7 +30,6 @@ #include "proc/mobject/builder/toolfactory.hpp" #include "lib/util.hpp" -#include #include namespace proc { diff --git a/src/proc/mobject/mobject-ref.hpp b/src/proc/mobject/mobject-ref.hpp index f45d8fe31..282ff0efa 100644 --- a/src/proc/mobject/mobject-ref.hpp +++ b/src/proc/mobject/mobject-ref.hpp @@ -109,7 +109,7 @@ namespace mobject { { if (!smPtr_) throw error::State("Lifecycle error: MObject ref not activated" - ,LUMIERA_ERROR_BOTTOM_MOBJECTREF); + , LERR_(BOTTOM_MOBJECTREF)); ENSURE (INSTANCEOF (MO, smPtr_.get())); return smPtr_.operator-> (); @@ -120,7 +120,7 @@ namespace mobject { { if (!isValid()) throw error::State("Accessing inactive MObject ref" - ,LUMIERA_ERROR_BOTTOM_MOBJECTREF); + , LERR_(BOTTOM_MOBJECTREF)); ENSURE (INSTANCEOF (MO, smPtr_.get())); return *pRef_; @@ -167,7 +167,7 @@ namespace mobject { { if (!isValid()) throw error::State("Attempt to attach a child to an inactive MObject ref" - , LUMIERA_ERROR_BOTTOM_MOBJECTREF); + , LERR_(BOTTOM_MOBJECTREF)); MORef newInstance; PlacementMO::ID thisScope = pRef_; return newInstance.activate ( diff --git a/src/proc/mobject/mobject.hpp b/src/proc/mobject/mobject.hpp index 214ec19ff..9f6fca05b 100644 --- a/src/proc/mobject/mobject.hpp +++ b/src/proc/mobject/mobject.hpp @@ -46,7 +46,6 @@ #include "lib/p.hpp" #include "lib/time/timevalue.hpp" -#include #include #include @@ -79,7 +78,7 @@ namespace mobject { */ class MObject : public Buildable, - boost::noncopyable, + util::NonCopyable, boost::equality_comparable< MObject > { protected: diff --git a/src/proc/mobject/output-designation.hpp b/src/proc/mobject/output-designation.hpp index c9469bdf8..ccbf18b55 100644 --- a/src/proc/mobject/output-designation.hpp +++ b/src/proc/mobject/output-designation.hpp @@ -80,11 +80,11 @@ namespace mobject { class OutputDesignation { public: - typedef asset::ID PID; - typedef asset::PPipe PPipe; + using PID = asset::ID; + using PPipe = asset::PPipe; explicit OutputDesignation (PID explicitTarget); - explicit OutputDesignation (RefPlacement const& indirectTarget); + explicit OutputDesignation (RefPlacement const& indirectTarget); explicit OutputDesignation (uint relative_busNr =0); // using default copying @@ -93,8 +93,8 @@ namespace mobject { /** retrieve the direct destination * this descriptor is actually pointing to. * In case of a target pipe not explicitly specified - * this might involve a resolution step and take the - * current context into account. + * this might involve a resolution step and take the + * current context into account. * @param origin starting point for figuring out connections * @return a pipe-ID, which should be used as next connection. * This might not be the final designation, but the @@ -121,7 +121,7 @@ namespace mobject { { VTABLE = sizeof(size_t) , SPEC_SIZ = VTABLE + mp::maxSize< - mp::Types< PID, lumiera_uid, uint>::List>::value + mp::Types< PID, lumiera_uid, uint>::List>::value }; typedef lib::OpaqueHolder SpecBuff; diff --git a/src/proc/mobject/output-mapping.hpp b/src/proc/mobject/output-mapping.hpp index 5667c2002..1cdec240c 100644 --- a/src/proc/mobject/output-mapping.hpp +++ b/src/proc/mobject/output-mapping.hpp @@ -40,7 +40,7 @@ ** values, which typically are interpreted as asset::ID. ** But the actual mapping result is retrieved on each access ** by invoking a functor on the stored hash value, - ** thus the final resolution is done \em late. + ** thus the final resolution is done _late_. ** ** @see OutputDesignation ** @see OutputMapping_test @@ -68,7 +68,7 @@ namespace mobject { namespace { // Helper to extract and rebind definition types - /** + /** * @internal used by OutputMapping * to figure out the mapping target type */ @@ -86,11 +86,11 @@ namespace mobject { typedef RET Res; }; - typedef typeof(&DEF::output) OutputMappingMemberFunc; // GCC extension: "typeof" - typedef Rebind Rebinder; + using OutputMappingMemberFunc = decltype(&DEF::output) ; + using Rebinder = Rebind; public: - typedef typename Rebinder::Res Target; + using Target = typename Rebinder::Res; }; }//(End) type rebinding helper @@ -111,7 +111,7 @@ namespace mobject { * may be derived by querying the OutputMapping. Here, the kind of * the target specification is defined through the type parameter. * - * \par definition of specific mapping behaviour + * ## definition of specific mapping behaviour * * This is an generic map-like container, acting as Interface to be used * in the signature of API functions either providing or requiring a Mapping. @@ -119,11 +119,12 @@ namespace mobject { * be created, providing a definition context as template parameter. * Instances of this concrete mapping type may then be default constructed * and copied freely. The definition context is supposed to provide - * - a functor \c DEF::output usable as function pipe-ID --> Target + * - a functor `DEF::output` usable as function pipe-ID --> Target * - the concrete output-functor also defines the concrete Target type, * which will be returned when accessing the OutputMapping - * - a function \c DEF::buildQuery(sourcePipeID,seqNr) yielding a (defaults) + * - a function `DEF::buildQuery (sourcePipeID,seqNr)` yielding a (defaults) * query to be issued in case of accessing a non existent mapping + * @tparam DEF static (compile-time) definition/configuration context */ template class OutputMapping @@ -131,14 +132,14 @@ namespace mobject { { typedef _def Setup; - typedef asset::ID PId; - typedef asset::PPipe PPipe; + using PId = asset::ID; + using PPipe = asset::PPipe; /* == mapping table storage == */ std::map table_; public: - typedef typename Setup::Target Target; + using Target = typename Setup::Target; // using default ctor and copy operations @@ -153,7 +154,7 @@ namespace mobject { * It is created on the stack by the OutputMapping container and internally wired * back to the container and the actually stored value (pipe-ID-hash) in the table. * Actually retrieving the result value by the client code triggers invocation - * of the specific resolution functor, embedded in the definition context DEF, + * of the specific resolution functor, embedded in the definition context `DEF`, * which was given when instantiating the OutputMapping template. * @note depends on the template parameter of the enclosing OutputMapping type! */ @@ -186,7 +187,7 @@ namespace mobject { public: /** explicitly define a new target ID for this individual mapping * @note the actually returned result depends on what the configured - * \c DEF::output functor will yield when invoked on this ID + * `DEF::output` functor will yield when invoked on this ID */ void operator= (PId newId2map) @@ -209,15 +210,15 @@ namespace mobject { /** actually retrieve the target object of the mapping. * This operation is invoked when client code accesses - * the result of an OutputMapping query. - * @return result of invoking the configured \c DEF::output functor - * @throw error::Logic when resolving an \em unconnected mapping + * the result of an OutputMapping query. + * @return result of invoking the configured `DEF::output` functor + * @throw error::Logic when resolving an _unconnected_ mapping */ operator Target() { - if (!isValid()) + if (not isValid()) throw error::Logic ("attempt to resolve an unconnected output mapping" - , error::LUMIERA_ERROR_UNCONNECTED); + , error::LERR_(UNCONNECTED)); return resolve(); } @@ -239,13 +240,13 @@ namespace mobject { friend bool operator== (Resolver const& a, Resolver const& b) { - return a.pID_ == b.pID_; + return a.pID_ == b.pID_; } // note: Resolver depends on template parameter DEF // All instances of DEF are considered equivalent! friend bool operator== (Resolver const& rr, Target const& tval) { - return rr.resolve() == tval; + return rr.resolve() == tval; } }; @@ -267,7 +268,7 @@ namespace mobject { contains (PPipe sourcePipe) { return !sourcePipe - || this->contains (sourcePipe->getID()); + or this->contains (sourcePipe->getID()); } @@ -327,7 +328,7 @@ namespace mobject { inline typename OutputMapping::Resolver OutputMapping::operator[] (PId sourcePipeID) { - if (!contains (sourcePipeID)) + if (not contains (sourcePipeID)) { // issue a defaults query to resolve this mapping first Query query4pipe = DEF::buildQuery (sourcePipeID); @@ -352,29 +353,29 @@ namespace mobject { * instead of just picking a mapped pipe (which is the default usage). * Accessing the OutputMapping this way by query enables all kinds of * extended usages: It suffices that the given query somehow yields a Pipe, - * which then is considered the mapped result and handed over to the \c DEF::output + * which then is considered the mapped result and handed over to the `DEF::output` * functor for resolution to a result object to be returned. * - * \par Query for the Nth default instance + * ## Query for the Nth default instance * OutputMapping provides a special behaviour for retrieving "the Nth default pipe". * The rationale being the request for connection to the Nth bus of a given kind, like * e.g. the 3rd audio subgroup or the 2nd video master. This special behaviour is triggered - * by the predicate "ord(##)" in the query. The \em remainder of the query is supposed to - * designate a \em default in this case, rather then querying directly for the result of - * the mapping. Thus this remainder of the query is used to retrieve a \em source pipe, + * by the predicate "ord(##)" in the query. The _remainder of the query_ is supposed to + * designate a _default_ in this case, rather then querying directly for the result of + * the mapping. Thus this remainder of the query is used to retrieve a _source pipe_, * which then is treated as if accessing a non-existent mapping: a suitable default * solution for this mapping is retrieved, but in this special case, we append the * given sequence number to the ID of the retrieved pipe, i.e. we get the Nth - * (identical) solution to the aforementioned query for a default pipe. + * (identical) solution to the aforementioned query for a default pipe. * * @note the mapped result is remembered within this mapping. Further invocations - * with the \em same query will just fetch this stored pipe-ID and hand it + * with the _same query_ will just fetch this stored pipe-ID and hand it * to the functor, without resolving the query again. You might want to - * \link Resolver::disconnect remove \endlink this specific mapping - * in order to force re-evaluation of the query. + * [remove](\ref Resolver::disconnect) this specific mapping + * in order to force re-evaluation of the query. * @param Query for a pipe, which is handed over as-is to the rules engine. * @warning depending on the actual query, there might be no solution, - * in which case an \em unconnected marker is retrieved and + * in which case an _unconnected marker_ is retrieved and * stored. Thus the yielded Resolver should be checked, * if in doubt. */ @@ -383,23 +384,28 @@ namespace mobject { OutputMapping::operator[] (Query query4pipe) { HashVal hash4query = _mapping::slot (query4pipe); - if (!contains (hash4query)) + if (not contains (hash4query)) { + // need to resolve this query first + auto addEntry = [&](auto query) + { + table_[hash4query] = _mapping::resolveQuery (query); + }; + if (uint seqNr = _mapping::is_defaults_query_with_channel (query4pipe)) { // treat the special case - // when actually requesting the "Nth default of this kind" + // when actually requesting the "Nth default of this kind" PPipe corresponding_sourcePipe = asset::Struct::retrieve ( _mapping::build_corresponding_sourceQuery (query4pipe)); ENSURE (corresponding_sourcePipe); PId sourcePipeID = corresponding_sourcePipe->getID(); - query4pipe = DEF::buildQuery (sourcePipeID, seqNr); + addEntry (DEF::buildQuery (sourcePipeID, seqNr)); } - - // need to resolve this query first - table_[hash4query] = _mapping::resolveQuery (query4pipe); + else + addEntry (query4pipe); } ENSURE (this->contains (hash4query)); diff --git a/src/proc/mobject/placement-ref.hpp b/src/proc/mobject/placement-ref.hpp index a9e11caeb..100c6633b 100644 --- a/src/proc/mobject/placement-ref.hpp +++ b/src/proc/mobject/placement-ref.hpp @@ -271,14 +271,14 @@ namespace proc { { if (!placementID) throw error::Logic ("Attempt to access a NIL PlacementRef" - ,LUMIERA_ERROR_BOTTOM_PLACEMENTREF); + , LERR_(BOTTOM_PLACEMENTREF)); Placement & genericPlacement (session::SessionServiceFetch::resolveID (placementID)); // may throw REQUIRE (genericPlacement.isValid()); if (!(genericPlacement.template isCompatible())) throw error::Invalid("actual type of the resolved placement is incompatible" - , LUMIERA_ERROR_INVALID_PLACEMENTREF); + , LERR_(INVALID_PLACEMENTREF)); ////////////////////////TODO: 1. better message, including type? ////////////////////////TODO: 2. define a separate error-ID for the type mismatch! diff --git a/src/proc/mobject/session.hpp b/src/proc/mobject/session.hpp index add3a7500..ce0e9355d 100644 --- a/src/proc/mobject/session.hpp +++ b/src/proc/mobject/session.hpp @@ -58,7 +58,6 @@ #include "lib/symbol.hpp" #include "lib/p.hpp" -#include #include @@ -106,7 +105,7 @@ namespace mobject { * */ class Session - : boost::noncopyable + : util::NonCopyable { protected: typedef lumiera::query::DefsManager& DefaultsAccess; @@ -147,6 +146,7 @@ namespace mobject { extern const char* ON_SESSION_START; ///< triggered before loading any content into a newly created session extern const char* ON_SESSION_INIT; ///< triggered when initialising a new session, after adding content extern const char* ON_SESSION_READY; ///< triggered after session is completely functional and all APIs are open. + extern const char* ON_SESSION_CLOSE; ///< triggered before initiating the session shutdown sequence extern const char* ON_SESSION_END; ///< triggered before discarding an existing session @@ -156,7 +156,8 @@ namespace mobject { * creation, access and Session lifecycle Interface. * An instance is accessible via Session::current */ - class SessManager : private boost::noncopyable + class SessManager + : util::NonCopyable { public: /** diagnostics: session interface opened? */ diff --git a/src/proc/mobject/session/dummy-session-connection.cpp b/src/proc/mobject/session/dummy-session-connection.cpp index d2fefe739..edff76ccd 100644 --- a/src/proc/mobject/session/dummy-session-connection.cpp +++ b/src/proc/mobject/session/dummy-session-connection.cpp @@ -40,7 +40,6 @@ //#include "include/logging.h" #include "proc/mobject/session/dummy-session-connection.hpp" -//#include #include #include diff --git a/src/proc/mobject/session/dummy-session-connection.hpp b/src/proc/mobject/session/dummy-session-connection.hpp index e91a21598..f83d47741 100644 --- a/src/proc/mobject/session/dummy-session-connection.hpp +++ b/src/proc/mobject/session/dummy-session-connection.hpp @@ -45,10 +45,9 @@ #include "lib/error.hpp" #include "lib/depend.hpp" -//#include "lib/symbol.hpp" +#include "lib/nocopy.hpp" #include "lib/util.hpp" -#include #include namespace proc { @@ -65,14 +64,14 @@ namespace session { * @see DummySessionConnection_test */ class DummySessionConnection - : boost::noncopyable + : util::NonCopyable { string nothing_; DummySessionConnection(); ~DummySessionConnection(); - friend class lib::DependencyFactory; + friend class lib::DependencyFactory; public: /** access point to set up the scaffolding. diff --git a/src/proc/mobject/session/element-query.hpp b/src/proc/mobject/session/element-query.hpp index 132b06317..c2afe4a01 100644 --- a/src/proc/mobject/session/element-query.hpp +++ b/src/proc/mobject/session/element-query.hpp @@ -37,8 +37,8 @@ #include "proc/mobject/mobject-ref.hpp" #include "proc/mobject/session/specific-contents-query.hpp" #include "proc/mobject/session/session-service-explore-scope.hpp" +#include "lib/nocopy.hpp" -#include #include @@ -100,7 +100,7 @@ namespace session { * @see struct-factory-impl.hpp usage example */ class ElementQuery - : boost::noncopyable + : util::NonCopyable { public: diff --git a/src/proc/mobject/session/fixture.hpp b/src/proc/mobject/session/fixture.hpp index 8d92ecbd3..79ffb3e2c 100644 --- a/src/proc/mobject/session/fixture.hpp +++ b/src/proc/mobject/session/fixture.hpp @@ -47,8 +47,8 @@ #include "proc/mobject/session/fork.hpp" #include "proc/mobject/explicitplacement.hpp" #include "proc/mobject/session/auto.hpp" +#include "lib/nocopy.hpp" -#include #include #include @@ -70,7 +70,7 @@ namespace session { * @see http://lumiera.org/wiki/renderengine.html#Fixture */ class Fixture - : boost::noncopyable + : util::NonCopyable { protected: /////////////////////////////////////////////////TODO: placeholder code diff --git a/src/proc/mobject/session/lifecycle-advisor.hpp b/src/proc/mobject/session/lifecycle-advisor.hpp index d2297a51e..dc123fa44 100644 --- a/src/proc/mobject/session/lifecycle-advisor.hpp +++ b/src/proc/mobject/session/lifecycle-advisor.hpp @@ -50,12 +50,11 @@ #define PROC_MOBJECT_SESSION_LIFECYCLE_ADVISOR_H #include "lib/error.hpp" +#include "lib/nocopy.hpp" #include "include/lifecycle.h" #include "proc/mobject/session.hpp" #include "lib/symbol.hpp" -#include - namespace proc { namespace mobject { @@ -72,7 +71,7 @@ namespace session { * in a controlled and safe (single threaded) environment */ class LifecycleAdvisor - : boost::noncopyable + : util::NonCopyable { @@ -112,6 +111,7 @@ namespace session { void shutDown() { + emitEvent (ON_SESSION_CLOSE); closeSessionInterface(); disconnectRenderProcesses(); emitEvent (ON_SESSION_END); diff --git a/src/proc/mobject/session/placement-index.cpp b/src/proc/mobject/session/placement-index.cpp index 3556f2beb..1779754d8 100644 --- a/src/proc/mobject/session/placement-index.cpp +++ b/src/proc/mobject/session/placement-index.cpp @@ -60,7 +60,6 @@ #include "include/logging.h" #include -#include #include #include #include @@ -72,13 +71,15 @@ namespace mobject { namespace session { using boost::hash; - using boost::noncopyable; using std::shared_ptr; using std::unordered_map; using std::unordered_multimap; using lib::TypedAllocationManager; using lib::iter_stl::IterSnapshot; using lib::iter_stl::eachVal; + using lib::iter_source::eachMapKey; + using lib::iter_source::eachDistinctKey; + using lib::iter_source::eachValForKey; using std::placeholders::_1; using std::function; using std::bind; @@ -514,7 +515,7 @@ namespace session { if (!contains (targetScope)) throw error::Logic ("Specified a non-registered Placement as scope " "while adding another Placement to the index" - ,LUMIERA_ERROR_INVALID_SCOPE); ////////////////TICKET #197 + ,LERR_(INVALID_SCOPE)); ////////////////TICKET #197 return pTab_->addEntry(newObj, targetScope); } diff --git a/src/proc/mobject/session/placement-index.hpp b/src/proc/mobject/session/placement-index.hpp index e459ebbea..480be0528 100644 --- a/src/proc/mobject/session/placement-index.hpp +++ b/src/proc/mobject/session/placement-index.hpp @@ -112,8 +112,8 @@ #include "lib/itertools.hpp" #include "proc/mobject/placement.hpp" #include "proc/mobject/placement-ref.hpp" +#include "lib/nocopy.hpp" -#include #include #include #include @@ -177,7 +177,7 @@ namespace session { * Placement's hash-IDs. */ class PlacementIndex - : boost::noncopyable + : util::NonCopyable { class Table; class Validator; @@ -266,7 +266,7 @@ namespace session { "while the actual type of the pointee (MObject) " "registered within the index isn't compatible with the " "requested specific MObject subclass" - ,LUMIERA_ERROR_PLACEMENT_TYPE); + , LERR_(PLACEMENT_TYPE)); } inline void @@ -274,10 +274,10 @@ namespace session { { if (!id) throw lumiera::error::Logic ("Encountered a NIL Placement-ID marker" - ,LUMIERA_ERROR_BOTTOM_PLACEMENTREF); + , LERR_(BOTTOM_PLACEMENTREF)); if (!idx.contains (id)) throw lumiera::error::Invalid ("Accessing Placement not registered within the index" - ,LUMIERA_ERROR_NOT_IN_SESSION); ///////////////////////TICKET #197 + , LERR_(NOT_IN_SESSION)); ///////////////////////TICKET #197 } }//(End) shortcuts diff --git a/src/proc/mobject/session/query-focus-stack.hpp b/src/proc/mobject/session/query-focus-stack.hpp index 15d9e2625..e6a077c76 100644 --- a/src/proc/mobject/session/query-focus-stack.hpp +++ b/src/proc/mobject/session/query-focus-stack.hpp @@ -32,8 +32,8 @@ #define MOBJECT_SESSION_QUERY_FOCUS_STACK_H #include "proc/mobject/session/scope-path.hpp" +#include "lib/nocopy.hpp" -#include #include using std::list; @@ -68,7 +68,7 @@ namespace session { * @see QueryFocus access point for client code */ class QueryFocusStack - : boost::noncopyable + : util::NonCopyable { std::list paths_; diff --git a/src/proc/mobject/session/query/fake-configrules.hpp b/src/proc/mobject/session/query/fake-configrules.hpp index b74c18993..868146c3e 100644 --- a/src/proc/mobject/session/query/fake-configrules.hpp +++ b/src/proc/mobject/session/query/fake-configrules.hpp @@ -48,6 +48,7 @@ #include "proc/mobject/session.hpp" #include "proc/config-resolver.hpp" +#include "lib/depend-inject.hpp" #include "lib/query-util.hpp" #include "lib/util.hpp" #include "lib/p.hpp" @@ -289,14 +290,14 @@ namespace session { > { protected: - MockConfigRules (); ///< to be used only by the singleton factory - friend class lib::DependencyFactory; - - virtual ~MockConfigRules() {} - + MockConfigRules (); ///< to be used only by the singleton factory + friend class lib::DependencyFactory; + friend class lib::DependInject::Local; public: + virtual ~MockConfigRules() {} ///< extends the ConfigResolver interface - // TODO: implementation of any additional functions on the ConfigRules interface goes here + + /* === implementation of additional functions on the ConfigRules interface goes here === */ }; diff --git a/src/proc/mobject/session/scope-locator.hpp b/src/proc/mobject/session/scope-locator.hpp index 0cd253c2f..735ab2699 100644 --- a/src/proc/mobject/session/scope-locator.hpp +++ b/src/proc/mobject/session/scope-locator.hpp @@ -99,7 +99,7 @@ namespace session { protected: ScopeLocator(); - friend class lib::DependencyFactory; + friend class lib::DependencyFactory; private: lumiera::QueryResolver const& theResolver(); diff --git a/src/proc/mobject/session/scope-path.cpp b/src/proc/mobject/session/scope-path.cpp index 00c1f0266..d660d2ca7 100644 --- a/src/proc/mobject/session/scope-path.cpp +++ b/src/proc/mobject/session/scope-path.cpp @@ -71,7 +71,7 @@ namespace session { REQUIRE (path); if (path->empty()) throw error::Logic (operation_descr+" an empty placement scope path" - , LUMIERA_ERROR_EMPTY_SCOPE_PATH); + , LERR_(EMPTY_SCOPE_PATH)); } }//(End) helpers @@ -319,7 +319,7 @@ namespace session { ___check_notBottom (this, "Navigating"); if (!target.isValid()) throw error::Invalid ("can't navigate to a target scope outside the model" - , LUMIERA_ERROR_INVALID_SCOPE); + , LERR_(INVALID_SCOPE)); std::vector otherPath; append_all (discoverScopePath(target), otherPath); diff --git a/src/proc/mobject/session/scope-path.hpp b/src/proc/mobject/session/scope-path.hpp index c3d6acae9..7c27db7f9 100644 --- a/src/proc/mobject/session/scope-path.hpp +++ b/src/proc/mobject/session/scope-path.hpp @@ -96,7 +96,7 @@ namespace lib { -namespace iter{ +namespace meta{ using proc::mobject::session::Scope; @@ -105,7 +105,7 @@ namespace iter{ * to yield const Scope elements, based on the const_reverse_iterator * used internally within ScopePath. This specialisation needs to be * injected prior to actually building the iterator type of ScopePath - * @see iter-type-binding.hpp + * @see value-type-binding.hpp * @see iter-adapter.hpp */ template<> diff --git a/src/proc/mobject/session/scope.cpp b/src/proc/mobject/session/scope.cpp index 88ab80f71..d54f11b87 100644 --- a/src/proc/mobject/session/scope.cpp +++ b/src/proc/mobject/session/scope.cpp @@ -43,7 +43,7 @@ #include "proc/mobject/session/query-focus-stack.hpp" #include "proc/mobject/session/session-service-explore-scope.hpp" #include "proc/mobject/mobject.hpp" -#include "lib/iter-source.hpp" ////////////////////TICKET #493 : using the IterSource adapters here +#include "lib/iter-source.hpp" ////////////////////TICKET #493 : only using the IterSource wrapIter adapter here #include "common/query/query-resolver.hpp" #include @@ -215,7 +215,7 @@ namespace session { { if (isRoot()) throw lumiera::error::Invalid ("can't get parent of root scope" - , LUMIERA_ERROR_NO_PARENT_SCOPE); + , LERR_(NO_PARENT_SCOPE)); return SessionServiceExploreScope::getScope (*anchor_); } diff --git a/src/proc/mobject/session/segmentation.cpp b/src/proc/mobject/session/segmentation.cpp index 17b8380f0..cd88eec32 100644 --- a/src/proc/mobject/session/segmentation.cpp +++ b/src/proc/mobject/session/segmentation.cpp @@ -58,7 +58,7 @@ namespace session { // return theGlobalRegistry(); // // throw error::State ("global model port registry is not accessible" -// , LUMIERA_ERROR_BUILDER_LIFECYCLE); +// , LERR_(BUILDER_LIFECYCLE)); //} diff --git a/src/proc/mobject/session/sess-manager-impl.cpp b/src/proc/mobject/session/sess-manager-impl.cpp index 61a98ead7..5b1a0b428 100644 --- a/src/proc/mobject/session/sess-manager-impl.cpp +++ b/src/proc/mobject/session/sess-manager-impl.cpp @@ -79,7 +79,7 @@ namespace session { { ERROR (progress, "Unrecoverable Failure while creating the empty default session."); throw lumiera::error::Fatal ( "Failure while creating the basic session object. System halted." - , LUMIERA_ERROR_CREATE_SESSION ); + , LERR_(CREATE_SESSION)); } diff --git a/src/proc/mobject/session/sess-manager-impl.hpp b/src/proc/mobject/session/sess-manager-impl.hpp index 27f99cc81..8c31f7b29 100644 --- a/src/proc/mobject/session/sess-manager-impl.hpp +++ b/src/proc/mobject/session/sess-manager-impl.hpp @@ -66,7 +66,7 @@ namespace session { unique_ptr lifecycle_; SessManagerImpl(); - friend class lib::DependencyFactory; + friend class lib::DependencyFactory; ~SessManagerImpl() ; diff --git a/src/proc/mobject/session/session-impl.cpp b/src/proc/mobject/session/session-impl.cpp index 40c00d68b..c176e897f 100644 --- a/src/proc/mobject/session/session-impl.cpp +++ b/src/proc/mobject/session/session-impl.cpp @@ -120,7 +120,7 @@ namespace session { { if (Scope(placement).isRoot()) throw error::Invalid ("Can't detach the model root." - , LUMIERA_ERROR_INVALID_SCOPE); + , LERR_(INVALID_SCOPE)); QueryFocus currentFocus; currentFocus.shift (Scope(placement).getParent()); diff --git a/src/proc/mobject/session/session-interface-modules.hpp b/src/proc/mobject/session/session-interface-modules.hpp index 3493cf399..c729088e7 100644 --- a/src/proc/mobject/session/session-interface-modules.hpp +++ b/src/proc/mobject/session/session-interface-modules.hpp @@ -94,7 +94,7 @@ namespace session { * exposed on the public Session API. */ struct SessionInterfaceModules - : boost::noncopyable + : util::NonCopyable { lumiera::query::DefsManager defaultsManager_; mobject::session::ElementQuery elementQueryAPI_; diff --git a/src/proc/mobject/session/session.cpp b/src/proc/mobject/session/session.cpp index b0e5c9d73..27f062b80 100644 --- a/src/proc/mobject/session/session.cpp +++ b/src/proc/mobject/session/session.cpp @@ -82,7 +82,7 @@ namespace mobject { - /** \par + /** * LifecycleHook, to perform all the basic setup for a new session, * prior to adding any specific data, configuration or content. Any subsystems * requiring to (re)-initialise for a new session should register here. When this @@ -93,17 +93,17 @@ namespace mobject { * session should register their basic setup functions using this hook, which can be * done via the C interface functions defined in lifecycle.h */ - const char* ON_SESSION_START ("ON_SESSION_START"); + const char* ON_SESSION_START = "ON_SESSION_START"; - /** \par + /** * LifecycleHook, to perform any initialisation, wiring and registrations necessary * to get the session into a usable state. When activated, the specific session content * and configuration has already be loaded. Any subsystems requiring to build some indices * or wiring to keep track of the session's content should register here. */ - const char* ON_SESSION_INIT ("ON_SESSION_INIT"); + const char* ON_SESSION_INIT = "ON_SESSION_INIT"; - /** \par + /** * LifecycleHook, to perform post loading tasks, requiring an already completely usable * and configured session to be in place. When activated, the session is completely restored * according to the standard or persisted definition and any access interfaces are already @@ -112,9 +112,18 @@ namespace mobject { * fully functional client side APIs. Examples would be statistics gathering, validation * or auto-correction of the session's contents. */ - const char* ON_SESSION_READY ("ON_SESSION_READY"); + const char* ON_SESSION_READY = "ON_SESSION_READY"; - /** \par + /** + * LifecycleHook, to commence any activity relying on an opened and fully operative session. + * When invoked, the session is still in fully operative state, all interfaces are open and + * the render engine is available. However, after issuing this event, the session shutdown + * sequence will be initiated, by detaching the engine interfaces and signalling the + * scheduler to cease running render jobs. + */ + const char* ON_SESSION_CLOSE ="ON_SESSION_CLOSE"; + + /** * LifecycleHook, to perform any state saving, deregistration or de-activation necessary * before bringing down an existing session. When invoked, the session is still fully valid * and functional, but the GUI/external access has already been closed. @@ -122,7 +131,7 @@ namespace mobject { * specific/internal information into the persisted state, besides actually attaching * data to objects within the session? */ - const char* ON_SESSION_END ("ON_SESSION_END"); + const char* ON_SESSION_END ="ON_SESSION_END"; diff --git a/src/proc/play/dummy-play-connection.hpp b/src/proc/play/dummy-play-connection.hpp index ff60ed45e..d059b0aad 100644 --- a/src/proc/play/dummy-play-connection.hpp +++ b/src/proc/play/dummy-play-connection.hpp @@ -90,13 +90,11 @@ #include "proc/asset/timeline.hpp" #include "proc/asset/pipe.hpp" #include "common/query.hpp" -#include "lib/util.hpp" -//#include "lib/symbol.hpp" #include "lib/iter-source.hpp" -// -#include -//#include +#include "lib/nocopy.hpp" +#include "lib/util.hpp" + #include @@ -131,7 +129,6 @@ namespace play { using lumiera::Query; // using lib::ScopedCollection; // using lib::Literal; - using lib::eachEntry; typedef asset::ID PID; typedef asset::ID TID; @@ -207,7 +204,7 @@ namespace play { ModelPorts getAllModelPorts() { - return eachEntry (modelPorts_.begin(), modelPorts_.end()); + return lib::iter_source::eachEntry (modelPorts_.begin(), modelPorts_.end()); } }; } @@ -225,7 +222,7 @@ namespace play { */ template class DummyPlayConnection - : boost::noncopyable + : util::NonCopyable { SimulatedBuilderContext mockBuilder_; diff --git a/src/proc/play/dummy-player-interface-proxy.hpp b/src/proc/play/dummy-player-interface-proxy.cpp similarity index 82% rename from src/proc/play/dummy-player-interface-proxy.hpp rename to src/proc/play/dummy-player-interface-proxy.cpp index a4db2b95b..8435e8737 100644 --- a/src/proc/play/dummy-player-interface-proxy.hpp +++ b/src/proc/play/dummy-player-interface-proxy.cpp @@ -22,9 +22,8 @@ -/** @file dummy-player-interface-proxy.hpp - ** This is an implementation fragment, intended to be included into common/interfaceproxy.cpp - ** +/** @file dummy-player-interface-proxy.cpp + ** Interface-Proxy for the lumiera::DummyPlayer facade (experimental/obsolete). ** The purpose is to define a proxy implementation of lumiera::DummyPlayer, in order to ** redirect any calls through the associated C Language Interface "lumieraorg_DummyPlayer" ** @@ -42,42 +41,43 @@ ** proxy used by the client code to access the interface). ** ** @see dummy-player-facade.hpp - ** @see dummy-player-service.hpp actual implementation within the Proc-Layer + ** @see dummy-player-service.hpp actual implementation within the Proc-Layer + ** @deprecated obsolete early design draft from 2009; + ** as of 2018 only kept in source to compile some likewise obsolete UI widgets. */ - - - - - /* ==================== DummyPlayer ======================================= */ - -//#include "proc/play/dummy-player-service.hpp" #include "include/dummy-player-facade.h" #include "include/interfaceproxy.hpp" namespace lumiera { - /** storage for the DummyPlayer facade proxy factory... */ - lumiera::facade::Accessor DummyPlayer::facade; - // emit the vtable here into this translation unit within liblumieracommon.so ... DummyPlayer::~DummyPlayer() { } + DummyPlayer::ProcessImplementationLink::~ProcessImplementationLink() { }; + /** static storage for the facade access front-end */ + lib::Depend DummyPlayer::facade; + namespace facade { - typedef lumiera::InstanceHandle< LUMIERA_INTERFACE_INAME(lumieraorg_DummyPlayer, 0) - , lumiera::DummyPlayer - > IHandle_DummyPlayer; + + + /* ==================== DummyPlayer ======================================= */ + + using Interface = LUMIERA_INTERFACE_INAME(lumieraorg_DummyPlayer, 0); + using Facade = lumiera::DummyPlayer; + + using IHandle = InstanceHandle; template<> - class Proxy - : public Holder + class Proxy + : public Binding { //----Proxy-Implementation-of-DummyPlayer-------- typedef lumiera::DummyPlayer::Process Process; @@ -106,16 +106,16 @@ namespace lumiera { public: - Proxy (IHandle const& iha) : THolder(iha) {} + using Binding::Binding; }; - template void openProxy (IHandle_DummyPlayer const&); - template void closeProxy (void); - + /** emit proxy code here... */ + template + class Link; } // namespace facade - + /* === Forwarding function(s) on the Process handle === */ @@ -126,9 +126,5 @@ namespace lumiera { impl().doPlay(yes); } - // emit the VTable and typeinfo for this interface here (in liblumieracommon.so) - DummyPlayer::ProcessImplementationLink::~ProcessImplementationLink() { }; - - } // namespace lumiera diff --git a/src/proc/play/dummy-player-service.cpp b/src/proc/play/dummy-player-service.cpp index 5b441c683..206757e57 100644 --- a/src/proc/play/dummy-player-service.cpp +++ b/src/proc/play/dummy-player-service.cpp @@ -29,6 +29,7 @@ #include "proc/play/dummy-player-service.hpp" #include "proc/engine/worker/dummy-image-generator.hpp" #include "proc/engine/worker/tick-service.hpp" +#include "lib/depend-inject.hpp" #include "lib/depend.hpp" extern "C" { @@ -58,6 +59,12 @@ namespace proc { class DummyPlayerSubsysDescriptor : public Subsys { + using ServiceHandle = lib::DependInject::ServiceInstance<>; + + /** manages the actual (single) instance of the player service impl */ + ServiceHandle thePlayer_{ServiceHandle::NOT_YET_STARTED}; + + operator string () const { return "Dummy-Player"; } @@ -72,18 +79,14 @@ namespace proc { { ASSERT (!thePlayer_); - thePlayer_.reset (new DummyPlayerService (terminationHandle)); + thePlayer_.createInstance (terminationHandle); return true; } - /** manages the actual (single) instance of the player service impl */ - std::unique_ptr thePlayer_; - - void triggerShutdown () noexcept override { - thePlayer_.reset(0); + thePlayer_.shutdown(); // note: shutdown of the DummyPlayerService instance may block // for a short period, until termination of all tick services } @@ -173,10 +176,8 @@ namespace proc { - using lumiera::facade::LUMIERA_ERROR_FACADE_LIFECYCLE; - typedef lib::SingletonRef::Accessor InstanceRef; - - InstanceRef _instance; ///< a backdoor for the C Language impl to access the actual DummyPlayer implementation... + using lumiera::error::LERR_(LIFECYCLE); + lib::Depend _instance; ///< a backdoor for the C Language impl to access the actual SessionCommand implementation... typedef ProcessImpl* ProcP; @@ -191,11 +192,11 @@ namespace proc { { if (!_instance) { - lumiera_error_set(LUMIERA_ERROR_FACADE_LIFECYCLE, 0); + lumiera_error_set (LUMIERA_ERROR_LIFECYCLE, 0); return 0; } - return static_cast (_instance->start(viewerHandle)); + return static_cast (_instance().start(viewerHandle)); } ) , LUMIERA_INTERFACE_INLINE (togglePlay, @@ -203,7 +204,7 @@ namespace proc { { if (!_instance) { - lumiera_error_set(LUMIERA_ERROR_FACADE_LIFECYCLE, 0); + lumiera_error_set(LUMIERA_ERROR_LIFECYCLE, 0); return; } @@ -218,7 +219,7 @@ namespace proc { { if (!_instance) { - lumiera_error_set(LUMIERA_ERROR_FACADE_LIFECYCLE, 0); + lumiera_error_set(LUMIERA_ERROR_LIFECYCLE, 0); return; } @@ -241,11 +242,10 @@ namespace proc { DummyPlayerService::DummyPlayerService (Subsys::SigTerm terminationHandle) : error_("") , notifyTermination_(terminationHandle) - , implInstance_(this,_instance) , serviceInstance_( LUMIERA_INTERFACE_REF (lumieraorg_DummyPlayer, 0, lumieraorg_DummyPlayerService)) - { - INFO (progress, "DummyPlayer Facade opened."); - } + { + INFO (progress, "DummyPlayer Facade opened."); + } diff --git a/src/proc/play/dummy-player-service.hpp b/src/proc/play/dummy-player-service.hpp index c82409e16..b91b560e1 100644 --- a/src/proc/play/dummy-player-service.hpp +++ b/src/proc/play/dummy-player-service.hpp @@ -44,9 +44,8 @@ #include "include/dummy-player-facade.h" #include "include/display-facade.h" #include "common/instancehandle.hpp" -#include "lib/singleton-ref.hpp" +#include "lib/nocopy.hpp" -#include #include #include @@ -79,7 +78,7 @@ namespace proc { */ class ProcessImpl : public lumiera::DummyPlayer::ProcessImplementationLink, - boost::noncopyable + util::NonCopyable { uint fps_; bool play_; @@ -126,7 +125,7 @@ namespace proc { * route calls through this interface. */ class DummyPlayerService - : boost::noncopyable + : util::NonCopyable { string error_; @@ -138,13 +137,10 @@ namespace proc { typedef lumiera::InstanceHandle< LUMIERA_INTERFACE_INAME(lumieraorg_DummyPlayer, 0) , DummyPlayer > ServiceInstanceHandle; - - lib::SingletonRef implInstance_; ServiceInstanceHandle serviceInstance_; public: DummyPlayerService(Subsys::SigTerm terminationHandle); - ~DummyPlayerService() { notifyTermination_(&error_); } diff --git a/src/proc/play/output-director.cpp b/src/proc/play/output-director.cpp index d27c4270b..31a32674c 100644 --- a/src/proc/play/output-director.cpp +++ b/src/proc/play/output-director.cpp @@ -56,6 +56,8 @@ namespace play { * the Lifecycle of booting, connecting, operating, disconnecting. */ OutputDirector::OutputDirector() + : player_{PlayServiceHandle::NOT_YET_STARTED} + , shutdown_initiated_{false} { } OutputDirector::~OutputDirector() { } @@ -72,7 +74,7 @@ namespace play { Lock sync(this); REQUIRE (not shutdown_initiated_); - player_.reset (new PlayService); + player_.createInstance(); return this->isOperational(); } @@ -100,7 +102,10 @@ namespace play { if (not shutdown_initiated_) { shutdown_initiated_ = true; - Thread ("Output shutdown supervisor", bind (&OutputDirector::bringDown, this, completedSignal)); + Thread ("Output shutdown supervisor", + [=]{ + bringDown (completedSignal); + }); } } @@ -129,7 +134,7 @@ namespace play { try { TODO ("actually bring down the output generation"); - player_.reset(0); + player_.shutdown(); completedSignal(0); } diff --git a/src/proc/play/output-director.hpp b/src/proc/play/output-director.hpp index 84ecb3691..60bfe39b1 100644 --- a/src/proc/play/output-director.hpp +++ b/src/proc/play/output-director.hpp @@ -37,16 +37,18 @@ #include "lib/error.hpp" #include "lib/depend.hpp" +#include "lib/depend-inject.hpp" #include "proc/play/output-manager.hpp" #include "common/subsys.hpp" +#include "lib/nocopy.hpp" #include "lib/sync.hpp" -#include -//#include -//#include #include +namespace lumiera { + class Play; +} namespace proc { namespace play { @@ -55,13 +57,9 @@ namespace play { //using std::shared_ptr; using std::unique_ptr; - class PlayService; -//typedef lib::ScopedPtrVect DisplayerTab; - - /**************************************************//** * Management of external Output connections. @@ -69,12 +67,13 @@ namespace play { * @todo write Type comment */ class OutputDirector - : boost::noncopyable + : util::NonCopyable , public lib::Sync<> { - typedef lumiera::Subsys::SigTerm SigTerm; + using SigTerm = lumiera::Subsys::SigTerm; + using PlayServiceHandle = lib::DependInject::ServiceInstance; - unique_ptr player_; + PlayServiceHandle player_; ///////TODO more components and connections to manage here... bool shutdown_initiated_ = false; /////TODO probably need a way more elaborate lifecylce management @@ -90,7 +89,7 @@ namespace play { private: OutputDirector() ; ~OutputDirector() ; - friend class lib::DependencyFactory; + friend class lib::DependencyFactory; void bringDown (SigTerm completedSignal); diff --git a/src/proc/play/output-manager.hpp b/src/proc/play/output-manager.hpp index 54bff0200..9bfc9175d 100644 --- a/src/proc/play/output-manager.hpp +++ b/src/proc/play/output-manager.hpp @@ -32,10 +32,10 @@ #include "lib/error.hpp" +#include "lib/nocopy.hpp" #include "proc/play/output-slot.hpp" #include "proc/mobject/model-port.hpp" -#include //#include //#include #include @@ -66,7 +66,7 @@ namespace play { * @todo write Type comment */ class OutputManager - : boost::noncopyable + : util::NonCopyable { public: virtual ~OutputManager() { } diff --git a/src/proc/play/output-slot-connection.hpp b/src/proc/play/output-slot-connection.hpp index 33ddf3068..ede82d9a2 100644 --- a/src/proc/play/output-slot-connection.hpp +++ b/src/proc/play/output-slot-connection.hpp @@ -40,6 +40,7 @@ #include "lib/error.hpp" +#include "lib/nocopy.hpp" #include "proc/play/output-slot.hpp" #include "lib/scoped-collection.hpp" #include "lib/iter-adapter-stl.hpp" @@ -50,7 +51,6 @@ //#include "proc/play/timings.hpp" //#include "lib/sync.hpp" -#include //#include #include #include @@ -64,7 +64,6 @@ namespace play { //using proc::engine::BufferProvider; //using lib::time::Time; //using std::string; - using lib::transform; using lib::iter_stl::eachElm; //using std::placeholders::_1; @@ -94,7 +93,7 @@ namespace play { * implementation; yet it may as well be called from a separate * service thread or some kind of callback. * @note the meaning of FrameID is implementation defined. - * @note typically the concrete connection is noncopyable + * @note typically the concrete connection is non-copyable */ class OutputSlot::Connection { @@ -120,7 +119,7 @@ namespace play { */ class OutputSlot::ConnectionState : public OutputSlot::Allocation - , boost::noncopyable + , util::NonCopyable { public: virtual ~ConnectionState() { } @@ -177,7 +176,7 @@ namespace play { { //////////////////////////TICKET #878 not re-entrant, lifecycle isn't clear REQUIRE (this->isActive()); - return transform (eachElm(connections_), connectOutputSink); + return lib::iter_source::transform (eachElm(connections_), connectOutputSink); } Timings diff --git a/src/proc/play/output-slot.cpp b/src/proc/play/output-slot.cpp index 15e50f999..1ae9d3c4c 100644 --- a/src/proc/play/output-slot.cpp +++ b/src/proc/play/output-slot.cpp @@ -30,7 +30,6 @@ #include "proc/play/output-slot.hpp" #include "proc/play/output-slot-connection.hpp" -#include #include diff --git a/src/proc/play/output-slot.hpp b/src/proc/play/output-slot.hpp index 87d685dad..8cb7a7baf 100644 --- a/src/proc/play/output-slot.hpp +++ b/src/proc/play/output-slot.hpp @@ -72,8 +72,8 @@ #include "proc/engine/buffer-provider.hpp" #include "proc/play/timings.hpp" #include "lib/iter-source.hpp" +#include "lib/nocopy.hpp" -#include #include @@ -108,7 +108,7 @@ namespace play { * through the Allocation object returned from there. */ class OutputSlot - : boost::noncopyable + : util::NonCopyable { protected: diff --git a/src/proc/play/play-controller.hpp b/src/proc/play/play-controller.hpp index 3bb16e876..7960a830b 100644 --- a/src/proc/play/play-controller.hpp +++ b/src/proc/play/play-controller.hpp @@ -36,8 +36,8 @@ //#include "include/display-facade.h" //#include "common/instancehandle.hpp" //#include "lib/singleton-ref.hpp" +#include "lib/nocopy.hpp" -#include //#include @@ -52,7 +52,7 @@ namespace play { * Isn't lumiera::Play::Controller already sufficient? */ class PlayController - : boost::noncopyable + : util::NonCopyable { public: diff --git a/src/proc/play/play-process.cpp b/src/proc/play/play-process.cpp index 4317e4ddc..2b3fe88e7 100644 --- a/src/proc/play/play-process.cpp +++ b/src/proc/play/play-process.cpp @@ -35,6 +35,8 @@ //#include //#include +using lib::iter_source::transform; +using lib::append_all; namespace proc { @@ -43,8 +45,6 @@ namespace play { // using std::string; // using lumiera::Subsys; // using std::bind; - using lib::transform; - using lib::append_all; namespace { // Implementation details... diff --git a/src/proc/play/play-process.hpp b/src/proc/play/play-process.hpp index 7fb265303..8334eeb9d 100644 --- a/src/proc/play/play-process.hpp +++ b/src/proc/play/play-process.hpp @@ -52,16 +52,16 @@ #include "lib/error.hpp" +#include "lib/nocopy.hpp" //#include "include/dummy-player-facade.h" //#include "include/display-facade.h" //#include "common/instancehandle.hpp" //#include "lib/singleton-ref.hpp" #include "proc/mobject/model-port.hpp" #include "proc/engine/calc-stream.hpp" -#include "lib/iter-source.hpp" +#include "lib/iter-source.hpp" ////////////////////TICKET #493 : only using the IterSource base feature here #include "lib/util.hpp" -// -#include + #include //#include #include @@ -127,7 +127,7 @@ namespace play { * from the PlayService. */ class PlayProcess - : boost::noncopyable + : util::NonCopyable { OutputFeeds outputFeeds_; diff --git a/src/proc/play/play-service.cpp b/src/proc/play/play-service.cpp index 3a1e7b001..4e40edd4a 100644 --- a/src/proc/play/play-service.cpp +++ b/src/proc/play/play-service.cpp @@ -27,7 +27,7 @@ #include "lib/error.hpp" -#include "include/play-facade.h" +#include "include/play-facade.hpp" #include "proc/play/play-service.hpp" #include "proc/play/play-process.hpp" #include "proc/play/render-configurator.hpp" @@ -47,15 +47,8 @@ namespace lumiera { Play::~Play() { } // emit VTables here... - - /** Storage for the lumiera::Play facade access point - * @note PlayService uses a InterfaceFacadeLink member - * for actually establishing the PlayService instance - * as implementation of lumiera::Play - */ - facade::Accessor Play::facade; - - + /** static storage for the facade access front-end */ + lib::Depend Play::facade; }//(End) namespace lumiera @@ -167,8 +160,7 @@ namespace play { * this service through the lumiera::Play facade. */ PlayService::PlayService() - : facadeAccess_(*this, "Player") - , pTable_(new ProcessTable) + : pTable_(new ProcessTable) { } diff --git a/src/proc/play/play-service.hpp b/src/proc/play/play-service.hpp index 2dcf103d8..b666c499e 100644 --- a/src/proc/play/play-service.hpp +++ b/src/proc/play/play-service.hpp @@ -57,10 +57,9 @@ #include "lib/error.hpp" -#include "include/play-facade.h" -#include "common/interface-facade-link.hpp" +#include "lib/nocopy.hpp" +#include "include/play-facade.hpp" -#include #include //#include @@ -69,7 +68,6 @@ namespace proc { namespace play { using std::string; - using lumiera::facade::InterfaceFacadeLink; //using lumiera::Subsys; //using lumiera::Display; //using lumiera::DummyPlayer; @@ -100,9 +98,8 @@ namespace play { */ class PlayService : public lumiera::Play - , boost::noncopyable + , util::NonCopyable { - InterfaceFacadeLink facadeAccess_; std::unique_ptr pTable_; diff --git a/src/proc/play/render-configurator.cpp b/src/proc/play/render-configurator.cpp index 73521fcdb..ac26b7229 100644 --- a/src/proc/play/render-configurator.cpp +++ b/src/proc/play/render-configurator.cpp @@ -78,7 +78,7 @@ namespace play { OutputSlot& slot = outputResolver_->getOutputFor (port); if (!slot.isFree()) throw error::State("unable to acquire a suitable output slot" /////////////////////TICKET #197 #816 - , LUMIERA_ERROR_CANT_PLAY); + , LERR_(CANT_PLAY)); return slot; } diff --git a/src/proc/play/render-configurator.hpp b/src/proc/play/render-configurator.hpp index 85b868b36..47833cf1b 100644 --- a/src/proc/play/render-configurator.hpp +++ b/src/proc/play/render-configurator.hpp @@ -35,6 +35,7 @@ #include "lib/error.hpp" +#include "lib/nocopy.hpp" //#include "include/dummy-player-facade.h" //#include "include/display-facade.h" //#include "common/instancehandle.hpp" @@ -46,8 +47,7 @@ #include "proc/play/output-manager.hpp" //#include "lib/iter-source.hpp" //#include "lib/util.hpp" -// -#include + #include #include //#include @@ -72,7 +72,7 @@ namespace play { /** Strategy for configuring the render process */ class RenderConfigurator - : boost::noncopyable + : util::NonCopyable { public: diff --git a/src/proc/play/sound/jack-output.hpp b/src/proc/play/sound/jack-output.hpp index 4aa50f4d2..aba5d1db4 100644 --- a/src/proc/play/sound/jack-output.hpp +++ b/src/proc/play/sound/jack-output.hpp @@ -41,7 +41,6 @@ // //#include //#include -//#include //#include //#include diff --git a/src/proc/play/timings.hpp b/src/proc/play/timings.hpp index e511ee780..5d61f614e 100644 --- a/src/proc/play/timings.hpp +++ b/src/proc/play/timings.hpp @@ -50,7 +50,6 @@ //#include "lib/iter-source.hpp" //#include "lib/sync.hpp" -//#include //#include //#include #include diff --git a/src/proc/streamtype.hpp b/src/proc/streamtype.hpp index 5f2436ede..a6475cc60 100644 --- a/src/proc/streamtype.hpp +++ b/src/proc/streamtype.hpp @@ -38,7 +38,6 @@ //#include "common/query.hpp" #include "lib/idi/entry-id.hpp" -#include namespace proc { @@ -52,7 +51,8 @@ namespace proc { /** * @todo this is just a draft to show the general idea.... */ - struct StreamType : boost::noncopyable + struct StreamType + : util::NonCopyable { enum MediaKind { diff --git a/tests/10exception.tests b/tests/10exception.tests index fb151a375..a2bafedf7 100644 --- a/tests/10exception.tests +++ b/tests/10exception.tests @@ -14,7 +14,7 @@ out: intermediate handler caught: LUMIERA_ERROR_EXTERNAL:failure in external ser out: caught lumiera::Error: LUMIERA_ERROR_STATE:unforeseen state -- caused by: LUMIERA_ERROR_EXTERNAL:failure in external service \(test-7\). out: intermediate handler caught: LUMIERA_ERROR_EXTERNAL:failure in external service \(test-8\).....will rethrow as error::State out: 2nd intermediate handler caught: LUMIERA_ERROR_STATE:unforeseen state -- caused by: LUMIERA_ERROR_EXTERNAL:failure in external service \(test-8\).....will rethrow as error::Config -out: caught lumiera::Error: LUMIERA_ERROR_CONFIG:misconfiguration -- caused by: LUMIERA_ERROR_EXTERNAL:failure in external service \(test-8\)\. +out: caught error::Invalid: LUMIERA_ERROR_CONFIG:misconfiguration -- caused by: LUMIERA_ERROR_EXTERNAL:failure in external service \(test-8\)\. out: caught lumiera::Error: LUMIERA_ERROR_LIFE_AND_UNIVERSE:and everything\? \(what is the answer\?\)\. out: caught error::Logic: LUMIERA_ERROR_LOGIC:internal logic broken \(the big bang\). -- caused by: LUMIERA_ERROR_LIFE_AND_UNIVERSE:and everything\? \(what is the answer\?\)\. return: 0 diff --git a/tests/12metaprogramming.tests b/tests/12metaprogramming.tests index c1414bba8..735cd3b53 100644 --- a/tests/12metaprogramming.tests +++ b/tests/12metaprogramming.tests @@ -2,6 +2,91 @@ TESTING "Metaprogramming facilities and techniques" ./test-suite --group=common +TEST "casting or converting access helper" AccessCasted_test < = 1 +out-lit: can_downcast = 1 +out-lit: can_downcast = 1 +out-lit: can_downcast = 0 +out-lit: can_downcast = 0 +out-lit: can_downcast = 1 +out-lit: can_downcast = 1 +out-lit: can_downcast = 1 +out-lit: can_downcast = 1 +out-lit: has_RTTI = 0 +out-lit: has_RTTI = 1 +out-lit: has_RTTI = 1 +out-lit: is_convertible = 0 +out-lit: is_convertible = 1 +out-lit: can_use_dynamic_downcast = 0 +out-lit: can_use_conversion = 0 +out-lit: can_use_dynamic_downcast = 0 +out-lit: can_use_conversion = 1 +out-lit: can_use_dynamic_downcast = 0 +out-lit: can_use_conversion = 1 +out-lit: can_use_conversion = 0 +out-lit: can_use_dynamic_downcast = 0 +out-lit: can_use_conversion = 0 +out-lit: can_use_dynamic_downcast = 1 +out-lit: === standard case: References === +out: Access\(D as D&\) .+D.. adr=╲\w{8} ↗«D» +out: Access\(D& as D&\) .+D.. adr=╲\w{8} ↗«D» +out-lit: === build a value object === +out: Access.D as D. .+D.. adr=╲\w{8} ↗«D» +out: Access.D& as D. .+D.. adr=╲\w{8} ↗«D» +out: Access.D&& as D. .+D.. adr=╲\w{8} ↗«D» +out-lit: === take a pointer === +out: Access.D as D\*. .+╲\w{8} +out: Access.D& as D\*. .+╲\w{8} +out-lit: === dereference a pointer === +out: Access.D\* as D&. .+D.. adr=╲\w{8} ↗«D» +out: Access.D\* as D. .+D.. adr=╲\w{8} ↗«D» +out: Access.D\*&& as D. .+D.. adr=╲\w{8} ↗«D» +out-lit: === const correctness === +out: Access.D as D const&. .+D.. adr=╲\w{8} ↗«D» +out: Access.D& as D const&. .+D.. adr=╲\w{8} ↗«D» +out: Access.D as const D. .+D.. adr=╲\w{8} ↗«D» +out: Access.D& as const D. .+D.. adr=╲\w{8} ↗«D» +out: Access.D as const D\*. .+╲\w{8} +out: Access.D& as const D\*. .+╲\w{8} +out: Access.D\* as D const&. .+D.. adr=╲\w{8} ↗«D» +out: Access.D\* as const D. .+D.. adr=╲\w{8} ↗«D» +out: Access.const D as D const&. .+D.. adr=╲\w{8} ↗«D» +out: Access.D const& as D const&. .+D.. adr=╲\w{8} ↗«D» +out: Access.const D as const D. .+D.. adr=╲\w{8} ↗«D» +out: Access.D const& as const D. .+D.. adr=╲\w{8} ↗«D» +out: Access.const D as const D\*..+╲\w{8} +out: Access.D const& as const D\*..+╲\w{8} +out: Access.const D\* as D const&..+D.. adr=╲\w{8} ↗«D» +out: Access.const D\* as const D. .+D.. adr=╲\w{8} ↗«D» +out: Access.D const& as D. .+D.. adr=╲\w{8} ↗«D» +out: Access.D const&& as D. .+D.. adr=╲\w{8} ↗«D» +out-lit: === work cases: actual conversions === +out: Access.B& as B&. .+B.. adr=╲\w{8} ↗«B» +out: Access.D& as B&. .+B.. adr=╲\w{8} ↗«B» +out: Access.B\* as B\*. .+╲\w{8} +out: Access.D\* as B\*. .+╲\w{8} +out: Access.D& as B\*. .+╲\w{8} +out: Access.D\* as B&. .+B.. adr=╲\w{8} ↗«B» +out: Access.B\*& as B\*&. .+╲\w{8} +out: Access.D\*& as D\*&. .+╲\w{8} +out: Access.D& as const B\*. .+╲\w{8} +out: Access.D\* as B const&. .+B.. adr=╲\w{8} ↗«B» +out: Access.D const& as const B\*..+╲\w{8} +out: Access.const D\* as B const&..+B.. adr=╲\w{8} ↗«B» +out: Access.E\(F\)& as F&. .+F.. adr=╲\w{8} ↗«F» +out: Access.E\(F\)\* as F\*. .+╲\w{8} +out: Access.E\(F\)\* as F&. .+F.. adr=╲\w{8} ↗«F» +out: Access.E\(F\)& as F\*. .+╲\w{8} +out: Access.F\\* as X\*. .+╲\w{8} +out: Access.X\(F\)\* as X\*. .+╲\w{8} +out: Access.F\* as B&. .+B.. adr=╲\w{8} ↗«B» +out: Access.F\* as E&*. .+E.. adr=╲\w{8} ↗«F» +out-lit: Access(int as double) --->2 +out-lit: Access(float as long) --->3 +return: 0 +END + + TEST "ConfigFlags_test" ConfigFlags_test <- @@ -116,6 +201,11 @@ return: 0 END +TEST "Duck detect extension points" DuckDetectorExtension_test <-<2>-<3>- out-lit: List2 :-<5>-<6>-<7>- @@ -238,7 +328,7 @@ return: 0 END -TEST "Iterator monad variations" IterExplorer_test <──.─┼N<2>──.─┼N<3>──.─┼N<3>──.─┤ +out: 4╎N<0>──.─┼N<1>──.─┼N<2>──.─┼N<3>──.─┤ +out: 4╎N<3>──.─┼N<2>──.─┼N<1>──.─┼N<0>──.─┤ +out: 2╎N<1>──.─┼N<0>──.─┤ +out: 2╎N<2>──.─┼N<3>──.─┤ +return: 0 +END + + TEST "Typelist combinations generator" GeneratorCombinations_test <-<2>-+ diff --git a/tests/15library.tests b/tests/15library.tests index 9cdf68e6e..e289c71ec 100644 --- a/tests/15library.tests +++ b/tests/15library.tests @@ -2,91 +2,6 @@ TESTING "Library Test Suite: basic and low-level components" ./test-suite --grou -TEST "casting or converting access helper" AccessCasted_test < = 1 -out-lit: can_downcast = 1 -out-lit: can_downcast = 1 -out-lit: can_downcast = 0 -out-lit: can_downcast = 0 -out-lit: can_downcast = 1 -out-lit: can_downcast = 1 -out-lit: can_downcast = 1 -out-lit: can_downcast = 1 -out-lit: has_RTTI = 0 -out-lit: has_RTTI = 1 -out-lit: has_RTTI = 1 -out-lit: is_convertible = 0 -out-lit: is_convertible = 1 -out-lit: can_use_dynamic_downcast = 0 -out-lit: can_use_conversion = 0 -out-lit: can_use_dynamic_downcast = 0 -out-lit: can_use_conversion = 1 -out-lit: can_use_dynamic_downcast = 0 -out-lit: can_use_conversion = 1 -out-lit: can_use_conversion = 0 -out-lit: can_use_dynamic_downcast = 0 -out-lit: can_use_conversion = 0 -out-lit: can_use_dynamic_downcast = 1 -out-lit: === standard case: References === -out: Access\(D as D&\) .+D.. adr=╲\w{8} ↗«D» -out: Access\(D& as D&\) .+D.. adr=╲\w{8} ↗«D» -out-lit: === build a value object === -out: Access.D as D. .+D.. adr=╲\w{8} ↗«D» -out: Access.D& as D. .+D.. adr=╲\w{8} ↗«D» -out: Access.D&& as D. .+D.. adr=╲\w{8} ↗«D» -out-lit: === take a pointer === -out: Access.D as D\*. .+╲\w{8} -out: Access.D& as D\*. .+╲\w{8} -out-lit: === dereference a pointer === -out: Access.D\* as D&. .+D.. adr=╲\w{8} ↗«D» -out: Access.D\* as D. .+D.. adr=╲\w{8} ↗«D» -out: Access.D\*&& as D. .+D.. adr=╲\w{8} ↗«D» -out-lit: === const correctness === -out: Access.D as D const&. .+D.. adr=╲\w{8} ↗«D» -out: Access.D& as D const&. .+D.. adr=╲\w{8} ↗«D» -out: Access.D as const D. .+D.. adr=╲\w{8} ↗«D» -out: Access.D& as const D. .+D.. adr=╲\w{8} ↗«D» -out: Access.D as const D\*. .+╲\w{8} -out: Access.D& as const D\*. .+╲\w{8} -out: Access.D\* as D const&. .+D.. adr=╲\w{8} ↗«D» -out: Access.D\* as const D. .+D.. adr=╲\w{8} ↗«D» -out: Access.const D as D const&. .+D.. adr=╲\w{8} ↗«D» -out: Access.D const& as D const&. .+D.. adr=╲\w{8} ↗«D» -out: Access.const D as const D. .+D.. adr=╲\w{8} ↗«D» -out: Access.D const& as const D. .+D.. adr=╲\w{8} ↗«D» -out: Access.const D as const D\*..+╲\w{8} -out: Access.D const& as const D\*..+╲\w{8} -out: Access.const D\* as D const&..+D.. adr=╲\w{8} ↗«D» -out: Access.const D\* as const D. .+D.. adr=╲\w{8} ↗«D» -out: Access.D const& as D. .+D.. adr=╲\w{8} ↗«D» -out: Access.D const&& as D. .+D.. adr=╲\w{8} ↗«D» -out-lit: === work cases: actual conversions === -out: Access.B& as B&. .+B.. adr=╲\w{8} ↗«B» -out: Access.D& as B&. .+B.. adr=╲\w{8} ↗«B» -out: Access.B\* as B\*. .+╲\w{8} -out: Access.D\* as B\*. .+╲\w{8} -out: Access.D& as B\*. .+╲\w{8} -out: Access.D\* as B&. .+B.. adr=╲\w{8} ↗«B» -out: Access.B\*& as B\*&. .+╲\w{8} -out: Access.D\*& as D\*&. .+╲\w{8} -out: Access.D& as const B\*. .+╲\w{8} -out: Access.D\* as B const&. .+B.. adr=╲\w{8} ↗«B» -out: Access.D const& as const B\*..+╲\w{8} -out: Access.const D\* as B const&..+B.. adr=╲\w{8} ↗«B» -out: Access.E\(F\)& as F&. .+F.. adr=╲\w{8} ↗«F» -out: Access.E\(F\)\* as F\*. .+╲\w{8} -out: Access.E\(F\)\* as F&. .+F.. adr=╲\w{8} ↗«F» -out: Access.E\(F\)& as F\*. .+╲\w{8} -out: Access.F\\* as X\*. .+╲\w{8} -out: Access.X\(F\)\* as X\*. .+╲\w{8} -out: Access.F\* as B&. .+B.. adr=╲\w{8} ↗«B» -out: Access.F\* as E&*. .+E.. adr=╲\w{8} ↗«F» -out-lit: Access(int as double) --->2 -out-lit: Access(float as long) --->3 -return: 0 -END - - TEST "CmdlineWrapper_test" CmdlineWrapper_test < @@ -133,21 +48,6 @@ return: 0 END -TEST "Dependency injection" DependencyFactory_test < : No +out-lit: BasicallyString : No +out-lit: BasicallyCString : No +out-lit: std::is_arithmetic : Yes +out-lit: can_lexical2string : Yes +out-lit: can_convertToString : No +out-lit: use_StringConversion4Stream : No +out-lit: Type: char ...... +out-lit: is_StringLike : No +out-lit: BasicallyString : No +out-lit: BasicallyCString : No +out-lit: std::is_arithmetic : Yes +out-lit: can_lexical2string : Yes +out-lit: can_convertToString : No +out-lit: use_StringConversion4Stream : No +out-lit: Type: double ...... +out-lit: is_StringLike : No +out-lit: BasicallyString : No +out-lit: BasicallyCString : No +out-lit: std::is_arithmetic : Yes +out-lit: can_lexical2string : Yes +out-lit: can_convertToString : No +out-lit: use_StringConversion4Stream : No +out-lit: Type: int64_t ...... +out-lit: is_StringLike : No +out-lit: BasicallyString : No +out-lit: BasicallyCString : No +out-lit: std::is_arithmetic : Yes +out-lit: can_lexical2string : Yes +out-lit: can_convertToString : No +out-lit: use_StringConversion4Stream : No +out-lit: Type: string ...... +out-lit: is_StringLike : Yes +out-lit: BasicallyString : Yes +out-lit: BasicallyCString : No +out-lit: std::is_arithmetic : No +out-lit: can_lexical2string : Yes +out-lit: can_convertToString : Yes +out-lit: use_StringConversion4Stream : No +out-lit: Type: StringPtr ...... +out-lit: is_StringLike : No +out-lit: BasicallyString : No +out-lit: BasicallyCString : No +out-lit: std::is_arithmetic : No +out-lit: can_lexical2string : No +out-lit: can_convertToString : No +out-lit: use_StringConversion4Stream : No +out-lit: Type: StringRef ...... +out-lit: is_StringLike : Yes +out-lit: BasicallyString : Yes +out-lit: BasicallyCString : No +out-lit: std::is_arithmetic : No +out-lit: can_lexical2string : Yes +out-lit: can_convertToString : Yes +out-lit: use_StringConversion4Stream : No +out-lit: Type: StringRRef ...... +out-lit: is_StringLike : Yes +out-lit: BasicallyString : Yes +out-lit: BasicallyCString : No +out-lit: std::is_arithmetic : No +out-lit: can_lexical2string : Yes +out-lit: can_convertToString : Yes +out-lit: use_StringConversion4Stream : No +out-lit: Type: StrConstRef ...... +out-lit: is_StringLike : No +out-lit: BasicallyString : No +out-lit: BasicallyCString : No +out-lit: std::is_arithmetic : No +out-lit: can_lexical2string : No +out-lit: can_convertToString : Yes +out-lit: use_StringConversion4Stream : Yes +out-lit: Type: CharLit ...... +out-lit: is_StringLike : Yes +out-lit: BasicallyString : No +out-lit: BasicallyCString : Yes +out-lit: std::is_arithmetic : No +out-lit: can_lexical2string : Yes +out-lit: can_convertToString : Yes +out-lit: use_StringConversion4Stream : No +out-lit: Type: CharPtr ...... +out-lit: is_StringLike : Yes +out-lit: BasicallyString : No +out-lit: BasicallyCString : Yes +out-lit: std::is_arithmetic : No +out-lit: can_lexical2string : Yes +out-lit: can_convertToString : Yes +out-lit: use_StringConversion4Stream : No +out-lit: Type: Reticent ...... +out-lit: is_StringLike : No +out-lit: BasicallyString : No +out-lit: BasicallyCString : No +out-lit: std::is_arithmetic : No +out-lit: can_lexical2string : No +out-lit: can_convertToString : No +out-lit: use_StringConversion4Stream : Yes +out-lit: Type: P ...... +out-lit: is_StringLike> : No +out-lit: BasicallyString> : No +out-lit: BasicallyCString> : No +out-lit: std::is_arithmetic> : No +out-lit: can_lexical2string> : No +out-lit: can_convertToString> : Yes +out-lit: use_StringConversion4Stream> : Yes +out-lit: Type: GenNode ...... +out-lit: is_StringLike : No +out-lit: BasicallyString : No +out-lit: BasicallyCString : No +out-lit: std::is_arithmetic : No +out-lit: can_lexical2string : No +out-lit: can_convertToString : Yes +out-lit: use_StringConversion4Stream : Yes +out-lit: Type: GenNodePtr ...... +out-lit: is_StringLike : No +out-lit: BasicallyString : No +out-lit: BasicallyCString : No +out-lit: std::is_arithmetic : No +out-lit: can_lexical2string : No +out-lit: can_convertToString : No +out-lit: use_StringConversion4Stream : No +out-lit: Type: GenNodeRef ...... +out-lit: is_StringLike : No +out-lit: BasicallyString : No +out-lit: BasicallyCString : No +out-lit: std::is_arithmetic : No +out-lit: can_lexical2string : No +out-lit: can_convertToString : Yes +out-lit: use_StringConversion4Stream : Yes +out-lit: Type: GenNodeRRef ...... +out-lit: is_StringLike : No +out-lit: BasicallyString : No +out-lit: BasicallyCString : No +out-lit: std::is_arithmetic : No +out-lit: can_lexical2string : No +out-lit: can_convertToString : Yes +out-lit: use_StringConversion4Stream : Yes +out-lit: Type: P ...... +out-lit: is_StringLike> : No +out-lit: BasicallyString> : No +out-lit: BasicallyCString> : No +out-lit: std::is_arithmetic> : No +out-lit: can_lexical2string> : No +out-lit: can_convertToString> : Yes +out-lit: use_StringConversion4Stream> : Yes +out-lit: ───────────────────────────╼━━━━━━━━━━╾─────────────────────────── +out-lit: smart-ptr, no string conv...«Reticent» +out-lit: smart-ptr, custom conv......GenNode-ID("Hui")-DataCap|«string»|Buh +out-lit: reference, no string conv...«Reticent» +out-lit: reference, custom conv......GenNode-ID("Hui")-DataCap|«string»|Buh +out: pointer, custom conv......╲_....+ ↗GenNode-ID("Hui")-DataCap|«string»|Buh +out-lit: smart-ptr, NULL pointee.....⟂ P +out-lit: pointer, NULL pointee.....⟂ «GenNode» return: 0 END @@ -415,6 +467,11 @@ return: 0 END +TEST "path array abstraction" PathArray_test < shared_lock_; - - Probe() {} - ~Probe() {} - }; - - - } // (End) test classes and data.... - - - - - - + } @@ -70,9 +50,10 @@ namespace test { * @test check proper handling of class (not instance)-based Monitor locks. * Because no instance is available in this case, a hidden storage for the * Monitor object needs to be provided in a way safe for use even in the - * static startup/shutdown phase. This test validates the associated - * refcounting and object creation works as expected. It does \em not - * validate the locking functionality as such. + * static startup/shutdown phase. This can not directly validate this + * allocation of a shared monitor object behind the scenes, but it + * can verify the monitor is indeed shared by all ClassLock instances + * templated to a specific type. * * @see sync.hpp */ @@ -82,15 +63,37 @@ namespace test { virtual void run (Arg) { - { - Probe objs[NUM_INSTANCES]; - - CHECK (1 == objs[0].shared_lock_.use_count()); - } + int contended = 0; - ClassLock get_class_lock; - CHECK ( 1 == get_class_lock.use_count()); // embedded PerClassMonitor got created exactly once - } // and stays alive until static dtors are called.... + using Threads = lib::ScopedCollection; + + // Start a bunch of threads with random access pattern + Threads threads{NUM_THREADS, + [&](Threads::ElementHolder& storage) + { + storage.create ("Sync-ClassLock stress test" + ,[&]{ + for (uint i=0; i guard; + ++contended; + } + } + }); + } + }; + + for (auto& thread : threads) + thread.join(); // block until thread terminates + + CHECK (contended == NUM_THREADS * NUM_LOOP, + "ALARM: Lock failed, concurrent modification " + "during counter increment. Delta == %d" + ,NUM_THREADS * NUM_LOOP - contended); + } }; diff --git a/tests/backend/test-fileheader.c b/tests/backend/test-fileheader.c index 70985b67c..6787e4c56 100644 --- a/tests/backend/test-fileheader.c +++ b/tests/backend/test-fileheader.c @@ -69,7 +69,7 @@ TEST (create_nowrite) { lumiera_fileheader header = lumiera_fileheader_create (file, "TEST", 0, sizeof (lumiera_fileheader), NULL); - CHECK(lumiera_error() == LUMIERA_ERROR_FILEHEADER_NOWRITE); + CHECK (lumiera_error() == LUMIERA_ERROR_FILEHEADER_NOWRITE); lumiera_fileheader_close (&header, LUMIERA_FILEHEADER_FLAG_CLEAN); } @@ -91,8 +91,8 @@ TEST (acquire_wrongheader) sizeof (lumiera_fileheader), LUMIERA_FILEHEADER_FLAG_CLEAN, LUMIERA_FILEHEADER_FLAG_CLEAN); - CHECK(!header.header); - CHECK(lumiera_error() == LUMIERA_ERROR_FILEHEADER_HEADER); + CHECK (!header.header); + CHECK (lumiera_error() == LUMIERA_ERROR_FILEHEADER_HEADER); lumiera_fileheader_close (&header, LUMIERA_FILEHEADER_FLAG_CLEAN); } @@ -114,8 +114,8 @@ TEST (acquire_basic) sizeof (lumiera_fileheader), LUMIERA_FILEHEADER_FLAG_CLEAN, LUMIERA_FILEHEADER_FLAG_CLEAN); - CHECK(header.header); - CHECK(!lumiera_error()); + CHECK (header.header); + CHECK (!lumiera_error()); CHECK (lumiera_fileheader_version (&header) == 0); @@ -141,8 +141,8 @@ TEST (acquire_basic_readonly) sizeof (lumiera_fileheader), LUMIERA_FILEHEADER_FLAG_CLEAN, NULL); - CHECK(header.header); - CHECK(!lumiera_error()); + CHECK (header.header); + CHECK (!lumiera_error()); CHECK (lumiera_fileheader_version (&header) == 0); diff --git a/tests/backend/thread-wrapper-self-recognition-test.cpp b/tests/backend/thread-wrapper-self-recognition-test.cpp index d6e53dc05..5816440aa 100644 --- a/tests/backend/thread-wrapper-self-recognition-test.cpp +++ b/tests/backend/thread-wrapper-self-recognition-test.cpp @@ -34,21 +34,18 @@ using test::Test; namespace backend { namespace test { - using lumiera::error::LUMIERA_ERROR_LOGIC; namespace { class TestThread : Thread { - void - doIt() - { - CHECK (invocation_happens_within_this_thread()); - } - public: TestThread() - : Thread{"test Thread self recognition", [&]() { doIt(); }} + : Thread{"test Thread self recognition" + ,[&]() + { + CHECK (invocation_happens_within_this_thread()); + }} { } bool diff --git a/tests/basics/call-queue-test.cpp b/tests/basics/call-queue-test.cpp index aba790ba6..42c931fa4 100644 --- a/tests/basics/call-queue-test.cpp +++ b/tests/basics/call-queue-test.cpp @@ -200,6 +200,7 @@ namespace test{ uint cnt = rand() % MAX_RAND_STEPS; uint delay = rand() % MAX_RAND_DELAY; + syncPoint(); // block until all threads are ready for (uint i=0; i + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +* *****************************************************/ + +/** @file dependency-configuration-test.cpp + ** unit test \ref DependencyConfiguration_test + ** @remark this test was written 3/2018 as a byproduct of the third rewrite + ** of the framework for singletons and dependency-injection. It is + ** quite redundant with the previously existing DependencyFactory_test + */ + + + +#include "lib/test/run.hpp" +#include "lib/format-cout.hpp" +#include "lib/test/test-helper.hpp" +#include "lib/util.hpp" + +#include "lib/depend.hpp" +#include "lib/depend-inject.hpp" + + + +namespace lib { +namespace test{ + + using ::Test; + using util::isSameObject; + + namespace { + + struct Dum + : util::NonCopyable + { + virtual ~Dum() { } + virtual int probe() =0; + }; + + + int checksum = 0; + + template + struct Dummy + : Dum + { + Dummy() { checksum += N; } + ~Dummy() { checksum -= N; } + + virtual int + probe() override + { + return N * checksum; + } + }; + } + + using error::LERR_(LIFECYCLE); + using error::LERR_(FATAL); + + + + + + /***************************************************************************//** + * @test verify the various modes of creating dependencies. + * - standard case is singleton creation + * - configuration of a specific subclass for the singleton + * - expose a service with explicit lifecycle + * - use of a custom factory function + * - injection of a mock implementation for unit tests + * + * @remark this test basically covers the same ground as DependencyFactory_test; + * but while the latter exists since our second rewrite of lib::Depend (2013), + * this test here is a byproduct of the third rewrite from 2018 and focuses + * more on the configuration and instance identities. + * @see lib::Dependency + * @see Singleton_test + */ + class DependencyConfiguration_test + : public Test + { + + + virtual void + run (Arg) + { + checksum = 0; + + verify_Singleton(); + verify_SubclassSingleton(); + verify_expose_Service_with_Lifecycle(); + verify_automaticReplacement(); + verify_customFactory(); + + CHECK (9+7+5+1 == checksum); // singletons stay alive until application shutdown + } + + + /** @test without special configuration, singletons are injected as dependency */ + void + verify_Singleton() + { + Depend> dep11; + Depend> dep5; + Depend> dep12; + + CHECK (1 == sizeof(dep11)); + CHECK (1 == sizeof(dep12)); + CHECK (1 == sizeof(dep5)); + + // no singleton instance created yet + CHECK ( 0 == checksum ); + + CHECK ( 1*1 == dep11().probe() ); + CHECK ( 1 == checksum ); + CHECK ((1+5)*5 == dep5().probe() ); + CHECK ((1+5) == checksum ); + CHECK ((1+5)*1 == dep12().probe() ); + CHECK ((1+5) == checksum ); + + CHECK (not isSameObject (dep11, dep12)); + CHECK ( isSameObject (dep11(), dep12())); + } + + + /** @test preconfigure a specific subclass to be injected as singleton dependency */ + void + verify_SubclassSingleton() + { + // unable to create singleton instance of abstract baseclass + VERIFY_ERROR (FATAL, Depend{}() ); + + CHECK ((1+5) == checksum ); + + Depend dumm; + DependInject::useSingleton>(); + CHECK ((1+5) == checksum ); + + CHECK ((1+5+7)*7 == dumm().probe() ); + CHECK ((1+5+7) == checksum ); + + VERIFY_ERROR (LIFECYCLE, DependInject::useSingleton>() ); + CHECK ((1+5+7)*7 == Depend{}().probe() ); + CHECK ((1+5+7)*7 == dumm().probe() ); + CHECK ((1+5+7) == checksum ); + } + + + /** @test expose a dedicated service instance, which can be shut down */ + void + verify_expose_Service_with_Lifecycle() + { + CHECK ((1+5+7) == checksum ); + + struct SubDummy + : Dummy<3> + { + virtual int + probe() override + { + return offset - checksum; + } + + int offset = 0; + }; + + Depend> dep3; + CHECK ((1+5+7) == checksum ); + { + DependInject>::ServiceInstance service{}; + CHECK (service); + CHECK ((1+5+7+3) == checksum ); + CHECK (-(1+5+7+3) == dep3().probe() ); + CHECK ((1+5+7+3) == checksum ); + service->offset = (1+5+7); + CHECK ( -3 == dep3().probe() ); + CHECK ((1+5+7+3) == checksum ); + } + CHECK ((1+5+7) == checksum ); + VERIFY_ERROR (LIFECYCLE, dep3().probe() ); + VERIFY_ERROR (LIFECYCLE, DependInject::ServiceInstance{} ); + CHECK ((1+5+7) == checksum ); + } + + + + /** @test injecting test mocks temporarily */ + void + verify_automaticReplacement() + { + Depend dumm; + Depend> depp; + CHECK ((1+5+7) == checksum ); + CHECK ((1+5+7)*7 == dumm().probe() ); + VERIFY_ERROR (LIFECYCLE, depp().probe() ); + + struct Mock + : Dummy<3> + { + virtual int + probe() override + { + return response; + } + + int response = -1; + }; + + {////////////////////////////////////////////////////TEST-Scope + DependInject::Local mockDumm; + DependInject>::Local mockDummy3; + CHECK ((1+5+7) == checksum ); + + CHECK (!mockDumm); + CHECK (!mockDummy3); + CHECK (-1 == dumm().probe() ); // NOTE: now returning the response from the mock instance + CHECK ( mockDumm); + CHECK (!mockDummy3); + CHECK ((1+5+7+3) == checksum ); + CHECK (-1 == mockDumm->probe() ); + CHECK ((1+5+7+3) == checksum ); + + mockDumm->response = 11; + CHECK (11 == dumm().probe() ); // NOTE: now returning the response changed on the mock instance + + CHECK (!mockDummy3); // the second mock is still in not yet created state... + CHECK ((1+5+7+3) == checksum ); + CHECK (-1 == depp().probe() ); + CHECK ((1+5+7+3+3) == checksum ); // ...and now we got a second mock instance! + CHECK ( mockDummy3); + CHECK (-1 == mockDummy3->probe() ); + CHECK ((1+5+7+3+3) == checksum ); + mockDummy3->response = 22; + CHECK (22 == depp().probe() ); + mockDumm->response = 12; + CHECK (22 == depp().probe() ); // these are really two distinct instances + CHECK (12 == dumm().probe() ); + CHECK ((1+5+7+3+3) == checksum ); + }////////////////////////////////////////////////////(End)TEST-Scope + + // Back to normal: the Mocks are gone, original behaviour uncovered + CHECK ((1+5+7) == checksum ); + CHECK ((1+5+7)*7 == dumm().probe() ); + VERIFY_ERROR (LIFECYCLE, depp().probe() ); + CHECK ((1+5+7) == checksum ); + + {/////////////////////////////////////////////////////////Service-Scope + DependInject>::ServiceInstance service{}; + CHECK ((1+5+7+3) == checksum ); // NOTE: we got a new Dummy<3> service instance + CHECK (-1 == depp().probe() ); // ..... which returns the pristine mock response + service->response = 33; + CHECK (33 == depp().probe() ); + CHECK ((1+5+7+3) == checksum ); + + {/////////////////////////////////////////////////////////NESTED-TEST-Scope + DependInject>::Local mockDummy31; + CHECK (!mockDummy31); + CHECK ((1+5+7+3) == checksum ); // ...while SerivceInstance is created eagerly + CHECK (-1 == depp().probe() ); // the Local mock instance is only created on-demand + CHECK ((1+5+7+3+3) == checksum ); + mockDummy31->response = 44; + CHECK (44 == depp().probe() ); + CHECK (44 == mockDummy31->probe() ); + CHECK (33 == service->probe() ); + CHECK (mockDummy31->response != service->response); + service->response = 34; + CHECK (44 == depp().probe() ); // NOTE: remains shadowed by the mockDummy + CHECK (44 == mockDummy31->probe() ); + CHECK (34 == service->probe() ); + CHECK ((1+5+7+3+3) == checksum ); + }/////////////////////////////////////////////////////////(End)NESTED-TEST-Scope + + // Now the mock is gone and the service instance becomes uncovered + CHECK ((1+5+7+3) == checksum ); + CHECK (34 == depp().probe() ); // now reveals the response changed from within the nested test scope + CHECK ((1+5+7+3) == checksum ); + }/////////////////////////////////////////////////////////(End)Service-Scope + + // Back to normal: Mocks is gone, Service is shutdown, original behaviour uncovered + CHECK ((1+5+7) == checksum ); + VERIFY_ERROR (LIFECYCLE, depp().probe() ); + CHECK ((1+5+7)*7 == dumm().probe() ); + CHECK ((1+5+7) == checksum ); + } + + + + /** @test instance creation can be preconfigured with a closure + * Both Singleton and Test-Mock creation can optionally be performed through a + * user provided Lambda or Functor. To demonstrate this, we use a `Veryspecial` local class, + * which takes an `int&` as constructor parameter -- and we create the actual instance through + * a lambda, which happens to capture a local variable by reference. + * @warning this can be dangerous; in the example demonstrated here, the created singleton instance + * continues to live until termination of the test-suite. After leaving this test function, + * it thus holds a dangling reference, pointing into stack memory.... + */ + void + verify_customFactory() + { + CHECK ((1+5+7) == checksum ); + + struct Veryspecial + : Dummy<9> + { + Veryspecial(int& ref) + : magic_{ref} + { } + + int& magic_; + + virtual int + probe() override + { + return magic_++; + } + }; + + // NOTE: the following is rejected due to missing default ctor + DependInject>::useSingleton(); + VERIFY_ERROR (FATAL, Depend>{}() ); + + + int backdoor = 22; + + DependInject>::useSingleton ([&]{ return new Veryspecial{backdoor}; }); + + CHECK ((1+5+7) == checksum ); + CHECK ( 22 == backdoor ); + + Depend> tricky; + CHECK ((1+5+7) == checksum ); + CHECK (22 == backdoor ); + + CHECK (22 == tricky().probe()); + CHECK (23 == backdoor ); + CHECK ((1+5+7+9) == checksum ); // Veryspecial Dummy<9> subclass was created on the heap + // and will continue to live there until the testsuite terminates + backdoor = 41; + CHECK (41 == tricky().probe()); + CHECK (42 == backdoor ); + + + Depend dumm; + CHECK ((1+5+7+9)*7 == dumm().probe() ); + + {////////////////////////////////////////////////////TEST-Scope + +////////////// NOTE: the following does not compile +// // since Veryspecial has no default ctor... +// // +// DependInject::Local impossible; + + DependInject::Local insidious ([&]{ return new Veryspecial{backdoor}; }); + + CHECK ((1+5+7+9) == checksum ); + CHECK (not insidious); + + CHECK (42 == dumm().probe() ); + CHECK (43 == backdoor ); + CHECK ((1+5+7+9+9) == checksum ); + + CHECK (isSameObject (dumm(), *insidious)); + + CHECK (43 == tricky().probe()); + CHECK (44 == backdoor ); + + backdoor = -1; + CHECK (-1 == dumm().probe() ); + CHECK ( 0 == backdoor ); + + CHECK ((1+5+7+9+9) == checksum ); + }////////////////////////////////////////////////////(End)TEST-Scope + + CHECK ((1+5+7+9) == checksum ); + CHECK ((1+5+7+9)*7 == dumm().probe() ); + CHECK ( 0 == tricky().probe()); + CHECK (+1 == backdoor ); + } // NOTE: Veryspecial holds a dangling reference into stack memory from now on! + }; + + + + LAUNCHER (DependencyConfiguration_test, "unit common"); + + +}} // namespace lib::test diff --git a/tests/library/dependency-factory-test.cpp b/tests/basics/dependency-factory-test.cpp similarity index 70% rename from tests/library/dependency-factory-test.cpp rename to tests/basics/dependency-factory-test.cpp index 7dca01d61..3ac29aa6c 100644 --- a/tests/library/dependency-factory-test.cpp +++ b/tests/basics/dependency-factory-test.cpp @@ -22,6 +22,9 @@ /** @file dependency-factory-test.cpp ** unit test \ref DependencyFactory_test + ** @remark this is an old test from 2013 and thus verifies that the functionality + ** for dependency-injection was not broken by the rewrite in 2018 + ** @see dependency-configuration-test.cpp */ @@ -31,7 +34,7 @@ #include "lib/util.hpp" #include "lib/depend.hpp" -#include "lib/test/depend-4test.hpp" +#include "lib/depend-inject.hpp" #include "test-target-obj.hpp" #include @@ -70,16 +73,15 @@ namespace test{ struct SubSub : Sub - { - /** marker typedef for Depend4Test, - * allowing to pick the correct Depend - * to apply the instrumentation with the test mock. */ - typedef Sub ServiceInterface; - }; + { }; struct SubSubSub : SubSub { }; + + struct SubSubDub + : SubSub + { }; } @@ -95,6 +97,7 @@ namespace test{ * * @see lib::Dependency * @see Singleton_test + * @see DependencyConfiguration_test newer test from 2018 to cover the same ground */ class DependencyFactory_test : public Test { @@ -107,7 +110,6 @@ namespace test{ verify_SubclassCreation(); verify_FactoryDefinition_is_sticky(); verify_customFactory(); - verify_temporaryReplacement(); verify_automaticReplacement(); } @@ -127,9 +129,12 @@ namespace test{ void verify_SubclassCreation() { - Depend specialAccessor(buildSingleton()); + Depend specialAccessor; Depend genericAccessor; + // configure singleton subclass (prior to first use) + DependInject::useSingleton(); + SubSub& oSub = specialAccessor(); Sub& o = genericAccessor(); @@ -147,71 +152,33 @@ namespace test{ SubSub& oSub = otherSpecialAccessor(); CHECK ( INSTANCEOF (SubSubSub, &oSub)); - Depend yetAnotherSpecialAccessor; + Depend yetAnotherSpecialAccessor; SubSub& yetAnotherInstance = yetAnotherSpecialAccessor(); CHECK ( INSTANCEOF (SubSubSub, &yetAnotherInstance)); + + // both refer to the same configuration and thus access the singleton + CHECK (isSameObject (oSub, yetAnotherInstance)); } void verify_customFactory() { - Depend customisedAccessor(&customFactoryFunction); - Depend otherSpecialAccessor; + DependInject::useSingleton (customFactoryFunction); - SubSub& oSub = otherSpecialAccessor(); - SubSubSub& oSubS = customisedAccessor(); + Depend customised; - CHECK (!isSameObject (oSub, oSubS)); - CHECK ( INSTANCEOF (SubSubSub, &oSub)); - CHECK ( INSTANCEOF (SubSubSub, &oSubS)); - - CHECK (oSub.instanceID_ != oSubS.instanceID_); - CHECK (MAX_ID + 10 == oSubS.instanceID_); + CHECK ( INSTANCEOF (SubSubDub, &customised())); + CHECK (MAX_ID + 10 == customised().instanceID_); } - static void* + static SubSubDub* customFactoryFunction (void) { - static SubSubSub specialInstance; - // NOTE: the factory function is responsible - // for managing the instance's lifecycle - - specialInstance.instanceID_ = MAX_ID + 10; - return &specialInstance; - } - - - - void - verify_temporaryReplacement() - { - typedef Depend GenericAccessor; - - GenericAccessor genericAccessor; - Sub& original = genericAccessor(); - uint oID = original.instanceID_; - - SubSubSub mockObject; - Sub* shaddowedOriginal = GenericAccessor::injectReplacement (&mockObject); - - Sub& replacement = genericAccessor(); - CHECK ( isSameObject (replacement, mockObject)); - CHECK (!isSameObject (original, replacement)); - CHECK ( isSameObject (original, *shaddowedOriginal)); - - Depend special; - Depend custom; - - CHECK(!isSameObject (replacement, special() )); - CHECK(!isSameObject (replacement, custom() )); - - GenericAccessor::injectReplacement (shaddowedOriginal); - - Sub& nextFetch = genericAccessor(); - CHECK (isSameObject (original, nextFetch)); - CHECK (oID == nextFetch.instanceID_); + SubSubDub* specialInstance = new SubSubDub; + specialInstance->instanceID_ = MAX_ID + 10; + return specialInstance; } @@ -223,8 +190,26 @@ namespace test{ Sub& original = genericAccessor(); uint oID = original.instanceID_; - { - Depend4Test withinThisScope; + {////////////////////////////////////////////////////TEST-Scope + DependInject::Local mockObject; + + Sub& replacement = genericAccessor(); + CHECK ( isSameObject (replacement, *mockObject)); + CHECK (!isSameObject (original, replacement)); + + Depend special; + Depend custom; + + CHECK(!isSameObject (replacement, special() )); + CHECK(!isSameObject (replacement, custom() )); + }////////////////////////////////////////////////////(End)TEST-Scope + + Sub& nextFetch = genericAccessor(); + CHECK (isSameObject (original, nextFetch)); + CHECK (oID == nextFetch.instanceID_); + + {////////////////////////////////////////////////////TEST-Scope-2 + DependInject::Local otherMock; Sub& replacement = genericAccessor(); uint repID = replacement.instanceID_; @@ -250,11 +235,10 @@ namespace test{ CHECK (!isSameObject (original, subTypeAccess)); CHECK (repID != subTypeAccess.instanceID_); CHECK ( oID != subTypeAccess.instanceID_); - } + }////////////////////////////////////////////////////(End)TEST-Scope-2 - Sub& nextFetch = genericAccessor(); - CHECK (isSameObject (original, nextFetch)); - CHECK (oID == nextFetch.instanceID_); + CHECK (isSameObject (original, genericAccessor())); + CHECK (oID == genericAccessor().instanceID_); } }; diff --git a/tests/basics/diagnostic-context-test.cpp b/tests/basics/diagnostic-context-test.cpp index 723ad2589..c674b10b0 100644 --- a/tests/basics/diagnostic-context-test.cpp +++ b/tests/basics/diagnostic-context-test.cpp @@ -56,7 +56,7 @@ namespace test{ } // (End) test setup.... using backend::ThreadJoinable; - using error::LUMIERA_ERROR_LOGIC; + using error::LERR_(LOGIC); using std::rand; diff --git a/tests/library/singleton-subclass-test.cpp b/tests/basics/singleton-subclass-test.cpp similarity index 77% rename from tests/library/singleton-subclass-test.cpp rename to tests/basics/singleton-subclass-test.cpp index 521189a35..8726dfff3 100644 --- a/tests/library/singleton-subclass-test.cpp +++ b/tests/basics/singleton-subclass-test.cpp @@ -33,6 +33,7 @@ #include "test-target-obj.hpp" #include "lib/depend.hpp" +#include "lib/depend-inject.hpp" #include @@ -46,7 +47,7 @@ using std::string; namespace lib { namespace test{ - using lumiera::error::LUMIERA_ERROR_LIFECYCLE; + using lumiera::error::LERR_(LIFECYCLE); /** * Target object to be instantiated as Singleton @@ -65,7 +66,7 @@ namespace test{ Interface () : TestTargetObj(cnt) {} virtual ~Interface() {} - friend class lib::DependencyFactory; + friend class lib::DependencyFactory; }; int Interface::cnt = 0; @@ -86,13 +87,12 @@ namespace test{ /***************************************************************//** - * @test specialised variant of the Singleton Factory, for creating - * subclasses (implementation classes) without coupling the - * caller to the concrete class type. - * Expected results: an instance of the subclass is created. + * @test specific dependency-injection setup, to create a singleton + * subclass (implementation class) instance, without coupling + * the caller to the concrete type. + * @remark Expected results: an instance of the subclass is created. * @see lib::Depend - * @see lib::buildSingleton() - * @see lib/dependency-factory.hpp + * @see lib/depend-inject.hpp */ class SingletonSubclass_test : public Test { @@ -106,12 +106,11 @@ namespace test{ Interface::setCountParam(num); - // marker to declare the concrete type to be created - DependencyFactory::InstanceConstructor factoryFunction = buildSingleton(); + // configuration to use the subclass on demand + DependInject::useSingleton(); - // define an instance of the Singleton factory, - // specialised to create the concrete Type passed in - Depend instance (factoryFunction); + // define an instance of the Singleton factory as always... + Depend instance; // Now use the Singleton factory... // Note: we get the Base type @@ -119,6 +118,8 @@ namespace test{ Interface& t2 = instance(); CHECK (isSameObject (t1, t2), "not a Singleton, got two different instances." ); + CHECK ( INSTANCEOF (Impl,&t1)); // got the subclass as expected + CHECK ("Implementation" == t2.identify()); cout << "calling a non-static method on the Singleton-" << t1.identify() << endl @@ -132,11 +133,13 @@ namespace test{ void verify_error_detection () { - VERIFY_ERROR (LIFECYCLE, Depend instance (buildSingleton()) ); - VERIFY_ERROR (LIFECYCLE, Depend instance (buildSingleton()) ); + VERIFY_ERROR (LIFECYCLE, DependInject::useSingleton() ); Depend newFactory; CHECK ( INSTANCEOF (Impl, &newFactory() )); // works as before + +//////////does not compile due to incompatible baseclass +// DependInject::useSingleton(); } }; diff --git a/tests/library/singleton-test.cpp b/tests/basics/singleton-test.cpp similarity index 98% rename from tests/library/singleton-test.cpp rename to tests/basics/singleton-test.cpp index 1fd565fb4..bee1e11d5 100644 --- a/tests/library/singleton-test.cpp +++ b/tests/basics/singleton-test.cpp @@ -59,7 +59,7 @@ namespace test{ protected: TargetObj () : TestTargetObj(cnt) {} - friend class lib::DependencyFactory; + friend class lib::DependencyFactory; }; int TargetObj::cnt = 0; diff --git a/tests/library/singleton-testmock-test.cpp b/tests/basics/singleton-testmock-test.cpp similarity index 67% rename from tests/library/singleton-testmock-test.cpp rename to tests/basics/singleton-testmock-test.cpp index 8749b7957..90f0cd91d 100644 --- a/tests/library/singleton-testmock-test.cpp +++ b/tests/basics/singleton-testmock-test.cpp @@ -27,7 +27,7 @@ #include "lib/test/run.hpp" -#include "lib/depend.hpp" +#include "lib/depend-inject.hpp" #include "lib/util.hpp" #include "lib/format-cout.hpp" @@ -45,14 +45,14 @@ namespace test{ * Client Class normally to be instantiated as Singleton. * But for tests, this class should be replaced by a Mock.... */ - class TestSingletonO + class TestSingO { int callCnt_; Symbol typid_; _Fmt msg_; public: - TestSingletonO(Symbol ty="TestSingletonO") + TestSingO(Symbol ty="TestSingO") : callCnt_(0) , typid_(ty) , msg_("%s::doIt() call=%d\n") @@ -61,7 +61,7 @@ namespace test{ } virtual - ~TestSingletonO() + ~TestSingO() { TRACE (test, "dtor %s", typid_.c()); } @@ -83,17 +83,23 @@ namespace test{ /** * Mock-1 to replace the Client Class... */ - struct Mock_1 : TestSingletonO + struct Mock_1 : TestSingO { - Mock_1() : TestSingletonO("Mock_1") { }; + Mock_1() : TestSingO("Mock_1") { }; }; /** * Mock-2 to replace the Client Class... + * @note no default ctor */ - struct Mock_2 : TestSingletonO + struct Mock_2 : TestSingO { - Mock_2() : TestSingletonO("Mock_2") { }; + int id; + + Mock_2(Literal specialID, int i) + : TestSingO{Symbol (_Fmt{"%s_%d"} % specialID % i)} + , id{i} + { }; }; @@ -112,7 +118,7 @@ namespace test{ * Client Object, then replace it by two different mocks, * and finally restore the original Client Object. * @see lib::Depend - * @see lib::test::Depend4Test + * @see depend-inject.hpp * @see DependencyFactory_test */ class SingletonTestMock_test : public Test @@ -121,28 +127,36 @@ namespace test{ void run (Arg) { - Depend sing; + Depend sing; sing().doIt(); sing().doIt(); CHECK (sing().getCnt() == 2); - Mock_1 mock_1; - TestSingletonO* original = - sing.injectReplacement (&mock_1); - sing().doIt(); - sing().doIt(); - sing().doIt(); - sing().doIt(); - sing().doIt(); - CHECK (sing().getCnt() == 5); + { + // shadow by local Mock instance + DependInject::Local mock_1; + sing().doIt(); + sing().doIt(); + sing().doIt(); + sing().doIt(); + sing().doIt(); + CHECK (sing().getCnt() == 5); + + // shadow again by different local Mock, this time with special ctor call + int instanceID = 0; + DependInject::Local mock_2 ([&]{ return new Mock_2{"Mock", instanceID}; }); + + // NOTE: the ctor call for the Mock really happens delayed... + instanceID = rand() % 10; + sing().doIt(); // ctor invoked on first access + CHECK (sing().getCnt() == 1); + + // can access the Mock for instrumentation + CHECK (instanceID == mock_2->id); + + }// original instance automatically un-shadowed here - Mock_2 mock_2; - sing.injectReplacement (&mock_2); - sing().doIt(); - CHECK (sing().getCnt() == 1); - - sing.injectReplacement (original); // un-shadowing original instance CHECK (sing().getCnt() == 2); sing().doIt(); CHECK (sing().getCnt() == 3); diff --git a/tests/library/test-target-obj.hpp b/tests/basics/test-target-obj.hpp similarity index 100% rename from tests/library/test-target-obj.hpp rename to tests/basics/test-target-obj.hpp diff --git a/tests/basics/time/digxel-configurations-test.cpp b/tests/basics/time/digxel-configurations-test.cpp index 12d0858a3..ea929d082 100644 --- a/tests/basics/time/digxel-configurations-test.cpp +++ b/tests/basics/time/digxel-configurations-test.cpp @@ -34,7 +34,7 @@ #include -using lumiera::error::LUMIERA_ERROR_ASSERTION; +using lumiera::error::LERR_(ASSERTION); using util::isSameObject; using util::typeStr; using std::rand; diff --git a/tests/basics/time/time-control-test.cpp b/tests/basics/time/time-control-test.cpp index 624c211d0..08f264785 100644 --- a/tests/basics/time/time-control-test.cpp +++ b/tests/basics/time/time-control-test.cpp @@ -57,7 +57,7 @@ namespace test{ using proc::asset::meta::TimeGrid; using lib::meta::Types; using lib::meta::InstantiateChainedCombinations; - using error::LUMIERA_ERROR_UNCONNECTED; + using error::LERR_(UNCONNECTED); @@ -81,7 +81,7 @@ namespace test{ */ template class TestListener - : boost::noncopyable + : util::NonCopyable { mutable ScopedHolder received_; diff --git a/tests/basics/time/time-parsing-test.cpp b/tests/basics/time/time-parsing-test.cpp index 499b4efd5..61a77978e 100644 --- a/tests/basics/time/time-parsing-test.cpp +++ b/tests/basics/time/time-parsing-test.cpp @@ -42,7 +42,7 @@ namespace time{ namespace test{ using proc::asset::meta::TimeGrid; - using format::LUMIERA_ERROR_INVALID_TIMECODE; + using format::LERR_(INVALID_TIMECODE); namespace { // Helper for writing test cases diff --git a/tests/basics/time/time-value-test.cpp b/tests/basics/time/time-value-test.cpp index bf9b1a13e..48077d80f 100644 --- a/tests/basics/time/time-value-test.cpp +++ b/tests/basics/time/time-value-test.cpp @@ -38,7 +38,7 @@ using boost::lexical_cast; using util::isnil; using std::string; -using lumiera::error::LUMIERA_ERROR_BOTTOM_VALUE; +using lumiera::error::LERR_(BOTTOM_VALUE); namespace lib { namespace time{ diff --git a/tests/core/application/subsystem-runner-test.cpp b/tests/core/application/subsystem-runner-test.cpp index 84678420a..90a487584 100644 --- a/tests/core/application/subsystem-runner-test.cpp +++ b/tests/core/application/subsystem-runner-test.cpp @@ -80,8 +80,8 @@ namespace test { /** marker for simulated failure exceptions */ LUMIERA_ERROR_DEFINE( TEST, "simulated failure."); - using error::LUMIERA_ERROR_LOGIC; - using error::LUMIERA_ERROR_STATE; + using error::LERR_(LOGIC); + using error::LERR_(STATE); @@ -137,7 +137,7 @@ namespace test { return true; else if ("throw"==startSpec) //---starting flounders - throw error::Fatal("simulated failure to start the subsystem", LUMIERA_ERROR_TEST); + throw error::Fatal("simulated failure to start the subsystem", LERR_(TEST)); return started_; } @@ -200,7 +200,7 @@ namespace test { if ("true" ==runSpec) termination(0); // signal regular termination if ("throw"==runSpec) { - Error problemIndicator("simulated Problem terminating subsystem",LUMIERA_ERROR_TEST); + Error problemIndicator("simulated Problem terminating subsystem",LERR_(TEST)); lumiera_error(); // reset error state.... // Note: in real life this actually // would be an catched exception! diff --git a/tests/core/backend/media-access-mock-test.cpp b/tests/core/backend/media-access-mock-test.cpp index 6d4113bb8..954d8612a 100644 --- a/tests/core/backend/media-access-mock-test.cpp +++ b/tests/core/backend/media-access-mock-test.cpp @@ -27,7 +27,7 @@ #include "backend/media-access-facade.hpp" #include "backend/media-access-mock.hpp" -#include "lib/test/depend-4test.hpp" +#include "lib/depend-inject.hpp" #include "lib/format-cout.hpp" #include "lib/test/run.hpp" @@ -35,7 +35,7 @@ #include -using lib::test::Depend4Test; +using lib::DependInject; using lib::Literal; using std::string; @@ -46,7 +46,6 @@ namespace test { - /****************************************************************************//** * @test inject a Mock object replacing the backend_interface::MediaAccessFacade. * Verify if the Mock object behaves as expected when calling the Facade. @@ -57,7 +56,7 @@ namespace test { virtual void run(Arg) { - Depend4Test within_this_scope; + DependInject::Local useMockMedia; queryScenario ("test-1"); queryScenario ("test-2"); diff --git a/tests/core/backend/media-access-mock.cpp b/tests/core/backend/media-access-mock.cpp index 1e6e92855..fac990051 100644 --- a/tests/core/backend/media-access-mock.cpp +++ b/tests/core/backend/media-access-mock.cpp @@ -26,6 +26,7 @@ ** information from the data backend. The Mock implementation instead holds ** a map of fixed response which will be delivered when querying some magic ** filenames. + ** @todo this facility was occasionally used until 2011, yet never really completed ** ** @see mediaaccessmocktest.cpp validating the Mock ** @see MediaAccessFactory the real thing @@ -83,7 +84,9 @@ namespace test { private: static int _i_; - ChanHandle genH() + + ChanHandle + genH() { return reinterpret_cast (++_i_); } @@ -111,7 +114,8 @@ namespace test { // ----------------------------------------------------------------------TESTCASES } - bool known (string key) + bool + known (string key) { const_iterator i = find (key); return (i != end()); @@ -130,7 +134,7 @@ namespace test { { if (isnil (name)) throw Invalid ("empty filename passed to MediaAccessFacade."); - + if (!testCases().known(name)) throw Invalid ("unable to use media file \""+name+"\"." "Hint: you're using a test-mock file access, " @@ -145,7 +149,7 @@ namespace test { { Response const& res (*reinterpret_cast (&h)); - if (res.channels.size() <= chanNo) + if (res.channels.size() <= chanNo) return NULLResponse; else return res.channels[chanNo]; diff --git a/tests/core/backend/media-access-mock.hpp b/tests/core/backend/media-access-mock.hpp index f44e9060e..4c69d8fb0 100644 --- a/tests/core/backend/media-access-mock.hpp +++ b/tests/core/backend/media-access-mock.hpp @@ -45,13 +45,11 @@ namespace test { * Mock implementation of the MediaAccessFacade. * Provides preconfigured responses for some Test-Filenames. */ - class MediaAccessMock : public MediaAccessFacade + class MediaAccessMock + : public MediaAccessFacade { - MediaDesc& queryFile (string const& name) const; - ChanDesc queryChannel (MediaDesc&, uint chanNo) const; - - public: - typedef MediaAccessFacade ServiceInterface; + MediaDesc& queryFile (string const& name) const override; + ChanDesc queryChannel (MediaDesc&, uint chanNo) const override; }; diff --git a/tests/core/proc/asset/create-asset-test.cpp b/tests/core/proc/asset/create-asset-test.cpp index 100530366..c81140b08 100644 --- a/tests/core/proc/asset/create-asset-test.cpp +++ b/tests/core/proc/asset/create-asset-test.cpp @@ -35,9 +35,8 @@ #include "proc/asset/asset-diagnostics.hpp" #include "backend/media-access-mock.hpp" -#include "lib/test/depend-4test.hpp" +#include "lib/depend-inject.hpp" -using lib::test::Depend4Test; using util::isnil; using std::string; @@ -45,6 +44,9 @@ using std::string; namespace proc { namespace asset{ namespace test { + + using MediaAccessMock = lib::DependInject + ::Local; @@ -57,7 +59,7 @@ namespace test { { virtual void run(Arg arg) { - Depend4Test within_this_scope; + MediaAccessMock useMockMedia; createMedia(); factoryVariants(); diff --git a/tests/core/proc/asset/dependent-assets-test.cpp b/tests/core/proc/asset/dependent-assets-test.cpp index 6d5eb8077..5ef0e3beb 100644 --- a/tests/core/proc/asset/dependent-assets-test.cpp +++ b/tests/core/proc/asset/dependent-assets-test.cpp @@ -29,21 +29,22 @@ #include "proc/asset/testasset.hpp" #include "proc/asset/asset-diagnostics.hpp" #include "backend/media-access-mock.hpp" -#include "lib/test/depend-4test.hpp" +#include "lib/depend-inject.hpp" #include "proc/asset/media.hpp" #include "proc/asset/clip.hpp" #include "lib/util.hpp" -using lib::test::Depend4Test; using util::contains; using util::isnil; - namespace proc { namespace asset{ namespace test { + using MediaAccessMock = lib::DependInject + ::Local; + @@ -199,7 +200,7 @@ namespace test { */ void checkRealAssetDependencyRegistration () { - Depend4Test within_this_scope; + MediaAccessMock useMockMedia; // -----Media and Clip-------------------------------- typedef lib::P PM; diff --git a/tests/core/proc/asset/entry-id-test.cpp b/tests/core/proc/asset/entry-id-test.cpp index 3c0734c0f..458e3602c 100644 --- a/tests/core/proc/asset/entry-id-test.cpp +++ b/tests/core/proc/asset/entry-id-test.cpp @@ -54,7 +54,7 @@ namespace lib { namespace idi { namespace test{ - using lumiera::error::LUMIERA_ERROR_WRONG_TYPE; + using lumiera::error::LERR_(WRONG_TYPE); namespace { // Test definitions... diff --git a/tests/core/proc/asset/identity-of-assets-test.cpp b/tests/core/proc/asset/identity-of-assets-test.cpp index 3ba2119a1..ff2e3c40d 100644 --- a/tests/core/proc/asset/identity-of-assets-test.cpp +++ b/tests/core/proc/asset/identity-of-assets-test.cpp @@ -35,9 +35,8 @@ #include "proc/asset/asset-diagnostics.hpp" #include "backend/media-access-mock.hpp" -#include "lib/test/depend-4test.hpp" +#include "lib/depend-inject.hpp" -using lib::test::Depend4Test; using util::isnil; using std::string; @@ -46,6 +45,9 @@ namespace proc { namespace asset{ namespace test { + using MediaAccessMock = lib::DependInject + ::Local; + @@ -76,7 +78,7 @@ namespace test { */ void createDuplicate() { - Depend4Test within_this_scope; + MediaAccessMock useMockMedia; PM mm1 = asset::Media::create ("test-1.mov", VIDEO); diff --git a/tests/core/proc/asset/make-clip-test.cpp b/tests/core/proc/asset/make-clip-test.cpp index d298c39d4..16813e70a 100644 --- a/tests/core/proc/asset/make-clip-test.cpp +++ b/tests/core/proc/asset/make-clip-test.cpp @@ -35,9 +35,8 @@ #include "proc/mobject/session/clip.hpp" #include "proc/asset/asset-diagnostics.hpp" #include "backend/media-access-mock.hpp" -#include "lib/test/depend-4test.hpp" +#include "lib/depend-inject.hpp" -using lib::test::Depend4Test; using util::contains; using util::isnil; using std::string; @@ -48,6 +47,9 @@ namespace proc { namespace asset{ namespace test { + using MediaAccessMock = lib::DependInject + ::Local; + @@ -63,7 +65,7 @@ namespace test { virtual void run (Arg) { - Depend4Test within_this_scope; + MediaAccessMock useMockMedia; PM mm = asset::Media::create("test-1", VIDEO); diff --git a/tests/core/proc/asset/ordering-of-assets-test.cpp b/tests/core/proc/asset/ordering-of-assets-test.cpp index 22677093c..a24fdf05c 100644 --- a/tests/core/proc/asset/ordering-of-assets-test.cpp +++ b/tests/core/proc/asset/ordering-of-assets-test.cpp @@ -34,9 +34,8 @@ #include "proc/asset/asset-diagnostics.hpp" #include "backend/media-access-mock.hpp" -#include "lib/test/depend-4test.hpp" +#include "lib/depend-inject.hpp" -using lib::test::Depend4Test; using util::isnil; using std::string; @@ -45,6 +44,9 @@ namespace proc { namespace asset{ namespace test { + using MediaAccessMock = lib::DependInject + ::Local; + @@ -65,7 +67,7 @@ namespace test { { virtual void run(Arg) { - Depend4Test within_this_scope; + MediaAccessMock useMockMedia; Asset::Ident key1("test-1", Category(AUDIO), "ichthyo", 5); diff --git a/tests/core/proc/control/command-instance-manager-test.cpp b/tests/core/proc/control/command-instance-manager-test.cpp index c68c41550..c166c71c6 100644 --- a/tests/core/proc/control/command-instance-manager-test.cpp +++ b/tests/core/proc/control/command-instance-manager-test.cpp @@ -50,7 +50,7 @@ namespace test { using std::move; using std::rand; - using lumiera::error::LUMIERA_ERROR_LIFECYCLE; + using lumiera::error::LERR_(LIFECYCLE); diff --git a/tests/core/proc/control/command-use2-test.cpp b/tests/core/proc/control/command-use2-test.cpp index b2b07edf1..8c6b490ad 100644 --- a/tests/core/proc/control/command-use2-test.cpp +++ b/tests/core/proc/control/command-use2-test.cpp @@ -62,7 +62,7 @@ namespace test { using util::contains; - using lumiera::error::LUMIERA_ERROR_EXTERNAL; + using lumiera::error::LERR_(EXTERNAL); /** diagnostics: checks if the given value has been written diff --git a/tests/core/proc/engine/buffer-metadata-key-test.cpp b/tests/core/proc/engine/buffer-metadata-key-test.cpp index 69479057c..6aaa06bb8 100644 --- a/tests/core/proc/engine/buffer-metadata-key-test.cpp +++ b/tests/core/proc/engine/buffer-metadata-key-test.cpp @@ -44,8 +44,8 @@ namespace metadata{ namespace test { - using lumiera::error::LUMIERA_ERROR_INVALID; - using lumiera::error::LUMIERA_ERROR_LIFECYCLE; + using lumiera::error::LERR_(INVALID); + using lumiera::error::LERR_(LIFECYCLE); namespace { // Test fixture diff --git a/tests/core/proc/engine/buffer-metadata-test.cpp b/tests/core/proc/engine/buffer-metadata-test.cpp index 3888358a5..3b72951dc 100644 --- a/tests/core/proc/engine/buffer-metadata-test.cpp +++ b/tests/core/proc/engine/buffer-metadata-test.cpp @@ -47,9 +47,9 @@ namespace proc { namespace engine{ namespace test { - using lumiera::error::LUMIERA_ERROR_FATAL; - using lumiera::error::LUMIERA_ERROR_INVALID; - using lumiera::error::LUMIERA_ERROR_LIFECYCLE; + using lumiera::error::LERR_(FATAL); + using lumiera::error::LERR_(INVALID); + using lumiera::error::LERR_(LIFECYCLE); namespace { // Test fixture diff --git a/tests/core/proc/engine/buffer-provider-protocol-test.cpp b/tests/core/proc/engine/buffer-provider-protocol-test.cpp index 0c269aba5..66cb1313c 100644 --- a/tests/core/proc/engine/buffer-provider-protocol-test.cpp +++ b/tests/core/proc/engine/buffer-provider-protocol-test.cpp @@ -46,8 +46,8 @@ namespace test { using lib::test::Dummy; using proc::engine::BuffHandle; - using error::LUMIERA_ERROR_LOGIC; - using error::LUMIERA_ERROR_LIFECYCLE; + using error::LERR_(LOGIC); + using error::LERR_(LIFECYCLE); namespace { // Test fixture diff --git a/tests/core/proc/mobject/builder/builder-tool-test.cpp b/tests/core/proc/mobject/builder/builder-tool-test.cpp index ded2de8dd..1dc26415e 100644 --- a/tests/core/proc/mobject/builder/builder-tool-test.cpp +++ b/tests/core/proc/mobject/builder/builder-tool-test.cpp @@ -34,13 +34,12 @@ #include "proc/mobject/session/clip.hpp" #include "proc/mobject/test-dummy-mobject.hpp" #include "backend/media-access-mock.hpp" -#include "lib/test/depend-4test.hpp" +#include "lib/depend-inject.hpp" #include "lib/format-cout.hpp" #include "lib/util.hpp" using util::cStr; using std::string; -using std::cout; @@ -51,8 +50,10 @@ namespace test { using session::Clip; using session::AbstractMO; - using lib::test::Depend4Test; using namespace mobject::test; + + using MediaAccessMock = lib::DependInject + ::Local; @@ -116,7 +117,7 @@ namespace test { virtual void run(Arg) { - Depend4Test within_this_scope; + MediaAccessMock useMockMedia; TestTool t1; diff --git a/tests/core/proc/mobject/mobject-interface-test.cpp b/tests/core/proc/mobject/mobject-interface-test.cpp index 698db1e7f..62165e3ab 100644 --- a/tests/core/proc/mobject/mobject-interface-test.cpp +++ b/tests/core/proc/mobject/mobject-interface-test.cpp @@ -40,7 +40,7 @@ //#include "proc/mobject/explicitplacement.hpp" #include "proc/mobject/test-dummy-mobject.hpp" #include "backend/media-access-mock.hpp" -#include "lib/test/depend-4test.hpp" +#include "lib/depend-inject.hpp" #include "lib/time/timevalue.hpp" #include "lib/format-cout.hpp" @@ -55,7 +55,6 @@ namespace test { // using lib::test::showSizeof; using lib::Symbol; - using lib::test::Depend4Test; using lib::time::Duration; using lib::time::FSecs; using lib::time::Time; @@ -64,6 +63,9 @@ namespace test { // using session::Clip; // using session::PMedia; + using MediaAccessMock = lib::DependInject + ::Local; + using namespace mobject::test; typedef TestPlacement PDummy; @@ -85,7 +87,7 @@ namespace test { virtual void run (Arg) { - Depend4Test within_this_scope; + MediaAccessMock useMockMedia; PMO testClip1 = asset::Media::create("test-1", asset::VIDEO)->createClip(); diff --git a/tests/core/proc/mobject/mobject-ref-test.cpp b/tests/core/proc/mobject/mobject-ref-test.cpp index 364d01e8e..5bd99dcb1 100644 --- a/tests/core/proc/mobject/mobject-ref-test.cpp +++ b/tests/core/proc/mobject/mobject-ref-test.cpp @@ -37,14 +37,13 @@ #include "proc/mobject/explicitplacement.hpp" #include "proc/mobject/test-dummy-mobject.hpp" #include "backend/media-access-mock.hpp" -#include "lib/test/depend-4test.hpp" +#include "lib/depend-inject.hpp" #include "lib/test/test-helper.hpp" #include "lib/time/timevalue.hpp" #include "lib/format-cout.hpp" #include "lib/util.hpp" -using lib::test::Depend4Test; using lib::test::showSizeof; using lib::time::Duration; using lib::time::FSecs; @@ -84,6 +83,9 @@ namespace test { using session::SessionServiceMockIndex; using session::PPIdx; + using MediaAccessMock = lib::DependInject + ::Local; + /***********************************************************************//** @@ -107,7 +109,7 @@ namespace test { virtual void run (Arg) { - Depend4Test within_this_scope; + MediaAccessMock useMockMedia; // create data simulating a "Session" diff --git a/tests/core/proc/mobject/output-mapping-test.cpp b/tests/core/proc/mobject/output-mapping-test.cpp index 0b15714f1..8617e9a78 100644 --- a/tests/core/proc/mobject/output-mapping-test.cpp +++ b/tests/core/proc/mobject/output-mapping-test.cpp @@ -88,7 +88,7 @@ namespace test { typedef OutputMapping Mapping; virtual void - run (Arg) + run (Arg) { map_and_retrieve(); instance_copy(); diff --git a/tests/core/proc/mobject/placement-hierarchy-test.cpp b/tests/core/proc/mobject/placement-hierarchy-test.cpp index a04d266a9..1c83f1cb3 100644 --- a/tests/core/proc/mobject/placement-hierarchy-test.cpp +++ b/tests/core/proc/mobject/placement-hierarchy-test.cpp @@ -27,7 +27,7 @@ #include "lib/test/run.hpp" #include "lib/test/test-helper.hpp" -#include "lib/test/depend-4test.hpp" +#include "lib/depend-inject.hpp" #include "backend/media-access-mock.hpp" #include "proc/mobject/test-dummy-mobject.hpp" #include "proc/mobject/session/clip.hpp" @@ -39,7 +39,6 @@ using lib::HashIndexed; -using lib::test::Depend4Test; namespace proc { @@ -52,6 +51,9 @@ namespace test { using namespace mobject::test; using lumiera::error::LUMIERA_ERROR_ASSERTION; + using MediaAccessMock = lib::DependInject + ::Local; + /***********************************************************************************//** * @test creating placements specifically typed, forming an hierarchy of placement types @@ -65,9 +67,9 @@ namespace test { { virtual void - run (Arg) + run (Arg) { - Depend4Test within_this_scope; + MediaAccessMock useMockMedia; typedef Placement PMObj; @@ -149,9 +151,9 @@ namespace test { CHECK (2 == pClip.use_count()); CHECK (2 == pSub1.use_count()); +#if false ///////////////////////////////////////////////////////////////////////////////TODO: find a way to configure NoBug to throw in case of assertion ///////////////////////////////////////////////////////////////////////////////TODO: configure NoBug specifically for the testsuite -#ifdef false VERIFY_ERROR (ASSERTION, hijacked->specialAPI() ); #endif diff --git a/tests/core/proc/mobject/session/query-focus-stack-test.cpp b/tests/core/proc/mobject/session/query-focus-stack-test.cpp index a98280ffa..a45b855a1 100644 --- a/tests/core/proc/mobject/session/query-focus-stack-test.cpp +++ b/tests/core/proc/mobject/session/query-focus-stack-test.cpp @@ -42,7 +42,7 @@ namespace test { using util::isnil; using util::isSameObject; - using lumiera::error::LUMIERA_ERROR_INVALID; + using lumiera::error::LERR_(INVALID); /***********************************************************************//** diff --git a/tests/core/proc/mobject/session/scope-path-test.cpp b/tests/core/proc/mobject/session/scope-path-test.cpp index 18e760999..4487aa679 100644 --- a/tests/core/proc/mobject/session/scope-path-test.cpp +++ b/tests/core/proc/mobject/session/scope-path-test.cpp @@ -46,8 +46,8 @@ namespace test { using util::isnil; using util::isSameObject; - using lumiera::error::LUMIERA_ERROR_LOGIC; - using lumiera::error::LUMIERA_ERROR_INVALID; + using lumiera::error::LERR_(LOGIC); + using lumiera::error::LERR_(INVALID); diff --git a/tests/core/proc/mobject/session/testclip.cpp b/tests/core/proc/mobject/session/testclip.cpp index 9edd7df3f..957adb992 100644 --- a/tests/core/proc/mobject/session/testclip.cpp +++ b/tests/core/proc/mobject/session/testclip.cpp @@ -28,7 +28,7 @@ #include "proc/mobject/session/testclip.hpp" #include "backend/media-access-facade.hpp" #include "backend/media-access-mock.hpp" -#include "lib/test/depend-4test.hpp" +#include "lib/depend-inject.hpp" #include "proc/asset/media.hpp" #include "proc/asset/clip.hpp" #include "lib/depend.hpp" @@ -41,18 +41,20 @@ namespace test { using lib::time::Time; using lib::time::Duration; - typedef shared_ptr PM; - typedef backend_interface::MediaAccessFacade MAF; - using backend::test::MediaAccessMock; using asset::VIDEO; + using PM = shared_ptr; + using MAF = backend_interface::MediaAccessFacade; + + using MediaAccessMock = lib::DependInject::Local; + asset::Media & createTestMedia () { // install Mock-Interface to Lumiera backend - lib::test::Depend4Test withinThisScope; + MediaAccessMock useMockMedia; return *asset::Media::create("test-2", VIDEO); // query magic filename } diff --git a/tests/core/proc/play/diagnostic-output-slot.hpp b/tests/core/proc/play/diagnostic-output-slot.hpp index 4837a9359..ba1cdfd1f 100644 --- a/tests/core/proc/play/diagnostic-output-slot.hpp +++ b/tests/core/proc/play/diagnostic-output-slot.hpp @@ -32,6 +32,7 @@ #include "lib/error.hpp" +#include "lib/nocopy.hpp" #include "include/logging.h" #include "proc/play/output-slot.hpp" #include "proc/play/output-slot-connection.hpp" @@ -47,7 +48,6 @@ #include "proc/engine/testframe.hpp" //#include "lib/sync.hpp" -#include //#include //#include #include @@ -97,7 +97,7 @@ namespace play { */ class TrackingInMemoryBlockSequence : public OutputSlot::Connection - , boost::noncopyable + , util::NonCopyable { typedef std::unordered_set FrameTrackingInfo; @@ -366,21 +366,21 @@ namespace play { */ class OutputFramesLog : public lib::IterSource - , boost::noncopyable + , util::NonCopyable { TrackingInMemoryBlockSequence const& outSeq_; uint currentFrame_; - virtual Pos - firstResult () + virtual Pos //////////////////////////////////////////////TICKET #1125 : this API should use three control functions, similar to IterStateWrapper + firstResult () override { REQUIRE (0 == currentFrame_); return outSeq_.accessEmittedFrame (currentFrame_); } virtual void - nextResult (Pos& pos) + nextResult (Pos& pos) override { ++currentFrame_; pos = outSeq_.accessEmittedFrame(currentFrame_); diff --git a/tests/core/proc/play/dummy-play-connection-test.cpp b/tests/core/proc/play/dummy-play-connection-test.cpp index 1d4ee4685..e6a2ae46f 100644 --- a/tests/core/proc/play/dummy-play-connection-test.cpp +++ b/tests/core/proc/play/dummy-play-connection-test.cpp @@ -30,7 +30,7 @@ #include "proc/play/dummy-play-connection.hpp" //#include "proc/engine/buffhandle.hpp" //#include "proc/engine/testframe.hpp" -#include "include/play-facade.h" +#include "include/play-facade.hpp" #include "lib/time/control.hpp" diff --git a/tests/gui/abstract-tangible-test.cpp b/tests/gui/abstract-tangible-test.cpp index 4e0494ce3..fdd077f52 100644 --- a/tests/gui/abstract-tangible-test.cpp +++ b/tests/gui/abstract-tangible-test.cpp @@ -84,7 +84,7 @@ namespace gui { namespace model{ namespace test { - using lumiera::error::LUMIERA_ERROR_WRONG_TYPE; + using lumiera::error::LERR_(WRONG_TYPE); using ID = lib::idi::BareEntryID const&; namespace { // test fixture... diff --git a/tests/gui/bus-term-test.cpp b/tests/gui/bus-term-test.cpp index e94b6fbcb..ed15d4f82 100644 --- a/tests/gui/bus-term-test.cpp +++ b/tests/gui/bus-term-test.cpp @@ -598,7 +598,7 @@ namespace test { * a representation of the "changes" in diff format */ struct BorgGenerator - : boost::noncopyable + : util::NonCopyable , TreeDiffLanguage , DiffSource { diff --git a/tests/gui/gen-node-location-query.hpp b/tests/gui/gen-node-location-query.hpp new file mode 100644 index 000000000..0c2c5c2fd --- /dev/null +++ b/tests/gui/gen-node-location-query.hpp @@ -0,0 +1,307 @@ +/* + GEN-NODE-LOCATION-QUERY.hpp - pose UI-coordinate location queries against a GenNode structure + + Copyright (C) Lumiera.org + 2017, Hermann Vosseler + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + + +/** @file gen-node-location-query.hpp + ** Implementation of the gui::interact::LocationQuery interface to work on a GenNode tree. + ** The interface allows to pose queries against a concrete structure to verify and reshape some + ** [UI Coordinate specification](\ref UICoord); basically it offers methods to navigate within a + ** tree-like structure. While in the actual implementation, such a query interface would be backed + ** by navigating real UI structures, the implementation given here instead uses a generic tree structure + ** given as `Record`. + ** + ** # Representing UI structure as GenNode tree + ** + ** While basically the interface LocationQuery abstracts and reduces the structure of an UI into + ** just some hierarchically arranged and nested IDs, we should note some specific twists how a GenNode + ** tree is used here to represent the structure elements as defined through [UI coordinates](\ref UICoord): + ** - we use the special _type_ attribute to represent the _perspective_ within each window; + ** deliberately, we'll use this twisted structure here to highlight the fact that the + ** backing structure need not be homogeneous; rather, it may require explicit branching + ** - we use the _attributes_ within the GenNode "object" representation, since these are _named_ + ** nested elements, and the whole notion of an UI coordinate path is based on named child components + ** - relying upon the [object builder notation](\ref Record::Mutator), it is possible to define a whole + ** structure as nested inline tree; named nested elements can be added with the `set(key, val)` + ** builder function, and for each nested scope, we start a new nested builder with `MakeRec()`. + ** - since GenNodeLocationQuery is conceived for writing test and verification code, there is a + ** special convention to set the `currentWindow` to be the last one in list -- in a real UI + ** this would not of course not be a configurable property of the LocationQuery, and rather + ** just reflect the transient window state and return the currently activated window + ** + ** @todo WIP 10/2017 started in the effort of shaping the LoactionQuery interface, and used + ** to support writing unit tests, to verify the UiCoordResolver. It remains to be seen + ** if this implementation can be used beyond this limited purpose + ** + ** @see UICoordResolver_test + ** @see IterTreeExplorer_test + ** @see ui-coord-resolver.hpp + ** @see navigator.hpp + */ + + +#ifndef GUI_INTERACT_GEN_NODE_LOCATION_QUERY_H +#define GUI_INTERACT_GEN_NODE_LOCATION_QUERY_H + +#include "lib/error.hpp" +#include "lib/symbol.hpp" +#include "gui/interact/ui-coord-resolver.hpp" +#include "lib/diff/gen-node.hpp" +#include "lib/format-string.hpp" +#include "lib/iter-tree-explorer.hpp" +#include "lib/iter-source.hpp" +#include "lib/itertools.hpp" +#include "lib/util.hpp" + +#include +#include + + +namespace gui { +namespace interact { + + namespace error = lumiera::error; + + using lib::Symbol; + using lib::Literal; + using lib::diff::Rec; + using util::isnil; + using util::_Fmt; + using std::forward; + using std::string; + + + + + /** + * Test/Diagnostics: implementation of the LocationQuery-API + * based on a abstract topological structure given as Record ("GenNode tree"). + * @remark intended for verifying path resolution and navigation through unit tests + */ + class GenNodeLocationQuery + : public LocationQuery + { + Rec tree_; + + public: + template + GenNodeLocationQuery(REC&& backingStructure) + : tree_(std::forward(backingStructure)) + { } + + + /* === LocationQuery interface === */ + + /** resolve Anchor against GenNode tree */ + virtual Literal + determineAnchor (UICoord const& path) override + { + if (isnil(tree_) or not path.isPresent(UIC_WINDOW)) + return Symbol::BOTTOM; + if (UIC_FIRST_WINDOW == path.getWindow()) + return getFirstWindow(); + if (UIC_CURRENT_WINDOW == path.getWindow()) + return getCurrentWindow(); + if (not tree_.hasAttribute(string{path.getWindow()})) /////////////////////////////////////TICKET #1113 : unnecessary repackaging of a Literal into string when GenNode ID is based on Literal + return Symbol::BOTTOM; + return path.getWindow(); + } + + + /** evaluate to what extent a UIcoord spec matches the structure given as GenNode tree */ + virtual size_t + determineCoverage (UICoord const& path) override + { + size_t depth = 0; + drillDown (tree_, path, path.size(), depth); + return depth; + } + + /** get the sequence of child IDs at a designated position in the backing GenNode tree */ + virtual ChildIter + getChildren (UICoord const& path, size_t pos) override + { + size_t depth = 0; + Rec const& node = drillDown (tree_, path, pos, depth); + if (depth != pos) + throw error::State(_Fmt{"unable to drill down to depth %d: " + "element %s at pos %d in path %s is in " + "contradiction to actual UI structure"} + % pos + % (depth(); + } + + + + + /* ==== iterate over siblings with the ability to expand one node's children ==== */ + + /** @return a heap allocated object attached at "current tree position" while + * exposing the names of all child nodes [through iteration](\ref lib::IterSource) + */ + static TreeStructureNavigator* + childNavigator (Rec const& node, size_t depth) + { + //////////////////////////////////////////////////////////////////////////TICKET #1113 : capturing the string into the global Symbol table becomes obsolete, once GenNode exposes Literal as ID + auto internedString = [](string const& id) -> Literal + { + return Symbol{id}; + }; + return depth==UIC_PERSP? buildNavigator (node, depth, singleValIterator (internedString (node.getType()))) + : buildNavigator (node, depth, transformIterator (node.keys(), internedString)); + } + + + /** + * Helper to navigate a tree topology represented as GenNode tree. + * Basically this is a lib::IterSource to encapsulate a sequence of sibling nodes. + * A "current element" representation is layered on top to allow to expand one level deeper + * on demand. This "child expansion" is triggered by invoking the `expandChildren()` function + * on the iterator front-end provided as LocationQuery::ChildIter + */ + template + class GenNodeNavigator + : public PAR + { + Rec const& pos_; + size_t depth_; + + virtual TreeStructureNavigator* + expandChildren() const override + { + return childNavigator (descendInto (pos_,depth_, currentChild_), depth_+1); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1125 : work around the misaligned IterSource design + // The design of IterSource attempts to be too clever, and we have to pay for it now... + // If IterSource would just work like a StateCore and expose the "current element" via API call, + // then we'd be able to retrieve the name of the current child node. Unfortunately it doesn't + // and thus we rig a "wire tap" here and capture the node names whenever an iteration happens. + // + Literal currentChild_ = Symbol::BOTTOM; + + using Pos = typename PAR::Pos; + + virtual Pos + firstResult () override + { + Pos pos = PAR::firstResult(); + if (pos) currentChild_ = *pos; + return pos; + } + + virtual void + nextResult(Pos& pos) override + { + PAR::nextResult (pos); + if (pos) currentChild_ = *pos; + } + ///////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1125 : work around the misaligned IterSource design + + + public: + template + GenNodeNavigator(Rec const& node, size_t depth, IT&& rawChildIter) + : PAR{forward (rawChildIter)} + , pos_{node} + , depth_{depth} + { } + }; + + + /** type rebinding helper to pick up the concrete child iterator type `IT` */ + template + static TreeStructureNavigator* + buildNavigator (Rec const& node, size_t depth, IT && rawIterator) + { + return new GenNodeNavigator< + lib::WrappedLumieraIter> {node, depth, forward (rawIterator)}; + } + }; + + + +}}// namespace gui::interact +#endif /*GUI_INTERACT_GEN_NODE_LOCATION_QUERY_H*/ diff --git a/tests/gui/interact/cmd-context-test.cpp b/tests/gui/interact/cmd-context-test.cpp index 2271e1ba9..98f31924a 100644 --- a/tests/gui/interact/cmd-context-test.cpp +++ b/tests/gui/interact/cmd-context-test.cpp @@ -22,6 +22,8 @@ /** @file cmd-context-test.cpp ** unit test \ref CmdContext_test + ** + ** @todo created 3/2017 as a marker for planned concepts, postponed for now... */ diff --git a/tests/gui/interact/ui-coord-resolver-test.cpp b/tests/gui/interact/ui-coord-resolver-test.cpp new file mode 100644 index 000000000..3f3bc6d66 --- /dev/null +++ b/tests/gui/interact/ui-coord-resolver-test.cpp @@ -0,0 +1,755 @@ +/* + UICoordResolver(Test) - resolve UI coordinates against actual topology + + Copyright (C) Lumiera.org + 2017, Hermann Vosseler + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +* *****************************************************/ + +/** @file ui-coord-resolver-test.cpp + ** unit test \ref UICoordResolver_test + */ + + +#include "lib/test/run.hpp" +#include "lib/test/test-helper.hpp" +#include "gui/interact/ui-coord.hpp" +#include "gui/interact/ui-coord-resolver.hpp" +#include "gen-node-location-query.hpp" +#include "lib/diff/gen-node.hpp" +#include "lib/format-util.hpp" +#include "lib/util.hpp" + +#include + + +using std::string; +using lib::diff::MakeRec; +using lib::diff::Rec; +using lib::Symbol; +using util::isnil; +using util::join; + + + +namespace gui { +namespace interact { +namespace test { + + using lumiera::error::LERR_(INVALID); + using lumiera::error::LERR_(STATE); + + + + /******************************************************************************//** + * @test verify query and mutation of UICoord in relation to actual UI topology. + * A UI-Coordinate resolver is a special builder, which is initialised by + * the given coordinate spec, and also attached to a "location query API", + * which allows to investigate the current actual UI structure. The builder + * then exposes query and mutation operations, to determine to what extent + * the coordinate spec is "covered" by the real UI, and to match and expand + * any wildcards in the coordinate spec (pattern). + * + * @see UICoordResolver + * @see navigator.hpp + * @see ViewLocator + * @see UICoord_test + */ + class UICoordResolver_test : public Test + { + + virtual void + run (Arg) + { + verify_simpleUsage(); + verify_backingQuery(); + verify_queryAnchor(); + verify_mutateCoverage(); + verify_mutateCoverPartially(); + verify_mutateAnchor(); + verify_mutateExtend(); + } + + + /** @test introduction to UI coordinate resolution + * - use a backing "real" (dummy) data structure to resolve against + * - establish a suitable implementation of the LocationQuery interface + * - attach a resolver + * - have fun + */ + void + verify_simpleUsage() + { + // a Test dummy placeholder for the real UI structure + Rec dummyUiStructure = MakeRec() + .set("window-1" + , MakeRec() + .type("perspective-A") + ) + .set("window-2" + , MakeRec() + .type("perspective-B") + .set("panelX", MakeRec()) + .set("panelXX", MakeRec()) + ); + + // helper to answer "location queries" backed by this structure + GenNodeLocationQuery locationQuery{dummyUiStructure}; + + UICoord uic{"window-2","*","panelX","someView"}; + UICoordResolver resolver{uic, locationQuery}; + + CHECK (not resolver.isCovered()); + CHECK ( resolver.canCover()); + + UICoord uic2 = resolver.cover() + .extend("otherView"); + + CHECK ("UI:window-2[perspective-B]-panelX.otherView" == string(uic2)); + } + + + /** @test verify the command-and-query interface backing the resolver. + * This test actually uses a dummy implementation of the interface, which, + * instead of navigating an actual UI topology, just uses a `Record` + * (a "GenNode tree") to emulate the hierarchical structure of UI components. + * @remarks note some twists how the GenNode tree is used here to represent + * an imaginary UI structure: + * - we use the special _type_ attribute to represent the _perspective_ + * within each window; deliberately, we'll use this twisted structure + * here to highlight the fact that the backing structure need not be + * homogeneous; rather, it may require explicit branching + * - we use the _attributes_ within the GenNode "object" representation, + * since these are named nested elements, and the whole notion of an + * UI coordinate path is based on named child components + * - we use the _object builder_ helper to define the whole structure + * as nested inline tree; named nested elements ("attributes") are + * added with the `set(key, val)` builder function, and for each + * nested scope, we start a new nested builder with `MakeRec()`. + * - there is a special convention _for this test setup solely_ to + * set the `currentWindow` to be the last one in list -- in a real + * UI this would of course not be a configurable property of the + * LocationQuery, but rather just reflect the transient window + * state and return the currently activated window + * @see IterTreeExplorer_test::verify_IterSource() regarding "child exploration"... + */ + void + verify_backingQuery() + { + GenNodeLocationQuery queryAPI{MakeRec() + .set("window-1" + , MakeRec() + .type("perspective-A") + .set("panelX" + , MakeRec() + .set("firstView", MakeRec()) + .set("secondView", MakeRec()) + ) + ) + .set("window-2" + , MakeRec() + .type("perspective-B") + .set("panelY", MakeRec()) + ) + .set("window-3" + , MakeRec() + .type("perspective-C") + .set("panelZ" + , MakeRec() + .set("thirdView", MakeRec()) + ) + .set("panelZZ", MakeRec()) + ) + }; + + // the LocationQuery API works by matching a UICoord spec against the "real" structure + UICoord uic1 = UICoord::window("window-2").persp("perspective-B"); + UICoord uic2 = UICoord::window("windows"); + UICoord uic3 = UICoord::firstWindow().persp("perspective-A").panel("panelX").view("secondView"); + UICoord uic4 = UICoord::currentWindow().persp("perspective-B"); + UICoord uic5 = UICoord::currentWindow().persp("perspective-C").panel("panelZ").view("someOtherView"); + + CHECK ("window-2" == queryAPI.determineAnchor(uic1)); + CHECK (Symbol::BOTTOM == queryAPI.determineAnchor(uic2)); + CHECK ("window-1" == queryAPI.determineAnchor(uic3)); + CHECK ("window-3" == queryAPI.determineAnchor(uic4)); + CHECK ("window-3" == queryAPI.determineAnchor(uic5)); + + CHECK (2 == queryAPI.determineCoverage(uic1)); + CHECK (0 == queryAPI.determineCoverage(uic2)); + CHECK (4 == queryAPI.determineCoverage(uic3)); + CHECK (1 == queryAPI.determineCoverage(uic4)); + CHECK (3 == queryAPI.determineCoverage(uic5)); + + LocationQuery::ChildIter cii = queryAPI.getChildren(uic3, 3); + CHECK (not isnil(cii)); + CHECK ("firstView" == *cii); + ++cii; + CHECK ("secondView" == *cii); + CHECK (not isnil(cii)); + ++cii; + CHECK (isnil(cii)); + + CHECK ("window-1, window-2, window-3" == join (queryAPI.getChildren (uic3, 0))); + CHECK ("perspective-A" == join (queryAPI.getChildren (uic3, 1))); + CHECK ("panelX" == join (queryAPI.getChildren (uic3, 2))); + CHECK ("firstView, secondView" == join (queryAPI.getChildren (uic3, 3))); + CHECK (isnil ( queryAPI.getChildren (uic3, 4))); // "firstView" has no children + + CHECK ("window-1, window-2, window-3" == join (queryAPI.getChildren (uic2, 0))); + VERIFY_ERROR (STATE, queryAPI.getChildren (uic2, 1) ); // "windows" at pos==0 is not covered by real UI + + CHECK ("window-1, window-2, window-3" == join (queryAPI.getChildren (uic5, 0))); + CHECK ("perspective-C" == join (queryAPI.getChildren (uic5, 1))); + CHECK ("panelZ, panelZZ" == join (queryAPI.getChildren (uic5, 2))); + CHECK ("thirdView" == join (queryAPI.getChildren (uic5, 3))); + VERIFY_ERROR (STATE, queryAPI.getChildren (uic5, 4) ); // "someOtherView" at level 4 does not exist + + + // verify "child exploration" via iterator interface + cii = queryAPI.getChildren (uic3, 0); // enter at root level... + CHECK ("window-1" == *cii); // first child of root to appear is "window-1" + CHECK (0 == cii.depth()); // (note depth just happens to coincide with absolute tree depth here) + cii.expandChildren(); // drill down into current element's children + CHECK (1 == cii.depth()); + CHECK ("perspective-A" == *cii); // which is just one, the perspective + cii.expandChildren(); // drill down into the (formal, logical) children of "perspective-A" + CHECK (2 == cii.depth()); + CHECK ("panelX" == *cii); // ..and find the "panelX" at level 2 + cii.expandChildren(); // drill down one level further + CHECK (3 == cii.depth()); + CHECK ("firstView" == *cii); // and then just continue iteration, which first explores that scope... + CHECK ("firstView, secondView, window-2, window-3" == join (cii)); // ...followed by returning to the enclosing scopes, finally top level. + } + + + /** @test query anchorage of given UI coordinates. + * - an anchored UI coordinate spec explicitly rooted within a top level window. + * - an explicit UI coordinate spec impossible to anchor within current UI tree + * - a UI coordinate spec with dynamic reference to first/current window + * - an incomplete spec, which needs to be solved (pattern matched) to determine anchor. + */ + void + verify_queryAnchor() + { + GenNodeLocationQuery tree{MakeRec() + .set("window-1" + , MakeRec() + .type("perspective-A") + ) + .set("window-2" + , MakeRec() + .type("perspective-B") + .set("panelX" + , MakeRec() + .set("someView", MakeRec()) + ) + ) + }; + UICoord uic1 = UICoord::window("window-1").persp("perspective-A"); + UICoord uic2 = UICoord::window("windows"); + UICoord uic3 = UICoord::firstWindow(); + UICoord uic4 = UICoord::currentWindow().persp("perspective-B"); + UICoord uic5 = UICoord::currentWindow().panel("panelY"); + UICoord uic6 = UICoord().view("someView"); + + UICoordResolver r1{uic1, tree}; + UICoordResolver r2{uic2, tree}; + UICoordResolver r3{uic3, tree}; + UICoordResolver r4{uic4, tree}; + UICoordResolver r5{uic5, tree}; + UICoordResolver r6{uic6, tree}; + + CHECK ( r1.isAnchored()); + CHECK (not r2.isAnchored()); + CHECK ( r3.isAnchored()); + CHECK ( r4.isAnchored()); + CHECK ( r5.isAnchored()); + CHECK (not r6.isAnchored()); + + CHECK ( r1.canAnchor()); + CHECK (not r2.canAnchor()); + CHECK ( r3.canAnchor()); + CHECK ( r4.canAnchor()); + CHECK ( r5.canAnchor()); + CHECK ( r6.canAnchor()); + } + + + + /** @test path matching algorithm to resolve UI coordinates with wildcards against the current UI structure tree. + * Since an UI coordinate path with gaps and wildcards could match anywhere, even several times, we need to perform + * an exhaustive search with backtracking over the whole tree. By convention, we use the first maximal solution, + * which can be just a partial solution, leaving an additional uncovered trailing part of the UI coordinate spec. + * Whenever a coordinate spec is _not explicit,_ has wildcards or a leading gap, we need to perform the full + * matching algorithm, even to just answer the question if coverage _is possible_. The result, i.e. the computed + * coverage, is cached internally, and can be used to _mutate_ the UI coordinate spec to match that coverage. + * + * This test verifies various corner cases; especially there is a rule to prevent a partial match based + * on wildcards solely, rather we require at least one explicit match to qualify as partial solution. + * - (1) trivial cases not requiring a tree search + * ** total coverage + * ** partial coverage, leaving an uncovered suffix + * - (2) expand dynamic anchor specifiers + * ** with following content + * ** anchor spec alone + * - (3) wildcard interpolation + * ** interpolate a single gap + * ** interpolate several gaps + * ** interpolate anchor and consecutive wildcards + * ** discriminate by anchor and fill additional gap + * - (4) failure detection + * ** trailing wildcards are stripped and ignored + * ** reject gap beyond existing real UI tree + * ** reject gap ending at perimeter of real UI tree + * ** reject interpolated gap on immediately following mismatch + * ** reject mismatch immediately behind second gap + * ** mismatch of tree level + * ** contradiction to anchorage + * - (5) selection between several possible solutions + * ** the length of the covered trailing suffix decides + * ** when two solutions are equivalent, pick the fist one + * ** best solution will be picked, irrespective of discovery order + */ + void + verify_mutateCoverage() + { + GenNodeLocationQuery tree{MakeRec() + .set("window-1" + , MakeRec() + .type("persp-A") + .set("panelX" + , MakeRec() + .set("firstView", MakeRec()) + .set("secondView", MakeRec()) + ) + .set("panelZ" + , MakeRec() + .set("thirdView" + , MakeRec() + .set("#1", MakeRec()) + .set("#2", MakeRec()) + .set("tab", MakeRec()) + ) + ) + ) + .set("window-2" + , MakeRec() + .type("persp-B") + .set("panelY", MakeRec()) + ) + .set("window-3" + , MakeRec() + .type("persp-C") + .set("panelZ" + , MakeRec() + .set("thirdView" + , MakeRec() + .set("tab" + , MakeRec() + .set("sub", MakeRec()) + ) + .set("#1", MakeRec()) + ) + ) + .set("panelZZ", MakeRec()) + ) + }; + + /* === trivial cases === */ + UICoordResolver r11 {UICoord::window("window-1") + .persp("persp-A") + .panel("panelX"), tree}; + CHECK (r11.isCovered()); + CHECK (3 == r11.coverDepth()); + + + UICoordResolver r12 {UICoord::window("window-1") + .persp("persp-A") + .panel("panelX") + .view("thirdView"), tree}; + CHECK (not r12.isCovered()); + CHECK ( r12.isCoveredPartially()); + CHECK (3 ==r12.coverDepth()); + CHECK ("UI:window-1[persp-A]-panelX.thirdView" == string(r12)); + + r12.cover(); + CHECK (r12.isCovered()); + CHECK (r12.isCoveredPartially()); + CHECK (3 ==r12.coverDepth()); + CHECK ("UI:window-1[persp-A]-panelX" == string(r12)); + + + /* === expand anchor === */ + UICoordResolver r21 {UICoord::firstWindow().persp("persp-A"), tree}; + CHECK ("UI:firstWindow[persp-A]" == string(r21)); + r21.cover(); + CHECK ("UI:window-1[persp-A]" == string(r21)); + + /* === expand anchor alone === */ + UICoordResolver r22 {UICoord::currentWindow(), tree}; + CHECK ("UI:window-3" == string(r22.cover())); + + + /* === interpolate a single gap === */ + UICoordResolver r31 {UICoord::window("window-1").view("secondView"), tree}; + CHECK ("UI:window-1[*]-*.secondView" == string(r31)); + CHECK (0 ==r31.coverDepth()); + CHECK (not r31.isCovered()); + CHECK (r31.canCover()); + r31.cover(); + CHECK (r31.isCovered()); + CHECK (4 == r31.coverDepth()); + CHECK ("UI:window-1[persp-A]-panelX.secondView" == string(r31)); + + /* === interpolate several gaps === */ + UICoordResolver r32 {UICoord().view("thirdView").path("sub"), tree}; + CHECK ("UI:window-3[persp-C]-panelZ.thirdView.tab/sub" == string(r32.cover())); + + /* === interpolate anchor and consecutive wildcards === */ + UICoordResolver r33 {UICoord::firstWindow().tab(2), tree}; + CHECK ("UI:window-1[persp-A]-panelZ.thirdView.#2" == string(r33.cover())); + + /* === discriminate by anchor and fill second gap === */ + UICoordResolver r34 {UICoord::currentWindow().panel("panelZ").tab("tab"), tree}; + CHECK ("UI:currentWindow[*]-panelZ.*.tab" == string(r34)); + CHECK ("UI:window-3[persp-C]-panelZ.thirdView.tab" == string(r34.cover())); // Note: rest of the path would also match on window-1, but currentWindow == window-3 + + UICoordResolver r35 {UICoord::currentWindow().persp(UIC_ELIDED).panel("panelZ").tab("tab"), tree}; + CHECK ("UI:currentWindow[.]-panelZ.*.tab" == string(r35)); + CHECK ("UI:window-3[persp-C]-panelZ.thirdView.tab" == string(r35.cover())); // elided (existentially quantified) element interpolated similar to a wildcard + + UICoordResolver r36 {UICoord::currentWindow().panel(UIC_ELIDED).view("nonexisting"), tree}; + CHECK ("UI:currentWindow[*]-..nonexisting" == string(r36)); + CHECK ("UI:window-3[persp-C]-panelZ" == string(r36.cover())); // ...but elided counts as existing element and matches arbitrarily (-> contrast this to r44) + + + /* === trailing wildcards stripped automatically === */ + UICoordResolver r41 {UICoord::window("window-2").append("*/*"), tree}; + CHECK ("UI:window-2" == string(r41)); // Note: trailing wildcards are already discarded by PathArray / UICoord + + r41.extend("*/*"); // if we now attempt to "sneak in" trailing wildcards... + CHECK ("UI:window-2[*]-*" == string(r41)); + CHECK (not r41.canCover()); // ...then the algorithm rejects any solution + CHECK ("UI:window-2" == string(r41.cover())); // Note: but cover() will act on the previous coverage and just strip the extraneous suffix + + /* === reject gap beyond existing real UI tree === */ + UICoordResolver r42 {UICoord::window("window-2").append("*/*/*/some/path"), tree}; + CHECK (not r42.canCover()); + + /* === reject gap ending at real UI tree boundary === */ + UICoordResolver r43 {UICoord::currentWindow().view("firstView").tab("nonexisting"), tree}; + CHECK (not r43.canCover()); + + /* === reject interpolated gap on mismatch right behind === */ + UICoordResolver r44 {UICoord().view("otherView"), tree}; // Note: will be checked on all four existing views, but never matches + CHECK (not r44.canCover()); + + /* === reject mismatch immediately behind second gap === */ + UICoordResolver r45 {UICoord().panel("panelZ").tab(3), tree}; // Note: we have two "panelZ", but none has a tab #3 + CHECK (not r45.canCover()); + + /* === mismatch of tree level === */ + UICoordResolver r46 {UICoord::currentWindow().append("*/*/panelZ/thirdView"), tree}; // Note: one '*' too much, thus 'panelZ' is matched on view level + CHECK (not r46.canCover()); + + /* === impossible to anchor === */ + UICoordResolver r47 {UICoord::firstWindow().tab(3), tree}; + CHECK (not r47.canCover()); + + + /* === the solution with maximum covered depth wins === */ + UICoordResolver r51 {UICoord().tab("tab").path("sub"), tree}; + CHECK ("UI:window-3[persp-C]-panelZ.thirdView.tab/sub" == string(r51.cover())); // the second solution found covers to maximum depth + + /* === when two solutions are equivalent, pick the fist one === */ + UICoordResolver r52 {UICoord().tab("tab"), tree}; + CHECK ("UI:window-1[persp-A]-panelZ.thirdView.tab" == string(r52.cover())); // "UI:window-3[persp-C]-panelZ.thirdView.tab" would match too + + /* === best solution will be picked, irrespective of discovery order === */ + UICoordResolver r531 {UICoord().persp("persp-A").tab(1), tree}; + CHECK ("UI:window-1[persp-A]-panelZ.thirdView.#1" == string(r531.cover())); // best solution discovered as first one + + UICoordResolver r532 {UICoord().view("thirdView").tab("tab"), tree}; + CHECK ("UI:window-1[persp-A]-panelZ.thirdView.tab" == string(r532.cover())); // best solution is 3rd of five possible ones + + UICoordResolver r533 {UICoord().persp("persp-C").tab(1), tree}; + CHECK ("UI:window-3[persp-C]-panelZ.thirdView.#1" == string(r533.cover())); // best solution is found as last one + } + + + + /** @test resolve by matching, but retain an extraneous, uncovered extension. + * This is a variation of the UICoordResolver::cover() operation, which likewise resolves + * any wildcards; but here we tolerate _additional elements below_ the covered part, as long + * as those are explicit. The typical use case is when we're about to create a new UI element + * at a specific existing anchor location within the UI. The extraneous uncovered part then + * describes those extra element yet to be created. + */ + void + verify_mutateCoverPartially() + { + GenNodeLocationQuery tree{MakeRec() + .set("window-2" + , MakeRec() + .type("persp-B") + .set("panelY" + , MakeRec() + .set("someView" + , MakeRec() + .set("#1", MakeRec()) + .set("#2", MakeRec()) + ) + ) + ) + }; + + /* === explicitly given spec partially covered === */ + UICoordResolver r1 {UICoord{"window-2","persp-B","panelY","otherView","tab"}, tree}; + CHECK (3 == r1.coverDepth()); + r1.coverPartially(); + CHECK (not r1.isCovered()); + CHECK (3 == r1.coverDepth()); + CHECK (r1.isCoveredPartially()); // is covered down to the "panelY" + CHECK ("UI:window-2[persp-B]-panelY.otherView.tab" == string(r1)); + r1.cover(); + CHECK (r1.isCovered()); // cover() retains the covered part only + CHECK ("UI:window-2[persp-B]-panelY" == string(r1)); + + /* === fill wildcard gap but retain uncovered extension === */ + UICoordResolver r2 {UICoord::currentWindow().view("someView").tab(3).path("sub"), tree}; + CHECK (0 == r2.coverDepth()); + r2.coverPartially(); + CHECK (not r2.isCovered()); + CHECK (4 == r2.coverDepth()); + CHECK (r2.isCoveredPartially()); + CHECK ("UI:window-2[persp-B]-panelY.someView.#3/sub" == string(r2)); + r2.cover(); + CHECK ("UI:window-2[persp-B]-panelY.someView" == string(r2)); + + /* === reject when gap can not be closed unambiguously === */ + UICoordResolver r3 {UICoord::currentWindow().view("someView").path("sub"), tree}; + CHECK (not r3.canCover()); // NOTE: second gap here, tab info missing + r3.coverPartially(); + CHECK (isnil (r3)); + + /* === reject when some wildcards remain after partial coverage === */ + UICoordResolver r4 {UICoord::currentWindow().tab(3).path("sub"), tree}; + r4.coverPartially(); + CHECK (isnil (r4)); + + /* === existentially quantified (elided) element constitutes partial coverage === */ + UICoordResolver r5 {UICoord::currentWindow().persp(UIC_ELIDED).panel("fantasy").view("fantomas"), tree}; + CHECK ("UI:currentWindow[.]-fantasy.fantomas" == string(r5)); + CHECK (1 == r5.coverDepth()); + r5.coverPartially(); + CHECK (not r5.isCovered()); + CHECK (2 == r5.coverDepth()); // Note side-effect of computing the coverage... + CHECK (r5.isCoveredPartially()); // it is known to be covered including "the" perspective + CHECK ("UI:window-2[persp-B]-fantasy.fantomas" == string(r5)); + r5.cover(); + CHECK ("UI:window-2[persp-B]" == string(r5)); + CHECK (2 == r5.coverDepth()); + } + + + + + /** @test mutate given UI coordinates by anchoring them. + * This operation changes only the window part of the coordinate spec; + * it might use the result of a preceding coverage solution search or even + * trigger such a search, but only to find out about the root window. + * @note some fine points touched here: to anchor a path is something + * different than to cover it; in fact there are cases where we + * can determine the possible anchor point, but are unable to + * cover the path spec beyond that. And, on the other hand, + * there are cases where you _need to compute a coverage_ + * in order to decide upon the anchor point. + */ + void + verify_mutateAnchor() + { + GenNodeLocationQuery tree{MakeRec() + .set("window-1" + , MakeRec() + .type("persp-A") + .set("panelX" + , MakeRec() + .set("firstView", MakeRec()) + .set("secondView", MakeRec()) + ) + ) + .set("window-2" + , MakeRec() + .type("persp-B") + .set("panelY" + , MakeRec() + .set("thirdView" + , MakeRec() + .set("#1", MakeRec()) + .set("#2", MakeRec()) + ) + ) + ) + .set("window-3" + , MakeRec() + .type("persp-C") + .set("panelZ" + , MakeRec() + .set("thirdView", MakeRec()) + ) + ) + }; + + /* === explicitly given window spec remains unchanged === */ + UICoordResolver r1 {UICoord{"window-2","persp-B","panelY"}, tree}; + CHECK (3 == r1.coverDepth()); + r1.anchor(); + CHECK ("UI:window-2[persp-B]-panelY" == string(r1)); + + /* === `firstWindow` meta spec is resolved === */ + UICoordResolver r2 {UICoord::firstWindow().view("blah"), tree}; + CHECK (0 == r2.coverDepth()); + CHECK (r2.isAnchored()); // can obviously be anchored, since there is always a first window + CHECK (not r2.canCover()); // yet this path is impossible to cover in the current UI + CHECK ("UI:firstWindow[*]-*.blah" == string(r2)); + r2.anchor(); + CHECK ("UI:window-1[*]-*.blah" == string(r2)); + CHECK (0 == r2.coverDepth()); + CHECK (not r2.canCover()); + + /* === `currentWindow` meta spec is resolved === */ + UICoordResolver r3 {UICoord::currentWindow().view("thirdView"), tree}; + CHECK (0 == r3.coverDepth()); + CHECK (r3.isAnchored()); + CHECK (not r3.isCovered()); + CHECK (r3.canCover()); + r3.anchor(); + CHECK (not r3.isCovered()); + CHECK (r3.isCoveredPartially()); + CHECK (1 == r3.coverDepth()); // anchoring also picks the second of two possible solutions + CHECK ("UI:window-3[*]-*.thirdView" == string(r3)); // thereby covering the "thirdView" + + /* === coverage solution is calculated on demand === */ + UICoordResolver r4 {UICoord().view("thirdView").append("#2/sub"), tree}; + CHECK ("UI:?.thirdView.#2/sub" == string(r4)); // an incomplete path is not automatically resolved + CHECK (not r4.isAnchored()); + CHECK (0 == r4.coverDepth()); + r4.anchor(); // but if we anchor, we force search for a coverage solution + CHECK (1 == r4.coverDepth()); // which is actually found starting from the second window, + CHECK (r4.isCoveredPartially()); // and kept in the internal cache for future use, + CHECK ("UI:window-2[*]-*.thirdView.#2/sub" == string(r4)); // but not made explicit, since we only requested anchorage + + /* === already calculated coverage solution is used === */ + UICoordResolver r5 {UICoord::currentWindow().view("thirdView"), tree}; + CHECK (not r5.isCovered()); + CHECK (not r5.isCoveredPartially()); + CHECK (0 == r5.coverDepth()); + CHECK (r5.canCover()); // this triggers search for a coverage solution + CHECK (1 == r5.coverDepth()); + CHECK (not r5.isCovered()); + CHECK (r5.isCoveredPartially()); + CHECK ("UI:currentWindow[*]-*.thirdView" == string(r5)); + r5.anchor(); // and this (cached) solution is also used to make anchorage explicit + CHECK ("UI:window-3[*]-*.thirdView" == string(r5)); + CHECK (1 == r5.coverDepth()); + CHECK (not r5.isCovered()); + r5.cover(); // ...now also the coverage solution was made explicit + CHECK (r5.isCovered()); + CHECK (4 == r5.coverDepth()); + CHECK ("UI:window-3[persp-C]-panelZ.thirdView" == string(r5)); + + /* === impossible to cover and can not be anchored === */ + UICoordResolver r6 {UICoord::window("windows").path("to/hell"), tree}; + CHECK (not r6.isAnchored()); + CHECK (not r6.canCover()); + r6.anchor(); + CHECK (not r6.isAnchored()); + CHECK (0 == r6.coverDepth()); + CHECK ("UI:windows[*]-*.*.*/to/hell" == string(r6)); + } + + + /** @test mutate given UI coordinates by uncovered extension. + * Contrary to just appending something to the path (which is a basic path operation + * available on the generic path builder), a _path extension_ is always rooted at the + * end of the actually covered part of the UI coordinates. So extending a path implies + * search for a coverage solution, followed by truncating the path to the covered part. + * There are two flavours of extending a path: + * - extending with a literal specification, which is just appended behind the coverage + * - extending with an incomplete UI coordinate spec, which allows to place the extension + * at a specific depth (e.g. as a view). This is typically what we want in practice. + */ + void + verify_mutateExtend() + { + GenNodeLocationQuery tree{MakeRec() + .set("window-2" + , MakeRec() + .type("persp-B") + .set("panelY" + , MakeRec() + .set("thirdView" + , MakeRec() + .set("#1", MakeRec()) + .set("#2", MakeRec()) + ) + ) + ) + }; + + /* === extend fully covered explicit path === */ + UICoordResolver r1 {UICoord{"window-2","persp-B","panelY"}, tree}; + CHECK ("UI:window-2[persp-B]-panelY" == string(r1)); + CHECK (r1.isCovered()); + r1.extend (UICoord().path("gappy").tab(2)); // can extend with partially defined UI coordinates + CHECK ("UI:window-2[persp-B]-panelY.*.#2/gappy" == string(r1)); // ...the resulting UI path is unresolved, yet can be partially covered + r1.extend ("seamless"); // ...and this partial coverage is used as base for further extension + CHECK ("UI:window-2[persp-B]-panelY.thirdView.#2/seamless" ==string(r1)); + + /* === extend partially covered path === */ + UICoordResolver r2 {UICoord().view("thirdView").append("some/where"), tree}; + CHECK ("UI:?.thirdView.some/where" ==string(r2)); // "thirdView" is covered, "some/where" is not + r2.extend ("no/where"); + CHECK ("UI:window-2[persp-B]-panelY.thirdView.no/where" ==string(r2)); // ...and thus the extension is attached behind "thirdView" + CHECK (r2.isCoveredPartially()); + + /* === impossible extensions rejected === */ // since r2 already specifies a perspective ("persp-B").... + VERIFY_ERROR (INVALID, r2.extend(UICoord().persp("fisheye"))); // ...overwriting with another perspective is rejected as extension + CHECK ("UI:window-2[persp-B]-panelY.thirdView.no/where" ==string(r2)); // ...and the existing state is unaffected from this error + VERIFY_ERROR (INVALID, r2.extend(UICoord().view("alternative"))); // Likewise, extending with a conflicting view spec is rejected + r2.extend(UICoord().tab("nada")); // But a tab is not yet covered and thus acceptable as extension + CHECK ("UI:window-2[persp-B]-panelY.thirdView.nada" ==string(r2)); + r2.extend(UICoord()); + CHECK ("UI:window-2[persp-B]-panelY.thirdView" ==string(r2)); // empty coordinates implicitly attached behind the covered part + + /* === unsolvable: truncate, extend, recalculate coverage === */ + UICoordResolver r3 {UICoord().persp("awesome"), tree}; + CHECK (not r3.canCover()); + CHECK (0 == r3.coverDepth()); + r3.extend (UICoord::currentWindow().tab(1)); // Extension implies covering, which effectively truncates the path + CHECK (1 == r3.coverDepth()); // ...and "currentWindow" can even be covered, thus the coverage increases + CHECK ("UI:currentWindow[*]-*.*.#1" ==string(r3)); // note coverage calculated internally, not made explicit + } + }; + + + /** Register this test class... */ + LAUNCHER (UICoordResolver_test, "unit gui"); + + +}}} // namespace gui::interact::test diff --git a/tests/gui/interact/ui-coord-test.cpp b/tests/gui/interact/ui-coord-test.cpp new file mode 100644 index 000000000..3f8908403 --- /dev/null +++ b/tests/gui/interact/ui-coord-test.cpp @@ -0,0 +1,458 @@ +/* + UICoord(Test) - properties of topological UI coordinate specifications + + Copyright (C) Lumiera.org + 2017, Hermann Vosseler + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +* *****************************************************/ + +/** @file ui-coord-test.cpp + ** unit test \ref UICoord_test + */ + + +#include "lib/test/run.hpp" +#include "lib/test/test-helper.hpp" +#include "gui/interact/ui-coord.hpp" +#include "lib/format-util.hpp" +#include "lib/util.hpp" + +#include + + +using std::string; +using lib::Symbol; +using util::isnil; +using util::join; + + + +namespace gui { +namespace interact { +namespace test { + + using lumiera::error::LERR_(INDEX_BOUNDS); + using lumiera::error::LERR_(LOGIC); + + + + + /******************************************************************************//** + * @test verify the basic properties of topological UI coordinate specifications. + * - created as path-like sequence of \ref Literal components + * - provides a builder API for definition and mutation + * - Normalisation and handling of missing parts + * - access to UI coordinate components + * - string representation + * - comparisons + * - predicates + * + * @see ui-coord.hpp + * @see path-array.hpp + * @see PathArray_test + * @see UICoordResolver_test + * @see ViewSpecDSL_test + */ + class UICoord_test : public Test + { + + virtual void + run (Arg) + { + verify_basics(); + verify_builder(); + verify_stringRepr(); + verify_comparisons(); + verify_localPredicates(); + } + + + void + verify_basics() + { + UICoord undef; + CHECK (isnil (undef)); + + UICoord uic{"Γ","Δ","Θ","Ξ","Σ","Ψ","Φ","Ω"}; + CHECK (not isnil (uic)); + CHECK (8 == uic.size()); + // coordinate sequence is iterable + CHECK ("Γ-Δ-Θ-Ξ-Σ-Ψ-Φ-Ω" == join(uic,"-")); + + // indexed access + CHECK ("Γ" == uic[UIC_WINDOW]); // window spec to anchor the path + CHECK ("Δ" == uic[UIC_PERSP]); // the perspective used within that window + CHECK ("Θ" == uic[UIC_PANEL]); // the docking panel within the window + CHECK ("Ξ" == uic[UIC_VIEW]); // the view residing in the docking panel + CHECK ("Σ" == uic[UIC_TAB]); // the tab or element group within the view + CHECK ("Ψ" == uic[UIC_PATH]); // a path sequence... + CHECK ("Φ" == uic[UIC_PATH+1]); // ...descending through local widgets + CHECK ("Ω" == uic[UIC_PATH+2]); + + // sequential access to the path part + CHECK ("Ψ-Φ-Ω" == join(uic.pathSeq(),"-")); + CHECK ("Ψ/Φ/Ω" == uic.getPath()); + + // iteration of complete coordinates matches index order + uint i=0; + for (UICoord::iterator ii = uic.begin(); ii; ++ii, ++i) + CHECK (uic[i] == *ii); + CHECK (8 == i); + + VERIFY_ERROR (INDEX_BOUNDS, uic[8]); + + // string representation + CHECK ("UI:Γ[Δ]-Θ.Ξ.Σ/Ψ/Φ/Ω" == string(uic)); + CHECK ( "Γ[Δ]-Θ.Ξ.Σ" == uic.getComp()); + CHECK ( "Ψ/Φ/Ω" == uic.getPath()); + + // new value can be assigned, but not altered in place + uic = UICoord{nullptr,nullptr,"Θ",nullptr,"Σ",nullptr,"Φ"}; + CHECK (7 == uic.size()); + + // representation is trimmed and filled + CHECK ("UI:?-Θ.*.Σ/*/Φ" == string(uic)); + CHECK (Symbol::EMPTY == uic[UIC_WINDOW]); + CHECK (Symbol::EMPTY == uic[UIC_PERSP]); + CHECK ("Θ" == uic[UIC_PANEL]); + CHECK ("*" == uic[UIC_VIEW]); + CHECK ("Σ" == uic[UIC_TAB]); + CHECK ("*" == uic[UIC_PATH]); + CHECK ("Φ" == uic[UIC_PATH+1]); + VERIFY_ERROR (INDEX_BOUNDS, uic[UIC_PATH+2]); + } + + + void + verify_builder() + { + UICoord uic1 = UICoord::window("window"); + UICoord uic2 = uic1.view("view"); // Note: does not alter uic1 + CHECK ("UI:window" == string(uic1)); + CHECK ("UI:window[*]-*.view" == string(uic2)); + CHECK (1 == uic1.size()); + CHECK (4 == uic2.size()); + + // fault-tolerant accessors for the generic part + CHECK ("window" == uic1.getWindow()); + CHECK ("window" == uic2.getWindow()); + CHECK ("" == uic1.getPersp()); + CHECK ("*" == uic2.getPersp()); + CHECK ("" == uic1.getPanel()); + CHECK ("*" == uic2.getPanel()); + CHECK ("" == uic1.getView()); + CHECK ("view" == uic2.getView()); + CHECK ("" == uic1.getTab()); + CHECK ("" == uic2.getTab()); + CHECK ("" == uic1.getPath()); + CHECK ("" == uic2.getPath()); + CHECK ("window" == uic1.getComp()); + CHECK ("window[*]-*.view" == uic2.getComp()); + + VERIFY_ERROR (INDEX_BOUNDS, uic1[UIC_PERSP]); + VERIFY_ERROR (INDEX_BOUNDS, uic2[UIC_TAB]); + + // partial (incomplete) coordinate spec + UICoord uic3 = UICoord().view("view"); + CHECK (4 == uic3.size()); + CHECK ("UI:?.view" == string(uic3)); + CHECK ("" == uic3.getWindow()); + CHECK ("" == uic3.getPersp()); + CHECK ("" == uic3.getPanel()); + CHECK ("view" == uic3.getView()); + + UICoord uic4 = uic3.persp("perspective"); + CHECK (4 == uic4.size()); + CHECK ("UI:?[perspective]-*.view" == string(uic4)); + + uic4 = uic3.append("tab"); + CHECK (5 == uic4.size()); + CHECK ("UI:?.view.tab" == string(uic4)); + uic4 = uic3.prepend("panel"); + CHECK (4 == uic4.size()); + CHECK ("UI:?-panel.view" == string(uic4)); + uic4 = uic4.tab(555); + CHECK (5 == uic4.size()); + CHECK ("UI:?-panel.view.#555" == string(uic4)); + VERIFY_ERROR(LOGIC, uic1.prepend("root")); + + // the builder may shorten/truncate the path + uic4 = uic4.append("α/β/γ/δ/ε/λ").truncateTo(9); + CHECK (9 == uic4.size()); + CHECK ("UI:?-panel.view.#555/α/β/γ/δ" == string(uic4)); + uic4 = uic4.path("ε/ε/ε/ε/ε").truncateTo(3); + CHECK (3 == uic4.size()); + CHECK ("UI:?-panel" == string(uic4)); + uic4 = uic4.append("something").truncateTo(2); + CHECK (0 == uic4.size()); // NOTE: normalisation detected absence of any remaining content + CHECK ("UI:?" == string(uic4)); + } + + + void + verify_stringRepr() + { + UICoord uic; + CHECK ("UI:?" == string(uic)); + CHECK ("" == uic.getComp()); + CHECK ("" == uic.getPath()); + + uic = uic.path("ἁρχή"); + CHECK ("UI:?/ἁρχή" == string(uic)); + CHECK ("" == uic.getComp()); + CHECK ("ἁρχή" == uic.getPath()); + + uic = uic.path("α/β/γ"); + CHECK ("UI:?/α/β/γ" == string(uic)); + CHECK ("" == uic.getComp()); + CHECK ("α/β/γ" == uic.getPath()); + + uic = uic.append("δ"); + CHECK ("UI:?/α/β/γ/δ" == string(uic)); + CHECK ("" == uic.getComp()); + CHECK ("α/β/γ/δ" == uic.getPath()); + + uic = uic.append(""); + CHECK ("UI:?/α/β/γ/δ" == string(uic)); + CHECK ("" == uic.getComp()); + CHECK ("α/β/γ/δ" == uic.getPath()); + + uic = uic.append("ε/λ/ον"); + CHECK ("UI:?/α/β/γ/δ/ε/λ/ον" == string(uic)); + CHECK ("" == uic.getComp()); + CHECK ("α/β/γ/δ/ε/λ/ον" == uic.getPath()); + + // note: we built a partially empty path array... + CHECK (12 == uic.size()); + CHECK (Symbol::EMPTY == uic.getView()); + CHECK (Symbol::EMPTY == uic.getTab()); + CHECK (Symbol::EMPTY == uic[UIC_WINDOW]); + CHECK (Symbol::EMPTY == uic[UIC_PERSP]); + CHECK (Symbol::EMPTY == uic[UIC_PANEL]); + CHECK (Symbol::EMPTY == uic[UIC_VIEW]); + CHECK (Symbol::EMPTY == uic[UIC_TAB]); + CHECK ("α" == uic[UIC_PATH]); + CHECK ("β" == uic[UIC_PATH+1]); + CHECK ("γ" == uic[UIC_PATH+2]); + CHECK ("δ" == uic[UIC_PATH+3]); + CHECK ("ε" == uic[UIC_PATH+4]); + CHECK ("λ" == uic[UIC_PATH+5]); + CHECK ("ον" == uic[UIC_PATH+6]); + + uic = uic.prepend("ειδος"); + CHECK ("UI:?.ειδος/α/β/γ/δ/ε/λ/ον" == string(uic)); + CHECK ("?.ειδος" == uic.getComp()); + CHECK ("α/β/γ/δ/ε/λ/ον" == uic.getPath()); + CHECK (12 == uic.size()); + + uic = uic.tab(""); + CHECK ("UI:?/α/β/γ/δ/ε/λ/ον" == string(uic)); + CHECK ("" == uic.getComp()); + CHECK ("α/β/γ/δ/ε/λ/ον" == uic.getPath()); + + uic = uic.view("ειδος"); + CHECK ("UI:?.ειδος.*/α/β/γ/δ/ε/λ/ον" == string(uic)); + CHECK ("?.ειδος.*" == uic.getComp()); + CHECK ("α/β/γ/δ/ε/λ/ον" == uic.getPath()); + + uic = uic.prepend("panel"); + CHECK ("UI:?-panel.ειδος.*/α/β/γ/δ/ε/λ/ον" == string(uic)); + CHECK ("?-panel.ειδος.*" == uic.getComp()); + CHECK ("α/β/γ/δ/ε/λ/ον" == uic.getPath()); + + uic = uic.view(nullptr); + CHECK ("UI:?-panel.*.*/α/β/γ/δ/ε/λ/ον" == string(uic)); + CHECK ("?-panel.*.*" == uic.getComp()); + CHECK ("α/β/γ/δ/ε/λ/ον" == uic.getPath()); + + uic = uic.tab(8); + CHECK ("UI:?-panel.*.#8/α/β/γ/δ/ε/λ/ον" == string(uic)); + CHECK ("?-panel.*.#8" == uic.getComp()); + CHECK ("α/β/γ/δ/ε/λ/ον" == uic.getPath()); + + uic = uic.noTab(); + CHECK ("UI:?-panel.*/α/β/γ/δ/ε/λ/ον" == string(uic)); + CHECK ("?-panel.*" == uic.getComp()); + CHECK ("α/β/γ/δ/ε/λ/ον" == uic.getPath()); + + uic = uic.tab(" "); + CHECK ("UI:?-panel.*. /α/β/γ/δ/ε/λ/ον" == string(uic)); + CHECK ("?-panel.*. " == uic.getComp()); + CHECK ("α/β/γ/δ/ε/λ/ον" == uic.getPath()); + + uic = uic.prepend("perspective"); + CHECK ("UI:?[perspective]-panel.*. /α/β/γ/δ/ε/λ/ον" == string(uic)); + CHECK ("?[perspective]-panel.*. " == uic.getComp()); + CHECK ("α/β/γ/δ/ε/λ/ον" == uic.getPath()); + + uic = uic.prepend("win"); + CHECK ("UI:win[perspective]-panel.*. /α/β/γ/δ/ε/λ/ον" == string(uic)); + CHECK ("win[perspective]-panel.*. " == uic.getComp()); + CHECK ("α/β/γ/δ/ε/λ/ον" == uic.getPath()); + + uic = uic.persp(""); + CHECK ("UI:win[*]-panel.*. /α/β/γ/δ/ε/λ/ον" == string(uic)); + CHECK ("win[*]-panel.*. " == uic.getComp()); + CHECK ("α/β/γ/δ/ε/λ/ον" == uic.getPath()); + CHECK (12 == uic.size()); + + uic = uic.path(" "); + CHECK ("UI:win[*]-panel.*. / " == string(uic)); + CHECK ("win[*]-panel.*. " == uic.getComp()); + CHECK (" " == uic.getPath()); + CHECK (6 == uic.size()); + CHECK (" " == uic[UIC_PATH]); + VERIFY_ERROR (INDEX_BOUNDS, uic[UIC_PATH+1]); + + uic = uic.path(nullptr); + CHECK ("UI:win[*]-panel.*. " == string(uic)); + CHECK ("win[*]-panel.*. " == uic.getComp()); + CHECK ("" == uic.getPath()); + CHECK (5 == uic.size()); + VERIFY_ERROR (INDEX_BOUNDS, uic[UIC_PATH]); + + uic = uic.append(nullptr); + CHECK ("UI:win[*]-panel.*. " == string(uic)); + CHECK ("win[*]-panel.*. " == uic.getComp()); + CHECK ("" == uic.getPath()); + CHECK (5 == uic.size()); + + uic = uic.append("*"); + CHECK ("UI:win[*]-panel.*. " == string(uic)); + CHECK ("win[*]-panel.*. " == uic.getComp()); + CHECK ("" == uic.getPath()); + CHECK (5 == uic.size()); + + uic = uic.append("**"); + CHECK ("UI:win[*]-panel.*. /**" == string(uic)); + CHECK ("win[*]-panel.*. " == uic.getComp()); + CHECK ("**" == uic.getPath()); + CHECK ("**" == uic[UIC_PATH]); + CHECK (6 == uic.size()); + + uic = uic.tab(""); + CHECK ("UI:win[*]-panel.*.*/**" == string(uic)); + CHECK ("win[*]-panel.*.*" == uic.getComp()); + CHECK ("**" == uic.getPath()); + CHECK (6 == uic.size()); + + uic = uic.path(""); + CHECK ("UI:win[*]-panel" == string(uic)); + CHECK ("win[*]-panel" == uic.getComp()); + CHECK ("" == uic.getPath()); + CHECK (3 == uic.size()); + } + + + void + verify_comparisons() + { + UICoord u1 {"Γ","Δ","Θ","Ξ","Σ","Ψ","Φ" }; + UICoord u11{"Γ","Δ","Θ","Ξ","Σ","Ψ","Φ","Ω"}; + UICoord u2 {"Γ","Δ","Θ","Σ","Ξ","Ψ","Φ","Ω"}; + + UICoord u1c{u1}; + + CHECK (u1 == u1 ); + CHECK (u1 != u11); + CHECK (u2 != u1 ); + CHECK (u2 != u11); + + CHECK (u1 == u1c); + CHECK (u1 == UICoord("Γ","Δ","Θ","Ξ","Σ","Ψ","Φ" )); + CHECK (u1 == UICoord("Γ","Δ","Θ","Ξ","Σ","Ψ","Φ","" )); + CHECK (UICoord("Γ","Δ","Θ","Ξ","Σ","Ψ","Φ",nullptr) == UICoord("Γ","Δ","Θ","Ξ","Σ","Ψ","Φ")); + + CHECK (u11 == u1.path("Ψ/Φ/Ω//")); + CHECK (u11 != u1.path("//Ψ/Φ/Ω")); + + CHECK (u1 > u11); + CHECK (u11 < u1 ); + CHECK (u1 >= u1 ); + CHECK (u1 >= u11); + CHECK (not (u1 < u11)); + CHECK (not (u1 < u1 )); + CHECK (not (u1 < u2 )); + CHECK (not (u11 < u2 )); + + // expansion of jokers from parent path is tolerated + CHECK (u11 < u1.view("*").window(NULL)); + } + + + void + verify_localPredicates() + { + UICoord nil; + UICoord u1 { "", "", "","Ξ","Σ"}; + UICoord u2 {"Γ","*","Θ","Ξ","Σ"}; + UICoord u3 {"Γ","Δ","Θ","Ξ","Σ"}; + + CHECK (not u1.isComplete()); + CHECK (not u1.isExplicit()); + + CHECK ( u2.isComplete()); + CHECK (not u2.isExplicit()); + + CHECK ( u3.isComplete()); + CHECK ( u3.isExplicit()); + + CHECK (u1.isIncomplete()); + CHECK (not nil.isExplicit()); + CHECK (not nil.isComplete()); + CHECK (not nil.isIncomplete()); // note fine point + + CHECK (not u1.isPresent(UIC_WINDOW)); + CHECK (not u1.isPresent(UIC_PERSP)); + CHECK (not u1.isPresent(UIC_PANEL)); + CHECK ( u1.isPresent(UIC_VIEW)); + CHECK ( u1.isPresent(UIC_TAB)); + CHECK (not u1.isPresent(UIC_PATH)); + CHECK (not u1.isPresent(UIC_PATH+1)); + + CHECK ( u2.isPresent(UIC_WINDOW)); + CHECK (not u2.isPresent(UIC_PERSP)); + CHECK ( u2.isPresent(UIC_PANEL)); + CHECK ( u2.isPresent(UIC_VIEW)); + CHECK ( u2.isPresent(UIC_TAB)); + CHECK (not u2.isPresent(UIC_PATH)); + CHECK (not u2.isPresent(UIC_PATH+1)); + + CHECK ( u3.isPresent(UIC_WINDOW)); + CHECK ( u3.isPresent(UIC_PERSP)); + CHECK ( u3.isPresent(UIC_PANEL)); + CHECK ( u3.isPresent(UIC_VIEW)); + CHECK ( u3.isPresent(UIC_TAB)); + CHECK (not u3.isPresent(UIC_PATH)); + CHECK (not u3.isPresent(UIC_PATH+1)); + + CHECK (not u2.isWildcard(UIC_WINDOW)); + CHECK ( u2.isWildcard(UIC_PERSP)); + CHECK (not u2.isWildcard(UIC_PANEL)); + CHECK (not u2.isWildcard(UIC_VIEW)); + CHECK (not u2.isWildcard(UIC_TAB)); + CHECK (not u2.isWildcard(UIC_PATH)); + CHECK (not u2.isWildcard(UIC_PATH+1)); + } + }; + + + /** Register this test class... */ + LAUNCHER (UICoord_test, "unit gui"); + + +}}} // namespace gui::interact::test diff --git a/tests/gui/interact/ui-location-solver-test.cpp b/tests/gui/interact/ui-location-solver-test.cpp new file mode 100644 index 000000000..6faaaf6e4 --- /dev/null +++ b/tests/gui/interact/ui-location-solver-test.cpp @@ -0,0 +1,487 @@ +/* + UILocationSolver(Test) - verify mechanics of a DSL to configure view allocation + + Copyright (C) Lumiera.org + 2018, Hermann Vosseler + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +* *****************************************************/ + +/** @file ui-location-solver-test.cpp + ** unit test \ref UILocationSolver_test + */ + + +#include "lib/test/run.hpp" +#include "lib/test/test-helper.hpp" +#include "gui/interact/ui-coord.hpp" +#include "gui/interact/ui-location-solver.hpp" +#include "gen-node-location-query.hpp" +#include "lib/format-cout.hpp" + +#include + + +using std::string; +using lib::diff::MakeRec; +using lib::diff::Rec; + +using util::isnil; + + +namespace gui { +namespace interact { +namespace test { + + + + /******************************************************************************//** + * @test verify a mechanism to resolve the desired location of an UI-element. + * The UILocationSolver is operated by the ViewLocator service, which itself + * is part of the InteractionDirector. In typical usage, the location rules + * are drawn from the [ViewSpec-DSL](\ref view-spec-dsl.hpp), evaluated + * with the help of a [Coordinate Resolver](\ref UICoordResolver), based on + * the real UI topology existing at that moment, accessible in abstracted + * form through the LocationQuery interface. This test setup here mimics that + * invocation scheme, but replaces the real UI by an abstract tree notation + * embedded directly into the individual test cases. + * + * @see ui-location-solver.hpp + * @see view-spec-dsl.hpp + * @see UICoordResolver_test + */ + class UILocationSolver_test : public Test + { + + virtual void + run (Arg) + { + simple_usage_example(); + verify_cornerCases(); + verify_standardSituations(); + } + + + /** @test demonstrate the typical invocation and usage */ + void + simple_usage_example() + { + //-------------------------------------------------------------Test-Fixture + // a Test dummy placeholder for the real UI structure + Rec dummyUiStructure = MakeRec() + .set("window-1" + , MakeRec() + .type("perspective") + .set("exclusivePanel", MakeRec()) + ); + // helper to answer "location queries" backed by this structure + GenNodeLocationQuery locationQuery{dummyUiStructure}; + //--------------------------------------------------------------(End)Test-Fixture + + + // our test subject.... + UILocationSolver solver{locationQuery}; + + // a rule to probe (meaning: attach it at the "shoddy" panel) + LocationRule rule{UICoord().panel("shoddy")}; + + // Now ask for a location to attach a view named "worldview" at the "shoddy" panel + // No solution can be found, since there is no "shoddy" panel + CHECK (isnil (solver.solve (rule, UIC_VIEW, "worldview"))); + + // add second location clause to the rule + // (meaning: accept any path leading down to an "exclusivePanel") + rule.append(UICoord().panel("exclusivePanel")); + + // and now we get a solution, since the second rule can be wildcard-matched + UICoord location = solver.solve (rule, UIC_VIEW, "worldview"); + CHECK (not isnil (location)); + + // the full solution filled in the missing parts and added the new view on top + CHECK ("UI:window-1[perspective]-exclusivePanel.worldview" == string(location)); + + // NOTE: the new view does not (yet) exist, but the preceding part can be "covered" + // To verify this, we attach a coordinate resolver (likewise backed by our dummy UI) + UICoordResolver resolver{location, locationQuery}; + CHECK (resolver.isCoveredPartially()); + CHECK (not resolver.isCoveredTotally()); + CHECK (UIC_VIEW == resolver.coverDepth()); // covered up to VIEW level + } // (the view itself is not covered) + + + + /** @test cover theoretical corner cases regarding the process of location solving. + * Point in question are the requirements and limits when querying against one or several + * location specification clauses. The actual matching of a location pattern against a UI topology + * is beyond scope and covered [elsewhere](\ref UICoordResolver_test::verify_mutateCoverage) + * - empty clauses act as neutral element + * - prerequisites regarding the depth of a location clause relevant for solution + * - the impact of the query and especially its expected depth + * - completely explicit clauses vs clauses with wildcards + * - relevance of partial or total coverage for the solution + * - regular clauses vs. _create clauses_ (which mandate creating parents as needed) + * - usage of the first applicable solution when several clauses are given + */ + void + verify_cornerCases() + { + //-------------------------------------------------------------Test-Fixture + GenNodeLocationQuery tree{MakeRec() + .set("win" + , MakeRec() + .type("A") + .set ("thePanel" + , MakeRec() + .set ("theView" + , MakeRec() + .set ("#5" + , MakeRec() + .set ("up", MakeRec()) + .set ("down" + , MakeRec() + .set ("the" + , MakeRec() + .set ("kitchen" + , MakeRec() + .set ("sink", MakeRec()) + ) + ) + ) + ) + ) + ) + )}; + UILocationSolver solver{tree}; + //--------------------------------------------------------------(End)Test-Fixture + + + /* === empty clause === */ + LocationRule r1{UICoord()}; + CHECK (isnil (solver.solve (r1, UIC_PATH, "to/salvation"))); + CHECK (isnil (solver.solve (r1, UIC_WINDOW, "redemption"))); + + /* === empty clause is neutral === */ + r1.append (UICoord().path("down/to").create()); + auto s1 = solver.solve(r1, UIC_PATH+2, "hell"); + CHECK ("UI:win[A]-thePanel.theView.#5/down/to/hell" == string{s1}); + + + /* === clause too short === */ + LocationRule r2{UICoord().path("down/the")}; + CHECK ( isnil (solver.solve (r2, UIC_PATH+3, "sink"))); + + /* === clause too long === */ + CHECK ( isnil (solver.solve (r2, UIC_VIEW, "theView"))); + + CHECK (not isnil (solver.solve (r2, UIC_PATH+1, "any"))); + CHECK (not isnil (solver.solve (r2, UIC_PATH+2, "kitchen"))); + + + + /* === query on existing window === */ + LocationRule r31{UICoord::window("win")}; + CHECK ("UI:win" == string{solver.solve (r31, UIC_WINDOW, "wigwam")}); + + /* === query on generic window spec === */ + LocationRule r32{UICoord::currentWindow()}; + CHECK ("UI:win" == string{solver.solve (r32, UIC_WINDOW, "wigwam")}); + + /* === query on non existing window === */ + LocationRule r33{UICoord::window("lindows")}; + CHECK (isnil (solver.solve (r33, UIC_WINDOW, "wigwam"))); + + /* === query on existing window with create clause === */ + LocationRule r34{UICoord::window("win").create()}; + CHECK ("UI:win" == string{solver.solve (r34, UIC_WINDOW, "wigwam")}); + + /* === query on non existing window with create clause === */ + LocationRule r35{UICoord::window("windux").create()}; + CHECK ("UI:windux" == string{solver.solve (r35, UIC_WINDOW, "wigwam")}); + + + /* === query on existing perspective === */ + LocationRule r41{UICoord().persp("A")}; + CHECK ("UI:win[A]" == string{solver.solve (r41, UIC_PERSP, "x")}); + CHECK ("UI:win[A]-x" == string{solver.solve (r41, UIC_PANEL, "x")}); + + /* === query on elided perspective ("just any existing") === */ + LocationRule r42{UICoord().persp(UIC_ELIDED)}; + CHECK ("UI:win[A]" == string{solver.solve (r42, UIC_PERSP, "x")}); + CHECK ("UI:win[A]-x" == string{solver.solve (r42, UIC_PANEL, "x")}); + + /* === query on non existing perspective === */ + LocationRule r43{UICoord::firstWindow().persp("Ω")}; + CHECK (isnil (solver.solve (r43, UIC_PERSP, "x"))); + CHECK (isnil (solver.solve (r43, UIC_PANEL, "x"))); + + /* === query on non existing perspective with create clause === */ + LocationRule r44{UICoord::firstWindow().persp("Ω").create()}; + CHECK ("UI:win[Ω]" == string{solver.solve (r44, UIC_PERSP, "x")}); + CHECK ("UI:win[Ω]-x" == string{solver.solve (r44, UIC_PANEL, "x")}); + + + /* === query on deep path covered === */ + LocationRule r51{UICoord("firstWindow","A","thePanel","theView","#5","down","the","kitchen")}; + CHECK ("UI:win[A]-thePanel.theView.#5/down/the/kitchen" == string{solver.solve (r51, UIC_PATH+2, "drain")}); + CHECK ("UI:win[A]-thePanel.theView.#5/down/the/kitchen/drain" == string{solver.solve (r51, UIC_PATH+3, "drain")}); + + /* === query on deep path covered with create clause === */ + LocationRule r52{UICoord::firstWindow().append("A/thePanel/theView/#5/down/the/kitchen").create()}; + CHECK ("UI:win[A]-thePanel.theView.#5/down/the/kitchen" == string{solver.solve (r52, UIC_PATH+2, "drain")}); + CHECK ("UI:win[A]-thePanel.theView.#5/down/the/kitchen/drain" == string{solver.solve (r52, UIC_PATH+3, "drain")}); + + /* === query on deep path partially covered === */ + LocationRule r53{UICoord::firstWindow().append("A/thePanel/theView/#5/down/the/drain")}; + CHECK (isnil (solver.solve (r53, UIC_PATH+2, "drain"))); + CHECK (isnil (solver.solve (r53, UIC_PATH+3, "drain"))); + + /* === query on deep path partially covered with create clause === */ + LocationRule r54{UICoord::firstWindow().append("A/thePanel/theView/#5/down/the/drain").create()}; + CHECK ("UI:win[A]-thePanel.theView.#5/down/the/drain" == string{solver.solve (r54, UIC_PATH+2, "drain")}); + CHECK ("UI:win[A]-thePanel.theView.#5/down/the/drain/drain" == string{solver.solve (r54, UIC_PATH+3, "drain")}); + + /* === query on deep path uncovered === */ + LocationRule r55{UICoord("rearWindow","A","thePanel","theView","#5","down","the","kitchen")}; + CHECK (isnil (solver.solve (r55, UIC_PATH+2, "floor"))); + CHECK (isnil (solver.solve (r55, UIC_PATH+3, "floor"))); + + /* === query on deep path uncovered with create clause === */ + LocationRule r56{UICoord("rearWindow","A","thePanel","theView","#5","down","the","kitchen").rebuild().create()}; + CHECK ("UI:rearWindow[A]-thePanel.theView.#5/down/the/kitchen" == string{solver.solve (r56, UIC_PATH+2, "floor")}); + CHECK ("UI:rearWindow[A]-thePanel.theView.#5/down/the/kitchen/floor" == string{solver.solve (r56, UIC_PATH+3, "floor")}); + + + /* === clause with wildcard covered === */ + LocationRule r61{UICoord().path("//kitchen")}; + CHECK ("UI:win[A]-thePanel.theView.#5/down/the/kitchen" == string{solver.solve (r61, UIC_PATH+2, "drain")}); + + /* === clause with wildcard covered without final element === */ + CHECK ("UI:win[A]-thePanel.theView.#5/down/the/kitchen/drain" == string{solver.solve (r61, UIC_PATH+3, "drain")}); + + /* === create clause with wildcard completely covered === */ + LocationRule r62{UICoord().path("//kitchen").create()}; + CHECK ("UI:win[A]-thePanel.theView.#5/down/the/kitchen" == string{solver.solve (r62, UIC_PATH+2, "window")}); + + /* === create clause with wildcard covered without final element === */ + CHECK ("UI:win[A]-thePanel.theView.#5/down/the/kitchen/window" == string{solver.solve (r62, UIC_PATH+3, "window")}); + + /* === clause with wildcard partially covered === */ + LocationRule r63{UICoord().path("/the/road")}; + CHECK (isnil (solver.solve (r63, UIC_PATH+2, "kitchen"))); //NOTE: .../down/the/kitchen would match, but actually .../down/the/road is tested, which fails + + /* === create clause with wildcard partially covered === */ + LocationRule r64{UICoord().path("/the/road").create()}; + CHECK ("UI:win[A]-thePanel.theView.#5/down/the/road" == string{solver.solve (r64, UIC_PATH+2, "drain")}); + + /* === clause with wildcard uncovered === */ + LocationRule r65{UICoord().path("//road")}; + CHECK (isnil (solver.solve (r65, UIC_PATH+2, "kitchen"))); + + /* === create clause with wildcard uncovered === */ + LocationRule r66{UICoord().path("//road").create()}; + CHECK (isnil (solver.solve (r66, UIC_PATH+2, "kitchen"))); + + + /* === two clauses both satisfied === */ + LocationRule r71{UICoord().path("down")}; + r71.append (UICoord().path("up")); + CHECK ("UI:win[A]-thePanel.theView.#5/down/time" == string{solver.solve (r71, UIC_PATH+1, "time")}); + + /* === two clauses first one unsatisfied === */ + LocationRule r72{UICoord().path("up/the")}; + r72.append (UICoord().path("down/")); + CHECK ("UI:win[A]-thePanel.theView.#5/down/time" == string{solver.solve (r72, UIC_PATH+1, "time")}); + + /* === create clause first and satisfied === */ + LocationRule r73{UICoord().path("up/link").create()}; + r73.append (UICoord().path("down/")); + CHECK ("UI:win[A]-thePanel.theView.#5/up/link" == string{solver.solve (r73, UIC_PATH+1, "time")}); + + /* === create clause first and unsatisfied === */ + LocationRule r74{UICoord().path("cross/link").create()}; + r74.append (UICoord().path("down/")); + CHECK ("UI:win[A]-thePanel.theView.#5/down/time" == string{solver.solve (r74, UIC_PATH+1, "time")}); + + /* === create clause second but first clause satisfied === */ + LocationRule r75{UICoord().path("up/")}; + r75.append (UICoord().path("down/link").create()); + CHECK ("UI:win[A]-thePanel.theView.#5/up/time" == string{solver.solve (r75, UIC_PATH+1, "time")}); + + /* === create clause second and satisfied === */ + LocationRule r76{UICoord().path("up/link")}; + r76.append (UICoord().path("down/link").create()); + CHECK ("UI:win[A]-thePanel.theView.#5/down/link" == string{solver.solve (r76, UIC_PATH+1, "time")}); + + /* === create clause second and both unsatisfied === */ + LocationRule r77{UICoord().path("up/link")}; + r77.append (UICoord().path("town/link").create()); + CHECK (isnil (solver.solve (r77, UIC_PATH+1, "time"))); + + CHECK (string{r77} == "=~ .. UI:?/up/link" + "\n OR UI:?/town/link create!"); + } + + + /** @test emulate the relevant standard situations of view location resolution. + * The typical location specifications to be expected in practice can be subsumed + * under a small selection of standard situations; this test demonstrates how these + * are triggered by specific tree configurations in a (hopefully) obvious way. + * + * For this purpose, we create a single set of location clauses here, but evaluate them + * each time against different (simulated) UI tree configurations to verify that the expected + * resulting location is actually derived in all those cases. + */ + void + verify_standardSituations() + { + // Test Fixture: a solver which always queries the current state of a (simulated) uiTree + Rec uiTree; + std::unique_ptr query; + UILocationSolver solver{[&]() -> GenNodeLocationQuery& + { + query.reset (new GenNodeLocationQuery(uiTree)); + return *query; + }}; + + // Test Fixture: common set of location clauses + LocationRule location{UICoord().persp("edit").panel("viewer")}; + location.append (UICoord::currentWindow().panel("viewer")); + location.append (UICoord().panel("viewer")); +// location.append (UICoord().tab("assetType()")); //////////////////////TICKET #1130 : do we want to support match based on invocation context (here: the type of the asset to be displayed) + location.append (UICoord().persp("asset").view("asset")); + location.append (UICoord().panel("asset").view("asset").create()); + location.append (UICoord::currentWindow().panel("viewer").create()); //Note: especially for this kind of rule, .persp(UIC_ELIDED) is injected automatically + location.append (UICoord::window("meta").persp("config").panel("infobox").view("inspect").create()); + + cout << location << endl; + + + /* === match by perspective + panel === */ + uiTree = MakeRec() + .set("win" + , MakeRec() + .type("edit") + .set ("viewer", MakeRec())); + CHECK ("UI:win[edit]-viewer.video" == string{solver.solve (location, UIC_VIEW, "video")}); + + /* === match by generic window + panel === */ + uiTree = MakeRec() + .set("win" + , MakeRec() + .type("murky") + .set ("viewer", MakeRec())) + .set("woe" + , MakeRec() + .type("gloomy") + .set ("viewer", MakeRec())); + CHECK ("UI:woe[gloomy]-viewer.video" == string{solver.solve (location, UIC_VIEW, "video")}); //Note: first rule does not match due to perspective + + /* === match by panel alone === */ + uiTree = MakeRec() + .set("win" + , MakeRec() + .type("murky") + .set ("viewer", MakeRec())) + .set("woe" + , MakeRec() + .type("gloomy") + .set ("timeline", MakeRec())); + CHECK ("UI:win[murky]-viewer.video" == string{solver.solve (location, UIC_VIEW, "video")}); //Note: current window (==last one) has no "viewer"-panel + + + + /* === wildcard match on explicit existing view === */ + uiTree = MakeRec() + .set("win" + , MakeRec() + .type("shady") + .set("timeline", MakeRec())) + .set("woe" + , MakeRec() + .type("asset") + .set ("panel" + , MakeRec() + .set ("asset", MakeRec()) + )); + CHECK ("UI:woe[asset]-panel.asset" == string{solver.solve (location, UIC_VIEW, "video")}); //Note: the 4th Rule matches on existing view "asset", + // in spite of our query demanding a view "video" + /* === wildcard match based on the type of entity to be displaced === */ +#if false ///////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1130 : not yet possible. Match based on placeholder substituted from context +// uiTree = MakeRec() +// .set("win" +// , MakeRec() +// .type("shady") +// .set ("special" +// , MakeRec() +// .set ("asset", +// MakeRec() +// .set ("specialAsset", MakeRec()) +// ) +// )) +// .set("woe" +// , MakeRec() +// .type("asset") +// .set ("panel" +// , MakeRec() +// .set ("asset", MakeRec()) +// )); +// CHECK ("UI:win[shady]-special.asset.specialAsset" == string{solver.solve (location, UIC_TAB, "specialAsset")}); +// //Note: the next rule would match on the general asset panel +// // but this special rule allows to re-use a tab dedicated to specialAsset +#endif ///////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1130 : not yet possible. Match based on placeholder substituted from context + + + + /* === create clause to build on a specific anchor point === */ + uiTree = MakeRec() + .set("win" + , MakeRec() + .type("shady") + .set ("asset", MakeRec()) + ); + auto solution = solver.solve (location, UIC_TAB, "video"); //Note: here the first "create"-rule is triggered: UI:?-asset.asset + CHECK ("UI:win[shady]-asset.asset.video" == string{solution}); // It requires a panel("asset") to exist, but creates the rest; + CHECK ( 3 == UICoordResolver(solution, *query) // indeed only the part up to the panel is detected as covered. + .coverDepth()); + //Note: the following test cases can not trigger this rule, since it + /* === match on create clause with generic window spec and panel === */ // contains leading wildcards and thus requires panel("asset") + uiTree = MakeRec() + .set("win" + , MakeRec() + .type("shady") + .set("timeline", MakeRec())) + .set("woe" + , MakeRec() + .type("shoddy")); + solution = solver.solve (location, UIC_VIEW, "video"); + CHECK ("UI:woe[shoddy]-viewer.video" == string{solution}); + CHECK ( 2 == UICoordResolver(solution, *query) //Note: only window and perspective are covered, the rest is to be created + .coverDepth()); + + /* === completely uncovered create-from-scratch === */ + solution = solver.solve (location, UIC_TAB, "engine"); //Note: same UI-tree, but this time we ask for a tab, so the previous rule + CHECK ("UI:meta[config]-infobox.inspect.engine" == string{solution}); // is too short and thus the last catch-all rule gets triggered; + CHECK ( 0 == UICoordResolver(solution, *query) //Note: result is indeed entirely uncovered (-> create from scratch) + .coverDepth()); + } + }; + + + /** Register this test class... */ + LAUNCHER (UILocationSolver_test, "unit gui"); + + +}}} // namespace gui::interact::test diff --git a/tests/gui/interact/view-spec-dsl-test.cpp b/tests/gui/interact/view-spec-dsl-test.cpp new file mode 100644 index 000000000..f2092b3c6 --- /dev/null +++ b/tests/gui/interact/view-spec-dsl-test.cpp @@ -0,0 +1,247 @@ +/* + ViewSpecDSL(Test) - verify mechanics of a DSL to configure view allocation + + Copyright (C) Lumiera.org + 2017, Hermann Vosseler + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +* *****************************************************/ + +/** @file view-spec-dsl-test.cpp + ** unit test \ref ViewSpecDSL_test + */ + + +#include "lib/test/run.hpp" +#include "lib/test/test-helper.hpp" +#include "gui/interact/ui-coord.hpp" +#include "gui/interact/view-locator.hpp" +#include "gui/interact/view-spec-dsl.hpp" +#include "gen-node-location-query.hpp" +#include "test/test-element-access.hpp" +#include "lib/depend-inject.hpp" +#include "lib/format-cout.hpp" ////////////////TODO only while this test is under development +//#include "lib/idi/entry-id.hpp" +//#include "lib/diff/gen-node.hpp" +//#include "lib/util.hpp" + +//#include +//#include + + +using std::string; +using lib::diff::MakeRec; +using lib::diff::Rec; +//using lib::idi::EntryID; +//using lib::diff::GenNode; +//using util::isSameObject; +//using util::isnil; + + +namespace gui { +namespace idi { //------Mock ViewSpec definitions for component test + + struct MockView1 + : gui::test::DummyWidget + { + using DummyWidget::DummyWidget; + }; + + struct MockView2 + : gui::test::DummyWidget + { + using DummyWidget::DummyWidget; + }; + + /* ==== Dummy ViewSpec rules for those two mock view types (--> see id-scheme.hpp) ==== */ + + template<> + struct Descriptor + { + ViewSpec locate = UICoord::currentWindow().panel("parentLocation"); + Allocator alloc = limitAllocation(2); + }; + +}//----------------(End)Mock ViewSpec definitions + +namespace interact { +namespace test { + +// using lumiera::error::LUMIERA_ERROR_WRONG_TYPE; + using lib::test::showSizeof; + using gui::model::ElementAccess; + using gui::test::TestElementAccess; + using gui::test::DummyWidget; + using gui::test::DummyView; + using gui::test::DummyTab; + + using MockLoationSolver = lib::DependInject::Local<>; + using MockElementAccess = lib::DependInject::Local; + + namespace { //Test fixture... + + }//(End)Test fixture + + + /******************************************************************************//** + * @test verify the mechanics of a functor based internal DSL + * to configure access and allocation patters for component views. + * + * @see id-scheme.hpp + * @see ViewLocator + * @see UICoord_test + */ + class ViewSpecDSL_test : public Test + { + + virtual void + run (Arg) + { +// verify_basicProperties(); + verify_standardUsage(); +// verify_alternatives(); + + verify_genericInvocation(); + } + + + void + verify_basicProperties() + { + UNIMPLEMENTED ("basic properties of the view spec DSL"); + } + + + void + verify_standardUsage() + { + //-------------------------------------------------------------Test-Fixture + // a Test dummy placeholder for the real UI structure + Rec dummyUiStructure = MakeRec() + .set("win-1" + , MakeRec() + .type("perspective") + .set("parentLocation", MakeRec()) + ); + // answer "location queries" backed by this structure + GenNodeLocationQuery locationQuery{dummyUiStructure}; + MockLoationSolver mock ([&]{ return new UILocationSolver{locationQuery}; }); + //--------------------------------------------------------------(End)Test-Fixture + + + + uint allocCounter = 0; + + // Simulation/Example for an allocator-builder + AllocSpec limitAllocation =[&](UICoord target, uint limit) + { + if (allocCounter < limit) + return target.tab(++allocCounter); + else + return target.tab(limit); + }; + + // the actual View Specification would then be written as... + ViewSpec locate = UICoord::currentWindow().panel("parentLocation"); + Allocator alloc = limitAllocation(3); + + // ...and it would be evaluated as follows + UICoord targetLocation = locate("viewID"); + UICoord realView1 = alloc(targetLocation); + CHECK (1 == allocCounter); + CHECK (string{realView1} == "UI:win-1[perspective]-parentLocation.viewID.#1"); + + UICoord realView2 = alloc(targetLocation); + CHECK (2 == allocCounter); + CHECK (string{realView2} == "UI:win-1[perspective]-parentLocation.viewID.#2"); + CHECK (realView2 != realView1); + + UICoord realView3 = alloc(targetLocation); + CHECK (3 == allocCounter); + CHECK (string{realView3} == "UI:win-1[perspective]-parentLocation.viewID.#3"); + + UICoord realView3b = alloc(targetLocation); + CHECK (3 == allocCounter); + CHECK (realView3b == realView3); + } + + + void + verify_alternatives() + { + UNIMPLEMENTED ("querying and selection of location alternatives"); + } + + + /** @test generic integrated access through ViewLocator + * This test demonstrates and verifies the way ViewLocator combines type based + * selection of the applicable DSL clauses, the invocation of those DSL definitions, + * the allocation of a suitable element and finally specifically typed access to this + * located or allocated element. + * @remarks due to limitations of our unit test setup (GTK is prohibited), this component + * integration test can not be performed against the actual DSL definitions of the + * real UI, even while it uses the actual code from ViewLocator. Simply because we + * can not instantiated UI widgets in a unit test. We have to resort to mock UI + * elements and thus use a dummy "view type" together with faked DSL definitions + * for this dummy. These definitions are given in the test fixture above, right + * within this translation unit. (see `namespace idi` and the class `MockView1`). + */ + void + verify_genericInvocation() + { + ViewLocator viewLocator; + + //-------------------------------------------------------------Test-Fixture + // a Test dummy placeholder for the real UI structure + Rec dummyUiStructure = MakeRec() + .set("win-1" + , MakeRec() + .type("perspective") + .set("parentLocation", MakeRec()) + ); + // answer "location queries" backed by this structure + GenNodeLocationQuery locationQuery{dummyUiStructure}; + MockLoationSolver mock ([&]{ return new UILocationSolver{locationQuery}; }); + + MockElementAccess fakeAccessor; + fakeAccessor.triggerCreate(); + //--------------------------------------------------------------(End)Test-Fixture + + + //--------------------------------------------------------------Staging: Testcase-1 + using idi::MockView1; + fakeAccessor->existingPath = UICoord{"win-1","perspective","parentLocation"}; + CHECK (not fakeAccessor->response); // not yet created + //--------------------------------------------------------------Staging: Testcase-1 + + MockView1& view1 = viewLocator.get(); + cout << "created view:" << view1.getID() << endl; + /////////////////////////////////////////////TICKET 1129 : some way to verify the last allocated path. Should be a child of "parentLocation" + + + +// TimelineView timeline = viewLocator.get(); + + /////////////////////////////////////////////////////////////////////////////////////////TICKET 1129 : use an EventLog to verify the forwarded invocations?? + } + }; + + + /** Register this test class... */ + LAUNCHER (ViewSpecDSL_test, "unit gui"); + + +}}} // namespace gui::interact::test diff --git a/tests/gui/model/element-access-test.cpp b/tests/gui/model/element-access-test.cpp new file mode 100644 index 000000000..c87ad38a4 --- /dev/null +++ b/tests/gui/model/element-access-test.cpp @@ -0,0 +1,163 @@ +/* + ElementAccess(Test) - verify mechanics of low-level UI element access + + Copyright (C) Lumiera.org + 2018, Hermann Vosseler + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +* *****************************************************/ + +/** @file element-access-test.cpp + ** unit test \ref ElementAccess_test + */ + + +#include "lib/test/run.hpp" +#include "lib/test/test-helper.hpp" +//#include "gui/interact/view-spec-dsl.hpp" +#include "test/test-element-access.hpp" +#include "gui/interact/ui-coord.hpp" +//#include "gen-node-location-query.hpp" +#include "lib/depend-inject.hpp" +#include "lib/format-cout.hpp" +//#include "lib/idi/entry-id.hpp" +//#include "lib/diff/gen-node.hpp" +#include "lib/util.hpp" + +//#include +//#include + + +//using std::string; +//using lib::diff::MakeRec; +//using lib::diff::Rec; +//using lib::idi::EntryID; +//using lib::diff::GenNode; +using util::isSameObject; +//using util::isnil; + + +namespace gui { +namespace model{ +namespace test { + +// using lumiera::error::LUMIERA_ERROR_WRONG_TYPE; +// using lib::test::showSizeof; + using interact::UICoord; + using gui::test::TestElementAccess; + using gui::test::DummyWidget; + using gui::test::DummyView; + using gui::test::DummyTab; + + using MockAccess = lib::DependInject::Local; + using AccessAPI = lib::Depend; + + + + /******************************************************************************//** + * @test verify the usage pattern of low-level UI element access, based on a + * mock implementation of the accessor directory. + * @todo 4/2018 in the course of establishing an UI backbone, it is sufficient + * just to _have_ that abstraction interface; so the test focuses merely + * on the invocation, and documents how the mock be used. Which is a + * prerequisite to get the ViewSpecDSL_test finished. The intention is + * to elaborate the mock in a second step later and use it to build a + * draft of the implementation mechanics, but based on `Rec` + * rather than on the real UI topology. + * @see GenNodeLocationQuery + * + * @see [Mock testing support](test-element-access.hpp) + * @see id-scheme.hpp + * @see ViewLocator + * @see ViewSpecDSL_test + */ + class ElementAccess_test : public Test + { + + virtual void + run (Arg) + { + verify_simpleAccess(); + verify_standardUsage(); + verify_limitedCreate(); + verify_createNewPath(); + } + + + /** @test simple access to an existing element designated by coordinates */ + void + verify_simpleAccess() + { + MockAccess fakeDirectory; + + auto location = UICoord{"win-1","persp-A","thePanel","someView","tab#5"}; + + fakeDirectory.triggerCreate(); + fakeDirectory->existingPath = location; + fakeDirectory->response.reset (new DummyTab); + + AccessAPI accessAPI; + auto answer = accessAPI().access (location); + + CHECK (answer.isValid()); + DummyWidget& widget = answer; + CHECK (INSTANCEOF (DummyTab, &widget)); + CHECK (isSameObject (widget, *fakeDirectory->response)); + } + + + /** @test the standard use case is to create one new child node + * below an existing path (widget) within the UI + */ + void + verify_standardUsage() + { + MockAccess fakeDirectory; + + auto path = UICoord{"win-1","persp-A","thePanel"}; + auto location = UICoord{"win-1","persp-A","thePanel","someView"}; + + fakeDirectory.triggerCreate(); + fakeDirectory->existingPath = path; + CHECK (not fakeDirectory->response); + + AccessAPI accessAPI; + DummyView& view = accessAPI().access (location); + CHECK ( fakeDirectory->response); // has been created + CHECK (isSameObject (view, *fakeDirectory->response)); + } + + + void + verify_limitedCreate() + { + UNIMPLEMENTED ("limit creation of new sibling objects"); + } + + + void + verify_createNewPath() + { + UNIMPLEMENTED ("create a new path from scratch"); + } + }; + + + /** Register this test class... */ + LAUNCHER (ElementAccess_test, "unit gui"); + + +}}} // namespace gui::model::test diff --git a/tests/gui/session-structure-mapping-test.cpp b/tests/gui/session-structure-mapping-test.cpp index 2d6588b2b..148a59187 100644 --- a/tests/gui/session-structure-mapping-test.cpp +++ b/tests/gui/session-structure-mapping-test.cpp @@ -32,7 +32,16 @@ ** @todo WIP ///////////////////////TICKET #955 ** @todo WIP ///////////////////////TICKET #961 ** - ** @see gui::model::SessionFacade + ** @todo as of 9/2017 there is now a whole subsystem based on those ideas, + ** yet still we can not demonstrate a complete integration as was + ** intended by this test. For now this test is an empty placeholder and + ** reminder to create a concise and readable high-level demonstration + ** eventually. And the idea to provide a model::Diagnostics counterpart + ** for test invocation seems worth exploring further... + ** + ** @note meanwhile there is a working diff framework and implementation in place... + ** @see DiffTreeApplication_test + ** @see DiffComplexApplication_test ** */ diff --git a/tests/gui/test/placeholder-command.hpp b/tests/gui/test/placeholder-command.hpp index 0cd1593e4..cd4528168 100644 --- a/tests/gui/test/placeholder-command.hpp +++ b/tests/gui/test/placeholder-command.hpp @@ -47,10 +47,7 @@ #include "lib/test/event-log.hpp" #include "proc/control/command-def.hpp" #include "lib/format-util.hpp" -//#include "lib/diff/gen-node.hpp" -//#include -//#include #include #include @@ -69,7 +66,7 @@ namespace test{ * This is a typed definition frame with some operations, * suitably to be bound into a Proc-Layer command. The actual * command "operation" just logs invocation into a statically - * obtained \ref EventLog Event-Log instance. + * obtained [Event-Log](\ref EventLog) instance. * @note by design, in Lumiera the actual command functions are stateless. * For this reason, the command framework provides no way to associate * an _instance_ of some kind of "command object" with the (conceptual) diff --git a/tests/gui/test/test-element-access.hpp b/tests/gui/test/test-element-access.hpp new file mode 100644 index 000000000..5e578518a --- /dev/null +++ b/tests/gui/test/test-element-access.hpp @@ -0,0 +1,153 @@ +/* + TEST-ELEMENT-ACCESS.hpp - fake access directory to handle generic UI entities for test + + Copyright (C) Lumiera.org + 2018, Hermann Vosseler + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + + +/** @file test-element-access.hpp + ** Unit test helper for access to UI elements without actually running an UI. + ** This allows to cover functionality for resolving UI-coordinates against an UI topology + ** and accessing or creating elements. Obviously, only faked UI widgets can be returned, + ** but this does not matter for those features anyway. + ** + ** @note as of 4/2018 this is a first draft and WIP-WIP-WIP + ** @todo right now this test is braindead; the idea is to extend it similar to the + ** GenNodeLocationQuery eventually, so to mimic the way an actual implementation + ** would drill down into the UI topology. Yet at the moment (4/2018) we are still + ** far from implementing anything in this regard; we just need the interface... ///////////////////////////////TICKET #1134 + ** + ** @see ElementAccess_test usage example + ** @see elem-access-dir.hpp real implementation + ** + */ + + +#ifndef GUI_TEST_ELEMENT_ACCESS_H +#define GUI_TEST_ELEMENT_ACCESS_H + + +#include "lib/error.hpp" +#include "gui/model/element-access.hpp" +#include "gui/interact/ui-coord.hpp" +#include "test/mock-elm.hpp" +#include "lib/symbol.hpp" +//#include "lib/util.hpp" + +#include +#include + + + +namespace gui { +namespace test { + namespace error = lumiera::error; + +// using util::isnil; + using std::string; + using lib::Literal; + using interact::UICoord; + using interact::UIC_VIEW; + using interact::UIC_TAB; + + + + + /* === Dummy Widgets for Unit testing === */ + + class DummyWidget + : public MockElm + { + public: + virtual ~DummyWidget() { } ///< is an interface + + DummyWidget(string name ="DummyWidget") + : MockElm{name} + { } + DummyWidget(Literal name) + : DummyWidget{string (name)} + { } + }; + + struct DummyTab + : DummyWidget + { + using DummyWidget::DummyWidget; + }; + + struct DummyView + : DummyWidget + { + using DummyWidget::DummyWidget; + }; + + + + /** + * Mock implementation of the model::ElementAccess interface for testing without actual UI. + * @see ElementAccess_test + */ + class TestElementAccess + : public model::ElementAccess + { + + public: + explicit + TestElementAccess () + { } + + + /* == Test/Diagnostics interface == */ + + /** the next query will fail unless it presents this Path */ + UICoord existingPath; + + /** ...and if acceptable, the next query will answer with this object */ + std::unique_ptr response; + + + /* == ElementAccess interface == */ + + RawResult + performAccessTo (UICoord const& target, size_t limitCreation) override + { + CHECK (existingPath >= target); + if (existingPath > target and !response) + { + if (target.size()-1 == UIC_VIEW) + response.reset(new DummyView(target[UIC_VIEW])); + else + if (target.size()-1 == UIC_TAB) + response.reset(new DummyTab(target[UIC_TAB])); + else + throw error::Invalid("Mock ElementAccess supports only creation of VIEW and TAB. Requested Target was "+string(target)); + } + + return response.get(); + } + + + protected: + }; + + + + +}} // namespace gui::test +#endif /*GUI_TEST_ELEMENT_ACCESS_H*/ diff --git a/tests/gui/test/test-nexus.hpp b/tests/gui/test/test-nexus.hpp index 664c24754..73639ac6f 100644 --- a/tests/gui/test/test-nexus.hpp +++ b/tests/gui/test/test-nexus.hpp @@ -46,6 +46,7 @@ #include "lib/error.hpp" +#include "lib/nocopy.hpp" #include "gui/ctrl/bus-term.hpp" #include "gui/model/tangible.hpp" #include "gui/ctrl/state-manager.hpp" @@ -53,7 +54,6 @@ #include "lib/test/event-log.hpp" #include "lib/diff/gen-node.hpp" -#include #include #include @@ -74,7 +74,7 @@ namespace test{ * @see abstract-tangible-test.cpp */ class Nexus - : boost::noncopyable + : util::NonCopyable { public: diff --git a/tests/library/del-stash-test.cpp b/tests/library/del-stash-test.cpp index a50f545c4..ea1548a77 100644 --- a/tests/library/del-stash-test.cpp +++ b/tests/library/del-stash-test.cpp @@ -134,7 +134,7 @@ namespace test{ killer.manage (p); killer.manage (r); - killer.manage> (v); + killer.manage (static_cast*> (v)); CHECK (3 == killer.size()); diff --git a/tests/library/diff/gen-node-basic-test.cpp b/tests/library/diff/gen-node-basic-test.cpp index 0608db467..2b73c72f8 100644 --- a/tests/library/diff/gen-node-basic-test.cpp +++ b/tests/library/diff/gen-node-basic-test.cpp @@ -52,8 +52,8 @@ namespace lib { namespace diff{ namespace test{ - using error::LUMIERA_ERROR_WRONG_TYPE; - using error::LUMIERA_ERROR_BOTTOM_VALUE; + using error::LERR_(WRONG_TYPE); + using error::LERR_(BOTTOM_VALUE); namespace {//Test fixture.... diff --git a/tests/library/diff/generic-record-representation-test.cpp b/tests/library/diff/generic-record-representation-test.cpp index 1bd5c6328..02f8ddc6a 100644 --- a/tests/library/diff/generic-record-representation-test.cpp +++ b/tests/library/diff/generic-record-representation-test.cpp @@ -47,9 +47,9 @@ namespace lib { namespace diff{ namespace test{ - using lumiera::error::LUMIERA_ERROR_INVALID; - using lumiera::error::LUMIERA_ERROR_INDEX_BOUNDS; - using lumiera::error::LUMIERA_ERROR_BOTTOM_VALUE; + using lumiera::error::LERR_(INVALID); + using lumiera::error::LERR_(INDEX_BOUNDS); + using lumiera::error::LERR_(BOTTOM_VALUE); namespace {//Test fixture.... diff --git a/tests/library/diff/mutation-message-test.cpp b/tests/library/diff/mutation-message-test.cpp index 209b55e0d..6cd9a59cb 100644 --- a/tests/library/diff/mutation-message-test.cpp +++ b/tests/library/diff/mutation-message-test.cpp @@ -37,7 +37,7 @@ #include #include -using lumiera::error::LUMIERA_ERROR_ITER_EXHAUST; +using lumiera::error::LERR_(ITER_EXHAUST); using lib::iter_stl::IterSnapshot; using lib::iter_stl::snapshot; using lib::time::Time; diff --git a/tests/library/diff/tree-mutator-binding-test.cpp b/tests/library/diff/tree-mutator-binding-test.cpp index 56df7c00e..8059810b8 100644 --- a/tests/library/diff/tree-mutator-binding-test.cpp +++ b/tests/library/diff/tree-mutator-binding-test.cpp @@ -55,7 +55,7 @@ namespace lib { namespace diff{ namespace test{ - using lumiera::error::LUMIERA_ERROR_LOGIC; + using lumiera::error::LERR_(LOGIC); namespace {//Test fixture.... diff --git a/tests/library/exception-error-test.cpp b/tests/library/exception-error-test.cpp index d38beed34..104883e7f 100644 --- a/tests/library/exception-error-test.cpp +++ b/tests/library/exception-error-test.cpp @@ -28,41 +28,39 @@ #include "lib/error.h" #include "lib/error.hpp" - #include "lib/test/run.hpp" +#include "lib/format-cout.hpp" #include "lib/util.hpp" - -#include -#include #include #include +#include using std::runtime_error; using std::exception; using std::string; -using std::cout; namespace lumiera { namespace test { - /** local specific error-constant for use in the + LUMIERA_ERROR_DEFINE (LIFE_AND_UNIVERSE, "and everything?"); + /** local specific error-constant for use in the * constructor of the nested SpecificError class. */ - LUMIERA_ERROR_DEFINE(LIFE_AND_UNIVERSE, "and everything?"); - LUMIERA_ERROR_DEFINE(DERIVED, "convoluted exception"); + LUMIERA_ERROR_DECLARE(DERIVED); + LUMIERA_ERROR_DEFINE (DERIVED, "convoluted exception"); - /** declare a specific Error class with parent class error::external */ - LUMIERA_EXCEPTION_DECLARE (DerivedError, error::External, LUMIERA_ERROR_DERIVED); + /** define a specific Error subclass derived from error::external */ + using DerivedError = error::LumieraError; /******************************************************//** * Some aspects of C++ exception handling. - * Not to be confused with the basic C-style error value + * Not to be confused with the basic C-style error value * mechanism used by the low-level parts of the backend. - * Both approaches are laregely orthogonal, but the + * Both approaches are largely orthogonal, but the * C++ exception handling uses the C-style error constants. * */ @@ -71,8 +69,8 @@ namespace lumiera { typedef ExceptionError_test test; virtual void run (Arg arg) { - if (0 < arg.size() && arg[1]=="terminate") - terminateUnknown (); + if (0 < arg.size() and arg[1]=="terminate") + terminateUnknown(); catcher (&test::throwSpecial, ""); @@ -109,7 +107,7 @@ namespace lumiera { * which we are able to catch because it is derived from std::exception. * We don't need to know the exact type, but we can classify the error situation * as a "state error" and throw an error::State, passing on the root cause. - * Some levels up, this error get caught and the root cause can be + * Some levels up, this error get caught and the root cause can be * extracted successfully. */ void nestedThrower (string msg) @@ -117,7 +115,7 @@ namespace lumiera { try { throwExternal(msg); } catch (std::exception& e) { - cout << "intermediate handler caught: " << e.what() + cout << "intermediate handler caught: " << e.what() << "....will rethrow as error::State\n"; throw error::State (e); } @@ -142,25 +140,25 @@ namespace lumiera { void checkErrorIntegration() { lumiera_error (); - CHECK (!lumiera_error ()); + CHECK (not lumiera_error()); Error err1; - Error err2("boo",LUMIERA_ERROR_DERIVED); + Error err2("boo",LERR_(DERIVED)); CHECK (err1.getID () == lumiera_error ()); // (we didn't clear the first one!) - Error err3("boooo",LUMIERA_ERROR_DERIVED); + Error err3("boooo",LERR_(DERIVED)); CHECK (err3.getID () == lumiera_error ()); SpecificError err4; - CHECK (err4.getID () == LUMIERA_ERROR_LIFE_AND_UNIVERSE); + CHECK (err4.getID () == LERR_(LIFE_AND_UNIVERSE)); CHECK (err4.getID () == lumiera_error ()); - CHECK (!lumiera_error ()); + CHECK (not lumiera_error()); } void detectErrorflag (string) { throwOnError(); } - void detectErrorflagChained (string msg) { maybeThrow(msg); } + void detectErrorflagChained (string msg) { maybeThrow (msg); } /** @test verify throwing of Exceptions @@ -168,14 +166,14 @@ namespace lumiera { */ void checkErrorFlagPropagation() { - lumiera_error_set(LUMIERA_ERROR_LIFE_AND_UNIVERSE, "what is the answer?"); + lumiera_error_set(LERR_(LIFE_AND_UNIVERSE), "what is the answer?"); CHECK (lumiera_error_peek()); catcher (&test::detectErrorflag, ""); - CHECK (LUMIERA_ERROR_LIFE_AND_UNIVERSE == lumiera_error_peek()); + CHECK (LERR_(LIFE_AND_UNIVERSE) == lumiera_error_peek()); catcher (&test::detectErrorflagChained, "the big bang"); - CHECK (LUMIERA_ERROR_LIFE_AND_UNIVERSE == lumiera_error()); + CHECK (LERR_(LIFE_AND_UNIVERSE) == lumiera_error()); } @@ -186,8 +184,8 @@ namespace lumiera { { error::Logic err1; error::Config err2(err1); - error::Config err3(err2); //note: using copy ctor behaves like chaining - Error err4(err1); // note: copy ctor + error::Config err3(err2); + Error err4(err1); // note: copy ctor std::runtime_error rerr("what a shame"); error::External err5(rerr); @@ -195,7 +193,7 @@ namespace lumiera { CHECK (err2.rootCause() == err1.what()); CHECK (err3.rootCause() == err1.what()); - CHECK (err4.rootCause() == err1.what()); + CHECK (err4.rootCause() == err1.rootCause()); // mere copy is not a root cause CHECK (err5.rootCause() == rerr.what()); CHECK (err6.rootCause() == rerr.what()); @@ -204,7 +202,7 @@ namespace lumiera { /** @test terminate the Application by throwing an undeclared exception. * this should result in the global unknown() handler to be called, - * so usually it will terminate the test run. + * so usually it will terminate the test run. * @note inside error.hpp, an initialisation hook has been installed into * AppState, causing our own unknown() handler to be installed and * invoked, which gives additional diagnostics.*/ @@ -215,20 +213,29 @@ namespace lumiera { /** a very specific Exception class - * local to this scope and with + * local to this scope and with * additional behaviour. - */ - class SpecificError : public error::Invalid + */ + class SpecificError + : public error::Invalid { - int value; + int value_; public: - SpecificError () : Invalid("don't panic",LUMIERA_ERROR_LIFE_AND_UNIVERSE), value(42) {} - int revealIt () { return value; } + SpecificError() + : error::Invalid{"don't panic", LUMIERA_ERROR_LIFE_AND_UNIVERSE} + , value_(42) + { } + + int + revealIt() + { + return value_; + } }; - /** helper: provides a bunch of catch-clauses and + /** helper: provides a bunch of catch-clauses and * runs the given member functions within */ void catcher (void (ExceptionError_test::*funky)(string), string context) diff --git a/tests/library/format-cout-test.cpp b/tests/library/format-cout-test.cpp index 19c2df69e..85bff6d85 100644 --- a/tests/library/format-cout-test.cpp +++ b/tests/library/format-cout-test.cpp @@ -49,7 +49,7 @@ namespace test { namespace { // test fixture /** opaque class without string conversion */ - class Reticent + class Reticent { uint neigh_ = 42; }; @@ -82,15 +82,25 @@ namespace test { void showTraits() { - using CharLit = typeof("literal"); - using CharPtr = typeof(const char*); - using StrCRef = typeof(string const&); - using GenNodePtr = typeof(GenNode*); - using GenNodeRef = typeof(GenNode&); + using CharLit = decltype("literal"); + using CharPtr = const char*; + using StringPtr = string *; + using StringRef = string &; + using StringRRef = string &&; + using StrConstRef = string const&; + using GenNodePtr = GenNode*; + using GenNodeRef = GenNode&; + using GenNodeRRef = GenNode&&; + ANALYSE (int); + ANALYSE (char); ANALYSE (double); + ANALYSE (int64_t); ANALYSE (string); - ANALYSE (StrCRef); + ANALYSE (StringPtr); + ANALYSE (StringRef); + ANALYSE (StringRRef); + ANALYSE (StrConstRef); ANALYSE (CharLit); ANALYSE (CharPtr) ANALYSE (Reticent) @@ -98,8 +108,9 @@ namespace test { ANALYSE (GenNode) ANALYSE (GenNodePtr) ANALYSE (GenNodeRef) + ANALYSE (GenNodeRRef) ANALYSE (P) - cout << endl; + cout << "───────────────────────────╼━━━━━━━━━━╾───────────────────────────"<` in this category, which allows us diff --git a/tests/library/format-helper-test.cpp b/tests/library/format-helper-test.cpp index eb1c46f5e..be22577ea 100644 --- a/tests/library/format-helper-test.cpp +++ b/tests/library/format-helper-test.cpp @@ -36,6 +36,7 @@ using lib::transformIterator; +using lib::iter_stl::snapshot; using lib::iter_stl::eachElm; using lib::eachNum; using util::_Fmt; @@ -157,6 +158,30 @@ namespace test { // another variant: collect arbitrary number of arguments VecS vals = stringify (short(12), 345L, "67", '8'); CHECK (vals == VecS({"12", "345", "67", "8"})); + + + // stringify can both consume (RValue-Ref) or take a copy from its source + auto nn = snapshot (eachNum (5, 10)); + CHECK (5 == *nn); + ++nn; + CHECK (6 == *nn); + + auto sn = stringify (nn); + CHECK ("6" == *sn); + ++sn; + CHECK ("7" == *sn); + CHECK (6 == *nn); + ++++nn; + CHECK (8 == *nn); + CHECK ("7" == *sn); + + sn = stringify (std::move(nn)); + CHECK ("8" == *sn); + CHECK (isnil (nn)); // was consumed by moving it into sn + ++sn; + CHECK ("9" == *sn); + ++sn; + CHECK (isnil (sn)); } diff --git a/tests/library/item-wrapper-test.cpp b/tests/library/item-wrapper-test.cpp index c8d585635..f336e066d 100644 --- a/tests/library/item-wrapper-test.cpp +++ b/tests/library/item-wrapper-test.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include #include @@ -51,6 +52,7 @@ namespace test{ using std::placeholders::_1; using std::ref; + using std::shared_ptr; using std::vector; using std::string; using std::rand; @@ -131,6 +133,7 @@ namespace test{ verifySaneInstanceHandling(); + verifySaneMoveHandling(); verifyWrappedRef (); verifyFunctionResult (); @@ -221,6 +224,60 @@ namespace test{ } + /** @test proper handling of move and rvalue references */ + void + verifySaneMoveHandling() + { + using Data = shared_ptr; + using Wrap = ItemWrapper; + + Data data{new int(12345)}; + CHECK (1 == data.use_count()); + + Wrap wrap{data}; + CHECK (2 == data.use_count()); + CHECK (12345 == **wrap); + CHECK (isSameObject (*data, **wrap)); + CHECK (!isSameObject (data, *wrap)); + + Wrap wcopy{wrap}; + CHECK (3 == data.use_count()); + + Wrap wmove{move (wcopy)}; + CHECK (3 == data.use_count()); + CHECK (not wcopy); + CHECK (wmove); + + wcopy = move(wmove); + CHECK (3 == data.use_count()); + CHECK (not wmove); + CHECK (wcopy); + + Wrap wmove2{move (data)}; + CHECK (0 == data.use_count()); + CHECK (3 == wmove2->use_count()); + CHECK (not data); + CHECK (wmove2); + CHECK (wrap); + + wmove2 = move (wcopy); + CHECK (2 == wmove2->use_count()); + CHECK (not wcopy); + CHECK (wmove2); + CHECK (wrap); + + wmove2 = move (wrap); + CHECK (1 == wmove2->use_count()); + CHECK (not wrap); + CHECK (wmove2); + + wmove2 = move (wmove); + CHECK (not wcopy); + CHECK (not wmove); + CHECK (not wmove2); + } + + /** @test verify especially that we can wrap and handle * a reference "value" in a pointer-like manner */ diff --git a/tests/library/iter-adapter-test.cpp b/tests/library/iter-adapter-test.cpp index d783a7da0..89b20101e 100644 --- a/tests/library/iter-adapter-test.cpp +++ b/tests/library/iter-adapter-test.cpp @@ -44,7 +44,7 @@ namespace lib { namespace test{ using ::Test; - using lumiera::error::LUMIERA_ERROR_ITER_EXHAUST; + using lumiera::error::LERR_(ITER_EXHAUST); using boost::lexical_cast; using util::for_each; using util::isnil; @@ -135,13 +135,17 @@ namespace test{ protected: /* ==== API for the IterAdapter ==== */ - /** Implementation of Iteration-logic: pull next element. */ + /** Implementation of Iteration-logic: pull next element. + * @remarks typically the implementation is simplistic, + * since the way this extension point is called from IterAdapter + * ensures that _`pos` is still valid_ and that the `checkPoint()` function + * is invoked immediately afterwards, allowing to adjust `pos` if necessary + */ template friend void - iterNext (const TestContainer* src, ITER& pos) + iterNext (const TestContainer*, ITER& pos) { ++pos; - checkPoint (src,pos); } /** Implementation of Iteration-logic: detect iteration end. diff --git a/tests/library/iter-cursor-test.cpp b/tests/library/iter-cursor-test.cpp index 64a1604b7..ef3b19195 100644 --- a/tests/library/iter-cursor-test.cpp +++ b/tests/library/iter-cursor-test.cpp @@ -44,7 +44,7 @@ namespace test{ using util::isnil; using std::vector; - using lumiera::error::LUMIERA_ERROR_ITER_EXHAUST; + using lumiera::error::LERR_(ITER_EXHAUST); namespace { // test fixture diff --git a/tests/library/iter-explorer-test.cpp b/tests/library/iter-explorer-test.cpp index ad7673a8b..453a84076 100644 --- a/tests/library/iter-explorer-test.cpp +++ b/tests/library/iter-explorer-test.cpp @@ -21,7 +21,34 @@ * *****************************************************/ /** @file iter-explorer-test.cpp - ** unit test \ref IterExplorer_test + ** The \ref IterExplorer_test covers and demonstrates several usage scenarios and + ** extensions built on top of the \ref lib::IterExplorer template. These introduce some + ** elements from Functional Programming, especially the _Monad Pattern_ to encapsulate + ** and isolate intricacies of evolving embedded state. At usage site, only a _state + ** transition function_ need to be provided, thereby focusing at the problem domain + ** and thus reducing complexity. + ** + ** The setup for this test relies on a demonstration example of encapsulated state: + ** a counter with start and end value, embedded into an iterator. Basically, this + ** running counter, when iterated, generates a sequence of numbers start ... end. + ** So -- conceptually -- this counting iterator can be thought to represent this + ** sequence of numbers. Note that this is a kind of abstract or conceptual + ** representation, not a factual representation of the sequence in memory. + ** The whole point is _not to represent_ this sequence in runtime state at once, + ** rather to pull and expand it on demand. Thus, all the examples demonstrate in + ** this case "build" on this sequence, they expand it into various tree-like + ** structures, without actually performing these structural operations in memory. + ** + ** All these tests work by first defining these _functional structures_, which just + ** yields an iterator entity. We get the whole structure it conceptually defines + ** only if we "pull" this iterator until exhaustion -- which is precisely what + ** the test does to verify proper operation. Real world code of course would + ** just not proceed in this way, like pulling everything from such an iterator. + ** Often, the very reason we're using such a setup is the ability to represent + ** infinite structures. Like e.g. the evaluation graph of video passed through + ** a complex processing pipeline. + ** + ** @todo as of 2017, this framework is deemed incomplete and requires more design work. ////////////////////TICKET #1116 */ @@ -30,17 +57,16 @@ #include "lib/test/test-helper.hpp" #include "lib/iter-adapter-stl.hpp" #include "lib/format-cout.hpp" +#include "lib/format-util.hpp" #include "lib/util.hpp" #include "lib/iter-explorer.hpp" +#include "lib/meta/trait.hpp" -#include -#include +#include #include #include -#include "lib/meta/trait.hpp" - namespace lib { namespace test{ @@ -48,16 +74,16 @@ namespace test{ using ::Test; using util::isnil; using util::isSameObject; - using std::string; using lib::iter_stl::eachElm; using lib::iter_explorer::ChainedIters; - using lumiera::error::LUMIERA_ERROR_ITER_EXHAUST; + using lumiera::error::LERR_(ITER_EXHAUST); + using std::string; namespace { // test substrate: simple number sequence iterator /** - * This iteration "state core" type describes + * This iteration _"state core" type_ describes * a sequence of numbers yet to be delivered. */ class State @@ -70,29 +96,29 @@ namespace test{ , e(end) { } - friend bool - checkPoint (State const& st) - { - return st.p < st.e; - } + bool + checkPoint () const + { + return p < e; + } - friend uint& - yield (State const& st) - { - return util::unConst(checkPoint(st)? st.p : st.e); - } + uint& + yield () const + { + return util::unConst (checkPoint()? p : e); + } - friend void - iterNext (State & st) - { - if (!checkPoint(st)) return; - ++st.p; - } + void + iterNext () + { + if (not checkPoint()) return; + ++p; + } }; - /** + /** * A straight ascending number sequence as basic test iterator. * The tests will dress up this source sequence in various ways. */ @@ -111,25 +137,25 @@ namespace test{ /** allow using NumberSequence in LinkedElements * (intrusive single linked list) */ - NumberSequence* next; + NumberSequence* next =nullptr; }; inline NumberSequence seq (uint end) { - return NumberSequence(end); + return NumberSequence(end); } inline NumberSequence seq (uint start, uint end) { - return NumberSequence(start, end); + return NumberSequence(start, end); } NumberSequence NIL_Sequence; - /** + /** * an arbitrary series of numbers * @note deliberately this is another type * and not equivalent to a NumberSequence, @@ -138,7 +164,8 @@ namespace test{ typedef IterQueue NumberSeries; - /** "exploration function" to generate a functional datastructure. + /** + * _"exploration function"_ to generate a functional datastructure. * Divide the given number by 5, 3 and 2, if possible. Repeatedly * applying this function yields a tree of decimation sequences, * each leading down to 1 @@ -159,15 +186,9 @@ namespace test{ */ template inline string - materialise (II ii) + materialise (II&& ii) { - std::ostringstream buff; - while (ii) - { - buff << *ii; - if (++ii) buff << "-"; - } - return buff.str(); + return util::join (std::forward (ii), "-"); } template @@ -190,27 +211,28 @@ namespace test{ - /*****************************************************************//** + /*******************************************************************//** * @test use a simple source iterator yielding numbers * to build various functional evaluation structures, - * based on the IterExplorer template. - * - the [state adapter](\ref verifyStateAdapter) iterator - * construction pattern - * - helper to [chain iterators](\ref verifyChainedIterators) - * - building [tree exploring structures](\ref verifyDepthFirstExploration) - * - the [monadic nature](\ref verifyMonadOperator) of IterExplorer - * - a [recursively self-integrating](\ref verifyRecrusiveSelfIntegration) + * based on the \ref IterExplorer template. + * - the [state adapter](\ref verifyStateAdapter() ) + * iterator construction pattern + * - helper to [chain iterators](\ref verifyChainedIterators() ) + * - building [tree exploring structures](\ref verifyDepthFirstExploration()) + * - the [monadic nature](\ref verifyMonadOperator()) of IterExplorer + * - a [recursively self-integrating](\ref verifyRecrusiveSelfIntegration()) * evaluation pattern * - * \par Explanation + * ## Explanation + * * Both this test and the IterExplorer template might be bewildering - * and cryptic, unless you know the Monad design pattern. Monads are + * and cryptic, unless you know the *Monad design pattern*. Monads are * heavily used in functional programming, actually they originate * from Category Theory. Basically, Monad is a pattern where we * combine several computation steps in a specific way; but instead * of intermingling the individual computation steps and their - * combination, the goal is to separate and isolate the mechanics - * of combination, so we can focus on the actual computation steps: + * combination, the goal is to isolate and separate the _mechanics + * of combination_, so we can focus on the actual _computation steps:_ * The mechanics of combination are embedded into the Monad type, * which acts as a kind of container, holding some entities * to be processed. The actual processing steps are then @@ -250,16 +272,15 @@ namespace test{ /** @test demonstrate the underlying solution approach of IterExplorer. - * All of the following IterExplorer flavours are built on top of - * a special iterator adapter, centred at the notion of an iterable - * state element type. The actual iterator just embodies one element - * of this state representation, and typically this element alone holds - * all the relevant state and information. Essentially this means the - * iterator is self contained. Contrast this to the more conventional - * approach of iterator implementation, where the iterator entity actually - * maintains a hidden back-link to some kind of container, which in turn - * is the one in charge of the elements yielded by the iterator. - * + * All of the following IterExplorer flavours are built on top of a + * special iterator adapter, centred at the notion of an iterable state + * element type. The actual iterator just embodies one element of this + * state representation, and typically this element alone holds all the + * relevant state and information. Essentially this means the iterator is + * _self contained_. Contrast this to the more conventional approach of + * iterator implementation, where the iterator entity actually maintains + * a hidden back-link to some kind of container, which in turn is the one + * in charge of the elements yielded by the iterator. */ void verifyStateAdapter () @@ -288,14 +309,12 @@ namespace test{ - /** @test verify a helper to chain a series of iterators into a - * "flat" result sequence. This convenience helper is built using - * IterExplorer building blocks. - * The resulting iterator \em combines and \em flattens a sequence - * of source iterators, resulting in a simple sequence accessible - * as iterator again. Here we verify the convenience / default - * implementation; it uses a STL container (actually std:deque) - * behind the scenes to keep track of all added source iterators. + /** @test verify a helper to chain a series of iterators into a "flat" result sequence. + * This convenience helper is built using IterExplorer building blocks. The resulting + * iterator _combines_ and _flattens_ a sequence of source iterators, resulting in a + * simple sequence accessible as iterator again. Here we verify the convenience + * implementation; this uses a STL container (actually std:deque) behind the scenes + * to keep track of all added source iterators. */ void verifyChainedIterators () @@ -314,7 +333,7 @@ namespace test{ CHECK (!iterChain (NIL_Sequence)); // Iterator chaining "flattens" one level of packaging - NumberSequence s9 = seq(9); + NumberSequence s9 = seq(9); ci = iterChain (s9); for ( ; s9 && ci; ++s9, ++ci ) @@ -398,12 +417,12 @@ namespace test{ - /** @test a depth-first visiting and exploration scheme - * of a tree like system, built on top of the IterExplorer monad. + /** @test a depth-first visiting and exploration scheme of a tree like system, + * built on top of the IterExplorer monad. * - * \par Test data structure + * ## Test data structure * We build a functional datastructure here, on the fly, while exploring it. - * The \c exploreChildren(m) function generates this tree like datastructure: + * The `exploreChildren(m)` function generates this tree like datastructure: * For a given number, it tries to divide by 5, 3 and 2 respectively, possibly * generating multiple decimation sequences. * @@ -416,23 +435,23 @@ namespace test{ * \endcode * This tree has no meaning in itself, beyond being an easy testbed for tree exploration schemes. * - * \par How the exploration works - * We use a pre defined Template \link DepthFirstExplorer \endlink, which is built on top of IterExplorer. - * It contains the depth-first exploration strategy in a hardwired fashion. Actually this effect is achieved - * by defining a specific way how to \em combine the results of an \em exploration -- the latter being the - * function which generates the data structure. To yield a depth-first exploration, all we have to do - * is to delve down immediately into the children, right after visiting the node itself. + * ## How the exploration works + * We use a pre defined Template \ref DepthFirstExplorer, which is built on top of IterExplorer. + * It contains the depth-first exploration strategy in a hardwired fashion. Actually this effect is + * achieved by defining a specific way how to _combine the results of an exploration_ -- the latter + * being the function which generates the data structure. To yield a depth-first exploration, all we + * have to do is to delve down immediately into the children, right after visiting the node itself. * * Now, when creating such a DepthFirstExplorer by wrapping a given source iterator, the result is again - * an iterator, but a specific iterator which at the same time is a Monad: It supports the \c >>= operation - * (also known as \em bind operator or \em flatMap operator). This operator takes as second argument a - * function, which in our case is the function to generate or explore the data structure. + * an iterator, but a specific iterator which at the same time is a Monad: It supports the `>>=` operation + * (also known as _bind_ operator or _flatMap_ operator). This operator takes as second argument a function, + * which in our case is the function to generate or explore the data structure. * - * The result of applying this \c >>= operation is a \em transformed version of the source iterator, + * The result of applying this monadic `>>=` operation is a _transformed_ version of the source iterator, * i.e. it is again an iterator, which yields the results of the exploration function, combined together * in the order as defined by the built-in exploration strategy (here: depth first) * - * @note technical detail: the result type of the exploration function (here \c exploreChildren() ) determines + * @note technical detail: the result type of the exploration function (here `exploreChildren()`) determines * the iterator type used within IterExplorer and to drive the evaluation. The source sequence used to * seed the evaluation process can actually be any iterator yielding assignment compatible values: The * second example uses a NumberSequence with unsigned int values 0..6, while the actual expansion and @@ -453,11 +472,11 @@ namespace test{ - /** @test a breadth-first visiting and exploration scheme - * of a tree like system, built on top of the IterExplorer monad; - * here, an internal queue is used to explore the hierarchy in layers. + /** @test a breadth-first visiting and exploration scheme of a tree like system, + * built on top of the IterExplorer monad. + * Here, an internal queue is used to explore the hierarchy in layers. * The (functional) datastructure is the same, just we're visiting it - * differently here (in rows or layers). + * in a different way here, namely in rows or layers. */ void verifyBreadthFirstExploration () @@ -471,20 +490,19 @@ namespace test{ /** @test verify a variation of recursive exploration, this time to rely * directly on the result set iterator type to provide the re-integration - * of intermediary results. Since our \c exploreChildren() function returns + * of intermediary results. Since our `exploreChildren()` function returns * a NumberSeries, which basically is a IterQueue, the re-integration of expanded * elements will happen at the end, resulting in breadth-first visitation order -- - * but contrary to the dedicated \c breadthFirst(..) explorer, this expansion is done + * but contrary to the dedicated `breadthFirst(..)` explorer, this expansion is done * separately for each element in the initial seed sequence. Note for example how the - * expansion series for number 30, which is also generated in - * \link #verifyBreadthFirstExploration \endlink, appears here at the end of the - * explorationResult sequence + * expansion series for number 30, which is also generated in verifyBreadthFirstExploration(), + * appears here at the end of the explorationResult sequence * @remarks this "combinator strategy" is really intended for use with custom sequences, * where the "Explorer" function works together with a specific implementation * and exploits knowledge about specifically tailored additional properties of - * the input sequence elements to yield the desired overall effect. - * Actually this is what we use in the proc::engine::Dispatcher to generate a - * series of frame render jobs, including all prerequisite jobs + * the input sequence elements, in order to yield the desired overall effect. + * Actually this is what we use in the proc::engine::Dispatcher to generate + * a series of frame render jobs, including all prerequisite jobs */ void verifyRecursiveSelfIntegration () @@ -501,14 +519,16 @@ namespace test{ /** @test cover the basic monad bind operator, - * which is used to build all the specialised Iterator flavours. - * The default implementation ("Combinator strategy") just joins and flattens - * the result sequences created by the functor bound into the monad. For this test, - * we use a functor \c explode(top), which returns the sequence 0...top. + * which is used to build all the specialised Iterator flavours. + * The default implementation ("Combinator strategy") just joins and flattens the result sequences + * created by the functor bound into the monad. For this test, we use a functor `explode(top)`, + * which returns the sequence 0...top. */ void verifyMonadOperator () { + auto explode = [](uint top) { return seq(0,top); }; + // IterExplorer as such is an iterator wrapping the source sequence string result = materialise (exploreIter(seq(5))); CHECK (result == "0-1-2-3-4"); @@ -517,7 +537,7 @@ namespace test{ result = materialise (exploreIter(seq(5,6))); CHECK (result == "5"); - // then binding the explode()-Function yields just the result of invoking explode(5) + // then binding the explode()-Function yields just the result of invoking explode(5) result = materialise (exploreIter(seq(5,6)) >>= explode); CHECK (result == "0-1-2-3-4"); @@ -550,13 +570,6 @@ namespace test{ // Note: when cascading multiple >>= the parentheses are necessary, since in C++ unfortunately // the ">>=" associates to the right, while the proper monad bind operator should associate to the left } - - /** @internal exploration function used in ::verifyMonadOperator */ - static NumberSequence - explode (uint top) - { - return seq(0,top); - } }; diff --git a/tests/library/iter-queue-test.cpp b/tests/library/iter-queue-test.cpp index 1000fe573..52476dda8 100644 --- a/tests/library/iter-queue-test.cpp +++ b/tests/library/iter-queue-test.cpp @@ -39,7 +39,7 @@ namespace test{ using ::Test; using util::isnil; - using lumiera::error::LUMIERA_ERROR_ITER_EXHAUST; + using lumiera::error::LERR_(ITER_EXHAUST); diff --git a/tests/library/iter-source-test.cpp b/tests/library/iter-source-test.cpp index a5740a199..c800a6370 100644 --- a/tests/library/iter-source-test.cpp +++ b/tests/library/iter-source-test.cpp @@ -29,12 +29,12 @@ #include "lib/test/run.hpp" #include "lib/test/test-helper.hpp" #include "lib/format-cout.hpp" +#include "lib/nocopy.hpp" #include "lib/util.hpp" #include "lib/iter-source.hpp" #include -#include #include #include #include @@ -48,7 +48,6 @@ namespace test{ using ::Test; using boost::lexical_cast; - using boost::noncopyable; using lib::time::TimeVar; using lib::test::randStr; using lib::test::randTime; @@ -59,6 +58,16 @@ namespace test{ using std::list; using std::rand; + using lumiera::error::LERR_(ITER_EXHAUST); + + using iter_source::eachEntry; + using iter_source::transform; + using iter_source::singleVal; + using iter_source::eachMapKey; + using iter_source::eachMapVal; + using iter_source::eachValForKey; + using iter_source::eachDistinctKey; + namespace { // Subject of test @@ -74,14 +83,14 @@ namespace test{ */ class TestSource : public IterSource - , noncopyable + , util::NonCopyable { string buffer_; CStr current_; - virtual Pos - firstResult () + virtual Pos ////////////////////////////////////////////TICKET #1125 : this iteration control API should use three control functions, similar to IterStateWrapper + firstResult () override { current_ = buffer_.c_str(); ENSURE (current_); @@ -89,7 +98,7 @@ namespace test{ } virtual void - nextResult (Pos& pos) + nextResult (Pos& pos) override { if (pos && *pos && **pos) ++(*pos); @@ -132,6 +141,16 @@ namespace test{ }; + + /** diagnostics helper */ + template + inline void + pullOut (IT& iter) + { + for ( ; iter; ++iter ) + cout << "::" << *iter; + cout << endl; + } } // (END) impl test dummy containers @@ -207,11 +226,25 @@ namespace test{ } + /** @test verify transforming an embedded iterator + * This test not only wraps a source iterator and packages it behind the + * abstracting interface IterSource, but in addition also applies a function + * to each element yielded by the source iterator. As demo transformation + * we use the values from our custom container (\ref WrappedList) to build + * a time value in quarter seconds + * + */ void verify_transformIter() { WrappedList customList(NUM_ELMS); - WrappedList::iterator sourceValues = customList.begin(); + WrappedList::iterator sourceValues = customList.begin(); + + // transformation function + auto makeTime = [](int input_sec) -> TimeVar + { + return time::Time (time::FSecs (input_sec, 4)); + }; TimeIter tIt (transform (sourceValues, makeTime)); CHECK (!isnil (tIt)); @@ -219,27 +252,39 @@ namespace test{ CHECK (!tIt); } - /** transformation function, to be applied for each element: - * just build a time value, using the input as 1/4 seconds - */ - static TimeVar - makeTime (int input_sec) - { - return time::Time (time::FSecs (input_sec, 4)); - } - - - template + /** @test an IterSouce which returns just a single value once */ void - pullOut (IT& iter) + verify_singleValIter() { - for ( ; iter; ++iter ) - cout << "::" << *iter; - cout << endl; + int i{-9}; + + IntIter ii = singleVal(12); + CHECK (not isnil(ii)); + CHECK (12 == *ii); + + ++ii; + CHECK (isnil (ii)); + VERIFY_ERROR (ITER_EXHAUST, *ii ); + + // IterSource is an abstracting interface, + // thus we're able to reassign an embedded iterator + // with a different value type (int& instead of int) + ii = singleVal(i); + + CHECK (not isnil(ii)); + CHECK (-9 == *ii); + + // NOTE: since we passed a reference, a reference got wrapped + i = 23; + CHECK (23 == *ii); + ++ii; + CHECK (isnil (ii)); } + + template void verify_MapWrappers() diff --git a/tests/library/iter-stack-test.cpp b/tests/library/iter-stack-test.cpp index 3be69c5c8..2457bcee2 100644 --- a/tests/library/iter-stack-test.cpp +++ b/tests/library/iter-stack-test.cpp @@ -39,7 +39,7 @@ namespace test{ using ::Test; using util::isnil; - using lumiera::error::LUMIERA_ERROR_ITER_EXHAUST; + using lumiera::error::LERR_(ITER_EXHAUST); diff --git a/tests/library/iter-tree-explorer-test.cpp b/tests/library/iter-tree-explorer-test.cpp new file mode 100644 index 000000000..3425789c6 --- /dev/null +++ b/tests/library/iter-tree-explorer-test.cpp @@ -0,0 +1,1169 @@ +/* + IterTreeExplorer(Test) - verify tree expanding and backtracking iterator + + Copyright (C) Lumiera.org + 2017, Hermann Vosseler + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +* *****************************************************/ + +/** @file iter-tree-explorer-test.cpp + ** The \ref IterTreeExplorer_test covers and demonstrates a generic mechanism + ** to expand and evaluate tree like structures. In its current shape (as of 2017), + ** it can be seen as an preliminary step towards retrofitting IterExplorer into + ** a framework of building blocks for tree expanding and backtracking evaluations. + ** Due to the nature of Lumiera's design, we repeatedly encounter this kind of + ** algorithms, when it comes to matching configuration and parametrisation against + ** a likewise hierarchical and rules based model. To keep the code base maintainable, + ** we deem it crucial to reduce the inherent complexity in such algorithms by clearly + ** separate the _mechanics of evaluation_ from the actual logic of the target domain. + ** + ** Similar to IterExplorer_test, the his test relies on a demonstration setup featuring + ** a custom encapsulated state type: we rely on a counter with start and end value, + ** embedded into an iterator. Basically, this running counter, when iterated, generates + ** a descending sequence of numbers start ... end. + ** So -- conceptually -- this counting iterator can be thought to represent this + ** sequence of numbers. Note that this is a kind of abstract or conceptual + ** representation, not a factual representation of the sequence in memory. + ** The whole point is _not to represent_ this sequence in runtime state at once, + ** rather to pull and expand it on demand. + ** + ** All these tests work by first defining these _functional structures_, which just + ** yields an iterator entity. We get the whole structure it conceptually defines + ** only if we "pull" this iterator until exhaustion -- which is precisely what + ** the test does to verify proper operation. Real world code of course would + ** just not proceed in this way, like pulling everything from such an iterator. + ** Often, the very reason we're using such a setup is the ability to represent + ** infinite structures. Like e.g. the evaluation graph of video passed through + ** a complex processing pipeline. + */ + + + +#include "lib/test/run.hpp" +#include "lib/test/test-helper.hpp" +#include "lib/iter-adapter-stl.hpp" +#include "lib/format-string.hpp" +#include "lib/format-cout.hpp" +#include "lib/format-util.hpp" +#include "lib/itertools.hpp" +#include "lib/util.hpp" + +#include "lib/iter-tree-explorer.hpp" +#include "lib/meta/trait.hpp" + +#include +#include +#include +#include +#include + + +namespace lib { +namespace test{ + + using ::Test; + using util::_Fmt; + using util::isnil; + using util::isSameObject; + using lib::iter_stl::eachElm; + using lumiera::error::LERR_(ITER_EXHAUST); + using std::vector; + using std::string; + + + namespace { // test substrate: simple number sequence iterator + + /** + * This iteration _"state core" type_ describes + * a descending sequence of numbers yet to be delivered. + */ + struct CountDown + { + uint p,e; + + CountDown(uint start =0, uint end =0) + : p(start) + , e(end) + { } + + bool + checkPoint () const + { + return p > e; + } + + uint& + yield () const + { + return util::unConst (checkPoint()? p : e); + } + + void + iterNext () + { + if (not checkPoint()) return; + --p; + } + }; + + + /** + * A straight descending number sequence as basic test iterator. + * It is built wrapping an opaque "state core" (of type CountDown). + * @note the "state core" is not accessible from the outside + */ + class NumberSequence + : public IterStateWrapper + { + + public: + explicit + NumberSequence(uint start = 0) + : IterStateWrapper (CountDown{start}) + { } + NumberSequence(uint start, uint end) + : IterStateWrapper (CountDown(start,end)) + { } + }; + + + + /** + * Another iteration _"state core"_ to produce a sequence of random numbers. + * Used to build an infinite random search space... + */ + class RandomSeq + { + size_t lim_; + size_t cnt_; + char letter_; + + static char + rndLetter() + { + return 'A' + rand() % 26; + } + + public: + RandomSeq(int len =0) + : lim_{len>=0? len : std::numeric_limits::max()} + , cnt_{0} + , letter_{rndLetter()} + { } + + bool + checkPoint () const + { + return cnt_ < lim_; + } + + char& + yield () const + { + return unConst(this)->letter_; + } + + void + iterNext () + { + ASSERT (checkPoint()); + ++cnt_; + letter_ = rndLetter(); + } + }; + + + /** Diagnostic helper: join all the elements from a _copy_ of the iterator */ + template + inline string + materialise (II&& ii) + { + return util::join (std::forward (ii), "-"); + } + + /** Diagnostic helper: "squeeze out" the given iterator until exhaustion */ + template + inline void + pullOut (II & ii) + { + while (ii) + { + cout << *ii; + if (++ii) cout << "-"; + } + cout << endl; + } + + } // (END) test helpers + + + + + + + + /*******************************************************************//** + * @test use a simple source iterator yielding numbers + * to build various functional evaluation structures, + * based on the \ref IterExplorer template. + * - the [state adapter](\ref verifyStateAdapter() ) + * iterator construction pattern + * - helper to [chain iterators](\ref verifyChainedIterators() ) + * - building [tree exploring structures](\ref verifyDepthFirstExploration()) + * - the [monadic nature](\ref verifyMonadOperator()) of IterExplorer + * - a [recursively self-integrating](\ref verifyRecrusiveSelfIntegration()) + * evaluation pattern + * + * ## Explanation + * + * Both this test and the IterExplorer template might be bewildering + * and cryptic, unless you know the *Monad design pattern*. »Monads« + * are heavily used in functional programming, actually they originate + * from Category Theory. Basically, Monad is a pattern where we combine + * several computation steps in a specific way; but instead of intermingling + * the individual computation steps and their combination, the goal is to + * isolate and separate the _mechanics of combination_, so we can focus on + * the actual _computation steps:_ The mechanics of combination are embedded + * into the Monad type, which acts as a kind of container, holding some entities + * to be processed. The actual processing steps are then attached to the monad as + * "function object" parameters. It is up to the monad to decide if, and when + * those processing steps are applied to the embedded values and how to combine + * the results into a new monad. + * + * Using the monad pattern is well suited when both the mechanics of + * combination and the individual computation steps tend to be complex. + * In such a situation, it is beneficial to develop and test both + * in isolation. The IterExplorer template applies this pattern + * to the task of processing a source sequence. Typically we use + * this in situations where we can't afford building elaborate + * data structures in (global) memory, but rather strive at + * doing everything on-the-fly. A typical example is the + * processing of a variably sized data set without + * using heap memory for intermediary results. + * + * @see TreeExplorer + * @see IterAdapter + */ + class IterTreeExplorer_test : public Test + { + + virtual void + run (Arg) + { + verify_wrappedState(); + verify_wrappedIterator(); + + verify_expandOperation(); + verify_transformOperation(); + verify_combinedExpandTransform(); + verify_scheduledExpansion(); + verify_FilterIterator(); + verify_asIterSource(); + verify_IterSource(); + + verify_depthFirstExploration(); + demonstrate_LayeredEvaluation(); + } + + + + /** @test without using any extra functionality, + * TreeExplorer just wraps an iterable state. + */ + void + verify_wrappedState() + { + auto ii = treeExplore (CountDown{5,0}); + CHECK (!isnil (ii)); + CHECK (5 == *ii); + ++ii; + CHECK (4 == *ii); + pullOut(ii); + CHECK ( isnil (ii)); + CHECK (!ii); + + VERIFY_ERROR (ITER_EXHAUST, *ii ); + VERIFY_ERROR (ITER_EXHAUST, ++ii ); + + ii = treeExplore (CountDown{5}); + CHECK (materialise(ii) == "5-4-3-2-1"); + ii = treeExplore (CountDown{7,4}); + CHECK (materialise(ii) == "7-6-5"); + ii = treeExplore (CountDown{}); + CHECK ( isnil (ii)); + CHECK (!ii); + } + + + /** @test TreeExplorer is able to wrap any _Lumiera Forward Iterator_ */ + void + verify_wrappedIterator() + { + vector numz{1,-2,3,-5,8,-13}; + auto ii = eachElm(numz); + CHECK (!isnil (ii)); + CHECK (1 == *ii); + ++ii; + CHECK (-2 == *ii); + + auto jj = treeExplore(ii); + CHECK (!isnil (jj)); + CHECK (-2 == *jj); + ++jj; + CHECK (3 == *jj); + + // we passed a LValue-Ref, thus a copy was made + CHECK (-2 == *ii); + + CHECK (materialise(ii) == "-2-3--5-8--13"); + CHECK (materialise(jj) == "3--5-8--13"); + + // can even adapt STL container automatically + auto kk = treeExplore(numz); + CHECK (!isnil (kk)); + CHECK (1 == *kk); + CHECK (materialise(kk) == "1--2-3--5-8--13"); + } + + + + /** @test use a preconfigured "expand" functor to recurse into children. + * The `expand()` builder function predefines a way how to _expand_ the current + * head element of the iteration. However, expansion does not happen automatically, + * rather, it needs to be invoked by the client, similar to increment of the iterator. + * When expanding, the current head element is consumed and fed into the expand functor; + * the result of this functor invocation is injected instead into the result sequence, + * and consequently this result needs to be again an iterable with compatible value type. + * Conceptually, the evaluation _forks into the children of the expanded element_, before + * continuing with the successor of the expansion point. Obviously, expansion can be applied + * again on the result of the expansion, possibly leading to a tree of side evaluations. + * + * The expansion functor may be defined in various ways and will be adapted appropriately + * - it may follow the classical "monadic pattern", i.e. take individual _values_ and return + * a _"child monad"_, which is then "flat mapped" (integrated) into the resulting iteration + * - the resulting child collection may be returned as yet another iterator, which is then + * moved by the implementation into the stack of child sequences currently in evaluation + * - or alternatively the resulting child collection may be returned just as a "state core", + * which can be adapted into a _iterable state_ (see lib::IterStateWrapper). + * - or it may even return the reference to a STL collection existing elsewhere, + * which will then be iterated to yield the child elements + * - and, quite distinct to the aforementioned "monadic" usage, the expansion functor + * may alternatively be written in a way as to collaborate with the "state core" used + * when building the TreeExplorer. In this case, the functor typically takes a _reference_ + * to this underlying state core or iterator. The purpose for this definition variant is + * to allow exploring a tree-like evaluation, without the need to disclose anything about + * the backing implementation; the expansion functor just happens to know the implementation + * type of the "state core" and manipulate it through its API to create a "derived core" + * representing a _child evaluation state_. + * - and finally, there is limited support for _generic lambdas._ In this case, the implementation + * will try to instantiate the passed lambda by using the concrete source iterator type as argument. + * + * @note expansion functor may use side-effects and indeed return something entirely different + * than the original sequence, as long as it is iterable and yields compatible values. + */ + void + verify_expandOperation() + { + /* == "monadic flatMap" == */ + + verify_treeExpandingIterator( + treeExplore(CountDown{5}) + .expand([](uint j){ return CountDown{j-1}; }) // expand-functor: Val > StateCore + ); + + verify_treeExpandingIterator( + treeExplore(CountDown{5}) + .expand([](uint j){ return NumberSequence{j-1}; }) // expand-functor: Val > Iter + ); // NOTE: different Iterator type than the source! + + // lambda with side-effect and return type different from source iter + vector> childBuffer; + auto expandIntoChildBuffer = [&](uint j) -> vector& + { + childBuffer.emplace_back(); + vector& childNumbz = childBuffer.back(); + for (size_t i=0; i STL-container& + ); + + // test routine called the expansion functor five times + CHECK (5 == childBuffer.size()); + + + + /* == "state manipulation" use cases == */ + + verify_treeExpandingIterator( + treeExplore(CountDown{5}) + .expand([](CountDown const& core){ return CountDown{ core.yield() - 1}; }) // expand-functor: StateCore const& -> StateCore + ); + + verify_treeExpandingIterator( + treeExplore(CountDown{5}) + .expand([](CountDown core){ return NumberSequence{ core.yield() - 1}; }) // expand-functor: StateCore -> Iter + ); + +#if false /////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1118 : GDB Segfault on loading the inferior + /////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1118 : Generated code works just fine and passes Test though + verify_treeExpandingIterator( + treeExplore(CountDown{5}) + .expand([](auto & it){ return CountDown{ *it - 1}; }) // generic Lambda: Iter& -> StateCore + ); + + verify_treeExpandingIterator( + treeExplore(CountDown{5}) + .expand([](auto it){ return decltype(it){ *it - 1}; }) // generic Lambda: Iter -> Iter + ); +#endif /////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1118 : GDB Segfault on loading the inferior + } + + + template + void + verify_treeExpandingIterator(EXP ii) + { + CHECK (!isnil (ii)); + CHECK (5 == *ii); + ++ii; + CHECK (4 == *ii); + + CHECK (0 == ii.depth()); + ii.expandChildren(); + CHECK (3 == *ii); + CHECK (1 == ii.depth()); + ++ii; + CHECK (2 == *ii); + CHECK (1 == ii.depth()); + ii.expandChildren(); + CHECK (1 == *ii); + CHECK (2 == ii.depth()); + ++ii; + CHECK (1 == *ii); + CHECK (1 == ii.depth()); + ++ii; + CHECK (3 == *ii); + CHECK (0 == ii.depth()); + CHECK (materialise(ii) == "3-2-1"); + ii.expandChildren(); + CHECK (1 == ii.depth()); + CHECK (materialise(ii) == "2-1-2-1"); + ++++ii; + CHECK (0 == ii.depth()); + CHECK (materialise(ii) == "2-1"); + ii.expandChildren(); + CHECK (1 == ii.depth()); + CHECK (materialise(ii) == "1-1"); + ++ii; + CHECK (0 == ii.depth()); + CHECK (1 == *ii); + CHECK (materialise(ii) == "1"); + ii.expandChildren(); + CHECK (isnil (ii)); + VERIFY_ERROR (ITER_EXHAUST, *ii ); + VERIFY_ERROR (ITER_EXHAUST, ++ii ); + } + + + /** @test pipe each result through a transformation function. + * The _transforming iterator_ is added as a decorator, wrapping the original iterator, + * TreeEplorer or state core. As you'd expect, the given functor is required to accept + * compatible argument types, and a generic lambda is instantiated to take a reference + * to the embedded iterator's value type. Several transformation steps can be chained, + * and the resulting entity is again a Lumiera Forward Iterator with suitable value type. + * The transformation function is invoked only once per step and the result produced by + * this invocation is placed into a holder buffer embedded within the iterator. + * @note since the implementation uses the same generic adaptor framework, + * the transformation functor may be defined with the same variations + * as described for the expand-operation above. In theory, it might + * collaborate with the embedded "state core" type, thereby possibly + * bypassing other decorators added below. + * @warning don't try this at home + */ + void + verify_transformOperation() + { + auto multiply = [](int v){ return 2*v; }; // functional map: value -> value + + _Fmt embrace{"≺%s≻"}; + auto formatify = [&](auto it){ return string{embrace % *it}; }; // generic lambda: assumed to take an Iterator& + + + auto ii = treeExplore (CountDown{7,4}) + .transform(multiply) + ; + + CHECK (14 == *ii); + ++ii; + CHECK (12 == *ii); + ++ii; + CHECK (10 == *ii); + ++ii; + CHECK (isnil (ii)); + VERIFY_ERROR (ITER_EXHAUST, *ii ); + VERIFY_ERROR (ITER_EXHAUST, ++ii ); + + + + // demonstrate chaining of several transformation layers + vector numz{1,-2,3,-5,8,-13}; + + CHECK ("≺1≻-≺-2≻-≺3≻-≺-5≻-≺8≻-≺-13≻" == materialise (treeExplore(numz) + .transform(formatify)) ); + + CHECK ("≺2≻-≺-4≻-≺6≻-≺-10≻-≺16≻-≺-26≻" == materialise (treeExplore(numz) + .transform(multiply) + .transform(formatify)) ); + + CHECK ("≺≺4≻≻-≺≺-8≻≻-≺≺12≻≻-≺≺-20≻≻-≺≺32≻≻-≺≺-52≻≻" == materialise (treeExplore(numz) + .transform(multiply) + .transform(multiply) + .transform(formatify) + .transform(formatify)) ); + + + // demonstrate the functor is evaluated only once per step + int fact = 3; + + auto jj = treeExplore (CountDown{4}) + .transform([&](int v) + { + v *=fact; + fact *= -2; + return v; + }); + CHECK (3*4 == *jj); + CHECK (fact == -2*3); + + CHECK (3*4 == *jj); + CHECK (3*4 == *jj); + + ++jj; + CHECK (fact == -2*3); // NOTE : functor is evaluated on first demand + CHECK (-2*3*3 == *jj); // ...which happens on yield (access the iterator value) + CHECK (fact == 2*2*3); // and this also causes the side-effect + CHECK (-2*3*3 == *jj); + CHECK (-2*3*3 == *jj); + CHECK (fact == 2*2*3); // no further evaluation and thus no further side-effect + + ++jj; + CHECK (2*2*3*2 == *jj); + CHECK (fact == -2*2*2*3); + + fact = -23; + CHECK (2*2*3*2 == *jj); + + ++jj; + CHECK (fact == -23); + CHECK (-23*1 == *jj); + CHECK (fact == 2*23); + + ++jj; + CHECK (isnil (jj)); + CHECK (fact == 2*23); + + VERIFY_ERROR (ITER_EXHAUST, *ii ); + CHECK (fact == 2*23); // exhaustion detected on source and thus no further evaluation + + + + // demonstrate a transformer accessing the source state core... + // should not be relevant in practice, but works due to the generic adapters + auto kk = treeExplore(CountDown{9,4}) + .transform([](CountDown& core) + { + uint delta = core.p - core.e; + if (delta % 2 == 0) + --core.p; // EVIL EVIL + return delta; + }); + + CHECK (5 == *kk); // the delta between 9 (start) and 4 (end) + ++kk; + CHECK (4 == *kk); // Core manipulated by SIDE-EFFECT at this point... + CHECK (4 == *kk); // ...but not yet obvious, since the result is cached + ++kk; + CHECK (2 == *kk); // Surprise -- someone ate my numberz... + ++kk; + CHECK (isnil (kk)); + } + + + /** @test combine the recursion into children with a tail mapping operation. + * Wile basically this is just the layering structure of TreeExplorer put into action, + * you should note one specific twist: the iter_explorer::Expander::expandChildren() call + * is meant to be issued from ``downstream'', from the consumer side. Yet the consumer at + * that point might well see the items as processed by a transforming step layered on top. + * So what the consumer sees and thinks will be expanded need not actually be what will + * be processed by the _expand functor_. This may look like a theoretical or cosmetic + * issue -- yet in fact it is this tiny detail which is crucial to make abstraction of + * the underlying data source actually work in conjunction with elaborate searching and + * matching algorithms. Even more so, when other operations like filtering are intermingled; + * in that case it might even happen that the downstream consumer does not even see the + * items resulting from child expansion, because they are evaluated and then filtered + * away by a transformer and filter placed in between. + * @note as a consequence of the flexible automatic adapting of bound functors, it is + * possible for bound functors within different "layers" to collaborate, based on + * additional knowledge regarding the embedded data source internals. This test + * demonstrates a transform functor, which takes the _source iterator_ as argument + * and invokes `it.expandChildren()` to manipulate the underlying evaluation. + * However, since the overall evaluation is demand driven, there are inherent + * limitations to such a setup, which bends towards fragility when leaving + * the realm of pure functional evaluation. + */ + void + verify_combinedExpandTransform() + { + auto ii = treeExplore(CountDown{5}) + .expand([](uint j){ return CountDown{j-1}; }) + .transform([](int v){ return 2*v; }) + ; + + CHECK ("int" == meta::typeStr(*ii)); // result type is what the last transformer yields + CHECK (10 == *ii); + ++ii; + CHECK (8 == *ii); + ii.expandChildren(); + CHECK ("6-4-2-6-4-2" == materialise(ii) ); + + + // the following contrived example demonstrates + // how intermediary processing steps may interact + + CHECK (materialise ( + treeExplore(CountDown{5}) + .expand([](uint j){ return CountDown{j-1}; }) + .transform([](int v){ return 2*v; }) + .transform([](auto& it) + { + auto elm = *it; + if (elm == 6) + { + it.expandChildren(); // NOTE at that point we're forced to decide if + elm = *it * 10; // we want to return the parent or the 1st child + } + return elm; + }) + .transform([](float f){ return 0.055 + f/2; }) + ) + == "5.055-4.055-20.055-1.055-2.055-1.055" ); + } + + + + /** @test child expansion can be scheduled to happen on next iteration. + * As such, _"child expansion"_ happens right away, thereby consuming a node + * and replacing it with its child sequence. Sometimes, when building search and matching + * algorithms, we rather just want to _plan_ a child expansion to happen on next increment. + * Such is especially relevant when searching for a locally or global maximal solution, which + * is rather simple to implement with an additional filtering layer -- and this approach requires + * us to deliver all partial solutions for the filter layer to act on. Obviously this functionality + * leads to additional state and thus is provided as optional layer in the TreeExplorer builder. + */ + void + verify_scheduledExpansion() + { + auto ii = treeExplore(CountDown{6}) + .expand([](uint j){ return CountDown{j-2}; }) + .expandOnIteration(); + + CHECK (!isnil (ii)); + CHECK (6 == *ii); + ++ii; + CHECK (5 == *ii); + CHECK (ii.depth() == 0); + + ii.expandChildren(); + CHECK (5 == *ii); + CHECK (ii.depth() == 0); + ++ii; + CHECK (3 == *ii); + CHECK (ii.depth() == 1); + + ii.expandChildren(); + ii.expandChildren(); + CHECK (ii.depth() == 1); + CHECK (3 == *ii); + ++ii; + CHECK (1 == *ii); + CHECK (ii.depth() == 2); + ++ii; + CHECK (2 == *ii); + CHECK (ii.depth() == 1); + + ii.expandChildren(); + ++ii; + CHECK (1 == *ii); + CHECK (ii.depth() == 1); + ++ii; + CHECK (4 == *ii); + CHECK (ii.depth() == 0); + ++ii; + CHECK (3 == *ii); + ++ii; + CHECK (2 == *ii); + ++ii; + CHECK (1 == *ii); + ++ii; + CHECK (isnil (ii)); + } + + + + /** @test add a filtering predicate into the pipeline. + * As in all the previously demonstrated cases, also the _filtering_ is added as decorator, + * wrapping the source and all previously attached decoration layers. And in a similar way, + * various kinds of functors can be bound, and will be adapted automatically to work as a + * predicate to approve the elements to yield. + */ + void + verify_FilterIterator() + { + // canonical example, using a clean side-effect free predicate based on element values + CHECK (materialise ( + treeExplore(CountDown{10}) + .filter([](uint j){ return j % 2; }) + ) + == "9-7-5-3-1"); + + + // Filter may lead to consuming util exhaustion... + auto ii = treeExplore(CountDown{10}) + .filter([](int j){ return j > 9; }); + + CHECK (not isnil (ii)); + CHECK (10 == *ii); + ++ ii; + CHECK (isnil (ii)); + VERIFY_ERROR (ITER_EXHAUST, ++ii ); + + + // none of the source elements can be approved here... + auto jj = treeExplore(CountDown{5}) + .filter([](int j){ return j > 9; }); + + CHECK (isnil (jj)); + + + + // a tricky example, where the predicate takes the source core as argument; + // since the source core is embedded as baseclass, it can thus "undermine" + // and bypass the layers configured in between; here the transformer changes + // uint to float, but the filter interacts directly with the core and thus + // judges based on the original values + CHECK (materialise ( + treeExplore(CountDown{10,4}) + .transform([](float f){ return 0.55 + 2*f; }) + .filter([](CountDown& core){ return core.p % 2; }) + ) + == "18.55-14.55-10.55"); + + + + // another convoluted example to demonstrate + // - a filter predicate with side-effect + // - and moreover the predicate is a generic lambda + // - accepting the iterator to trigger child expansion + // - which also causes re-evaluation of the preceding transformer + bool toggle = false; + auto kk = treeExplore(CountDown{10,5}) + .expand([](uint j){ return CountDown{j-1}; }) + .transform([](int v){ return 2*v; }) + .filter([&](auto& it) + { + if (*it == 16) + { + it.expandChildren(); + toggle = true; + } + return toggle; + }); + + CHECK (materialise(kk) + == "14-12-10-8-6-4-2-14-12"); + // Explanation: + // The source starts at 10, but since the toggle is false, + // none of the initial values makes it though to the result. + // The interspersed transformer doubles the source values, and + // thus at source == 8 the trigger value (16) is hit. Thus the + // filter now flips the context-bound toggle (side-effect) and + // then expands children, which consumes current source value 8 + // to replace it with the sequence 7,6,5,4,3,2,1, followed by + // the rest of the original sequence, 7,6 (which stops above 5). + + CHECK (materialise(kk.filter([](long i){ return i % 7; })) + == "12-10-8-6-4-2-12"); + // Explanation: + // Since the original TreeExplorer was assigned to variable kk, + // the materialise()-Function got a lvalue-ref and thus made a copy + // of the whole compound. For that reason, the original state within + // kk still rests at 7 -- because the filter evaluates eagerly, the + // source was pulled right at construction until we reached the first + // value to yield, which is the first child (7,....) within the + // expanded sequence. But now, in the second call to materialise(), + // we don't just copy, rather we add another filter layer on top, + // which happens to filter away this first result (== 2*7), and + // also the first element of the original sequence after the + // expanded children + + // WARNING: kk is now defunct, since we moved it into the builder expression + // and then moved the resulting extended iterator into materialise! + } + + + + /** @test package the resulting Iterator as automatically managed, + * polymorphic opaque entity implementing the IterSource interface. + * The builder operations on TreeExplorer each generate a distinct, implementation + * defined type, which is meant to be captured by `auto`. However, the terminal builder + * function `asIterSource()` moves the whole compound iterator object, as generated by + * preceding builder steps, into a heap allocation and exposes a simplified front-end, + * which is only typed to the result value type. Obviously, the price to pay comes in + * terms of virtual function calls for iteration, delegating to the pipeline backend. + * - thus a variable typed to that front-end, `IterSource` is polymorphic and + * can be reassigned at runtime with an entirely different pipeline. + * - but this structure also has the downside, that the implementation no longer + * resides directly within the iterator: several front-end copies share the + * same back-end. Note however that the behaviour of iterators copied this + * way is _implementation defined_ anyway. There is never a guarantee that + * a clone copy evolves with state independent from its ancestor; it just + * happens to work this way in many simple cases. You should never use + * more than one copy of a given iterator at any time, and you should + * discard it, when done with iteration. + * - actually, the returned front-end offers an extended API over plain vanilla + * `IterSource::iterator`, to expose the `expandChildren()` operation. + */ + void + verify_asIterSource() + { + IterSource::iterator sequence; // note `sequence` is polymorphic + CHECK (isnil (sequence)); + + sequence = treeExplore(CountDown{20,10}) + .filter([](uint i){ return i % 2; }) + .asIterSource(); // note this terminal builder function + // moves the whole pipeline onto the heap + CHECK (not isnil (sequence)); + CHECK (19 == *sequence); + + + // use one sequence as source to build another one + sequence = treeExplore(sequence) + .transform([](uint i){ return i*2; }) + .asIterSource(); + + CHECK (38 == *sequence); + CHECK ("38-34-30-26-22" == materialise(sequence)); + + // WARNING pitfall: `sequence` is a copyable iterator front-end + // but holds onto the actual pipeline by shared-ptr + // Thus, even while materialise() creates a copy, + // the iteration state gets shared.... + CHECK (22 == *sequence); + ++sequence; // ...and even worse, iteration end is only detected after increment + CHECK (isnil (sequence)); + + + // extended API to invoke child expansion opaquely + IterExploreSource exploreIter; + CHECK (isnil (exploreIter)); + + exploreIter = treeExplore(CountDown{20,10}) + .filter([](uint i){ return i % 2; }) + .transform([](uint i){ return i*2; }) + .filter([](int i){ return i>25; }) + .expand([](uint i){ return CountDown{i-10, 20}; }) + .transform([](uint u) -> char { return '@'+u-20; }) + .asIterSource(); + + + CHECK ('R' == *exploreIter); // 38-20 + '@' + ++exploreIter; + CHECK ('N' == *exploreIter); // 34-20 + '@' + + exploreIter.expandChildren(); // expand consumes the current element (34) + // and injects the sequence (24...20[ instead + CHECK ('D' == *exploreIter); // 34-10 == 24 and 'D' == 24-20 + '@' + + CHECK ("D-C-B-A-J-F" == materialise(exploreIter)); + } // note how the remainder of the original sequence is picked up with 'J'... + + + + + /** @test ability to wrap and handle IterSource based iteration. + * Contrary to the preceding test case, here the point is to _base the whole pipleine_ + * on a data source accessible through the IterSource (VTable based) interface. The notable + * point with this technique is the ability to use some _extended sub interface of IterSource_ + * and to rely on this interface to implement some functor bound into the TreeExplorer pipeline. + * Especially this allows to delegate the "child expansion" through such an interface and just + * return a compatible IterSource as result. This way, the opaque implementation gains total + * freedom regarding the concrete implementation of the "child series" iterator. In fact, + * it may even use a different implementation on each level or even on each individual call; + * only the result type and thus the base interface need to match. + */ + void + verify_IterSource() + { + class PrivateSource + : public IterSource + { + public: + virtual PrivateSource* expandChildren() const =0; + }; + + class VerySpecivicIter + : public WrappedLumieraIter + { + public: + VerySpecivicIter(uint start) + : WrappedLumieraIter(NumberSequence{start}) + { } + + virtual PrivateSource* + expandChildren() const override + { + return new VerySpecivicIter{*wrappedIter() - 2}; + } + + uint + currentVal() const + { + return *wrappedIter(); + } + }; + + + // simple standard case: create a new heap allocated IterSource implementation. + // TreeExplorer will take ownership (by smart-ptr) and build a Lumiera Iterator front-End + CHECK ("7-6-5-4-3-2-1" == materialise ( + treeExplore(new VerySpecivicIter{7}))); + + + // missing source detected + PrivateSource* niente = nullptr; + CHECK (isnil (treeExplore(niente))); + + + // attach to an IterSource living here in local scope... + VerySpecivicIter vsit{5}; + + // ...and build a child expansion on top, which calls through the PrivateSource-API + // Effectively this means we do not know the concrete type of the "expanded children" iterator, + // only that it adheres to the same IterSource sub-interface as used on the base iterator. + auto ii = treeExplore(vsit) + .expand ([](PrivateSource& source){ return source.expandChildren(); }); + + CHECK (not isnil (ii)); + CHECK (5 == *ii); + CHECK (5 == vsit.currentVal()); + ++ii; + CHECK (4 == *ii); + CHECK (4 == vsit.currentVal()); + + CHECK (0 == ii.depth()); + ii.expandChildren(); // note: calls through source's VTable to invoke VerySpecificIter::expandChildren() + CHECK (1 == ii.depth()); + + CHECK (2 == *ii); + ++ii; + CHECK (1 == *ii); + + CHECK (3 == vsit.currentVal()); + CHECK (1 == ii.depth()); + ++ii; + CHECK (0 == ii.depth()); + CHECK (3 == *ii); + CHECK (3 == vsit.currentVal()); + ++ii; + CHECK (2 == *ii); + CHECK (2 == vsit.currentVal()); + ++ii; + CHECK (1 == *ii); + CHECK (1 == vsit.currentVal()); + ++ii; + CHECK (isnil (ii)); + } + + + + /** @test use a preconfigured exploration scheme to expand depth-first until exhaustion. + * This is a simple extension where all elements are expanded automatically. In fact, the + * `expandChildren()` operation implies already an iteration step, namely to dispose of the + * parent element before injecting the expanded child elements. Based on that observation, + * when we just replace the regular iteration step by a call to `expandChildren()`, we'll + * encounter first the parent element and then delve depth-first into exploring the children. + * @note such continued expansion leads to infinite iteration, unless the _expand functor_ + * contains some kind of termination condition. + * - in the first example, we spawn a child sequence with starting point one below + * the current element's value. And since such a sequence is defined to terminate + * when reaching zero, we'll end up spawning an empty sequence at leaf nodes, which + * prompts the evaluation mechanism to pop back to the last preceding expansion. + * - the second example demonstrates how to use value tuples for the intermediary + * computation. In this case, we only generate a linear chain of children, + * thereby summing up all encountered values. Termination is checked + * explicitly in this case, returning an empty child iterator. + */ + void + verify_depthFirstExploration() + { + CHECK (materialise( + treeExplore(CountDown{4}) + .expand([](uint j){ return CountDown{j-1}; }) + .expandAll() + .transform([](int i){ return i*10; }) + ) + == "40-30-20-10-10-20-10-10-30-20-10-10-20-10-10"); + + + using std::get; + using Tu2 = std::tuple; + auto summingExpander = [](Tu2 const& tup) + { + uint val = get<0>(tup); + uint sum = get<1>(tup); + return val? singleValIterator (Tu2{val-1, sum+val}) + : SingleValIter(); + }; + + CHECK (materialise( + treeExplore(CountDown{4}) + .transform([](uint i){ return Tu2{i,0}; }) + .expand(summingExpander) + .expandAll() + .transform([](Tu2 res){ return get<1>(res); }) + ) + == "0-4-7-9-10-0-3-5-6-0-2-3-0-1"); + } + + + + /** @test Demonstration how to build complex algorithms by layered tree expanding iteration + * @remarks this is the actual use case which inspired the design of TreeExplorer: + * Search with backtracking over an opaque (abstracted), tree-shaped search space. + * - the first point to note is that the search algorithm knows nothing about its + * data source, beyond its ability to delve down (expand) into child nodes + * - in fact our data source for this test here is "infinite", since it is an + * very large random root sequence, where each individual number can be expanded + * into a limited random sub sequence, down to arbitrary depth. We just assume + * that the search has good chances to find its target sequence eventually and + * thus only ever visits a small fraction of the endless search space. + * - on top of this (opaque) tree navigation we build a secondary search pipeline + * based on a state tuple, which holds onto the underlying data source + * - the actual decision logic to guide the search lives within the filter predicate + * to pull for the first acceptable solution, i.e. a path down from root where + * each node matches the next element from the search string. It is from here + * that the `expandChildren()` function is actually triggered, whenever we've + * found a valid match on the current level. The (random) data source was chosen + * such as to make it very likely to find a match eventually, but also to produce + * some partial matches followed by backtracking + * - note how the "downstream" processing accesses the `depth()` information exposed + * on the opaque data source to react on navigation into nested scopes: here, we use + * this feature to create a protocol of the search to indicate the actual "winning path" + */ + void + demonstrate_LayeredEvaluation() + { + // Layer-1: the search space with "hidden" implementation + using DataSrc = IterExploreSource; + DataSrc searchSpace = treeExplore(RandomSeq{-1}) + .expand([](char){ return RandomSeq{15}; }) + .asIterSource(); + + // Layer-2: State for search algorithm + struct State + { + DataSrc& src; + string& toFind; + vector protocol; + + State(DataSrc& s, string& t) + : src{s} + , toFind{t} + , protocol{0} + { } + + bool + checkPoint() const + { + return src; + } + + State& + yield() const + { + return *unConst(this); + } + + void + iterNext() + { + ++src; + protocol.resize (1+src.depth()); + ++protocol.back(); + } + + void + expandChildren() + { + src.expandChildren(); + protocol.resize (1+src.depth()); + } + + bool + isMatch() const + { + ASSERT (src.depth() < toFind.size()); + return *src == toFind[src.depth()]; + } + }; + + + // Layer-3: Evaluation pipeline to drive search + string toFind = util::join (treeExplore (RandomSeq{5}), ""); + cout << "Search in random tree: toFind = "<src.depth() < it->toFind.size() - 1 + and it->isMatch()) + it->expandChildren(); + + return it->isMatch(); + }); + + + // perform the search over a random tree... + CHECK (not isnil(theSearch)); + cout << "Protocol of the search: " << materialise(theSearch->protocol) <{}; + CHECK (isnil(i1)); + } + + + static ulong addTwo (int i) { return i+2; } static int negate (int i) { return -i; } static int idFunc (int i) { return i; } diff --git a/tests/library/linked-elements-test.cpp b/tests/library/linked-elements-test.cpp index 2a9101528..15820c0aa 100644 --- a/tests/library/linked-elements-test.cpp +++ b/tests/library/linked-elements-test.cpp @@ -46,7 +46,7 @@ namespace test{ using util::isnil; using util::isSameObject; - using lumiera::error::LUMIERA_ERROR_ITER_EXHAUST; + using lumiera::error::LERR_(ITER_EXHAUST); namespace { // test data... @@ -80,7 +80,7 @@ namespace test{ , next(0) { if (i == exception_trigger) - throw error::Fatal("simulated error", LUMIERA_ERROR_PROVOKED_FAILURE); + throw error::Fatal("simulated error", LERR_(PROVOKED_FAILURE)); } }; diff --git a/tests/library/meta/access-casted-test.cpp b/tests/library/meta/access-casted-test.cpp index e6ad2f55a..f641bda40 100644 --- a/tests/library/meta/access-casted-test.cpp +++ b/tests/library/meta/access-casted-test.cpp @@ -41,8 +41,8 @@ using std::ostream; using util::isSameObject; -using lumiera::error::LUMIERA_ERROR_BOTTOM_VALUE; -using lumiera::error::LUMIERA_ERROR_WRONG_TYPE; +using lumiera::error::LERR_(BOTTOM_VALUE); +using lumiera::error::LERR_(WRONG_TYPE); namespace util { diff --git a/tests/library/meta/duck-detector-extension-test.cpp b/tests/library/meta/duck-detector-extension-test.cpp new file mode 100644 index 000000000..81365dfec --- /dev/null +++ b/tests/library/meta/duck-detector-extension-test.cpp @@ -0,0 +1,226 @@ +/* + DuckDetectorExtension(Test) - detecting support for extension points at compile time + + Copyright (C) Lumiera.org + 2017, Hermann Vosseler + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +* *****************************************************/ + +/** @file duck-detector-extension-test.cpp + ** unit test \ref DuckDetectorExtension_test + */ + + +#include "lib/test/run.hpp" +#include "lib/meta/duck-detector.hpp" +#include "lib/util.hpp" + +#include + + +namespace lib { +namespace meta{ +namespace test{ + + using std::string; + + + namespace { // test types and definitions to explore.... + + void + fun (long) + { } + + int + fun (string, long) + { + return 12; + } + + void + fun () + { } + + + class Cheesy + { + public: + double funny (char, char, string); + void funky() const; + short fuzzy (float, float); + long long fuzzy(); + double fully; + }; + + class Fishy + { + /** @note private function can never be detected */ + long long fuzzy(); + + /** @note type Fishy exposes an extension point `fun` */ + friend void fun (Fishy&); + }; + + + }//(End) test definitions + + + + + /***********************************************************************************//** + * @test demonstrate some details regarding detection of functions within a type. + * The purpose of these metaprogramming techniques is to write generic containers, + * handlers, iterators etc, which automatically adapt themselves to the abilities + * of their payload type. To give an example, we may investigate the argument type + * of a functor or lambda, and then compose it with a suitable adapter or converter + * to work with the given data. + * + * However, when it comes to detecting the presence of a specific function, there + * are some complexities and variations to consider. Sometimes we only want to check + * for the presence of some function, while in other cases we also want to verify + * the exact signature of that function. Moreover, some of these detection techniques + * break down whenever there is overload ambiguity; thus we might need to resort to + * an alternative, not so strict test to get past such limitations. + */ + class DuckDetectorExtension_test : public Test + { + + META_DETECT_EXTENSION_POINT (funZ); + META_DETECT_EXTENSION_POINT (fun); + + META_DETECT_FUNCTION (double, funny, (char, char, string)); + META_DETECT_FUNCTION (long long, fuzzy, (void)); + META_DETECT_FUNCTION_NAME (funny); + META_DETECT_FUNCTION_NAME (funky); + META_DETECT_FUNCTION_NAME (fuzzy); + META_DETECT_FUNCTION_NAME (fully); + META_DETECT_MEMBER(funny); + META_DETECT_MEMBER(funky); + META_DETECT_MEMBER(fuzzy); + META_DETECT_MEMBER(fully); + META_DETECT_FUNCTION_ARGLESS (funny); + META_DETECT_FUNCTION_ARGLESS (funky); + META_DETECT_FUNCTION_ARGLESS (fuzzy); + + + void + run (Arg) + { + detect_freeFunctionADL_ExtensionPoint(); + detect_memberFunctionVariations(); + } + + + /** @test detect the presence of a free function extension point at compile time. + * It is a common idiom in C++ to expose an extension point through a free function, + * which is expected to be picked up by ADL. To mention a prominent example, any type + * can offer the ability to be _iterated_ by injecting free functions `begin(TY)` and + * `end(TY)`, to yield a STL compatible iterator. + * + * Since such an extension point is used just by invoking the _unqualified_ function + * with the target type, we can build a meta predicate based on the fact if such an + * function invocation expression can be formed for the type in question. + * + * @note the test really hinges on the ability to form the extension point call. + * For this reason, some implicit conversions might be involved, and some + * other conversions won't work (like passing a value to an extension point + * taking a reference). + */ + void + detect_freeFunctionADL_ExtensionPoint() + { + fun (); + fun (23); + fun ("FUN", 45); + + CHECK ( not HasExtensionPoint_funZ::value ); + + CHECK ( HasExtensionPoint_fun ::value ); + CHECK ( HasExtensionPoint_fun ::value ); + CHECK ( HasExtensionPoint_fun ::value ); + CHECK ( HasExtensionPoint_fun ::value ); + CHECK ( HasExtensionPoint_fun ::value ); + CHECK ( HasExtensionPoint_fun ::value ); + CHECK ( not HasExtensionPoint_fun ::value ); + CHECK ( not HasExtensionPoint_fun ::value ); + + CHECK ( not HasExtensionPoint_fun ::value ); + CHECK ( not HasExtensionPoint_fun ::value ); + CHECK ( HasExtensionPoint_fun ::value ); + CHECK ( not HasExtensionPoint_fun ::value ); + CHECK ( not HasExtensionPoint_fun::value ); + + CHECK ( not HasExtensionPoint_fun::value ); + } + + + /** @test cover variations of member function detection + * - detect an explicitly given full signature + * - detect just the presence of a function _name_ + * - distinguish this from detecting a _member_ + * - detect especially a member function without arguments + * @note some corner cases are demonstrated here as well: + * - private functions can not be detected + * - detection fails on ambiguity due to overloads + */ + void + detect_memberFunctionVariations() + { + CHECK ( HasFunSig_funny ::value ); // explicit function signature detected + CHECK ( HasFunSig_funny ::value ); // const qualifier is irrelevant + CHECK ( not HasFunSig_funny ::value ); // but reference does not work, obviously + + CHECK ( HasFunSig_fuzzy ::value ); // explicit function signature detected, overload is irrelevant + + CHECK ( HasFunName_funny ::value ); // function name detected (arguments irrelevant) + CHECK ( HasFunName_funky ::value ); // detected irrespective of const modifier + CHECK ( not HasFunName_fuzzy ::value ); // function name fuzzy *not* detected due to overload ambiguity + CHECK ( not HasFunName_fully ::value ); // name fully is a member, not a function + + CHECK ( HasMember_funny ::value ); // 'funny' is not only a function, it is also a member + CHECK ( HasMember_funky ::value ); + CHECK ( not HasMember_fuzzy ::value ); // WARNING: member 'fuzzy' *not* detected due to overload ambiguity + CHECK ( HasMember_fully ::value ); // 'fully' is not a function, but it is detected as member here + + CHECK ( not HasArglessFun_funny ::value ); // there is no argument less function 'funny' (it takes arguments) + CHECK ( HasArglessFun_funky ::value ); // but an argument-less 'funky' + CHECK ( HasArglessFun_fuzzy ::value ); // and one of the 'fuzzy' overloads also takes no arguments + + CHECK ( not HasFunSig_fuzzy ::value ); // Fishy::fuzzy() is private and can thus never be detected + CHECK ( not HasFunSig_funny ::value ); // and no fun with Fishy beyond that... + CHECK ( not HasFunName_funny ::value ); + CHECK ( not HasFunName_funky ::value ); + CHECK ( not HasFunName_fuzzy ::value ); + CHECK ( not HasFunName_fully ::value ); + CHECK ( not HasMember_funny ::value ); + CHECK ( not HasMember_funky ::value ); + CHECK ( not HasMember_fuzzy ::value ); + CHECK ( not HasMember_fully ::value ); + CHECK ( not HasArglessFun_funny ::value ); + CHECK ( not HasArglessFun_funky ::value ); + CHECK ( not HasArglessFun_fuzzy ::value ); + CHECK ( not HasFunSig_fuzzy ::value ); + } + }; + + + /** Register this test class... */ + LAUNCHER (DuckDetectorExtension_test, "unit meta"); + + + +}}} // namespace lib::meta::test diff --git a/tests/library/meta/duck-detector-test.cpp b/tests/library/meta/duck-detector-test.cpp index fe14f1be0..b392be783 100644 --- a/tests/library/meta/duck-detector-test.cpp +++ b/tests/library/meta/duck-detector-test.cpp @@ -21,24 +21,21 @@ * *****************************************************/ /** @file duck-detector-test.cpp - ** unit test \ref DuckDetector_test + ** unit test \ref DuckDetector_test: + ** duck typing through metaprogramming */ #include "lib/test/run.hpp" #include "lib/meta/duck-detector.hpp" +#include "lib/format-cout.hpp" #include "lib/util.hpp" -#include - namespace lib { namespace meta{ namespace test{ - using std::cout; - using std::endl; - namespace { // some test ducks.... @@ -79,7 +76,7 @@ namespace test{ void - run (Arg) + run (Arg) { SHOW_CHECK( HasNested_Core ); SHOW_CHECK( HasNested_Core ); @@ -89,7 +86,7 @@ namespace test{ SHOW_CHECK( HasFunSig_honk ); SHOW_CHECK( HasFunSig_honk ); - } + } }; diff --git a/tests/library/meta/function-closure-test.cpp b/tests/library/meta/function-closure-test.cpp index 510ebfa12..b36e4c99d 100644 --- a/tests/library/meta/function-closure-test.cpp +++ b/tests/library/meta/function-closure-test.cpp @@ -22,7 +22,7 @@ /** @file function-closure-test.cpp - ** Testing a combination of tr1::function objects and metaprogramming. + ** Testing a combination of std::function objects and metaprogramming. ** Argument types will be extracted and represented as typelist, so they ** can be manipulated at compile time. This test uses some test functions ** and systematically applies or binds them to corresponding data tuples. diff --git a/tests/library/meta/function-composition-test.cpp b/tests/library/meta/function-composition-test.cpp index a807b3f1e..863f7b612 100644 --- a/tests/library/meta/function-composition-test.cpp +++ b/tests/library/meta/function-composition-test.cpp @@ -178,14 +178,14 @@ namespace test { typedef Num<1> Sig123(Num<1>, Num<2>, Num<3>); // signature of the original function typedef Num<1> Sig23(Num<2>, Num<3>); // signature after having closed over the first argument - typedef function F23; // and a tr1::function object to hold such a function + typedef function F23; // and a std::function object to hold such a function Sig123& f =fun13<1,2,3>; // the actual input: a reference to the bare function // Version1: do a direct argument binding----------------- // - using PH1 = std::_Placeholder<1>; // tr1::function argument placeholders + using PH1 = std::_Placeholder<1>; // std::function argument placeholders using PH2 = std::_Placeholder<2>; PH1 ph1; // these empty structs are used to mark the arguments to be kept "open" @@ -246,7 +246,7 @@ namespace test { // what follows is the real unit test... - function func123 (f); // alternatively do it with an tr1::function object + function func123 (f); // alternatively do it with an std::function object fun_23 = func::applyFirst (func123, Num<1>(19)); res = fun_23 (_2_,_3_).o_; CHECK (24 == res); diff --git a/tests/library/meta/function-erasure-test.cpp b/tests/library/meta/function-erasure-test.cpp index bb66d91b5..9cda013e6 100644 --- a/tests/library/meta/function-erasure-test.cpp +++ b/tests/library/meta/function-erasure-test.cpp @@ -130,7 +130,7 @@ namespace test { typedef int (Sig4) (); _sum_ = 0; - f1.getFun() (-11,'M'); // invoke stored tr1::function... + f1.getFun() (-11,'M'); // invoke stored std::function... CHECK (_sum_ == 'M'-11); _sum_ = 0; diff --git a/tests/library/meta/function-signature-test.cpp b/tests/library/meta/function-signature-test.cpp index 59247f672..afee8756c 100644 --- a/tests/library/meta/function-signature-test.cpp +++ b/tests/library/meta/function-signature-test.cpp @@ -213,6 +213,28 @@ namespace test { CHECK ("int (unsigned int)" == typeStr<_Fun::Sig>()); CHECK ("int (Functor&, unsigned int)" == typeStr<_Fun::Sig >()); CHECK ("int (unsigned int)" == typeStr<_Fun::Sig >()); + + + // _Fun can be used for metaprogramming with enable_if + CHECK ( _Fun::value); // yes : a Functor + CHECK ( _Fun::value); // yes : a function type + CHECK (not _Fun::value); // no : a type without function call operator + + auto lambda1 = [](int i) { return double(i) / (i*i); }; + auto lambda2 = [](auto i) { return double(i) / (i*i); }; + + using TLamb1 = decltype(lambda1); + using TLamb2 = decltype(lambda2); + + CHECK ( _Fun::value); // yes : detect signature of lambda + CHECK (not _Fun::value); // no : can not detect signature of a generic lambda! + + // but detection works, once the templated operator() has been instantiated to some fixed type + auto stdFunction = function (lambda2); + CHECK ( _Fun::value); + + CHECK ("double (int)" == showSig (lambda1)); + CHECK ("double (float)" == showSig (stdFunction)); } }; diff --git a/tests/library/meta/tuple-diagnostics.hpp b/tests/library/meta/tuple-diagnostics.hpp index 27a4f5c4a..019c013b9 100644 --- a/tests/library/meta/tuple-diagnostics.hpp +++ b/tests/library/meta/tuple-diagnostics.hpp @@ -80,7 +80,7 @@ namespace test { string > showType () { - using TypeList = typename Types::List; + using TypeList = typename RebindTySeq::List; using DumpPrinter = InstantiateChained; return "TUPLE" diff --git a/tests/library/meta/tuple-helper-test.cpp b/tests/library/meta/tuple-helper-test.cpp index 6b6c69705..477783d55 100644 --- a/tests/library/meta/tuple-helper-test.cpp +++ b/tests/library/meta/tuple-helper-test.cpp @@ -120,8 +120,8 @@ namespace test { typedef Types1::List L1; // starting from an existing Typelist... typedef Tuple T_L1; // derive a tuple type from this typelist - typedef Types::Seq Seq1; // extract the underlying type sequence - + typedef RebindTySeq::Seq Seq1; + // extract the underlying type sequence DISPLAY (T_L1); DISPLAY (Seq1); diff --git a/tests/library/meta/tuple-record-init-test.cpp b/tests/library/meta/tuple-record-init-test.cpp index bbe68543a..48b075314 100644 --- a/tests/library/meta/tuple-record-init-test.cpp +++ b/tests/library/meta/tuple-record-init-test.cpp @@ -32,7 +32,6 @@ #include "lib/format-cout.hpp" #include "lib/format-util.hpp" -#include #include using lib::Symbol; @@ -48,7 +47,7 @@ using lib::time::Duration; using lib::time::Time; using lib::hash::LuidH; -using lumiera::error::LUMIERA_ERROR_WRONG_TYPE; +using lumiera::error::LERR_(WRONG_TYPE); using std::string; using std::tuple; diff --git a/tests/library/meta/type-display-test.cpp b/tests/library/meta/type-display-test.cpp index ca201c475..01886295c 100644 --- a/tests/library/meta/type-display-test.cpp +++ b/tests/library/meta/type-display-test.cpp @@ -26,21 +26,13 @@ #include "lib/test/run.hpp" - -//#include "lib/p.hpp" -//#include "lib/diff/gen-node.hpp" - #include "lib/meta/util.hpp" +#include "lib/format-cout.hpp" -#include //////////TODO #include -//using lib::P; -//using lib::diff::GenNode; using std::string; -using std::cout; /////////////TODO -using std::endl; /////////////TODO namespace lib { @@ -130,8 +122,8 @@ namespace test{ CHECK (typeStr(&magic) == "Space const* (*)(Outer::Inner&&)"); CHECK (typeSymbol(&magic) == "Function"); - CHECK (typeStr ::Inner>() == "Outer::Inner"); - CHECK (typeSymbol::Inner>() == "Inner" ); + CHECK (typeStr ::Inner>() == "Outer::Inner"); + CHECK (typeSymbol::Inner>() == "Inner" ); CHECK (primaryTypeComponent("") == "void" ); CHECK (primaryTypeComponent("Sym&") == "Sym" ); diff --git a/tests/library/meta/typelist-diagnostics.hpp b/tests/library/meta/typelist-diagnostics.hpp index 514cdd6bf..f5a92880a 100644 --- a/tests/library/meta/typelist-diagnostics.hpp +++ b/tests/library/meta/typelist-diagnostics.hpp @@ -42,6 +42,7 @@ #include "lib/meta/typelist.hpp" #include "lib/meta/generator.hpp" #include "lib/format-string.hpp" +#include "lib/format-cout.hpp" #include "lib/meta/util.hpp" #include diff --git a/tests/library/meta/typeseq-manip-test.cpp b/tests/library/meta/typeseq-manip-test.cpp index 0330e4b11..419ceff26 100644 --- a/tests/library/meta/typeseq-manip-test.cpp +++ b/tests/library/meta/typeseq-manip-test.cpp @@ -42,10 +42,6 @@ #include "lib/meta/typelist-manip.hpp" #include "meta/typelist-diagnostics.hpp" -#include -#include - -using ::test::Test; using std::string; using std::cout; using std::endl; diff --git a/tests/library/meta/value-type-binding-test.cpp b/tests/library/meta/value-type-binding-test.cpp new file mode 100644 index 000000000..e7eefa314 --- /dev/null +++ b/tests/library/meta/value-type-binding-test.cpp @@ -0,0 +1,191 @@ +/* + ValueTypeBinding(Test) - verify handling of value_type definitions on custom containers + + Copyright (C) Lumiera.org + 2017, Hermann Vosseler + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +* *****************************************************/ + +/** @file type-display-test.cpp + ** unit test \ref ValueTypeBinding_test + */ + + +#include "lib/test/run.hpp" +#include "lib/test/test-helper.hpp" +#include "lib/meta/value-type-binding.hpp" +#include "lib/format-cout.hpp" +#include "lib/meta/util.hpp" + +#include + + +namespace lib { +namespace meta{ +namespace test{ + + using lib::test::showType; + + + namespace { // test fixture + + template + struct Outer + { + struct Inner + { + T val; + }; + + typedef T value_type; + typedef Inner & reference; + typedef std::shared_ptr pointer; + }; + + struct Space { }; + + }//(end)fixture + + + + + /**********************************************************************************//** + * @test verify result type rebinding for containers, iterators values and references. + * When augmenting, combining or decorating generic entities, there is often the need + * to find out about the `value_type`, a `reference` or `pointer` to such a value. + * Many STL compliant containers and iterators provide suitably nested type definitions + * to indicate those types. The meta::TypeBinding helper allows to pick up such + * definitions, and additionally it levels and unifies access for various combinations + * of primitive types, references and pointers. The purpose of this test is to verify + * and document this behaviour. + * + * @see value-type-binding.hpp + * @see lib::RangeIter + * @see lib::TreeExplorer::transform() + */ + class ValueTypeBinding_test + : public Test + { + void + run (Arg) + { + // verify the type diagnostics helper... + CHECK ("int" == showType() ); + CHECK ("int&" == showType() ); + CHECK ("int &&" == showType() ); + CHECK ("int const&" == showType() ); + CHECK ("const int &&" == showType() ); + CHECK ("int *" == showType() ); + CHECK ("const int *" == showType() ); + CHECK ("const int * const" == showType() ); + CHECK ("int const*&" == showType() ); + CHECK ("int const* const&" == showType() ); + + + // Test fixture: the template Outer provides nested value type bindings + using OuterSpace = Outer; + using Join = ulong; + + CHECK ("Space" == showType() ); + CHECK ("Outer::Inner&" == showType() ); + CHECK ("shared_ptr" == showType() ); + + // ...such nested type bindings will be picked up + CHECK ("Space" == showType::value_type>() ); + CHECK ("Outer::Inner&" == showType::reference>() ); + CHECK ("shared_ptr" == showType::pointer>() ); + + CHECK ("unsigned long" == showType>::value_type>() ); + CHECK ("Outer::Inner&" == showType>::reference>() ); + CHECK ("shared_ptr" == showType>::pointer>() ); + + // contrast this to a type without such nested bindings + CHECK ("Space" == showType::value_type>() ); + CHECK ("Space&" == showType::reference>() ); + CHECK ("Space *" == showType::pointer>() ); + + // reference types will be levelled (reference stripped) + CHECK ("Space" == showType::value_type>() ); + CHECK ("Outer::Inner&" == showType::reference>() ); + CHECK ("shared_ptr" == showType::pointer>() ); + + CHECK ("Space" == showType::value_type>() ); + CHECK ("Outer::Inner&" == showType::reference>() ); + CHECK ("shared_ptr" == showType::pointer>() ); + + CHECK ("Space" == showType::value_type>() ); + CHECK ("Outer::Inner&" == showType::reference>() ); + CHECK ("shared_ptr" == showType::pointer>() ); + + // but a pointer counts as a different, primitive type. No magic here + CHECK ("Outer *" == showType::value_type>() ); + CHECK ("Outer*&" == showType::reference>() ); + CHECK ("Outer* *" == showType::pointer>() ); + + CHECK ("const Outer *" == showType::value_type>() ); + CHECK ("Outer const*&" == showType::reference>() ); + CHECK ("Outer const* *" == showType::pointer>() ); + + CHECK ("const Outer * const" == showType::value_type>() ); + CHECK ("Outer const* const&" == showType::reference>() ); + CHECK ("Outer * const *" == showType::pointer>() ); + + CHECK ("Outer * const" == showType::value_type>() ); + CHECK ("Outer* const&" == showType::reference>() ); + CHECK ("Outer * const *" == showType::pointer>() ); + + // similar for a type without nested type bindings: references are levelled + CHECK ("unsigned long" == showType::value_type>() ); + CHECK ("unsigned long&" == showType::reference>() ); + CHECK ("unsigned long *" == showType::pointer>() ); + + CHECK ("unsigned long" == showType::value_type>() ); + CHECK ("unsigned long&" == showType::reference>() ); + CHECK ("unsigned long *" == showType::pointer>() ); + + CHECK ("unsigned long" == showType::value_type>() ); + CHECK ("unsigned long&" == showType::reference>() ); + CHECK ("unsigned long *" == showType::pointer>() ); + + CHECK ("unsigned long" == showType::value_type>() ); + CHECK ("unsigned long const&" == showType::reference>() ); + CHECK ("const unsigned long *" == showType::pointer>() ); + + //... but pointer types are not treated special in any way + CHECK ("unsigned long *" == showType::value_type>() ); + CHECK ("unsigned long*&" == showType::reference>() ); + CHECK ("unsigned long* *" == showType::pointer>() ); + + CHECK ("const unsigned long *" == showType::value_type>() ); + CHECK ("unsigned long const*&" == showType::reference>() ); + CHECK ("unsigned long const* *" == showType::pointer>() ); + + CHECK ("const unsigned long * const" == showType::value_type>() ); + CHECK ("unsigned long const* const&" == showType::reference>() ); + CHECK ("unsigned long * const *" == showType::pointer>() ); + + CHECK ("unsigned long * const" == showType::value_type>() ); + CHECK ("unsigned long* const&" == showType::reference>() ); + CHECK ("unsigned long * const *" == showType::pointer>() ); + } + }; + + LAUNCHER (ValueTypeBinding_test, "unit common"); + + +}}} // namespace lib::meta::test + diff --git a/tests/library/meta/variadic-argument-picker-test.cpp b/tests/library/meta/variadic-argument-picker-test.cpp new file mode 100644 index 000000000..46ee16955 --- /dev/null +++ b/tests/library/meta/variadic-argument-picker-test.cpp @@ -0,0 +1,270 @@ +/* + VariadicArgumentPicker(Test) - access individual arguments from a variadic template + + Copyright (C) Lumiera.org + 2008, Hermann Vosseler + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +* *****************************************************/ + + +/** @file variadic-argument-picker-test.cpp + ** verify metaprogramming manipulation on a variadic parameter pack. + ** The unittest VariadicArgumentPicker_test calls a variadic function with different + ** number of arguments and it employs variadic-helper.hpp to alter the order of passed arguments. + ** + ** The primary difficulty when dealing with variadic templates is the fact that a variadic + ** parameter pack is not a first class type, rather a special language construct which can only + ** be used to perform a pattern match. For this reason, metaprogramming has to proceed "backwards", + ** by invoking a delegate template, thereby dissecting the parameter pack by a pattern match. + ** The key technique for more elaborate manipulation is to construct a variadic index sequence + ** as a helper, and then to expand that index sequence to drive instantiation of a helper + ** template on individual arguments. + ** + ** @see variadic-helper.hpp + ** @see path-array.hpp + ** @see TypeSeqManipl_test + ** @see TupleRecordInit_test + ** @see TypeListManip_test + ** + */ + + +#include "lib/test/run.hpp" +#include "lib/meta/variadic-helper.hpp" +#include "lib/format-string.hpp" +#include "lib/format-cout.hpp" +#include "lib/format-util.hpp" + +#include + +using util::_Fmt; +using util::join; +using util::toString; +using std::forward; +using std::string; + + +namespace lib { +namespace meta { +namespace test { + + + namespace { // test data + + template + struct N + { + uint i_; + static int instanceCnt; + + N (uint x = rand() % (abs(n)+1)) + : i_{x} + { + ++instanceCnt; + } + ~N() + { + --instanceCnt; + } + N (N const& r) + : i_{r.i_} + { + ++instanceCnt; + } + N (N&& rr) + { + std::swap (i_, rr.i_); + ++instanceCnt; + } + + // instanceCnt remains same... + N& operator= (N const&) = default; + N& operator= (N&&) = default; + + operator string() const + { + static _Fmt format{"%s──%s─"}; + return format % typeStr(*this) % i_; + } + friend bool operator== (N const& l, N const& r) { return l.i_ == r.i_; } + friend bool operator!= (N const& l, N const& r) { return l.i_ != r.i_; } + }; + + template + int N::instanceCnt = 0; + + + /** + * Variadic test function to invoke + */ + template + string + fun (ARGS&& ...args) + { + static _Fmt format{"%2d╎%s┤"}; + return format % sizeof...(ARGS) % join({toString(args)...}, "┼"); + } + } // (End) test data + + + + + + + /****************************************************************************//** + * @test check the basic utilities for manipulating variadic template arguments. + * - single out and pick an argument designated by index + * - pick an argument with fallback on default construction + * - demonstrate usage by reordering the position of arguments + */ + class VariadicArgumentPicker_test : public Test + { + virtual void + run (Arg) + { + verify_fixture(); + check_pickArg (); + check_pickInit(); + check_reorderedArguments(); + + CHECK (0 == N<0>::instanceCnt); + CHECK (0 == N<1>::instanceCnt); + CHECK (0 == N<2>::instanceCnt); + CHECK (0 == N<3>::instanceCnt); + } + + + void + verify_fixture () + { + CHECK (0 == N<0>::instanceCnt); + CHECK (0 == N<1>::instanceCnt); + CHECK (0 == N<2>::instanceCnt); + CHECK (0 == N<3>::instanceCnt); + { + N<1> n1; + N<2> n2; + N<3> n3; + N<3> nn{n3}; + cout << fun (n1,n2,n3,nn) << endl; + + CHECK (0 == N<0>::instanceCnt); + CHECK (1 == N<1>::instanceCnt); + CHECK (1 == N<2>::instanceCnt); + CHECK (2 == N<3>::instanceCnt); + + } + CHECK (0 == N<0>::instanceCnt); + CHECK (0 == N<1>::instanceCnt); + CHECK (0 == N<2>::instanceCnt); + CHECK (0 == N<3>::instanceCnt); + } + + + void + check_pickArg () + { + N<1> n1; + N<2> n2; + N<3> n3; + + CHECK (n1 == pickArg<0> (n1,n2,n3)); + CHECK (n2 == pickArg<1> (n1,n2,n3)); + CHECK (n3 == pickArg<2> (n1,n2,n3)); + + // does not compile... +// pickArg<3> (n1,n2,n3); + + N<0> n0{42}; + CHECK (n0 != pickArg<0> (N<0>{23})); + CHECK (n0 == pickArg<0> (N<0>{n0})); + } + + + void + check_pickInit () + { + N<1> n1; + N<2> n2; + using N0 = N<0>; + + CHECK (1 == (pickInit<0,int> (1,2) )); + CHECK (2 == (pickInit<1,int> (1,2) )); + CHECK (0 == (pickInit<2,int> (1,2) )); + + CHECK (n1 == (pickInit<0,N0> (n1,n2) )); + CHECK (n2 == (pickInit<1,N0> (n1,n2) )); + + CHECK ("N<0>" == typeStr(pickInit<3,N0> (n1,n2))); + CHECK ("N<0>" == typeStr(pickInit<3,N0> (1,"2"))); + CHECK ("N<0>" == typeStr(pickInit<3,N0> ())); + } + + + + /** + * Demonstration of argument manipulation through metaprogramming. + * This function invokes the ubiquitous `fun` test function with arbitrary arguments, + * but it re-orders and even prunes arguments as dictated by the Index sequence parameter. + * @remarks Note some fine points: + * - the value of the parameter `IndexSeq` is irrelevant + * - rather, its sole purpose is to _pattern match_ against the type + * - we use `IndexSeq` just as an container to hold the sequence `idx...` + * - the expression within the function argument list expands the argument packs + * - and both of them are treated separately + * - the actual arguments are wrapped into a `std::forward` for _perfect forwarding_ + * - but the key trick of the whole operation lies in the expansion of the `idx...` pack + * - note that `idx` without the `...` sits within the template list of `pickArg` + * - while the corresponding expansion operator is outmost and thus works on the whole expression + * - and thus it is the `idx...` pack which actually drives the generation of several `pickArg` instantiations + * - in the end the net effect is that the _Index sequence_ absolutely dictates which arguments are picked + */ + template + static auto + call_with_reversed_arguments (IndexSeq, ARGS&& ...args) + { + return fun (pickArg (forward(args)...) ...); + } + + /** @test demonstrate reordering of arguments */ + void + check_reorderedArguments () + { + N<0> n0; + N<1> n1; + N<2> n2; + N<3> n3; + + cout << fun (n0,n1,n2,n3) << endl; + + using Backwards = typename BuildIndexSeq<4>::Descending; // 3,2,1,0 + using Back2 = typename BuildIndexSeq<2>::Descending; // 1,0 + using After2 = typename BuildIndexSeq<4>::After<2>; // 2,3 + + cout << call_with_reversed_arguments (Backwards(), n0,n1,n2,n3) < #include #include @@ -42,8 +42,8 @@ namespace test{ using ::Test; using util::min; using lumiera::error::LUMIERA_ERROR_FATAL; + using util::NonCopyable; - using boost::noncopyable; using std::strlen; using std::cout; using std::endl; @@ -58,7 +58,7 @@ namespace test{ struct Base - : noncopyable + : NonCopyable { uint id_; diff --git a/tests/library/path-array-test.cpp b/tests/library/path-array-test.cpp new file mode 100644 index 000000000..5656efb09 --- /dev/null +++ b/tests/library/path-array-test.cpp @@ -0,0 +1,506 @@ +/* + PathArray(Test) - verify container abstraction for a sequence of path elements + + Copyright (C) Lumiera.org + 2017, Hermann Vosseler + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +* *****************************************************/ + +/** @file path-array-test.cpp + ** unit test \ref PathArray_test + */ + + +#include "lib/test/run.hpp" +#include "lib/test/test-helper.hpp" +#include "lib/path-array.hpp" +#include "lib/format-util.hpp" +#include "lib/util.hpp" + +#include + + +using std::string; +using lib::Symbol; +using util::isnil; +using util::join; + + + +namespace lib { +namespace test { + + using lumiera::error::LERR_(INDEX_BOUNDS); + using lumiera::error::LERR_(INVALID); + + using ParrT = lib::PathArray<5>; + + + /******************************************************************************//** + * @test verify abstraction wrapper to handle some char pointers in the way of a + * standard sequence container with iteration and range checks. + * + * @see path-array.hpp + * @see UICoord_test + * @see Navigator + */ + class PathArray_test : public Test + { + + virtual void + run (Arg) + { + verify_basics(); + verify_iteration(); + verify_boundaries(); + verify_comparisons(); + } + + + void + verify_basics() + { + ParrT undef; + CHECK (isnil (undef)); + + ParrT parr{"Γ","Δ","Θ","Ξ","Σ","Ψ","Φ","Ω"}; + CHECK (not isnil (parr)); + CHECK (8 == parr.size()); + + // indexed access + CHECK ("Γ" == parr[0]); + CHECK ("Δ" == parr[1]); + CHECK ("Θ" == parr[2]); + CHECK ("Ξ" == parr[3]); + CHECK ("Σ" == parr[4]); + CHECK ("Ψ" == parr[5]); + CHECK ("Φ" == parr[6]); + CHECK ("Ω" == parr[7]); + + VERIFY_ERROR (INDEX_BOUNDS, parr[8]); + + // string representation + CHECK ("Γ/Δ/Θ/Ξ/Σ/Ψ/Φ/Ω" == string(parr)); + + // new value can be assigned, but not altered in place + parr = ParrT{nullptr,nullptr,"Θ",nullptr,nullptr,nullptr,"Φ"}; + CHECK (7 == parr.size()); + + // representation is trimmed and filled + CHECK ("Θ/*/*/*/Φ" == string(parr)); + CHECK (Symbol::EMPTY == parr[0]); + CHECK (Symbol::EMPTY == parr[1]); + CHECK ("Θ" == parr[2]); + CHECK ("*" == parr[3]); + CHECK ("*" == parr[4]); + CHECK ("*" == parr[5]); + CHECK ("Φ" == parr[6]); + VERIFY_ERROR (INDEX_BOUNDS, parr[7]); + VERIFY_ERROR (INDEX_BOUNDS, parr[8]); + } + + + void + verify_iteration() + { + ParrT parr; + CHECK (isnil (parr)); + // path is iterable + CHECK ("" == join(parr)); + + parr = ParrT{"Γ","Δ","Θ","Ξ","Σ","Ψ","Φ","Ω"}; + CHECK (8 == parr.size()); + // path is iterable + CHECK ("ΓΔΘΞΣΨΦΩ" == join(parr,"")); + + // iteration matches index order + uint i=0; + for (ParrT::iterator ii = parr.begin(); ii; ++ii, ++i) + CHECK (parr[i] == *ii); + CHECK (8 == i); + + + // path information is automatically trimmed and filled + parr = ParrT{nullptr,nullptr,"Θ",nullptr,nullptr,nullptr,"Φ"}; + CHECK (7 == parr.size()); + CHECK ("Θ***Φ" == join(parr,"")); + + parr = ParrT{nullptr,"Δ",nullptr,"Ξ",nullptr,nullptr,nullptr}; + CHECK (4 == parr.size()); + CHECK ("Δ*Ξ" == join(parr,"")); + + parr = ParrT{nullptr,"Δ",nullptr,"Ξ",nullptr,nullptr,"Φ"}; + CHECK (7 == parr.size()); + CHECK ("Δ*Ξ**Φ" == join(parr,"")); + + parr = ParrT{nullptr,nullptr,nullptr,nullptr,nullptr,nullptr,nullptr,nullptr,nullptr,nullptr,nullptr,nullptr,nullptr,nullptr,nullptr,"Ω",nullptr}; + CHECK (16 == parr.size()); + CHECK ("Ω" == join(parr,"")); + + // index numbering starts at absolute root + CHECK ("Ω" == *parr.begin()); + CHECK (Symbol::EMPTY == parr[0]); + CHECK (Symbol::EMPTY == parr[1]); + CHECK (Symbol::EMPTY == parr[2]); + CHECK ("Ω" == parr[15]); + VERIFY_ERROR (INDEX_BOUNDS, parr[16]); + + // but iteration starts with actual content + parr = ParrT{nullptr,nullptr,"Θ","Ξ","Σ","Ψ","Φ","Ω"}; + CHECK ("Θ" == *parr.begin()); + CHECK ( 2 == parr.indexOf (*parr.begin())); + CHECK ("Θ" == parr[2]); + + parr = ParrT{nullptr,nullptr,nullptr,nullptr,"Σ","Ψ","Φ","Ω"}; + CHECK ("Σ" == *parr.begin()); + CHECK ( 4 == parr.indexOf (*parr.begin())); + CHECK ( 5 == parr.indexOf (*++parr.begin())); + CHECK ( 6 == parr.indexOf (*++++parr.begin())); + + Literal some{"muck"}; + VERIFY_ERROR (INVALID, parr.indexOf (some)); + } + + + /** @test cover some tricky corner cases of + * the bound checks and normalisation routine. + * @remark the container used for this test has an + * inline chunk size of 5, which means any further + * elements are in heap allocated extension storage. + * Normalisation seamlessly surpasses that boundary. + */ + void + verify_boundaries() + { + ParrT parr; + CHECK ("" == join(parr)); + CHECK (0 == parr.size()); + + parr = ParrT{"Γ","Δ","Θ","Ξ","Σ","Ψ","Φ","Ω"}; + CHECK ("ΓΔΘΞΣΨΦΩ" == join(parr,"")); + CHECK (8 == parr.size()); + + parr = ParrT{"Γ","Δ","Θ","Ξ","Σ","Ψ","Φ",""}; + CHECK ("ΓΔΘΞΣΨΦ" == join(parr,"")); + CHECK (7 == parr.size()); + + parr = ParrT{"Γ","Δ","Θ","Ξ","Σ","Ψ","Φ",nullptr}; + CHECK ("ΓΔΘΞΣΨΦ" == join(parr,"")); + CHECK (7 == parr.size()); + + parr = ParrT{"Γ","Δ","Θ","Ξ","Σ","Ψ",nullptr,""}; + CHECK ("ΓΔΘΞΣΨ" == join(parr,"")); + CHECK (6 == parr.size()); + + parr = ParrT{"Γ","Δ","Θ","Ξ","Σ","",nullptr,""}; + CHECK ("ΓΔΘΞΣ" == join(parr,"")); + CHECK (5 == parr.size()); + + parr = ParrT{"Γ","Δ","Θ","Ξ","Σ",nullptr,"",nullptr}; + CHECK ("ΓΔΘΞΣ" == join(parr,"")); + CHECK (5 == parr.size()); + + parr = ParrT{"Γ","Δ","Θ","Ξ","Σ",nullptr,nullptr,nullptr}; + CHECK ("ΓΔΘΞΣ" == join(parr,"")); + CHECK (5 == parr.size()); + + parr = ParrT{"Γ","Δ","Θ","Ξ","Σ",nullptr,nullptr,nullptr,""}; + CHECK ("ΓΔΘΞΣ" == join(parr,"")); + CHECK (5 == parr.size()); + + parr = ParrT{"Γ","Δ","Θ","Ξ","Σ","","",""}; + CHECK ("ΓΔΘΞΣ" == join(parr,"")); + CHECK (5 == parr.size()); + + parr = ParrT{"Γ","Δ","Θ","Ξ","","","Φ",""}; + CHECK ("ΓΔΘΞ**Φ" == join(parr,"")); + CHECK (7 == parr.size()); + + parr = ParrT{"Γ","Δ","Θ","Ξ",nullptr,"","Φ",""}; + CHECK ("ΓΔΘΞ**Φ" == join(parr,"")); + CHECK (7 == parr.size()); + + parr = ParrT{"Γ","Δ","Θ","Ξ","",nullptr,"Φ",""}; + CHECK ("ΓΔΘΞ**Φ" == join(parr,"")); + CHECK (7 == parr.size()); + + parr = ParrT{"Γ","Δ","Θ","Ξ",nullptr,nullptr,"Φ",""}; + CHECK ("ΓΔΘΞ**Φ" == join(parr,"")); + CHECK (7 == parr.size()); + + parr = ParrT{"Γ","Δ","Θ","Ξ","","","Φ",nullptr}; + CHECK ("ΓΔΘΞ**Φ" == join(parr,"")); + CHECK (7 == parr.size()); + + parr = ParrT{"Γ","Δ","Θ","Ξ","","","Φ",nullptr,""}; + CHECK ("ΓΔΘΞ**Φ" == join(parr,"")); + CHECK (7 == parr.size()); + + parr = ParrT{"Γ","Δ","Θ","Ξ","","","Φ",nullptr,"",nullptr}; + CHECK ("ΓΔΘΞ**Φ" == join(parr,"")); + CHECK (7 == parr.size()); + + parr = ParrT{"Γ","Δ","Θ","Ξ","","","Φ",nullptr,"",nullptr,""}; + CHECK ("ΓΔΘΞ**Φ" == join(parr,"")); + CHECK (7 == parr.size()); + + parr = ParrT{"Γ","Δ","Θ","Ξ","",nullptr,"",nullptr,"",nullptr,""}; + CHECK ("ΓΔΘΞ" == join(parr,"")); + CHECK (4 == parr.size()); + + parr = ParrT{"Γ","Δ","Θ","Ξ",nullptr,"",nullptr,"",nullptr,"",nullptr}; + CHECK ("ΓΔΘΞ" == join(parr,"")); + CHECK (4 == parr.size()); + + parr = ParrT{"","Δ","Θ","Ξ","","",""}; + CHECK ("ΔΘΞ" == join(parr,"")); + CHECK (4 == parr.size()); + CHECK ("" == parr[0]); + + parr = ParrT{nullptr,"Δ","Θ","Ξ","","",""}; + CHECK ("ΔΘΞ" == join(parr,"")); + CHECK (4 == parr.size()); + CHECK ("" == parr[0]); + + parr = ParrT{nullptr,"Δ",nullptr,"Ξ","","",""}; + CHECK ("Δ*Ξ" == join(parr,"")); + CHECK (4 == parr.size()); + CHECK ("" == parr[0]); + CHECK ("Δ" == parr[1]); + CHECK ("*" == parr[2]); + CHECK ("Ξ" == parr[3]); + + parr = ParrT{nullptr,"",nullptr,"Ξ","","",""}; + CHECK ("Ξ" == join(parr,"")); + CHECK (4 == parr.size()); + CHECK ("" == parr[0]); + CHECK ("" == parr[1]); + CHECK ("" == parr[2]); + CHECK ("Ξ" == parr[3]); + + parr = ParrT{nullptr,"",nullptr,"Ξ","","Ψ",""}; + CHECK ("Ξ*Ψ" == join(parr,"")); + CHECK (6 == parr.size()); + CHECK ("" == parr[0]); + CHECK ("" == parr[1]); + CHECK ("" == parr[2]); + CHECK ("Ξ" == parr[3]); + CHECK ("*" == parr[4]); + CHECK ("Ψ" == parr[5]); + + parr = ParrT{nullptr,"",nullptr,"Ξ",nullptr,"Ψ",""}; + CHECK ("Ξ*Ψ" == join(parr,"")); + CHECK (6 == parr.size()); + CHECK ("" == parr[0]); + CHECK ("" == parr[1]); + CHECK ("" == parr[2]); + CHECK ("Ξ" == parr[3]); + CHECK ("*" == parr[4]); + CHECK ("Ψ" == parr[5]); + + parr = ParrT{nullptr,"",nullptr,"Ξ",nullptr,nullptr,"Φ",""}; + CHECK ("Ξ**Φ" == join(parr,"")); + CHECK (7 == parr.size()); + CHECK ("" == parr[0]); + CHECK ("" == parr[1]); + CHECK ("" == parr[2]); + CHECK ("Ξ" == parr[3]); + CHECK ("*" == parr[4]); + CHECK ("*" == parr[5]); + CHECK ("Φ" == parr[6]); + + parr = ParrT{nullptr,"",nullptr,"Ξ","",nullptr,"Φ",""}; + CHECK ("Ξ**Φ" == join(parr,"")); + CHECK (7 == parr.size()); + CHECK ("" == parr[0]); + CHECK ("" == parr[1]); + CHECK ("" == parr[2]); + CHECK ("Ξ" == parr[3]); + CHECK ("*" == parr[4]); + CHECK ("*" == parr[5]); + CHECK ("Φ" == parr[6]); + + parr = ParrT{nullptr,"",nullptr,"Ξ",nullptr,"","Φ",""}; + CHECK ("Ξ**Φ" == join(parr,"")); + CHECK (7 == parr.size()); + CHECK ("" == parr[0]); + CHECK ("" == parr[1]); + CHECK ("" == parr[2]); + CHECK ("Ξ" == parr[3]); + CHECK ("*" == parr[4]); + CHECK ("*" == parr[5]); + CHECK ("Φ" == parr[6]); + + parr = ParrT{nullptr,"",nullptr,"Ξ","","","Φ",""}; + CHECK ("Ξ**Φ" == join(parr,"")); + CHECK (7 == parr.size()); + CHECK ("" == parr[0]); + CHECK ("" == parr[1]); + CHECK ("" == parr[2]); + CHECK ("Ξ" == parr[3]); + CHECK ("*" == parr[4]); + CHECK ("*" == parr[5]); + CHECK ("Φ" == parr[6]); + + parr = ParrT{"",nullptr,"",nullptr,"Σ","","Φ",""}; + CHECK ("Σ*Φ" == join(parr,"")); + CHECK (7 == parr.size()); + CHECK ("" == parr[0]); + CHECK ("" == parr[1]); + CHECK ("" == parr[2]); + CHECK ("" == parr[3]); + CHECK ("Σ" == parr[4]); + CHECK ("*" == parr[5]); + CHECK ("Φ" == parr[6]); + + parr = ParrT{"",nullptr,"",nullptr,"Σ",nullptr,"Φ",""}; + CHECK ("Σ*Φ" == join(parr,"")); + CHECK (7 == parr.size()); + CHECK ("" == parr[0]); + CHECK ("" == parr[1]); + CHECK ("" == parr[2]); + CHECK ("" == parr[3]); + CHECK ("Σ" == parr[4]); + CHECK ("*" == parr[5]); + CHECK ("Φ" == parr[6]); + + parr = ParrT{"",nullptr,"",nullptr,"","Ψ",nullptr,"Ω",""}; + CHECK ("Ψ*Ω" == join(parr,"")); + CHECK (8 == parr.size()); + CHECK ("" == parr[0]); + CHECK ("" == parr[1]); + CHECK ("" == parr[2]); + CHECK ("" == parr[3]); + CHECK ("" == parr[4]); + CHECK ("Ψ" == parr[5]); + CHECK ("*" == parr[6]); + CHECK ("Ω" == parr[7]); + + parr = ParrT{nullptr,"",nullptr,"",nullptr,"Ψ",nullptr,"Ω",""}; + CHECK ("Ψ*Ω" == join(parr,"")); + CHECK (8 == parr.size()); + CHECK ("" == parr[0]); + CHECK ("" == parr[1]); + CHECK ("" == parr[2]); + CHECK ("" == parr[3]); + CHECK ("" == parr[4]); + CHECK ("Ψ" == parr[5]); + CHECK ("*" == parr[6]); + CHECK ("Ω" == parr[7]); + + parr = ParrT{nullptr,"",nullptr,"",nullptr,"","Φ",nullptr,"ω",""}; + CHECK ("Φ*ω" == join(parr,"")); + CHECK (9 == parr.size()); + CHECK ("" == parr[0]); + CHECK ("" == parr[1]); + CHECK ("" == parr[2]); + CHECK ("" == parr[3]); + CHECK ("" == parr[4]); + CHECK ("" == parr[5]); + CHECK ("Φ" == parr[6]); + CHECK ("*" == parr[7]); + CHECK ("ω" == parr[8]); + + parr = ParrT{"",nullptr,"",nullptr,"",nullptr,"Φ",nullptr,"ω",""}; + CHECK ("Φ*ω" == join(parr,"")); + CHECK (9 == parr.size()); + CHECK ("" == parr[0]); + CHECK ("" == parr[1]); + CHECK ("" == parr[2]); + CHECK ("" == parr[3]); + CHECK ("" == parr[4]); + CHECK ("" == parr[5]); + CHECK ("Φ" == parr[6]); + CHECK ("*" == parr[7]); + CHECK ("ω" == parr[8]); + + parr = ParrT{"",nullptr,"",nullptr,"",nullptr,"Φ",nullptr,"ω","*"}; + CHECK ("Φ*ω" == join(parr,"")); + CHECK (9 == parr.size()); + CHECK ("" == parr[0]); + CHECK ("" == parr[1]); + CHECK ("" == parr[2]); + CHECK ("" == parr[3]); + CHECK ("" == parr[4]); + CHECK ("" == parr[5]); + CHECK ("Φ" == parr[6]); + CHECK ("*" == parr[7]); + CHECK ("ω" == parr[8]); + + parr = ParrT{"",nullptr,"",nullptr,"",nullptr,"Φ",nullptr,"*",""}; + CHECK ("Φ" == join(parr,"")); + CHECK (7 == parr.size()); + CHECK ("" == parr[0]); + CHECK ("" == parr[1]); + CHECK ("" == parr[2]); + CHECK ("" == parr[3]); + CHECK ("" == parr[4]); + CHECK ("" == parr[5]); + CHECK ("Φ" == parr[6]); + + parr = ParrT{"",nullptr,"",nullptr,"",nullptr,""}; + CHECK ("" == join(parr,"")); + CHECK (0 == parr.size()); + } + + + /** @test verify equality comparison + * Equality of PathArray is based on overall size, position and _normalised_ content. + * @note especially how `"*"` might match "" or `nullptr` at corresponding positions. + */ + void + verify_comparisons() + { + CHECK (ParrT("Γ","Δ","Θ","Ξ","Σ","Ψ","Φ","Ω") == ParrT("Γ","Δ","Θ","Ξ","Σ","Ψ","Φ","Ω")); + CHECK (ParrT("Γ","Δ","Θ","Ξ","Σ","Ψ","Φ","Ω") != ParrT("Γ","Δ","Θ","Σ","Ξ","Ψ","Φ","Ω")); + CHECK (ParrT("Γ","Δ","Θ","Ξ","Σ","Ψ","Φ","Ω") != ParrT("Γ","Δ","Θ","Ξ","Ξ","Ψ","Φ","Ω")); + CHECK (ParrT("Γ","Δ","Θ","Ξ","Σ","Ψ","Φ","Ω") != ParrT("" ,"Γ","Δ","Θ","Ξ","Σ","Ψ","Φ")); + CHECK (ParrT("Γ","Δ","Θ","Ξ","Σ","Ψ","Φ","" ) != ParrT("" ,"Γ","Δ","Θ","Ξ","Σ","Ψ","Φ")); + CHECK (ParrT("Γ","Δ","Θ","Ξ","Σ","Ψ","Φ","" ) != ParrT("" ,"Γ","Δ","Θ","Ξ","Σ","Ψ","" )); + CHECK (ParrT("" ,"Δ","Θ","Ξ","Σ","Ψ","Φ","" ) != ParrT("*","Δ","Θ","Ξ","Σ","Ψ","Φ","" )); + CHECK (ParrT("" ,"Δ","Θ","Ξ","Σ","Ψ","Φ","" ) != ParrT("" ,"Δ","Θ","Ξ","" ,"Σ","Ψ","Φ")); + CHECK (ParrT("" ,"Δ","Θ","Ξ","Σ","Ψ","Φ","" ) != ParrT("" ,"Δ","Θ","Ξ","Σ***Ψ","Φ","" )); + CHECK (ParrT("" ,"Δ","Θ","Ξ","Σ","" ,"Φ","" ) != ParrT("" ,"Δ","Θ","Ξ","*","Ψ","Φ","" )); + CHECK (ParrT("" ,"Δ","Θ","Ξ","Σ","" ,"Φ","" ) != ParrT("" ,"Δ","Θ","Ξ","Σ","* ","Φ","")); + CHECK (ParrT("" ,"Δ","Θ","Ξ","Σ","" ,"Φ","" ) != ParrT("" ,"Δ","Θ","Ξ","Σ","**","Φ","")); + CHECK (ParrT("" ,"Δ","Θ","Ξ","Σ","Ψ","Φ","" ) == ParrT("" ,"Δ","Θ","Ξ","Σ","Ψ","Φ","*")); + CHECK (ParrT("" ,"Δ","Θ","Ξ","Σ","", "Φ","" ) == ParrT("" ,"Δ","Θ","Ξ","Σ","*","Φ","" )); + CHECK (ParrT("" ,"Δ","Θ","Ξ","Σ","Ψ","Φ","" ) == ParrT(nullptr,"Δ","Θ","Ξ","Σ","Ψ","Φ")); + CHECK (ParrT("" ,"Δ","Θ","Ξ","Σ","Ψ","Φ","" ) == ParrT("" ,"Δ","Θ","Ξ","Σ","Ψ","Φ",nullptr)); + CHECK (ParrT("" ,"Δ","Θ","Ξ","Σ","Ψ","Φ","" ) == ParrT("" ,"Δ","Θ","Ξ","Σ","Ψ","Φ")); + CHECK (ParrT("" ,"Δ","Θ","Ξ","" ,"Ψ","Φ","" ) == ParrT("" ,"Δ","Θ","Ξ",nullptr,"Ψ","Φ")); + CHECK (ParrT("" ,"Δ","Θ","Ξ","*","Ψ","Φ","" ) == ParrT("" ,"Δ","Θ","Ξ",nullptr,"Ψ","Φ")); + CHECK (ParrT("" ,"Δ","Θ","Ξ","*","Ψ","Φ","" ) == ParrT("" ,"Δ","Θ","Ξ","", "Ψ","Φ")); + + ParrT src{"Γ","Δ","Θ","Ξ","Σ","Ψ","Φ","Ω"}; + ParrT copy{src}; + CHECK (not isnil(copy)); + CHECK (src == copy); + + ParrT target {std::move (copy)}; + CHECK (src == target); + CHECK (copy != target); + CHECK (copy != src); + } + }; + + + /** Register this test class... */ + LAUNCHER (PathArray_test, "unit gui"); + + +}} // namespace lib::test diff --git a/tests/library/scoped-collection-test.cpp b/tests/library/scoped-collection-test.cpp index 1473d4b90..48947ee7d 100644 --- a/tests/library/scoped-collection-test.cpp +++ b/tests/library/scoped-collection-test.cpp @@ -91,7 +91,7 @@ namespace test{ using util::isnil; - using lumiera::error::LUMIERA_ERROR_ITER_EXHAUST; + using lumiera::error::LERR_(ITER_EXHAUST); typedef ScopedCollection CollD; diff --git a/tests/library/scoped-holder-test.cpp b/tests/library/scoped-holder-test.cpp index c8549a6be..baee6dbf0 100644 --- a/tests/library/scoped-holder-test.cpp +++ b/tests/library/scoped-holder-test.cpp @@ -28,14 +28,13 @@ #include "lib/test/run.hpp" #include "lib/test/test-helper.hpp" +#include "lib/format-cout.hpp" #include "lib/util.hpp" #include "lib/error.hpp" #include "lib/scoped-holder.hpp" #include "lib/test/testdummy.hpp" -#include -#include #include @@ -47,7 +46,6 @@ namespace test{ using lumiera::error::LUMIERA_ERROR_LOGIC; using std::map; - using std::cout; typedef ScopedHolder HolderD; typedef ScopedPtrHolder PtrHolderD; diff --git a/tests/library/verb-function-dispatch-test.cpp b/tests/library/verb-function-dispatch-test.cpp index bed17b0ad..ec4f04256 100644 --- a/tests/library/verb-function-dispatch-test.cpp +++ b/tests/library/verb-function-dispatch-test.cpp @@ -21,7 +21,8 @@ * *****************************************************/ /** @file verb-function-dispatch-test.cpp - ** unit test \ref VerbFunctionDispatch_test + ** Demonstrate the concept of a _verb language_ based on double dispatch. + ** @see diff-language.hpp */ @@ -124,7 +125,7 @@ namespace test{ * of double dispatch; thus the implementation relies on a * variation of the visitor pattern. * - * @see session-structure-mapping-test.cpp + * @see DiffListApplication_test */ class VerbFunctionDispatch_test : public Test { diff --git a/wiki/renderengine.html b/wiki/renderengine.html index 042db3fd5..9b4f9af9d 100644 --- a/wiki/renderengine.html +++ b/wiki/renderengine.html @@ -522,6 +522,95 @@ noscript {display:none;} /* Fixes a feature in Firefox 1.5.0.2 where print previ

+
+
//Monads are of questionable usefullness//
+
+Monads are a concept and theoretical framework from category theory.
+But the use of Monads as a programming construct is much touted in the realm of functional programming -- which unfortunately is riddled with some kind of phobia towards ''State''.
+And this in itself is ill-guided, since //not State is the problem,// ''Complexity'' is. Complexity arises from far reaching non local interdependencies and coupling. Some complexity is essential.
+
+A telltale sign is that people constantly ask »What is a Monad?«
+And they don't get an answer, rather they get a thousand answers.
+The term //"Monad" fails to evoke an image// once mentioned.
+
+What remains is a set of clever technicalities. Such can be applied and executed without understanding. The same technicality can be made to work on vastly distinct subjects. And a way to organise such technicalities can be a way to rule and control subjects. The exertion of control is not in need of invoking images, once its applicability is ensured. //That// state is indifferent towards complexity, at least.
+
+When we care for complexity, we do so while we care for matters tangible to humans. Related to software, this is the need to maintain it, to adjust it, to adapt and remould it, to keep it relevant. At the bottom of all of these is the need to understand software. And this mandates to use terms and notions, even patterns, which evoke meaning -- even to the uninitiated. How can Monads even be helpful with that?
+
+!To make sensible usage of Monads
+Foremost, they should be kept as what they are: technicalities. For the understanding, they must be subordinate to a real concept or pattern. One with the power to reorient our view of matters at hand.
+Thus we ask: what can be said about Monads?
+
+!!!Formalism
+Regarding the formalism, it should be mentioned
+* that a Monad is a //type constructor// -- it takes a type parameter and generates a new instant of monadic kind.
+* and assuming such an instance, there is a //constructor// <br>{{{
+   unit: A → M<A>
+   }}}
+* and once we've optained a concrete entity of type {{{M<A>}}}, we can //bind a function// to transform it in another monadic entity<br/>{{{
+   M<A>::bind( A → M<B> ) → M<B>
+   }}}
+At this point, also the ''Monad Axioms'' should be mentioned
+;neutrality
+:{{{unit}}} (the monad constructor) is neutral with respect to the {{{bind}}} operation
+{{{
+unit(a)::bind(f)  ≡  f(a)
+M::bind(unit)  ≡  M
+}}}
+;composition
+:we can define a //composition// of monadic functions,
+:which is equivalent to consecutive binding
+{{{
+(M::bind(f) )::bind(g)  ≡  M::bind( f ∘ g )     with f ∘ g  defined as λ.x → { f(x)::bind(g) }
+}}}
+
+!!!Distinct properties
+The obvious first observation is that a Monad must be some kind of //container// -- or //object,// for that.
+
+The next observation to spring into mind is the fact that the {{{bind}}}-operation is notoriously hard to understand.
+Why is this so? Because it //intermingles// the world of monads with the things from the value domain. You can not just write a monadic function for use with {{{bind}}}, without being aware of the world of monads. It is not possible to "lift" an operation from the world of values automatically into the world of monads. Please contrast this to the ''map operation'', which is easy to understand for that very reason: if you map an operation onto a container of values, the operation itself does not need to be aware of the existence of containers as such. It just operates on values.
+
+This observation can be turned into a positive use-case for monadic structures -- whenever there is an ''additional cross-cutting concern'' within the world of values, to necessitate our constant concern all over the place. The Monad technique can then be used to re-shuffle this concern in such a way that it becomes possible to keep it //mentally separate.// Thus, while it is not possible to //remove// that concern, re-casting it as Monad creates a more regular structure, which is easier to cope with. A Monad can thus be seen as an ''augmented or "amplified" value''. A value outfitted with additional capabilities. This view might be illustrated by looking at some of the most prominent applications
+;failure handling
+:when we mark an expression as prone to failure by wrapping it into some //exception handling monad,//
+:it becomes easy to combine several such unreliable operations into a single program unit with just one failure handler
+;partially defined operations
+:when an operation can only work on some part of the value domain, we can mark the result as //optional//
+:and combining several tasks with optional result into one chain of operations can then be done completely generic
+;multiple results
+:when some tasks might possibly produce multiple results, we can mark those with a //container monad,//
+:and collecting and combining of such multifold results can then be dealt with completely separate from the generating actions
+;secondary state attributions
+:when we have to observe, extend and communicate additional data beyond what is involved in the basic computation
+:the collection and management of such additional attributions can be externalised with a //state monad//
+;local technicalities
+:when some algorithm or computation involves specific technicalities to get at the underlying data
+:a monad can be used to encapsulate those details and expose only abstracted higher-level //operation primitives//
+
+!!!Building with monads
+As pointed out above, while all those disparate usages might share some abstract structural similarities, no overarching theme can be found to span them all. If we tend to distil an essence from all those usages, we're bound to end up with nothing. The reason is, //we do apply// monadic techniques while coping with the problem, but there is //nothing inherent "monadic" in the nature// of things at hand.
+
+Yet to guide our thinking and doing, when we deal with matters, other concepts, notions and patterns are better suited to guide our actions, because they concur with the inherent nature of things:
+;builder
+:a builder allows to assemble a very specific structure from building blocks,
+:when done, the structure is self-contained and can be operated and used devoid all intricacies involved in building.
+:Monads are //exceptionally viable// for performing this process of assembly within the builder.
+;domain specific language
+:a DSL can found a higher level of abstraction, where local handling details are stashed away.
+:Monads can be useful as supporting backbone below a DSL, especially in cases where a full language system and runtime engine would be overblown
+;abstract composite value
+:an abstract data type or complex value can be used to represent computations beyond simple numbers,
+:prominent examples being colours with colour space or measurement values with numerical precision and unit of measurement.
+:Monads can be used to build the basic combining operations on such custom types, and their interplay.
+;engine
+:an engine guides and enacts ongoing computations, thereby transitioning internal state
+:Monads can be used as blueprint for managing these internal state transitions, and to prevent them from leaking out into the target of processing
+;context
+:a (possibly global) context can be used to represent all further concerns, which need to be there as well.
+:Monads can be used to create a distinct, secondary system of communication (»wormhole«) and they can be used to capture and replay events.
+:beyond that, for most practical purposes, simple plain mutable variables plus nested scopes //are a solution superior to the application of Monads.//
+
+
PageTemplate
 |>|SiteTitle - SiteSubtitle|
@@ -1838,6 +1927,74 @@ As we don't have a Prolog interpreter on board yet, we utilize a mock store with
 {{{default(Obj)}}} is a predicate expressing that the object {{{Obj}}} can be considered the default setup under the given conditions. Using the //default// can be considered as a shortcut for actually finding an exact and unique solution. The latter would require to specify all sorts of detailed properties up to the point where only one single object can satisfy all conditions. On the other hand, leaving some properties unspecified would yield a set of solutions (and the user code issuing the query had to provide means for selecting one solution from this set). Just falling back on the //default// means that the user code actually doesn't care for any additional properties (as long as the properties he //does// care for are satisfied). Nothing is said specifically on //how//&nbsp; this default gets configured; actually there can be rules //somewhere,// and, additionally, anything encountered once while asking for a default can be re-used as default under similar circumstances.
 &rarr; [[implementing defaults|DefaultsImplementation]]
+
+
//Access point to dependencies by-name.//
+In the Lumiera code base, we refrain from building or using a full-blown Dependency Injection Container. Rather, we rely on a generic //Singleton Factory// -- which can be augmented into a //Dependency Factory// for those rare cases where we actually need more instance and lifecycle management beyond lazy initialisation. Client code indicates the dependence on some other service by planting an instance of that Dependency Factory (for Lumiera this is {{{lib::Depend<TY>}}}). The //essence of a "dependency"// of this kind is that we ''access a service //by name//''. And this service name or service ID is in our case a //type name.//
+
+&rarr; see the Introductory Page about [[Dependencies|http://lumiera.org/documentation/technical/library/Dependencies.html]] in Lumiera online documentation.
+
+!Requirements
+Our DependencyFactory satisfies the following requirements
+* client code is able to access some service //by-name// -- where the name is actually the //type name// of the service interface.
+* client code remains agnostic with regard to the lifecycle or backing context of the service it relies on
+* in the simplest (and most prominent case), //nothing// has to be done at all by anyone to manage that lifecycle.<br/>By default, the Dependency Factory creates a singleton instance lazily (heap allocated) on demand and ensures thread-safe initialisation and access.
+* we establish a policy to ''disallow any significant functionality during application shutdown''. After leaving {{{main()}}}, only trivial dtors are invoked and possibly a few resource handles are dropped. No filesystem writes, no clean-up and reorganisation, not even any logging is allowed. For this reason, we established a [[Subsystem]] concept with explicit shutdown hooks, which are invoked beforehand.
+* the Dependency Factory can be re-configured for individual services (type names) to refer to an explicitly installed service instance. In those cases, access while the service is not available will raise an exception. There is a simple one-shot mechanism to reconfigure Dependency Factory and create a link to an actual service implementation, including automatic deregistration.
+
+!!!Configuration
+The DependencyFactory and thus the behaviour of dependency injection can be reconfigured, ad hoc, at runtime.
+Deliberately, we do not enforce global consistency statically (since that would lead to one central static configuration). However, a runtime sanity check is performed to ensure configuration actually happens prior to any use, which means any invocation to retrieve (and thus lazily create) the service instance. The following flavours can be configured
+;default
+:a singleton instance of the designated type is created lazily, on first access
+:define an instance for access (preferably static) {{{Depend<Blah> theBla;}}}
+:access the singleton instance as {{{theBla().doIt();}}}
+;singleton subclass
+:{{{DependInject<Blah>::useSingleton<SubBlah>() }}}
+:causes the dependency factory {{{Depend<Bla>}}} to create a {{{SubBlah}}} singleton instance from now on
+;attach to service
+:{{{DependInject<Blah>::ServiceInstance<SubBlah> service{p1, p2, p3}}}}
+:* build and manage an instance of {{{SubBlah}}} in heap memory immediately (not lazily)
+:* configure the dependency factory to return a reference //to this instance//
+:* the generated {{{ServiceInstance<SubBlah>}}} object itself acts as lifecycle handle (and managing smart-ptr)
+:* when it is destroyed, the dependency factory is automatically cleared, and further access will trigger an error
+;support for test mocking
+:{{{DependInject<Blah>::Local<SubBlah> mock }}}
+:temporarily shadows whatever configuration resides within the dependency factory
+:the next access will create a (non singleton) {{{SubBlah}}} instance in heap memory and return a {{{Blah&}}}
+:the generated object again acts as lifecycle handle and smart-ptr to access the {{{SubBlah}}} instance like {{{mock->doItSpecial()}}}
+:when this handle goes out of scope, the original configuration of the dependency factory is restored
+;custom constructors
+:both the subclass singleton configuration and the test mock support optionally accept a functor or lambda argument with signature {{{SubBlah*()}}}.
+:the contract is for this construction functor to return a heap allocated object, which will be owned and managed by the DependencyFactory.
+:especially this enables use of subclasses with non default ctor and / or binding to some additional hidden context.
+:please note //that this closure will be invoked later, on-demand.//
+
+We consider the usage pattern of dependencies a question of architecture rather -- such can not be solved by any mechanism at implementation level.
+For this reason, Lumiera's Dependency Factory prevents reconfiguration after use, but does nothing exceeding such basic sanity checks
+
+!!!Performance considerations
+To gain insight into the rough proportions of performance impact, in 2018 we conducted some micro benchmarks (using a 8 core AMD ~FX-8350 64bit CPU running Debian/Jessie and GCC 4.9 compiler)
+The following table lists averaged results //in relative numbers,// in relation to a single threaded optimised direct non virtual member function invocation (≈ 0.3ns)
+| !Access Technique |>| !development |>| !optimised |
+|~| single threaded|multithreaded | single threaded|multithreaded |
+|direct invoke on shared local object             |   15.13|   16.30|  ''1.00''|   1.59|
+|invoke existing object through unique_ptr  |   60.76|   63.20|     1.20|   1.64|
+|lazy init unprotected (not threadsafe)           |   27.29|   26.57|     2.37|   3.58|
+|lazy init always mutex protected                    | 179.62| 10917.18| 86.40| 6661.23|
+|Double Checked Locking with mutex          |   27.37|   26.27|    2.04|    3.26|
+|DCL with std::atomic and mutex for init       |   44.06|   52.27|    2.79|    4.04|
+
+Some observations:
+* The numbers obtained pretty much confirm [[other people's measurments|http://www.modernescpp.com/index.php/thread-safe-initialization-of-a-singleton]]
+* Synchronisation is indeed necessary; the unprotected lazy init crashed several times randomly during multithreaded tests.
+* Contention on concurrent access is very tangible; even for unguarded access the cache and memory hardware has to perform additional work
+* However, the concurrency situation in this example is rather extreme and deliberately provokes collisions; in practice we'd be closer to the single threaded case
+* Double Checked Locking is a very effective implementation strategy and results in timings within the same order of magnitude as direct access
+* Unprotected lazy initialisation performs spurious duplicate initialisations, which can be avoided by DCL
+* Naïve Mutex locking is slow even with non-recursive Mutex without contention
+* Optimisation achieves access times around ≈ 1ns
+
+
Along the way of working out various [[implementation details|ImplementationDetails]], decisions need to be made on how to understand the different facilities and entities and how to tackle some of the problems. This page is mainly a collection of keywords, summaries and links to further the discussion. And the various decisions should allways be read as proposals to solve some problem at hand...
 
@@ -2359,11 +2516,13 @@ Additionally, they may be used for resource management purposes by embedding a r
 #* one OpenGL Dataframe could contain raw texture data (but I am lacking expertise for this topic)
 
-
+
An entity within the RenderEngine, responsible for translating a logical [[calculation stream|CalcStream]] (corresponding to a PlayProcess) into a sequence of individual RenderJob entries, which can then be handed over to the [[Scheduler]]. Performing this operation involves a special application of [[time quantisation|TimeQuant]]: after establishing a suitable starting point, a typically contiguous series of frame numbers need to be generated, together with the time coordinates for each of those frames.
 
 The dispatcher works together with the job ticket(s) and the scheduler; actually these are the //core abstractions//&nbsp; the process of ''job planning'' relies on. While the actual scheduler implementation lives within the backend, the job tickets and the dispatcher are located within the [[Segmentation]], which is the backbone of the [[low-level model|LowLevelModel]]. More specifically, the dispatcher interface is //implemented//&nbsp; by a set of &rarr; [[dispatcher tables|DispatcherTables]] within the segmentation.
 
+{{red{stalled since 2014}}} -- development on this (important) topic has been postponed. Moreover, some rough edges remain within the Design &rarr; see [[some notes...|AboutMonads]]
+
 !defining the dispatcher interface
 The purpose of this interface is to support the planning of new jobs, for a given CalcStream. From time to time, a chunk of new RenderJob entries will be prepared for the [[Scheduler]]. Each job knows his frame number and the actual ProcNode to operate. So, to start planning jobs, we need to translate time &rarr; frame number &rarr; segment &rarr; real exit node.
 
@@ -2522,7 +2681,7 @@ we need a test setup for this investigation.
 * realistic: shall reflect the situation in our actual UI
 
-
+
//The representation of a [[media clip|Clip]] for manipulation by the user within the UI.//
 Within Lumiera, a clip is conceived as a //chunk of media,// which can be handled in compound. Clip as such is an abstract concept, which is treated with minimal assumptions...
 * we know that a clip has //media content,// which need not be uniform and can be inherently structured (e.g. several media, several channels)
@@ -2535,7 +2694,7 @@ To start with, a clip can be rendered in ''abridged form'', which means that the
 
 The next step in a series of progressively more detailed clip representations is the ''compact form'', which still focuses on handling the clip as an unit, while at least indicating some of the inherent structuring. Essentially, the clip here is represented as a //strip of rendered preview content,// decorated with some overlays. One of these overlays is the //ID pane,// which resembles the arrangement known from the abridged form: The icon here is always the [[Placement]] icon, followed by the expand widget and the ID label. Again, this pane is aligned such as to remain in sight. Then, there is a pair of overlays, termed the //boundary panes,// which indicate the begin and the end of the clip respectively. Graphically, these overlays should be rendered in a more subtle way, just enough to be recognisable. The boundary panes are the attachment areas for //trimming gestures,// as opposed to moving and dragging the whole clip or shuffle editing of the content. Moreover, these boundary panes compensate for the alignment of the ID pane, which mostly keeps the latter in sight. As this might counterfeit the visual perception of scrolling, the boundary panes serve to give a clear visual clue when reaching the boundary of an extended clip. Optionally, another overlay is rendered at the upper side of the clip's area, to indicate attached effect(s). It is quite possible for these effect decorations not to cover the whole temporal span of the clip.
 
-A yet more detailed display of the clip's internals is exposed in the ''expanded form.'' Here, the clip is displayed as an window pane holding nested clip displays, which in turn might again be abridged, compact or ({{red{maybe 11/16}}}) even expanded. This enclosing clip window pane should be rendered semi transparent, just to indicate the enclosing whole. The individual clip displays embedded therein serve to represent individual media parts or channels, or individual attached effects. Due to the recursive nature of Lumiera's HighLevelModel, each of these parts exposes essentially the same controls, allowing to control the respective aspects of the part in question. We may even consider {{red{unclear as of 11/16}}} to allow accessing the parts of a VirtualClip, this way turning the enclosing clip into a lightweight container ({{red{11/2016 give proper credit for this concept! Who proposed this initially in 2008? was this Thosten Wilms?}}}
+A yet more detailed display of the clip's internals is exposed in the ''expanded form.'' Here, the clip is displayed as a window pane holding nested clip displays, which in turn might again be abridged, compact or ({{red{maybe 11/16}}}) even expanded. This enclosing clip window pane should be rendered semi transparent, just to indicate the enclosing whole. The individual clip displays embedded therein serve to represent individual media parts or channels, or individual attached effects. Due to the recursive nature of Lumiera's HighLevelModel, each of these parts exposes essentially the same controls, allowing to control the respective aspects of the part in question. We may even consider ({{red{unclear as of 11/16}}}) to allow accessing the parts of a VirtualClip, this way turning the enclosing clip into a lightweight container ({{red{11/2016 give proper credit for this concept! Who proposed this initially in 2008? was this Thosten Wilms?}}}
 
 Finally, there can be a situation where it is just not possible to render any of the aforementioned display styles properly, due to size constraints. Especially, this happens when zooming out such as to show a whole sequence or even timeline in overview. We need to come up with a scheme of ''graceful display degradation'' to deal with this situation -- just naively attempting to render any form might easily send our UI thread into a minute long blocking render state, for no good reason. Instead, in such cases display should fall back to a ''degraded form''
 * showing just a placeholder rectangle, when the clip (or any other media element) will cover a temporal span relating to at least 1 pixel width (configurable trigger condition)
@@ -2561,7 +2720,7 @@ A prime consideration regarding this whole clip presentation strategy is the per
 !clip content rendering
 In a typical editing application, the user can expect to get some visual clue regarding the media content of a clip. For example, sound clips can be visualised as waveform, while movie clips might feature a sequence of images taken from the video. Our intention is to ''use our own rendering engine'' to produce these thumbnails. In fact, our engine is perfectly suited for this task: it has precisely the necessary media decoding and rendering abilities, plus it offers an elaborate system of priorities and deadlines, allowing to throttle the load produced by thumbnail generation. In addition to all those qualities, our engine is planned to be complemented by an "intelligent" frame cache, which, given proper parametrisation, ensures the frequently requested thumbnails will be available for quick display. For this approach to work, we need to provide some infrastructure
 * we need to configure and maintain a //preview rendering strategy.//
-* the request for rendering has to be cast "just somewhere" as message, obviously via the UI-Bus
+* the request for rendering has to be cast "just somewhere" as message, possibly via the UI-Bus
 * actually rendered content will likewise arrive asynchronously as message via UI-Bus.
 * we still need to work out how buffer management for this task will be handled; it should be a derivative of typical buffer management for display rendering.
 * the clip widget needs to provide a simple placeholder drawing to mark the respective space in the interface, until the actual preview arrives.
@@ -2747,6 +2906,142 @@ Command instances are like prototypes -- thus each additional level of different
 see the description in &rarr; CommandSetup
 
+
+
//A view within the UI, featuring some component of relevance to »the model«.//
+While any UI is comprised of numerous widgets acting as //view of something,// only some of those views play the prominent role to act as //building block component// of the user interface.
+Such UI component views exhibit some substantial traits
+* they conform to a built-in fixed list of view types, each of which is unique and dedicated to a very specific purpose: //''Timeline'', ''Viewer'', (Asset)''Bin'', ''Infobox'', ''Playcontrol'',...//
+* each component view has a distinguishable identity and is connected to and addressable through the UI-Bus
+* it can be hosted only at a dedicated location within one or several specific [[docking panels|GuiDockingPanel]].
+* multiplicity (one, one per window, many) depends on the type of view and needs to be managed.
+* such a view is not just //created// -- rather it needs to be //allocated//
+
+!Allocation of UI component views
+Here, //Allocation// means
+* to constitute the desired element's identity
+* to consider multiplicity and possibly retrieve an existing instance
+* to determine the hosting location
+* possibly to instantiate and register a new instance
+* and finally to configure that instance for the desired role
+The classical example to verify this definition is the //allocation of viewers:// when starting playback of a new media item, we "need a viewer" to show it. But we can not just create yet another viewer window -- rather we're bound to allocate one of the possible "viewer slots". In fact this is a configurable property of the UI layout employed; sometimes some people need the limitation to one single viewer entity (which might even be external, routed to a beamer or monitor), while other ones request the classical editor layout with two viewer windows side by side, while yet different working styles might exploit a limited set of viewers allocated in stack- or round-robin style.
+
+!View access
+The global access point to component views is the ViewLocator within InteractionDirector, which exposes a generic access- and management API to
+* get (possibly create) some view of given type
+* get (possibly create) a view with specific identity
+* destroy a specific view
+For all these direct access operations, elements are designated by a private name-ID, which is actually more like a type-~IDs, and just serves to distinguish the element from its siblings. The same ~IDs are used for the components in [[UI coordinate specifications|UICoord]]; both usages are closely interconnected, because view access is accomplished by forming an UI coordinate path to the element, which is then in turn used to navigate the internal UI widget structure to reach out for the actual implementation element.
+
+While these aforementioned access operations expose a strictly typed direct reference to the respective view component and thus allow to //manage them like child objects,// in many cases we are more interested in UI elements representing tangible elements from the session. In those cases,  it is sufficient to address the desired component view just via the UI-Bus. This is possible since component ~IDs of such globally relevant elements are formed systematically and thus always predictable: it is the same ID as used within Proc-Layer, which basically is an {{{EntryID<TYPE>}}}, where {{{TYPE}}} denotes the corresponding model type in the [[Session model|HighLevelModel]].
+
+!!!Configuration of view allocation
+Since view allocation offers a choice amongst several complex patterns of behaviour, it seems adequate to offer at least some central configuration site with a DSL for readability. That being said -- it is conceivable that we'll have to open this topic altogether for general configuration by the user. For this reason, the configuration site and DSL are designed in a way to foster further evolution of possibilites...
+* at definition site {{{id-scheme.hpp}}}, explicit specialisations are given for the relevant types of component view
+* each of those general //view configurations//
+** defines the multiplicity allowed for this kind of view
+** defines how to locate this view
+* and that //location definition// is given as a list of //alternatives in order of precedence.// That is, the system tries each pattern of location and uses the first one applicable
+
+!!Standard examples
+;Timeline
+:add to group of timelines within the timelinePanel
+{{{
+alloc = unlimited
+locate = perspective(edit).panel(timeline)
+          or panel(timeline)
+          or currentWindow().panel(timeline).create()
+}}}
+;Viewer
+:here multiple alternatives are conceivable
+:* allow only a single view instance in the whole application
+{{{
+alloc = onlyOne
+locate = external(beamer)
+          or view(viewer)
+          or perspective(mediaView).panel(viewer)
+          or panel(viewer)
+          or firstWindow().panel(viewer).view(viewer).create()
+}}}
+:* allow two viewer panels (the standard layout of editing applications)
+{{{
+alloc = limitPerWindow(2)
+locate = perspective(edit).panel(viewer)
+          or currentWindow().panel(viewer)
+          or panel(viewer)
+          or currentWindow().panel(viewer).create()
+}}}
+;(Asset)Bin
+:within the dedicated asset panel, add to the appropriate group for the kind of asset
+{{{
+alloc = unlimited
+locate = currentWindow().perspective(edit).tab(assetType())
+          or perspective(asset).view(asset)
+          or tab(assetType())
+          or view(asset).tab(assetType()).create()
+          or firstWindow().panel(asset).view(asset).create()
+}}}
+;~Error-Log
+:use the current {{{InfoBoxPanel}}} if such exists, else fall back to using a single view on the primary window
+{{{
+alloc = limitPerWindow(1)
+locate = currentWindow().panel(infobox)
+          or view(error)
+          or panel(infobox)
+          or firstWindow().panel(infobox).view(error).create()
+}}}
+;Playcontrol
+://not determined yet what we'll need here....//
+
+!!DSL structure
+* the DSL assigns tokens to some expected Specs
+* the tokens themselves are provided as constants
+* in fact those tokens are functors
+* and are opaquely bound into {{{ViewLocator}}}'s implementation
+
+!!!Signatures
+;locate
+:is evaluated first and determines the UICoord of the target view
+:is a disjunction of alternative specs, which are resolved to use the first applicable one
+;alloc
+:the //Allocator// is run on those coordinates and actually //allocates// the view
+:returns coordinates of an (now) existing view
+;allocator specs
+:these are //builder functions// to yield a specifically parametrised allocator
+:typical example is an allocator to create only a limited number of view instances per window
+:the actual builder tokens are synthesised by partial function application on the given lambda
+!!!Spec Tokens
+;locate
+:firstWindow
+: currentWindow
+: perspective(id)
+: panel(id)
+: assetType() {{red{WIP 2/18 not clear if necessary}}}
+: create()
+;alloc
+: unlimited
+: onlyOne
+: limitPerWindow(cnt)
+
+!!!Semantics of location
+The given UICoord specs are matched one by one, using the first one applicable. The location indicated by this process describes the parent or scope where the desired view can be found or shall be created. The matching itself is based on the matching of UI coordinates against the existing UI topology, but enriched with some contextual information. By default, a given coordinate spec //is required to exist// -- which implies the semantics of //total coverage,// as defined by the coordinate resolver. When to the contrary the predicate {{{create()}}} is used, we only require that the given path //can be formed unambiguously// within the existing window topology -- the semantics of which is, again, defined by the coordinate resolver and basically implies that the path can be anchored and any missing parts can be interpolated without ambiguity, while possibly some extraneous suffix of components remains to be created by the instantiation process.
+
+The coordinate specs as written within the DSL are slightly abridged, as the final element, the actual component to be created, can be omitted and will be supplied automatically. This is possible insofar the ID of the queried element is actually more like a type specifier, and thus drawn from a finite and well known collection of possible elements (Timeline, Asset Bin, Error Log, Media Viewer, etc.). The elements to be created must invoke existing code and must be able to interface with their actual environment after all.
+
+However, the semantics of UI coordinate resolution and matching are applied against the coordinate specs //as given in the DSL.// Since, by default, what is given is required to exist, it makes quite a difference as to what kind of element is used as terminal element of a given spec. Since our UI coordinate system establishes a distinct depth for each kind of element, we can and even must reject any spec which does not yield a definite location for at least the parent element; we do not interpolate or even invent arbitrary elements, we only ever match against existing ones. Thus any DSL definition must encompass a sane default, a final alternative clause which always succeeds -- and the DSL is considered broken if it doesn't.
+
+!!!!The problem with {{{assetType()}}}
+There is a very special use case, where we'd might want to express some fine points regarding placement of a new sub-tab within the view requested. This need arises when the system requires to show some sub-element through a dedicated view. Such might be the case for a special kind of asset, or when a virtual clip or media is to be opened from a context //other than editing the timeline.// In such a situation, a preferable action would be to use or re-use an existing tab of a view of similar kind. Only if this fails, we'd want a new view to be created at a generic location (e.g. a window dedicated to asset management), and only as fall-back we'd want a completely new asset section to show up within the current window.
+
+Unfortunately such surpasses the ability of the solution mechanism backing this location DSL, which basically relies on pattern matching without further expression evaluation or unification. To express the aforementioned special treatment, we'd need a placeholder within the rules, indicated above as {{{assetType()}}}, which has to be replaced by the actual typeID used in the concrete location query. Since our system does not support unification, either the matching mechanism has to be manipulated based on the context, or the specific rule must be preprocessed for token replacement.
+It is not clear yet {{red{as of 2/2018}}}, if those additional complexities are justified...
+&rarr; Ticket #1130
+
+
+!!!Semantics of allocation
+While the process of probing and matching the location specification finally yields an explicit UICoord path to the desired element, it is up to the allocation step actually to decide on the action to be taken. Some allocation operations impose some kind of limit, and are thus free to ignore the given spec and rather return an existing element in place. In the end, the purpose of this whole matching and allocation process is to get hold of a suitable UI component without knowing its precise coordinates in the UI topology. And this is the very property to enable flexible mapping of the strictly hierarchical session structures onto the UI in a fluid way.
+&rarr; [[low level component access|UILowLevelAccess]]
+
+
All communication between Proc-Layer and GUI has to be routed through the respective LayerSeparationInterfaces. Following a fundamental design decision within Lumiera, these interface are //intended to be language agnostic// &mdash; forcing them to stick to the least common denominator. Which creates the additional problem of how to create a smooth integration without forcing the architecture into functional decomposition style. To solve this problem, we rely on ''messaging'' rather than on a //business facade// -- our facade interfaces are rather narrow and limited to lifecycle management. In addition, the UI exposes a [[notification facade|GuiNotificationFacade]] for pushing back status information created as result of the edit operations, the build process and the render tasks.
 
@@ -2873,6 +3168,22 @@ In order to build a sensible plan for our timeline structure, we need to investi
 }}}
 
+
+
//Management of dockable content panes//
+Within each top level application window, the usable screen real estate can be split and arranged into a number of standard view building blocks. Each of this //panels// follows a specific preconfigured basic layout -- these are hard coded, yet customisable in detail. There is a finite list of such panel types available:
+* the [[timeline display|GuiTimelineView]]
+* the media viewer
+* asset management
+* information box
+Within the preconfigured layout it is a panel's role to host and incorporate GuiComponentView elements.
+Please note the distinction between UI component view and panel; in many cases there is one of each for a given kind, and these need to be distinguished: e.g. there is the [[timeline view|GuiTimelineView]] and there is the timeline panel, which houses several timelines (as tabs). Or there is the viewer component, which is located within a dedicated viewer panel each.
+
+!Instantiation
+!Placing and addressing of embedded contents
+A specific problem arises insofar other parts of the UI need to create, address and control some UI entities, which at the same time exist as part of a docking panel. This is a problem of crosscutting concerns: UI control and interaction structure should not be mingled with the generic concern to maintain a component as part of a screen layout. Unfortunately, the design of standard UI toolkit sets is incredibly naive when it comes to interaction design, and the only available alternative is to rely on frameworks, which come with a hard-wired philosophy.
+As a result, we're forced to build our UI from components which fall short on the distinction between //ownership and control.//
+
+
special LayerSeparationInterface which serves the main purpose to load the GuiStarterPlugin, thus bringing up the Lumiera GTK UI at application start.
 
@@ -2985,16 +3296,16 @@ This treatment ensures each nested diff is consumed within a properly typed cont
 &rarr; this is the purpose of the {{{DataCap}}} within our [[generic node element|GenNode]]
 
-
+
LayerSeparationInterface provided by the GUI.
-Access point for the lower layers to push information and state changes (asynchronously) to the GUI. Most operations within Lumiera are in fact initiated by the user through the GUI. In the course of such actions, the GUI uses the services of the lower layer and typically receives an immediate synchronous response to indicate the change was noted. Yet often, these operations may cause additional changes to happen asynchronously from the GUI's perspective. For example, an edit operation might trigger a re-build of the low-level model, which then detects an error. Any such consequences and notifications can be "cast" up into the UI, using this service here.
+Access point for the lower layers to push information and state changes (asynchronously) to the GUI. Most operations within Lumiera are in fact initiated by the user through the GUI. In the course of such actions, the GUI uses the services of the lower layer and typically receives an immediate synchronous response to indicate the change was noted. Yet often, these operations may cause additional changes to happen asynchronously from the GUI's perspective. For example, an edit operation might trigger a re-build of the low-level model, which then detects an error. Any such consequences and notifications can be "cast" up into the UI, using the {{{NotificationService}}} described here.
 
-Beyond that, a call to trigger shutdown of the UI layer is also exposed here -- which becomes relevant when other [[sub-systems|Subsystem]] initiate general shutdown.
+Beyond that, a call to trigger shutdown of the UI layer is also exposed at this façade -- which becomes relevant when other [[sub-systems|Subsystem]] initiate general shutdown.
 
 !Lifecycle and Threading concerns
 The GuiNotificationFacade is installed as part of and managed by the ''UI Manager'', and connected to the UI-Bus, which happens while establishing the GuiTopLevel. Yet there is a specific twist, insofar GTK is ''not threadsafe by design''. Any event handling and presentation changes will happen from within a dedicated UI event loop, which needs to be started explicitly, after all of the primary windows and widgets are created and wired. Only after this point the UI becomes //life.//
 
-A dedicated ''activation state'' is necessary for this reason -- which within the implementation translate into a queuing and dispatching facility to reschedule any calls into the UI event thread ''asynchronously''.
+A dedicated ''activation state'' is necessary for this reason -- which within the implementation translates into a queuing and dispatching facility to reschedule any calls into the UI event thread ''asynchronously''.
 
 !Addressing of UI elements
 Most calls in this interface require to specify a receiver or target, in the form of an element ID. It is assumed the caller effectively just knows these ~IDs, typically because the same ~IDs are also used as element ~IDs for the corresponding session entities. And, to start with, the whole UI representation of the session was at some point generated by //population diff messages,// which used the same ~IDs to indicate the creation of the corresponding UI representation elements.
@@ -3039,7 +3350,7 @@ This top-level circle is established starting from the UI-Bus (''Nexus'') and th
 
A specially configured LumieraPlugin, which actually contains or loads the complete code of the (GTK)GUI, and additionally is linked dynamically against the application core lib. During the [[UI startup process|GuiStart]], loading of this Plugin is triggered from {{{main()}}}. Actually this causes spawning of the GTK event thread and execution of the GTK main loop.
 
-
+
Within the Lumieara GUI, the [[Timeline]] structure(s) from the HighLevelModel are arranged and presented according to the following principles and conventions.
 Several timeline views may be present at the same time -- and there is not necessarily a relation between them, since »a Timeline« is the top-level concept within the [[Session]]. Obviously, there can also be several //views// based on the same »Timeline« model element, and in this latter case, these //coupled views// behave according to a linked common state. An entity »Timeline« as represented through the GUI, emerges from the combination of several model elements
 * a root level [[Binding|BindingMO]] acts as framework
@@ -3062,7 +3373,7 @@ The workspace dimension (vertical layout) is more like a ''Fork'', which can be
 ** the content stack, comprised of [[clip widgets|GuiClipWidget]], attached effects and transitions
 ** a stack of nested sub-scopes (recursive).
 
-@@float: right;background-color: #E9EDF8;width: 82ex;padding: 2ex;margin: 0px 4em 1em 2em;__Note in this example__
+@@float: right;background-color: #e9edf8;width: 82ex;padding: 2ex;margin: 0px 4em 1em 2em;__Note in this example__
 * on top level, there are two tracks, the second track has nested sub tracks
 * Clip-2 has an effect attached, Clip-3 is expanded and also has an effect attached
 * the second track has a global effect attached; it shows up in the scope ruler
@@ -3133,7 +3444,7 @@ Applying a diff changes the structure, that is, the structure of the local model
 Together this means we get a fix up stage after model changes, where the display is re-adjusted to fit the new situation. This works in concert with the [[display manager|TimelineDisplayManager]] representing only those elements as actual widgets, which get a real chance to become visible. This way we can build on the assumption that the actual number of widgets to be managed any time remains so small as to get away with simple linear list processing. It remains to be seen how far this assumption can be pushed -- the problem is that the GTK container components don't support anything beyond such simple linear list processing; there isn't even a call to remove all child widgets of a container in a single pass.
 
-
+
To a large extent, the Lumiera user interface is built around a //backbone structure,// known as the UI-Bus.
 But there are some dedicated top-level entities, collaborating to maintain a consistent application lifecycle
 ;Application
@@ -3150,6 +3461,7 @@ But there are some dedicated top-level entities, collaborating to maintain a con
 :the InteractionDirector is the root controller and corresponds to the [[root object in session|ModelRootMO]].
 ;Window List
 :organise and maintain the top level workspace windows
+:which involves the interplay with [[docking panels|GuiDockingPanel]]
 ;Notification Façade
 :attachment point for lower layers and anyone in need to "talk to the UI"
 :the GuiNotificationFacade is a LayerSeparationInterface and integrated with Lumiera's interface system
@@ -3197,8 +3509,8 @@ Besides routing to a global pipe, wiring plugs can also connect to the source po
 Finally, this example shows an ''automation'' data set controlling some parameter of an effect contained in one of the global pipes. From the effect's POV, the automation is simply a ParamProvider, i.e a function yielding a scalar value over time. The automation data set may be implemented as a bézier curve, or by a mathematical function (e.g. sine or fractal pseudo random) or by some captured and interpolated data values. Interestingly, in this example the automation data set has been placed relatively to the meta clip (albeit on another track), thus it will follow and adjust when the latter is moved.
 
-
-
This wiki page is the entry point to detail notes covering some technical decisions, details and problems encountered in the course of the implementation of the Lumiera Renderengine, the Builder and the related parts.
+
+
This wiki page is the entry point to detail notes covering some technical decisions, details and problems encountered in the course of the years, while building the Lumiera application.
 
 * [[Packages, Interfaces and Namespaces|InterfaceNamespaces]]
 * [[Memory Management Issues|MemoryManagement]]
@@ -3232,6 +3544,8 @@ Finally, this example shows an ''automation'' data set controlling some paramete
 * designing how to [[build|BuildFixture]] the [[Fixture]] (...{{red{WIP}}}...)
 * from [[play process|PlayProcess]] to [[frame dispatching|FrameDispatcher]] and [[node invocation|NodeInvocation]]
 * how to shape the GuiConnection: with the help of a mediating GuiModel, which acts as UI-Bus, exchanging TreeDiffModel messages for GuiModelUpdate
+* shape the Backbone of the UI, the UI-Bus and the GuiTopLevel
+* build a framework for InteractionControl in the User Interface
 
@@ -3426,12 +3740,12 @@ The InstanceHandle is created by the service implementation and will automatical &rarr; see [[detailed description here|LayerSeparationInterfaces]]
-
+
This overarching topic is where the arrangement of our interface components meets considerations about interaction design.
 The interface programming allows us to react on events and trigger behaviour, and it allows us to arrange building blocks within a layout framework. Beyond that, there needs to be some kind of coherency in the way matters are arranged -- this is the realm of conventions and guidelines. Yet in any more than trivial UI application, there is an intermediate and implicit level of understanding, where things just happen, which can not fully be derived from first principles. It is fine to have a convention to put the "OK" button right -- but how to we get at trimming a clip? How do we how we are to get at trimming a clip? if we work with the mouse? or the keyboard? or with a pen? or with a hardware controller we don't even know yet? We could deal with such on a case-by-case base (as the so called reasonable people do) or we could aim at an abstract intermediary space, with the ability to assimilate the practical situation yet to come.
 
 ;interface has a spatial quality
-:the elements within an user interface are arranged in a way that parallels our experience when working in real world space. With the addition of some dose of //"hyper"// -- allowing for cross connections and shortcuts beyond spatial logic
+:the elements within an user interface are arranged in a way that parallels our experience when working in real world space. With the addition of some minor dose of //"hyper"// -- allowing for cross connections and shortcuts beyond spatial logic
 ;locality of work spaces
 :but the arrangement of the interface interactions is not amorphous, rather it is segregated into cohesive clusters of closely interrelated actions. We move between these clusters of activity the same way as we move between several well confined rooms within a building.
 ;context and focus of activity
@@ -3468,28 +3782,39 @@ The primary insight is  //that we build upon a spatial metaphor// -- and thus we
 :the focus path specifies the UI frame (top-level window), the perspective, and then some canonical path to navigate down a hierarchy to reach the anchor point of the work site
 ;the spot locator
 :navigating means to move the SpotLocator, in order to move the spot from work site to work site
-:the spot locator is relocated by loading a new focus path to another [[work site|WorkSite]]
+:the spot locator is relocated by loading a new focus path leading to another [[work site|WorkSite]]
 
-The concept of a //focus goal// has several ramifications: for one it implies that there is something like //"current cotrol system",// which also could be the //currently active control system(s).// Simply because focus, as the realisation of the abstract notion of the spot, is always tied to a control system able to implement it. And when we're able to define generic location coordinates and then //"move there",// with the help of the SpotLocator, we draw the conclusion that there must be a focus (implementation) be moving towards that location. Like e.g.  the desired entity gaining the keyboard focus. And the second thing we may conclude is that there is some degree of leeway in the way how such a focus goal is reached. Since the inner logic of control systems can be quite drastically different from each other, we need to leave it to the actual control system //how a focus goal is reached.// To point out an obvious example: it is not a good idea to move the mouse pointer forcibly onto a screen element. Rather, we must use the established mechanisms of switching, scrolling and unfolding to bring the desired target element into the visible area, leaving the last step to the user, which must actively move the mouse onto the target. And we must give good visual clues as to what happened, and what we expect from the user (namely to direct her attention onto the element brought into focus).
+The concept of a //focus goal// has several ramifications: for one it implies that there is something akin the //"current cotrol system",// which also could be the //currently active control system(s).// Simply because focus, as the realisation of the abstract notion of the spot, is always tied to a control system able to implement it. And when we're able to define generic location coordinates and then //"move there",// with the help of the SpotLocator, we draw the conclusion that there must be a focus (implementation), somehow getting shifted towards that location. Like e.g.  the desired entity to gain the keyboard focus. And, beyond that, the second thing we may conclude is that there need to be some degree of leeway in the way such a focus goal can be reached. Since the inner logic of control systems can be quite drastically different from each other, we are well advised to leave it to the actual control system //how actually to fulfil the focus goal.// To point out an obvious example: it is not a good idea to move the mouse pointer forcibly onto a screen element. Rather, we must use the established mechanisms of switching, scrolling and unfolding to bring the desired target element into the visible area, leaving the last step to the user, which must actively move the mouse onto the target. And we must give good visual clues as to what happened, and what we expect from the user (namely to direct her attention onto the element brought into focus).
 
 !Building the framework
-To create such a system is an ambitious goal. We can not reach it in a single step, since it entails the formation of a whole intermediary layer, on top of the //usual UI mechanics,// yet below the concrete UI interactions. Especially, we'd need to clarify the meaning of //perspective,// we need to decide on the relation of top level frame, individual view, layout, focus and //current location within the UI.// On a second thought, building such a system implies we'll have to live with an intermediary state of evolution, where parts of the new framework are already in place without interfering with common conventional usage of the interface as-is.
+To create such a system is an ambitious goal for sure. We can not reach it in a single step, since it entails the formation of a whole intermediary layer, on top of the //usual UI mechanics,// yet below the concrete UI interactions. Especially, we'd need to clarify the meaning of //perspective,// we need to decide on the relation of top level frame, individual view, layout, focus and //current location within the UI.// On a second thought, building such a system implies we'll have to live with an intermediary state of evolution, where parts of the new framework are already in place without interfering with common conventional usage of the interface as-is.
+
+!!!UI coordinates
+Especially the focus navigation entails the use of some kind of ubiquitous [[coordinate system within the user interface|UICoord]]. In fact this is more of a topological navigation, since these coordinates describe the decisions and forks taken on navigation down the //focus path.//
+* [optional] top-level Window (UI frame)
+* [optional] Perspective
+* Panel
+* local path
+** [optional] Group
+** ~View-ID
+** component.component.component...
+
 
-
+
//the top-level controller within the UI.//
-In Lumiera, the structures of the model within the [[Session]] (the so called HighLevelModel) are mapped onto corresponding [[tangible UI entities|UI-Element]], which serve as a front-end to represent those entities towards the user. Within the model, there is a //conceptual root node// -- which logically corresponds to the session itself. This [[root element in model|ModelRootMO]] links together the actual top-level entities, which are the (multiple) timelines, with the asset management and defaults and rules configuration within the session.
+In Lumiera, the structures of the model within the [[Session]] (the so called HighLevelModel) are mapped onto corresponding [[tangible UI entities|UI-Element]], which serve as a front-end to represent those entities towards the user. Within this model, there is a //conceptual root node// -- which logically corresponds to the session itself. This [[root element in model|ModelRootMO]] links together the actual top-level entities, which are the (multiple) timelines, together with the asset management and defaults and rules configuration within the session.
 
-And the counterpart of this root element within the UI is the {{{InteractionDirector}}}, a top-level controller. As a controller, it responds to actions like opening a specific timeline, entering the asset management section, opening and closing of the session as a whole, and even shutdown of the application as a whole. Beyond that, the Interaction Director is the connection joint to that part of the UI which deals with global interaction state: this relates to questions about "the current element", "the focus", "where we are right now" (in what "location" or "room" within the UI) and also what tangible interface controller we're actually using (mouse, keyboard, graphics pen, hardware controller, touch screen).
+And the counterpart of this root element within the UI is the {{{InteractionDirector}}}, a top-level controller. As a controller, it responds to actions like opening a specific timeline, entering the asset management section, the request for help, and actions like saving, opening and closing of the session as a whole. Beyond that, the Interaction Director is the connection joint to that part of the UI which deals with global interaction state: this topic relates to questions about "the current element", "the focus", "where we are right now" (in what "location" or "room" within the UI) and also what tangible interface controller we're actually using (mouse, keyboard, graphics pen, hardware controller, touch screen).
 
 Why do we need a connection joint between those parts?
-Because issuing any actions on the model within the session -- i.e. any editing operation -- is like forming a sentence: we need to spell out //what we want to do// and we need to spell out the subject and the object of our activity. And any one of these can, and will in fact, be sometimes derived //from the context of the interaction.// Because, given the right context, it is almost clear what you want to do -- you just need to fill in that tiny little bit of information to actually make it happen. In Lumiera we want to build a good UI, which is an UI well suited to this very human way of interacting with one's environment within a given context.
+Because issuing any actions on the model within the session -- i.e. any editing operation -- is like forming a sentence: we need to spell out //what we want to do// and we need to spell out the subject and the object of our activity. And any one of these can, and will in fact, be sometimes derived //from the context of the interaction.// Because, given the right context, it is almost clear what you want to do -- you just need to fill in that tiny little bit of information to actually make it happen. In Lumiera we strive at building a good UI, which is an UI well suited to this very human way of interacting with one's environment within a given context.
 > to understand this, it is best &rarr; to [[look at some examples|CommandInvocationAnalysis]]
 
 !Children
 The InteractionDirector is part of the model, and thus we have to distinguish between model children and other UI components held as children.
 ;model
-:anything related to global structures falls into that category
+:anything related to //core concerns// and session global structures falls into that category
 :* some parts might be related to ''Asset management''
 :* some concerns might touch questions of ''configuration''
 :* and interaction state is closely related to ''persistent UI state''
@@ -3497,9 +3822,20 @@ The InteractionDirector is part of the model, and thus we have to distinguish be
 :anything related to forming and tracking of user interactions &rarr; InteractionControl
 :* the SpotLocator is what is "moved" when the [[Spot]] of current activity moves
 :* the FocusTracker is responsible for //detecting changes in current selection and focus.//
-:* the [[Navigator]] is a special controller to handle moving the SpotLocator
+:* the [[Navigator]] is a special controller to handle moving the SpotLocator within the UI tree topology
+:* the ViewLocator serves for any kind of high-level, abstracted access to [[component views|GuiComponentView]] and location resolution.
 
 !Collaborations
+* several global actions are exposed here, like opening the session and creating a new timeline.<br/>Such operations are typically bound into menu and action buttons, and in turn invoke [[commands|CommandHandling]] into the Session
+* some further actions involve [[management of docking panels|GuiDockingPanel]], like e.g. instantiating a new [[timeline display|GuiTimelineView]] into a timeline pane.
+* in fact it is the InteractionDirector's job to keep track of [[component views|GuiComponentView]], some of which
+** can exist only once
+** or can exist only once per top level window
+** some might even be required once per window
+** while others may be duplicated locally or globally
+
+!!!cyclic dependencies
+The InteractionDirector interconnects various aspects of UI management and thus can be expected to exhibit cyclic dependencies on several levels. Bootstraping the InteractionDirector is thus a tricky procedure and requires all participating services to operate demand-driven. In fact, these services represent some aspects of the same whole -- the UI. They are bounded by thematic considerations, not so much implementation concerns, and many of them rely on their siblings actually provide some essential part of their service. For example, the Navigator exposes the UI as topological tree structure, while the ViewLocator encapsulates the concrete access paths towards specific kinds of UI entities (views, panels). Obviously, the Navigator needs the ViewLocator to build its tree abstraction on top. But, thematically, //resolving a view// is part of accessing and / or creating  some view, and thus the ViewLocator becomes indirectly dependent on the tree topology established by the Navigator.
 
@@ -4024,6 +4360,46 @@ The UI-Bus offers a dedicated API to direct ~MutationMessages towards {{{Tangibl In the case at hand, the basic building block of the Lumiera UI, the {{{Tangible}}} offers this interface and thus the ability to construct a concrete TreeMutator, which in turn is bound to the internals of the actual UI-Element in question. Together this allows for a generic implementation of MutationMessage handling, where the designated UI-Element is reshaped by applying a concrete diff sequence embedded in the message with the help of a {{{DiffApplicator<DiffMutable>}}}, based on the TreeMutator exposed.
+
+
//Service to navigate through the UI as generic structure.//
+The Navigator is a component maintained by the InteractionDirector, and the actual implementation is backed by several facilities of the GuiTopLevel. It serves as foundation to treat the UI as a topological network of abstracted locations, represented as [[UI-Coordinates|UICoord]]. This design, together with the UI-Bus helps to reduce coupling within the UI implementation, since it enables to //get somewhere// and reach //some place// -- without the necessity to rely on concrete widget implementation structure.
+
+!The problem of abstraction
+This goal initially poses some challenge, since it aims beyond the conventional notion of UI programming, where it is sufficient just to wire some widgets and manipulate the receiver of an event notification. The usual UI widgets are just not meant to be treated in a more systematic, generic way -- and indeed, in most cases and for most purposes it is not a good idea to approach UI programming in a to much schematic way. User interfaces need to be tangible, something concrete, with lots of specifics and local traits. Yet this very nature of UI programming tends to turn some //cross-cutting concerns// into serious liabilities. So the deliberate ''decision to introduce a Navigator'' in avoidance of these future burdens and liabilities is a decision of priorities when it comes to shaping the Lumiera UI.
+
+Which leaves us with the quest of mapping a generic location scheme onto a load of implementation defined structures not exposing any kind of genericness, and to accomplish this within an environment lacking meta information and support for self discovery beyond the most basic abstraction level. As a first step towards bridging this gap, we'll have to identify the actual //command-and-query operations// required to treat UI elements as a topological structure.
+
+!!!Analysis of expected functionality
+In order to build a navigation facility, we need to...
+* follow a path
+** which means to constitute a location
+** and to discover child nodes at that location
+* and we might want to extend (maybe also prune) the collection of children
+
+!!!Use cases
+In the current situation ({{red{10/2017}}}), before engaging into the actual implementation, we're able to identify two distinct use cases
+;View [[specification|GuiComponentView]]
+:locate a view based on a preconfigured placement
+:* either to allocate a new view instance
+:* or to get //just some instance// of a view identified by type
+;WorkSite navication
+:move the Spot to some other place in the UI known by its [[UI-Coordinates|UICoord]]
+!!!{{red{Update 2/2018:}}} changed responsibilities
+Elaboration on the topic of »View Allocation« caused some minor architectural rearrangements.
+* Navigator became a pure information service (read-only), working on an abstracted view of the UI through [[UI coordinates|UICoord]]
+* the ViewLocator became service point for any high-level access to GuiComponentView elements
+
+!!!Requirements clarified
+From these use cases we conclude that the actual requirements for a Navigator component are less than one might expect.
+In fact it is sufficient to keep //the actual element// entirely opaque, so the Navigator works on [[UI coordinates|UICoord]] solely. The result -- some other UI coordinates -- can then be used to accomplish some tasks implemented elsewhere, like allocating a new view or actually moving [[the Spot|Spot]] (&rarr; InteractionControl)
+
+!Challenges of the implementation
+Some tricky problems remain to be solved, though: since the Navigator works on UI coordinates, the fundamental problem remains how to acquire the initial coordinates to start navigation. This is a problem of //reverse lookup:// given a concrete element of the UI, find it's UI coordinates. While we should note that it might not be necessary to "discover" coordinates, because in fact we may know them already -- either the element has to store them (on creation), or some lookup index table could be maintained to serve the same purpose. The actual access to low-level UI entities generates a host of further tecnicalities, which we attempt to stash away into a different low-level service, the  [[gui:ctrl::ElmAccessDir|UILowLevelAccess]].
+
+Moreover, the design of coordinate matching and resolving incurs a structure similar to [[render job planning|FrameDispatcher]] -- and the corresponding design problems remain to be solved in a satisfactory way &rarr; [[some notes...|AboutMonads]]
+
+
+
Various aspects of the individual [[render node|ProcNode]] are subject to configuration and may influence the output quality or the behaviour of the render process.
 * the selection //what// actual implementation (plugin) to used for a formally defined &raquo;[[Effect|EffectHandling]]&laquo;
@@ -5610,7 +5986,7 @@ When the session is closed or dismantled, further processing in the ProcDispatch
 
 
-
+
The Render Engine is the part of the application doing the actual video calculations. Relying on system level services and retrieving raw audio and video data through [[Lumiera's Backend|Backend]], its operations are guided by the objects and parameters edited by the user in [[the session|Session]]. The middle layer of the Lumiera architecture, known as the Proc-Layer, spans the area between these two exteremes, providing the the (abstract) edit operations available to the user, the representation of [["editable things"|MObjects]] and the translation of those into structures and facilities allowing to [[drive the rendering|Rendering]].
 
 !About this wiki page
@@ -9472,6 +9848,90 @@ The dispatch of //diff messages// is directly integrated into the UI-Bus -- whic
 
 The Graphical User interface, the upper layer in this hierarchy, embodies everything of tangible relevance to the user working with the application. The interplay with Proc-Layer, the middle layer below the UI, is organised along the distinction between two realms of equal importance: on one side, there is the immediate //mechanics of the interface,// which is implemented directly within the ~UI-Layer, based on the Graphical User Interface Toolkit. And, on the other side, there are those //core concerns of working with media,// which are cast into the HighLevelModel at the heart of the middle layer.
+
+
//A topological addressing scheme to designate structural locations within the UI.//
+Contrary to conventional screen pixel coordinates, here we aim at a topological description of the UI structure. Such a framework of structural reference allows us                                                                     
+* to refer to some "place" or "space" within the interface                                    
+* to remember and return to such a location
+* to move a [[work focus (»Spot«)|Spot]] structurally within the UI
+* to describe and configure the pattern view access and arrangement
+
+As starting point for the design of such a framework, we'll pick the notion of an access path within a hierarchical structure    
+* the top-level window
+* the perspective used within that window
+* the panel within this window
+* a view group within the panel
+* plus a locally defined access path further down to the actual UI element
+
+!Properties
+UI coordinates are a symbolic specification, and as such, their representation is immutable value-like. Essentially, a coordinate specification designates a pathway, and thus is comprised of a sequence of symbols. The individual path component symbol can be addressed by its depth index, starting with the top level window as rooting point. The meaning of first steps within each path is thus always fixed (top-level window, perspective, panel, view), followed by an essentially open sequence of local component names. Yet a given index position need not necessarily be actually defined. A coordinate spec can be indeterminate (NIL), but in all other cases a minimal consecutive sequence of symbols is guaranteed to exist.
+
+!!!about component names
+The individual components within such a coordinate spec are given as name-~IDs. In this context, we are more interested in the //topology// of UI components, and thus the names serve to distinguish sibling nodes. In fact, those names are created as type names and then decorated with number suffix while creating actual components. In this form, those name become real identifiers attached to those widgets, and can be used to access the respective implementation entity. Most of the elements designated and accessed this way are //mere UI entities// -- however, some elements can be representations of corresponding elements in the session (e.g. Tracks, Clips). If that is the case, obviously the name-ID has to match the "official" ID as used within the session.
+
+!!!flavours of meaning
+A given coordinate spec needs to be //interpreted// with the help of a resolver, which supplies additional knowledge regarding the actual window and UI configuration backing those interpretations. Such an interpretation allows to query for some typical predications, and it allows for //mutating operations,// which actually build a new coordinate spec from the given one. The general assumption is for all those coordinate interpretations to happen not within an excessively performance critical zone, where a little bit of iteration and repeated recomputation is deemed less costly than extended caching of evaluation state.
+
+The first distinction to draw is the ''anchor point'' of a given coordinate spec. After anchoring, the designated path is explicitly rooted within a top level window. The act of anchoring can thus be obvious(trivial), it can be //covered by wildcard,// or it can be a free interpolation or match against the existing environment. Starting from the anchor, the next predication to consider is the ''coverage'': the extent to which a given coordinate spec is actually covered by real UI structures. The test for coverage starts at the anchor point and descends following the coordinate spec in question. Backed by this evaluation process of interpretation and matching, we may judge a coordinate spec with respect to several predications
+* the coordinate spec may be totally ''explicit'' -- or it might contain wildcards
+* one specific path component of the coordinate spec may be known(present) or unknown
+* regarding the coverage, we may distinguish
+** a totally covered coordinate spec
+** a partially covered coordinate spec
+** a coordinate spec impossible to cover
+
+!UI coordinate path evaluation
+As indicated above, evaluation of UI coordinates requires a ''resolver'', to perform a search and matching operation and to hold the evaluation state. Evaluation is accomplished by first constituting an anchoring, followed by traversal of the coordinate spec and matching against a navigation path within the actual UI window configuration. This process might involve interpretation of some meta-symbols and interpolation of wildcards.
+
+Internally the coordinate resolver in turn relies on a context query interface, to find out about existing windows, panels, views and tabs and to navigate the real UI structure. The actual implementation of this context query interface is backed by the [[Navigator]] component exposed through the InteractionDirector. In practice, the Navigator relies on another service for the [[low level access to UI components|UILowLevelAccess]]
+!!!Query operations
+In addition to the //locally decidable properties// of a coordinate spec, which are the explicitness and the presence of some component, several contextual predications may be queried 
+;anchorage
+:the way the given coordinate spec is or can be anchored
+:* it is already //explicitly anchored// by referring either to a specific window or by generic specification
+:* it //can be a anchored// by interpolation of some wildcards
+:* it is //incomplete// and need to be extended to allow anchoring
+:* it is //impossible to anchor// in the current UI configuration
+;coverage
+:the extent to which a given coordinate spec is backed by the actual UI configuration
+:please note: to determine the coverage, the spec needs to be anchored (either explicitly, or by interpolation, or by extension of an incomplete spec)
+:* it is //completely covered//
+:* it is //partially covered// with an remaining, uncovered extension part
+:* it is //possible to cover completely//
+:* it is //impossible to cover//
+__Some fine points to note__: Anchorage and coverage are not the same thing, but coverage implies anchorage. Only when a path is complete (starts with the window spec) and explicit (has no wildcards), then anchorage implies also partial coverage (namely at least to depth 1). To determine the possibility of coverage means to perform a resolution with backtracking to pick the maximal solution. Moreover, since "covered" means that the path specification //is at least partially supported by the real UI,// we establish an additional constraint to ensure this resolution did not just match some arbitrary wildcards. Rather we demand that behind rsp. below the last wildcard there is at least one further explicit component in the path spec, which is supported by the real UI. As a consequence, the coverage resolution may fail altogether, while still providing at least a possible anchor point.
+!!!Mutations
+In addition to querying the interpretation of a given coordinate spec with respect to the current UI environment, it is also possible to rewrite or extend the spec based on this environment
+;anchoring
+:in correspondence to the possible states of anchorage, we may derive an explicitly anchored spec
+:*by interpolating the given spec
+:*by interpretation and extension of the given spec
+;covering
+:we may construct the covered part of a given spec, which includes automatic anchoring.
+;extending
+:a (partially) covered UI coordinate pattern is extended by a further suffix of path...
+:* the given UI coordinate pattern is first anchored and covered
+:* and //truncated// to the covered part
+:* the given //extension suffix// is then attached behind
+
+__Navigation mutations:__ //In theory,// it would even be possible to extend the path by creating suitable child components; but actually this would require all "elements" to implement a suitable mutation interface -- which in the case of //generic elements// might be far beyond the common ground. For this reason, we keep mutation of the backing environment outside of a path mutator's scope and rather keep mutation limited to the path itself.
+
+
+
+
//Cross cutting access to elementary UI structures.//
+We have several orthogonal identification and access schemes within the UI. A naively written UI application just attaches the core logic below some widgets and controllers -- not only does this lead to a hard to maintain code base, this approach is even outright impossible in our case, due to the strict decoupling between core and GUI, which places us into the situation to connect a self contained core with a self contained UI. This is a binding, which, as a sideline, also generates a control structure of its own. We can indeed write code dealing with a generic UI element -- but there needs to be some place where this kind of generic designation is translated into internal structures of the UI toolkit (GTK in our case), to obtain a direct (language) reference to some implementation widget finally.
+
+A service to translate some generic address scheme into actual entities builds the foundation of designating a "place within the UI" by abstracted topological [[UI coordinates|UICoord]]. It allows to define //rules// how some generic [[kind of view|GuiComponentView]] shall be //placed and allocated// into the existing UI structure.
+
+!Notes about design and implementation {{red{WIP 4/2018}}}
+At the time of this writing, it is not really clear if we need such a facility and what form its implementation will take -- which in turn places several further planning steps and design consideration into dangling state. The programmer's usual remedy in such a situation is to create yet another abstraction as a tie break. We do not know what it is, but at least we can write unit tests against it to find out what it could be.
+* we need to confine ourselves to a small selection of base interfaces to cover all possible elements accessible this way: {{{gui::model::Tangible}}}, {{{Gtk::Widget}}}, ...?
+* looks like this accessor service will be backed by a structure similar to {{{LocationQuery}}} -- maybe it will even be the same backing structure at the end.
+* so effectively an access request will drill down the real UI topology, following the path of the given UI Coordinates.
+* the result can then be packaged into a {{{lib::Variant}}} and use a //variant visitor// (double dispatch) to invoke the apropriate {{{dynamic_cast}}}
+* together, this mechanism allows us to return direct language references to actual implementation widgets
+
+
For any kind of playback to happen, timeline elements (or similar model objects) need to be attached to a Viewer element through a special kind of [[binding|BindingMO]], called a ''view connection''. In the most general case, this creates an additional OutputMapping (and in the typical standard case, this boils down to a 1:1 association, sending the master bus of each media kind to the standard OutputDesignation for that kind).
 
@@ -9480,6 +9940,14 @@ Establishing a ~ViewConnection is prerequisite for creating or attaching an Play
 View connections are part of the model and thus persistent. They can be created explicitly, or just derived by //allocating a viewer.// And a new view connection can push aside (and thus "break") an existing one from another timeline or model element. When a view connection is //broken,// any associated PlayProcess needs to be terminated (this is a blocking operation). Thus, at any time, there can be only one active view connection to a given viewer or output sink; here "active" means, that a PlayController has been hooked up, and the connection is ready for playback or rendering. But on the other hand, nothing prevents a timeline (or similar model object) to maintain multiple view connections -- consequently the actual playback position behaves as if associated with the view connection; it has only meaning with respect to this specific connection. An obvious example is that you may play back, without interfering with an ongoing render.
 
+
+
//access and location of [[component views|GuiComponentView]] as abstracted from actual UI widgets.//
+The ViewLocator is a close sibling and mutually interdependent with the [[Navigator]] -- the primary difference being the uniform, completely symbolic representation by [[UI-tree topology|UICoord]], as exposed through the latter, which precludes //immediate manipulation.// The ViewLocator likewise presents an abstracted view, but oriented towards individual components, which it allows to locate, allocate and expose by direct reference as UI-Element.
+
+!Rules based handling by kind-of-view
+A small selection of //relevant UI elements// can in fact be located "by-type", relying on a configuration in the form of //locating rules...// &rarr; elaborated in more detail [[here|GuiComponentView]].
+This access mechanism allows to act on "the primary one of that kind", possibly creating a new instance on demand -- such UI elements are not to be confused with the model entities they expose for user interaction; typically they can either be re-linked or re-bound to a specific entity, or they are able to handle multiple model connections in separate tabs within the view (e.g. several [[Timelines|GuiTimelineView]]).
+
A [[structural Asset|StructAsset]] corresponding to a Viewer element in the GUI.
 
diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm
index affd1f4e2..f4dc5e2d4 100644
--- a/wiki/thinkPad.ichthyo.mm
+++ b/wiki/thinkPad.ichthyo.mm
@@ -1,75 +1,75 @@
 
 
-
+
 
-
+
 
-
-
-
-
+
+
+
+
 
 
-
-
-
-
-
-
+
+
+
+
+
+
 
-
-
-
+
+
+
 
 
-
-
-
-
+
+
+
+
 
-
-
-
+
+
+
 
-
-
-
+
+
+
 
 
 
 
-
+
 
 
-
-
-
+
+
+
 
-
+
 
 
-
-
-
-
+
+
+
+
 
 
-
+
 
 
-
+
 
 
-
+
 
-
-
-
-
+
+
+
+
 
-
+
 
-
+
 
   
     
@@ -81,7 +81,7 @@
   
 
 
-
+
 
   
     
@@ -102,10 +102,10 @@
 
 
 
-
-
+
+
 
-
+
 
   
     
@@ -126,8 +126,8 @@
   
 
 
-
-
+
+
 
   
     
@@ -145,26 +145,26 @@
 
 
 
-
-
-
+
+
+
 
 
-
+
 
 
 
-
+
 
-
-
-
+
+
+
 
 
-
+
 
 
-
+
 
   
     
@@ -198,15 +198,15 @@
 
 
 
-
+
 
-
-
-
+
+
+
 
-
+
 
-
+
 
   
     
@@ -218,9 +218,22 @@
   
 
 
-
-
-
+
+
+  
+    
+  
+  
+    

+ 4/18 inzwischen hier alles geklärt. +

+

+ Wartet nur noch auf proof-of-concept (DemoGuiRoundtrip) +

+ +
+ + @@ -236,23 +249,23 @@ - + - + - + - + - + - - + + @@ -262,18 +275,17 @@ Sicht "von unten"

- -
+
- + - - + + - + - + @@ -295,69 +307,69 @@ - + - + - + - + - + - + - - - - - + + + + + - - - + + + - - - - + + + + - + - + - + - - + + - - - - - - + + + + + + - + - - - - + + + + - - - - + + + + @@ -378,7 +390,7 @@ - + @@ -393,51 +405,51 @@ - - - + + + - + - - + + - - + + - + - - + + - - - + + + - - - - + + + + - - - + + + - - + + - + @@ -447,10 +459,9 @@ GtkLumiera in Konstruktion

- -
+
- + @@ -460,27 +471,26 @@ NotificationFacade noch nicht offen

- -
+
- + - - + + - - - - + + + + - - + + - - + + @@ -490,47 +500,46 @@ müssen eigens aktiviert werden

- -
+ - - + + - + - + - + - + - - + + - + - + - + - + - + - + @@ -567,32 +576,31 @@ dann kann der Shutdown-Prozeß den Start des GUI überholen.

- -
+
- + - - + + - + - - - + + + - + - + - + - + @@ -604,26 +612,26 @@ - + - + - - + + - + - + - + - + @@ -638,7 +646,7 @@ - + @@ -659,11 +667,11 @@ - + - - + + @@ -685,19 +693,19 @@ - + - + - - + + - + - + @@ -709,9 +717,9 @@ - - - + + + @@ -743,7 +751,7 @@ - + @@ -755,38 +763,38 @@ - + - + - + - - - + + + - - + + - + - + - + - + - - + + @@ -814,7 +822,7 @@ - + @@ -824,71 +832,126 @@ muß eigens aktiviert werden

- -
+ - - + + - + - + - + - - - + + + - + - + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ Fehlerlog-Anzeige vorläufig irgendwo.... +

+ +
+ +
+
+ + + + + + + + + + + + + + + + + + - + - - + + @@ -906,51 +969,50 @@ - - + - - + + - - - + + + - + - + - + - + - + - + - - - - - - + + + + + + - - - - - - - + + + + + + + @@ -963,11 +1025,11 @@ - - + + - + @@ -985,18 +1047,18 @@ - + - + - - - - - - + + + + + + @@ -1023,10 +1085,9 @@ - - + - + @@ -1036,24 +1097,23 @@ das wird sowiso ein Desaster

- -
+
- +
- + - + - - + + @@ -1065,7 +1125,7 @@ - + @@ -1077,7 +1137,7 @@ - + @@ -1090,33 +1150,33 @@ - + - + - + - + - + - - - - - + + + + + - + - + @@ -1130,16 +1190,16 @@

- + - - - + + + - - - + + + @@ -1152,9 +1212,9 @@ - - - + + + @@ -1166,7 +1226,7 @@ - + @@ -1184,7 +1244,7 @@ - + @@ -1196,48 +1256,67 @@ - + - + - - - + + + - + - + - + - + - - + + + + + + + + +

+ kann mich so vague erinnern +

+

+ daß hier zwar schon ein catch eingebaut war, +

+

+ aber noch irgend ein Hund begraben liegt +

+ +
+
- + + - + - + - + - + - + - + @@ -1255,11 +1334,11 @@ - + - + - + @@ -1274,7 +1353,7 @@ - + @@ -1290,53 +1369,113 @@ - + - - - + + + - - + + - + - + - + - + - + - + - + - + + + + + + + + + + + - - - - + + + + + + +

+ ...in den eigentlich kaputten DockManager +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
- + +
+
+ @@ -1349,64 +1488,63 @@ indem wir ein GTK-Signal erzeugen, das das Hauptfenster schließt

- -
- + + - + - - + + - + - + - + - + - + - + - + - + - - + + - - - + + + - + - - - + + + - - - + + + - - + + @@ -1425,30 +1563,29 @@ eine einzige Funktion konkret durchdenkt: es läuft auf Spaghetti-Code hinaus

- -
+
- + - + - + - + - + - + @@ -1458,13 +1595,12 @@ ...indem der NotificatonService nun vom UI-Manager gemanaged wird :)

- -
+
- + @@ -1480,29 +1616,29 @@ - + - - - - - - - - + + + + + + + + - + - - - + + + - + - - + + @@ -1556,132 +1692,132 @@ - + - - - + + + - + - - - + + + - - - - + + + + - + - - - - - - - - + + + + + + + + - + - + - - - - - - + + + + + + - - - - + + + + - + - + - + - + - - - - + + + + - + - + - - - - + + + + - + - - + + - + - - - + + + - - + + - - + + - - + + - - + + - + - + - + - + - + @@ -1690,7 +1826,7 @@ - + @@ -1702,13 +1838,13 @@ - + - + - - + + @@ -1742,7 +1878,7 @@ - + @@ -1763,7 +1899,7 @@ - + @@ -1788,10 +1924,10 @@ - + - - + + @@ -1806,14 +1942,14 @@ - + - - - + + + - - + + @@ -1825,19 +1961,19 @@ - - - + + + - + - - - + + + - - + + @@ -1852,7 +1988,7 @@ - + @@ -1866,23 +2002,23 @@ - + - - - - + + + + - + - + - + - - + + @@ -1894,14 +2030,14 @@ - + - + - + @@ -1913,7 +2049,7 @@ - + @@ -1934,7 +2070,7 @@ - + @@ -1943,7 +2079,7 @@ - + @@ -1962,7 +2098,7 @@ - + @@ -1984,9 +2120,9 @@ - + - + @@ -2013,7 +2149,7 @@ - + @@ -2024,7 +2160,7 @@ - + @@ -2045,7 +2181,7 @@ - + @@ -2054,7 +2190,7 @@ - + @@ -2095,7 +2231,7 @@ - + @@ -2105,18 +2241,18 @@ - + - + - + - + - + @@ -2131,12 +2267,12 @@ - - + + - - - + + + @@ -2165,16 +2301,16 @@

- +
- + - + @@ -2189,55 +2325,55 @@ - + - + - + - + - - - + + + - - - - - - - + + + + + + + - - - - - - + + + + + + - + - + - - - + + + - + - - - - + + + + @@ -2249,7 +2385,7 @@ - + @@ -2261,11 +2397,11 @@ - + - - - + + + @@ -2294,46 +2430,46 @@ - - - + + + - - + + - - - + + + - + - + - + - - - - + + + + - + - - - + + + - - - - + + + + - + @@ -2345,10 +2481,10 @@ - - - - + + + + @@ -2359,9 +2495,9 @@

- +
- + @@ -2376,27 +2512,27 @@
- + - - - + + + - - - + + + - + - + - + @@ -2413,44 +2549,44 @@ - + - + - + - + - + - + - + - - + + - + - + - + - - - - + + + + @@ -2470,28 +2606,28 @@ - - - - + + + + - + - - - - + + + + - + - - + + - + - + @@ -2509,14 +2645,14 @@ - + - + - + @@ -2530,12 +2666,12 @@

- - - + + + - + @@ -2548,18 +2684,18 @@ - + - + - + - + - + @@ -2567,91 +2703,92 @@ - - - + + + - - - - + + + + - + - - - - + + + + - + - + - + - + - + - - - + + + - + - + - + - + - - - + + + + - + - + - + - + - + - + - + - + - + - + @@ -2667,7 +2804,7 @@ - + @@ -2687,72 +2824,80 @@ - - - - - + + + + + + + + + + + + + - + - + - + - + - + - + - - + + - + - + - + - - - - + + + + - + - + - + - + - + - - - + + + @@ -2783,47 +2928,47 @@ - + - - + + - - - - - + + + + + - - + + - - - + + + - + - + - + - - - - + + + + @@ -2838,7 +2983,7 @@ - + @@ -2850,9 +2995,9 @@ - - - + + + @@ -2864,19 +3009,19 @@ - - - - - - + + + + + + - + - + @@ -2902,36 +3047,37 @@

- - + + - + + + - - - + + - + - + - + - + - - - + + + @@ -2944,24 +3090,24 @@ - + - + - + - + - - - - + + + + - - - + + + @@ -2976,100 +3122,100 @@ - - + + - - - - + + + + - - + + - + - + - + - - - + + + - - - - - + + + + + - + - - - + + + - + - + - + - + - + - + - - - - + + + + - + - - + + - + - - + + - - + + - - - - + + + + - + - - - + + + @@ -3085,23 +3231,23 @@ - + - - - + + + - + - + - + @@ -3115,10 +3261,10 @@ - - + + - + @@ -3134,43 +3280,43 @@ - + - + - + - + - + - + - + - - - - + + + + - + - - - + + + - + - - - + + + - + @@ -3185,7 +3331,7 @@ - + @@ -3206,18 +3352,18 @@ - - + + - + - + - - + + @@ -3241,17 +3387,17 @@ - + - + - + @@ -3268,92 +3414,93 @@ - + - + - + - - + + - - - + + + - + - + - - + + - + - - + + - + + - - + + - + - - + + - + - + - + - - - - + + + + - + - + - + - + - - - - + + + + - + - + @@ -3365,127 +3512,9713 @@ - + - + - - + + - - - - - + + + + + - - - - - - - + + + + + + + - - - - - + + + + + - - - - - - - + + + + + + + - + - - + + - + - - - + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + +

+ UI-Koordinaten (UICoord) +

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ ...and this anchorage can be covered and backed by the currently existing UI configuration +

+ +
+
+ + + + + + +

+ ...by interpolation of some wildcards +

+ +
+
+ + + + + + +

+ ...need to be extended to allow anchoring +

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ we may construct the covered part of a given spec, including automatic anchoring. +

+ +
+
+ + + + + + + + + + + + + +

+ navigate to the real UI component +

+

+ designated by the given coordinate spec +

+ +
+ + + + + + + + + + + + +

+ ...halten wir besser raus aus diesem Design. +

+

+ Denn es würde stärkere Annahmen über die "Zielelemente" erforderlich machen, +

+

+ und diese dann doch wieder in ein Korsett zwängen. Im Moment (10/17) habe ich +

+

+ stark den Verdacht, daß wir das nur in wenigen Spezialfällen brauchen werden, +

+

+ und dann kann man es auch extern belassen. +

+ +
+ +
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + +

+ ...was für verschiedene Arten von Zugriff +

+

+ sind denkbar und müssen in der Strategy konfigurierbar sein? +

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ mögliche +

+

+ Komponenten +

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ wie funktioniert +

+

+ Pfad-Navigation? +

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + +

+ habe diese Analyse 2017/2018 ein Stück weit vorangetrieben. +

+

+ Ergebnis war die Schaffung von UI-Koordinaten und die ViewSpec-DSL +

+

+ +

+

+ Damit ist das Thema aber bei Weitem noch nicht ausgeschöpft, +

+

+ jedoch genügend aufgeschlossen, um die konkrete Implementierung fortzusetzen +

+ +
+ + + + + + + + + + + + + + + + + + + + + +

+ betrachte ich als ungesund +

+ +
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + +

+ eine reverse resolution +

+ +
+
+ + + + + + +

+ zentrales Problem +

+ +
+ + +
+ + + + + + + + + +

+ ...der beim Erstellen des Elements +

+

+ mit den zu diesem Zeitpunkt bekannten UI-Korrdinaten bestückt wird +

+ +
+
+
+ + + + + + + + + + + + + + +

+ auf welche Eigenschaften +

+

+ stützen wir uns? +

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ wer bestimmt, +

+

+ was "Kind" ist? +

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ oder den Spot verschieben +

+ +
+
+
+
+ + + + + + +

+ verbleibende +

+

+ Probleme +

+ +
+ + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ VariadicArgumentPicker_test +

+ +
+ +
+
+
+
+
+ + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ welche Operationen +

+

+ sind wirklich notwendig +

+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ ...denn wir verlangen, daß wir nach dem Interpolieren über eine Lücke +

+

+ immer noch mindestens ein explizt gegebenes Element im Pfad haben, +

+

+ welches auch von der UI-Topologie bestätigt wird. +

+

+ Grund: wir wollen vermeiden, abschließende Wildcards bloß irgendwie zu binden +

+ +
+
+ + + + + + +

+ nämlich wenn der Pfad mit einem explizit gegebenen Präfix anfängt, +

+

+ dann aber Wildcards enthält, die nicht nach den verschärften Bedingungen gecovert werden können. +

+ +
+
+ + + + + + +

+ beispielsweise +

+
    +
  • + wenn schon das Präfix nicht paßt +
  • +
  • + wenn das erste Element nach dem Gap nirgends im realen UI in der tiefe existiert +
  • +
  • + wenn mehr Wildcards da sind, als restliche Tiefe zum Matchen +
  • +
+ +
+
+
+ + + + + + + + + + + + + + +

+ letzten Endes war es nahezu gleich schwer zu implementieren, +

+

+ aber von der Aufruf-Logik her einfacher, stets nach partieller zu suchen +

+

+ und totale Coverage nur nachträglich durch Längenvergleich festzustellen +

+ +
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

+ wo der Spot ist +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + +

+ Innerhalb einer Kinder-Folge gibt es keine duplikaten IDs. +

+

+ Das heißt, es genügt, den ersten Match zu nehmen. +

+

+ Warnung: diese Konvention ist besonders tückisch, +

+

+ denn eine Verletzung kann weithin unbemerkt bleiben +

+ +
+ + +
+ + + + + + +

+ der Resolver macht kein Memory-Management, +

+

+ sondern speichert einfach Zeiger. +

+

+ Es wird erwartet, daß diese gültig bleiben, +

+

+ solange irgend jemand auf den Resolver oder den +

+

+ daraus resultierenden Pfad zugreift +

+ +
+ + +
+ + + + + + +

+ die Topologie, aber auch der Fokus-Zustand +

+

+ ändern sich nicht während der aktiven Lebensdauer eines Resolvers +

+

+ Hierbei ist aktive Lebensdauer wie bei einem Iterator zu verstehen. +

+ +
+ + +
+ + + + + + +

+ aber es gibt Konsistenzchecks + Exceptions +

+

+ wenn die Auswertung aufgrund einer gebrochenen Konvention entgleist +

+ +
+ + +
+
+ + + + + + + + + + +

+ Tiefensuche über die reale UI-Topologie +

+

+ Ziel ist, den Pfad bestmöglich zu covern +

+

+ Es gilt die erste maximal abdeckende Lösung +

+ +
+ +
+ + + + + + + + + + + + + + +

+ Tiefe, bis zu der dieser Pfad gedeckt ist. +

+

+ Sofern der Pfad bereits explizit ist, genügt diese Info allein +

+ +
+
+ + + + + + +

+ heap-allozierter expliziter Pfad. +

+
    +
  • + wird notwendig, wenn *this wildcards enthält +
  • +
  • + Lösung wird unter Alternativen ausgewählt (nach maximaler Tiefe) +
  • +
+ +
+
+
+
+ + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ Ha! das ist eine Monade!!!!!1!11! +

+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ ...aber im Moment der Lösung brauche ich den Pfad aufwärts. +

+ +
+
+ + + + + + +

+ Das heißt, wir müssen ihn bereits im Aufrufkontext bereit liegen haben +

+ +
+
+ + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ Puffer ab Tiefe +

+

+ vom Pfad initialisieren +

+ +
+ +
+
+ + + + + + +

+ monadische Lösung +

+

+ möglich? +

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + +

+ echte Expand-Funktion notwendig +

+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + +

+ mathematische Monaden sind viel mehr... +

+

+ Im Besonderen sind es Typen höherer Ordnung, +

+

+ also mehr als bloß parametrisierte Typen (Templates)! +

+ +
+
+ + + + + + + + + + + + + + + + + + +

+ alles mit einer Form des IterExplorer machbar ist +

+ +
+ + + + + +

+ ...da IterExplorer einen Template-Template-Parameter nimmt, +

+

+ ist er eigentlich ein Meta-Template, und es gibt diverse +

+

+ Ausprägungen, die alle subtile Seiteneffekte ausnutzen.... +

+

+ ...was nicht grade zur Verständlichkeit des Ganzen beiträgt +

+ +
+
+ + + + + + +

+ das Fortschreiten der Berechnung dargestellt werden kann +

+ +
+
+ + + + + + + + + + + + + + + + + +

+ Problem: Layer sind verkoppelt +

+ +
+ +
+
+ + + + + + + + +

+ dann aber als State Monad +

+ +
+
+ + + + + + + + + +

+ m >>= f = \r -> let (x, s) = m r +

+

+                             in (f x) s +

+ +
+ + + + + + + + + + + + + +

+ wende die resultierende State-Monade +

+

+ auf den Zwischenzustand x aus (1) an +

+ +
+ +
+
+
+ + + +
+ + + + +
+
+ + + + + + +

+ sind Monaden +

+

+ wirklich hilfreich? +

+ +
+ + + + + + + + + + + + + + + + + +

+ alternatives +

+

+ Ziel +

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + +

+ Fazit: +

+

+ brauche... +

+ +
+ + + + + + + + + + + + + + + + + + +

+ das impliziert grundsätzlich einen Stack +

+ +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ expand() ruft eine vorbereitete Parametrisierung +

+

+  für diesen Expand-Mechanismus auf +

+ +
+
+ + + + + + + + +

+ ...selbst wenn man ihn für eine triviale Implementierung +

+

+ eigentlich überhaupt nicht braucht. +

+

+ +

+

+ Das kann zwar zu einem gewissen Grad abgemildert werden, +

+

+ indem man einen speziellen Inline-Stack mit Heap-Overflow nutzt +

+ +
+
+
+ + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ ich brauche ihn nicht +

+ +
+
+ + + + + + +

+ die Builder-Operationen moven den bisherigen Iterator-compound weg. +

+

+ Ich könnte mir vorstellen, daß das einen naiven User ziemlich schockiert.... +

+

+ +

+

+ Lösung wäre, das Iterator-API erst nach einem expliziten terminalen Aufruf freizuschalten +

+ +
+ +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ weil... +

+
    +
  • + sich zwar die Logik syntaktisch anschreiben läßt +
  • +
  • + aber beide Zweige u.U nicht auf den gleichen Typ hinauslaufen +
  • +
  • + und erst in der Anwendung dieses Ausdruckes werden die Typen gleichgestellt +
  • +
+ +
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ _fun<FUN>::Sig scheitert +

+ +
+ +
+ + + + + + +

+ das ist aber unpraktisch.... +

+ +
+ +
+ + + + + + +

+ ....also ist es gradezu natürlich, +

+

+ einen Expand-Funktor als generisches Lambda zu schreiben! +

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +

+ aber eine falsche Template-Instantiierung +

+

+ ist ein Compile-Fehler, kein Substitutions-Fehler +

+ +
+
+
+
+
+ + + + + + + + + + + +

+ oder man fällt auf eine mögliche Substitution zurück +

+ +
+
+ + + + + + +

+ ...und wenn die Scheitert, ist das ein compile-Fehler +

+ +
+
+
+ + + + + + +

+ denn das ist der sinnvollste Fall für ein generisches Lambda +

+ +
+
+
+
+
+ + + + + + + + +

+ ....und das ist alarmierend, +

+

+ denn Debugging ist mindestens doppelt so schwer... +

+ +
+ + +
+ + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ TreeExplorer per slicing move entfernen +

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ wir haben bisher viel zu naiv angenommen, +

+

+ daß der parent-Iterator immer auch ein TreeExploer ist. +

+

+ +

+

+ Dem ist nicht so, ab dem Moment, wo wir mehrere Decorator-Layer haben!! +

+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ Aufrufpunkt: invokeTransformation() +

+ +
+ + + + + +

+ In instantiation of 'lib::{anonymous}::_ExpansionTraits<FUN, SRC>::Res lib::{anonymous}::_ExpansionTraits<FUN, SRC>::Functor::operator()(ARG&) [with ARG = long int; FUN = lib::test::IterTreeExplorer_test::verify_transformOperation()::<lambda(auto:2)>&; SRC = lib::iter_explorer::IterableDecorator<long int, lib::iter_explorer::WrappedIteratorCore<lib::TreeExplorer<lib::iter_explorer::StlRange<std::vector<long int>&> > > >; lib::{anonymous}::_ExpansionTraits<FUN, SRC>::Res = std::basic_string<char>]': +

+

+ +

+

+ src/lib/iter-tree-explorer.hpp:426:50: error: no match for call to '(std::function<std::basic_string<char>(lib::iter_explorer::IterableDecorator<long int, lib::iter_explorer::WrappedIteratorCore<lib::TreeExplorer<lib::iter_explorer::StlRange<std::vector<long int>&> > > >&)>) (long int&)' +

+ +
+ + + + + + + + + + + + + + + + +

+ also IterableDecorator aufgesetzt auf die Core im Transformer +

+ +
+
+ + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

+ wir gehen davon aus, +

+

+ daß der Optimizer solche inline-Accessor-Funktionen +

+

+ ohnehin restlos wegoptimieren wird +

+ +
+
+ + + + + + +

+ und zwar in dem Moment, wo man die Layer zusammensetzt. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + +

+ ...denn wenn mal ein Layer "nur" Iterator wäre, +

+

+ dann könnte es eine Kombination geben, die einen solchen Layer übersrpingt +

+ +
+
+ + + + + +
+ + + +
+
+
+
+ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ ...nur was billig ist, +

+

+ denn im Moment brauchen wir das überhaupt nicht +

+ +
+ +
+ + + + + + +

+ ...setzt eigentliche Expand-Operation darunter voraus +

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ ...also Programmierung analog zum Filter +

+
    +
  • + sofort aus dem Konstruktor heraus die Invariante etablieren +
  • +
  • + nach jedem Iterations-Schritt die Invariante erneut wiederherstellen +
  • +
+ +
+
+
+ + + + + + + + + + + + +

+ der Natur der Dinge folgen, +

+

+ nicht den technischen Möglichkeiten +

+ +
+ +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ ....und dann könnten diese Transformer in der Kette +

+

+ nicht mehr die expandChildren aufrufen +

+ +
+
+
+ + + + + + + + + +

+ ...das heißt, es ist nicht möglich, +

+

+ daß ein Layer irgendwo in der Mitte einen solchen generischen Hook aufruft. +

+

+ Es sei denn, man speichert einen Funktion-Pointer in der Basis, +

+

+ oder nimmt eben gleich eine virtuelle Funktion. +

+

+ +

+

+ Das ist aber hier aus grundsätzlichen Überlegungen heraus keine Option +

+ +
+
+
+ + + + + + + + + +

+ das IterStateWrapper-API ist optimal +

+ +
+ + + + + +

+ unter der Annahme, daß wir beim Lumiera Forward Iterator - Konzept bleiben +

+

+ +

+

+ -- das heißt, beliebig oft yield, und Iterations-Ende per bool()-Test +

+

+ +

+

+ Unter dieser Annahme kommt yield stets vor iterNext (wenn überhaupt). +

+

+ Und yield muß (a) einen Status liefern, (b) einen Wert liefern. +

+

+ Einziger Ausweg wäre, wie das IterAdapter macht, einen Pointer rauszugeben. +

+

+ Das ist eigentlich keine gute Lösung, weil die Implementierung dann sehr tricky wird. +

+

+ Siehe IterAdapter als abstoßendes Beispiel. +

+

+ Und als weitere Alternative bleibt nur die Einführung von State, und das bedeutet, +

+

+ sich im Iterator oder in der Implementierung irgendwo noch eine zusätzliche bool-Flag zu speichern. +

+ +
+
+
+ + + + + + + + + +

+ implementieren ebenfalls expandChildren() +

+ +
+
+
+
+
+ + + + + + + + + + + +

+ ganz anders als bei IterAdapter, wo das Sinn macht... +

+ +
+
+ + + + + + + + + +

+ ...ohne daß die Funktionen auch virtuell sind, +

+

+ können wir nicht sicherstellen +

+
    +
  • + daß die hereingereichte Implementierung die Funktionen überschreibt +
  • +
+ +
+
+ + + + + + + + +
+ + + + + + + + + + + + + + + +

+ Konzept funktioniert nicht +

+

+ aus processing-Function +

+

+ innen heraus +

+ +
+ + + + + + + +

+ will sagen, das ist ja auch eine durchgeknallte Idee.... +

+ +
+ +
+ + + + + + +

+ wenn eine Funktion in einem Layer expanded +

+ +
+
+ + + + + + +

+ ...d.h, entweder man gibt aus dem Functor das zurück, +

+

+ was vor dem expand anstand (=der Vater), oder man verwirft diesen +

+

+ und liefert das, was nach dem expand erscheind +

+ +
+
+ + + + + + + +
+ + + + + + + + + + + +
    +
  • + asIterSource() verwendet ein eigenes Interface, um diesen call an die Implementierung durchzureichen +
  • +
  • + in dieses Interface habe ich nun einen Rückgabewert eingebaut +
  • +
  • + damit kann ich das IterSource-Front-End refreshen +
  • +
  • + trotzdem hässlich... +
  • +
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ an welcher Stelle wird diese Mechanik +

+

+ an einen bestehenden Iterator angeschlossen +

+ +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ Stichwort HierarchyOrientationIndicator +

+

+ +

+

+ ...will sagen, bin nun schon mehrfach in dieses Problem gelaufen, +

+

+ nachdem ich dachte, alles so schön gelöst zu haben. +

+

+ Das Problem ist, daß eben auch der Konsument irgendwie +

+

+ von den verschachtelten Strukturen mit gesteuert wird. +

+

+ Das Ergebnis ist eben nicht rein linear. +

+ +
+
+ + + + + + + + + + +

+ ...das ist nämlich der triviale Workaround +

+ +
+
+ + + + + + +
+
+
+ + + + + + + + + + + + + + +

+ wir hatten bisher eine auto-Aufräum-Routine in iterNext(), +

+

+ welche dazu führt, daß ein erschöpfter Vater sofort weggeräumt wird, +

+

+ noch bevor wir dazu kommen, die Kinder zu pushen +

+ +
+
+ + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ jeder Zugriff auf ein Sub-Objekt muß durch eine VTable +

+ +
+
+ + + + + + +

+ Stichwort: virtual base offset +

+ +
+
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ ...da gibt es eine explizite Ausnahme-Regel. +

+

+ Die Copy-Konstruktoren werden aus der Kandidaten-Menge entfernt. +

+

+ +

+

+ Grund ist, daß die default-Initialisierung der Member-Felder noch nicht hinrechend geklärt war. +

+

+ C++17 holt das nach +

+ +
+
+ + + + + + +

+ ...dort wird einfach on-demand in der Basisklasse nachgeschaut. +

+

+ Wenn dabei ein Basis-Copy-Ctor gezogen wird, dann wird eben default-Init für die Felder im abgeleiteten Objekt gemacht. +

+

+ Es gibt dann eine neue, explizite Regel, die verhindert, daß zufällig ein aus der Basis geerbter Ctor +

+

+ die Signatur eines Copy-ctors überdeckt +

+ +
+
+
+ + + + + + +

+ Allerdings genügt es, dies an einer Stelle in der Kette zu ergänzen +

+ +
+ + + + + +

+ ...und zwar genau dort, wo erstmals ein Basis-Objekt akzeptiert wird. +

+

+ Das ist bei uns im BaseAdapter, also der ersten Ebene über dem zu initialisierenden Basis-Objekt. +

+

+ Alle anderen Layer darüber reichen dann korrekt mit dem geerbten Ctor diese Initialisierung nach Unten. +

+ +
+
+ + + + + + +

+ übrigens ist es im IterSource<T>::iterator nicht  notwendig +

+ +
+ + + + + +

+ ...das war nur ein unnötiger Fix nach dem Gießkannen-Prinzip. +

+

+ +

+

+ Denn dieser Iterator soll niemals mit einem Basis-Objekt initialisiert werden, +

+

+ sondern stets von der IterSource-Builder-Funktion konstruiert. +

+

+ Und wenn man selber keinen Ctor in eine Klasse schreibt, sondern nur ctor-erbt, +

+

+ dann werden auch die Copy-Konstruktoren korrekt automatisch generiert. +

+ +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ Ticket machen: #1125 +

+ +
+ + +
+
+
+
+
+ + + + + + + + + + + +

+ ...daß man ein Ding komplett in einen Iterator packt, +

+

+ und dieser es dann auch managed +

+ +
+ + + + + + +

+ ist das #190 +

+ +
+ + + + + + + + + + +
+ + + + + + +

+ ...ist partiell diese Idee. +

+

+ Nur auch das auf einem etwas anderem Level, +

+

+ und immer mit einem Heap-allozierten vector +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ versehentlich wurde auch der an std::forward gegeben +

+ +
+
+ + + + + + +

+ habs mit FormatUtils_test bewiesen +

+

+ Dazu in NumIter einen explizit tracenden move-ctor eingebaut +

+ +
+
+
+ + + + + + +

+ weil der Aufruf von join(&&) selber wasserdicht ist +

+

+ D.h. er frisst keine Werte. +

+

+ Deshalb fällt dieses doppelte Problem nicht auf +

+ +
+ + + + + + + + + + + + + + +
+ + + + + + + + + +

+ ...weil man den konkreten Typ der Core kennen muß +

+ +
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ verschiedendste Pipeline-Konstruktionen +

+

+ können nun hinter dem gleichen Interface sitzen +

+ +
+ +
+ + + + + + + + + + + + + + + + + + + +

+ rein ein Problem mit der Test-Fixture. +

+

+ Da die Quelle nun von einem shared-ptr gehalten wird, +

+

+ erzeugt eine Kopie des Iterator-Front-End +

+

+ nun nicht mehr eine Kopie des ganzen Zustandes. +

+

+ +

+

+ das wäre aber bequem für den Test. +

+

+ Frage: ist das überhaupt eine gute Idee, vom Design her?? +

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ siehe std::shuffle +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ vorgegebene Zahlenfolge
in untendlichem Zufalls-Baum finden +

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ man kann move(iter) verwenden, +

+

+ wenn man konsumieren möchte +

+ +
+
+ + + + +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ rLet(40878 < 18446744073709551615) → S +

+

+ |↯| S ... 40878 +

+

+ rLet(40879 < 18446744073709551615) → F +

+

+ |!| expand 40879 +

+

+ rLet(0 < 4) → A +

+

+ rLet(40880 < 18446744073709551615) → Q +

+

+ |.| A -->> 40879 +

+

+ |!| expand 40879 +

+

+ rLet(0 < 4) → F +

+

+ rLet(1 < 4) → N +

+

+ |.| F -->> 40879 +

+

+ |↯| F ... 40879 +

+

+ rLet(1 < 4) → W +

+

+ |↯| W ... 40879-0-1 +

+

+ rLet(2 < 4) → N +

+

+ |↯| N ... 40879-0-2 +

+

+ rLet(3 < 4) → T +

+

+ |↯| T ... 40879-0-3 +

+

+ rLet(4 < 4) → F +

+

+ |↯| N ... 40879-1 +

+

+ rLet(2 < 4) → F +

+

+ |↯| F ... 40879-2 +

+

+ rLet(3 < 4) → A +

+

+ |!| expand 40879-3 +

+

+ rLet(0 < 4) → J +

+

+ rLet(4 < 4) → Y +

+

+ |.| J -->> 40879-3 +

+

+ |↯| J ... 40879-3 +

+

+ rLet(1 < 4) → H +

+

+ |↯| H ... 40879-4 +

+

+ rLet(2 < 4) → H +

+

+ |↯| H ... 40879-5 +

+

+ rLet(3 < 4) → F +

+

+ |↯| F ... 40879-6 +

+

+ rLet(4 < 4) → V +

+

+ |↯| Q ... 40880 +

+

+ rLet(40881 < 18446744073709551615) → A +

+

+ |↯| A ... 40881 +

+

+ rLet(40882 < 18446744073709551615) → X +

+

+ |↯| X ... 40882 +

+ +
+
+ + + + + + +
+ + + + + + +

+ rLet(77943 < 18446744073709551615) → R +

+

+ |↯| R ... 77943 +

+

+ rLet(77944 < 18446744073709551615) → X +

+

+ |!| expand 77944 +

+

+ rLet(0 < 4) → U +

+

+ rLet(77945 < 18446744073709551615) → I +

+

+ |.| U -->> 77944-0 +

+

+ |↯| U ... 77944-0 +

+

+ rLet(1 < 4) → X +

+

+ |!| expand 77944-1 +

+

+ rLet(0 < 4) → K +

+

+ rLet(2 < 4) → Z +

+

+ |.| K -->> 77944-1-0 +

+

+ |!| expand 77944-1-0 +

+

+ rLet(0 < 4) → V +

+

+ rLet(1 < 4) → Y +

+

+ |.| V -->> 77944-1-0-0 +

+

+ |↯| V ... 77944-1-0-0 +

+

+ rLet(1 < 4) → I +

+

+ |↯| I ... 77944-1-0-1 +

+

+ rLet(2 < 4) → I +

+

+ |↯| I ... 77944-1-0-2 +

+

+ rLet(3 < 4) → Z +

+

+ |!| expand 77944-1-0-3 +

+

+ rLet(0 < 4) → Q +

+

+ rLet(4 < 4) → X +

+

+ |.| Q -->> 77944-1-0-3-0 +

+

+ |↯| Q ... 77944-1-0-3-0 +

+

+ rLet(1 < 4) → M +

+

+ Protocol of the search: 77944-1-0-3-1 +

+ +
+ +
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ ...weil es mutmaßlich +

+

+ im realen UI in ähnlicher Form auch auftreten wird: +

+

+ die Menge der Top-Level-Fenster ist eben etwas anderes, +

+

+ als die Menge der Tracks in der Timeline. +

+

+ +

+

+ Erst nach einer Transformation wird daraus eine Menge von Strings +

+ +
+ +
+ + + + + + + + + + + + +

+ Expand-Funktor hat einen Rückgabe-Typ +

+ +
+ + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ wenn es sie gäbe könnte man sie hier nutzen +

+ +
+
+ + + + + + + + + + + +

+ ...das heißt: +

+

+ diese Struktur muß bereits beim Aufbauen des GUI +

+

+ nebenbei mit aufgebaut werden, und über alle +

+

+ mutierenden Aktionen hinweg automatisch konsistent bleiben +

+ +
+
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + +

+ möglicherwese aber notwendig +

+ +
+ + + + + +

+ ...will sagen +

+

+ wenn ich mir heute so die Situation vorstelle, +

+

+ könnte es darauf hinauslaufen, daß man das braucht. +

+

+ Und zwar, zumindest die Eigenschaft, von gegebenem Element +

+

+ die Koordinaten zu ermitteln. +

+

+ +

+

+ Das ist aber dann pratkisch auch schon eine "Up"-Funktion, +

+

+ selbst wenn man sie nur indirekt implementiert +

+ +
+
+
+
+ + + + + + +

+ vorläufige MInimal-Lösung +

+ +
+ + + + + + + + + + +

+ bis jetzt kommen wir ohne Pos-Abstraktion aus +

+ +
+ +
+ + + + + + +

+ das Nav-Interface könnte daraus entstehen +

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +

+ es werden jetzt keine weiteren Features für TreeExplorer gebaut.... +

+ +
+
+ + + + + + + + +

+ ...mal sehen, ob wir jemals daran anstoßen... +

+ +
+ +
+ + + + + + + + +

+ gemeint ist, +

+

+ zusätzlich zu dem Eintrag im Stack, +

+

+ der ohnehin selbst Heap-alloziert ist +

+ +
+ + + + + + + + + + + + + + + + + + +

+ Was ist Nav und was ist Iteration-control? +

+

+ Ist es sinnvoll, beide in einem gemeinsamen API zu haben, +

+

+ oder delegieren wir besser? +

+

+ Was sind die Kosten dafür? +

+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ IterSource muß insgesamt besser erweiterbar werden.... +

+ +
+
+
+ + + + + + + + + + + +

+ der Expander sitzt nun doch dahinter, in der Implementierung +

+ +
+
+ + + +
+
+
+ + + + + + + + + + + + + +

+ das Ergebnis ist der konkrete Iterator-Typ +

+ +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ Wire-Tap-Implementierung +

+ +
+ +
+ + + + + + + + + + + + + + + + + + +

+ das heißt, depth ist aktuelle Tiefe! +

+ +
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ ist keine Lösung +

+ +
+ +
+
+ + + + + + + + + + + +

+ ist partielle Lösung +

+ +
+ +
+
+
+ + + +
+ + + + + + + + + +

+ Ergebnis-Ausgabe ist die jeweilige mögliche Coverage +

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ ...insofern wir nur eine (partielle) Lösung signalisieren, +

+

+ wenn wir einen direkten Match erziehlen. +

+

+ Ein wildcard-Match führt nur dazu, daß wir zu den Kindern absteigen, +

+

+ aber zählt erst mal für sich nicht als Lösung +

+ +
+ +
+ + + + + + +

+ Support für elided element +

+ +
+ + + +
+
+ + + + + + + + + + + + + + + + +
    +
  • + nur für internen Gebrauch +
  • +
  • + protected im Builder +
  • +
+ +
+ +
+ + + + + + + + + + + + + + + +

+ Lösungen müssen +

+

+ am Ende des Patterns liegen +

+ +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ YAGNI +

+ +
+ + + + + + + + + + + + + + +
+ + + + + + + + +
    +
  • + die Standard-Implementierung von std::swap macht einen Dreiecks-Move +
  • +
  • + wir haben effiziente Move-Konstruktoren +
  • +
+ +
+
+ + + + + + + + + + + + + + + +
+ + + + + + + + + + + +

+ ...und nicht den möglichen Zustand. +

+

+ Denn für letzteren gibt es die "canXX"-Prädikate +

+ +
+ +
+ + + +
+
+ + + + + + + + + + + + + + + + + + + +

+ macht es Sinn, dafür einen expliziten Testfall zu konstruieren, +

+

+ oder verfangen wir uns da sofort zu sehr in der Implementierungs-Technik? +

+ +
+ + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ realer Pfad endet mit elided nach Wildcard +

+ +
+ + + + + + + + + +
+ + + + + + + + + + + +

+ kann jedoch demonstrieren, +

+

+ daß der Algorithmus solche Lösungen verwirft +

+ +
+ +
+
+ + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + + + + + + + + + + + + + +

+ neue Einsicht 31.12.17 +

+

+ totale Coverage ist das, was man naiverweise erwartet. +

+

+ Also sollte das auf dem API der default sein +

+ +
+ +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ ...das heißt: keine Wildcards, keine pseudo-Specs (currentWindow) +

+ +
+ +
+ + + + + + + + + + + +

+ Zweck ist vor allem, meta-Specs wie firstWindow, currentWindow aufzulösen +

+ +
+
+
+ + + + + + + + + + + + + + + + + + +

+ ...verwendet einen GenNode-Tree +

+

+ als Repräsentation des real-existierenden UI +

+ +
+ +
+
+ + + + + + +

+ Integration ViewLocator +

+

+ Resolver / Navigator +

+ +
+ + + + + + + + + + + + + + + + + +

+ aber auch: Resolver +

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + +

+ die Regel ist: +

+

+ Bei zyklischen Abhängigkeiten erfolgt der Ringschluß +

+

+ an einer Stelle über eine allgemeine Abstraktion +

+ +
+ +
+
+
+ + + + + + + + + + + +

+ implementiert LocationQuery +

+ +
+ + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +

+ ...als Namespace-globale Variable mit externer Linkage +

+ +
+
+ + + + + + +

+ Ein Lookup-Vorgang ist schon ehr aufwendig, +

+

+ jedoch harmlos im Vergleich zu einer einzigen Frame-Berechnung. +

+
    +
  • + im ersten Schritt machen wir eine Tiefensuche potentiell über die ganze UI-Topologie +
  • +
  • + im zweiten Schritt wiederholen wir noch mal den Abstiegspfad zur Lösung des ersten Schrittes +
  • +
+

+ Grundsätzlich gilt hier die Einschätzung: Klarheit der Schnittstelle hat Vorrang +

+ + +
+ + +
+
+ + + + + + + + + + + + + + + + + + + +

+ ViewLocator ruft die DSL auf +

+ +
+
+ + + + + + + +

+ ...welcher wiederum von ViewLocator betrieben +

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ sonst bekommen wir eine versteckte +

+

+ zweite hart-gecodete Fallback-Konfig +

+ +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ existing() sollte default sein und create() explizit anzufordern +

+ +
+ + + + + +

+ weil der größte Teil aller real anzugebenden Regel-Klauseln +

+

+ von der Bedeutung her "existing" meint +

+ +
+
+ + + + + + + + + + + + + + + + + + + +

+ komplett definierter Pfad incl Zielobjekt +

+ +
+
+ + + + + + +

+ ggfs wird höchstes ein abschließendes Element hinzugefügt +

+ +
+
+ + + + + + +

+ dieser Pfad ist stets anchored und partially covered +

+ +
+
+
+
+ + + + + + + + + + + + + + + + + + + +

+ zwar könnte es (später mal) sein, +

+

+ daß wir mehrere Perspektiven gleichzeitig in die UI-Topologie abbilden... +

+ +
+
+ + + + + + +

+ ...weil die Perspektive eigentlich als etwas Orthogonales empfunden wird, +

+

+ das nicht dirket zur "harten" Topologie gehört, sondern vielmehr bestehende Elemente umgruppiert. +

+

+ Andererseits möchte man eben doch manchmal eine View-Spec eigens auf eine bestimmte Perspektive beschränken, +

+

+ und deshalb habe ich die Perspektive zu den UI-Koordinaten hinzugenommen +

+ +
+
+ + + + + + +

+ ...und zwar genau dann, wenn bereits die nächste Komponente unterhalb der Perspektive, +

+

+ also das Pannel, nicht oder nicht in dieser Form existiert, also erzeugt werden müßte. +

+

+ Unser Kriterium für Lösungen jedoch verlangt mindestens einen Match jenseits der Wildcards, +

+

+ um den Match eindeutig zu machen. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + +

+ ...sofern es stets eine Perspektive geben muß +

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + +

+ ...und zwar zwingend notwendig, weil es (viele) Views geben wird, +

+

+ welche keine mehrfachen TABs unterstützen. In solchen Fällen brauchen wir +

+

+ ein Konstrukt, mit dem sich eine Ebene im Baum überspringen läßt +

+ +
+
+ + + + + + +

+ "muß" ist relativ, denn mit den bisherigen Anforderungen +

+

+ hätte es genügt, den "elided"-Platzhalter nur in den konkreten UI-Koordinaten +

+

+ zu verwenden, und ihn dann jeweils per Wildcard zu matchen (was automatisch passiert, +

+

+ einfach wenn die betreffende Komponente in der Angabe fehlt) +

+ +
+
+ + + + + + +

+ Die korrekte Semantik fällt uns hier wirklich in den Schoß, +

+

+ es ist nur eine weitere Zeile in dem Test, ob ein Match vorliegt +

+ +
+
+
+ + + + + + + + +

+ wobei einer, nämlich '*' sehr offensichtlich und bekannt ist, +

+

+ während der andere (eben dieses '.' == elided) eigens erklärt werden muß +

+ +
+
+ + + + + + +

+ ...weil es eigentlich ein Wildcard ist, +

+

+ aber vom gesamten sonstigen Algorithmus nicht als Wildcard behandelt wird. +

+

+ Damit kann man alle Einschränkungen unterlaufen +

+ +
+
+ + + + + + +

+ Korrekter Gebrauch setzt eigentlich voraus, +

+

+ daß es an dieser Stelle auch tatsächlich "gar nichts" oder nur "ein stets festes Element" gibt. +

+

+ Für die Perspektive ist das (nach jetztiger Planung) stets gegeben. +

+

+ +

+

+ Wenn man allerdings diese Bedingung verletzt, dann matcht der "elided"-Platzhalter +

+

+ in mehreren alternativen Zweigen wie ein Wildcard, und es hängt dann von +

+

+ zufälligen Umständen ab, ob man die erwartete Lösung bekommt +

+ +
+
+
+
+
+ + + + +
+ + + + + + + + + + + + + + +

+ bloß würde sich die Signatur der DSL-Bausteine ändern: +

+

+ Allocator = std::function<UICoord(UICoordResolver)>; +

+ +
+
+ + + + + + +

+ ...denn Reolver ist ein UICoord::Builder und als Solcher non-copyable. +

+

+ Also würde das ganze Gefrickel mit Referenzen losgehen, +

+

+ in einem Stück Metaprogramming-Code, das ohnenhin schon ziemlich "dicht" ist.... +

+ +
+
+ + + + + + +

+ Der Punkt ist: das ist eine reine Lauzeit/Effzienz-Überlegung. +

+

+ +

+

+ Nachdem das Pfad-Matching in der DSL für die Location die passende Lösung gewählt hat, +

+

+ wäre -- im UICoordResolver eben -- auch schon die effektive Coverage bekannt. +

+

+ Da aber unser API nach (reinen) UI-Coord verlangt, werden diese aus der berechneten Lösung +

+

+ herausbewegt. Und der Allokator muß sich dann erneut einen UICoordResolver bauen, +

+

+ oder zumindest das LocationQuery-Interface bemühen, welches dann nochmal den Baum +

+

+ traversiert um die Coverage festzustellen. +

+
    +
  • + Ja, das kostet und ist verschwenderisch... +
  • +
  • + und Ja, vermutlich sind die paar CPU-Zyklen komplett egal +
  • +
+ +
+
+ + + + + + +

+ das ist der klassische Fall, wo man wegen einer solchen Optimierung +

+

+ sich ein Interface versaut und ziemliche zusätzliche Komplexitäten an Bord zieht. +

+ +
+ +
+
+ + + +
+ + + + + + + + + + + + + + + + +

+ ...insofern auch dort +

+

+ die jeweilige generische Regel parametrisiert / instantiiert wird +

+

+ gegen den Kontext, mit dem sie matchen soll +

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ das ist eine typische, rein lokale Optimierung (Speicher vs CPU) +

+

+ Ein solcher View-Index sollte dann ebenfalls via LocationQuery exponiert werden +

+ +
+ +
+
+ + + + + + + + +
+ + + + + + + + + + + + + +

+ es gibt nur einen "Locator" +

+ +
+
+ + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ ein LocationSolver +

+ +
+ +
+ + + + + + +

+ LocationQuery qua Navigator +

+ +
+ +
+ + + + + + +

+ ...die es hinten herum bekommt +

+ +
+
+
+ + + + + + + + +

+ sehen ViewLocator-API +

+ +
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + +

+ Regel-Parametrisierung +

+

+ Kontextualisierung +

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + +

+ Man möchte, daß für spezielle Sub-Elemente, +

+

+ die aus einem fremden Kontext heraus geöffnet werden, +

+

+ zunächst versucht wird, einen irgendwo im UI schon bestehenden TAB +

+

+ für speziell diesen Element-Typ wiederzuverwenden; das erlaubt dem User, +

+

+ sich einen Platz für sehr spezielle Sachen beiseite zu setzen. +

+

+ +

+

+ z.B. sehr spezielle Assets oder ein virtueller Clip. +

+

+ +

+

+ Erst wenn so ein Ort nicht gefunden wird, möchte man auf einen +

+

+ generischen Ort zurückfallen, und erst als letzte default-Lösung +

+

+ im aktuellen Fenster einen völlig neuen UI-Elementkontext schaffen. +

+ +
+ +
+ + + + + + + +

+ ...statt die gesamte Matching-Engine mit einer Art +

+

+ halbgaren Unifikation aufzubrezln +

+ +
+
+ + + + + + + + + + + + + + +

+ nur eingeschränkt auf die TypID? +

+ +
+ +
+ + + + + + + + +

+ Preprocessing beim Anlegen der Klausel +

+ +
+
+ + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ bleibt dem Charakter nach imperativ +

+ +
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ was ist der Locator? +

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ der Level im UI ist noch offen +

+ +
+
+ + + + + + + + + +

+ fast immer ist das aber UIC_VIEW +

+ +
+ + + + + +

+ im Moment fällt mir überhaupt keine Ausnahme ein +

+

+ aber man soll niemals nie sagen; +

+

+ +

+

+ jedenfalls ist der LocationSolver komplett generisch geschrieben, +

+

+ wäre ja auch dämlich, den auf einen Level festzunageln +

+ +
+
+ + + + + + +

+ kann man den Level erschließen? +

+ +
+ + + + + + + + + + +

+ es ist nicht klar, ob die pattern bereits das fragliche View-Element mit einschließen, +

+

+ oder ob das View-Element noch angehängt werden soll. Diese Variation ist essentiell, +

+

+ um Regeln auszudrücken, die explizit nur eine schon existierende UI-Komponente greifen +

+ +
+ +
+
+ + + + + + + + + + + + + +
+
+ + + + + + + +

+ auto locate = matchView( +

+

+                           panel("blah") +

+

+                           or currentWindow().panel("blubb").create() ) +

+ +
+ +
+ + + + + + +

+ LocatorSpec<UIC_VIEW> locate = panel("blah") +

+

+                                                      or currentWindow().panel("blubb").create() +

+ +
+ +
+ + + + + + +

+ ViewSpec locate = panel("blah") +

+

+                                 or currentWindow().panel("blubb").create() +

+ +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ Anwendung delegiert an einen Serivce +

+ +
+ + + + + + + +

+ für LocationQuery +

+ +
+ +
+ + + + + + +

+ für LocationSolver +

+ +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ Schreibweise für create Clauses +

+ +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ technische Lösug diskutierbar +

+ +
+ + + + + +

+ ...will sagen, +

+

+ man kann das erheblich tief und generisch ausbauen +

+ +
+
+ + + + +
+ + + + + + +

+ Perspective elided +

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

+ ...denn durch overwrite kann man denormalisierte Pattern erzeugen. +

+

+ Also muß jeder Anwender dieser Funktion sicherstellen, daß dies +

+

+ entweder nicht passieren kann, oder explizit normalise() aufrufen. +

+ +
+ +
+ + + +
+ + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ ...das würde bedeuten, daß man sogar ein neues Hauptfenster erzeugt. +

+

+ Also in diesem Fall würde überhaupt nichts mit dem existierenden UI matchen... +

+ +
+ + + + + + + +
+
+ + + + + + +

+ Komponente falls nötig anhängen +

+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ ...weil wir keinen Zustand sammeln +

+

+ und daher jede Klausel von Grund auf neu lösen. +

+ +
+ +
+ + + + + + + + + + + + + +

+ könnte man zulassen +

+ +
+ +
+ + + +
+ + + + + + + + + + + + + + + + + +

+ not empty? UND +

+

+ Create ODER totalyCovered? +

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ Komma heißt "and then" in der Logikprogrammierung +

+ +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ nur "hinten herum" über die verwendete LocationQuery +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ klassischer Fall von »premature optimisation« +

+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + +

+ wer interpretiert +

+

+ UI-Koordinaten +

+ +
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + +

+ um eine Position zu kennzeichnen +

+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ nämlich in lib::meta::func::PApply::bindBack +

+ +
+
+ + + + + + +
+ + + + + + + + + + + + + + + +

+ // generic lambda, operator() is a template with one parameter +

+

+ auto vglambda = [](auto printer) { +

+

+     return [=](auto&&... ts) // generic lambda, ts is a parameter pack +

+

+     { +

+

+         printer(std::forward<decltype(ts)>(ts)...); +

+

+         return [=] { printer(ts...); }; // nullary lambda (takes no parameters) +

+

+     }; +

+

+ }; +

+ +
+ +
+ + + + + + + + + + + + + + + + +

+ Vermutung: muß Lambda instantiieren... +

+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ ...merke +

+

+ die Spezialbehandlung für const& gilt nur, wenn wir direkt auch diesen Typ nehmen. +

+

+ Im vorliegenden fall wird aber der conversion-Operator aufgerufen, um den Initializser zu erzeugen. +

+

+ Daher denkt der Compiler, er kann das Ursprungsobjekt jezt wergwerfen. +

+ +
+ +
+
+ + + + + + +

+ Spezialbehandlung +

+

+ Perspektive +

+ +
+ + + + + + + +

+ ...um zu prüfen, ob das allgemeine Design +

+

+ mit solchen Asymetrien umgehen kann, +

+

+ welche ziemlich sicher noch viel mehr +

+

+ bei der Navigation in einem realen GUI auftreten +

+ +
+
+ + + + + + + + + + + + + + + + + +

+ ...und ich hab mir letzte Woche noch solche Vorwürfe gemacht, +

+

+ daß ich mich wieder mal "akademisch" verspielt habe.... :-P +

+ +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + +

+ ...und die IterSource dann nur über WrapIter definieren. +

+

+ Schichten-Prinzip... +

+ +
+
+ + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ das ist immer schon korrekt erledigt +

+ +
+ + + + + +
+ + + + + + + + + + + + + + + + + + +

+ ...und das heißt. +

+

+ ein Value wird auch sofort konstruiert, +

+

+ egal, ob man den dann gleich wegwirft. +

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ nur einen Satz Klauseln +

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ ...denn irgendwann wird's lächerlich mit der Unit-Testerei. +

+

+ Oder zumindest Hexagonal. +

+

+ Bedingt durch die ganzen rausgezogenen Interfaces hat jetzt bereits ViewLocator überhaupt keinen Gehalt mehr. +

+

+ Wenn ich jetzt auch noch die einzige verbleibende Methode rausziehe, um sie testen zu können, +

+

+ drehe ich mich komplett im Kreis. Schließlich kann ich diese Methode ja, genau genommen, +

+

+ im Moment auch noch nicht wirklich testen, aus genau den gleichen Gründen, +

+

+ warum ViewLocator so nebulös bleibt: es gibt noch kein Lumiera GUI +

+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ Das ursprüngliche Ziel für diesen Test +

+

+ ist in unserem Test-Framework nicht realisierbar +

+ + +
+ + + + + + + + + + + + + + +

+ Policy: Unit-Tests dürfen keine GTK-Abhängigkeit haben +

+ + +
+
+ + + + + + + + + + +
+
+
+
+ + + + + + + + + + + + + + +
    +
  • + wird mäßig häufig aufgerufen +
  • +
  • + beim "Öffnen" und zur Navigation +
  • +
  • + im Interaktions-Kontext +
  • +
  • + keinen Speicherdruck erzeugen +
  • +
+ +
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ ...es ist im Rahmen; +

+

+ denn wir akzeptieren double dispatch sogar in der Diff-Anwendung, +

+

+ welche viel häufiger läuft, als dieser View-Zugriff hier. +

+

+ +

+

+ Allerdings, die doppelte Indirektion ist nicht grundsätzlich notwendig hier, +

+

+ da wir nur einen einzigen Anwendungsfall haben. Die zweite Indirektion in jedem Aufruf +

+

+ bewirkt nur eine Entkoppelung vom Implementierungs-Kontext +

+ +
+
+
+ + + + + + + + +

+ wir brauchen keine Token +

+ +
+ +
+ + + + + + +

+ ....anders als im Diff-Framework +

+

+ senden wir hier keine beliebigen Nachrichten, +

+

+ sondern interpretieren jeweils nur eine einzige feste Konfiguration +

+ +
+
+ + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + +

+ Allokator pro Typ +

+ +
+ + + + + + + + + + + + + + + + + + + + +

+ ...wir brauchen eine Repräsentation, +

+

+ um auszudrücken, daß gewissen Angaben ausgelassen wurden +

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ Alternative: wrap UI-Coord, +

+

+ thin augmentation layer +

+ +
+ +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

+ alloc = unlimited +

+

+ locate = panel(timeline) +

+ +
+
+
+ + + + + + + + +

+ alloc = onlyOne +

+

+ locate = external(beamer) +

+

+                or perspective(mediaView).panel(viewer) +

+

+                or existingPanel(viewer) +

+

+                or firstWindow().panel(viewer) +

+ +
+
+ + + + + + + +

+ alloc = limitPerWindow(2) +

+

+ locate = perspective(edit).existingPanel(viewer) +

+

+                or currentWindow().existingPanel(viewer) +

+

+                or existingPanel(viewer) +

+

+                or panel(viewer) +

+ +
+
+
+ + + + + + + +

+ im Asset-Panel der jeweiligen Gruppe hinzufügen +

+ +
+
+ + + + + + +

+ alloc = unlimited +

+

+ locate = currentWindow().perspective(edit).existingPanel(asset).existingGroup() +

+

+                or perspective(asset)panel(asset) +

+

+                or firstWindow().panel(asset) +

+ +
+
+
+ + + + + + + + +

+ alloc = limitPerWindow(1) +

+

+ locate = currentWindow().existingPanel(infobox) +

+

+                or firstWindow().panel(infobox) +

+ +
+
+
+ + + + + +
+ + + + + + + + + + + +

+ Voraussetzung: Anwendbarkeit erkennen +

+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ was hier vielleicht der Fall sein könnte +

+ +
+ +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

+ wir haben nicht einfach UI-Coordinaten als DSl-Elemente, +

+

+ sondern einzelne Klauseln, die allerdings jeweils eine UI-Coord wrappen. +

+

+ +

+

+ Wenn man jedoch, rein syntaktisch zu schreiben beginnt "UICoord::window()" +

+

+ dann bekommt man einen UICoord::Builder  und das ist noch keine Klausel! +

+ +
+ +
+ + + + + + + + +
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

+ Schicht unter dem ViewLocator +

+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ model::Tangible ist schön, +

+

+ aber ich weiß nicht, ob das nicht zu eingeschränkt ist. +

+

+ Beispielsweise werden Panel oder WorkspaceWindow ganz sicher keine Tangibles sein, +

+

+ aber es könnte durchaus sein, daß man auf sie generisch zugreifen möchte +

+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ Zwei Fälle sind hier zu unterscheiden: +

+
    +
  1. + der gewünschte Wert existiert nicht, und das ist auch das Ergebnis der Anfrage +
  2. +
  3. + es liegt eine Fehleinschätzung der Situation vor, insofern fest mit einem Ergebnis gerechnet wurde +
  4. +
+

+ In Fall-1 wird man eine bool-Abfrage machen wollen, und man kann auch mit einer false-Antwort umgehen. In Fall-2 dagegen bleibt nur noch der Tod. Und davon ist im Regelfall nicht auszugehen. Im Moment sehe ich Fall-2 als den standard-use-Case +

+ + +
+
+ + + + + + +
    +
  • + im Fall-1 weiß der Client, daß er prüfen muß +
  • +
  • + im Fall-2 marschiert der Client einfach durch +
  • +
+ + +
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ das mag überraschend klingen, +

+

+ aber in der (zu erwartenden) Nutzsituation interessiert sich keine Sau dafür, +

+

+ was denn nun konkret gemacht wurde, um den Dienst zu erbringen. +

+

+ Die einzig interessante Information ist, ob es gelungen ist +

+ + +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + +

+ aber genau das ist hier jeweils nur in einem Fall gegeben +

+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - + + - + - + - - - - + + + + - + - - + + - - + + - + - - - + + + - - - - - + + + + + + + + - - - + + + - + - + - + @@ -3500,7 +13233,7 @@ - + @@ -3513,59 +13246,101 @@ - + - + - - + + - - - + + + + + + +

+ Problem: Zusammenarbeit +

+

+ mit docking panels +

+ +
+ + + + + + + + + + + + + + + + +
+ + + + + + + +

+ Grundlagen für InteractionControl +

+ +
+ + - - - + + + - + - + - + - + - + - + - + - + - + - - - - - - + + + + + + @@ -3579,16 +13354,16 @@ - + - + - + - - - + + + @@ -3606,70 +13381,127 @@ - + - - - - - + + + + + - + - - - - - + + + + + - - - - + + + + - - - - - + + + + + + + - - - + + + + + + + + + + + + + + - + - - + + - - - + + + - - + + + + + + + + + + +

+ in generischer UI-Struktur bewegen +

+ +
- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + @@ -3682,8 +13514,8 @@ - - + + @@ -3696,141 +13528,264 @@ - + - + + - + - + - - - + + + - + - + - + + + + + + + + + + + + + + + + + + + +

+ Abstraktion zur Steuerung schaffen +

+ +
+ + + + + + + + + + + + + + + + + +

+ wie bestimmt? +

+ +
+ + + + + + + + + + + + + + + + + - + + + + + + + + - - - - + + + + - - - - - - - - - - + + + + + + + + + + - + - - - + + + - - - - - + + + + + - + - + - - + + + + - + + + + + + + + + + + +

+ das Diff wird auf den Platzhalter angewendet +

+ +
+
+ + + + + + +

+ wenn das Diff ein Element aus einer Kind-Menge wegfallen läßt, +

+

+ dann muß dieses automatisch deregistriert werden +

+ +
+
+ + + + + + +

+ vermittelt über den ViewLocator (InteractionDirector) +

+ +
+
- + + - + - - + + - + - + - + + + + + + + - - + + + - - - + + + - + - + - + - + - + - + - + - + - + - + - + - + @@ -3842,45 +13797,55 @@ - - - + + + - - - + + + - - - - - - - - + + + + + + + + - - - - - - + + + + - - + + + + + + + + + + + + + + - + - - - - - + + + + + @@ -3894,33 +13859,28 @@ - - - + + + + - - - - - - - + + +

- der an geeigneter Stelle hängt + Verwaltung autmatisch via ViewLocator -> PanelLocator

- -
- - + + @@ -3931,23 +13891,33 @@

- + - - - + + + - - - - - - + + + + + + + + + + + + + + + + - + @@ -3963,45 +13933,45 @@ - - - + + + - + - + - - - + + + - - + + - + - - - - + + + + - - - + + + @@ -4020,57 +13990,57 @@ - + - + - - - + + + - + - + - - + + - - - - - - - + + + + + + + - + - - - + + + - - - - + + + + - - + + - - - + + + - + @@ -4082,14 +14052,14 @@ - + - + - + @@ -4104,20 +14074,20 @@ - - + + - + - + - - - + + + @@ -4154,21 +14124,21 @@ - + - - + + - - + + - + @@ -4180,55 +14150,60 @@ - - + + - - - + + + - + - + - - - + + + - - - - - + + + + + - - - - - - - + + + + + + + - + - + - + - - + + - - - - + + + + + + + + + @@ -4242,9 +14217,10 @@

- - - + + + + @@ -4256,18 +14232,18 @@ - - + + - - + + - - + + @@ -4285,7 +14261,7 @@ - + @@ -4315,7 +14291,7 @@ - + @@ -4336,7 +14312,7 @@ - + @@ -4359,34 +14335,38 @@ - + - + - + - + - + - - - - + + + + - + - - - - + + + + + + + + @@ -4397,25 +14377,25 @@

- - - - + + + + - + - - - + + + - - + + - - + + @@ -4428,10 +14408,10 @@ - - - - + + + + @@ -4444,37 +14424,37 @@ - - - - - + + + + + - + - - - - - - + + + + + + - - - + + + - + - - + + - - + + @@ -4489,28 +14469,28 @@ - + - - + + - + - - + + - - + + - - - - + + + + @@ -4522,11 +14502,11 @@ - + - - + + @@ -4547,10 +14527,10 @@ - + - + @@ -4565,15 +14545,15 @@ - - - - - + + + + + - - - + + + @@ -4597,23 +14577,23 @@ - - - + + + - - - - - - - + + + + + + + - - + + @@ -4644,8 +14624,8 @@ - - + + @@ -4659,11 +14639,11 @@

- - - - - + + + + + @@ -4687,8 +14667,8 @@ - - + + @@ -4700,8 +14680,8 @@ - - + + @@ -4724,7 +14704,7 @@ - + @@ -4741,9 +14721,9 @@ - - - + + + @@ -4762,11 +14742,11 @@ - - - + + + - + @@ -4780,8 +14760,8 @@

- - + + @@ -4808,11 +14788,11 @@ - + - - + + @@ -4830,7 +14810,7 @@ - + @@ -4852,9 +14832,9 @@ - - - + + + @@ -4866,9 +14846,9 @@ - - - + + + @@ -4896,7 +14876,7 @@ - + @@ -4925,15 +14905,15 @@ - - - + + + - - - - - + + + + + @@ -4948,25 +14928,25 @@ - - - + + + - - - - + + + + - - + + - + - + @@ -4978,17 +14958,18 @@ - + - + - - - + + + + @@ -5006,47 +14987,47 @@ - - - - + + + + - - - - + + + + - - - + + + - + - - + + - - - - - + + + + + - - - - + + + + - + - + - - + + - + @@ -5057,16 +15038,16 @@

- + - - - + + + - + - + @@ -5078,10 +15059,10 @@ - + - + @@ -5093,16 +15074,16 @@ - + - - - - - - - + + + + + + + @@ -5116,25 +15097,120 @@

- - - + + + - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ ...um mal was im UI anzeigen zu können +

+ +
+ +
+ + + + + + + + + + + + + + + + +

+ ...wird zwar vom Skript ausgelesen, +

+

+ aber nicht weiterverwendet. +

+

+ Die Icon-Größen ergeben sich aus den Boxes auf 'plate' +

+ +
+
+ + + + + + + + + + + + + + + + + + +

+ Docks enthalten Component Views +

+ +
+ + +
+
+ + + + + + + @@ -5150,13 +15226,13 @@ - - - - + + + + - - + + @@ -5171,14 +15247,14 @@ - - - - + + + + - - - + + + @@ -5210,26 +15286,26 @@ - - - + + + - - - + + + - + - + - + - + @@ -5253,7 +15329,7 @@ - + @@ -5271,8 +15347,8 @@ - - + + @@ -5285,17 +15361,17 @@ - - - - - - + + + + + + - - + + @@ -5310,72 +15386,87 @@ - + - - + + - - - - - + + + + + - - - - + + + + - - - - - + + + + + - - + + + + + + + + + + + + + + + - - - - - - + + + + + + + + - + - - - - + + + + - - - - - + + + + + - - - - + + + + - - + + - + @@ -5390,19 +15481,203 @@ - + + + + + + + + + + + + + + + + +

+ (GlobalCtx)->InteractionDirector (=Model Root) +

+ + +
+
+
+
+ + + + + + + + + + + + + +

+ (GlobalCtx)->InteractionDirector->Navigator +

+ + +
+ + + + + + + + + +

+ (GlobalCtx)->WindowLocator->UIComponentAccessor +

+ +
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + +

+ (GlobalCtx)->InteractionDirector->ViewLocator +

+ + +
+ + + + + + + + + +

+ (GlobalCtx)->WindowLocator->UIComponentAccessor +

+ + +
+ + + + + + + + + + + + + + + + + + + + + + +

+ man muß die Implementierungs-Details jeder einzelnen Komponente kennen, +

+

+ um damit überhaupt etwas anfangen zu können. Es gibt hier keine schematische Ordnung. +

+

+ Selbst die Frage, ob es sich um ein Blatt handelt, oder um einen inneren Knoten, +

+

+ erfordert bereits Kenntnis der Innereien +

+ + +
+
+
+ + + + + + + + +

+ (GlobalCtx)->WindowLocator +

+ + +
+ + + + + + + + + +

+ (GlobalCtx)->WindowLocator->PanelLocator +

+ + +
+ + + + + +
+ + + + + + + - - - + + + - - + + - - + + @@ -5414,7 +15689,7 @@ - + @@ -5427,8 +15702,8 @@ - - + + @@ -5440,10 +15715,10 @@ - - + + - + @@ -5457,8 +15732,8 @@

- - + + @@ -5470,25 +15745,25 @@ - - - - - - + + + + + + - - - - - + + + + + - - + + - + @@ -5501,7 +15776,7 @@ - + @@ -5513,7 +15788,7 @@ - + @@ -5525,7 +15800,7 @@ - + @@ -5539,9 +15814,9 @@ - + - + @@ -5553,15 +15828,15 @@ - - - + + + - + - + @@ -5586,7 +15861,7 @@ - + @@ -5604,11 +15879,11 @@ - - + + - + @@ -5622,31 +15897,31 @@ - - - - - - - + + + + + + + - + - + - - - - - - - + + + + + + + - + - - + + @@ -5664,20 +15939,20 @@ - + - + - - + + - - + + @@ -5692,8 +15967,8 @@ - - + + @@ -5705,8 +15980,8 @@ - - + + @@ -5720,15 +15995,15 @@ - + - + - + - + @@ -5740,11 +16015,11 @@ - - + + - - + + @@ -5756,34 +16031,34 @@ - + - + - - + + - - - + + + - + - - + + - + - + - + - - - + + + @@ -5822,27 +16097,27 @@ - + - - - - - - + + + + + + - - + + - + - + - + @@ -5868,7 +16143,7 @@ - + @@ -5906,12 +16181,12 @@ - + - + @@ -5923,7 +16198,7 @@ - + @@ -5936,7 +16211,7 @@ - + @@ -5963,7 +16238,7 @@

- + @@ -5975,28 +16250,28 @@ - + - + - + - + - + - + - + @@ -6022,7 +16297,7 @@ - + @@ -6036,7 +16311,7 @@ - + @@ -6045,7 +16320,7 @@ - + @@ -6055,7 +16330,7 @@ - + @@ -6063,24 +16338,24 @@ - + - + - + - + - + - + @@ -6088,15 +16363,15 @@ - + - + - + @@ -6111,25 +16386,25 @@ - + - + - + - + - + @@ -6154,7 +16429,7 @@ - + @@ -6167,32 +16442,32 @@ - + - + - + - + - + - - + + @@ -6221,7 +16496,7 @@ - + @@ -6246,7 +16521,7 @@ - + @@ -6255,7 +16530,7 @@ - + @@ -6264,80 +16539,80 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -6353,12 +16628,12 @@ - + - + @@ -6378,9 +16653,9 @@ - + - + @@ -6397,7 +16672,7 @@ - + @@ -6437,13 +16712,13 @@ - + - + @@ -6457,7 +16732,7 @@ - + @@ -6517,10 +16792,10 @@ - + - + @@ -6534,28 +16809,28 @@ - + - + - + - + - + - + - + @@ -6573,7 +16848,7 @@ - + @@ -6597,15 +16872,15 @@ - + - + - + @@ -6621,7 +16896,7 @@ - + @@ -6652,10 +16927,10 @@ - + - + @@ -6665,12 +16940,12 @@ - + - + @@ -6706,13 +16981,13 @@
- + - + - + @@ -6744,7 +17019,7 @@ - + @@ -6765,7 +17040,7 @@ - + @@ -6774,7 +17049,7 @@ - + @@ -6797,60 +17072,60 @@ - + - + - + - + - + - + - + - + - + - + - + @@ -6863,19 +17138,19 @@ - + - - + + - + - + @@ -6901,7 +17176,7 @@ - + @@ -6910,7 +17185,7 @@ - + @@ -6963,7 +17238,7 @@ - + @@ -6995,17 +17270,17 @@ - + - + - + - + @@ -7022,8 +17297,8 @@ - - + + @@ -7115,12 +17390,12 @@ - - + + - + @@ -7132,9 +17407,9 @@ - + - + @@ -7176,7 +17451,7 @@ - + @@ -7184,7 +17459,7 @@ - + @@ -7204,7 +17479,7 @@ - + @@ -7214,21 +17489,21 @@ - + - + - + - + @@ -7251,10 +17526,10 @@ - + - - + + @@ -7317,9 +17592,9 @@ - + - + @@ -7356,7 +17631,7 @@ - + @@ -7390,9 +17665,9 @@ - + - + @@ -7406,7 +17681,7 @@ - + @@ -7430,7 +17705,7 @@ - + @@ -7438,13 +17713,13 @@ - - + + - + @@ -7452,7 +17727,7 @@ - + @@ -7480,19 +17755,20 @@ - - + + + - - + + - + - - + + @@ -7506,7 +17782,7 @@ - + @@ -7533,7 +17809,7 @@ - + @@ -7603,7 +17879,7 @@ - + @@ -7621,10 +17897,10 @@ - + - + @@ -7634,7 +17910,7 @@ - + @@ -7653,7 +17929,7 @@ - + @@ -7671,16 +17947,16 @@ - + - + - + @@ -7689,9 +17965,9 @@ - + - + @@ -7702,7 +17978,7 @@ - + @@ -7711,7 +17987,7 @@ - + @@ -7725,14 +18001,15 @@

- + + - + @@ -7771,10 +18048,10 @@ - + - - + + @@ -7794,34 +18071,35 @@
- + - + - + - - + + - + - + + - + @@ -7848,34 +18126,34 @@ - + - + - + - + - + - + - + @@ -7883,11 +18161,11 @@ - + - + @@ -7896,18 +18174,18 @@ - + - + - + - + @@ -7924,7 +18202,7 @@ - + @@ -7969,7 +18247,7 @@ - + @@ -7977,15 +18255,15 @@ - + - + - + @@ -8018,13 +18296,13 @@ - - + + - + @@ -8032,8 +18310,8 @@ - - + + @@ -8048,7 +18326,7 @@ - + @@ -8113,7 +18391,7 @@ - + @@ -8160,14 +18438,14 @@ - - + + - + @@ -8201,9 +18479,9 @@ - + - + @@ -8226,7 +18504,7 @@ - + @@ -8260,8 +18538,8 @@ - - + + @@ -8277,25 +18555,25 @@ - + - + - + - + - + @@ -8322,7 +18600,7 @@
- + @@ -8345,7 +18623,7 @@ - + @@ -8400,7 +18678,7 @@ - + @@ -8419,8 +18697,8 @@ - - + + @@ -8434,7 +18712,7 @@

- + @@ -8453,8 +18731,8 @@ - - + + @@ -8487,18 +18765,18 @@ - - + + - + - + @@ -8558,7 +18836,7 @@ - + @@ -8574,7 +18852,7 @@ - + @@ -8587,11 +18865,11 @@ - + - - + + @@ -8605,7 +18883,7 @@

- + @@ -8629,7 +18907,7 @@ - + @@ -8640,11 +18918,11 @@ - + - + @@ -8652,14 +18930,14 @@ - + - + @@ -8670,7 +18948,7 @@ - + @@ -8705,19 +18983,19 @@ - + - + - + @@ -8734,7 +19012,7 @@ - + @@ -8751,7 +19029,7 @@ - + @@ -8760,7 +19038,7 @@ - + @@ -8774,25 +19052,25 @@

- + - + - + - + - + - + @@ -8800,18 +19078,18 @@ - + - + - + @@ -8832,21 +19110,21 @@ - + - + - + @@ -8881,28 +19159,28 @@ - - + + - + - + - - - - + + + + - + @@ -8951,22 +19229,22 @@ - + - + - + - + @@ -8977,11 +19255,11 @@ - + - + - + @@ -9013,7 +19291,7 @@ - + @@ -9022,7 +19300,7 @@ - + @@ -9050,7 +19328,7 @@ - + @@ -9065,14 +19343,14 @@ - + - + @@ -9092,7 +19370,7 @@ - + @@ -9104,7 +19382,7 @@ - + @@ -9151,7 +19429,7 @@ - + @@ -9172,7 +19450,7 @@ - + @@ -9190,13 +19468,13 @@ - - + + - + @@ -9249,7 +19527,7 @@ - + @@ -9294,9 +19572,9 @@ - + - + @@ -9318,14 +19596,14 @@ - + - + @@ -9334,7 +19612,7 @@ - + @@ -9344,7 +19622,7 @@ - + @@ -9353,15 +19631,15 @@ - - + + - + @@ -9385,9 +19663,9 @@ - + - + @@ -9410,9 +19688,9 @@ - + - + @@ -9420,7 +19698,7 @@ - + @@ -9428,7 +19706,7 @@ - + @@ -9442,7 +19720,7 @@ - + @@ -9473,8 +19751,8 @@ - - + + @@ -9486,7 +19764,7 @@ - + @@ -9497,18 +19775,18 @@ - + - + - + - + @@ -9534,7 +19812,7 @@ - + @@ -9637,38 +19915,38 @@ - + - - + + - + - + - - - + + + - + - + - + - - + + @@ -9686,13 +19964,13 @@ - + - + @@ -9710,14 +19988,14 @@ - + - + - + @@ -9725,9 +20003,9 @@ - + - + @@ -9761,25 +20039,25 @@ - + - + - + - + - + @@ -9806,7 +20084,7 @@ - + @@ -9814,7 +20092,7 @@ - + @@ -9859,7 +20137,7 @@ - + @@ -9876,14 +20154,14 @@ - + - + - + @@ -9896,14 +20174,14 @@ - + - + @@ -9917,10 +20195,10 @@ - + - + @@ -9977,9 +20255,9 @@ - + - + @@ -9990,7 +20268,7 @@

- + @@ -10019,7 +20297,7 @@ - + @@ -10046,14 +20324,14 @@ - + - + - + @@ -10061,9 +20339,9 @@ - + - + @@ -10075,18 +20353,18 @@ - + - + - + - + @@ -10123,7 +20401,7 @@ - + @@ -10145,9 +20423,9 @@ - + - + @@ -10170,7 +20448,7 @@ - + @@ -10188,13 +20466,13 @@ - + - + @@ -10241,7 +20519,7 @@ - + @@ -10288,7 +20566,7 @@ - + @@ -10300,24 +20578,24 @@ - + - + - + - + - + @@ -10358,7 +20636,7 @@ - + @@ -10381,9 +20659,9 @@ - + - + @@ -10398,7 +20676,7 @@ - + @@ -10435,12 +20713,12 @@ - + - + @@ -10462,16 +20740,16 @@ - + - + - + @@ -10485,14 +20763,14 @@ - + - + - + @@ -10509,7 +20787,7 @@ - + @@ -10553,11 +20831,11 @@ - + - + @@ -10590,10 +20868,10 @@ - + - + @@ -10648,22 +20926,22 @@ - + - + - + - + @@ -10697,7 +20975,7 @@ - + @@ -10724,21 +21002,21 @@ - + - + - + - + @@ -10751,10 +21029,10 @@ - + - - + + @@ -10764,7 +21042,7 @@ - + @@ -10822,7 +21100,7 @@ - + @@ -10842,9 +21120,9 @@ - - - + + + @@ -10854,7 +21132,7 @@ - + @@ -10878,22 +21156,22 @@ - + - + - + - + - - - + + + - + - + @@ -10911,15 +21189,15 @@ - + - + - + - + @@ -10932,15 +21210,15 @@ - + - + - + - + @@ -10948,7 +21226,7 @@ - + @@ -10982,7 +21260,7 @@ - + @@ -11042,7 +21320,7 @@ - + @@ -11065,8 +21343,7 @@ nach beiden Setien

- -
+
@@ -11088,10 +21365,10 @@
- + - + @@ -11113,11 +21390,11 @@ - - + + - + @@ -11151,16 +21428,16 @@ - + - + - + - + @@ -11178,7 +21455,7 @@ - + @@ -11216,7 +21493,7 @@ - + @@ -11259,7 +21536,7 @@ - + @@ -11276,43 +21553,43 @@ - + - + - + - + - + - + - + - + - + - + - - + + - - - + + + @@ -11327,10 +21604,10 @@ - + - - + + @@ -11347,13 +21624,13 @@ - - - - + + + + - + @@ -11368,26 +21645,26 @@ - + - - - - - + + + + + - - - - - - + + + + + + - + - - + + @@ -11402,22 +21679,22 @@ - - - - + + + + - + - + - + @@ -11437,7 +21714,7 @@ - + @@ -11496,7 +21773,7 @@ - + @@ -11519,109 +21796,110 @@ - + + - + - - - + + + - + - + - + - - - - - - - + + + + + + + - + - - - - - - - + + + + + + + - + - - - + + + - - + + - - - - - - + + + + + + - - + + - - - - - + + + + + - - - - - - + + + + + + - - - + + + - - - - - - - - + + + + + + + + - - - + + + - - + + - - + + @@ -11634,128 +21912,152 @@ - - + + - - - + + + - - - - - - + + + + + + - - + + - + - + + + + + + + + + + + + + - + + + + + + + + + + + + + - - - - - - + + + + + + - - - + + + - + - + - + - - - - + + + + - + - - - - - + + + + + - - + + - + - + - - - + + + - - + + - - + + - - - - + + + + - - + + - - + + - - + + - + - - - + + + - - - - - - - + + + + + + + - + - - + + @@ -11767,7 +22069,7 @@ - + @@ -11779,17 +22081,17 @@ - - - + + + - - - - + + + + - - + + @@ -11801,9 +22103,9 @@ - - - + + + @@ -11818,47 +22120,47 @@ - - + + - - - - + + + + - - - - - + + + + + - - - + + + - - - + + + - - - - - + + + + + - + - - - + + + - - - + + + @@ -11885,15 +22187,15 @@ - - + + - + - - + + @@ -11911,10 +22213,10 @@ - + - - + + @@ -11930,10 +22232,10 @@ - + - - + + @@ -11949,23 +22251,23 @@ - - + + - - - + + + - - + + - + @@ -11987,10 +22289,10 @@ - + - - + + @@ -12015,40 +22317,40 @@ - + - + - + - + - - + + - + - - - - + + + + - - + + - - + + - + @@ -12065,7 +22367,7 @@ - + @@ -12083,21 +22385,21 @@ - + - + - + - + - + - + - + @@ -12108,7 +22410,7 @@ - + @@ -12142,8 +22444,8 @@ - - + + @@ -12183,67 +22485,71 @@ - + - - + + - - - + + + - - - - + + + + - + - + - + - + - - + + - + - + - + - + - + - - - - - - - - - + - + + + + + + + + + + + + + @@ -12255,20 +22561,20 @@ - - - + + + - + - + - + - - + + @@ -12280,7 +22586,7 @@ - + @@ -12292,41 +22598,41 @@ - + - + - - + + - - + + - - - + + + - + - + - + - - + + - - + + @@ -12340,10 +22646,10 @@

- - + + - + @@ -12357,8 +22663,8 @@

- - + + @@ -12377,21 +22683,21 @@ - + - + - + - + - + @@ -12419,23 +22725,22 @@ - - +
- + - + - + - + - + - + @@ -12457,59 +22762,59 @@ - + - - + + - + - - + + - + - + - - - + + + - + - + - + - + - - + + - + - + - + - + - + @@ -12587,7 +22892,7 @@ - + @@ -12612,7 +22917,7 @@ - + @@ -12688,7 +22993,7 @@ - + @@ -12708,7 +23013,7 @@ - + @@ -12723,7 +23028,7 @@ - + @@ -12737,10 +23042,11 @@

- - - - + + + + + @@ -12767,10 +23073,10 @@ - - - - + + + + @@ -12786,7 +23092,7 @@ - + @@ -12798,9 +23104,9 @@ - + - + @@ -12821,7 +23127,7 @@ - + @@ -12842,7 +23148,7 @@ - + @@ -12854,7 +23160,7 @@ - + @@ -12870,56 +23176,57 @@ - + - + - - + + - - + + - - - + + + - - - - - - + + + + + + - - - + + + + - + - + - + - + - + - + @@ -12946,7 +23253,7 @@ - + @@ -12956,7 +23263,7 @@ - + @@ -13029,7 +23336,7 @@ - + @@ -13093,7 +23400,7 @@ - + @@ -13261,7 +23568,7 @@ - + @@ -13399,14 +23706,14 @@ - + - - + + - - + + @@ -13418,27 +23725,23 @@ - - + + - + - - + + - - - - - - - - - + + + + + - - + + @@ -13452,19 +23755,19 @@ - - - + + + - - - - - + + + + + - + @@ -13571,74 +23874,74 @@ - + - + - + - + - + - + - - + + - - + + - + - + - + - + - - + + - + - + - - - - + + + + - + - - - + + + - + - - - + + + - + @@ -13653,7 +23956,7 @@ - + @@ -13674,8 +23977,8 @@ - - + + @@ -13735,9 +24038,9 @@ - + - + @@ -13758,25 +24061,25 @@ - + - + - - - + + + - + - - + + - + - + @@ -13794,26 +24097,26 @@ - + - - - - + + + + - - - - - + + + + + - + - + @@ -13827,7 +24130,7 @@ - + @@ -13844,7 +24147,7 @@ - + @@ -13866,8 +24169,8 @@ - - + + @@ -13944,7 +24247,7 @@ - + @@ -14097,52 +24400,52 @@ - + - + - + - + - + - - - - - - + + + + + + - + - + - + - + - + - - + + - - + + @@ -14186,7 +24489,7 @@ - + @@ -14204,7 +24507,7 @@ - + @@ -14225,7 +24528,7 @@ - + @@ -14248,10 +24551,10 @@

- - + + - + @@ -14264,7 +24567,7 @@ - + @@ -14301,7 +24604,7 @@ - + @@ -14310,7 +24613,7 @@ - + @@ -14336,50 +24639,51 @@ - + - + - + - - + + - - - - - + + + + + + - + - + - - - + + + - + - - - + + + - - - - - - + + + + + + - + @@ -14436,7 +24740,7 @@ - + @@ -14472,23 +24776,23 @@ - - + + - + - + - - + + - + - + @@ -14556,15 +24860,15 @@ - + - - - - + + + + - + @@ -14581,7 +24885,7 @@ - + @@ -14602,26 +24906,26 @@ - + - - - - + + + + - + - - - - + + + + - + - + - + @@ -14630,38 +24934,38 @@ - + - + - + - - + + - + - + - - + + - - + + - - - - + + + + - - + + - + @@ -14669,7 +24973,7 @@ - + @@ -14678,14 +24982,14 @@ - + - - + + - + @@ -14735,11 +25039,11 @@ - + - + - + @@ -14755,7 +25059,7 @@ - + @@ -14810,7 +25114,7 @@ - + @@ -14834,7 +25138,7 @@
- + @@ -14853,11 +25157,11 @@ - + - + @@ -14877,7 +25181,7 @@ - + @@ -14911,21 +25215,21 @@ - + - + - + - + @@ -14991,7 +25295,7 @@ - + @@ -15013,12 +25317,12 @@ - + - + @@ -15065,7 +25369,7 @@ - + @@ -15087,7 +25391,7 @@ - + @@ -15132,9 +25436,9 @@ - + - + @@ -15147,10 +25451,10 @@ - + - + @@ -15165,9 +25469,9 @@ - + - + @@ -15188,7 +25492,7 @@ - + @@ -15199,7 +25503,7 @@

- + @@ -15233,7 +25537,7 @@ - + @@ -15297,13 +25601,13 @@
- + - + - + @@ -15365,16 +25669,16 @@ - + - + - + @@ -15414,7 +25718,7 @@ - + @@ -15425,7 +25729,7 @@ - + @@ -15444,14 +25748,14 @@ - + - + - + @@ -15467,7 +25771,7 @@
- + @@ -15484,7 +25788,7 @@ - + @@ -15496,15 +25800,15 @@ - - - - + + + + - + - + @@ -15518,7 +25822,7 @@ - + @@ -15539,7 +25843,7 @@ - + @@ -15574,7 +25878,7 @@ - + @@ -15600,7 +25904,7 @@ - + @@ -15617,7 +25921,7 @@ - + @@ -15668,10 +25972,10 @@ - + - + @@ -15687,17 +25991,17 @@ - + - + - + @@ -15721,8 +26025,8 @@ - - + + @@ -15738,7 +26042,7 @@ - + @@ -15750,7 +26054,7 @@ - + @@ -15764,7 +26068,7 @@

- + @@ -15772,9 +26076,9 @@ - + - + @@ -15790,7 +26094,7 @@
- + @@ -15812,22 +26116,22 @@ - + - + - + - + - + @@ -15847,13 +26151,13 @@ - + - + - + @@ -15865,25 +26169,26 @@ - - + + + - + - + - + - - + + @@ -15899,27 +26204,27 @@ - + - + - + - - + + - + @@ -15927,7 +26232,7 @@ - + @@ -15949,8 +26254,8 @@ - - + + @@ -16005,7 +26310,7 @@ - + @@ -16039,11 +26344,11 @@ - - + + - + @@ -16093,15 +26398,15 @@ - + - + - + @@ -16130,9 +26435,9 @@ - - - + + + @@ -16140,7 +26445,7 @@ - + @@ -16148,18 +26453,18 @@ - + - - + + - + @@ -16174,7 +26479,7 @@ - + @@ -16203,7 +26508,7 @@ - + @@ -16211,7 +26516,7 @@ - + @@ -16268,7 +26573,7 @@ - + @@ -16276,21 +26581,21 @@ - + - + - + @@ -16305,12 +26610,12 @@ - + - + @@ -16328,7 +26633,7 @@ - + @@ -16385,9 +26690,9 @@ - + - + @@ -16400,15 +26705,15 @@ - + - + - + @@ -16424,20 +26729,20 @@ - + - + - + - + - + @@ -16453,12 +26758,12 @@
- + - + @@ -16483,7 +26788,7 @@
- + @@ -16520,7 +26825,7 @@ - + @@ -16536,7 +26841,7 @@ - + @@ -16556,7 +26861,7 @@ - + @@ -16570,7 +26875,7 @@ - + @@ -16586,9 +26891,9 @@ - + - + @@ -16611,19 +26916,19 @@ - + - + - + @@ -16646,11 +26951,11 @@ - + - + @@ -16694,7 +26999,7 @@ - + @@ -16711,7 +27016,7 @@ - + @@ -16731,7 +27036,7 @@ - + @@ -16742,7 +27047,7 @@ - + @@ -16779,8 +27084,7 @@ Paßt hier, da IterSource genau dieses Vorgehen nahelegt

- -
+
@@ -16792,8 +27096,7 @@ MutationMessage::updateDiagnostics()

- -
+
@@ -16812,8 +27115,7 @@ ...diejenige, die zum Zeitpunkt des updateDiagnostics() noch anstand

- - +
@@ -16827,45 +27129,45 @@
- + - - - + + + - + - - - - - + + + + + - - - - + + + + - + - + - + - + - + - + - + @@ -16873,21 +27175,21 @@ - + - - + + - + - + @@ -16901,48 +27203,48 @@ - - + + - + - - + + - + - + - + - - + + - + @@ -16950,7 +27252,7 @@ - + @@ -16968,30 +27270,30 @@ - - - - - - + + + + + + - - - - + + + + - + - - - - + + + + - - - + + + @@ -17003,10 +27305,10 @@ - - - - + + + + @@ -17018,12 +27320,12 @@ - + - - - - + + + + @@ -17036,7 +27338,7 @@ - + @@ -17048,8 +27350,8 @@ - - + + @@ -17061,10 +27363,10 @@ - - + + - + @@ -17078,21 +27380,21 @@

- +

- lt. eigenen Benchmakrs deutlich schneller als rapitjson, welches eigentlich immer als der "schnelle" JSON-Parser gilt. + lt. eigenen Benchmakrs deutlich schneller als rapidjson, welches eigentlich immer als der "schnelle" JSON-Parser gilt.

- - - + + + @@ -17104,18 +27406,18 @@ - - - + + + - - - - - + + + + + - + @@ -17128,21 +27430,21 @@ - - - - + + + + - + - + - - + + @@ -17154,103 +27456,3077 @@ - - - + + + - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ man hätte genausogut std::future und std::async verwenden können. +

+

+ Vorteil von unseren Framework: +

+
    +
  • + wir haben es schon, und wir werden es verwenden, wegen den Thradpools +
  • +
  • + man baut ein Objekt für einen Thread. Das ist explizit und sauber +
  • +
  • + wir haben eine eingebaute Barriere und können unseren Objekt-Monitor nutzen +
  • +
+ +
+ +
+ + + + + + + + + + + + +

+ habe einen usleep(1000) getimed +

+ +
+ +
+
+ + + + + + + + + + + + + +

+ daher messen wir die Loop als Ganzes. +

+

+ Es gibt daher keine Möglichkeit, den Loop-Overhead selber zu messen. +

+

+ Er sollte sich aber bei einer Wiederholung im Millionenbereich gut amortisieren +

+

+ +

+

+ Außerdem ist ja auch noch der Aufruf des Funktors mit im Spiel, wenngleich der auch typischerweise geinlined wird +

+ +
+ + + + + + + +
+ + + + + + + + +

+ volatile Variable außen, im Aufrufkontext +

+ +
+
+ + + + + + + + + + +

+ ...was sehr schön beweist, +

+

+ daß x86_64 tatsächlich cache-kohärent ist +

+ +
- - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ war im Einsatz seit Beginn der Lumiera-Projektes. +

+

+ Wurde aufgegeben da +

+
    +
  • + die Policies komplexe Lösungsvarianten implementierten, die nie gebraucht wurden +
  • +
  • + die Implementierung einen tückischen Fehler in CLang aufgedeckt hat +
  • +
+ +
+ + + + + + + + + + + + +

+ Im Einsatz seit der Behebung des CLang-Problems bis heute (3/2018). +

+

+ Wird nun aufgegeben, da sich auf dieser Basis keine DI implementieren läßt, +

+

+ welche auf einem Service mit explizitem Lebenszyklus beruht. +

+

+ Außerdem stellte sich diese Lösung als ziemlich fragil heraus +

+

+ und benötigt diverse Laufzeit-Konsistenzchecks, die den Implementierungscode schwer lesbar machen +

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ Bei diesem Wunsch-Profil bleibt nur eine Variante von Lösung-2 +

+ +
+ + + + + +

+ ...denn nur eine dynamische Laufzeit-Factory ermöglicht, jederzeit  den Konstruktionsmodus zu wechseln +

+ +
+ +
+ + + + + + +

+ ...denn nur ein Instanz-Pointer kann umgebogen oder auf NULL zurückgesetzt werden. +

+

+ Der einzige Ausweg aus diesem Dilemma wäre eine statische Lösung, +

+

+ in der bereits durch den #include von lib/depend.hpp endgültig klar wäre, +

+

+ was für eine Art von Dependency-Factory zum Einsatz kommen soll. Denn nur auf diesem +

+

+ Weg könnte der Optimiser unmittelbar auf eine Singleton-Instanz im statischen Speicher +

+

+ zugreifen, nach einem Check auf ein atomic<bool>. +

+

+ +

+

+ Eine solche statische Lösung allerdings widerspricht nicht nur meinen Wünschen, +

+

+ sondern wäre auch architektonisch ungünstig, denn dadurch +

+
    +
  • + entsteht eine zentrale DI-Konfiguration +
  • +
  • + erfolgt eine Rückverkopplung von lib/depend.hpp auf die Applikations-Struktur +
  • +
+ +
+
+ + + + + + +

+ ...denn er muß komplett generisch sein, und lib/depend.hpp darf keinerlei Kenntnis +

+

+ über die konkrete DependencyFactory voraussetzen. Denn sonst würden wir die +

+

+ Freiheit der dynamischen Laufzeit-Konfiguration verlieren. +

+ +
+
+ + + + + + +

+ ...denn ein Lambda kann in dieser Lage grundsätzlich keinen optimierungs-Vorteil bringen, +

+

+ und die konventionelle Lösung hat demgegenüber den Vorteil, daß sie Struktur und Kontrakt explizit macht. +

+ +
+
+ + + + + + +

+ ...denn es läuft darauf hinaus, daß die Nutzung eines Dienstes zwar on demand erfolgt, +

+

+ jedoch stets erst nach seiner Bereitstellung. So etwas läßt sich niemals über einzelne +

+

+ technische Einrichtungen lösen, denn es ist eine Frage des Aufbaues der gesamten Applikation. +

+

+ Es gibt hierfür nur zwei Lösungswege +

+
    +
  • + dynamisch: man definiert alle Abhängigkeiten durch Regeln und zieht das System nach Bedarf konsistent hoch, was bedingt, daß alle Abhängigkeiten über diesen Mechanismus laufen müssen +
  • +
  • + statisch: man strukturiert das System so, daß Nutzer erst nach dem Hochfahren der von ihnen benötigten Subsysteme aktiv sein können +
  • +
+ +
+
- + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ scheidet für unser Nutzungsmuster aus +

+ +
+ + + + + +

+ ...denn es bedutet effektiv, daß viele Instanz-Zeiger "herumfliegen", +

+

+ welche man in einer zentralen Registry erfassen müßte, um sie bei Bedarf +

+

+ wieder auf NULL zurücksetzen zu können. +

+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ ...d.h. Service-Zugang wird automatisch geschlossen, +

+

+ wenn die DependInject-Instanz stirbt +

+ +
+ +
+ + + + + + + + + + + + + + +

+ sie werden ja sofort ausgewertet, da die Service-Instanz unmittelbar gebaut wird +

+ +
+ +
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ aber Lebenszyklus ist an die Factory gebunden +

+ +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ Folgendes Szenario ignorieren wir: +

+
    +
  • + eine Dependency ist als Service konfiguriert +
  • +
  • + für einen Testmock wird eine Local-Konfiguration darübergelegt +
  • +
  • + der Service wechselt seinen Lebenszyklus-Status (aktiv/inaktiv) +
  • +
+

+ ⟹ der Service zerschießt den Local, oder der Local restauriert am Ende den bereits toten Service. +

+

+ Har Har Har! Selber schuld wer sowas macht. +

+ +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ statische Storage ist irgendwie cool +

+ +
+ +
+ + + + + + + + + + + + +

+ das gilt im Besonderen für eine default-Storage als Singleton. +

+

+ Wofern wir dynamisch konfigurieren (wollen), muß dieser Default stets statisch bereitgestellt werden, +

+

+ selbst wenn die dynamische Konfiguration so angelegt ist, daß die Storage nie benötigt wird +

+ +
+
+ + + + + + +
+ + + + + + + + + + + + + + + + + + +

+ ...aber dann eben nicht mehr elegant. +

+ +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ Und das hängt nur von den Umständen ab. +

+

+ In einem einfachen statisch gelinkten Executable entfernt gcc die gesamte Variable sogar ohne Optimierung. +

+

+ +

+

+ In Zukunft könnten Compiler/Linker noch "schlauer" werden... +

+ +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ daher muß sein dtor vor dem dtor von DependencyFactory laufen +

+ +
+
+ + + + + + + + + + + + + + + + + + + +

+ denn während dem dtor existiert das Lock nicht mehr. +

+

+ Man kann aber die Logik so umordnen, daß der instance-Ptr nach dem eigentlichen Deleter auf NULL gesetzt wird. +

+

+ Das ist nicht threadsafe, was aber hier akzeptabel ist (Shutdown läuft überwiegend +

+

+ single-threaded, sofern man keine thread_locals einsetzt. Aber diese werden vor alle anderen +

+

+ Destruktoren gesequenced. Sollte passen. +

+ +
+ +
+ + + + + + +
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ denn nun wird das "singleton" schon ziemlich gehaltlos, +

+

+ und es ist einigermaßen undurchsichtig, wo nun die Instanz erzeugt wird. +

+

+ +

+

+ Allerdings gibt es auch kein stichhaltiges Argument, dieses Feature nicht zu implementieren. +

+

+ Es ist halt einfach nahheliegend, daß man mal eine Subklasse mit abweichenden Parametern +

+

+ konstruieren wollen könnte, und es ist von der Implementierung her "quasi geschenkt". +

+ +
+ + + + + + + + + + +
+
+
+ + + + + + + + + + +

+ Thema: Memory access order constraints +

+ +
+ + + + + + + + + + +

+ Grundidee: synchronizes-with-Beziehung herstellen auf Guard-Variable +

+ +
+
+ + + + + + + + + + +

+ ...das meint zweierlei +

+
    +
  • + wir brauchen keine volle sequentielle Konsistenz +
  • +
  • + eigentlich würde consume statt acquire genügen,
    aber wir verzichten auf diesen ehr theoretischen Performance-Gewinn,
    welcher nur relevant wäre, wenn wir auf ARM einen modernen Compiler einsetzen +
  • +
+ +
+
+ + + + + + +

+ essentiell ist, im Mutex-geschützten Bereich +

+

+ auf einer temporären lokalen Instanz-Variable zu arbeiten +

+ +
+ +
+
+ + + + + + + + + + + +

+ warum? +

+

+ weil per Definitionem dieses gesamte Konfigurations-Thema +

+

+ als nicht performance-kritisch eingestuft wird -- und ich mehr Wert darauf lege, +

+

+ die verschiedenen Belange im Quelltext nicht zu vermischen +

+ +
+ +
+
+ + + + + + + + + + + + + + +

+ es gilt schlichtweg als Architektur-Fehler, wenn hier eine Kollision geschiet. +

+

+ Und es gibt keinen sinnvollen Weg, wie die Applikation dann weiterarbeiten kann. +

+

+ Daher werfen wir ja auch error::Fatal +

+ +
+
+
+ + + + + + + +

+ ...denn sonst könnte genau das gleiche Desaster passieren, +

+

+ das auch in fehlerhaftem Double-Checked-Locking auftritt +

+ +
+ +
+ + + +
+ + + + + + + + + + + +

+ ...d.h. das ganze Locking und die memory-order schützt uns hier überhaupt nicht! +

+

+ Es kann sehr wohl passieren, daß ein anderer Thread grade eben noch +

+

+ sicht den Pointer auf den Service geholt hat, und wir dann den Service zerstören, während +

+

+ der andere Thread ihn grade nutzt. +

+ +
+
+ + + + + + +

+ unsere Architektur stellt aber sicher, +

+

+ daß dieser Fall nicht relevant ist +

+ +
+ + + + + +

+ warum? +

+

+ Weil der "andere Thread" nur von zwei Subsystemen her kommen kann +

+
    +
  • + dem Subsystem selber, das auch den Service erzeugt.
    Beispiel ist eine UI-Interaktion aus dem Event-Loop thread +
  • +
  • + aus einem anderen Subsystem, das vom Serivce-Provider abhängt +
  • +
+

+ In beiden Fällen stellen unsere Prinzipien zum Betreiben von Subsystemen sicher, +

+

+ daß dieser "andere Thread" nicht (mehr) aktiv sein darf, wenn der Shutdown erfolgt. +

+ +
+
+ + + + + + + + + + + +

+ hier ist ein Segfault möglich +

+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + +

+ ...denn wir sind auf x86_64 -- und diese Plattform ist per default fast überall sequentially coherent +

+ +
+
+ + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ ...man könnte ihn aber genausogut auch machen. +

+

+ Das Argument ist: wenn wir kaskadierend aufrufen, dann ist das Ergebnis in jedem Fall korrekt, +

+

+ und wird auch durch das Installieren dieses (zweiten) Zuganges in keinster Weise beeinträchtigt. +

+

+ Sollte Depend<SUB> bereits instantiiert sein, dann auch gut. +

+

+ Der kaskadierende Aufruf liest dann einfach dessen Instanz-Pointer +

+ +
+
+
+ + + + + + + + + + +

+ hier notwendig, weil wir eine neue Factory-Funktion ablegen. +

+

+ Das könnte mit dem Factory-Management einer bereits installierten Konfiguration kollidieren +

+ +
+
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ zwar wird beim Löschen des Mock +

+

+ die ursprüngliche Factory wieder an ihren Platz zurückgeschoben, +

+

+ aber niemand sagt, daß ein Move auch wirklich ein Move (swap) ist. +

+

+ Muß daher diese leer gewordene temporäre Factory explizit auf Default-Zustand zurücksetzen, +

+

+ damit nicht doch noch der Deleter läuft. +

+ +
+ +
+ + + + + + +

+ ...und man stattdessen explizit eine gefährliche Funktion  aufrufen muß +

+ +
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ Konsequenz: das ist keine Library-Implementierung +

+ +
+
+
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ die von der alten DependencyFactory abhängen +

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +

+ ...wenn man nämlicht Lumiera's Lösung nicht genau kennt, +

+

+ könnte sich das so lesen, als wäre Depend<X> ein Mixin, +

+

+ welches einer Klasse magisch eine Dependency als protected-Feld zugänglich macht. +

+

+ Und dann wäre es ziemlich pervasiv, sowas zum Freund zu erklären. +

+ +
+
+ + + + + + +

+ DependencyFactory ist viel besser geeignet +

+ +
+ +
+
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ den habe ich nicht mehr über das Interface-System gemappt +

+ +
+ + + + + +

+ weil mir das ganze C-gefrickel zu blöd geworden ist. +

+

+ Also hat das hier Prototyp-Charakter! +

+ +
+
+ + + +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + +

+ der Interface-Anbieter implementiert einen konkreten Proxy +

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ Modus der Definition +

+

+ und Instantiierung +

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + +

+ ...wir müssen immer, für jeden Proxy +

+

+ explizit eine Template-Instaniierung triggern, und zwar für +

+ +
+ +
+ + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ brauchen eigenen Zugriffs-Mechanismus auf ein weiteres Handle-Objekt +

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ ...oder sogar den ctor, das ist egal -- +

+

+ denn das Problem ist, ctor (oder activate) werden aus demjenigen Kontext heraus aufgerufen, +

+

+ der die Service-Implementierung startet und damit die Erzeugung des Proxy triggert. +

+

+ +

+

+ Dieser Kontext darf den konkreten Typ des Proxy aber genau nicht kennen (wegen Entkopplung)… +

+

+ Genau aus diesem Grunde leiten wir ja ctor/dtor der ServiceInstance(Handle)-Klasse in eine ander TU um. +

+

+ +

+

+ Problem ist nun, diese umgeleiteten Funktionsdefinitionen können nur einen Typ (Template-Parameter) bekommen, und dieser Typ wiederum muß -- zumindest als abstrakter Platzhalter -- in allen TU sichtbar sein. +

+ +
+
+ + + + +
+
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ obwohl es das sollte. +

+

+ Aber anscheinend macht der Compiler das nur partiell, +

+

+ denn es wird ja nur eine const& an den ctor von Binding übergeben. +

+

+ +

+

+ Allerdings erscheint es mir nicht sinnvoll, hier mit Tricksereien zu arbeiten! +

+ +
+
+ + + + + + +
+
+
+
+ + + + + + +

+ sie ist zwar nicht schön -- aber was ist an einem Interface-Binding schon "schön"? +

+

+ +

+

+ Sie ist hinreichend wartbar, sofern man sie per copy-n-pate vervielfältigt. +

+

+ Es wäre sogar denkbar, in diesen Rump eine generierte Proxy-Klasse zu kleben, +

+

+ da nur wenige Variable erstetzt werden müßten. +

+ +
+ +
+
+
+
+
+ + + + + + + + + +

+ das ist ein konzeptionelles Problem. +

+

+ Eigentlich möchte man durch ein Interface Entkoppelung erreichen. +

+

+ +

+

+ Nun ist es so, daß +

+
    +
  • + das Binding BusinessInterface -> InterfaceSystem +
  • +
  • + InterfaceSystem -> Service-Implementierung +
  • +
+

+ an der gleichen Stelle erfolgen +

+ +
+ + + + + + +
+ + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +

+ ...und lib::Depend so umarrangiert, +

+

+ daß re-entrant-Aufrufe während dem dtor erlaubt sind +

+ +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

+ so wie das Advice-System geschrieben ist, +

+

+ kann und muß man das "durchwinken" +

+ +
+ + + + + +

+ denn grundsätzlich ist das ganze Advice-System bewußt "billig" implementiert; +

+

+ Verbesserungen später willkommen.... +

+

+ +

+

+ Der Destructor räumt alle AdviceProvisions weg. +

+

+ Diese werden in einen statischen Kontext geschoben, damit sie unabhängig vom Advisor weiterleben. +

+

+ Daher müssen wir aus diesem statischen Kontext heraus wieder zurück in's AdviceSystem kommen. +

+

+ +

+

+ Alternative wäre, eine komplett spezielle De-Allokations-Routine zu schreiben, +

+

+ welche die Datenstrukturen direkt traversiert und freigibt, und dann die Provisions +

+

+ abfischt und ebenfalls alles wegwirft. Machbar, erscheint aber im Mißverhältnis +

+

+ zum Level der gesamten übrigen Implementierung (welche nämlich um einiges +

+

+ elaborierter sein könnte, incl. Verwendung von Atomics und einem besseren +

+

+ Memory-Management. +

+

+ +

+

+ Aber wie gesagt, das ganze Advice-System ist eine Skizze +

+ +
+ +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ ...weil Nobug-Init ON_BASIC_INIT braucht, +

+

+ und lib::Depend wiederum von Nobug-Init abhängig ist. +

+

+ +

+

+ Also würde DependencyFactory<LifecycleRegistry> aufgerufen, +

+

+ bevor es statisch initialisiert sein kann... +

+ +
+ +
+ + + +
+ + + + + + + + + + + + +

+ und heute würde ich den Code so nicht mehr schreiben +

+ +
+ +
+ + + + + + +

+ genauer: +

+

+ er ist nicht kaputt, sondern hat sehr gut funktioniert und diesen Unfug festgestellt. +

+

+ Nämlich daß unser lib::Depend ein ClassLock braucht, um einen Fehler zu melden. +

+

+ Das ist, für sich betrachtet, eigentlich unvermeidlich, aber verlangt dann auch +

+

+ nach einer grundlegenden Lösung. D.h. einem echten Schwartz-Counter. +

+

+ Nicht einem, der in einem Meyer's Singleton steckt... +

+ +
+ + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ ...es ruft sich selbst rekursiv auf, via Depend<AdviceSystem> +

+ +
+ +
+ + + + + + +

+ ...nämlich eine ganz spezielle, dedzierte Aufräum-Routine schreiben +

+ +
+
+ + + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ ...und das kann ziemlich indirekt passieren. +

+

+ Beispiel ist das ClassLock. Das ist ein Front-End, und verwendet verdeckt wieder einen Static. +

+

+ Und genau dafür gibt es anscheinend keine Garantieren +

+ +
+ + + + + + + +

+ C++ hällt die Erzeugungs/Zerstörungs-Reihenfolge exakt ein +

+ +
+
+ + + + + + +

+ d.h. wenn das local static später erzeugt wird, wird es vor  dem Hauptobjekt zerstört +

+ +
+
+
+ + + + + + + + + + + + + + +

+ Statische Initialisierung funktioniert präzise, korrekt und zuverlässig +

+ +
+
+ + + + + + +

+ Der Aufruf von Konstrukturen statischer Objekte konstituiert eine (dynamische) Reihenfolge. +

+

+ Desktuktoren werden exakt rückwärts in dieser Reihenfolge aufgerufen. +

+

+ Statische Objektfelder werden vor der ersten Verwendung der Klassendefinition  initialisiert +

+

+ Dagegen Funktions-lokale statische Variablen werden initialisiert, wenn der Kontrollfluß sie zum ersten mal berührt. +

+

+ Wenn ein Konstruktor ein statisches Feld verwendet, dann wird dieses Feld vor dem Konstruktor erzeugt. +

+

+ +

+

+ Beachte: in jedem dieser Fälle wird auch die o.g. Reihenfolge konstituiert. +

+

+ +

+

+ Corollar: wenn man ein Meyer's Singleton erst indirekt aus dem Implementierungs-Code verwendet, +

+

+ so wird es garantiert zerstört, bevor der Destruktor des aufrufenden Objekts läuft. +

+

+ Hallo ClassLock... +

+ +
+ +
+ + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + - + - - - - - - - + + + + + + + - - + + - + - - - + + + - - - - + + + + - - - + + + - - + + - + - + - - - - + + + + - - - - + + + + - - + + - + - - + + @@ -17267,7 +30543,7 @@ - + @@ -17279,24 +30555,24 @@ - - - + + + - + - - - - - - + + + + + + - - - - + + + + @@ -17308,7 +30584,7 @@ - + @@ -17322,9 +30598,9 @@ - + - + @@ -17337,38 +30613,38 @@ - - + + - - + + - + - - + + - + - + - + - + - + - + @@ -17380,13 +30656,13 @@ - + - + - + @@ -17431,53 +30707,53 @@ - + - - - - + + + + - + - + - - - - + + + + - - - - - + + + + + - - - - + + + + - + - + - + - - + + - + - - - - + + + + @@ -17492,7 +30768,7 @@ - + @@ -17514,54 +30790,54 @@ - + - + - + - + - + - - - - - - - + + + + + + + - - + + - + - + - + - + - - + + - + - + @@ -17573,58 +30849,58 @@ - + - + - + - + - - + + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -17640,30 +30916,209 @@ - + - + + + + + + + + + - + + + + + + + + + +

+ Thema: Monaden +

+ +
+ + + + + + + + + + + + + + + + +

+ gehört zu dem Themenkomplex "itertools" +

+

+ Python hat das auch, Java neuerdings auch +

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ ...will sagen, es ist klar, wie man sowas machen kann. +

+

+ Seinerzeit war mir das auch klar, aber ich wollte es nicht gleich ausprogrammieren. +

+

+ Inzwischen kam dann das Thema UI-Coordinaten, und dort habe ich es ausprogrammiert, +

+

+ und zwar direkt in die Low-Level-Schicht integriert, was nicht schlecht ist, +

+

+ da eine Abstraktion hier sehr technisch werden würde +

+ +
+
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + +

+ ...bindet die Betrachtung auf einen technischen Level, +

+

+ und führt dazu, daß die Abstraktion undicht wird +

+ +
+
+ + + + + + + + + + + + +

+ genau der Umstand, +

+

+ daß funktionale Sprachen von einer Zustands-Phobie getrieben sind, +

+

+ macht Monaden nützlich, um inhärenten Zustand wegzuabstrahieren. +

+

+ Das kann genutzt werden, um den Zustand einer Wechselwirkung +

+

+ nach einer Seite der Glieder auszukoppeln. +

+ +
+
+ + + + + + + + + + + + + +
+
+
+
+ + + + + + - - + + - - - - - + + + + + - + @@ -17677,17 +31132,17 @@ - - - - - + + + + + - + - + @@ -17714,15 +31169,15 @@ - - - - - - - - - + + + + + + + + + @@ -17745,34 +31200,34 @@ - + - - - - - - - - - - - + + + + + + + + + + + - + - - + + - + @@ -17786,27 +31241,27 @@

- - + + - - + + - + - - + + - + - - - - + + + + @@ -17829,16 +31284,16 @@ - - + + - + - + - - + + @@ -17860,11 +31315,10 @@ Das ist eine subtile Falle.

- -
+
- + @@ -17874,14 +31328,13 @@ alles was von sigc::trackable erbt

- -
+
- + - + @@ -17891,8 +31344,7 @@ für alles aus GTKmm zu verwenden

- -
+ @@ -17905,44 +31357,44 @@ alles das nicht aus dem GUI-Thread heraus geschieht

- -
+
- + - - + + - + - + - - + + - - - + + + - + - - - + + + - + - - + + + - + @@ -17959,54 +31411,31 @@ - - - - - - - - -

- in der Implementierung, mywidget.cc -

-

- ist eine komplette Sequenz, wie man einen CSS-StyleProvider setzt -

-

- und auch ein Signal für Parse-Fehler anschließt -

- -
- - + + + - - - - - - + - - - - + + + + - - + + - - - + + + - - + + - - - + + + @@ -18018,7 +31447,7 @@ - + @@ -18030,38 +31459,38 @@ - + - - + + - - + + - + - + - + - - + + - + - + - - - + + + - + @@ -18077,9 +31506,9 @@ - + - + @@ -18092,10 +31521,10 @@ - - + + - + @@ -18108,10 +31537,10 @@ - - - - + + + + @@ -18126,11 +31555,11 @@ - - - - - + + + + + @@ -18148,31 +31577,31 @@ - + - - + + - + - - - + + + - - - + + + - + - - - - + + + + @@ -18187,13 +31616,10 @@

oder neu in das Window-System gemappt wird.

-

- -

- + @@ -18207,63 +31633,63 @@ - - - + + + - + - - + + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -18294,28 +31720,28 @@ - + - + - + - + - + - + @@ -18326,12 +31752,101 @@ - - - + + + + + + + + + +

+ in der Implementierung, mywidget.cc +

+

+ ist eine komplette Sequenz, wie man einen CSS-StyleProvider setzt +

+

+ und auch ein Signal für Parse-Fehler anschließt +

+ +
+ + +
+
+ + + + + + + + +

+ Beispiel im Guide +

+ +
+
+ + + + + + + + + + + + + + +
    +
  • + left gravity: Marker bleibt beim Einfügen an dieser Stelle links von der Einfügung stehen +
  • +
  • + right gravity: Marker wird durch Einfügen an dieser Stelle nach rechts geschoben +
  • +
+

+ Beachte: der Text-Cursor (Marker "insert") hat right gravity +

+ +
+
+
+ + + + + + +

+ Multithreded-Beispiel +

+

+ im Guide demonstriert das +

+ +
+
+ + + +
+
+
+
+ + + - + @@ -18344,26 +31859,340 @@ + + + + + + +

+ Lessons +

+

+ learned +

+ +
+ + + - - - + + + + + + +

+ auf incomplete type achten +

+ +
+ + + + + + + + +

+ Vorsicht bei +

+

+ mutually dependent templates +

+ +
+ + + + + + +

+ kann eines der Templates im Zyklus vorrübergehend als "incomplete" gelten. +

+ +
+
+ + + + + + +

+ ...wenn man dummerweise auf verschlungenen Pfaden +

+

+ genau in dieser Phase die Metafunktion anfragt, +

+

+ kann der betreffende Check stillschweigend scheitern. +

+

+ +

+

+ Konsequenz: man wählt dann z.B. eine subtil falsche Spezialisierung. +

+ +
+
+ + + +
+
+
+
+
+ + + - + - - - + + + + + + + + + + + + + + + + + + + + +

+ das sind verschiedene Blickwinkel auf das gleiche Thema +

+ +
+ + + + + + + + + + + + + + +

+ Bedeutung dieser Schreibweise: +

+
    +
  • + der erste Zugriff liegt vor der Barriere, der zweite danach +
  • +
  • + die Barriere garantiert jeweils nur, daß der zweitgenannte Zugriff nicht vor den erstgenannten verschoben werden kann +
  • +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ eine Solche konstituiert die synchronizes-with-Beziehuung +

+ +
- - - - - - + + + + + + +

+ Grundbeziehung: synchronizes-with +

+ +
+
+
+
+ + + + + + + + + + + + + + +

+ das gilt nur im Rahmen der synchronizes-with-Beziehung +

+ +
+
+ + + + + + +

+ das heißt, nur für einen vom gleichen Mutex geschützen  Bereich! +

+ +
+
+ + + + + + +

+ In der naiven Implementierung greift der prüfende Thread auf die instance-Variable +

+

+ ohne jedwede Beziehung zum anderen Thread zu; er verwendet keinen Mutex und keinen Atomic. +

+

+ Und genau deshalb kann er das Setzen des Instanz-Pointers sehen, ohne daß eine +

+

+ Ordnungsbeziehung zur der restlichen Initialisierung oder lazy computation besteht. +

+

+ +

+

+ Fix: die Beziehung herstellen. Das ist verursacht stets zusätzliche Kosten. +

+

+ +

+

+ Allerdinsg nicht auf einer Plattform, die ohnehin sequentiell-konsistent ist. Wie "zum Beispiel" x86/64 +

+ +
+
+
+
+ + + + + + + +

+ ...haben wir es hier mit einem Pattern zu tun +

+ +
+
+ + + + + + +

+ ich nenne es "synchronised visibility cones" +

+ +
+
+ + + + + + +

+ Dieses errichtet die Fiktion, +

+

+ als würden wir nur auf einer gemeinsamen (shared) Instanz arbeiten. +

+

+ In Realität arbeiten mehrere Threads/Cores mit mehreren Entitäten +

+ +
+ +
+ + + + + + +

+ ...und andernfalls überhaupt vermeiden, +

+

+ die shared zone anzufassen! +

+ +
+
+
+
+
+ + + + + + @@ -18390,7 +32219,7 @@ - + @@ -18428,11 +32257,11 @@ - - - - - + + + + + @@ -18457,19 +32286,19 @@ - - - - - + + + + + - - - + + + @@ -18483,15 +32312,104 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - + - + @@ -18503,7 +32421,7 @@ - + @@ -18515,7 +32433,7 @@ - + @@ -18541,7 +32459,7 @@ - + @@ -18553,12 +32471,12 @@ - - - + + + - + @@ -18570,10 +32488,10 @@ - - - - + + + + @@ -18586,7 +32504,7 @@ - + @@ -18600,7 +32518,7 @@

- + @@ -18612,7 +32530,7 @@ - + @@ -18627,7 +32545,7 @@ - + @@ -18639,9 +32557,9 @@ - - - + + + @@ -18653,7 +32571,7 @@ - + @@ -18668,7 +32586,7 @@ - + @@ -18683,7 +32601,7 @@ - + @@ -18704,8 +32622,8 @@ - - + + @@ -18721,10 +32639,10 @@ - + - + @@ -18743,11 +32661,11 @@ - - + + - + @@ -18759,9 +32677,9 @@ - - - + + + @@ -18773,9 +32691,9 @@ - + - + @@ -18788,7 +32706,7 @@ - + @@ -18801,7 +32719,7 @@ - + @@ -18837,29 +32755,29 @@ - + - - - + + + - + - + - + @@ -18878,7 +32796,7 @@ - + @@ -18891,12 +32809,12 @@ - + - - + + @@ -18908,7 +32826,7 @@ - + @@ -18920,39 +32838,39 @@ - + - + - + - - + + - + - + - + - + - + - + - + - + @@ -18967,7 +32885,7 @@ - + @@ -18981,14 +32899,14 @@ - + - + - + - + @@ -19001,7 +32919,7 @@ - + @@ -19014,17 +32932,17 @@ - + - + - + - + @@ -19038,7 +32956,7 @@ - + @@ -19054,7 +32972,7 @@ - + @@ -19072,15 +32990,15 @@ - - - + + + - + - + @@ -19093,12 +33011,12 @@ - - - - + + + + - + @@ -19120,17 +33038,17 @@ - + - + - + - + @@ -19146,18 +33064,18 @@ - - - + + + - + - + - + @@ -19174,26 +33092,26 @@ - + - + - + - + - + - + - + @@ -19205,13 +33123,13 @@ - + - + - + @@ -19223,9 +33141,9 @@ - - - + + + @@ -19238,26 +33156,26 @@ - + - + - - + + - + - + - + - + @@ -19272,7 +33190,7 @@ - + @@ -19303,12 +33221,12 @@ - + - + - + @@ -19321,10 +33239,10 @@ - + - + @@ -19336,7 +33254,7 @@ - + @@ -19349,7 +33267,7 @@ - + @@ -19372,12 +33290,12 @@ - + - + - + @@ -19390,28 +33308,28 @@ - + - + - + - + - + - + - - + + @@ -19430,7 +33348,7 @@ - + @@ -19442,18 +33360,18 @@ - + - - + + - + - - - + + + @@ -19465,7 +33383,7 @@ - + @@ -19477,15 +33395,15 @@ - - + + - + - + - + @@ -19499,20 +33417,166 @@ - + - - + + - - + + - + + + + + + + + + + + +

+ TEST Dispatch functors into other threads: CallQueue_test .. FAILED +

+

+ unexpected return value 134, expected 0 +

+

+ stderr was: +

+

+ 0000000459: INFO: suite.cpp:180: thread_1: invokeTestCase: ++------------------- invoking TEST: CallQueue_test +

+

+ 0000003117: CHECK: call-queue-test.cpp:251: thread_1: verify_ThreadSafety: (globalProducerSum == globalConsumerSum) +

+

+ 0000003127: BACKTRACE: call-queue-test.cpp:251: thread_1: verify_ThreadSafety: /Werk/devel/lumi/target/modules/libtest-basics.so(_ZN3lib4test14CallQueue_test19verify_ThreadSafetyEv+0x24a) [0x7fdfce328a20] +

+

+ 0000003128: BACKTRACE: call-queue-test.cpp:251: thread_1: verify_ThreadSafety: /Werk/devel/lumi/target/modules/libtest-basics.so(_ZN3lib4test14CallQueue_test3runERSt6vectorISsSaISsEE+0x34) [0x7fdfce32764e] +

+

+ 0000003129: BACKTRACE: call-queue-test.cpp:251: thread_1: verify_ThreadSafety: /Werk/devel/lumi/target/modules/liblumierasupport.so(+0x1d7cb0) [0x7fdfcb7dfcb0] +

+

+ 0000003130: BACKTRACE: call-queue-test.cpp:251: thread_1: verify_ThreadSafety: /Werk/devel/lumi/target/modules/liblumierasupport.so(_ZN4test5Suite3runERSt6vectorISsSaISsEE+0x38f) [0x7fdfcb7e0249] +

+

+ 0000003131: BACKTRACE: call-queue-test.cpp:251: thread_1: verify_ThreadSafety: ./test-suite() [0x40625e] +

+

+ 0000003132: BACKTRACE: call-queue-test.cpp:251: thread_1: verify_ThreadSafety: /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf5) [0x7fdfc9029b45] +

+

+ 0000003133: BACKTRACE: call-queue-test.cpp:251: thread_1: verify_ThreadSafety: ./test-suite() [0x4060a9] +

+

+ END +

+ +
+ + + + + + + + +

+ for I in `seq 1 50`; do target/test-suite CallQueue_test; done +

+ +
+
+
+ + + + + + + +

+ habe gleichzeitig erst die Testsuite gebaut mit -j 36 und dann laufen lassen. +

+

+ Gleichzeitig aber auch noch das ./build-website-Skript +

+

+ und eine Doxygen-Seite im Browser geladen +

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +

+ weil sich die Threads gegenseitig ihre Counter inkrementieren. +

+ +
+
+ + + + + +
+
+ + + + + + + + + +

+ alle anderen (mit Ausnahme von BusTerm_test) +

+

+ verwenden globale Variable oder überhaupt keine Objektfelder +

+ +
+ +
+
+
+
+