Compare commits
145 commits
prg@ichthy
...
integratio
| Author | SHA1 | Date | |
|---|---|---|---|
| ce40b83bb6 | |||
| b804015cd7 | |||
| eeabbb4f60 | |||
| ab22f3766c | |||
| 216ab525fd | |||
| 9461443e84 | |||
| fd0e62076a | |||
| 56561c8f78 | |||
| f868a7462a | |||
| 9f0d7cc796 | |||
| ae7c9e7889 | |||
| 7989e825b9 | |||
| 5a7cfb613f | |||
| 08fef0c64c | |||
| 37fc78b027 | |||
| 3b9641597f | |||
| 0256ad8528 | |||
| e32eec38dd | |||
| cd7581ba76 | |||
| a4b83135a0 | |||
| 0204438147 | |||
| c4db787b25 | |||
| 7448b81d49 | |||
| 3a491855e7 | |||
| d94bfe64b5 | |||
| 0567633bd9 | |||
| 76542e873a | |||
| 5a47b099be | |||
| 7c44cac3df | |||
| 9835e837c5 | |||
| 24bb03f510 | |||
| 5a904eaf2c | |||
|
|
5a2dd77bcc | ||
| bf446626a8 | |||
| 76dbb980b5 | |||
| 077ef891ca | |||
| 5fafb16807 | |||
| b59cb12974 | |||
| dd37600c4d | |||
| e328018b05 | |||
| e277913e22 | |||
| 3b7877204d | |||
| abecd2cbde | |||
| 606d98bf9c | |||
| 57cd51e3a2 | |||
| 1601838037 | |||
| c05a73d68a | |||
| 9bd3e550dc | |||
| a06ef95e94 | |||
| 8d563d1ea9 | |||
| 4dc6885665 | |||
| 44b3dbe682 | |||
| ce5f7d5e06 | |||
| 5686694999 | |||
| 7652ca05cd | |||
| 2c8ee05a23 | |||
| a0d83b4fe8 | |||
| 0760525ea5 | |||
| a835a796b2 | |||
| 40d3167d1a | |||
| d3407094be | |||
| 00098dd815 | |||
| 0326f9e85d | |||
| f05402c04e | |||
| fbda9baeb3 | |||
| 68460d4900 | |||
| bf77e53b4c | |||
| a7c3a8d80d | |||
| 54c2a30f54 | |||
| ec9b73d418 | |||
| bd09d9fbbb | |||
| c87bc7fe27 | |||
| f29ec44fda | |||
| eeedb7a028 | |||
| 7bae52d1f8 | |||
| 473299fb5a | |||
| 0438571b73 | |||
| 2ee6bb3b04 | |||
| 9b6933b572 | |||
| ea0ae5f78e | |||
| 94320d2d23 | |||
| 7463228d38 | |||
| e0d6362bad | |||
| 9be75c9305 | |||
| 0178e11e65 | |||
| d91ca67a27 | |||
| 705bd3d2e0 | |||
| 259d0a5728 | |||
| 4fa18cf1d2 | |||
| 7dee16ba90 | |||
| 994a4cf557 | |||
| cc9dbd8a40 | |||
| 3caa5cfb03 | |||
| 9c74466778 | |||
| 35f946d8d9 | |||
| 9187812472 | |||
| 8e250839f4 | |||
| ea627c1c7a | |||
| 8437710232 | |||
| 77217d9785 | |||
| 02ba977c61 | |||
| 5a203513c9 | |||
| cfdb659ccb | |||
| 722c12e312 | |||
| 6c880c5d10 | |||
| 8a8f98ceb0 | |||
| 064651335f | |||
| 191667197d | |||
| 2ca763bce3 | |||
| 945636c446 | |||
| 43c4f7a518 | |||
| 6de1837042 | |||
| 3d6e2fea75 | |||
| 75913725a8 | |||
| c0e2d3b70e | |||
| 7d237647d4 | |||
| 1230cbae12 | |||
| 3738026945 | |||
| 6f14046c0c | |||
| 5518030a7d | |||
| 9b66818bda | |||
| 70e32a3e66 | |||
| c3ebade5d2 | |||
| 75cda5ebc6 | |||
| 925ee0f27e | |||
| cd415c05aa | |||
| e10aa751bb | |||
| ace20660aa | |||
| 9c5eb11b0c | |||
| a9bdad2c66 | |||
| c21ddc1844 | |||
| bb6401aaf3 | |||
| 1ebefbe36e | |||
| 256c446705 | |||
| 0f12b19148 | |||
| 559bb17c17 | |||
| d00992ff70 | |||
| 3fe6d5f54c | |||
| 1986850fa3 | |||
| b22ffdacb6 | |||
| 1f0b8085e3 | |||
| 9df85992ee | |||
| 1266427d68 | |||
| 8220981e1b | |||
| 5289e43066 |
734 changed files with 67030 additions and 11647 deletions
4
README
4
README
|
|
@ -61,8 +61,8 @@ For building Lumiera, you'll need:
|
|||
* https://gtkmm.gnome.org/en/[gtkmm]
|
||||
* https://cgit.freedesktop.org/xorg/lib/libXv[libXv]
|
||||
* https://wiki.gnome.org/Projects/LibRsvg[lib rSVG]
|
||||
* https://git.lumiera.org/?p=debian/gdl;a=summary[lib GDL]
|
||||
* https://git.lumiera.org/?p=debian/gdlmm;a=summary[GDL-mm bindings]
|
||||
* https://git.lumiera.org/gitweb?p=debian/gdl;a=summary[lib GDL]
|
||||
* https://git.lumiera.org/gitweb?p=debian/gdlmm;a=summary[GDL-mm bindings]
|
||||
|
||||
Further information:
|
||||
|
||||
|
|
|
|||
|
|
@ -97,6 +97,8 @@ def configure(env):
|
|||
problems.append('We need boost::lexical_cast')
|
||||
if not conf.CheckCXXHeader('boost/format.hpp'):
|
||||
problems.append('We need boost::format (header).')
|
||||
if not conf.CheckCXXHeader('boost/lockfree/queue.hpp'):
|
||||
problems.append('We need boost::lockfree (header).')
|
||||
if not conf.CheckLibWithHeader('boost_program_options','boost/program_options.hpp','C++'):
|
||||
problems.append('We need boost::program_options (including binary lib for linking).')
|
||||
|
||||
|
|
|
|||
|
|
@ -1,42 +1,47 @@
|
|||
The Application : Start-up and Subsystems
|
||||
=========================================
|
||||
Application : Start-up and Subsystems
|
||||
=====================================
|
||||
:Date: 2009
|
||||
:Author: Ichthyo
|
||||
|
||||
//MENU: label Start-up
|
||||
|
||||
.the purpose of »Application-main«
|
||||
Lumiera is envisioned as a heavyweight yet coherent »**Application**« -- not so much
|
||||
as platform, framework or operating system. And, in accordance with this focus, we
|
||||
place no emphasis on possible configuration changes and state transitions within
|
||||
the running application _as a whole._ And, while in fact it is possible to close
|
||||
and load a new Session, most of the time we assume the application just to be
|
||||
``up and running'' -- all required services to be available and interfaces to
|
||||
be accessible. Without doubt, these are simplifications, but serve us well
|
||||
to cut down complexity -- yet still there needs to be one dedicated realm
|
||||
to deal with these specific concerns of configuration, dependency and lifecyle.
|
||||
Lumiera is envisaged as a heavyweight yet coherent »**Application**« -- which implies
|
||||
it is not so much seen as ``a platform'', framework or operating system. And, in line
|
||||
with this understanding, we place no emphasis on possible configuration changes and state
|
||||
transitions within the running application _as a whole._ The ability to react ``life'' to
|
||||
some global setting changes can be limited to relevant special cases. And, while it is
|
||||
indeed possible to close and load a new Session from a running application instance, in
|
||||
most parts of the code base we assume the application just to be ``up and running'' -- all
|
||||
required services are assumed to be available and interfaces to be accessible. If some of
|
||||
these assumptions break, an exception is raised and the application unwinds.
|
||||
Without doubt, these decisions represent a forcible simplification, and might be limiting
|
||||
at times, yet serve us well to cut down complexity in large parts of the code base --
|
||||
however, there needs to be _one dedicated realm_ to deal with all these specific concerns
|
||||
related to configuration, interdependency and lifecyle.
|
||||
|
||||
|
||||
The Application Realm
|
||||
---------------------
|
||||
So we treat all these concerns within a small and self contained structure, clearly
|
||||
set apart from all the other layers, services and subsystems. This dedicated
|
||||
_Application Realm_ is organised around the ``Application main object''.footnote:[
|
||||
This is the singleton `lumiera::AppState`, which is triggered by the `main` function
|
||||
of the Lumiera Application. The sourcecode is kept in a separate folder 'src/common'
|
||||
and linked into the shared library 'liblumieracommon.so']
|
||||
It serves the purpose of pulling up and tearing down the application in a controlled
|
||||
fashion. And additionally, it provides the Interface and Config core services.
|
||||
The act of building or tearing down this core realm and main object is what creates
|
||||
the *Lifecycle* of the application. This is a succession of ``lifecycle phases'' --
|
||||
and almost all activities happen within the _operational phase,_ when everything
|
||||
is ``up and running'' or ``just available''.
|
||||
The Vessel
|
||||
----------
|
||||
So we treat all these concerns within a focused and self contained structure, called
|
||||
the »**Vessel**«, and clearly set apart from all the other layers, services and subsystems.
|
||||
This dedicated _Application Realm_ is organised around the ``Application main object''.footnote:[
|
||||
This is the singleton `vessel::Voyage`, which is triggered by the `main` function
|
||||
of the Lumiera Application. The sourcecode is kept in a separate folder 'src/vessel'
|
||||
and linked into the shared library 'liblumieravessel.so']
|
||||
Its purpose is to bootstrap and tear-down the application in a controlled way,
|
||||
which includes to provide the backbone services, evaluate the start-up parameters
|
||||
and to connect and start the necessary core components. The beginning and end of
|
||||
this vessel's voyage therefore marks the *Lifecycle* of the application, as a
|
||||
succession of ``lifecycle phases'' -- where almost any activity happens within
|
||||
the _operational phase,_ when everything is considered ``up and running''.
|
||||
|
||||
Subsystems
|
||||
~~~~~~~~~~
|
||||
However, initially the application needs to be brought up, and at the end, all
|
||||
parts need to be unwound cleanly. To organise this process, we identify a limited
|
||||
number of *Subsystems* within the Application, which are more or less independent.
|
||||
However, before becoming operational, the application must be bootstrapped, and at the end,
|
||||
all parts need to be unwound cleanly. To organise this process, we identify a limited number
|
||||
of *Subsystems* within the Application, which are self-sufficient and operate independently,
|
||||
at least to some degree.
|
||||
Each link:{ldoc}/design/architecture/Subsystems.html[Subsystem] is self contained
|
||||
and groups some other parts and services, which typically work together and may
|
||||
be mutually dependent. These subsystems represent a grouping, applied for the purpose
|
||||
|
|
@ -61,7 +66,7 @@ the startup phase. It is the responsibility of the _Application main object_ to
|
|||
pull up required functionality, which in turn might result in pulling up
|
||||
further subsystems as dependencies.
|
||||
|
||||
Subsystems are defined by implementing the interface `lumiera::Subsys`, which acts
|
||||
Subsystems are defined by implementing the interface `vessel::Subsys`, which acts
|
||||
as façade to conduct the lifecycle, find out about dependencies and shut down
|
||||
the subsystem in the end. So this interface, together with the _Subsystem Runner,_
|
||||
define a lifecycle protocol; each subsystem is free to implement this as it
|
||||
|
|
@ -97,20 +102,21 @@ within the engine independently, relying on worker jobs and a thread pool.
|
|||
|
||||
Initialisation and Lifecycle
|
||||
----------------------------
|
||||
After some discussion,footnote:[See the
|
||||
link:{rfc}/GlobalInitialization.html[GlobalInitialisation] RfC
|
||||
from spring 2008. In the beginning, we all agreed to ``keep matters simple''
|
||||
and build an `init()` function within one central piece of code everyone knows
|
||||
and hooks into. However, while the outline of the application emerged, there
|
||||
was a tension between the concern about _over-engineering_ versus the concern
|
||||
about _tangled and unmanageable complexity._ At some point, an alternative
|
||||
implementation based on lifecycle callbacks was elaborated, which then turned
|
||||
into the solution described here. Lumiera then ceased to be the typical UI
|
||||
application started by GTK, and the existing GTK code was retrofitted to
|
||||
launch from within a plug-in.]
|
||||
After some discussion,footnote:[
|
||||
See the link:{rfc}/GlobalInitialization.html[GlobalInitialisation] RfC
|
||||
from spring 2008. In the beginning, we all agreed to ``keep matters simple''
|
||||
and build an `init()` function within one central piece of code everyone knows
|
||||
and hooks into. However, while the outline of the application emerged, there
|
||||
was a tension between the concern about _over-engineering_ versus the concern
|
||||
about _tangled and unmanageable complexity._ At some point, an alternative
|
||||
implementation based on lifecycle callbacks was elaborated, which then turned
|
||||
into the solution described here. At that point, Lumiera ceased to be the
|
||||
typical UI based application started-up by GTK-main, and the existing GTK
|
||||
code was retrofitted to be launched from within a plug-in.
|
||||
]
|
||||
the design leaned toward loosely coupled parts and a formal lifecycle; which
|
||||
saves us from investigating and coding up the various interdependencies
|
||||
explicitly. Rather, the parts of the application have to comply to
|
||||
explicitly. Rather, all parts of the application have to comply to
|
||||
link:{ldoc}/design/architecture/Subsystems.html#lifecycle[Lifecycle Phases],
|
||||
and each part has to care for its own state transitions, invoked through
|
||||
_lifecycle callbacks._ We can distinguish two distinct models how to deal
|
||||
|
|
@ -128,9 +134,9 @@ Application Start
|
|||
~~~~~~~~~~~~~~~~~
|
||||
* some fundamental language-level facilities will be prepared during
|
||||
_static initialisation._ At some point, execution enters `main(argc,arvv)`.
|
||||
* `AppState::init()` brings up the plugin loader and opens the config-interface.
|
||||
* `Voyage::init()` brings up the plugin loader and opens the config-interface.
|
||||
* ...followed by triggering *ON_GLOBAL_INIT*
|
||||
* the main thread then pulls up the subsystems (`AppState::maybeStart(subsystem)`),
|
||||
* the main thread then pulls up the subsystems (`Voyage::maybeStart(subsystem)`),
|
||||
according to the command line options.
|
||||
* within each subsystem, façade interfaces will be opened through the
|
||||
interface/plug-in system.
|
||||
|
|
@ -142,7 +148,7 @@ Application Start
|
|||
the main window.
|
||||
* there is an *ON_GLOBAL_SHUTDOWN* event, which can be used for normal cleanup;
|
||||
In case of an emergency exit, the *ON_EMERGENCY_EXIT* event is triggered alternatively.
|
||||
* the AppState destructor tears down the core systems (config-interface and pluginloader).
|
||||
* the Voyage destructor tears down the core systems (config-interface and pluginloader).
|
||||
* we establish a policy to _prohibit any non-local and non-trivial activities_ during the
|
||||
tear-down phase after leaving the `main()` function.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +1,71 @@
|
|||
Lumiera Design: Application and Session Configuration
|
||||
=====================================================
|
||||
Application and Session Configuration
|
||||
=====================================
|
||||
|
||||
*TODO* : 'write design document'
|
||||
.After initial discussion, this topic was postponed.
|
||||
[CAUTION]
|
||||
In its current form, as of 2026, the Lumiera application is headed towards a first
|
||||
integration of the parts developed thus far. A simple configuration file, 'setup.ini'
|
||||
is used to supply some minimal parameters for bootstrap of the application; all further
|
||||
settings are defined as local constants in the source code.
|
||||
|
||||
The Lumiera application uses two quite different sources for configuration
|
||||
**Configuration and Parametrisation** of software describe a state of setting, that lies
|
||||
between the fixed structure embodied into the code and the transient work state of the
|
||||
application. It encompasses all settings and options that determine how a software system
|
||||
behaves in a specific environment, without changing its underlying source code.
|
||||
|
||||
- individual __settings values__ can be loaded from an application- and user configuration in the conventional way
|
||||
- various facilities, especially in the Steam-Layer, base the actual behavour on __queries__, which are
|
||||
to be resolve employing a rules based system '(planned)'. Configuration rules will be provided by the
|
||||
application (defaults), a session template and rules stored in the actual session.
|
||||
The purpose of configuration
|
||||
----------------------------
|
||||
For a video editing application, the scope of ``configuration'' can be far reaching.
|
||||
Not only are there numerous parameters in the engine, that must be fine-tuned for optimal
|
||||
performance, but also several patterns of behaviour are available that can not be decided by
|
||||
first principle alone and must thus be left open for the user to configure to their preference.
|
||||
Some of these are linked to the actual user, some to the setup in the system, and some to the
|
||||
current project.
|
||||
Furthermore, many of these ``settings'' turn out, upon closer inspection, to be a form of
|
||||
rule-based knowledge. In a project with the scale of a movie, it is essential for a fluid
|
||||
and intuitive working style to handle some default setup and configuration of connections
|
||||
and conversions automatically, while the user can have very valid reasons to prohibit
|
||||
some other specific class of automatic conversions to be applied without explicit confirmation.
|
||||
|
||||
-> see also the link:rfc/ConfigLoaderDraft2008.html[Config Loader brainstorming from 2008] (implementation details)
|
||||
After some initial, prototyping for a ``config loader'', it was deemed too risky to engage
|
||||
into any kind of preliminary design without knowing the possible ramifications. +
|
||||
-> link:/x/rfc/ConfigLoaderDraft2008.html[Config Loader brainstorming from 2008]
|
||||
|
||||
The following preliminary measures have now been established for the ongoing development:
|
||||
|
||||
- whenever a _tweaking parameter_ is discovered, it will be extracted and marked
|
||||
with a **language constant** in the setup or integration header of the software component.
|
||||
- whenever a _decision point_ is encountered, that can not be related to technical dependencies,
|
||||
it is marked as a **query call** -- with a corresponding, hard-wired _faked configuration rule_.
|
||||
|
||||
The integration of some kind of configuration rule language is considered part of the primary agenda.
|
||||
|
||||
Requirements
|
||||
------------
|
||||
To track any insights gained regarding the requirements for configuration,
|
||||
https://issues.lumiera.org/ticket/1224[Ticket #1224 (»Layered Configuration«)]
|
||||
was added to the list of medium-term
|
||||
https://issues.lumiera.org/report/17[»Focus Topics«]
|
||||
|
||||
The following requirements have been identified to date (2026)
|
||||
|
||||
- It is pointless to build one huge flat global "configuration file";
|
||||
rather, groups of configuration settings are scoped and closely related to a _local context_
|
||||
- the degree of _consistency checking_ must be tied to such a local context, where the
|
||||
situation is reasonably clear: the consumer of specific configuration usually has
|
||||
clear-cut requirements to demand some configuration to be mandatory, while other
|
||||
configuration is optional. Misspelled or misplaced configuration that does not
|
||||
fit in such a category must be flagged as error.
|
||||
- such a locally defined set of expected and optional configuration might become
|
||||
subject to versioning (forward- / backward compatibility)
|
||||
- Configuration must be assembled from several configuration sources,
|
||||
with clearly defined precedence and shadowing. Notably we have default configuration,
|
||||
system-wide configuration, user specific configuration and project related configuration.
|
||||
- a _preferences UI_ must be able to alter configuration settings, thereby intelligently
|
||||
routing the changes into the appropriate configuration source or create a suitable overlay
|
||||
- when configuration causes a decision from a rule to flip, this fact must be recorded
|
||||
permanently in the session; the reason for this mechanism is that configuration might
|
||||
change later, and the application must be able then to point out all places
|
||||
affected by the change.
|
||||
- Any detected configuration change must be captured and logged as Event and thus
|
||||
be un-doable cleanly, with all possible ripple effects.
|
||||
|
|
|
|||
|
|
@ -1,10 +1,48 @@
|
|||
Design Documents: Application Framework
|
||||
=======================================
|
||||
The Application Realm
|
||||
=====================
|
||||
|
||||
// Menu : prepend child ApplicationStart
|
||||
|
||||
* link:Config.html[Configuration]
|
||||
* link:plugin_loader.html[Plugin Loader]
|
||||
* link:ApplicationStart.html[Application Subsystems and Lifecycle]
|
||||
Shaping the outside world through software requires a variety of effective mechanisms
|
||||
that capture, represent, and transform elements of reality. A well-designed part is
|
||||
focused on a single aspect, is coherent and cohesive in itself, with links to other
|
||||
parts based on concepts and abstraction rather then on shared technology. Yet this
|
||||
structural perspective must be complemented by a meaningful overall arrangement that
|
||||
supports the desired workflow and can be adapted to circumstances.
|
||||
|
||||
In software systems, such coherency does not emerge spontaneously. It must be supported
|
||||
by definition work to establish suitable terms and interfaces, components must be
|
||||
interconnected and configured, and the overall state needs coordination.
|
||||
An application is therefore more than just a collection of software components.
|
||||
|
||||
In the Lumiera Architecture, this application realm is embodied into a structure
|
||||
called the »**Vessel**«:
|
||||
|
||||
- it is built as a library with only basic linkage to the support library
|
||||
- it can be invoked from a `main()` function, with an argument parser
|
||||
- at source code level, it provides all the types and definitions
|
||||
required to express the _binding_ between components
|
||||
- _conceptually,_ all layers are encased in the _application vessel_
|
||||
- and while the Vessel can not just ``control'' the configuration of parts
|
||||
on upper layers, it has to bootstrap their ability to adapt to circumstances.
|
||||
|
||||
--
|
||||
|
||||
--
|
||||
|
||||
Dependencies::
|
||||
While the code base is organised into _three layers,_ the actual implementation
|
||||
structures are arranged into **Subsystems** that _depend_ on each other and
|
||||
can be _started and shut down_
|
||||
+
|
||||
-> link:ApplicationStart.html[Subsystems and Lifecycle]
|
||||
|
||||
Configuration::
|
||||
As a means to adapt to the environment and enable flexibility,
|
||||
this topic is surprisingly intricate and development has barely scratched
|
||||
the surface: while the effective configuration is assembled from several
|
||||
layered sources, the meaning (and documentation) of individual settings
|
||||
is scoped, and users can expect the ability to override some settings
|
||||
from within the application.
|
||||
+
|
||||
-> link:Config.html[Configuration]
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
Lumiera Design: Plugin system
|
||||
=============================
|
||||
|
||||
Eventually, this will have design documentation for the Plugin system.
|
||||
|
||||
|
|
@ -237,7 +237,7 @@ 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`.
|
||||
can be simplified by planting a static variable of type `vessel::LifecycleHook`.
|
||||
|
||||
WARNING: A callback enrolled this way needs to be callable at the respective point in the lifecycle,
|
||||
otherwise the application will crash.
|
||||
|
|
@ -246,7 +246,7 @@ WARNING: A callback enrolled this way needs to be callable at the respective poi
|
|||
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`::
|
||||
`Voyage`::
|
||||
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:
|
||||
+
|
||||
|
|
@ -255,7 +255,7 @@ WARNING: A callback enrolled this way needs to be callable at the respective poi
|
|||
- the application configuration
|
||||
--
|
||||
+
|
||||
After starting those services within the `AppState::init()` function,
|
||||
After starting those services within the `Voyage::init()` function,
|
||||
the event `ON_GLOBAL_INIT` is emitted.
|
||||
|
||||
`ON_GLOBAL_INIT`::
|
||||
|
|
|
|||
19
doc/design/extension/index.txt
Normal file
19
doc/design/extension/index.txt
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
Extending the Application
|
||||
=========================
|
||||
|
||||
During the inception phase, the link:/x/rfc/AllPluginInterfacesAreC.html[idea was commended]
|
||||
to bootstrap the application as a multi-lingual compound of plug-ins, for maximum flexibility
|
||||
and ease of contribution. Although this approach garnered much support initially, the ensuing
|
||||
discussions questioned its viability, with well-known plugin-based systems and frameworks
|
||||
cited as cautionary example.
|
||||
|
||||
A prototype implementation
|
||||
(-> link:PluginBrainstorm.html[draft for the plugin interface])
|
||||
was nevertheless developed, but it proved problematic in practical use.
|
||||
The goal of a flexibly expandable application is still maintained,
|
||||
but the path to achieving it should be based on actual requirements.
|
||||
|
||||
Development of an https://issues.lumiera.org/ticket/1212[Extension Concept #1212]
|
||||
was thus placed on the list of medium-term
|
||||
https://issues.lumiera.org/report/17[»Focus Topics«]
|
||||
|
||||
|
|
@ -7,22 +7,23 @@ Lumiera Design Documents
|
|||
// Menu : prepend child design/gui
|
||||
// Menu : prepend child architecture
|
||||
|
||||
// Menu : append child plugins
|
||||
// Menu : append child extension
|
||||
// Menu : append child workflow
|
||||
// Menu : append child governance
|
||||
|
||||
Lumiera is to be a professional tool for video editing on
|
||||
Lumiera aspires to be a professional tool for video editing on
|
||||
GNU/Linux systems. The vision of the development team defines a modern design
|
||||
for the core of a Non-Linear Editing software. A key aspect of Lumiera
|
||||
is the strong separation between the user interface and the processing core.
|
||||
We are developing Lumiera with a GTK GUI but that will in no way be exclusive;
|
||||
other GUIs can be written, as well as scripts to drive the core.
|
||||
We are developing Lumiera with a general purpose GTK UI -- which is loaded as
|
||||
an optional component, so that the core can also be invoked from scripts to
|
||||
use the editing and processing capabilities of the engine.
|
||||
This becomes possible by an ongoing effort to decrease coupling. Each major
|
||||
component in the application strives to be open to extensions, but closed
|
||||
against external modification. The presentation, the model and the ``performing''
|
||||
of the model are separate and self-contained parts of the application. Lumiera
|
||||
as a processing core will be able to perform any conceivable
|
||||
task on video and audio, maybe even tasks entirely unrelated to video editing.
|
||||
of the model are separate and self-contained parts of the application.
|
||||
Lumiera as a processing core will be able to perform any conceivable task
|
||||
on video and audio, maybe even tasks entirely unrelated to video editing.
|
||||
|
||||
.Workflow
|
||||
*****************
|
||||
|
|
@ -52,7 +53,8 @@ specialised GUIs. The interface is the component that is closest to the user. I
|
|||
provides purely visual interaction with the user. Within this work environment, the user
|
||||
manipulates, organizes, loads, configures all sorts of data, especially MObjects (media
|
||||
objects) and Assets. These elements are contained within a structure called the Session. +
|
||||
-> link:gui/index.html[GUI/Stage-Layer design documents]
|
||||
-> link:gui/index.html[GUI/Stage-Layer design documents] +
|
||||
-> link:{ldoc}/technical/stage/index.html[GUI technical documentation]
|
||||
|
||||
|
||||
Steam: the Transformation Layer
|
||||
|
|
@ -87,24 +89,23 @@ the screen or to external output. +
|
|||
Extra Components
|
||||
----------------
|
||||
|
||||
The Application
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
The application controls the initialization and shutdown procedures as well as
|
||||
loading external elements such as plug-ins, which are widely used throughout
|
||||
Lumiera. It acts as the framework which supports core component operations. This
|
||||
framework is complemented by a library of commonly used components, algorithms and data structures. +
|
||||
-> link:application/index.html[Application framework]
|
||||
The Vessel
|
||||
~~~~~~~~~~
|
||||
A structure that is anchored below all layers, yet contains everything, conceptually.
|
||||
Definitions for building common interfaces are located here, as is the mechanism for initialisation
|
||||
and shutdown of the application, the loading of bootstrap configuration and optional components.
|
||||
It encloses and coordinates all parts to work together as a coherent whole.
|
||||
This -> link:application/index.html[Application framework] is complemented by
|
||||
a link:{ldoc}/technical/library/index.html[Support-Lbrary] with commonly used components,
|
||||
algorithms and data structures.
|
||||
|
||||
|
||||
link:plugins/index.html[Plugins]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Plug-ins play an important role within Lumiera since the application relies
|
||||
heavily on current FLOSS implementations and external libraries. Moreover, the
|
||||
application will be configurable and can be extended in various
|
||||
ways; whenever some extension isn't permanent or will be used only on demand, it
|
||||
is packaged as a separate module into a plug-in. For example, the GUI of Lumiera
|
||||
is a plugin. +
|
||||
-> design documents regarding the link:plugins/index.html[Plugins]
|
||||
|
||||
Extensions
|
||||
~~~~~~~~~~
|
||||
Lumiera uses proven and field-tested implementations for the actual media processing,
|
||||
which are available as FLOSS or open-source libraries. To avoid extensive external dependencies
|
||||
on the main application however, such libraries need to be loaded through _adaptor plug-ins._
|
||||
Furthermore, the application will be configurable and it is conceivable that external
|
||||
extensions will be created. Designing and building a future-proof framework for configuration
|
||||
and extension is an important medium-term goal for further development.
|
||||
|
|
|
|||
|
|
@ -1,14 +1,30 @@
|
|||
Design Documents: Vault
|
||||
=======================
|
||||
Low-level Services
|
||||
==================
|
||||
|
||||
What follows is a summary of Lumiera's *Data Handling Backend*
|
||||
Towards the _upper side_ of the Lumiera core, in »Steam Layer«,
|
||||
several components and subsystems work together to provide functionality
|
||||
that is close to the requirements of the _problem domain_ -- editing and
|
||||
rendering of media. The implementation of these features however requires
|
||||
a foundation of generic, technical services: Memory needs to be allocated,
|
||||
working buffers must be provided and managed, work tasks coordinated.
|
||||
It is hard to associate these responsibilities directly to
|
||||
some specific domain, other than just _data processing._
|
||||
|
||||
This is the foundation layer responsible for any high performance or high volume
|
||||
data access. Within Lumiera, there are two main kinds of data handling:
|
||||
A common trait of all those special aspects of processing is that they
|
||||
require special care and attention to achieve the necessary level of
|
||||
performance, while they are _technical_ in nature and do not directly
|
||||
reflect and model structures of the real world.
|
||||
|
||||
Data Handling
|
||||
-------------
|
||||
The task of editing video and processing media requires to read and write
|
||||
a significant amount of data, while using the capabilities of the hardware
|
||||
to the best degree possible. Lumiera thus needs a foundation layer for
|
||||
high volume data access; two kinds of data handling can be distinguished:
|
||||
|
||||
* The Session and the object models manipulated through the GUI are kept in memory.
|
||||
They are backed by a _storage backend,_ which provides database-like storage and
|
||||
especially logging, replaying and ``Undo'' of all ongoing modifications..
|
||||
They will be backed by a _storage backend,_ which provides database-like storage
|
||||
and especially logging, and controlled execution of _mutating actions._
|
||||
* Media data is handled _frame wise_ -- prefetching data asynchronously.
|
||||
|
||||
The goal is to optimize CPU utilization; large scale data IO will be performed
|
||||
|
|
|
|||
|
|
@ -1,4 +0,0 @@
|
|||
Design Documents: Plug-In
|
||||
=========================
|
||||
|
||||
* link:PluginBrainstorm.html[early draft for the plugin interface]
|
||||
|
|
@ -1,16 +1,15 @@
|
|||
Lumiera Workflow Proposals
|
||||
==========================
|
||||
:Author: Wouter Verwijlen
|
||||
:Date: 19 August 2025
|
||||
:Date: 16 March 2026
|
||||
:TOC:
|
||||
|
||||
I would like to share a first version of a collection of workflow ideas for Lumiera. These ideas come
|
||||
from having cut hundreds (or possibly thousands) of videos over the past 20 years as a professional
|
||||
editor, working in Avid Media Composer, Adobe Premiere Pro (from before it was called "Pro"),
|
||||
Lightworks and DaVinci Resolve.
|
||||
I would like to share a collection of workflow ideas for Lumiera. These ideas come from
|
||||
having cut hundreds (or possibly thousands) of videos over the past 20 years as a professional
|
||||
editor, working in Avid Media Composer, Adobe Premiere, Lightworks and DaVinci Resolve.
|
||||
|
||||
This document focuses on analysing the behaviour of Non-linear Editors (NLE's), in other words
|
||||
software in which a user can edit films and videos, in order to discuss possible solutions for the
|
||||
This document focuses on analysing the behaviour of Non-linear Editors (NLE's): software
|
||||
in which a user can edit films and videos, in order to discuss possible solutions for the
|
||||
design of Lumiera.
|
||||
|
||||
The main ideas behind these proposals are:
|
||||
|
|
@ -178,14 +177,354 @@ specific project in order to select the parts needed for the final edit. I will
|
|||
which different NLE's handle this and will propose possible directions for Lumiera to support
|
||||
certain workflows.
|
||||
|
||||
[red]#To be written:#
|
||||
Project creation
|
||||
~~~~~~~~~~~~~~~~
|
||||
When starting a new project, a user often needs to set the project resolution and framerate. Adobe
|
||||
Premiere (optionally) shows an import panel immediately after this. The first time you drop a clip
|
||||
on a timeline, Resolve and Premiere will ask if you want to change your project settings to match
|
||||
the clip settings, when these are different.
|
||||
|
||||
.Premiere's import panel
|
||||
image::{imgg}/wouter/v4-01-importPanel.jpg[width="100%", alt="Premiere's import panel"]
|
||||
|
||||
Projects themselves exist within an application's database (Resolve), or as a file/folder on local or
|
||||
shared storage (Avid, Premiere, Lightworks, FCP).
|
||||
|
||||
One of the first things that needs to be setup is the organisational structure of the project, in other
|
||||
words: the structure that's going to house all of the project's contents. Most applications use bins for
|
||||
this, a bin being simply a container for items like video clips. In a sense they are similar to folders
|
||||
on a computer's storage, except bins usually only exist within the project in your NLE. Avid's bins
|
||||
also exist as folders on local or shared storage, but this is an exception.
|
||||
|
||||
For top level organisation, bins can either be placed in other bins (Premiere, Resolve) or in
|
||||
groups/folders (Avid, Lightworks).
|
||||
|
||||
As we'll see throughout this document, Final Cut Pro is usually the only application that takes a
|
||||
different approach. Projects are called ``libraries'' and FCP sorts footage based on events (top level
|
||||
containers of timelines, footage and keyword collections), keywords (tags) and collections, instead
|
||||
of bins (timelines are confusingly called ``projects''). Footage, when imported, is placed in an event
|
||||
and from there all emphasis is on the metadata, in how you rate and tag (parts of) the footage. This
|
||||
also serves as a replacement of the subclips feature found in other NLE's.
|
||||
|
||||
.Final Cut Pro's Keywords dialog. +
|
||||
image::{imgg}/wouter/v4-02-keywords.jpg[width="100%", alt="Final Cut Pro's Keywords dialog"]
|
||||
^↑``Travel part 1'' and ``Travel part 2'' on the left are events.^
|
||||
|
||||
|
||||
In any case, when working in a professional environment, you will probably want to start with a
|
||||
template that defines your top level structure, instead of having to recreate your entire bin setup
|
||||
for every project. Surprisingly, at the time of writing only Adobe Premiere has introduced a project
|
||||
template feature (in version 2024). In other apps you will need to manually create your own template project
|
||||
and duplicate that, or even literally copy and paste bins from a template project in a file browser (Avid).
|
||||
|
||||
Importing footage
|
||||
~~~~~~~~~~~~~~~~~
|
||||
Every application works with clips: virtual representations of (parts of) the video files that exist on
|
||||
your preferred type of storage. Getting footage into your project (``ingest'') will create these clips in
|
||||
your project. Most apps will offer several options to do this:
|
||||
|
||||
. Creating links to the original video files, wherever these may reside (on your computer,
|
||||
external storage, network storage or in the cloud).
|
||||
. Copying your files to a dedicated folder managed by the application (this could again be on
|
||||
local or network storage). Importing to a dedicated application folder is very useful for
|
||||
feature film editing and TV production, but is less common elsewhere in the media industry.
|
||||
Its strong point is that when your media is managed by the application, you won't have to
|
||||
re-link files when moving to a different editing station.
|
||||
. Optionally, files can be transcoded during import. New media files will be created in either
|
||||
the app's dedicated folder or to a user specified location. The transcoded files will be much
|
||||
more optimised for editing, resulting in better playback performance.
|
||||
|
||||
The latter is part of what is known as the *offline-online* workflow, in which you work with
|
||||
optimised files, and once an edit is finished (``picture lock''), the clips on the timeline
|
||||
will be exchanged with clips that link to the original camera files. Often, after colour grading
|
||||
in an external application, all colour graded shots are rendered with their colour corrections
|
||||
and placed back onto the timeline in the NLE.
|
||||
|
||||
A specific type of offline-online workflow is the *proxy workflow*, that allows switching
|
||||
on-the-fly between the proxy files and the original camera files and will ensure that exports
|
||||
of your video will use these original camera files when rendering. When using a proxy workflow,
|
||||
you can either create optimised files inside the application itself (or in Premiere's case, via
|
||||
Adobe Media Encoder) or attach existing proxy files that were created by a third party application
|
||||
or by the camera during recording.
|
||||
|
||||
After importing media and possibly creating/attaching proxies, clip attributes might need altering.
|
||||
You might need to change incorrectly determined frame rates or, more commonly, change the audio
|
||||
channel mapping of your clips (e.g. setup clips with two audio channels as dual mono instead of
|
||||
stereo).
|
||||
|
||||
Clip types
|
||||
^^^^^^^^^^
|
||||
We usually find the following types of clips in NLE's:
|
||||
|
||||
Source clips:: they represent media files (containing video and/or audio streams) to its full
|
||||
extent, and for other types of media (most notably still images) -- these usually get a default
|
||||
duration upon import.
|
||||
Subclips:: they represent a portion of a video or audio file.
|
||||
Timeline clips:: duplicates of source clips that only exist within a timeline. Properties of
|
||||
these clips can often be altered without altering the same properties of the source clip.
|
||||
Compound clips/nested sequences:: these are in fact (sub)timelines that can contain any
|
||||
number of tracks and clips.
|
||||
Multicam clips:: (group clips in Avid), these are compound clips, in which multiple clips are
|
||||
synchronised and combined into one. This compound contains clips with different camera angles
|
||||
(for video) or audio recordings for the same event.
|
||||
Filler (Avid), FX clips (Lightworks) or Adjustment Clips (Premiere, Resolve):: these special clips
|
||||
can hold effects that are applied to all clips on lower tracks of a timeline, directly underneath.
|
||||
|
||||
Bin types
|
||||
^^^^^^^^^
|
||||
Often there are two types of bins in which these clips sit
|
||||
|
||||
. ``Normal'' bins.
|
||||
. Search bins (Premiere) / filters (Lightworks) / smart bins (Resolve) / smart collections
|
||||
(FCP): for these you setup certain rules based on clip names or metadata. All clips to which
|
||||
these rules apply are automatically added to these bins. This means that the same clip can
|
||||
simultaneously exist in a regular bin and a search bin, in contrast to normal bins, where each
|
||||
clip will only exist in a single bin and you'd have to duplicate this clip for it to be also
|
||||
placed in another bin (Lightworks being the exception here).
|
||||
|
||||
.Filter options in Lightworks
|
||||
image::{imgg}/wouter/v4-03-filterOptions.jpg[alt="Filter options in Lightworks"]
|
||||
|
||||
|
||||
Resolve adds a third kind of bins, known as ``Power bins'': these are bins that are accessible by all projects.
|
||||
|
||||
Bin views
|
||||
^^^^^^^^^
|
||||
Bins offer several views to see its contents. These include:
|
||||
|
||||
* List View / Text View: all items in the bin are listed vertically. Multiple columns will show
|
||||
different kinds of metadata -- the user can choose which fields are displayed and can
|
||||
rearrange their order.
|
||||
|
||||
* Icon View (Premiere) / Frame View (Avid) / Thumbnail View (Resolve) / Tile View
|
||||
(Lightworks): this will show a small frame from each clip in a raster. In general you're able
|
||||
to change which frame is displayed if you wish to see another.
|
||||
|
||||
* Freeform View (Premiere): same as Icon View, but you can freely position the clips
|
||||
wherever you want. Note that Avid and Lightworks allow you to do this within Icon/Frame
|
||||
View itself.
|
||||
|
||||
* Script View (Avid): similar to Text View, but with a frame being displayed for each clip and
|
||||
a large field for entering comments. In Script View, the vertical order of clips can be
|
||||
rearranged, in contrast to Text View, which only supports automatic sorting.
|
||||
|
||||
* Metadata View (Resolve): similar to Avid's Script View, except you can't reorder clips.
|
||||
|
||||
* Filmstrip View (Final Cut Pro): similar to Icon View, but [red]#TODO: missing text??#
|
||||
|
||||
Avid is famous for allowing you to save your Text or Script View configuration as a preset that can
|
||||
be quickly recalled. Premiere allows this through its Metadata Display window, Final Cut Pro by
|
||||
Control+left clicking any column header and Lightworks via the Columns > Presets menu, but these
|
||||
methods are less straightforward than Avid's. Another neat feature that Avid has is a bin map: an
|
||||
overview of bin contents that can be toggled when in Frame View.
|
||||
|
||||
.Creating a custom view preset in Avid Media Composer, which can be selected from the drop-down menu in the top bar.
|
||||
image::{imgg}/wouter/v4-04-customView.jpg[width="120%", alt="Creating a custom view preset"]
|
||||
|
||||
|
||||
Note that all apps offer a search/filter box (or button) on top of each bin view. Final Cut Pro and
|
||||
Resolve allow skimming clips from the bin view, simply by hovering the mouse on top of the
|
||||
thumbnail and moving it from left to right.
|
||||
|
||||
|
||||
Organising footage
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
Once your footage is represented by clips in bins, organisation can begin. Wildly different methods
|
||||
exist for this. In the simplest version you will just drop your footage in one or several bins and be
|
||||
done with it. In professional environments there are often strict bin structures and naming
|
||||
conventions, and additional methods for organising are being used:
|
||||
|
||||
* First and foremost: with multi-camera shoots and/or when audio has been recorded
|
||||
separately, now is the time to synchronise these. Create multicam clips and/or synchronise
|
||||
sound, either in the NLE itself, or in an external application. I personally create source reels
|
||||
first (see further down this list), export these to another application for syncing, import the
|
||||
synced reel back, and in case of a multicam shoot, split this reel into multiple reels and
|
||||
convert each to a multicam clip (Premiere and Resolve both support this).
|
||||
|
||||
* Metadata. This is a very powerful way to search for or filter specific types of content and it
|
||||
could be a chapter on its own. Metadata is often created by the camera during recording,
|
||||
added during offloading by a DIT (digital imaging technician) or applied after ingest by
|
||||
assistant editors. Nowadays metadata is also added by AI/machine learning algorithms to
|
||||
help identify the contents of a clip, create transcriptions from audio, etc. Avid provides the
|
||||
option to (automatically or manually) connect clips to lines in a script (ScriptSync).
|
||||
|
||||
* The aforementioned subclips are often used to extract sections out of a longer clip.
|
||||
|
||||
* Sequence/timeline markers to mark points in time, or sections (so-called ranged or spanned
|
||||
markers; terminology is different per app).
|
||||
+
|
||||
.Plenty of marker options in Adobe Premiere
|
||||
image::{imgg}/wouter/v4-05-markerOptions.png[width="70%", alt="Marker options in Premiere"]
|
||||
+
|
||||
|
||||
* Timeline clip notes (Avid): these can be added to a timeline clip, and will stay connected to
|
||||
this clip. You can't trim them off and they will automatically move whenever a clip moves.
|
||||
They can be burned into a render upon export. Premiere and Resolve offer clip markers
|
||||
instead -- these are similar to sequence markers, but are attached to clips.
|
||||
|
||||
* Tagging or rating (parts of) clips (FCP and to a lesser extent Resolve). FCP's rating only
|
||||
goes as far as letting you mark clips or sections as ``favourite'' or reject clips entirely,
|
||||
but its keyword system (as described later in this chapter) is much more elaborate and useful.
|
||||
Resolve has an option to add keywords, but its workflow is quite clunky when trying to
|
||||
apply this to a section within a clip.
|
||||
|
||||
* Clip colours. Often there exist multiple levels at which a clip can get a colour. Colours can be
|
||||
applied based on source clip (usually different colours for different media types), timeline
|
||||
clip, timeline track, offline footage, different framerates, and more. Avid has the most
|
||||
options in this field and lets you choose exactly which types of colours to display on the
|
||||
timeline. Manually set clip colours are a very useful organisational tool, e.g. to differentiate
|
||||
between several cameras, interviewed persons in a documentary or different locations.
|
||||
+
|
||||
.Avid has many options for choosing how clips are coloured in the timeline
|
||||
image::{imgg}/wouter/v4-06-clipColor.png[width="50%", alt="Coosing clip colours in Avid"]
|
||||
+
|
||||
|
||||
* Flags (Resolve). Similar to markers, but these apply to entire (source) clips, while markers
|
||||
apply to a single point in time or a time range within a clip. Multiple flags can be added to
|
||||
each source clip.
|
||||
|
||||
* Reels: source footage is divided over one or multiple timelines to be able to quickly navigate
|
||||
many clips without having to load individual clips in the source viewer. These are called
|
||||
``source reels''. A special kind of source reel is a ``sync map'', which contains all footage
|
||||
of a day of shooting from multiple cameras synced and laid out chronologically.
|
||||
FCP doesn't quite support source reels and instead offers continuous playback from the
|
||||
browser window. Resolve has a ``source tape'' option that works in a similar way.
|
||||
|
||||
|
||||
.Simple example of a source reel (top), converted to a sync map (bottom)
|
||||
image::{imgg}/wouter/v4-07-syncMap.png[width="100%", alt="Example of a sync map", link="{imgg}/wouter/v4-07-syncMap.png"]
|
||||
|
||||
Viewing footage
|
||||
^^^^^^^^^^^^^^^
|
||||
Once basic organisation is in place, the editor can start watching and adding further notes/comments
|
||||
to narrow down large amounts of footage and seek out options of useful content for the edit. Most
|
||||
apps offer two types of monitors for video playback: source and record/program monitors. FCP has
|
||||
only a single monitor that switches between these two functions. Some others (Avid, Resolve) give
|
||||
users the choice between single or multi viewer modes. Lightworks and Avid support an unlimited
|
||||
amount of floating viewers.
|
||||
|
||||
.You can open (and position) as many viewers as you like in Lightworks
|
||||
image::{imgg}/wouter/v4-08-multiViewer.jpg[width="120%", alt="Multiple viewers in Lightworks", link="{imgg}/wouter/v4-08-multiViewer.jpg"]
|
||||
|
||||
Viewers are usually coupled with timecode display and have a ton of settings.
|
||||
I won't go into details about these.
|
||||
|
||||
In most applications you can also watch your source footage from the bin when in Frame/Icon
|
||||
View. You'll be able to use JKL shortcuts for playback, mark in and out points and add it to the
|
||||
timeline, similar as to when the clip is loaded in the source viewer. Final Cut Pro lets you click and
|
||||
drag in-out regions in the thumbnail.
|
||||
|
||||
Selects
|
||||
^^^^^^^
|
||||
Time to create a selection of material to consider for the rough cut. One way to do this, as
|
||||
previously described, is by creating subclips or by tagging. Drop the subclips in a bin in
|
||||
Icon / Frame View and you have a good visual overview of selected shots. This is great for selecting
|
||||
B-roll material, or for projects that require a very visual approach, f.e. trailers, music videos
|
||||
or aftermovies (not hoping to ever cut one of those again though, but that's a different story 😉).
|
||||
|
||||
Another way to select footage is to cut selections from source clips or source reels to one or more
|
||||
selects reels: these are timelines that hold your selections and are only used for that purpose. Selects
|
||||
reels can then be loaded again as if they were a source clip, in order for an another selection to be
|
||||
made. This is called *reel-to-reel editing*. Many apps allow seeing the source timeline, either in their
|
||||
own panel (Premiere) or window (Lightworks), or by switching the timeline panel between a source
|
||||
and record mode (Avid, Resolve). A method known as _pancake timeline editing_ works when an
|
||||
application supports viewing multiple timelines simultaneously. By stacking timelines above each
|
||||
other you can easily drag selections from one timeline to another. This is possible in Premiere,
|
||||
Lightworks and Resolve.
|
||||
|
||||
.Pancake timeline editing in Lightworks
|
||||
image::{imgg}/wouter/v4-09-pancake.png[width="110%", alt="Example of a sync map", link="{imgg}/wouter/v4-09-pancake.png"]
|
||||
|
||||
Reels can be further managed by using markers to describe certain points in time or sections, and
|
||||
these markers can be viewed as a list in their own panel. When selecting a marker in this panel, the
|
||||
playhead of the corresponding monitor will jump to the (start) frame of this marker. Markers can
|
||||
have multiple input fields and a colour. They are one of the most important tools to be able to make
|
||||
notes and navigate large amounts of footage over extended periods of time. As an aside: Lightworks
|
||||
has an option to turn ranged markers into subclips, quite useful, as it allows you to use markers
|
||||
to make your selection.
|
||||
|
||||
Some NLE's have a ``ripple markers'' option for timeline markers. This means markers will move
|
||||
when clips in the timeline are shortened or extended.
|
||||
|
||||
FCP has ``todo markers'' that can be checked. And when tagging clips with keywords in FCP,
|
||||
keyword collections are automatically created in which all sections of clips with the same keyword
|
||||
can be found. It's also possible to manually create keyword collections and drag and drop clips or
|
||||
tagged sections (``content ranges'') there.
|
||||
|
||||
Back to the reels: one advantage of reel-to-reel editing is that it's very useful to see the audio
|
||||
waveforms of your source clips on an actual timeline: this way you can freely zoom and scroll.
|
||||
Final Cut Pro will only display a clip's waveform at the bottom of the clip preview in the browser,
|
||||
but when an interview is an hour or longer, this waveform will be squeezed in a way that's of little
|
||||
use.
|
||||
|
||||
Another advantage of reel-to-reel editing is a sense of space: you get an idea of where certain
|
||||
footage can be found (more to the left, in the middle, or at the end of a reel). Combined with
|
||||
markers, clip colours and gaps between sections, this makes it easier to find things back later. A third
|
||||
advantage is that it's easy to filter in rounds: you can narrow down your footage in multiple reel-to-reel
|
||||
selection passes.
|
||||
|
||||
I personally often use a variation of reel-to-reel editing, in which I lift sections in my source reel.
|
||||
I then duplicate this reel, remove the first video and audio track(s) and keep the lifted parts to create
|
||||
my selects reel. The advantage here is that I can rate sections as _not useful_ (V1), _possibly useful_
|
||||
(V2), _quite useful_ (V3) or _highly useful_ (V4) by lifting the video clip one, two or three tracks up --
|
||||
see it as a 4-star rating system. I have seen other editors use this method for selecting footage,
|
||||
although in all fairness I'm not sure how widespread this technique is.
|
||||
|
||||
.Lifting selections; also note the use of clip colours, gaps and markers for organising the selects reel.
|
||||
image::{imgg}/wouter/v4-10-selectsReel.png[width="100%", alt="Example of a sync map", link="{imgg}/wouter/v4-10-selectsReel.png"]
|
||||
|
||||
|
||||
In FCP you can create your own rating system by using keywords (as shown in the screenshot at the
|
||||
start of this chapter) and then having everything filtered through keyword collections, as described
|
||||
in this https://blog.frame.io/2016/10/18/make-fcp-x-log-better-1/[blog post at Frame.io].
|
||||
Quite handy as well!
|
||||
|
||||
Proposals
|
||||
~~~~~~~~~
|
||||
* Which system(s) of organising a project and selecting footage should Lumiera support? I
|
||||
would say: most. They don't interfere with each other, so let the user choose. Definitely
|
||||
support source timelines and let people rate, tag and mark all they want. Subclips might not
|
||||
be necessary when a tagging/keyword system is present, as Final Cut has proven.
|
||||
|
||||
* Metadata-based organising, searching and filtering of content are _must-haves_.
|
||||
|
||||
* I would include a native rating system (1 to 5 stars should suffice) that works both on clip
|
||||
sections and entire clips, rather than FCP's favourite/reject system, as it's more granular.
|
||||
|
||||
* As for bins-in-bins vs bins-in-folders: the first one is more flexible (bins can contain both
|
||||
media files and other bins, while folders can only contain bins) and more closely resembles
|
||||
how folders work on a PC, so my preference would be to go that route, although there's also
|
||||
something to be said for the strict division of function that folders+bins have.
|
||||
|
||||
* I would like to propose an option to automatically create source reels based on rules you can
|
||||
set, for example: one source reel for every day of shooting. These automatic source reels
|
||||
could have a filter function to select which clips are being displayed on the timeline. This
|
||||
way it becomes easy to say something like ``show me all clips from the second day of the
|
||||
shoot from camera A''.
|
||||
|
||||
* A versioning system for timelines is something that only Resolve has recently picked up
|
||||
with its Create (and Restore) Timeline Backup feature, and honestly I've been amazed by
|
||||
how this hasn't been a core feature in NLE's for decades now, as it makes so much sense to
|
||||
have. Let users make snapshots and perhaps make one automatically every day, without the
|
||||
need to manually duplicate and rename timelines and putting them in some sort of archive
|
||||
bin.
|
||||
|
||||
* Importing: auto-import based on watch folders could be very useful during certain stages of
|
||||
an edit. Setup which folders to watch and whenever a new file is dropped in that folder it
|
||||
will be imported into a bin of choice. Especially handy when working with sound effects.
|
||||
However, we might not want to be scanning folders all the time.
|
||||
|
||||
* About sound effects: there's a free application called Metadigger that let's you quickly filter
|
||||
your sound collection by metadata and then copy sound files to your project folder with a
|
||||
keyboard shortcut. Something like this, built-in, would be quite handy for those of us with
|
||||
local sound effect libraries.
|
||||
|
||||
* A small one: I'd like each project to remember the last location you imported from. I often
|
||||
switch between projects and each time I want to import something in current NLE's, the file
|
||||
dialog points to the last used directory, which is often the wrong one.
|
||||
Not a big deal, but not ideal either.
|
||||
|
||||
|
||||
- Asset organisation: bins vs metadata-based structures (+ search bins/filters)
|
||||
- Viewing footage: single clips vs source reels; continuous playback from browser
|
||||
- Source monitor + program monitor vs a single monitor
|
||||
- Creating selects: selects reels, subclips and keyword collections
|
||||
- On markers and notes
|
||||
- From selects to editing in the timeline: Timelines in the source viewer: reel to reel editing
|
||||
|
||||
Chapter 2: the timeline
|
||||
-----------------------
|
||||
|
|
@ -283,7 +622,7 @@ Editors often proudly share screenshots of their timelines on social media, and
|
|||
impressive, but these are in fact pretty fragile structures.
|
||||
|
||||
.One of my own timelines, which is relatively simple by comparison to those of many Hollywood movies
|
||||
image::{imgg}/wouter/01-timeline.jpg[width="110%", alt="Screenshot of a timeline", link="{imgg}/wouter/01-timeline.jpg"]
|
||||
image::{imgg}/wouter/v3-01-timeline.jpg[width="110%", alt="Screenshot of a timeline", link="{imgg}/wouter/v3-01-timeline.jpg"]
|
||||
|
||||
So naturally, a question would be: what will happen if we would let go of the track paradigm? This
|
||||
is what Final Cut Pro has done, starting from the rewrite of Final Cut Pro X. At the time, a
|
||||
|
|
@ -300,10 +639,10 @@ primary storyline, and all connected clips automatically move with it. With a mo
|
|||
ignore clip connections, so that you can also easily move a primary clip elsewhere without its
|
||||
connected siblings coming along.
|
||||
|
||||
image::{imgg}/wouter/02-trim-magnetic-1.jpg[width="100%", alt="(before trim)"]
|
||||
image::{imgg}/wouter/v3-02-trim-magnetic-1.jpg[width="100%", alt="(before trim)"]
|
||||
|
||||
.Extending one clip will automatically move all connected clips in order to keep sync
|
||||
image::{imgg}/wouter/02-trim-magnetic-2.jpg[width="100%", alt="(after trim)"]
|
||||
image::{imgg}/wouter/v3-02-trim-magnetic-2.jpg[width="100%", alt="(after trim)"]
|
||||
|
||||
_Sounds good._ Why not just copy this? One reason is that FCP assumes that all clips that are not on
|
||||
the primary storyline should be connected to this primary storyline. This might work well for fiction
|
||||
|
|
@ -318,7 +657,7 @@ How else could we group clips together that ought to be connected? +
|
|||
We could create groups that are directly editable, like so:
|
||||
|
||||
.Compound clips
|
||||
image::{imgg}/wouter/03-grouped-1.png[width="100%", alt="Timeline with compound clips"]
|
||||
image::{imgg}/wouter/v3-03-grouped-1.png[width="100%", alt="Timeline with compound clips"]
|
||||
|
||||
However, there are a few problems with this: we expect rendering to happen from top to bottom, in
|
||||
which case the b-roll would cover the subtitles. On top of that, it's hard to see at which points video
|
||||
|
|
@ -327,7 +666,7 @@ clips overlap. Last but not least: it looks rather unorganised.
|
|||
Can we restructure this? Perhaps like this:
|
||||
|
||||
.Overlapping compound clips
|
||||
image::{imgg}/wouter/03-grouped-2.png[width="100%", alt="Timeline with stacked overlapping compound clips"]
|
||||
image::{imgg}/wouter/v3-03-grouped-2.png[width="100%", alt="Timeline with stacked overlapping compound clips"]
|
||||
|
||||
But how exactly would a user interact with a timeline like this? I will leave the answer to that
|
||||
question for an actual design document, as for now I just wanted to show the concept of grouping so
|
||||
|
|
@ -354,7 +693,7 @@ FCP's precision editor. It doesn't change the working of keyboard shortcuts or t
|
|||
alters the timeline via animations in a very fluid way.
|
||||
|
||||
.Final Cut Pro's precision editor
|
||||
image::{imgg}/wouter/04-fcp-precision-editor.jpg[precision editor]
|
||||
image::{imgg}/wouter/v3-04-fcp-precision-editor.jpg[precision editor]
|
||||
|
||||
Another example is the Trim View in DaVinci Resolve: whenever you perform a trimming
|
||||
operation, the program monitor displays a trim view (showing the frames left and right of the cut
|
||||
|
|
@ -386,7 +725,7 @@ the use of a _contextual bar_ that appears over the bottom part of the timeline
|
|||
mode is active.
|
||||
|
||||
.Contextual bar with options for the mode/tool
|
||||
image::{imgg}/wouter/05-contextual-bar.png[width="100%", alt="contextual bar"]
|
||||
image::{imgg}/wouter/v3-05-contextual-bar.png[width="100%", alt="contextual bar"]
|
||||
|
||||
Currently I'm thinking of using this bar on two or three occasions:
|
||||
|
||||
|
|
@ -467,10 +806,10 @@ One last thing I would like to address is the destruction that an insert operati
|
|||
this can be prevented by proper track management, but you can wonder why you would ever want
|
||||
an insert action to cause the following:
|
||||
|
||||
image::{imgg}/wouter/06-sync-insert-1.png[height=110, alt="sync insert"]
|
||||
image::{imgg}/wouter/v3-06-sync-insert-1.png[height=110, alt="sync insert"]
|
||||
|
||||
.Inserting a clip with sync locks enabled for V2 might split a clip in Avid Media Composer.
|
||||
image::{imgg}/wouter/06-sync-insert-2.png[height=110, alt="sync insert"]
|
||||
image::{imgg}/wouter/v3-06-sync-insert-2.png[height=110, alt="sync insert"]
|
||||
|
||||
In this case, if you'd like the clips on V2 to stay in sync, the overlapping clip will be split in two.
|
||||
If you don't want this to happen you would have to move the overlapping clip to another track
|
||||
|
|
@ -622,7 +961,7 @@ will move both the viewport and playhead. Clicking the left mouse button will ac
|
|||
position and close the widget.
|
||||
+
|
||||
.Skim widget
|
||||
image::{imgg}/wouter/07-1-skim.png[width="100%", alt="A skim widget rendered as overlay"]
|
||||
image::{imgg}/wouter/v3-07-1-skim.png[width="100%", alt="A skim widget rendered as overlay"]
|
||||
|
||||
|
||||
Autoscroll widget:: when the user moves the mouse cursor slightly left or right, it will enter zones
|
||||
|
|
@ -631,14 +970,14 @@ When the user moves the mouse farther away from the initial starting point, the
|
|||
increase. This will also work vertically. The playhead will move along as well.
|
||||
+
|
||||
.Autoscroll widget
|
||||
image::{imgg}/wouter/07-2-autoscroll.png[width="100%", alt="A widget for auto-scrolling"]
|
||||
image::{imgg}/wouter/v3-07-2-autoscroll.png[width="100%", alt="A widget for auto-scrolling"]
|
||||
|
||||
|
||||
Zoom widget:: moving the mouse left or right from the centre will zoom horizontally, up and
|
||||
down will zoom vertically.
|
||||
+
|
||||
.Zoom widget
|
||||
image::{imgg}/wouter/07-3-zoom.png[width="100%", alt="An overlay widget to control zooming"]
|
||||
image::{imgg}/wouter/v3-07-3-zoom.png[width="100%", alt="An overlay widget to control zooming"]
|
||||
|
||||
Mouse-only navigation:
|
||||
|
||||
|
|
@ -648,7 +987,7 @@ mouse button. When released, it will commit to the new location/view. If a user
|
|||
immediately releases the button, a regular context menu will appear.
|
||||
+
|
||||
.Combined skim, zoom and scroll widget
|
||||
image::{imgg}/wouter/07-4-combined.png[width="100%", alt="Overlay widget to combin skim, zoom and autoscrol function"]
|
||||
image::{imgg}/wouter/v3-07-4-combined.png[width="100%", alt="Overlay widget to combin skim, zoom and autoscrol function"]
|
||||
|
||||
|
||||
Why popup widgets?
|
||||
|
|
@ -708,7 +1047,7 @@ what any other NLE does at the moment, but enabling multi-clip selection by keyb
|
|||
good reason to go in this direction.
|
||||
|
||||
.Selection mockup: selected clips are highlighted in yellow, the cursor is a white dot.
|
||||
image::{imgg}/wouter/08-selection.png[width="100%", alt="selecting clips"]
|
||||
image::{imgg}/wouter/v3-08-selection.png[width="100%", alt="selecting clips"]
|
||||
|
||||
For selecting, we can introduce an ``add to selection'' key which adds the highlighted clip to the
|
||||
current selection (apart from the convention of using Ctrl+mouse click). We could also think of
|
||||
|
|
@ -723,10 +1062,10 @@ Most NLE's have functions for selecting many clips at once, called ``select forw
|
|||
backward''. In Adobe Premiere these are tools: click and you select all clips to either the left or right
|
||||
of the mouse cursor, and using Shift will toggle between selecting all clips on a single track or on
|
||||
all tracks (it doesn't respect track activation status though, which it should). Other NLE's (Resolve,
|
||||
Avid, Lightworks) only have keyboard shortcuts for these functions, which do only affect activated
|
||||
tracks. This is a bit faster: in Premiere you need to switch to the Select Forward/Backward Tool,
|
||||
make your selection, then switch back to your previous tool. With a keyboard shortcut you only
|
||||
have to press a single key (if your track selection is correct).
|
||||
Avid, Lightworks) have keyboard shortcuts for these functions (in Avid you can place a button in the
|
||||
GUI as well), which do affect activated tracks only. This is a bit faster: in Premiere you need to
|
||||
switch to the Select Forward/Backward Tool, make your selection, then switch back to your previous
|
||||
tool. With a keyboard shortcut you only have to press a single key (if your track selection is correct).
|
||||
|
||||
Arranging clips
|
||||
~~~~~~~~~~~~~~~
|
||||
|
|
@ -789,7 +1128,7 @@ entering trim mode.
|
|||
* Lightworks has a trim mode that defaults to ripple trimming, unless you specifically use the
|
||||
“drag to black” feature by Control-clicking trim edges when entering trim mode. When
|
||||
using the keyboard, one needs to mark a section and use the Remove command (similar to
|
||||
Avid''s ``lift'') to create a gap.
|
||||
Avid's ``lift'') to create a gap.
|
||||
|
||||
Next to ripple/non-ripple trimming there are three other edit operations closely related to trimming
|
||||
that need to be mentioned:
|
||||
|
|
@ -816,10 +1155,10 @@ tracks automatically stay in sync. This means (manually) enabling or disabling t
|
|||
track. Once set, trimming a single clip can be enough to have all tracks move along, although you
|
||||
might still need to manually select trim sides for overlapping clips to get the desired result.
|
||||
|
||||
image::{imgg}/wouter/09-sync-trim-1.png[height=140, alt="trimming with sync-lock(before)"]
|
||||
image::{imgg}/wouter/v3-09-sync-trim-1.png[height=140, alt="trimming with sync-lock(before)"]
|
||||
|
||||
.Avid's sync locks will split longer clips by default, in order to keep sync. Such destructive behaviour should be avoided in Lumiera.
|
||||
image::{imgg}/wouter/09-sync-trim-2.png[height=140, alt="trimming with sync-lock(after)"]
|
||||
image::{imgg}/wouter/v3-09-sync-trim-2.png[height=140, alt="trimming with sync-lock(after)"]
|
||||
|
||||
Without sync locks you will have to select trim sides on every track that needs to stay in sync,
|
||||
although, as we'll see, even that can be accomplished by a single keystroke. FCP is the exception
|
||||
|
|
@ -850,7 +1189,7 @@ These commands can be combined: when pressing multiple keys, new trim sides are
|
|||
selection. This allows for quickly selecting many trim sides on many tracks.
|
||||
|
||||
.One keystroke led to the selection of these four trim sides in Lightworks
|
||||
image::{imgg}/wouter/10-trim-side.png["select trim side"]
|
||||
image::{imgg}/wouter/v3-10-trim-side.png["select trim side"]
|
||||
|
||||
Another feature that was already mentioned in a previous chapter, is FCP's precision editor, which
|
||||
can be triggered by double clicking a cut or by keyboard shortcut. This shows how much footage
|
||||
|
|
@ -937,7 +1276,7 @@ exited automatically whenever the user clicks in an empty spot of the timeline o
|
|||
mode key (T). This key can also be used to return to the last-used trim configuration.
|
||||
|
||||
.Mockup of the trim mode. The striped area marks the pre- and post-roll that will be previewed.
|
||||
image::{imgg}/wouter/11-trim-mode.png[width="100%", alt="using trim mode"]
|
||||
image::{imgg}/wouter/v3-11-trim-mode.png[width="100%", alt="using trim mode"]
|
||||
|
||||
|
||||
Splitting and merging clips
|
||||
|
|
@ -973,7 +1312,7 @@ I would like to propose the ability to divide a timeline into multiple sections.
|
|||
will have a header in the ruler that can be edited, to give each section a name (similar to how
|
||||
duration markers in Premiere are displayed).
|
||||
|
||||
image::{imgg}/wouter/12-sections.png[width="100%", alt="Timeline with sections"]
|
||||
image::{imgg}/wouter/v3-12-sections.png[width="100%", alt="Timeline with sections"]
|
||||
|
||||
The benefits of sections:
|
||||
|
||||
|
|
@ -1030,7 +1369,7 @@ transition from a clip's start or end to the position of the playhead.
|
|||
|
||||
* Avid has a configuration panel for adding transitions:
|
||||
|
||||
image::{imgg}/wouter/13-transition.png[align="center", alt="transition properties"]
|
||||
image::{imgg}/wouter/v3-13-transition.png[align="center", alt="transition properties"]
|
||||
|
||||
* Some apps allow adding transitions to every cut in the in-out range (Avid, Lightworks). Avid
|
||||
gives you the option to skip cuts that already have a transition. Very useful for adding short
|
||||
|
|
@ -1100,6 +1439,12 @@ Chapter 3: finishing
|
|||
--------------------
|
||||
[red]#To be written.#
|
||||
|
||||
- Nodes vs Layers
|
||||
- Audio mixing
|
||||
- Color grading
|
||||
- Visual effects compositing
|
||||
- Titling
|
||||
|
||||
Chapter 4: a broader GUI concept
|
||||
--------------------------------
|
||||
[red]#To be written.#
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ Current Release
|
|||
[red]#Lumiera is not usable yet#.
|
||||
|
||||
- A »Preview Release« was
|
||||
https://git.lumiera.org/?p=LUMIERA;a=tag;h=refs/tags/v0.pre.04[published at Dec 2, 2025]
|
||||
https://git.lumiera.org/gitweb?p=LUMIERA;a=tag;h=refs/tags/v0.pre.04[published at Dec 2, 2025]
|
||||
|
||||
- link:/debian[DEB packages]
|
||||
were built for »Trixie« and »Noble«
|
||||
|
|
|
|||
|
|
@ -214,7 +214,7 @@ Interface Definition Language
|
|||
-----------------------------
|
||||
In his work on the plugin loader, _Cehteh_ created a first draft how to export an interface, and calls for review.
|
||||
An example can be found in
|
||||
https://git.lumiera.org/?p=LUMIERA;a=blob;f=tests/backend/test-interfaces.c;h=09fa25afa3cb57b79a2717d0bf3a8253afeba000;hb=ebcc5c7fd2d8700f3375e3309b724ac2517a0f40[tests/backend/test-interfaces.c]
|
||||
https://git.lumiera.org/gitweb?p=LUMIERA;a=blob;f=tests/backend/test-interfaces.c;h=09fa25afa3cb57b79a2717d0bf3a8253afeba000;hb=ebcc5c7fd2d8700f3375e3309b724ac2517a0f40[tests/backend/test-interfaces.c]
|
||||
|
||||
An interface is a list of »slots« mapping functions. The interface descriptor is itself realised as interface,
|
||||
and thus can be versioned, extended and specialised.
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ For now the focus is on getting a single frame into the gui and there will be ti
|
|||
|
||||
The devs agreed to meet up on Saturday to get the latest version up and running.
|
||||
|
||||
NOTE: Demonstration in https://git.lumiera.org/?p=LUMIERA;a=blob;f=src/common/guifacade.cpp;h=60a16d0f67bd95b43e44662daa9c0525235e14f5;hb=5f10e65852de2c51364c2e2a5a1cf29cae81c6b2#l64[guifacade.cpp]
|
||||
NOTE: Demonstration in https://git.lumiera.org/gitweb?p=LUMIERA;a=blob;f=src/common/guifacade.cpp;h=60a16d0f67bd95b43e44662daa9c0525235e14f5;hb=5f10e65852de2c51364c2e2a5a1cf29cae81c6b2#l64[guifacade.cpp]
|
||||
|
||||
(The thread shall be forked from within the GUI code not from the facade)
|
||||
This was discussed and ichthyo has already pushed a new cleaned up state and resolved most of the dependency problems with the liblumiera. It raised the question: do we prefer a UI facade to a GUI facade or do we want an extra script facade someday?
|
||||
|
|
|
|||
|
|
@ -191,7 +191,7 @@ don't think we get much program options, maybe something to set a GUI skin.
|
|||
Moreover, I've written last year a thin wrapper around the commandline and
|
||||
integrated it with the boost options parser such that user code can receive the
|
||||
remaining options as a vector of `std::string`s. Please have a look at
|
||||
https://git.lumiera.org/?p=LUMIERA;a=blob;f=tests/common/mainsuite.cpp;h=455bfd98effd0b7dbe6597f712a1bdfa35232308;hb=80e1e382f42512ebf2e10a802f77e50327b8fb73[the test class runner main]
|
||||
https://git.lumiera.org/gitweb?p=LUMIERA;a=blob;f=tests/common/mainsuite.cpp;h=455bfd98effd0b7dbe6597f712a1bdfa35232308;hb=80e1e382f42512ebf2e10a802f77e50327b8fb73[the test class runner main]
|
||||
for an usage example. I really want our Lumiera main to be clean and expressive
|
||||
in the way showed there. Probably the most important part of the startup is
|
||||
pulling up the session core; because of that I think most of the startup
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@ This proposal intends to capture envisioned Architecture of the Application.
|
|||
image:{ldoc}/devel/draw/rendered/Lumi.Architecture-1.png[Overview of Lumiera Architecture]
|
||||
|
||||
^The SVG source of this drawing is
|
||||
https://git.lumiera.org/?p=LUMIERA;a=blob_plain;f=doc/devel/draw/Lumi.Architecture-1.svg;hb=HEAD[maintained in GIT(2008)]·
|
||||
https://git.lumiera.org/?p=LUMIERA;a=blob_plain;f=doc/devel/draw/Lumi.Architecture-2.svg;hb=HEAD[(current: 2022)]^
|
||||
https://git.lumiera.org/gitweb?p=LUMIERA;a=blob_plain;f=doc/devel/draw/Lumi.Architecture-1.svg;hb=HEAD[maintained in GIT(2008)]·
|
||||
https://git.lumiera.org/gitweb?p=LUMIERA;a=blob_plain;f=doc/devel/draw/Lumi.Architecture-2.svg;hb=HEAD[(current: 2022)]^
|
||||
|
||||
|
||||
Description
|
||||
|
|
@ -75,7 +75,7 @@ Conclusion
|
|||
----------
|
||||
Accepted. The drawing was moved into the GIT tree. +
|
||||
It was upgraded and reworked
|
||||
https://git.lumiera.org/?p=LUMIERA;a=blob_plain;f=doc/devel/draw/Lumi.Architecture-2.svg;hb=0b9f2e2c3139cad542013caac593cb5972a2e3f6[in 2022]
|
||||
https://git.lumiera.org/gitweb?p=LUMIERA;a=blob_plain;f=doc/devel/draw/Lumi.Architecture-2.svg;hb=0b9f2e2c3139cad542013caac593cb5972a2e3f6[in 2022]
|
||||
|
||||
|
||||
Back to link:/x/DesignProcess.html[Lumiera Design Process overview]
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ details of carrying out the operations are much more involved.
|
|||
Please see the
|
||||
link/wiki/renderengine.html#Rendering[Development internal doc (TiddlyWiki)]
|
||||
and the
|
||||
https://git.lumiera.org/?p=LUMIERA;a=blob;f=src/proc/engine/procnode.hpp;h=9cf3a2ea8c33091d0ee992ec0fc8f37bb5874d34;hb=f9452f654cf39a4abbbeecfd81bef51aa347b321[Draft: ProcNode (src)]
|
||||
https://git.lumiera.org/gitweb?p=LUMIERA;a=blob;f=src/proc/engine/procnode.hpp;h=9cf3a2ea8c33091d0ee992ec0fc8f37bb5874d34;hb=f9452f654cf39a4abbbeecfd81bef51aa347b321[Draft: ProcNode (src)]
|
||||
for details
|
||||
(and/or contact Ichthyo for in-depth discussion of those technical details)
|
||||
|
||||
|
|
|
|||
|
|
@ -149,7 +149,7 @@ We soon settled upon the following toolchain, which is used up to today:
|
|||
history is consolidated eventually into the
|
||||
link:{rfc}/MasterRepositorySetup.html#historical_remark[»official Lumiera repository«],
|
||||
which is made accessible at
|
||||
https://git.lumiera.org/?p=LUMIERA;a=summary[`git://git.lumiera.org/LUMIERA`]
|
||||
https://git.lumiera.org/gitweb?p=LUMIERA;a=summary[`git://git.lumiera.org/LUMIERA`]
|
||||
- We never used link:{rfc}/GitSubmoduleTransistion.html#_comments[Git submodules]...
|
||||
- https://www.scons.org/[SCons] became the primary buildsystem soon,
|
||||
with Autotools discontinued in 2011
|
||||
|
|
@ -158,14 +158,14 @@ We soon settled upon the following toolchain, which is used up to today:
|
|||
- Website and documentation pages are generated by
|
||||
https://asciidoc-py.github.io/[Asciidoc], with source files for
|
||||
documentation maintained
|
||||
https://git.lumiera.org/?p=LUMIERA;a=blob;f=doc/index.txt;h=9bc0f11e6fb67588388e7616320b695841571278;hb=20f3252892d5b53a05ecd8aeb571122806baba17[in-tree],
|
||||
https://git.lumiera.org/gitweb?p=LUMIERA;a=blob;f=doc/index.txt;h=9bc0f11e6fb67588388e7616320b695841571278;hb=20f3252892d5b53a05ecd8aeb571122806baba17[in-tree],
|
||||
published
|
||||
https://git.lumiera.org/?p=website;a=blob;f=build_website.sh;h=60b90e18d5732d66566baf2c20a217212a5dcece;hb=aefc91e13a95a3c742dc8143c374ec982e0aa56b[automatically on push].
|
||||
https://git.lumiera.org/gitweb?p=website;a=blob;f=build_website.sh;h=60b90e18d5732d66566baf2c20a217212a5dcece;hb=aefc91e13a95a3c742dc8143c374ec982e0aa56b[automatically on push].
|
||||
- A https://classic.tiddlywiki.com/[TiddlyWiki] is used for development notes
|
||||
- for Unit-Tests, we use our own minimalist
|
||||
https://git.lumiera.org/?p=LUMIERA;a=blob;f=src/lib/test/run.hpp;h=035633af9bde7a8053bcd304baffc21693dcf031;hb=20f3252892d5b53a05ecd8aeb571122806baba17[Test-runner].
|
||||
https://git.lumiera.org/gitweb?p=LUMIERA;a=blob;f=src/lib/test/run.hpp;h=035633af9bde7a8053bcd304baffc21693dcf031;hb=20f3252892d5b53a05ecd8aeb571122806baba17[Test-runner].
|
||||
and a
|
||||
https://git.lumiera.org/?p=LUMIERA;a=blob;f=tests/test.sh;h=8c13810eb475bbacd97c528607e6750f47bb145e;hb=20f3252892d5b53a05ecd8aeb571122806baba17[shell script]
|
||||
https://git.lumiera.org/gitweb?p=LUMIERA;a=blob;f=tests/test.sh;h=8c13810eb475bbacd97c528607e6750f47bb145e;hb=20f3252892d5b53a05ecd8aeb571122806baba17[shell script]
|
||||
to support definition of test collections.
|
||||
- A Debian package is maintained with
|
||||
https://wiki.debian.org/PackagingWithGit[git-buildpackage] and published
|
||||
|
|
|
|||
|
|
@ -68,9 +68,9 @@ rather -- a task no one was willing to take on.
|
|||
|
||||
Yet the Lumiera project succeeded in building a web presence based on
|
||||
https://asciidoc-py.github.io/[Asciidoc] sources
|
||||
https://git.lumiera.org/?p=website;a=summary[checked into Git],
|
||||
https://git.lumiera.org/gitweb?p=website;a=summary[checked into Git],
|
||||
with a lightweight
|
||||
https://git.lumiera.org/?p=website;a=blob;f=build_website.sh;h=60b90e18d5732d66566baf2c20a217212a5dcece;hb=aefc91e13a95a3c742dc8143c374ec982e0aa56b[commit-hook script]
|
||||
https://git.lumiera.org/gitweb?p=website;a=blob;f=build_website.sh;h=60b90e18d5732d66566baf2c20a217212a5dcece;hb=aefc91e13a95a3c742dc8143c374ec982e0aa56b[commit-hook script]
|
||||
to automatically publish any changes on git-push -- a scheme
|
||||
which comes close to the gist of this RfC.
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ This describes how the shared MASTER is set up and syncronized.
|
|||
Description
|
||||
~~~~~~~~~~~
|
||||
We have now a shared master repository in
|
||||
https://git.lumiera.org/?p=LUMIERA;a=summary[/git/LUMIERA].
|
||||
https://git.lumiera.org/gitweb?p=LUMIERA;a=summary[/git/LUMIERA].
|
||||
|
||||
- The public/anonymous git url is [yellow-background]#`git://git.lumiera.org/LUMIERA`#
|
||||
- for people with ssh access it is pushable at [purple]#`UID@git.lumiera.org:/git/LUMIERA`#.
|
||||
|
|
@ -71,10 +71,10 @@ That being said -- the shared master at [yellow-background]#`git://git.lumiera.o
|
|||
still contains the _official_ state of the Lumiera codebase, while
|
||||
actual development is published first in a repository per developer
|
||||
(so effectively in
|
||||
https://git.lumiera.org/?p=lumiera/ichthyo;a=summary[the Ichthyo repository]
|
||||
https://git.lumiera.org/gitweb?p=lumiera/ichthyo;a=summary[the Ichthyo repository]
|
||||
for the time being);
|
||||
development is consolidated intermittently into the
|
||||
https://git.lumiera.org/?p=LUMIERA;a=summary[LUMIERA Git].
|
||||
https://git.lumiera.org/gitweb?p=LUMIERA;a=summary[LUMIERA Git].
|
||||
|
||||
|
||||
''''
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ Propose a documentation/planning about the setup.
|
|||
Description
|
||||
~~~~~~~~~~~
|
||||
Take a look at my draft at:
|
||||
https://git.lumiera.org/?p=LUMIERA;a=blob;f=doc/devel/nobug_flags.txt;h=325141acb52a7be7f75425359b6057c74ed30253;hb=a03e3c5e73a4c0be2264ac4e4e0f02eb8749837b[nobug_flags.txt]
|
||||
https://git.lumiera.org/gitweb?p=LUMIERA;a=blob;f=doc/devel/nobug_flags.txt;h=325141acb52a7be7f75425359b6057c74ed30253;hb=a03e3c5e73a4c0be2264ac4e4e0f02eb8749837b[nobug_flags.txt]
|
||||
|
||||
I've added the things I planning for the backend, others might add their own
|
||||
plans there too. So far this is an early draft, comments welcome.
|
||||
|
|
|
|||
|
|
@ -210,7 +210,7 @@ during this effort leads to the following refinements:
|
|||
- Workers manage their capacity through a statistical distribution of _sleep times._
|
||||
|
||||
In this form, the proposal can be considered as implemented. +
|
||||
^(see -> https://git.lumiera.org/?p=LUMIERA;a=commit;h=d71eb37[d71eb37]
|
||||
^(see -> https://git.lumiera.org/gitweb?p=LUMIERA;a=commit;h=d71eb37[d71eb37]
|
||||
and https://issues.lumiera.org/ticket/1344[#1344])^
|
||||
|
||||
Ichthyostega:: '2024-04-20 01:55 CEST'
|
||||
|
|
|
|||
|
|
@ -49,6 +49,4 @@ On occasion, we'll replace them by drawings from our current UML model.
|
|||
128517: classdiagram 142725 "Time flavours"
|
||||
133637: classdiagram 143877 "Player Entities"
|
||||
133637: objectdiagram 144005 "Play Process Structure"
|
||||
133637: sequencediagram 145157 "output data exchange"
|
||||
133637: classdiagram 151685 "Player Output"
|
||||
128773: classdiagram 158213 "UIBus"
|
||||
|
|
|
|||
9180
doc/devel/uml/OutputSlot_components.svg
Normal file
9180
doc/devel/uml/OutputSlot_components.svg
Normal file
File diff suppressed because it is too large
Load diff
|
After Width: | Height: | Size: 256 KiB |
1678
doc/devel/uml/OutputSlot_sequence.svg
Normal file
1678
doc/devel/uml/OutputSlot_sequence.svg
Normal file
File diff suppressed because it is too large
Load diff
|
After Width: | Height: | Size: 83 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 13 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 25 KiB |
|
|
@ -1,7 +1,7 @@
|
|||
Documentation
|
||||
=============
|
||||
|
||||
This documentation section contains documentation for both users and developers.
|
||||
This documentation section contains documentation for users and developers alike.
|
||||
|
||||
== User ==
|
||||
|
||||
|
|
@ -60,8 +60,8 @@ deeper documentation dungeons.
|
|||
|
||||
At the second layer, the reader must make the first major decision on which
|
||||
router to pursue: documentation with a heavy code based leaning; or more descriptive
|
||||
documentation on the design of Lumiera which avoids a code narrative. This
|
||||
section has two main sections:
|
||||
documentation on the design of Lumiera, which omits most technical details of the implementation.
|
||||
Consequently, the documentation is divided into two main parts:
|
||||
|
||||
* link:design/index.html[Lumiera Developer Design Documents]
|
||||
* link:technical/index.html[Lumiera Developer Technical Documents]
|
||||
|
|
@ -75,14 +75,14 @@ section has two main sections:
|
|||
|
||||
[width="90%",frame="topbot",options="header",cols="^,2<"]
|
||||
|================================================================================================
|
||||
|Sub-System|Remarks
|
||||
|Section|Remarks
|
||||
|link:design/architecture/[Architecture]|Structure of Lumiera
|
||||
|link:design/gui/[GUI]|The Lumiera ⟷ user interaction layer
|
||||
|link:design/model/[Model]|Low-level and high-level models
|
||||
|link:design/engine/[Engine]|Renderer, output generation, ...
|
||||
|link:design/lowlevel/[Low-level]|Low-level and system services
|
||||
|link:design/application/[Application]|Application framework
|
||||
|link:design/plugins/[Plugins]|Dynamically loaded Extensions
|
||||
|link:design/extension/[Extension]|Dynamically loaded Extensions
|
||||
|link:design/workflow/[Workflow]|Workflow and Interaction Design
|
||||
|link:design/governance/[Governance]|Meta project concerns
|
||||
|================================================================================================
|
||||
|
|
@ -93,12 +93,12 @@ section has two main sections:
|
|||
|
||||
[width="90%",frame="topbot",options="header",cols="^,2<"]
|
||||
|================================================================================================
|
||||
|Sub-System|Remarks
|
||||
|Section|Remarks
|
||||
|link:technical/build/[Build System]|Build, Packaging and CI
|
||||
|link:technical/stage/[Stage]|GUI (technical aspects)
|
||||
|link:technical/steam/[Steam]|Session and processing coordination
|
||||
|link:technical/vault/[Vault]|Low-level operations
|
||||
|link:technical/library/[Support lib]|Interface and support libraries
|
||||
|link:technical/library/[Library]|Utilities and custom frameworks
|
||||
|link:technical/code/[Code base]|Code management, organisation, ...
|
||||
|link:technical/infra/[Infrastructure]|Website and developer tooling ...
|
||||
|link:technical/howto/[Developer HowTos]|Instructions and guides for developers
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ Lumiera expects a ``standard'' desktop installation running a XServer.footnote:[
|
|||
Wayland is ``just around the corner'' since 10 years now. Well, recently it
|
||||
seems it is _really_ just around the corner. We are aware of the necessary
|
||||
changes, which are not very complicated to implement (since we are using
|
||||
GTK-3). However, as long as Wayland simply crashes or hands in our
|
||||
GTK-3). However, as long as Wayland simply crashes or hangs in our
|
||||
everyday work setup with XFCE, we will not consider this topic
|
||||
a priority. In the meantime, please report any problems
|
||||
you encounter with Lumiera running on XWayland.
|
||||
|
|
@ -97,7 +97,8 @@ Languages and Tools
|
|||
|
||||
* BOOST (listed below are the Debian package names)
|
||||
- libboost-dev (at least *1.83*)
|
||||
- libboost-program-options-dev
|
||||
- notably including `boost::lockfree`
|
||||
- libboost-program-options-dev (link dependency)
|
||||
|
||||
* Script languages
|
||||
- Python (*3.10*) required for building (->SCons)
|
||||
|
|
@ -123,7 +124,7 @@ Build Tools
|
|||
* pkg-config
|
||||
* Doxygen (optional)
|
||||
* Asciidoc (optional)
|
||||
* Git -> https://git.lumiera.org/?p=LUMIERA;a=summary[git://git.lumiera.org/LUMIERA]
|
||||
* Git -> https://git.lumiera.org/gitweb?p=LUMIERA;a=summary[git://git.lumiera.org/LUMIERA]
|
||||
footnote:[
|
||||
_Ichthyo_ maintains a repo at https://github.com/Ichthyostega/Lumiera/[Github ^~Ichthyostega/Lumiera^]
|
||||
]
|
||||
|
|
@ -160,9 +161,9 @@ Libraries
|
|||
which is possible, but would require some considerable effort. As of 2025, GDL is still usable,
|
||||
but no longer available through the standard distribution channels. +
|
||||
We provide a Git repository at our Website for
|
||||
https://git.lumiera.org/?p=debian/gdl;a=summary[libGDL (3.40) +git://git.lumiera.org/debian/gdl+]
|
||||
https://git.lumiera.org/gitweb?p=debian/gdl;a=summary[libGDL (3.40) +git://git.lumiera.org/debian/gdl+]
|
||||
and the
|
||||
https://git.lumiera.org/?p=debian/gdlmm;a=summary[GDL C++ bindings (3.7.3) +git://git.lumiera.org/debian/gdlmm+]
|
||||
https://git.lumiera.org/gitweb?p=debian/gdlmm;a=summary[GDL C++ bindings (3.7.3) +git://git.lumiera.org/debian/gdlmm+]
|
||||
and we maintain custom Debian packages for both in our
|
||||
link:/debian/[Debian DEB depot].
|
||||
]
|
||||
|
|
|
|||
|
|
@ -79,8 +79,8 @@ How the Lumiera Debian package works
|
|||
Since we maintain both the Debian package and our build system, we can always try to do
|
||||
_the heavy lifting_ in our link:{ldoc}/technical/build/SCons.html[SCons build system],
|
||||
as this helps to keep the Debian packaging as simple as possible. Please have a look at the
|
||||
-> https://git.lumiera.org/?p=debian/lumiera;a=blob;hb=refs/heads/deb;f=debian/control[control file]
|
||||
· https://git.lumiera.org/?p=debian/lumiera;a=blob;hb=refs/heads/deb;f=debian/rules[debian/rules]
|
||||
-> https://git.lumiera.org/gitweb?p=debian/lumiera;a=blob;hb=refs/heads/deb;f=debian/control[control file]
|
||||
· https://git.lumiera.org/gitweb?p=debian/lumiera;a=blob;hb=refs/heads/deb;f=debian/rules[debian/rules]
|
||||
|
||||
- we rely on the _Debhelper Sequencer_ "`dh_*`" in our 'debian/rules' +
|
||||
-> https://manpages.debian.org/trixie/debhelper/dh.1.en.html[Manpage]
|
||||
|
|
@ -106,7 +106,7 @@ into `master` and marked with a tag there.
|
|||
For the actual packaging, we use these release tags as _upstream tag_ and perform a merge
|
||||
from that tag into the **debian branch**.footnote:[
|
||||
This _debian branch_ is called `deb` by convention. As mentioned above, it is published through
|
||||
a separate https://git.lumiera.org/?p=debian/lumiera;a=summary[Git repository `git://git.lumiera.org/debian/lumiera`],
|
||||
a separate https://git.lumiera.org/gitweb?p=debian/lumiera;a=summary[Git repository `git://git.lumiera.org/debian/lumiera`],
|
||||
which is kept separate, since packaging is considered a ``downstream process''.
|
||||
]
|
||||
This branch carries only the additions and tweaks done for debian packaging. Yet still, this `deb`
|
||||
|
|
@ -115,7 +115,7 @@ repository as the `master` branch. When the packaging process is complete, the p
|
|||
DEB version is marked with a tag `debian/<full_debian_version_spec>`
|
||||
|
||||
Note however that there is another branch in the aforementioned `debian/lumiera` repository,
|
||||
which is called https://git.lumiera.org/?p=debian/lumiera;a=shortlog;h=refs/heads/depot[depot]
|
||||
which is called https://git.lumiera.org/gitweb?p=debian/lumiera;a=shortlog;h=refs/heads/depot[depot]
|
||||
and is related to our link:/debian[DEB package depot] on Lumiera.org. This branch carries a completely
|
||||
different directory structure, since it is used with the
|
||||
https://wiki.debian.org/DebianRepository/SetupWithReprepro[»reprepro« tool from Debian]
|
||||
|
|
|
|||
|
|
@ -352,6 +352,6 @@ shared object (dynamic library), which is then again a SCons target
|
|||
and can be passed to other executable builders as library dependency
|
||||
for compilation and linking. Look into 'src/SConscript' or 'test/SConscript'
|
||||
to see examples of that technique -- which we also use to define that
|
||||
global compound target variables like `core`, `app_lib`, `core_lib`,
|
||||
global compound target variables like `core`, `vessel_lib`, `core_lib`,
|
||||
`vault_lib`, and `support_lib`. These in turn are essential for
|
||||
building the layered dependency hierarchy in our code.
|
||||
|
|
|
|||
|
|
@ -322,8 +322,8 @@ actively step by step
|
|||
modules of the application will be placed by default
|
||||
|
||||
. after the core application has been loaded and all direct dependencies are resolved,
|
||||
but still before entering `main()`, the class `lumiera::AppState` will be initialised,
|
||||
which in turn holds a member of type `lumiera::BasicSetup`. The latter will figure out
|
||||
but still before entering `main()`, the class `vessel::Voyage` will be initialised,
|
||||
which in turn holds a member of type `vessel::BasicSetup`. The latter will figure out
|
||||
the location of the executable footnote:[this is a Linux-only trick, using `/proc/self/exe`]
|
||||
and require a 'setup.ini' file in the same directory. This setup file is mandatory.
|
||||
|
||||
|
|
|
|||
|
|
@ -88,6 +88,33 @@ test with random numbers::
|
|||
several threads; each thread should use its own generator then (yet this can be handled sloppy if
|
||||
the _quality_ of the random number actually does not matter!)
|
||||
|
||||
special test setup::
|
||||
It can be very helpful to use _policy based design_ for some complex evaluation or algorithm,
|
||||
even in cases where it is clear that the actual usage will only ever involve one specific data type
|
||||
and configuration. Not only does such a structured approach help with clarifying the design itself,
|
||||
since the core of the evaluation logic can be abstracted from data representation -- yet also the
|
||||
testing and verification of correctness is simplified, as the test can be written to use simple,
|
||||
human-readable data (strings, symbols), and, furthermore, many kinds of instrumentation can be
|
||||
included into the test data to gain detailed insight what is happening in the test subject.
|
||||
|
||||
verify behaviour sequence::
|
||||
Use the `lib::EventLog`:
|
||||
+
|
||||
- within the test, notable points are marked into the log, possibly with arguments
|
||||
- after the test, the Event Log can be verified with a search-sequence
|
||||
- this way it is possible to ensure that some events did happen, and happened
|
||||
in a very specific order, or that some event did _not_ happen after another event
|
||||
- the `test::Tracker` defined in 'src/test/tracking-dummy.hpp' will mark all constructor,
|
||||
destructor and copy events into an embedded Event Log instance. By inheriting or mix-in
|
||||
from that type, the allocation and instance behaviour of some service can be scrutinised.
|
||||
|
||||
verify incidences and concurrency::
|
||||
Aside from using some generic tracing utility, sometimes it is much easier to setup
|
||||
a _specifically tailored instrumentation_. This can be achieved with 'incidence-count.hpp'
|
||||
+
|
||||
- for instrumentation, the `markEnter()` and `markLeave()` functions need to be invoked
|
||||
- after the test run, various statistics can be computed and evaluated, including
|
||||
cumulated time and concurrency for the "cases" marked with enter / leave.
|
||||
|
||||
|
||||
Common Tasks
|
||||
|
|
@ -109,6 +136,12 @@ structured data::
|
|||
- also useful as output format, both for debugging and because it can be matched against
|
||||
an expected structure, which is generated with the DSL notation.
|
||||
- [yellow-background]#(planned)# will be able to map this from/to JSON easily.
|
||||
scoped collection::
|
||||
unique-ownership collection that holds a sequence of objects in a preallocated fixed
|
||||
storage block. Notably even non-copyable objects can be managed (which is not possible
|
||||
with a `std::vector`), and type-erased instances of subclasses can be mixed. Furthermore
|
||||
there is a constructor variant that allows to populate a `ScopedCollection` in RAII-style.
|
||||
This can e.g. be used to fire-off a set of Threads or open a set of wrapped connections.
|
||||
|
||||
Iterating
|
||||
~~~~~~~~~
|
||||
|
|
@ -134,6 +167,26 @@ IterSource::
|
|||
even the production-state can be hooked up into the IterSource instance (with a smart-ptr).
|
||||
This allows e.g. to send a Diff to the UI through the UI-Bus, while the actual generation
|
||||
and application both happen demand-driven or _lazy..._
|
||||
State Core::
|
||||
When some algorithm involves a statefull evaluation, it might be helpful to package that into
|
||||
a _State Core_ container. This is a class that is expected to expose three operations, which
|
||||
allow to build an iteration on top. The header 'iter-adapter.hpp' provides several adapters
|
||||
and wrappers, notable `lib::IterStateWrapper` wraps any _state core_ and provides a
|
||||
Lumiera Iterator interface; likewise, `IterExplorer` heavily relies on that concept and
|
||||
is able to accept and iterate any state core in a pipeline.
|
||||
special iterators::
|
||||
Some additional adaptors are provided as building block so that specific evaluations
|
||||
can be made to conform with the »Lumiera Iterator« concept
|
||||
+
|
||||
- several given iterators can be combined into a iteration over tuples with 'lib/iter-zip.hpp'
|
||||
- there is also a variation 'lib::izip' that adds a running index into the _zipping operation_
|
||||
- IterExplorer provides a `flatten()` operation, that can be used to join an iterator-of-iterators
|
||||
into a single seamless iteration lazily.
|
||||
- when a STL container also supports _backwards iteration_, then a lumiera iterator can be
|
||||
created that can _switch direction_ dynamically, with the help of 'iter-cursor.hpp'
|
||||
- a STL »deque« container can be used both as a stack and a queue; the header 'iter-stack.hpp'
|
||||
provides an iteration adapter for both usage schemes, so that either the stack or the queue
|
||||
can be _"discharged"_ as a Lumiera Iterator (possibly with an IterExplorer pipeline)
|
||||
|
||||
|
||||
Formatting
|
||||
|
|
@ -155,6 +208,25 @@ Formatting
|
|||
because there are way more capable solutions _out there_ ☺)
|
||||
|
||||
|
||||
Setup and wiring
|
||||
~~~~~~~~~~~~~~~~
|
||||
Generally speaking, we prefer _constructor wiring_ whenever possible, to avoid creating
|
||||
unnecessary statefulness (as would be the case when connecting dependencies through setters).
|
||||
This design also favours decomposition into self-contained components with a single purpose,
|
||||
and simplifies error handling with the help of the RAII pattern.
|
||||
|
||||
However, sometimes a piece of code depends on some remote and even global service; in these
|
||||
cases, the `lib::Depend` framework can be used to access that service via type name. The
|
||||
actual service can either be created on-demand as a singleton, or it can be installed
|
||||
explicitly as a service instance. Furthermore, it is possible to "push aside" the
|
||||
current service instance temporarily and replace it by a _test mock_.
|
||||
|
||||
One point to note is that `lib::Depend` is a _monostate_, and not a _singleton_.
|
||||
The actual service should be written as a free-standing class, that can be instantiated
|
||||
directly for the purpose of testing.
|
||||
|
||||
|
||||
|
||||
Language constructs
|
||||
-------------------
|
||||
Templates
|
||||
|
|
@ -235,4 +307,14 @@ unpack iterator into tuple::
|
|||
- see tuple-record-init-test.cpp
|
||||
- used in 'command-simple-closure.hpp' to receive parameter values sent via UI-Bus and package them
|
||||
into a tuple for invocation of a Steam-Layer command.
|
||||
+
|
||||
index and iterate over tuple elements::
|
||||
This is another example of bridging the compile-time / runtime barrier, using a trampoline.
|
||||
Obviously such a solution requires that it is somehow possible to define a generic functor / λ that
|
||||
can accept any of the tuple elements. And, in order to build a runtime index access, we additionally
|
||||
need to convert each of the tuple elements into a _common reconciled type_.
|
||||
+
|
||||
- If such a type exists, can be figured out with the help of the `meta::CommonReturn` _metafunction_
|
||||
- `lib::meta::TupleIdxAdaptor` provides the necessary machinery to define a subscript `operator[]`
|
||||
- see `TupleIdxAdaptor_test` for some usage examples, including a runtime iteration...
|
||||
|
||||
|
|
|
|||
|
|
@ -9,34 +9,34 @@ Technical Documentation
|
|||
// Menu : prepend child overview
|
||||
|
||||
|
||||
This documentation section contains technical documentation about Lumiera. To
|
||||
get an *overview* of all the internals and components, you might want to read
|
||||
This documentation section contains technical documentation about Lumiera. +
|
||||
To get an *overview* of all the internals and components, you might want to read
|
||||
link:overview.html[Lumiera the Inner Core]
|
||||
|
||||
== Three Layers
|
||||
|
||||
The technical documentation is split in three parts, one for each of the three
|
||||
main layers of Lumiera. You may want to read the
|
||||
link:{ldoc}/design/index.html[Design Documents] first to get an overview of all the
|
||||
components.
|
||||
The technical documentation is divided into three main parts and some supplements,
|
||||
corresponding to the code organisation in three layers. The information here can be
|
||||
very detailed and technical at times, and the topics are organised by individual components.
|
||||
|
||||
* link:stage/index.html[*Stage Layer*] : Documents related to the GTK based Lumiera GUI
|
||||
|
||||
* link:steam/index.html[*Steam Layer*] : Documents regarding Session, models and the engine structure
|
||||
|
||||
* link:steam/index.html[*Steam Layer*] : Documents regarding Session, the models and the engine
|
||||
* link:vault/index.html[*Vault Layer*] : Documents about the data and processing backend
|
||||
|
||||
To better understand the underlying concepts, it might be helpful to also consult the
|
||||
link:{ldoc}/design/index.html[Design Documents].
|
||||
|
||||
|
||||
== Tools
|
||||
|
||||
.Development
|
||||
* link:/doxy/[*Doxygen* generated documentation] : API documentation of the Lumiera code.
|
||||
* link:/doxy/[*Doxygen*] : API documentation of the Lumiera code.
|
||||
* organisation of the link:code/index.html[Code Base] in general
|
||||
* link:/x/CodingGuidelines.html[Coding Style] and further guidelines
|
||||
|
||||
.Building
|
||||
* link:build/index.html[Buildsystem] : Installation & compilation tools, dependencies and packaging.
|
||||
* link:build/index.html[Buildsystem] : Installation and compilation tools, dependencies and packaging.
|
||||
|
||||
.HOWTO
|
||||
* link:/x/DevHowto.html[Developer *HOWTOs*] : Collection of instructions, recipes, hints and similar
|
||||
materials intended for developers
|
||||
* link:/x/DevHowto.html[Developer *HOWTOs*] : Collection of instructions, recipes, hints, misdeeds
|
||||
and confessions and similar helpful resources for developers
|
||||
|
|
|
|||
|
|
@ -8,30 +8,32 @@ NOTE: This page holds content copied from the old "main" TiddlyWiki +
|
|||
It needs to be reworked, formatted and generally brought into line
|
||||
|
||||
********************************************
|
||||
.Lumiera relies on *test-driven development*
|
||||
.Lumiera employs test-driven development
|
||||
This page discusses the overall organisation
|
||||
of test code, and the tools used for
|
||||
running test cases
|
||||
********************************************
|
||||
|
||||
Tests are the only form of documentation, known to provides some resilience against
|
||||
becoming outdated. Tests help to focus on the usage, instead of engaging in spurious
|
||||
Tests are the only known form of documentation that offers some resilience against
|
||||
getting outdated. Tests help to focus on the usage, instead of engaging in spurious
|
||||
implementation details. Developers are highly encouraged to write the tests _before_
|
||||
the actual implementation, or at least alongside and interleaved with expanding the
|
||||
feature set of the actual code.
|
||||
|
||||
There may be exceptions to this rule. Not every single bit needs to be covered by tests.
|
||||
Some features are highly cross-cutting and exceptionally difficult to cover with tests.
|
||||
And sometimes, just an abstract specification is a better choice.
|
||||
Test code must not duplicate the logic of implementation code, it should represent an
|
||||
abstracted, outside, logical view of the matter at hand. And sometimes it is the better
|
||||
choice to simply write an abstract specification in understandable language.
|
||||
|
||||
As a rule of thumb, consider to write test code which is easy to read and understand,
|
||||
_like a narration_ to show off the relevant properties of the test subject.
|
||||
|
||||
|
||||
Test Structure
|
||||
--------------
|
||||
- a *test case* is an executable, expected to run without failure, and optionally producing
|
||||
some verifiable output
|
||||
- simple test cases may be written as stand-alone application, while the more tightly
|
||||
integrated test cases can be written as classes within the Lumiera application framework.
|
||||
- a *test case* is an executable, expected to run without failure; test code not only invokes
|
||||
a _subject under test_ -- the important part is to _verify_ its behaviour.
|
||||
- test cases should use the +CHECK+ macro of NoBug to verify test results, since the normal
|
||||
assertions may be de-configured for optimised release builds.
|
||||
- our test runner script 'test.sh' provides mechanisms to check for expected output
|
||||
|
|
@ -41,15 +43,13 @@ several functions within the same test fixture. This allows to create a ``narrat
|
|||
in the code: first do this, than do that, and now that, and now this should happen...
|
||||
Generally speaking, it is up to the individual test to take care or isolate himself
|
||||
from any _dependencies_. Test code and application code uses the same mechanisms
|
||||
for accessing other components within the application. Up to now (2014), there
|
||||
was no need for any kind of _dependency injection_, nor did we face any
|
||||
difficulties with tainted state.
|
||||
for accessing other components within the application.
|
||||
|
||||
Test classes are organised into a tree closely mirroring the main application source
|
||||
code tree. Large sections of this test tree are linked together into *test libraries*.
|
||||
Some of these are linked against a specific (sub)scope of the application, like e.g.
|
||||
only against the support library, the application framework or the vault. Since we
|
||||
use _strict dependencies_, this linking step will spot code not being placed at the
|
||||
use _strict dependencies_, this linking step will also spot code not placed at the
|
||||
correct scope within the whole system. As a final step, the build system creates a
|
||||
*test runner* application (`target/test-suite`), which links dynamically against _all_
|
||||
the test libraries and thus against all application dependencies.
|
||||
|
|
@ -81,9 +81,9 @@ to help with automating the build and test execution, test code should adhere to
|
|||
- test classes should be placed into a sub namespace +...::test+
|
||||
- specific definitions for a single test case may rely on global variables,
|
||||
but these should live within an anonymous namespace
|
||||
- all test executables should be named according to the pattern +test-*+
|
||||
- free standing test executables should be named according to the pattern `test-*`
|
||||
- all test source code is within the 'tests' subtree
|
||||
- immediately within the 'tests/' directory are the test collection definitions +*.tests+,
|
||||
- immediately within the 'tests/' directory are the test collection definitions `*.tests`,
|
||||
ordered by number prefixes
|
||||
- below are the directories with test cases, to be grouped into the aforementioned test-executables.
|
||||
- we distinguish two ways to write and link tests: _standalone_ and _suite_
|
||||
|
|
@ -107,7 +107,7 @@ See the following
|
|||
.hello-world-test example
|
||||
[source,C]
|
||||
----------------------------------------------------------------
|
||||
#include "lib/test/run.hpp"
|
||||
#include "test/run.hpp"
|
||||
#include <iostream>
|
||||
|
||||
using std::cout;
|
||||
|
|
|
|||
|
|
@ -18,22 +18,25 @@ The main Website.
|
|||
|
||||
- Data is in '/var/www'. The Website is a git repository.
|
||||
- Configuration is at '/etc/apache2/sites-available/lumiera.conf'
|
||||
- cehteh cares for the administration.
|
||||
- cehteh and ichthyo are responsible for the administration.
|
||||
- To proprose some changes clone the git repository and send a
|
||||
notfication/merge-request to the mailinglist.
|
||||
|
||||
Some directories symlink to other directories (see below).
|
||||
|
||||
|
||||
https://staging.lumiera.org
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
The »staging site«
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Testing repository for the Website. Web development is done and
|
||||
reviewed here before it gets pushed to the main Website.
|
||||
Testing repository for the Website. It is configured in Apache
|
||||
to be served from a separate virtual host, with an obvious DNS
|
||||
name (which _must not_ be mentioned in published content!).
|
||||
Web development is done and reviewed here before it gets
|
||||
pushed to the main Website.
|
||||
|
||||
- Data is in '/var/www-staging'. The Website is a git repository.
|
||||
- Configuration is at '/etc/apache2/sites-available/staging.conf'
|
||||
- cehteh cares for the administration.
|
||||
- cehteh and ichthyo are responsible for the administration.
|
||||
- To proprose some changes clone the git repository and send a
|
||||
notfication/merge-request to the mailinglist.
|
||||
|
||||
|
|
@ -108,5 +111,113 @@ Upload directory where people can rsync data.
|
|||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Clone of the lumiera source repository to serve the documentation within.
|
||||
|
||||
/var/local/trac/
|
||||
~~~~~~~~~~~~~~~~
|
||||
This is the configuration root of our https://trac.edgewall.org/[Trac] installation
|
||||
for the Lumiera issue tracker. This is actually a Git repository, since our setup
|
||||
became gradually more elaborate over time. We even have some local tweaks to
|
||||
the CSS styles and the Subtickets-Plugin (Python code).
|
||||
|
||||
- a copy of the Apache configuration is checked in as well
|
||||
- note some scripts in the 'scripts' subdirectory
|
||||
- an export of current Trac resources was dumped into 'static'
|
||||
and is served directly by Apache from there
|
||||
- the actual »Trac-environment« with config and SQLite-DB is in 'trac-env'
|
||||
- all source trees of all essential plug-ins are checked into 'PluginBuild',
|
||||
so that any plugin could be regenerated by `python3 setup.py bdist_egg`
|
||||
- a dump of the TracDB is also checked into that Git
|
||||
|
||||
|
||||
|
||||
Setup and Configuration
|
||||
-----------------------
|
||||
|
||||
- Apache is configured with the `worker'-MPM
|
||||
- Trac runs under WSGI configured with a pool of _multithread daemon processes_
|
||||
|
||||
Load and Calibration
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
As of 1/2026, we employ a simplistic load monitoring based on evaluating the
|
||||
running processes and some server statistics. This is implemented as a bash script,
|
||||
checked in as '/var/local/trac/scripts/collect-server-stats' and configured to run
|
||||
every two minutes (via SystemD timer). The resulting data trail can be found in
|
||||
'/var/log/apache2/loadwatch.csv', with the following columns:
|
||||
|
||||
- Timestamp: ISO UTC min
|
||||
- Sum webserver...
|
||||
* Processes
|
||||
* Threads
|
||||
* overall resident memory (KiB)
|
||||
* factor of overall memory resident
|
||||
- Apache
|
||||
* workload factor
|
||||
* Processes
|
||||
* Threads
|
||||
* resident memory
|
||||
- WSGI-Trac
|
||||
* workload factor
|
||||
* Processes
|
||||
* Threads
|
||||
* resident memory
|
||||
- Server-statistics
|
||||
* ∅ requests per second
|
||||
* ∅ request duration in ms
|
||||
* currently active slots
|
||||
* currently idle workers
|
||||
* scoreboard of slot allocation
|
||||
|
||||
The parameters of the Apache MPM and the WSGI daemon are then tuned such
|
||||
as to make good use of the available memory, while avoiding excess idle workers.
|
||||
|
||||
Anubis
|
||||
~~~~~~
|
||||
https://anubis.techaro.lol/[Anubis] is a service that was discussed controversially
|
||||
in the community this year. It was created by a
|
||||
https://github.com/Xe[Canadian Open-Source programmer] as an immediate stopgap
|
||||
to help protect the _»small internet«_ from the endless storm of requests that
|
||||
flood in from AI companies. It is configured as a gateway proxy, which classifies
|
||||
incoming requests based on request properties and sends suspicious clients first
|
||||
to a JavaScript proof-of-work challenge. After passing Anubis, the client gets
|
||||
a Cookie to prevent further challenges for some limited time.
|
||||
|
||||
Anubis was reported to be very effective for keeping aggressive scraper bots away,
|
||||
yet the bots have adapted already to some degree. So this remains a game of
|
||||
cat and mouse. Our first evaluations however indicate very promising numbers,
|
||||
by keeping out 92% of PageImpressions and UniqueUsers, thereby reducing
|
||||
the traffic deliverey by the Lumiera issue tracker by more than 80%.
|
||||
|
||||
Introducing such an entrance gateway may be seen as an extreme measure;
|
||||
the rationale is that we have reached such a level of threat that generic rate limits
|
||||
would have to be so severe that they practically render those resources useless.
|
||||
Without these protective measures, we would no longer be able to provide Lumiera.org
|
||||
as a free resource to everyone.
|
||||
|
||||
[underline]#Legal stance#: I consider this usage covered by the provisions of GDPR.
|
||||
Setting this cookie is justified by our legitimate interest of keeping the service operational;
|
||||
we evaluate IP data patterns for the sole purpose of detecting abuse of our website,
|
||||
and we state that fact clearly on our GDPR page.
|
||||
|
||||
Setup
|
||||
^^^^^
|
||||
Anubis runs as single-instance SystemD service unit `anubis@lumiera`, with transient
|
||||
storage backend only. It is integrated into the Website as a _gateway_ (reverse proxy).
|
||||
Anubis is configured by a set of rules in https://cel.dev/[Common Excpression Language].
|
||||
The active rule set is configured in '/etc/anubis' and also checked into the
|
||||
xref:_var_local_trac[Trac-Git]. Rules are evaluated according to the _commited-choice_
|
||||
pattern, i.e. the first rule where the guard condition matches will terminate the
|
||||
evaluation and yield a decision. Anubis was installed from a custom DEB package
|
||||
and ships with
|
||||
https://github.com/TecharoHQ/anubis/tree/main/data[a set of rule building blocks],
|
||||
that can be imported from the `(data)` prefix -- which translates into the path
|
||||
'/usr/share/doc/anubis/data'
|
||||
|
||||
TIP: https://anubis.techaro.lol/[Homepage]
|
||||
· https://anubis.techaro.lol/docs/admin/native-install[Installation]
|
||||
· https://anubis.techaro.lol/docs/admin/environments/apache[Apache-setup]
|
||||
· https://github.com/TecharoHQ/anubis[Github]
|
||||
|
||||
As of 1/2026, Anubis protects our
|
||||
https://issues.lumiera.org/timeline[Issue tracker],
|
||||
the https://git.lumiera.org/[Git repository browser] and the
|
||||
https://lumiera.org/doxy/[API-doc] -- which together constitute
|
||||
the majority of pages accessible on the website.
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ Configuration
|
|||
~~~~~~~~~~~~~
|
||||
All the current configuration and the state of the Debian depot index files is tracked
|
||||
in the aforementioned ``debian/lumiera'' Git repository, on a separate branch called
|
||||
https://git.lumiera.org/?p=debian/lumiera;a=shortlog;h=refs/heads/depot[`depot']
|
||||
https://git.lumiera.org/gitweb?p=debian/lumiera;a=shortlog;h=refs/heads/depot[`depot']
|
||||
|
||||
TIP: this is a slightly unconventional setup, insofar the `depot' branch carries
|
||||
a directory tree that is completely separate from the main Lumiera source tree, which
|
||||
|
|
|
|||
|
|
@ -1,41 +1,70 @@
|
|||
Library documentation
|
||||
=====================
|
||||
|
||||
//Menu: label Support Lib
|
||||
|
||||
Alongside with the core Lumiera Application, we're also creating several *Libraries*. +
|
||||
This section holds pages with in-depth technical documentation regarding those. Please don't
|
||||
expect a coherent or complete coverage for the time being -- library functionality is typically
|
||||
``discovered'' while in the middle of the implementation, then factored out, reused and remoulded
|
||||
over time.
|
||||
Alongside with the core Lumiera Application, and as part of ongoing development work,
|
||||
a lot of *Library code* is written. Many of this happens on occasion. Library solutions
|
||||
are frequently ``discovered'', while in the middle of the implementation, driven by the
|
||||
desire to re-use some code structure that was built just recently and in a similar way.
|
||||
|
||||
|
||||
Support Library
|
||||
---------------
|
||||
Over time, a huge and growing number of generic building blocks has been collected.
|
||||
Hoever, not all parts of this collection are equally well maintained and presentable.
|
||||
It happened time and again that some pattern extracted into the library turned out
|
||||
to be rather some kind or ephemeral habit -- maybe even a bad habit in hindsight --
|
||||
and turned into a dead-end eventually. Furthermore, a lot of our library code has
|
||||
been superseded by very similar features of newer language standards.footnote:[
|
||||
It seems like developers working on similar problems tend to have similar ideas
|
||||
to improve productivity. Unfortunately, the solutions resulting from this convergence
|
||||
of ideas have a tendency not to ``converge'' so well in practical terms.
|
||||
For example, Lumiera had a tuple type a long time before a mature tuple implementation
|
||||
was available in the Boost libraries, yet in the end, we could retrofit most of our
|
||||
tuple-handling library. However, other cases did not play out so well: we evolved
|
||||
the idea of ``iterators'' to become a lazy processing pipeline, but unfortunately
|
||||
our design leans more into the direction of functional programming and is much
|
||||
more expressive and powerful as abbreviation than the »Range-TS« from C++20.
|
||||
]
|
||||
The entrance barrier for something to be added to our _Support Library_ is rather low:
|
||||
|
||||
- extracting it as a library function _must make the client code clearer_
|
||||
- it must be possible to describe its purpose and its usage in _generic terms_ --
|
||||
without referring to data types and definitions from our domain models.
|
||||
- it must be _essentially stateless_ and self-contained
|
||||
- it must be easy to document with an unit-test.
|
||||
|
||||
Over time, parts of the Support Library have evolved into some kind of a framework:
|
||||
Notably the link:{ldoc}/design/architecture/time/index.html[time and timecode] handling,
|
||||
our link:Dependencies.html[dependency and service handling],
|
||||
the link:DiffFramework.html[»Diff framework«], together with the recursive property trees
|
||||
used for link:{ldoc}/design/architecture/ETD.html[»External Tree Description«], the
|
||||
link:iterator.html[iterator pipeline builders], the metaprogramming helpers for transforming
|
||||
function signatures and generic sequences, and our collection of string conversion, parsing
|
||||
and rendering functions. These all turned into distinguishing traits of the Lumiera code base.
|
||||
|
||||
The link:{ldoc}/technical/overview.html#_library[»Library« section of the »Overview« document]
|
||||
contains a long list of short descriptions for relevant features of the Support Library.
|
||||
|
||||
|
||||
.how to crack nuts...
|
||||
[NOTE]
|
||||
In the how-to section, there is a page with a loose collection of
|
||||
link:{ldoc}/technical/howto/crackNuts.html[successful solution patterns],
|
||||
many of which rely on unique features of our Support Lib.
|
||||
|
||||
|
||||
Interface Library
|
||||
-----------------
|
||||
The Lumiera Application is designed deliberately in a way to be controllable by scripts. All of
|
||||
the Session, the models and the vault are fully operational without the GUI. Any significant
|
||||
functionality can be addressed through the primary Façade Interfaces.
|
||||
functionality shall be usable through the primary Façade Interfaces alone. Yet in order to create
|
||||
such an open and extensible application as envisioned, we also need to build up an _interface layer._
|
||||
Eventually, this should evolve into a binding library for using Lumiera -- however, none of this
|
||||
interface building work has been accomplished yet, as of 2026. We do not even have a complete
|
||||
picture how _event sourcing_ meshes together with our UI commands and the configuration rules.
|
||||
|
||||
But, in order to make such an open and extensible application a reality, we need to build up
|
||||
an 'interface layer' -- provided as library for client code and script adapters to link against.
|
||||
This library allows to access the primary external interfaces and exposes everything needed to
|
||||
work with the specifics of the application.
|
||||
|
||||
[,yellow]#TODO as of 1/2012:# _this is just a nebulous plan for now._ Nothing of the work required
|
||||
really to address facilities from the outside has been done. All we've managed to achieve is actually
|
||||
to use clean interfaces and decouple the parts of the application appropriately.
|
||||
|
||||
- we still need to identify those core concepts which need to be exposed
|
||||
- we still need to identify those select core concepts which need to be exposed
|
||||
- we need to refactor the corresponding data types to resolve cross dependencies to the implementation
|
||||
- then we could think about moving those data types into an _official Lumiera interface_
|
||||
|
||||
Support Library
|
||||
---------------
|
||||
There is a huge collection of generic building blocks, re-used more or less frequently.
|
||||
The're written in a generic way, but in accordance to the rest of the source base, relying only
|
||||
on the general error handling and logging framework used within Lumiera. (Some facilities may depend
|
||||
on other parts of the Application; this can be qualified as a bug -- please complain when you've found
|
||||
something of that sort).
|
||||
|
||||
|
||||
|
||||
- only then we could think about moving those data types into an _official Lumiera interface_
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
Lumiera: The Inner Core
|
||||
=======================
|
||||
:Author: Lumiera_Core_Developers
|
||||
:Date: 2012 / 2025
|
||||
:Date: 2012 / 2026
|
||||
:toc:
|
||||
|
||||
[abstract]
|
||||
|
|
@ -30,7 +30,7 @@ This three Layers are:
|
|||
|
||||
The Steam Layer::
|
||||
Keeps the Session, generates the rendering graphs for sequences,
|
||||
arranges what to do when and how.
|
||||
prearranges elements and decides what to do when and how.
|
||||
|
||||
The Vault Layer::
|
||||
Manages worker pools, schedules jobs, does the memory management for the
|
||||
|
|
@ -39,13 +39,11 @@ This three Layers are:
|
|||
|
||||
The extra components are:
|
||||
|
||||
Lumiera::
|
||||
The main program itself, which starts all Subsystems and controls the
|
||||
overall lifecycle.
|
||||
|
||||
Common::
|
||||
Vital components and common services which must be available for pulling up
|
||||
any part of the system.
|
||||
The Vessel::
|
||||
This is the »backbone« of the application, it is rooted below the three
|
||||
layers and encompasses everything. It defines the periphery, starts all
|
||||
Subsystems, controls the overall lifecycle and provides vital services
|
||||
which must be available for engaging any part of the system.
|
||||
|
||||
Library::
|
||||
A lot of (largely) stateless helper functionality used throughout the code.
|
||||
|
|
@ -55,9 +53,9 @@ Coding Style
|
|||
~~~~~~~~~~~~
|
||||
|
||||
The Lumiera team agreed on using GNU coding style with slight adaptations.
|
||||
Generally speaking, we strive to keep the code base consistent and stick to
|
||||
widely accepted guidelines and best practices. See our separate
|
||||
link:{ldoc}/technical/code/codingGuidelines.html[Coding Guidelines] page.
|
||||
In general, we strive to maintain source code consistency and adhere to widely
|
||||
accepted guidelines and practices of good craftsmanship. See our separate
|
||||
link:{ldoc}/technical/code/CodingGuidelines.html[Coding Guidelines] page.
|
||||
|
||||
|
||||
|
||||
|
|
@ -148,7 +146,7 @@ contained within the Session, which belongs to the _steam layer_ below.
|
|||
User interfaces are implemented as plug-ins and are pulled up on demand,
|
||||
they won't contain any relevant persistent state beyond presentation.
|
||||
|
||||
_As of 2023, the one and only interface under active development is
|
||||
_As of 2026, the one and only interface under active development is
|
||||
the Lumiera GTK GUI,_ based on GTK-3 / gtkmm. The sources are in tree
|
||||
(directory 'src/stage') and it is integrated into the standard build and
|
||||
installation process. By default, running the 'lumiera' executable will
|
||||
|
|
@ -250,7 +248,7 @@ for each output channel is required.
|
|||
|
||||
The player service
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
Client code accesses the player (subsystem) through the play-facade (`lumiera::Play`).
|
||||
Client code accesses the player (subsystem) through the play-facade (`Play`).
|
||||
The exposed service allows to _set up an output connection for playback or rendering,_
|
||||
resulting in a play-controller object.
|
||||
|
||||
|
|
@ -285,11 +283,6 @@ performance critical.
|
|||
- the OutputSlot exposed by the output manager is responsible for accepting timed frame delivery
|
||||
|
||||
|
||||
|
||||
|
||||
Vault Layer
|
||||
-----------
|
||||
|
||||
Engine Interface
|
||||
~~~~~~~~~~~~~~~~
|
||||
While on itself just a thin interface and adaptation layer forwarding calls to
|
||||
|
|
@ -318,16 +311,21 @@ the job ticket acts as blueprint for the actual jobs to be enqueued
|
|||
with the _Scheduler._
|
||||
|
||||
|
||||
|
||||
Vault Layer
|
||||
-----------
|
||||
Scheduler
|
||||
~~~~~~~~~
|
||||
The Scheduler serves as the central hub in the implementation of the RenderEngine
|
||||
and coordinates the processing resources of the application. Its purpose is to
|
||||
invoke and control the dependency and time based execution of the _Render Jobs._
|
||||
|
||||
Regarding architecture, the Scheduler is located in the Vault-Layer and running the Scheduler
|
||||
is equivalent to activating the »Vault Subsystem«. An EngineFaçade acts as entrance point,
|
||||
providing high-level render services to other parts of the application: render jobs can be
|
||||
activated under various timing and dependency constraints, which can be grouped
|
||||
As far as architecture is concerned, the Scheduler is located in the Vault-Layer and
|
||||
is a _passive service_ -- like most components in the vault it needs to be put into
|
||||
use by some coordinating and integrating facility. An EngineFaçade, itself located
|
||||
above, in the »Steam« layer, acts as entrance point, to provide the high-level
|
||||
render services to other parts of the application: render jobs can be activated
|
||||
under various timing and dependency constraints, which can be grouped
|
||||
into three basic modes of control:
|
||||
|
||||
Deadline::
|
||||
|
|
@ -362,6 +360,29 @@ The scheduler is able to observe and maintain dependencies between render jobs,
|
|||
to break down an extended calculation into a chain of small steps.
|
||||
|
||||
|
||||
The Work Force
|
||||
~~~~~~~~~~~~~~
|
||||
Since the typical work job dimensions can be expected to lie in the range of _some milliseconds,_
|
||||
the Lumiera engine can adopt a statistical model of capacity distribution. This is notably
|
||||
different to an _operating system scheduler_ where the response times are measured in
|
||||
microseconds and the coordination of work approaches hardware limits. These observations
|
||||
lead to the (maybe slightly surprising approach) to rely on an _actively pulling_ work force
|
||||
combined with a _passive scheduling service._
|
||||
|
||||
The _Work Force_ is a **pool of worker threads** with a size typically matching the nominal
|
||||
number of independent CPU cores in the system. Spinning up the Work Force is equivalent to
|
||||
activating the »Vault Subsystem«. Deliberately, there is no dedicated ``engine'' or
|
||||
``manager'' thread. All workers participate in the coordination work on equal footing.
|
||||
An idle worker enters the (passive) scheduling code, and thereby performs management
|
||||
work up to the point where the next render job is determined. The worker then leaves
|
||||
the management realm and continues with performing this job. However, the scheduling
|
||||
code preloads each worker with some random sleep delay in order to distribute the
|
||||
``incidence'' of idle workers and to avoid contention. This way, the available
|
||||
computing capacity is distributed stochastically into that time window where
|
||||
demand is foreseeable, based on the next job's starting time, the currently
|
||||
dispatched jobs and the observed load factor.
|
||||
|
||||
|
||||
Resource Management
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
|
@ -378,12 +399,12 @@ the system and current circumstances.
|
|||
|
||||
|
||||
|
||||
Common Services
|
||||
---------------
|
||||
Vessel: Common Services
|
||||
-----------------------
|
||||
|
||||
Subsystem runner
|
||||
~~~~~~~~~~~~~~~~
|
||||
Conceptually, the application is structured into layers. However, as actual runtime
|
||||
Conceptually, the application is structured into layers. However, as the 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:
|
||||
|
||||
|
|
@ -418,17 +439,14 @@ link:{ldoc}/design/architecture/Subsystems.html#_lifecycle[documentation]
|
|||
[small]#(likewise incomplete, but we're getting there, eventually [yellow-background]##2018##)#
|
||||
|
||||
|
||||
Plugin loader
|
||||
~~~~~~~~~~~~~
|
||||
_tbw_
|
||||
|
||||
Advice framework
|
||||
~~~~~~~~~~~~~~~~
|
||||
This is a ``whiteboard'' system, allowing implementation-level services to _publish_
|
||||
some piece of information as _advice_, while other parts of the system may pick up
|
||||
this advice just by a name token, without requiring a direct knowledge of the
|
||||
original _advisor._ The _Advice System_ is a singleton service maintaining a
|
||||
lookup and registration data structure. Individual _piece of advice_ elements
|
||||
original _advisor._ The _Advice System_ is a singleton service that maintains
|
||||
a lookup and registration data structure. Individual _piece of advice_ elements
|
||||
are stored _as value copy_. Publishing new advice requires locking, but accessing
|
||||
advice is lock-free (actually there needs to be a memory barrier ``somewhere'',
|
||||
otherwise the advice requesting client might not see new advice)
|
||||
|
|
@ -458,13 +476,27 @@ Library
|
|||
-------
|
||||
|
||||
The Lumiera support library contains lots of helper functionality
|
||||
factored out from the internals and re-used. It is extended as we go.
|
||||
factored out from the internals and re-used. Some parts evolved into
|
||||
basic frameworks, that are used pervasively throughout the code base.
|
||||
|
||||
- link:{ldoc}/design/architecture/time/index.html[time and timecode]
|
||||
(-> xref:_time[below])
|
||||
- link:Dependencies.html[dependency and service handling]
|
||||
(-> xref:_singletons_and_dependencies[below]),
|
||||
- the link:DiffFramework.html[»Diff framework«]
|
||||
- property trees for link:{ldoc}/design/architecture/ETD.html[»External Tree Description«]
|
||||
(-> xref:_external_tree_description[below])
|
||||
- the link:iterator.html[iterator pipeline builders]
|
||||
(-> xref:_iterators[below])
|
||||
- various metaprogramming helpers (function signatures and generic sequences)
|
||||
- string conversion, parsing and rendering functions
|
||||
|
||||
|
||||
practical shortcuts
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
The header 'lib/util.hpp' defines some shortcuts heavily used throughout
|
||||
the code base. The idea is to highlight a common semantic meaning, while
|
||||
hide differentiation on the technical details.
|
||||
The header 'lib/util.hpp' defines some shortcuts that are used almost
|
||||
everywhere in the code base. The general idea is to highlight a common semantic meaning,
|
||||
while hide the differentiation related to the technical details.
|
||||
|
||||
isnil:: indicate a _missing value_, irrespective if this is a NULL pointer,
|
||||
an empty string or an empty container. Several of our custom wrappers also
|
||||
|
|
@ -473,8 +505,29 @@ isnil:: indicate a _missing value_, irrespective if this is a NULL pointer,
|
|||
contains:: indicate that a value is _somehow contained_ within a collection,
|
||||
irrespective if this is a set, a map, a vector or string (substring test)
|
||||
|
||||
sanitise:: make any string usable as identifier.
|
||||
limited:: to bracket some value.
|
||||
|
||||
unConst:: a functional notation to cast away `const`, which is used typically
|
||||
at the barrier between a type safe front-end and a low-level back-end,
|
||||
and to avoid needless duplication of accessor code implementation
|
||||
for sake of providing `const` information functions.
|
||||
|
||||
isSameObject:: identity test based on bare memory location,
|
||||
which is used a lot in assertions and unit tests
|
||||
|
||||
string shorthands::
|
||||
trim, startsWith, endsWith, boolVal, sanitise (turn arbitrary string into an identifier)
|
||||
|
||||
|
||||
Literals and Symbols
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
Besides the well-known C++ strings (and `string_view`), Lumiera introduces
|
||||
two marker types for string tokens
|
||||
|
||||
Literal:: a C-string literal directly given in the Source code
|
||||
|
||||
Symbol:: an _interned_ token stored in a Symbol table, so that its
|
||||
address hash can be used as unique identity
|
||||
|
||||
|
||||
Locking
|
||||
|
|
@ -892,6 +945,17 @@ and assignment incur the cost of an additional virtual call,
|
|||
assuming the target objects cooperate by mixing in a
|
||||
copy management interface.
|
||||
|
||||
.uninitialised storage
|
||||
A cornerstone of the C++ language is the deterministic and reliable invocation
|
||||
of constructors and destructors. However, when building low-level storage schemes,
|
||||
a frequent requirement is to cover the precise storage of an object collection,
|
||||
_without_ actually invoking any constructor or destructor. The `UninitialisedStorage`
|
||||
template is a building block to support this task. It is parametrised with a type
|
||||
and an element count, and provides a force-casted array-like access to the storage cells.
|
||||
This can be used either to placement-construct some elements on-demand, or to ``project''
|
||||
an object-like view over existing data in the storage.
|
||||
|
||||
|
||||
|
||||
Unique Identifiers
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
Technical Documentation: Stage Layer
|
||||
====================================
|
||||
|
||||
Eventually, this will have technical documentation for the GUI.
|
||||
This section offers a loose collection with detailed information regarding the GUI implementation.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,27 +1,36 @@
|
|||
Technical Documentation: Steam-Layer
|
||||
====================================
|
||||
|
||||
Eventually, this section will hold technical documentation for the Steam-Layer.
|
||||
As implementation progresses, this section will gradually compile
|
||||
the technical documentation for the Steam layer.
|
||||
|
||||
Development Scrapbook TiddlyWiki
|
||||
--------------------------------
|
||||
Hermann Voßeler, core developer of the Steam-Layer uses an separate embedded wiki
|
||||
for his day-to-day design sketches, notes and also for the more persistent planning.
|
||||
This *TiddlyWiki* is a nifty JavaScript wiki embedded in a single HTML page; as such
|
||||
it lives right within the source tree, where it can be worked on alongside with the
|
||||
code. Besides the actual planning and descriptions, this wiki also links to the
|
||||
UML diagrams created with *BOUML* during the ongoing planning and design.
|
||||
It is sometimes difficult to write complete documentation while a system is still under construction.
|
||||
For taking work notes and formulating new ideas while working, _Ichthyostega_ frequently uses the
|
||||
https://classic.tiddlywiki.com/[TiddlyWiki] -- which is a nifty JavaScript wiki embedded within a
|
||||
single HTML page; as such it lives right within the source tree, where it can be worked on alongside
|
||||
with the code. Notes and sketches for the planning often evolve into detailed descriptions, complete
|
||||
with test data and UML diagrams to document the structure. Material drafted in the TiddlyWiki
|
||||
will be moved gradually into this documentation section, as time permits.
|
||||
|
||||
This setup started as a temporary solution -- but as uWiki is not expected to come
|
||||
to life soon, the current website can't serve as replacement for a real wiki, an
|
||||
we'll stick to that situation for the time being. As the content in the TiddlyWiki
|
||||
becomes more mature and stable, it will be moved over into this section here.
|
||||
This arrangement started as a temporary solution at a time where we did not have a suitable website
|
||||
for permanent documentation -- and it was kept ever since, as the TiddlyWiki is very convenient
|
||||
to work with, once you are accustomed to its quirks.footnote:[
|
||||
It is quite fluid and inspiring to work with indeed. The content is an unordered collection
|
||||
of small notepad pages, called ``Tiddlers'', which can be opened and closed conveniently.
|
||||
Any "CamelCaseWord" becomes a new Tiddler, recent changes appear in a timeline, and Tiddlers
|
||||
can be organised with tags. The only point to be careful is that you need to _save_
|
||||
your work, which can be achieved with the `save' action in the menu to overwrite
|
||||
the old version in the Git tree. Sometimes this might produce Git conflicts,
|
||||
which are easy to resolve, since the content is plain text.
|
||||
]
|
||||
Additions to the TiddlyWiki are usually made during coding, and are deliberately committed always
|
||||
together with the corresponding code. As such, these additions will propagate to Lumiera/Master eventually;
|
||||
yet to make the newest development notes easier to access, we have put up a `rsync`ed version of the
|
||||
TiddlyWiki online for direct access over the internet (which then is of course read-only).
|
||||
|
||||
While additions to the TiddlyWiki generally propagate to Lumiera/Master through
|
||||
the normal merges, we've put up a rsynced version of the TiddlyWiki online for
|
||||
direct access (of course read-only).
|
||||
|
||||
-> access the Proy-Layer link:/wiki/renderengine.html[TiddlyWiki online here]
|
||||
-> access the link:/wiki/renderengine.html[TiddlyWiki online here]
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -113,8 +113,11 @@ each have a distinct _type_ which explicitly identifies the possible connections
|
|||
modules. Within these constraints, any combination is possible without
|
||||
hidden restrictions.
|
||||
|
||||
Lumiera is not a set of Lego bricks, nor is it a business application
|
||||
driven by finite usage stories.
|
||||
However, the application strives for a certain degree of internal coherence. +
|
||||
Lumiera is not a set of Lego bricks allowing you to build ``just anything''
|
||||
(in a clumsy way) -- nor is it the next universal programming platform or
|
||||
application framework driven by pre-canned success stories, where you
|
||||
``just push the button'' and magic does the rest.
|
||||
|
||||
|
||||
Medium Level Abstraction and Project Specific Conventions
|
||||
|
|
@ -131,9 +134,8 @@ details such as header format conversion or access to individual channels.
|
|||
To complement this approach, Lumiera does _not_ rely on hard-wired, global
|
||||
conventions -- rather we allow the building up of project specific conventions and
|
||||
rules xref:rules[<-] to fit a given requirement and preferred working style.
|
||||
|
||||
Lumiera will be supplied with a conventional template and a default
|
||||
configuration to ease the learning curve for new users.
|
||||
However, in its default configuration, Lumiera will be supplied with a conventional
|
||||
UI template and key bindings to ease the learning curve for new users.
|
||||
|
||||
|
||||
[[graphs]]
|
||||
|
|
@ -144,12 +146,12 @@ Processing video (and audio) data can be conceived as a graph (more precisely
|
|||
a _directed acyclic graph_). In this model of video processing, data flows
|
||||
along the edges of the graph and is processed at the nodes.
|
||||
|
||||
The following figure depicts manipulating video data as a graph. The nodes at
|
||||
the top of the graph are where data input occurs. From there, the data moves on
|
||||
to the next nodes: the direct siblings of the root. Here, the video data is
|
||||
pre-processed. All other operations on the data can be represented by
|
||||
nodes, and data flows from one operation to the next along the nodes of the
|
||||
graph.
|
||||
The following figure depicts manipulating video data as a graph.
|
||||
Input data is retrieved from the source nodes, which are located at the
|
||||
top of the figure. From there, the data moves on to the next stage, where
|
||||
video and audio data and meta information is pre-processed. All the further
|
||||
operations on data can be represented by nodes, and data flows from one operation
|
||||
to the next along the nodes of the graph.
|
||||
|
||||
|
||||
image:{imgd}/lumiera_big_graph.png[Example for a graph]
|
||||
|
|
@ -218,16 +220,15 @@ Lumiera consists of three major parts:
|
|||
In this section, we discuss the user's perspective when working with the GUI.
|
||||
|
||||
|
||||
Although Lumiera will initially ship with a standard, default GUI, we do not presume that this GUI will be suitable for all uses.
|
||||
We expect there to be multiple, different GUIs, each designed for different
|
||||
kinds of tasks. We expect that some of these will be written by users, and the
|
||||
steam-layer is designed to facilitate this easily.
|
||||
Although Lumiera ships with a conventional desktop UI by default, we do not presume
|
||||
that this GUI will be suitable for all uses. We expect there to be several specialised
|
||||
UIs eventually, designed to handle very specific kinds of tasks.
|
||||
|
||||
Indeed, Lumiera can even work
|
||||
satisfactorily without a GUI, for example, for special purposes
|
||||
(e.g. as headless render-node xref:rendernode[<-] or frameserver
|
||||
xref:frameserver[<-]). Scripts will eventually be written
|
||||
to facilitate automated processing.
|
||||
xref:frameserver[<-]). A script interface will be added at some
|
||||
point to facilitate automated processing.
|
||||
|
||||
|
||||
Now it is time to take a look at the preliminary Lumiera GUI:
|
||||
|
|
@ -505,21 +506,19 @@ A Plug-in is a kind of generalisation of a library.
|
|||
All applications use, to varying degrees of intensity, libraries. A programmer
|
||||
will not reinvent the wheel each time he sits down to programme an
|
||||
application. A programmer will typically borrow and use features and
|
||||
functionality from other programmers---or even borrow from himself, stuff
|
||||
functionality from other programmers -- or even borrow from himself, stuff
|
||||
written long ago in the past. Such features are collected together in
|
||||
libraries.
|
||||
|
||||
A library is used in an application by _linking_ the library into the
|
||||
application. (There are other things to be done, but we'll call these 'details',
|
||||
which wont concern us here.) There are different ways to _link_ a library
|
||||
into an application: linking statically and linking dynamically.
|
||||
A library is used in an application by _linking_ the library into the application.
|
||||
However, there are different ways to _link_ a library: linking statically and
|
||||
linking dynamically.
|
||||
|
||||
_Static Linking_ is done while the application is being built, or
|
||||
compiled. It is performed by the linker, which executes as the last step
|
||||
of the compilation process. The linker can perform some sanity checks
|
||||
(mostly checks regarding formally correct function invocation) and warn the user
|
||||
hat some particular feature is being used incorrectly. The user can then correct
|
||||
the offending code, and recompile. +
|
||||
of the compilation process. The linker can perform some sanity checks and
|
||||
some optimisation. In the end, the functionality of the library is ``baked into''
|
||||
the resulting executable. +
|
||||
There are a number of disadvantages associated with static linking. Features and
|
||||
libraries are being constantly improved. If the application wants to use new
|
||||
features, it will have to be recompiled with the new library which provides the
|
||||
|
|
@ -532,15 +531,14 @@ features can be used by the application. The features provided by a dynamic
|
|||
library are loaded when the application starts to run.
|
||||
|
||||
However both methods exhibit a number of shortcomings. Wouldn't it be better if
|
||||
all features could be loaded only when needed? If features could be loaded only
|
||||
when needed, then they could also be unloaded when not required, thus saving
|
||||
memory and possibly increasing performance. This scheme of making features
|
||||
available to an application is known as run-time linking, aka plug-ins.
|
||||
all features could be loaded only when needed? This scheme of making features
|
||||
available to an application on-demand is known as run-time linking. A component
|
||||
intended to be added this way, at run time, by the user, is known as a Plug-in.
|
||||
Plug-ins offer other benefits: the application can continue to use both the old
|
||||
features and the new features together, side-by-side, by using the version
|
||||
number associated with the plug-in. This saves the application from the havoc
|
||||
of library incompatibility that is
|
||||
associated with other linking methods.
|
||||
number associated with the plug-in. Furthermore, Plug-ins can load other Plug-ins
|
||||
and thus extend the application in creative ways, beyond anything the original
|
||||
developers of the application had planned.
|
||||
|
||||
Most modern applications use plug-ins, some are heavily dependent on plug-ins
|
||||
and only provide limited functionality without any plug-ins.
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ in addition to the following tools and libraries:
|
|||
* https://www.boost.org/[Boost libraries]
|
||||
* https://github.com/bplaum/gavl[GAVL library]
|
||||
* https://nobug.pipapo.org/[NoBug] ~([purple]#⚠ see below#)~
|
||||
* Lumiera https://git.lumiera.org/?p=LUMIERA;a=summary[source code]
|
||||
* Lumiera https://git.lumiera.org/gitweb?p=LUMIERA;a=summary[source code]
|
||||
|
||||
The GUI depends on the following:
|
||||
|
||||
|
|
|
|||
|
|
@ -115,9 +115,9 @@ sudo make install
|
|||
but was orphaned in 2021. Since it is currently a prerequisite for building Lumiera,
|
||||
we maintain a clone of the original repository and a Debian package on Lumiera.org.
|
||||
This is relevant both for
|
||||
https://git.lumiera.org/?p=debian/gdl;a=summary[libGDL (3.40) +git://git.lumiera.org/debian/gdl+]
|
||||
https://git.lumiera.org/gitweb?p=debian/gdl;a=summary[libGDL (3.40) +git://git.lumiera.org/debian/gdl+]
|
||||
and the
|
||||
https://git.lumiera.org/?p=debian/gdlmm;a=summary[GDL C++ bindings (3.7.3) +git://git.lumiera.org/debian/gdlmm+].
|
||||
https://git.lumiera.org/gitweb?p=debian/gdlmm;a=summary[GDL C++ bindings (3.7.3) +git://git.lumiera.org/debian/gdlmm+].
|
||||
]
|
||||
|
||||
* first you have to build libGTL itself...
|
||||
|
|
|
|||
411
research/try.cpp
411
research/try.cpp
|
|
@ -1,6 +1,7 @@
|
|||
/* try.cpp - to try out and experiment with new features....
|
||||
* scons will create the binary bin/try
|
||||
*/
|
||||
// 03/26 - runtime index access and iteration over tuple-likes
|
||||
// 06/25 - provide a concept to accept _tuple-like_ objects
|
||||
// 06/25 - investigate function type detection of std::bind Binders
|
||||
// 12/24 - investigate problem when perfect-forwarding into a binder
|
||||
|
|
@ -10,22 +11,28 @@
|
|||
|
||||
|
||||
/** @file try.cpp
|
||||
* Develop a concept to detect _tuple-like_ classes, based on the requirements
|
||||
* of the »tuple protocol«. Using some ideas from [Stackoverflow] as starting point.
|
||||
* However, we model both a _friend function_ `get` and a similar member function
|
||||
* as alternatives for element access, and we break down the checks into sub-concepts.
|
||||
* [Stackoverflow]: https://stackoverflow.com/q/68443804/444796
|
||||
* Work out a construction scheme to build an efficient element-accessor to data
|
||||
* in a tuple-like with run-time indexing. As an extension, run-time pulled iteration
|
||||
* can be established, using the IterExplorer framework.
|
||||
* @see lib::meta::FunTrampoline
|
||||
* @see lib::meta::TupleIdxAdaptor
|
||||
*/
|
||||
|
||||
|
||||
#include "lib/format-cout.hpp"
|
||||
#include "lib/test/test-helper.hpp"
|
||||
#include "lib/test/diagnostic-output.hpp"
|
||||
#include "lib/format-util.hpp"
|
||||
#include "test/test-helper.hpp"
|
||||
#include "test/diagnostic-output.hpp"
|
||||
#include "lib/meta/common-result.hpp"
|
||||
#include "lib/meta/tuple-concept.hpp"
|
||||
#include "lib/meta/tuple-helper.hpp"
|
||||
#include "lib/meta/function.hpp"
|
||||
#include "lib/iter-adapter.hpp"
|
||||
#include "lib/hetero-data.hpp"
|
||||
#include "lib/meta/trait.hpp"
|
||||
#include "lib/util.hpp"
|
||||
|
||||
#include <concepts>
|
||||
#include <array>
|
||||
|
||||
using std::string;
|
||||
|
||||
|
|
@ -41,14 +48,263 @@ template<lib::meta::tuple_like X>
|
|||
void
|
||||
show()
|
||||
{
|
||||
cout << "Tup!! "<< lib::test::showType<X>() <<endl;
|
||||
cout << "Tulik!! "<< ::test::showType<X>() <<endl;
|
||||
lib::meta::forEachIDX<X> ([](auto i)
|
||||
{
|
||||
using Elm = std::tuple_element_t<i, X>;
|
||||
cout <<" "<<uint(i)<<": "<< lib::test::showType<Elm>() <<endl;
|
||||
cout <<" "<<uint(i)<<": "<< ::test::showType<Elm>() <<endl;
|
||||
});
|
||||
}
|
||||
|
||||
namespace lib {
|
||||
namespace meta{
|
||||
|
||||
#if false ///////////////////////////////////////////////////////////////////////////////////////////////////TODO : new generic library functions
|
||||
/**
|
||||
* Concept to require a generic type sequence `Types<TYPES...>`
|
||||
*/
|
||||
template<class TSEQ>
|
||||
concept typeseq = requires (TSEQ seq)
|
||||
{
|
||||
// TSEQ can invoke a function with a Types<TYPES> subclass
|
||||
[]<typename...TYPES>(Types<TYPES...>&){/*placeholder*/}(seq);
|
||||
};
|
||||
#endif ///////////////////////////////////////////////////////////////////////////////////////////////////(End) : new generic library functions
|
||||
|
||||
|
||||
template<template<class> class BUILD, typeseq TSEQ>
|
||||
class FunTrampoline
|
||||
{
|
||||
using AllElm = ElmTypes<TSEQ>;
|
||||
using Indices = AllElm::Idx;
|
||||
using Builder = BUILD<TSEQ>;
|
||||
using Functor = Builder::Functor;
|
||||
using Table = std::array<Functor, AllElm::SIZ>;
|
||||
|
||||
template<size_t...idx>
|
||||
static constexpr Table
|
||||
buildFunctors (std::index_sequence<idx...>)
|
||||
{
|
||||
return {Builder().template build<idx>()...};
|
||||
}
|
||||
|
||||
static constexpr Table functors = buildFunctors (Indices{});
|
||||
|
||||
public:
|
||||
static constexpr size_t size() { return functors.size(); }
|
||||
static constexpr size_t memSiz() { return sizeof(functors); }
|
||||
|
||||
static Functor const&
|
||||
dispatch (size_t idx)
|
||||
{
|
||||
ENSURE (idx < functors.size());
|
||||
return functors[idx];
|
||||
}
|
||||
|
||||
using ResType = _Fun<Functor>::Ret;
|
||||
};
|
||||
|
||||
|
||||
#if false ///////////////////////////////////////////////////////////////////////////////////////////////////TODO : new generic library functions
|
||||
template<typename T>
|
||||
using isConst = std::is_const<remove_reference_t<T>>;
|
||||
template<typename T>
|
||||
using isLRef = std::is_lvalue_reference<T>;
|
||||
|
||||
|
||||
template<class SEQ>
|
||||
using Any_Const = ElmTypes<SEQ>::template OrAll<isConst>;
|
||||
|
||||
template<class SEQ>
|
||||
using All_LRef = ElmTypes<SEQ>::template AndAll<isLRef>;
|
||||
|
||||
template<class SEQ>
|
||||
static constexpr bool Any_Const_v = Any_Const<SEQ>::value;
|
||||
|
||||
template<class SEQ>
|
||||
static constexpr bool All_LRef_v = All_LRef<SEQ>::value;
|
||||
|
||||
|
||||
|
||||
|
||||
template<typename X, typename U, typename...TYPES>
|
||||
struct ProbeCommonType
|
||||
{
|
||||
|
||||
template<typename T1, typename T2>
|
||||
using Reconciled = decltype(true ? std::declval<T1>() : std::declval<T2>());
|
||||
|
||||
|
||||
template<typename RES>
|
||||
struct FoundResult
|
||||
: std::true_type
|
||||
{
|
||||
using Type = RES;
|
||||
};
|
||||
struct NoResultFound
|
||||
: std::false_type
|
||||
{ };
|
||||
|
||||
template<class T1
|
||||
,class T2
|
||||
,typename = Reconciled<T1,T2>>
|
||||
static FoundResult<Reconciled<T1,T2>>
|
||||
check(int);
|
||||
|
||||
template<class,class>
|
||||
static NoResultFound
|
||||
check(...);
|
||||
|
||||
using FirstResult = decltype(check<X,U>(0));
|
||||
|
||||
template<bool success, typename...TS>
|
||||
struct Join
|
||||
{
|
||||
using Result = FirstResult;
|
||||
};
|
||||
|
||||
template<typename T1, typename...TS>
|
||||
struct Join<true, T1,TS...>
|
||||
{
|
||||
//wenn RES erfolgreich
|
||||
using CT = FirstResult::Type;
|
||||
using Result = ProbeCommonType<CT,T1,TS...>::Result;
|
||||
};
|
||||
|
||||
using Result = Join<FirstResult::value, TYPES...>::Result;
|
||||
};
|
||||
|
||||
|
||||
|
||||
template<typename...TYPES>
|
||||
struct CommonResult
|
||||
: ProbeCommonType<TYPES...>::Result
|
||||
{ };
|
||||
|
||||
template<typename T1>
|
||||
struct CommonResult<T1>
|
||||
: CommonResult<T1,T1>
|
||||
{ };
|
||||
|
||||
template<>
|
||||
struct CommonResult<>
|
||||
: std::false_type
|
||||
{ };
|
||||
|
||||
template<typename...TYPES>
|
||||
struct CommonResult<Types<TYPES...>>
|
||||
: CommonResult<TYPES...>
|
||||
{ };
|
||||
|
||||
template<typename...TYPES>
|
||||
using CommonResult_t = CommonResult<TYPES...>::Type;
|
||||
|
||||
template<typename...TYPES>
|
||||
static constexpr bool has_CommonResult_v = CommonResult<TYPES...>::value;
|
||||
|
||||
|
||||
template<typename...TYPES>
|
||||
concept reconcilable = requires
|
||||
{
|
||||
typename CommonResult<TYPES...>::Type;
|
||||
};
|
||||
|
||||
template<typename...TYPES>
|
||||
concept reconcilable_ref = reconcilable<TYPES...>
|
||||
and isLRef_v<CommonResult_t<TYPES...>>;
|
||||
|
||||
template<typename...TYPES>
|
||||
struct CommonResultTraits
|
||||
{
|
||||
static_assert (reconcilable<TYPES...>, "unable to reconcile these types");
|
||||
|
||||
using CommonVal = std::common_type_t<TYPES...>;
|
||||
using ResType = CommonResult_t<TYPES...>;
|
||||
//NOTE: std::common_type decays (strips CV and ref)
|
||||
static constexpr bool makeConst = isConst_v<ResType>;
|
||||
static constexpr bool exposeRef = isLRef_v<ResType>;
|
||||
};
|
||||
#endif ///////////////////////////////////////////////////////////////////////////////////////////////////(End) : new generic library functions
|
||||
|
||||
|
||||
|
||||
template<class TUP>
|
||||
class TupleIndexAdaptor
|
||||
: public TUP
|
||||
{
|
||||
template<typeseq SEQ>
|
||||
struct SubscriptBuilder
|
||||
{
|
||||
using Tuple = RefTraits<TUP>::value_type;
|
||||
using AddRefs = ElmTypes<SEQ>::template Apply<std::add_lvalue_reference_t>;
|
||||
using ResType = CommonResult_t<AddRefs>;
|
||||
using Functor = ResType(*)(Tuple&);
|
||||
|
||||
template<size_t idx>
|
||||
constexpr auto
|
||||
build()
|
||||
{
|
||||
return [](Tuple& tuple) -> ResType
|
||||
{
|
||||
return getElm<idx> (tuple);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
using TypeSeq = ElmTypes<TUP>::Seq;
|
||||
using Accessor = FunTrampoline<SubscriptBuilder, TypeSeq>;
|
||||
public:
|
||||
using ResType = Accessor::ResType;
|
||||
using reference = ResType;
|
||||
using value_Type = RefTraits<ResType>::value_type;
|
||||
|
||||
template<typename T>
|
||||
TupleIndexAdaptor(T&& dataSrc)
|
||||
: TUP{forward<T> (dataSrc)}
|
||||
{ }
|
||||
|
||||
using TUP::TUP;
|
||||
|
||||
static constexpr size_t size() { return Accessor::size(); }
|
||||
|
||||
ResType
|
||||
operator[] (size_t idx)
|
||||
{
|
||||
return Accessor::dispatch(idx)(*this);
|
||||
}
|
||||
};
|
||||
|
||||
template<class TUP>
|
||||
TupleIndexAdaptor(TUP) -> TupleIndexAdaptor<std::decay_t<TUP>>;
|
||||
}}
|
||||
|
||||
using namespace lib::meta;
|
||||
|
||||
template<typeseq SEQ>
|
||||
struct Stringify
|
||||
{
|
||||
using Tuple = ElmTypes<SEQ>::Tup;
|
||||
using Functor = string(*)(Tuple const&);
|
||||
|
||||
template<size_t idx>
|
||||
constexpr auto
|
||||
build()
|
||||
{
|
||||
using ElmT = std::tuple_element_t<idx, Tuple>;
|
||||
|
||||
return [](Tuple const& tuple)
|
||||
{
|
||||
return util::toString<ElmT> (getElm<idx> (tuple));
|
||||
};
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
struct Uber { };
|
||||
struct Untr : Uber { };
|
||||
struct Unta : Uber { };
|
||||
|
||||
|
||||
int
|
||||
main (int, char**)
|
||||
|
|
@ -57,20 +313,10 @@ main (int, char**)
|
|||
using Arr = std::array<int,3>;
|
||||
using Hetero = lib::HeteroData<int,string>::Chain<short>::ChainExtent<bool,lib::meta::Nil>::ChainType;
|
||||
|
||||
SHOW_EXPR((lib::meta::tuple_sized<Tup> ))
|
||||
SHOW_EXPR((lib::meta::tuple_sized<Arr> ))
|
||||
SHOW_EXPR((lib::meta::tuple_sized<Hetero> ))
|
||||
SHOW_EXPR((lib::meta::tuple_sized<int> ))
|
||||
|
||||
SHOW_EXPR((lib::meta::tuple_element_accessible<Tup,0>))
|
||||
// SHOW_EXPR((lib::meta::tuple_element_accessible<Tup,2>))
|
||||
SHOW_EXPR((lib::meta::tuple_element_accessible<Hetero,0>))
|
||||
SHOW_EXPR((lib::meta::tuple_accessible<Tup>))
|
||||
SHOW_EXPR((lib::meta::tuple_accessible<Hetero>))
|
||||
|
||||
SHOW_EXPR((lib::meta::tuple_like<Tup> ))
|
||||
SHOW_EXPR((lib::meta::tuple_like<Arr> ))
|
||||
SHOW_EXPR((lib::meta::tuple_like<Hetero> ))
|
||||
SHOW_EXPR((lib::meta::tuple_like<string> ))
|
||||
SHOW_EXPR((lib::meta::tuple_like<int> ))
|
||||
|
||||
show<Tup>();
|
||||
|
|
@ -78,25 +324,112 @@ main (int, char**)
|
|||
show<Hetero>();
|
||||
show<int>();
|
||||
|
||||
SHOW_EXPR((std::tuple_size_v<const Tup>))
|
||||
using Elm1 = std::tuple_element_t<1, const Tup>;
|
||||
SHOW_TYPE(Elm1)
|
||||
using TupConstSeq = lib::meta::ElmTypes<const Tup>::Seq;
|
||||
SHOW_TYPE(TupConstSeq)
|
||||
using TupStringy = FunTrampoline<Stringify, ElmTypes<Tup>::Seq>;
|
||||
SHOW_EXPR(TupStringy::size())
|
||||
|
||||
using T1 = decltype(lib::meta::getElm<0> (std::declval<Tup>()));
|
||||
SHOW_TYPE(T1)
|
||||
using T2 = decltype(lib::meta::getElm<0> (std::declval<Tup&>()));
|
||||
SHOW_TYPE(T2)
|
||||
using T3 = decltype(lib::meta::getElm<0> (std::declval<Tup const&>()));
|
||||
SHOW_TYPE(T3)
|
||||
|
||||
using H1 = decltype(lib::meta::getElm<4> (std::declval<Hetero>()));
|
||||
SHOW_TYPE(H1)
|
||||
using H2 = decltype(lib::meta::getElm<4> (std::declval<Hetero&>()));
|
||||
SHOW_TYPE(H2)
|
||||
using H3 = decltype(lib::meta::getElm<4> (std::declval<Hetero const&>()));
|
||||
SHOW_TYPE(H3)
|
||||
Tup tup23{2,3};
|
||||
Tup tup42{4,2};
|
||||
SHOW_TYPE(decltype(std::get<1>(tup42)))
|
||||
SHOW_EXPR(std::get<1>(tup42))
|
||||
SHOW_EXPR(TupStringy::dispatch(0)(tup23))
|
||||
SHOW_EXPR(TupStringy::dispatch(0)(tup42))
|
||||
SHOW_EXPR(TupStringy::dispatch(1)(tup23))
|
||||
SHOW_EXPR(TupStringy::dispatch(1)(tup42))
|
||||
|
||||
using T1 = Types<int const&,long&, char&>;
|
||||
using T2 = Types<int*,void*>;
|
||||
using T3 = Types<int,string>;
|
||||
SHOW_EXPR((has_CommonResult_v<T1>))
|
||||
SHOW_EXPR((has_CommonResult_v<T2>))
|
||||
SHOW_EXPR((has_CommonResult_v<T3>))
|
||||
SHOW_EXPR((has_CommonResult_v<>))
|
||||
SHOW_EXPR((has_CommonResult_v<float*>))
|
||||
SHOW_EXPR((has_CommonResult_v<float*,void* const>))
|
||||
|
||||
SHOW_TYPE(CommonResult_t<T1>)
|
||||
SHOW_TYPE(CommonResult_t<T2>)
|
||||
SHOW_TYPE(CommonResult_t<float*>)
|
||||
using CoTx = CommonResult_t<float*,float const*>;
|
||||
SHOW_TYPE(CoTx)
|
||||
|
||||
|
||||
TupleIndexAdaptor<Tup> idxAdapt{tup42};
|
||||
SHOW_EXPR(sizeof(tup42))
|
||||
SHOW_EXPR(sizeof(idxAdapt))
|
||||
SHOW_TYPE(TupleIndexAdaptor<Tup>::ResType)
|
||||
SHOW_EXPR(idxAdapt.size())
|
||||
SHOW_EXPR(idxAdapt[0])
|
||||
SHOW_EXPR(idxAdapt[1])
|
||||
SHOW_EXPR(getElm<0>(static_cast<Tup&> (idxAdapt)))
|
||||
SHOW_EXPR(getElm<1>(static_cast<Tup&> (idxAdapt)))
|
||||
SHOW_TYPE(decltype(idxAdapt[1]))
|
||||
|
||||
using Tup2 = std::tuple<int,int&, int>;
|
||||
int ii{23};
|
||||
Tup2 tup21{-1,ii,55};
|
||||
TupleIndexAdaptor idxApt2{tup21};
|
||||
SHOW_TYPE(decltype(tup21))
|
||||
using IdxApt2 = decltype(idxApt2);
|
||||
SHOW_TYPE(IdxApt2)
|
||||
SHOW_TYPE(IdxApt2::ResType)
|
||||
SHOW_EXPR(idxApt2[0])
|
||||
SHOW_EXPR(idxApt2[1])
|
||||
SHOW_EXPR(idxApt2[2])
|
||||
SHOW_EXPR(++idxApt2[0])
|
||||
SHOW_EXPR(idxApt2[1]++)
|
||||
SHOW_EXPR(++++idxApt2[2])
|
||||
SHOW_EXPR(tup21)
|
||||
SHOW_EXPR(Tup2(idxApt2))
|
||||
SHOW_EXPR(ii)
|
||||
|
||||
using TupIt = lib::IterableDecorator<lib::IdxStoreCore<IdxApt2>>;
|
||||
TupIt tupIt{move(idxApt2)};
|
||||
SHOW_EXPR( bool(tupIt) )
|
||||
SHOW_EXPR( *tupIt )
|
||||
SHOW_TYPE(decltype(*tupIt))
|
||||
++*tupIt;
|
||||
--ii;
|
||||
SHOW_EXPR( *tupIt )
|
||||
SHOW_EXPR(util::join(tupIt, "▶▶"))
|
||||
|
||||
SHOW_EXPR(( reconcilable<long,string> ))
|
||||
SHOW_EXPR(( reconcilable<long&,string&> ))
|
||||
SHOW_EXPR(( reconcilable<long,short> ))
|
||||
SHOW_EXPR(( reconcilable<long&,short&> ))
|
||||
SHOW_EXPR(( reconcilable_ref<long,string> ))
|
||||
SHOW_EXPR(( reconcilable_ref<long&,string&> ))
|
||||
SHOW_EXPR(( reconcilable_ref<long,short> ))
|
||||
SHOW_EXPR(( reconcilable_ref<long&,const long&> ))
|
||||
using TX = CommonResult_t<long&,const long&>;
|
||||
SHOW_TYPE(TX)
|
||||
SHOW_EXPR(( reconcilable<float*,void*> ))
|
||||
SHOW_EXPR(( reconcilable<void,void*> ))
|
||||
SHOW_EXPR(( reconcilable<float*&,void*&> ))
|
||||
using TX2 = CommonResult_t<float*&,float*&>;
|
||||
SHOW_TYPE(TX2)
|
||||
SHOW_EXPR(( reconcilable<Uber,Unta> ))
|
||||
SHOW_EXPR(( reconcilable<Untr,Unta> ))
|
||||
SHOW_EXPR(( reconcilable<Untr&,Unta&> ))
|
||||
SHOW_EXPR(( reconcilable<Uber&,Unta&> ))
|
||||
SHOW_EXPR(( reconcilable_ref<Uber&,Unta&> ))
|
||||
SHOW_EXPR(( reconcilable_ref<Uber&,Unta> ))
|
||||
SHOW_EXPR(( reconcilable<Uber&,Unta> ))
|
||||
SHOW_EXPR(( reconcilable<Uber,Untr&> ))
|
||||
using TX3 = CommonResult_t<Uber&,Unta>;
|
||||
SHOW_TYPE(TX3)
|
||||
using TX4 = CommonResult_t<Unta&, const Uber&>;
|
||||
SHOW_TYPE(TX4)
|
||||
SHOW_EXPR(( reconcilable<Uber&,Unta&,Untr&> ))
|
||||
using TX5 = CommonResult_t<const Uber&,Unta&,Untr&>;
|
||||
SHOW_TYPE(TX5)
|
||||
using TX6 = CommonResult_t<const int&,uint16_t,int64_t>;
|
||||
SHOW_TYPE(TX6)
|
||||
SHOW_EXPR(( CommonResult<int,string>::value ))
|
||||
SHOW_EXPR(( CommonResult<int,string,long>::value ))
|
||||
SHOW_EXPR(( CommonResult<const int,int&>::value ))
|
||||
SHOW_EXPR(( reconcilable<const int&,uint16_t,int64_t> ))
|
||||
SHOW_EXPR(( reconcilable<const Uber&,Unta&,Untr&> ))
|
||||
SHOW_EXPR(( reconcilable<Uber&,const Unta&,Untr&> ))
|
||||
|
||||
cout << "\n.gulp." <<endl;
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -12,16 +12,16 @@ Import('env icons config')
|
|||
# define the source file/dirs comprising each artifact to be built.
|
||||
|
||||
lLib = env.SharedLibrary('lumierasupport', srcSubtree('lib'), install=True)
|
||||
lApp = env.SharedLibrary('lumieracommon', srcSubtree('common'), addLibs=lLib, install=True)
|
||||
lVault = env.SharedLibrary('lumieravault', srcSubtree('vault'), addLibs=lLib+lApp, install=True)
|
||||
lSteam = env.SharedLibrary('lumierasteam', srcSubtree('steam'), addLibs=lLib+lApp+lVault,install=True)
|
||||
lApp = env.SharedLibrary('lumieravessel', srcSubtree('vessel'),addLibs=lLib, install=True)
|
||||
lVault = env.SharedLibrary('lumieravault', srcSubtree('vault'), addLibs=lLib+lApp, install=True)
|
||||
lSteam = env.SharedLibrary('lumierasteam', srcSubtree('steam'), addLibs=lLib+lApp+lVault, install=True)
|
||||
|
||||
# in reverse dependency order
|
||||
core = lSteam+lVault+lApp+lLib # used to build the core application
|
||||
app_lib = lApp+lLib # use to link against the platform
|
||||
core_lib = core # use to link against the core application
|
||||
vault_lib = lVault+app_lib # use to link against the low-level backend
|
||||
support_lib = lLib # use to link against the support lib only
|
||||
vessel_lib = lApp+support_lib # use to link against the application-framework
|
||||
vault_lib = lVault+vessel_lib # use to link against the low-level backend
|
||||
core_lib = lSteam+vault_lib # use to link against the core application
|
||||
|
||||
lumiera = ( env.Program('lumiera', ['lumiera/main.cpp'] + core, install=True)
|
||||
+ config
|
||||
|
|
@ -32,6 +32,10 @@ lumiera = ( env.Program('lumiera', ['lumiera/main.cpp'] + core, install=True)
|
|||
env.SymLink('$DESTDIR/bin/lumiera',env.path.installExe+'lumiera','../lib/lumiera/lumiera')
|
||||
|
||||
|
||||
# Unit/Integration test support
|
||||
test_support = env.SharedLibrary('testsupport', srcSubtree('test'), addLibs=vessel_lib, install=False)
|
||||
|
||||
|
||||
# building Lumiera Plugins
|
||||
plugins = [] # currently none
|
||||
|
||||
|
|
@ -49,4 +53,4 @@ gui = ( guimodule
|
|||
)
|
||||
|
||||
|
||||
Export('lumiera core core_lib app_lib vault_lib support_lib plugins envGtk guimodule gui')
|
||||
Export('lumiera core core_lib vessel_lib vault_lib support_lib test_support plugins envGtk guimodule gui')
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
Common application services
|
||||
|
|
@ -122,7 +122,7 @@ extern "C" {
|
|||
#endif /* =========================== CL Interface ===================== */
|
||||
|
||||
|
||||
#include "common/interface.h"
|
||||
#include "vessel/interface.h"
|
||||
#include "lib/hash-value.h"
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
LIFECYCLE.h - interface for registering and triggering lifecycle callbacks
|
||||
LIFECYCLE.hpp - interface for registering and triggering lifecycle callbacks
|
||||
|
||||
Copyright (C)
|
||||
2008, Hermann Vosseler <Ichthyostega@web.de>
|
||||
|
|
@ -11,32 +11,30 @@
|
|||
|
||||
*/
|
||||
|
||||
/** @file lifecycle.h
|
||||
/** @file lifecycle.hpp
|
||||
** Installing and invoking of application lifecycle event callbacks.
|
||||
** This service is a facade for and implemented by the lumiera::LifecycleRegistry.
|
||||
** This service is a facade for and implemented by the vessel::LifecycleRegistry.
|
||||
** By placing a static LifecycleHook variable or by calling LifecycleHook::add,
|
||||
** a callback can be registered to be executed on a specific application lifecycle
|
||||
** event. Examples being #ON_BASIC_INIT, #ON_GLOBAL_INIT. Other subsystems may
|
||||
** register additional events for more specific purpose.
|
||||
**
|
||||
** @note the implementation resides in lib/lifecycle.cpp and gets linked into liblumiera.so
|
||||
** @see lumiera::AppState
|
||||
** @see vessel::Voyage
|
||||
** @see main.cpp
|
||||
*/
|
||||
|
||||
|
||||
#ifndef LUMIERA_LIFECYCLE_H
|
||||
#define LUMIERA_LIFECYCLE_H
|
||||
#ifndef VESSEL_LIFECYCLE_H
|
||||
#define VESSEL_LIFECYCLE_H
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
#include "lib/symbol.hpp"
|
||||
#include "lib/nocopy.hpp"
|
||||
|
||||
|
||||
|
||||
namespace lumiera {
|
||||
namespace vessel {
|
||||
|
||||
using lib::Symbol;
|
||||
|
||||
|
|
@ -81,21 +79,5 @@ namespace lumiera {
|
|||
|
||||
|
||||
|
||||
} // namespace lumiera
|
||||
|
||||
|
||||
|
||||
#else /* =========== C interface ====================== */
|
||||
|
||||
|
||||
//defined in liblumiera.so
|
||||
extern const char * lumiera_ON_BASIC_INIT;
|
||||
extern const char * lumiera_ON_GLOBAL_INIT;
|
||||
extern const char * lumiera_ON_GLOBAL_SHUTDOWN;
|
||||
|
||||
|
||||
void lumiera_LifecycleHook_add (const char* eventLabel, void callbackFun(void));
|
||||
void lumiera_Lifecycle_trigger (const char* eventLabel);
|
||||
|
||||
#endif
|
||||
#endif
|
||||
} // namespace vessel
|
||||
#endif /*VESSEL_LIFECYCLE_H*/
|
||||
|
|
@ -116,7 +116,7 @@ extern "C" {
|
|||
#endif /* =========================== CL Interface ===================== */
|
||||
|
||||
|
||||
#include "common/interface.h"
|
||||
#include "vessel/interface.h"
|
||||
|
||||
LUMIERA_INTERFACE_DECLARE (lumieraorg_SessionCommand, 0,
|
||||
LUMIERA_INTERFACE_SLOT (const char*, cycle, (const char*, const char*)),
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@
|
|||
|
||||
#include <cstddef>
|
||||
#include <utility>
|
||||
#include <memory>
|
||||
#include <list>
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@
|
|||
** @warning since these qualifiers manipulate by side-effect, it is up to the user to ensure
|
||||
** consistent settings and behaviour (typically within a terminal `build()` function).
|
||||
** @note the `qualify()` function accepts the qualifier functors by-reference; thus it is up
|
||||
** to the user to either pass them by-value, by reverence or even by perfect forwarding.
|
||||
** to the user to either pass them by-value, by reference or even by perfect forwarding.
|
||||
**
|
||||
** @see BuilderQualifierSupport_test
|
||||
** @see ElementBoxWidget as usage example
|
||||
|
|
|
|||
|
|
@ -376,7 +376,7 @@ namespace lib {
|
|||
using ResultVal = meta::_Fun<FUN>::Ret;
|
||||
using Subclass = meta::Strip<ResultVal>::TypePlain;
|
||||
|
||||
static_assert (std::is_pointer<ResultVal>::value,
|
||||
static_assert (std::is_pointer_v<ResultVal>,
|
||||
"Function must yield a pointer to a heap allocated instance");
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@
|
|||
using util::join;
|
||||
using boost::lexical_cast;
|
||||
using lib::time::TimeValue;
|
||||
using lib::transformIterator;
|
||||
using lib::transformIter;
|
||||
using util::almostEqual;
|
||||
using lib::hash::LuidH;
|
||||
|
||||
|
|
@ -297,9 +297,9 @@ namespace diff{
|
|||
|
||||
return (Rec::TYPE_NIL==rec.getType()? "" : rec.getType())
|
||||
+ "{"
|
||||
+ join (transformIterator (rec.attribs(), renderAttrib))
|
||||
+ join (transformIter (rec.attribs(), renderAttrib))
|
||||
+ (isnil(rec.scope())? "" : "|")
|
||||
+ join (transformIterator (rec.scope() , renderChild))
|
||||
+ join (transformIter (rec.scope() , renderChild))
|
||||
+ "}"
|
||||
;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -375,8 +375,6 @@ namespace diff{
|
|||
iterator end() const;
|
||||
|
||||
|
||||
using ChildDataIter = TransformIter<Rec::scopeIter, DataCap const&>;
|
||||
|
||||
/** visit the _data_ of nested child elements
|
||||
* @return an iterator over the DataCap elements of all children,
|
||||
* in case this GenNode actually holds a Record.
|
||||
|
|
@ -384,26 +382,26 @@ namespace diff{
|
|||
* @note this iterator visits _only_ the children, which are
|
||||
* by definition unnamed. It does _not_ visit attributes.
|
||||
*/
|
||||
friend ChildDataIter
|
||||
friend auto
|
||||
childData (GenNode const& n)
|
||||
{
|
||||
return ChildDataIter{ n.data.childIter()
|
||||
, [](GenNode const& child) ->DataCap const&
|
||||
return transformIter(n.data.childIter()
|
||||
,[](GenNode const& child) ->DataCap const&
|
||||
{
|
||||
return child.data;
|
||||
}
|
||||
};
|
||||
);
|
||||
}
|
||||
|
||||
friend ChildDataIter
|
||||
friend auto
|
||||
childData (Rec::scopeIter&& scopeIter)
|
||||
{
|
||||
return ChildDataIter{ std::forward<Rec::scopeIter>(scopeIter)
|
||||
, [](GenNode const& child) ->DataCap const&
|
||||
return transformIter(std::forward<Rec::scopeIter>(scopeIter)
|
||||
,[](GenNode const& child) ->DataCap const&
|
||||
{
|
||||
return child.data;
|
||||
}
|
||||
};
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -776,7 +774,7 @@ namespace diff{
|
|||
inline std::optional<X>
|
||||
DataCap::retrieveAttribute (string key) const
|
||||
{
|
||||
static_assert (not std::is_reference_v<X>
|
||||
static_assert (not meta::isRef_v<X>
|
||||
,"optional access only possible by value");
|
||||
|
||||
Rec* nested = unConst(this)->maybeAccessNestedRec();
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ namespace diff{
|
|||
using std::__and_;
|
||||
using std::__not_;
|
||||
using lib::meta::enable_if;
|
||||
using lib::meta::can_IterForEach;
|
||||
using lib::meta::can_LumieraIter;
|
||||
using lib::meta::can_STL_ForEach;
|
||||
|
||||
using DiffStep = TreeDiffLanguage::DiffStep;
|
||||
|
|
@ -168,7 +168,7 @@ namespace diff{
|
|||
* @note source iterator is copied into a heap allocated IterSource
|
||||
*/
|
||||
template<class IT>
|
||||
MutationMessage(IT&& ii, enable_if< can_IterForEach<IT>, void*> =nullptr)
|
||||
MutationMessage(IT&& ii, enable_if< can_LumieraIter<IT>, void*> =nullptr)
|
||||
: _FrontEnd{iter_source::wrapIter (std::forward<IT>(ii))}
|
||||
{ }
|
||||
|
||||
|
|
@ -179,7 +179,7 @@ namespace diff{
|
|||
*/
|
||||
template<class CON>
|
||||
MutationMessage(CON& container, enable_if< __and_< can_STL_ForEach<CON>
|
||||
,__not_<can_IterForEach<CON>>>, void*> =nullptr)
|
||||
,__not_<can_LumieraIter<CON>>>, void*> =nullptr)
|
||||
: _FrontEnd{iter_source::eachEntry(container)}
|
||||
{ }
|
||||
|
||||
|
|
|
|||
|
|
@ -308,18 +308,16 @@ namespace diff{
|
|||
|
||||
using iterator = IterAdapter<ElmIter, const Record*>;
|
||||
using scopeIter = iter_stl::_SeqT<const Storage>::Range;
|
||||
using keyIter = TransformIter<scopeIter, string>;
|
||||
using valIter = TransformIter<scopeIter, Access>;
|
||||
|
||||
/** default iteration exposes all data within this "object", starting with the attributes */
|
||||
iterator begin () const { return iterator(this, attribs_.empty()? children_.begin() : attribs_.begin()); }
|
||||
iterator end () const { return iterator(); }
|
||||
|
||||
scopeIter attribs() const { return iter_stl::eachElm(attribs_); }
|
||||
scopeIter scope() const { return iter_stl::eachElm(children_); }
|
||||
scopeIter attribs() const { return iter_stl::eachElm (attribs_); }
|
||||
scopeIter scope() const { return iter_stl::eachElm (children_); }
|
||||
|
||||
keyIter keys() const { return transformIterator(attribs(), extractKey); }
|
||||
valIter vals() const { return transformIterator(attribs(), extractVal); }
|
||||
auto keys() const { return transformIter (attribs(), extractKey); }
|
||||
auto vals() const { return transformIter (attribs(), extractVal); }
|
||||
|
||||
protected: /* ==== API for the IterAdapter ==== */
|
||||
|
||||
|
|
@ -792,11 +790,11 @@ namespace diff{
|
|||
Record<VAL>::operator std::string() const
|
||||
{
|
||||
using util::join;
|
||||
using lib::transformIterator;
|
||||
using lib::transformIter;
|
||||
|
||||
return "Rec("
|
||||
+ (TYPE_NIL==type_? "" : type_)
|
||||
+ (isnil(this->attribs())? "" : "| "+join (transformIterator (this->attribs(), renderAttribute))+" ")
|
||||
+ (isnil(this->attribs())? "" : "| "+join (transformIter (this->attribs(), renderAttribute))+" ")
|
||||
+ (isnil(this->scope())? "" : "|{"+join (this->scope())+"}")
|
||||
+ ")"
|
||||
;
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@
|
|||
#include "lib/idi/genfunc.hpp"
|
||||
#include "lib/format-string.hpp"
|
||||
#include "lib/format-util.hpp"
|
||||
#include "lib/test/event-log.hpp"
|
||||
#include "test/event-log.hpp"
|
||||
#include "lib/nocopy.hpp"
|
||||
#include "lib/util.hpp"
|
||||
|
||||
|
|
@ -58,8 +58,8 @@ namespace diff{
|
|||
namespace error = lumiera::error;
|
||||
|
||||
using lib::Literal;
|
||||
using lib::test::EventLog;
|
||||
using lib::test::EventMatch;
|
||||
using test::EventLog;
|
||||
using test::EventMatch;
|
||||
using iter_stl::eachElm;
|
||||
using util::unConst;
|
||||
using util::isnil;
|
||||
|
|
@ -110,12 +110,12 @@ namespace diff{
|
|||
renderRecord (Rec const& record)
|
||||
{
|
||||
using util::join;
|
||||
using lib::transformIterator;
|
||||
using lib::transformIter;
|
||||
|
||||
return "Rec("
|
||||
+ (Rec::TYPE_NIL==record.getType()? "" : record.getType())
|
||||
+ (isnil(record.attribs())? "" : "| "+join (transformIterator (record.attribs(), renderAttribute))+" ")
|
||||
+ (isnil(record.scope())? "" : "|{"+join (transformIterator (record.scope(), renderChild))+"}")
|
||||
+ (isnil(record.attribs())? "" : "| "+join (transformIter (record.attribs(), renderAttribute))+" ")
|
||||
+ (isnil(record.scope())? "" : "|{"+join (transformIter (record.scope(), renderChild))+"}")
|
||||
+ ")"
|
||||
;
|
||||
}
|
||||
|
|
@ -289,14 +289,14 @@ namespace diff{
|
|||
string
|
||||
showContent () const
|
||||
{
|
||||
return join (transformIterator (begin(), renderNode));
|
||||
return join (transformIter (begin(), renderNode));
|
||||
}
|
||||
|
||||
/** render elements waiting in source buffer to be accepted */
|
||||
string
|
||||
showSrcBuffer () const
|
||||
{
|
||||
return join (transformIterator (eachElm(prev_content_), renderNode));
|
||||
return join (transformIter (eachElm(prev_content_), renderNode));
|
||||
}
|
||||
|
||||
EventMatch
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@
|
|||
#include "lib/error.hpp"
|
||||
#include "lib/nocopy.hpp"
|
||||
#include "lib/meta/trait.hpp"
|
||||
#include "lib/meta/function.hpp"
|
||||
#include "lib/diff/gen-node.hpp"
|
||||
#include "lib/diff/tree-mutator.hpp"
|
||||
#include "lib/iter-adapter-stl.hpp"
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@
|
|||
|
||||
|
||||
#include "lib/error.hpp"
|
||||
#include "include/lifecycle.h"
|
||||
#include "include/lifecycle.hpp"
|
||||
#include "lib/meta/util.hpp"
|
||||
#include "lib/util.hpp"
|
||||
|
||||
|
|
@ -154,6 +154,9 @@ namespace lumiera {
|
|||
|
||||
namespace error {
|
||||
namespace {
|
||||
using vessel::LifecycleHook;
|
||||
using vessel::ON_BASIC_INIT;
|
||||
|
||||
void install_unexpectedException_handler ()
|
||||
{
|
||||
std::set_terminate (lumiera_unexpectedException);
|
||||
|
|
|
|||
|
|
@ -197,14 +197,15 @@ apologies for that."
|
|||
"|(\\w+::)+\\(anonymous namespace\\)::"
|
||||
"|lib::meta::"
|
||||
"|lib::time::"
|
||||
"|lib::test::"
|
||||
"|lib::diff::"
|
||||
"|lib::"
|
||||
"|util::"
|
||||
"|\\btest::"
|
||||
"|steam::(asset::|mobject::(session::)?|play::)?"
|
||||
"|stage::model"
|
||||
"|stage::ctrl"
|
||||
"|lumiera::"
|
||||
"|vessel::"
|
||||
, regex::ECMAScript | regex::optimize};
|
||||
|
||||
static regex lolong {"long long"
|
||||
|
|
|
|||
|
|
@ -100,6 +100,119 @@ namespace util {
|
|||
|
||||
|
||||
|
||||
|
||||
namespace { // helper for printing type diagnostics
|
||||
|
||||
template<typename X>
|
||||
struct TypeDiagnostics
|
||||
{
|
||||
using Type = X;
|
||||
static constexpr auto prefix = "";
|
||||
static constexpr auto postfix = "";
|
||||
};
|
||||
template<typename X>
|
||||
struct TypeDiagnostics<const X>
|
||||
{
|
||||
using Type = X;
|
||||
static constexpr auto prefix = "const ";
|
||||
static constexpr auto postfix = "";
|
||||
};
|
||||
template<typename X>
|
||||
struct TypeDiagnostics<X&>
|
||||
{
|
||||
using Type = X;
|
||||
static constexpr auto prefix = "";
|
||||
static constexpr auto postfix = "&";
|
||||
};
|
||||
template<typename X>
|
||||
struct TypeDiagnostics<X&&>
|
||||
{
|
||||
using Type = X;
|
||||
static constexpr auto prefix = "";
|
||||
static constexpr auto postfix = " &&";
|
||||
};
|
||||
template<typename X>
|
||||
struct TypeDiagnostics<X const&>
|
||||
{
|
||||
using Type = X;
|
||||
static constexpr auto prefix = "";
|
||||
static constexpr auto postfix = " const&";
|
||||
};
|
||||
template<typename X>
|
||||
struct TypeDiagnostics<X const&&>
|
||||
{
|
||||
using Type = X;
|
||||
static constexpr auto prefix = "const ";
|
||||
static constexpr auto postfix = " &&";
|
||||
};
|
||||
template<typename X>
|
||||
struct TypeDiagnostics<X *>
|
||||
{
|
||||
using Type = X;
|
||||
static constexpr auto prefix = "";
|
||||
static constexpr auto postfix = " *";
|
||||
};
|
||||
template<typename X>
|
||||
struct TypeDiagnostics<const X *>
|
||||
{
|
||||
using Type = X;
|
||||
static constexpr auto prefix = "const ";
|
||||
static constexpr auto postfix = " *";
|
||||
};
|
||||
template<typename X>
|
||||
struct TypeDiagnostics<const X * const>
|
||||
{
|
||||
using Type = X;
|
||||
static constexpr auto prefix = "const ";
|
||||
static constexpr auto postfix = " * const";
|
||||
};
|
||||
template<typename X>
|
||||
struct TypeDiagnostics<X * const>
|
||||
{
|
||||
using Type = X;
|
||||
static constexpr auto prefix = "";
|
||||
static constexpr auto postfix = " * const";
|
||||
};
|
||||
template<typename X>
|
||||
struct TypeDiagnostics<X * const *>
|
||||
{
|
||||
using Type = X;
|
||||
static constexpr auto prefix = "";
|
||||
static constexpr auto postfix = " * const *";
|
||||
};
|
||||
}//(End)decoration helper
|
||||
|
||||
/** 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 test::TypeDebugger<T> to reveal the type as the compiler sees it.
|
||||
* @remarks the function lib::meta::typeStr was deliberately written such as to remove type adornments
|
||||
* and does not work on all kinds of reference. This helper complements this and
|
||||
* attempts to preserve const-ness, pointer and reference decoration.
|
||||
*/
|
||||
template<typename X>
|
||||
inline std::string
|
||||
showType()
|
||||
{
|
||||
using Case = TypeDiagnostics<X>;
|
||||
using Type = Case::Type;
|
||||
|
||||
return Case::prefix
|
||||
+ lib::meta::humanReadableTypeID (typeid(Type).name())
|
||||
+ Case::postfix;
|
||||
}
|
||||
|
||||
template<typename...TS>
|
||||
inline std::string
|
||||
showTypes()
|
||||
{
|
||||
return "<| " + ((showType<TS>()+", ") + ... + "|>");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
namespace {
|
||||
/** toggle to prefer specialisation with direct lexical conversion */
|
||||
template<typename X>
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@
|
|||
|
||||
namespace util {
|
||||
|
||||
using lib::meta::can_IterForEach;
|
||||
using lib::meta::lum_iter;
|
||||
using std::string;
|
||||
using std::forward;
|
||||
using std::move;
|
||||
|
|
@ -135,13 +135,13 @@ namespace util {
|
|||
{
|
||||
using Val = lib::meta::ValueTypeBinding<IT>::value_type;
|
||||
|
||||
return lib::transformIterator(forward<IT>(src), util::toString<Val>);
|
||||
return lib::transformIter (forward<IT>(src), util::toString<Val>);
|
||||
}
|
||||
|
||||
|
||||
|
||||
namespace { // helper to build range iterator on demand
|
||||
template<class CON, typename TOGGLE = void>
|
||||
template<class CON>
|
||||
struct _RangeIter
|
||||
{
|
||||
using StlIter = CON::const_iterator;
|
||||
|
|
@ -153,8 +153,8 @@ namespace util {
|
|||
{ }
|
||||
};
|
||||
|
||||
template<class IT>
|
||||
struct _RangeIter<IT, lib::meta::enable_if< can_IterForEach<IT>> >
|
||||
template<lum_iter IT>
|
||||
struct _RangeIter<IT>
|
||||
{
|
||||
IT iter;
|
||||
|
||||
|
|
|
|||
|
|
@ -44,9 +44,11 @@
|
|||
** Please ensure it is always included _before_ the latter. Failing to do so will result
|
||||
** in mysterious failures.
|
||||
**
|
||||
** @todo 4/2014 doesn't work as expected. My suspicion is that in the actual use case (PlacementIndex),
|
||||
** the type providing the hasher is mixed in through inheritance, and the template specialisation
|
||||
** for this base type is not considered on lookup. ///////TICKET #722
|
||||
** @todo 4/2026 this shady trickery works without problems since many years now,
|
||||
** while it is not clear if we actually need to do these horrible things any more.
|
||||
** Even while we may retain that automatic bridge to boost-functional hash, doing so
|
||||
** should be possible with the current STDLIB without having to patch and rewire
|
||||
** the library implementation. //////////////////////////////////////////////////////////////////////TICKET #1422 : re-assess the situation !
|
||||
**
|
||||
** @see HashIndexed
|
||||
** @see LUID
|
||||
|
|
@ -106,10 +108,6 @@ namespace std {
|
|||
{
|
||||
static_assert (sizeof(TY) < 0, "No hash implementation found. "
|
||||
"Either specialise std::hash or provide a boost-style hash_value via ADL.");
|
||||
|
||||
// the default provides *no* hash implementation
|
||||
// and adds a marker type for metaprogramming
|
||||
typedef int NotHashable;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -129,8 +127,8 @@ namespace std {
|
|||
|
||||
/**
|
||||
* Primary class template for std::hash.
|
||||
* We provide no default implementation, but a marker type
|
||||
* to allow detection of custom implementation through metaprogramming
|
||||
* We provide no default implementation, but bridge automatically
|
||||
* when a boost-style hash function can be picked up via ADL.
|
||||
*
|
||||
* @note this definition has been \em hijacked by Lumiera
|
||||
* to add an automatic bridge to use boost::hash functions
|
||||
|
|
@ -140,12 +138,14 @@ namespace std {
|
|||
: public _HashImplementationSelector<TY>
|
||||
{ };
|
||||
|
||||
}
|
||||
}// namespace std
|
||||
|
||||
|
||||
|
||||
|
||||
/* ==== Evil Evil ==== */
|
||||
|
||||
|
||||
/* ==== Evil Evil ==== */ ////////////////////////////////////////////////////////////////TICKET #1422 : very likely we do not need this manipulation any more !!!
|
||||
|
||||
#define hash hash_HIDDEN
|
||||
#define _Hash_impl _Hash_impl_HIDDEN
|
||||
|
|
@ -204,6 +204,12 @@ namespace std {
|
|||
STD_HASH_IMPL (long double)
|
||||
|
||||
#undef STD_HASH_IMPL
|
||||
}
|
||||
|
||||
/** rewire the partial specialisation for pointer types */
|
||||
template<typename TY>
|
||||
struct hash<TY*> : public hash_HIDDEN<TY*> { };
|
||||
|
||||
|
||||
}// namespace std
|
||||
|
||||
#endif /*LIB_HASH_STANDARD_H*/
|
||||
|
|
|
|||
|
|
@ -50,8 +50,8 @@ namespace lib {
|
|||
|
||||
using ResVal = decltype(data_->operator[](0));
|
||||
|
||||
using value_type = meta::RefTraits<ResVal>::Value;
|
||||
using reference = meta::RefTraits<ResVal>::Reference;
|
||||
using value_type = meta::RefTraits<ResVal>::value_type;
|
||||
using reference = meta::RefTraits<ResVal>::reference;
|
||||
|
||||
bool
|
||||
checkPoint() const
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@
|
|||
** this to the pattern of _type erasure_, where we discard specific
|
||||
** knowledge after construction; for _such_ cases, opaque-holder.hpp
|
||||
** might be a better fit.
|
||||
** @see lib::TransformIter
|
||||
** @see lib::iter::Transform
|
||||
** @see lib::explore
|
||||
**
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ namespace lib {
|
|||
public:
|
||||
/** this iterator adapter is meant to wrap an iterator yielding pointer values */
|
||||
using pointer = meta::ValueTypeBinding<IT>::value_type;
|
||||
static_assert(std::is_pointer_v<pointer>);
|
||||
static_assert(is_pointer_v<pointer>);
|
||||
|
||||
using value_type = std::remove_pointer_t<pointer>;
|
||||
using reference = value_type&;
|
||||
|
|
@ -74,8 +74,8 @@ namespace lib {
|
|||
|
||||
using ValueTypeBase = std::remove_const_t<value_type>; // value_type without const
|
||||
|
||||
using WrappedIterType = IterType<IT>::template SimilarIter< ValueTypeBase* * >::Type;
|
||||
using WrappedConstIterType = IterType<IT>::template SimilarIter<const ValueTypeBase* * >::Type;
|
||||
using WrappedIterType = IterRebind<IT>::template SimilarIter< ValueTypeBase* * >::Type;
|
||||
using WrappedConstIterType = IterRebind<IT>::template SimilarIter<const ValueTypeBase* * >::Type;
|
||||
|
||||
using IterType = PtrDerefIter<WrappedIterType>;
|
||||
using ConstIterType = PtrDerefIter<WrappedConstIterType>;
|
||||
|
|
|
|||
|
|
@ -90,15 +90,18 @@ namespace iter_stl {
|
|||
|
||||
|
||||
/**
|
||||
* helper baseclass to simplify
|
||||
* defining customised wrapped STL iterators
|
||||
* @internal helper baseclass to simplify defining customised wrapped STL iterators.
|
||||
* @remark this is meant to be compliant to a STL input-iterator;
|
||||
* it's _not_ a »Lumiera Forward Iterator« (lacks the bool check for iteration end)
|
||||
* @warning minimal definition, since it is only intended to be wrapped into lib::RangeIter
|
||||
*/
|
||||
template<typename DEF>
|
||||
struct WrappedStlIter : DEF
|
||||
{
|
||||
using Iter = DEF::Iter;
|
||||
using reference = DEF::reference;
|
||||
using pointer = DEF::pointer;
|
||||
using Iter = DEF::Iter;
|
||||
using value_type = DEF::value_type;
|
||||
using reference = DEF::reference;
|
||||
using pointer = DEF::pointer;
|
||||
|
||||
|
||||
WrappedStlIter() : i_() { }
|
||||
|
|
@ -112,6 +115,10 @@ namespace iter_stl {
|
|||
friend bool operator== (WrappedStlIter const& i1, WrappedStlIter const& i2) { return i1.i_ == i2.i_; }
|
||||
friend bool operator!= (WrappedStlIter const& i1, WrappedStlIter const& i2) { return i1.i_ != i2.i_; }
|
||||
|
||||
/** @note the following dummy definitions are required to fulfil the concept std::input_iterator */
|
||||
WrappedStlIter& operator++(int) { NOTREACHED("never used as STL iterator"); return *this; }
|
||||
using difference_type = ptrdiff_t;
|
||||
|
||||
private:
|
||||
mutable Iter i_;
|
||||
};
|
||||
|
|
@ -424,11 +431,14 @@ namespace iter_stl {
|
|||
/** create empty snapshot */
|
||||
IterSnapshot() { }
|
||||
|
||||
// standard copy operations acceptable
|
||||
|
||||
|
||||
/** take snapshot by discharging the given Lumiera Forward iterator
|
||||
* @warning depending on the implementation backing the source iterator,
|
||||
* this might or might not yield side-effects.
|
||||
*/
|
||||
template<class IT>
|
||||
template<class IT, typename =meta::disable_if_self<IterSnapshot, IT>>
|
||||
IterSnapshot (IT&& src)
|
||||
{
|
||||
for ( ; src; ++src)
|
||||
|
|
@ -439,7 +449,7 @@ namespace iter_stl {
|
|||
* @warning depending on the implementation backing the source iterator,
|
||||
* this might or might not yield side-effects.
|
||||
*/
|
||||
template<class IT>
|
||||
template<class IT, typename =meta::disable_if_self<IterSnapshot, IT>>
|
||||
IterSnapshot (IT const& src)
|
||||
{
|
||||
for (IT copy{src}; copy; ++copy)
|
||||
|
|
@ -462,11 +472,6 @@ namespace iter_stl {
|
|||
buffer_.emplace_back (*pos);
|
||||
}
|
||||
|
||||
IterSnapshot(IterSnapshot &&) = default;
|
||||
IterSnapshot(IterSnapshot const&) = default;
|
||||
IterSnapshot& operator= (IterSnapshot const&) = default;
|
||||
IterSnapshot& operator= (IterSnapshot &&) = default;
|
||||
|
||||
explicit
|
||||
operator bool() const
|
||||
{
|
||||
|
|
|
|||
|
|
@ -30,7 +30,8 @@
|
|||
** - very similar is IterableDecorator, but this time directly as
|
||||
** decorator to inherit from the »state core«, and without checks.
|
||||
** - the RangeIter allows just to expose a range of elements defined
|
||||
** by a STL-like pair of "start" and "end" iterators
|
||||
** by a STL-like pair of "start" and "end" iterators; thus it
|
||||
** serves a bridge between STL iterators and Lumiera iterators.
|
||||
**
|
||||
** Some more specific use cases are provided in the extension header
|
||||
** iter-adapter-ptr-deref.hpp
|
||||
|
|
@ -62,14 +63,14 @@
|
|||
** Similar to the STL, instead of using a common "Iterator" base class,
|
||||
** we rather define a common set of functions and behaviour which can be
|
||||
** expected from any such iterator. These rules are similar to STL's
|
||||
** "forward iterator", with the addition of an bool check to detect
|
||||
** iteration end. The latter is inspired by the \c hasNext() function
|
||||
** "input iterator", with the addition of an bool check to detect
|
||||
** iteration end. The latter is inspired by the `hasNext()` function
|
||||
** found in many current languages supporting iterators. However, by
|
||||
** inspiration from functional programming, we deliberately do not
|
||||
** support the various extended iterator concepts from STL and boost
|
||||
** drawing from insights promoted in functional programming, deliberately
|
||||
** we do not support the various extended iterator concepts from the STL
|
||||
** (random access iterators, output iterators, arithmetics, difference
|
||||
** between iterators and the like). According to this concept,
|
||||
** _an iterator is a promise for pulling values once,_
|
||||
** between iterators and the like). According to this concept proposed
|
||||
** here, _an iterator is a promise for pulling values once,_
|
||||
** and nothing beyond that.
|
||||
**
|
||||
** Notably,
|
||||
|
|
@ -87,10 +88,48 @@
|
|||
** Conceptually, a Lumiera Iterator represents a lazy stream of calculations
|
||||
** rather than a target value considered to be »within« a container. And while
|
||||
** the result is in may cases deliberately _exposed as a reference,_ in order
|
||||
** to keep the door open for special-case manipulations, for the typical usage
|
||||
** it is _discouraged_ to assume anything about the source, beyond the limited
|
||||
** access to some transient state as exposed during active iteration. Together,
|
||||
** these rules enable a _loose coupling_ to the source of data.
|
||||
** to allow for tightly coupled implementation in special cases -- for the
|
||||
** typical usage it is _discouraged_ to assume anything about the source,
|
||||
** beyond the limited access to some transient state as exposed during
|
||||
** active iteration. Together, these rules enable a _loose coupling_
|
||||
** to the source of data.
|
||||
**
|
||||
** ## Usage
|
||||
**
|
||||
** All the building blocks defined her provide the bridge definitions to allow
|
||||
** using a »Lumiera Forward Iterator« in common language constructs: It can be
|
||||
** used directly in for-each loops (notably this implies that a Lumiera iterator
|
||||
** is used in the same position where usually the container would be referred).
|
||||
** Yet, beyond that, Lumiera iterators can also be used in normal `for` loops
|
||||
** and even in while loops, due to the bool-check to detect iteration end.
|
||||
** A default constructed Lumiera iterator is always in _invalid state_
|
||||
** and can be used as _sentinel value_ like the `end()` iterator.
|
||||
** Yet, while Lumiera iterators can be copied and assigned, the effect of
|
||||
** these operations is implementation defined; in some cases, taking a copy
|
||||
** may allow to create a snapshot, while in other cases (especially when the
|
||||
** Lumiera iterator uses an reference to tap into some remote service) all
|
||||
** copies will refer to the same shared state. The rationale for this design
|
||||
** was to simplify both building and using Lumiera Iterators; they are meant
|
||||
** as an implementation tool, and not for creating external interfaces.
|
||||
**
|
||||
** However, there is a special provision for the latter use case, in the form
|
||||
** of the lib::IterSource interface and framework; an `IterSource` embeds a
|
||||
** classic OO interface with virtual methods, and thus allows to abstract the
|
||||
** backing implementation entirely away.
|
||||
**
|
||||
** ## Concepts and Traits
|
||||
**
|
||||
** The »Lumiera Forward Iterators« do not map directly to any specification from
|
||||
** the C++ standard. The concept std::input_iterator comes close, but a Lumiera
|
||||
** iterator can not fulfil this concept, since it lacks the postfix increment.
|
||||
** In the opposite direction, there is also no subsumption possible, since a
|
||||
** STL iterator does not offer a bool-check to detect iteration end.
|
||||
**
|
||||
** Notably the header `lib/meta/trait.hpp` defines some detector traits, allowing
|
||||
** to recognise if something is an iterable STL container, or a Lumiera iterator.
|
||||
** Furthermore, lib::iter::Trait defines a transparent mechanism to extract the
|
||||
** basic type definitions (`reference` ≙ what the iterator yields and
|
||||
** `value_type` ≙ what to use to store results from an iterator)
|
||||
**
|
||||
** @see iter-adapter-test.cpp
|
||||
** @see itertools.hpp
|
||||
|
|
@ -105,6 +144,7 @@
|
|||
|
||||
|
||||
#include "lib/error.hpp"
|
||||
#include "lib/nocopy.hpp"
|
||||
#include "lib/meta/value-type-binding.hpp"
|
||||
|
||||
#include <iterator>
|
||||
|
|
@ -120,6 +160,9 @@ namespace util { // see lib/util.hpp
|
|||
|
||||
namespace lib {
|
||||
|
||||
using std::is_pointer_v;
|
||||
using std::remove_pointer_t;
|
||||
using std::remove_reference_t;
|
||||
|
||||
namespace { // internal helpers
|
||||
inline void
|
||||
|
|
@ -149,7 +192,14 @@ namespace lib {
|
|||
|
||||
|
||||
namespace iter {
|
||||
/** type binding helper: an iterato's actual result type */
|
||||
using meta::is_StateCore;
|
||||
using meta::can_LumieraIter;
|
||||
using meta::can_STL_ForEach;
|
||||
using meta::ValueTypeBinding;
|
||||
using meta::RefTraits;
|
||||
|
||||
|
||||
/** type binding helper: an iterator's actual result type */
|
||||
template<class IT>
|
||||
using Yield = decltype(std::declval<IT>().operator*());
|
||||
|
||||
|
|
@ -157,6 +207,35 @@ namespace lib {
|
|||
template<class COR>
|
||||
using CoreYield = decltype(std::declval<COR>().yield());
|
||||
|
||||
|
||||
/** some legacy STL type iterators do not strictly fulfil all requirements,
|
||||
* yet were marked by a specialisation of `std::iterator_traits` */
|
||||
template<class IT>
|
||||
concept legacy_traits_marked = requires
|
||||
{
|
||||
typename std::iterator_traits<IT>::iterator_category;
|
||||
};
|
||||
|
||||
|
||||
/** Iterator-Traits: definitions to level the difference between
|
||||
* Lumiera-Iterators and the STL usage of ranges / iterators.
|
||||
*/
|
||||
template<class IT>
|
||||
struct Trait
|
||||
{
|
||||
static constexpr bool is_LumieraIter = meta::is_LumieraIter_v<IT>;
|
||||
static constexpr bool is_STLIter = std::input_iterator<IT>;
|
||||
static constexpr bool is_Legacy = legacy_traits_marked<IT>;
|
||||
|
||||
static_assert(is_LumieraIter or is_STLIter or is_Legacy
|
||||
,"source type must be an iterator (either Lumiera or STL");
|
||||
|
||||
using _ValTrait = std::conditional_t<is_LumieraIter, ValueTypeBinding<IT>
|
||||
, RefTraits<std::iter_reference_t<IT>>>;
|
||||
using value_type = _ValTrait::value_type;
|
||||
using reference = _ValTrait::reference;
|
||||
using pointer = _ValTrait::pointer;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -302,7 +381,7 @@ namespace lib {
|
|||
|
||||
|
||||
protected:
|
||||
using ConRef = meta::RefTraits<CON>::Reference;
|
||||
using ConRef = meta::RefTraits<CON>::reference;
|
||||
|
||||
/** allow derived classes to access backing container */
|
||||
ConRef source() { return source_; }
|
||||
|
|
@ -370,15 +449,15 @@ namespace lib {
|
|||
* @see iter-explorer-test.hpp
|
||||
* @see iter-adaptor-test.cpp
|
||||
*/
|
||||
template<class ST, typename T =iter::CoreYield<ST>>
|
||||
template<class ST, typename RES =iter::CoreYield<ST>>
|
||||
class IterStateWrapper
|
||||
{
|
||||
ST core_;
|
||||
|
||||
public:
|
||||
using value_type = meta::RefTraits<T>::Value;
|
||||
using reference = meta::RefTraits<T>::Reference;
|
||||
using pointer = meta::RefTraits<T>::Pointer;
|
||||
using value_type = meta::RefTraits<RES>::value_type;
|
||||
using reference = meta::RefTraits<RES>::reference;
|
||||
using pointer = meta::RefTraits<RES>::pointer;
|
||||
|
||||
IterStateWrapper (ST&& initialState)
|
||||
: core_(std::forward<ST>(initialState))
|
||||
|
|
@ -399,9 +478,9 @@ namespace lib {
|
|||
}
|
||||
|
||||
|
||||
/* === lumiera forward iterator concept === */
|
||||
/* === Lumiera Forward Iterator concept === */
|
||||
|
||||
T
|
||||
RES
|
||||
operator*() const
|
||||
{
|
||||
__throw_if_empty();
|
||||
|
|
@ -411,13 +490,13 @@ namespace lib {
|
|||
pointer
|
||||
operator->() const
|
||||
{
|
||||
if constexpr (meta::isLRef_v<T>)
|
||||
if constexpr (meta::isLRef_v<RES>)
|
||||
{
|
||||
__throw_if_empty();
|
||||
return & core_.yield(); // core interface: yield
|
||||
}
|
||||
else
|
||||
static_assert (!sizeof(T),
|
||||
static_assert (!sizeof(RES),
|
||||
"can not provide operator-> "
|
||||
"since iterator pipeline generates a value");
|
||||
}
|
||||
|
|
@ -500,7 +579,7 @@ namespace lib {
|
|||
class IterStateCore
|
||||
: public IT
|
||||
{
|
||||
static_assert (lib::meta::can_IterForEach<IT>::value
|
||||
static_assert (iter::can_LumieraIter<IT>::value
|
||||
,"Lumiera Iterator required as source");
|
||||
protected:
|
||||
IT&
|
||||
|
|
@ -545,7 +624,7 @@ namespace lib {
|
|||
class CheckedCore
|
||||
: public COR
|
||||
{
|
||||
static_assert (lib::meta::is_StateCore<COR>::value
|
||||
static_assert (iter::is_StateCore<COR>::value
|
||||
,"Adapted type must expose a »state core« API");
|
||||
protected:
|
||||
COR&
|
||||
|
|
@ -608,6 +687,7 @@ namespace lib {
|
|||
* @warning be sure to understand the implications of this setup
|
||||
* - when initialised by reference, the container's contents will be copied
|
||||
* - when move-initialised, the container will be destroyed with this iterator
|
||||
* - when moved / swapped during iteration, the iterator is invalidated (-> data corruption)
|
||||
* - the container API remains visible (baseclass), which could confuse trait detection
|
||||
*/
|
||||
template<class CON>
|
||||
|
|
@ -649,6 +729,67 @@ namespace lib {
|
|||
|
||||
|
||||
|
||||
template<class CON>
|
||||
concept subscriptable = requires (CON& con, size_t idx)
|
||||
{
|
||||
typename CON::reference;
|
||||
{ con[idx] } -> std::convertible_to<typename CON::reference>;
|
||||
{ con.size() } -> std::convertible_to<size_t>;
|
||||
};
|
||||
|
||||
template<class CON>
|
||||
using is_subscriptable = std::bool_constant<subscriptable<remove_reference_t<CON>>>;
|
||||
|
||||
/**
|
||||
* Adapter to package some container store inline and access it bey sequenced subscript.
|
||||
* This setup works similar to the ContainerCore, yet has the benefit of being movable.
|
||||
* @warning using any kind of inline storage in the context of IterExplorer is dangerous.
|
||||
* Anything beyond the actual iteration state and especially an extended data storage
|
||||
* might lead to surprising and harmful behaviour, due to the contradiction to the
|
||||
* common mental image associated with an iterator. These dangers are amplified
|
||||
* when such embedded data is transformed, filtered or modified by reference.
|
||||
*/
|
||||
template<subscriptable CON>
|
||||
class IdxStoreCore
|
||||
: public CON
|
||||
{
|
||||
size_t i_;
|
||||
|
||||
public:
|
||||
IdxStoreCore (CON&& container)
|
||||
: CON(std::forward<CON> (container))
|
||||
, i_{0}
|
||||
{ }
|
||||
|
||||
IdxStoreCore()
|
||||
: CON{}
|
||||
, i_{CON::size()}
|
||||
{ } // explicitly ensure that iterator is empty
|
||||
|
||||
// copy and assignment acceptable (warning!)
|
||||
|
||||
|
||||
/* === »state core« protocol API === */
|
||||
bool
|
||||
checkPoint() const
|
||||
{
|
||||
return i_ < CON::size();
|
||||
}
|
||||
|
||||
decltype(auto)
|
||||
yield() const
|
||||
{
|
||||
return util::unConst(*this).operator[] (i_);
|
||||
} // rationale: const iterator ≠ const_iterator
|
||||
|
||||
void
|
||||
iterNext()
|
||||
{
|
||||
++i_;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Decorator-Adapter to make a »*State Core*« iterable as Lumiera Forward Iterator.
|
||||
|
|
@ -694,9 +835,9 @@ namespace lib {
|
|||
|
||||
public:
|
||||
using YieldRes = iter::CoreYield<COR>;
|
||||
using value_type = meta::RefTraits<YieldRes>::Value;
|
||||
using reference = meta::RefTraits<YieldRes>::Reference;
|
||||
using pointer = meta::RefTraits<YieldRes>::Pointer;
|
||||
using value_type = meta::RefTraits<YieldRes>::value_type;
|
||||
using reference = meta::RefTraits<YieldRes>::reference;
|
||||
using pointer = meta::RefTraits<YieldRes>::pointer;
|
||||
|
||||
|
||||
/** by default, pass anything down for initialisation of the core.
|
||||
|
|
@ -782,17 +923,23 @@ namespace lib {
|
|||
|
||||
|
||||
/**
|
||||
* Accessing a STL element range through a Lumiera forward iterator,
|
||||
* Access 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
|
||||
* 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
|
||||
* the details of the iteration (as would be 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`
|
||||
* - when IT is a STL iterator, we rely on the iterator concepts and
|
||||
* especially on `std::iter_reference_t` to find the `reference_type`
|
||||
* - but when IT is a »Lumiera Forward Iterator«, we need to evaluate
|
||||
* nested typedefs `value_type`, `reference` and `pointer`
|
||||
* @remark the reason for this tricky distinction is a _clash of concepts:_
|
||||
* In Lumiera, »Iterator« has a special meaning and can used on itself,
|
||||
* while in the STL, an »Iterator« is a pointer in disguise and is
|
||||
* only meaningful when the backing container is known. This adaptor,
|
||||
* RangeIter, acts as a bridge and levels those usage styles.
|
||||
*/
|
||||
template<class IT>
|
||||
class RangeIter
|
||||
|
|
@ -800,14 +947,10 @@ namespace lib {
|
|||
IT p_;
|
||||
IT e_;
|
||||
|
||||
using _ValTrait = meta::ValueTypeBinding<meta::remove_pointer_t<IT>>;
|
||||
|
||||
public:
|
||||
using pointer = _ValTrait::pointer;
|
||||
using reference = _ValTrait::reference;
|
||||
|
||||
/// @note special twist, since a STL const_iterator would yield a non-const `value_type`
|
||||
using value_type = std::remove_reference<reference>::type;
|
||||
using pointer = iter::Trait<IT>::pointer;
|
||||
using reference = iter::Trait<IT>::reference;
|
||||
using value_type = iter::Trait<IT>::value_type;
|
||||
|
||||
|
||||
RangeIter (IT const& start, IT const& end)
|
||||
|
|
@ -1027,13 +1170,13 @@ namespace lib {
|
|||
|
||||
/**
|
||||
* Helper for type rewritings:
|
||||
* get the element type for an iterator like entity
|
||||
* build a similar iterator with another value type
|
||||
*/
|
||||
template<class TY>
|
||||
struct IterType;
|
||||
struct IterRebind;
|
||||
|
||||
template<template<class,class> class Iter, class TY, class CON>
|
||||
struct IterType<Iter<TY,CON>>
|
||||
struct IterRebind<Iter<TY,CON>>
|
||||
{
|
||||
using Container = CON;
|
||||
using ElemType = TY ;
|
||||
|
|
@ -1046,13 +1189,13 @@ namespace lib {
|
|||
};
|
||||
|
||||
template<class IT>
|
||||
struct IterType<RangeIter<IT>>
|
||||
: IterType<IT>
|
||||
struct IterRebind<RangeIter<IT>>
|
||||
: IterRebind<IT>
|
||||
{
|
||||
template<class T2>
|
||||
struct SimilarIter ///< rebind to rewritten Iterator wrapped into RangeIter
|
||||
{
|
||||
using WrappedIter = IterType<IT>::template SimilarIter<T2>::Type;
|
||||
using WrappedIter = IterRebind<IT>::template SimilarIter<T2>::Type;
|
||||
using Type = RangeIter<WrappedIter>;
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -56,9 +56,9 @@ namespace lib {
|
|||
|
||||
|
||||
public:
|
||||
using pointer = meta::ValueTypeBinding<IT>::pointer;
|
||||
using reference = meta::ValueTypeBinding<IT>::reference;
|
||||
using value_type = std::remove_reference<reference>::type; ///< @note will be const for const iterators (while const_iterator::value_type isn't)
|
||||
using pointer = iter::Trait<IT>::pointer;
|
||||
using reference = iter::Trait<IT>::reference;
|
||||
using value_type = iter::Trait<IT>::value_type;
|
||||
|
||||
|
||||
CursorGear()
|
||||
|
|
|
|||
|
|
@ -13,13 +13,13 @@
|
|||
*/
|
||||
|
||||
/** @file iter-explorer.hpp
|
||||
** Building tree expanding and backtracking evaluations within hierarchical scopes.
|
||||
** Build tree expanding and backtracking evaluations within hierarchical scopes.
|
||||
** Based on the *Lumiera Forward Iterator* concept and using the basic IterAdapter 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
|
||||
** 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 historically, this template, as well as the initial IterExplorer (draft from 2012) can be
|
||||
|
|
@ -36,7 +36,7 @@
|
|||
** 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. (strictly speaking, it means the `flatMap` operation)
|
||||
** to _map the function onto_ the elements in the list. (more precisely, this indicates a `flatMap` operation)
|
||||
**
|
||||
** # A Pipeline builder
|
||||
** Based on such concepts, structures and evaluation patterns, the IterExplorer serves the purpose to provide
|
||||
|
|
@ -49,8 +49,9 @@
|
|||
** 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
|
||||
** - 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 iterator results
|
||||
** - the **flatten** operation allows to combine a sequence of nested _iterables_ into a single seamless iterator
|
||||
** - 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.
|
||||
** - further special convenience adaptors and _terminal functions_ are provided.
|
||||
|
|
@ -101,6 +102,7 @@
|
|||
#include "lib/error.hpp"
|
||||
#include "lib/uninitialised-storage.hpp"
|
||||
#include "lib/meta/duck-detector.hpp"
|
||||
#include "lib/meta/common-result.hpp"
|
||||
#include "lib/meta/function.hpp"
|
||||
#include "lib/meta/trait.hpp"
|
||||
#include "lib/item-wrapper.hpp"
|
||||
|
|
@ -217,6 +219,14 @@ namespace lib {
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
// (forward declarations to allow defining all traits together)
|
||||
template<class SRC, typename RES>
|
||||
class RedressAdapter;
|
||||
|
||||
template<class SRC, typename WRA>
|
||||
class WrapperAdapter;
|
||||
|
||||
}//(End) namespace iter_explorer : basic iterator wrappers
|
||||
|
||||
|
||||
|
|
@ -237,9 +247,10 @@ namespace lib {
|
|||
using std::common_type_t;
|
||||
using std::conditional_t;
|
||||
using std::is_convertible;
|
||||
using std::is_lvalue_reference;
|
||||
using std::remove_reference_t;
|
||||
using meta::is_StateCore;
|
||||
using meta::can_IterForEach;
|
||||
using meta::can_LumieraIter;
|
||||
using meta::can_STL_ForEach;
|
||||
using meta::ValueTypeBinding;
|
||||
using meta::has_TypeResult;
|
||||
|
|
@ -248,17 +259,24 @@ namespace lib {
|
|||
template<class SRC>
|
||||
struct shall_wrap_STL_Iter
|
||||
: __and_<can_STL_ForEach<SRC>
|
||||
,__not_<can_IterForEach<SRC>>
|
||||
,__not_<can_LumieraIter<SRC>>
|
||||
>
|
||||
{ };
|
||||
|
||||
template<class SRC>
|
||||
struct shall_use_Lumiera_Iter
|
||||
: __and_<can_IterForEach<SRC>
|
||||
: __and_<can_LumieraIter<SRC>
|
||||
,__not_<is_StateCore<SRC>>
|
||||
>
|
||||
{ };
|
||||
|
||||
template<class SRC>
|
||||
struct shall_use_StateCore
|
||||
: __and_<__not_<can_LumieraIter<SRC>>
|
||||
,is_StateCore<SRC>
|
||||
>
|
||||
{ };
|
||||
|
||||
|
||||
|
||||
/**
|
||||
|
|
@ -273,8 +291,8 @@ namespace lib {
|
|||
template<class SRC>
|
||||
struct _DecoratorTraits<SRC, enable_if<is_StateCore<SRC>>>
|
||||
{
|
||||
using SrcRaw = lib::meta::Strip<SRC>::Type;
|
||||
using SrcVal = meta::RefTraits<iter::CoreYield<SrcRaw>>::Value;
|
||||
using SrcRaw = lib::meta::Strip<SRC>::TypeReferred;
|
||||
using SrcVal = meta::RefTraits<iter::CoreYield<SrcRaw>>::value_type;
|
||||
using SrcIter = lib::IterableDecorator<lib::CheckedCore<SrcRaw>>;
|
||||
};
|
||||
|
||||
|
|
@ -288,7 +306,7 @@ namespace lib {
|
|||
template<class SRC>
|
||||
struct _DecoratorTraits<SRC, enable_if<shall_wrap_STL_Iter<SRC>>>
|
||||
{
|
||||
static_assert (not std::is_rvalue_reference<SRC>::value,
|
||||
static_assert (not std::is_rvalue_reference_v<SRC>,
|
||||
"container needs to exist elsewhere during the lifetime of the iteration");
|
||||
using SrcIter = iter_explorer::StlRange<SRC>;
|
||||
using SrcVal = SrcIter::value_type;
|
||||
|
|
@ -312,6 +330,78 @@ namespace lib {
|
|||
{ };
|
||||
|
||||
|
||||
/**
|
||||
* @internal Type-selector to adapt a recursive iterable for flattening...
|
||||
* @tparam SRC the underlying source iterator (which yields a nested iterable as result)
|
||||
* @tparam RES result type of the source iterator (this is the nested iterable itself)
|
||||
* @remark to explain the general scheme employed here...
|
||||
* - we want to add a processing layer with a iter_explorer::Flattener
|
||||
* - this requires a source-iterator that yields a nested iterable of sorts
|
||||
* - the source-iterator as such has already been adapted by the [generic mechanism](\ref _DecoratorTraits)
|
||||
* - however there remains the twist that the _nested result_ (which is some kind of iterable)
|
||||
* also needs to be adapted, so that it can be handled as Lumiera Forward Iterator...
|
||||
* - this can be achieved by interspersing an adapter layer _below_ the Flattener,
|
||||
* so that the result of the underlying iterator will be adapted / casted / wrapped
|
||||
* - furthermore, we have to distinguish the cases where the underlying iterator produces a value result
|
||||
* - overall this trait template is instantiated with the type \a SRC of the underlying iterator
|
||||
* - with the help of the second parameter's default, the \a RES result type of this iterator is determined
|
||||
* - the specialisation matcher then classifies this result type and determines an adaptation strategy
|
||||
* - finally, the Flattener is parametrised with this `SrcAdapter` type, but we pass `move(*this) to
|
||||
* its constructor; since `*this` inherits from \a SRC, the Adapter will pick up and wrap \a SRC
|
||||
* There are two basic adaptation strategies. First, if the source iterator yields an iterator by reference,
|
||||
* and we can adapt this iterator on-the-fly (without requiring additional storage), we can just cast the result.
|
||||
* In all other cases, a setup similar to a Transformer is required: for each source-result (which is in itself
|
||||
* iterable), a wrapper is constructed into an _inline storage space_ (implemented as \ref lib::ItemWrapper),
|
||||
* and this wrapper is exposed by-ref as "result value", so that the Flattener can iterate it until exhausted.
|
||||
*/
|
||||
template<class SRC, typename RES=iter::Yield<SRC>, typename SEL=void>
|
||||
struct _FlatteningTraits
|
||||
{
|
||||
static_assert (!sizeof(SRC), "Can not build Flattener: Unable to figure out how to iterate the nested iterable.");
|
||||
};
|
||||
|
||||
template<class SRC, typename RES>
|
||||
struct _FlatteningTraits<SRC,RES, enable_if<__and_< is_lvalue_reference<RES>, can_LumieraIter<RES> >>>
|
||||
{
|
||||
using SrcAdapter = meta::RefTraits<SRC>::value_type;
|
||||
};
|
||||
template<class SRC, typename RES>
|
||||
struct _FlatteningTraits<SRC,RES, enable_if<__and_<__not_<is_lvalue_reference<RES>>, can_LumieraIter<RES> >>>
|
||||
{
|
||||
using NestedIter = meta::RefTraits<RES>::value_type;
|
||||
using SrcAdapter = iter_explorer::WrapperAdapter<SRC,NestedIter>;
|
||||
};
|
||||
|
||||
template<class SRC, typename RES>
|
||||
struct _FlatteningTraits<SRC,RES, enable_if<__and_< is_lvalue_reference<RES>, shall_use_StateCore<RES> >>>
|
||||
{
|
||||
using CoreRaw = lib::meta::Strip<RES>::TypeReferred;
|
||||
using NestedIter = IterableDecorator<CoreRaw>;
|
||||
using SrcAdapter = iter_explorer::RedressAdapter<SRC,NestedIter>;
|
||||
};
|
||||
template<class SRC, typename RES>
|
||||
struct _FlatteningTraits<SRC,RES, enable_if<__and_<__not_<is_lvalue_reference<RES>>, shall_use_StateCore<RES> >>>
|
||||
{
|
||||
using CoreRaw = lib::meta::Strip<RES>::TypeReferred;
|
||||
using NestedIter = IterableDecorator<CoreRaw>;
|
||||
using SrcAdapter = iter_explorer::WrapperAdapter<SRC,NestedIter>;
|
||||
};
|
||||
|
||||
template<class SRC, typename RES>
|
||||
struct _FlatteningTraits<SRC,RES, enable_if<__and_< is_lvalue_reference<RES>, shall_wrap_STL_Iter<RES> >>>
|
||||
{
|
||||
using NestedIter = iter_explorer::StlRange<RES>;
|
||||
using SrcAdapter = iter_explorer::WrapperAdapter<SRC,NestedIter>;
|
||||
};
|
||||
template<class SRC, typename RES>
|
||||
struct _FlatteningTraits<SRC,RES, enable_if<__and_<__not_<is_lvalue_reference<RES>>, is_subscriptable<RES> >>>
|
||||
{
|
||||
using StoreRaw = lib::meta::Strip<RES>::TypeReferred;
|
||||
using NestedIter = IterableDecorator<IdxStoreCore<StoreRaw>>;
|
||||
using SrcAdapter = iter_explorer::WrapperAdapter<SRC,NestedIter>;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* helper to derive a suitable common type when expanding children
|
||||
|
|
@ -327,19 +417,15 @@ namespace lib {
|
|||
using ResIter = _DecoratorTraits<RES>::SrcIter;
|
||||
using SrcYield = iter::Yield<SRC>;
|
||||
using ResYield = iter::Yield<ResIter>;
|
||||
using _CommonT = meta::CommonResultYield<SrcYield,ResYield>;
|
||||
using _CommonT = meta::CommonResult<SrcYield,ResYield>;
|
||||
static constexpr bool can_reconcile = _CommonT::value;
|
||||
static constexpr bool isRefResult = _CommonT::isRef;
|
||||
|
||||
static_assert (can_reconcile,
|
||||
"source iterator and result from the expansion must yield compatible values");
|
||||
static_assert (is_const_v<SrcYield> == is_const_v<ResYield>,
|
||||
"source and expanded types differ in const-ness");
|
||||
|
||||
using YieldRes = _CommonT::ResType;
|
||||
using value_type = _CommonT::value_type;
|
||||
using reference = _CommonT::reference;
|
||||
using pointer = _CommonT::pointer;
|
||||
using YieldRes = _CommonT::Type;
|
||||
};
|
||||
|
||||
}//(End) IterExplorer traits
|
||||
|
|
@ -478,13 +564,15 @@ namespace lib {
|
|||
struct _ReduceTraits
|
||||
{
|
||||
using Result = iter_explorer::_FunTraits<FUN,SRC>::Res;
|
||||
using ResVal = lib::meta::RefTraits<Result>::Value;
|
||||
using ResVal = lib::meta::RefTraits<Result>::value_type;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* ========= IterExplorer pipeline building blocks ========= */
|
||||
|
||||
/**
|
||||
* @internal Base of pipe processing decorator chain.
|
||||
* IterExplorer allows to create a stack out of various decorating processors
|
||||
|
|
@ -516,45 +604,81 @@ namespace lib {
|
|||
};
|
||||
|
||||
|
||||
/**
|
||||
* @internal Iterator adapter to _virtually project_ an arbitrary type \a RES
|
||||
* onto the result of the underlying Lumiera Iterator, by means of a force-cast.
|
||||
*/
|
||||
template<class SRC, typename RES>
|
||||
class RedressAdapter
|
||||
: public SRC
|
||||
{
|
||||
static_assert(can_LumieraIter<SRC>::value, "Lumiera Iterator required as source.");
|
||||
|
||||
using SrcRes = iter::Yield<SRC>;
|
||||
static_assert(meta::isLRef_v<SrcRes>, "Source iterator must yield its result by-ref.");
|
||||
|
||||
public:
|
||||
using value_type = meta::RefTraits<RES>::value_type;
|
||||
using reference = meta::RefTraits<RES>::reference;
|
||||
using pointer = meta::RefTraits<RES>::pointer;
|
||||
|
||||
reference
|
||||
operator*() const
|
||||
{
|
||||
return reinterpret_cast<reference> (SRC::operator*());
|
||||
}
|
||||
|
||||
pointer
|
||||
operator->() const
|
||||
{
|
||||
return & operator*();
|
||||
}
|
||||
|
||||
using SRC::SRC;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @internal Decorator for IterExplorer 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 be 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. The source pipeline is only pulled
|
||||
* once the expanded children are exhausted.
|
||||
* 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 (within this context, "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 that
|
||||
* the nested sequence was _flattened_ into the results. Since this expand() operation can
|
||||
* again be 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 on top_ in order to become the new active source for iteration. Thus the prime
|
||||
* 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. Overall, the given
|
||||
* source pipeline is only pulled, whenever the expanded children are exhausted.
|
||||
* @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 _FunTraits traits. For the same reason, we need to prepare two different
|
||||
* bindings of the passed raw functor, one to work on the source sequence, and the other
|
||||
* one to work on the result sequence of a recursive child expansions; these two sequences
|
||||
* need not be implemented in the same way, which simplifies the definition of algorithms.
|
||||
* @tparam SRC the wrapped source iterator, typically a IterExplorer or nested decorator.
|
||||
* @tparam FUN the concrete type of the functor passed. Will be dissected to find the signature
|
||||
* _expansion functor_, various technical details must be observed, and these minutiae
|
||||
* are largely maintained within the _FunTraits traits. For the same reason, we need to
|
||||
* prepare two different bindings of the passed raw functor, where one of them is used
|
||||
* to work on the source sequence, while the other one handles the result sequence of
|
||||
* _recursive child expansions;_ these two sequences need not be implemented precisely
|
||||
* in the same way — what matters here is compatible behaviour — and this flexible
|
||||
* setup helps to simplify the definition of search- and evaluation algorithms.
|
||||
* @tparam SRC the wrapped source iterator, typically an iterator adaptor or nested decorator.
|
||||
* @tparam FUN actual type of the functor passed. Will be type-matched to detect its signature.
|
||||
* @note the _return type_ of #yield depends _both_ on the return type produced from the original
|
||||
* sequence and the return type of the sequence established through the expand functor.
|
||||
* An attempt is made to _reconcile these_ and this attempt may fail (at compile time).
|
||||
* The reason is, any further processing downstream can not tell if data was produced
|
||||
* by the original sequence of the expansion sequence. Notably, if one of these two
|
||||
* delivers results by-value, then the Expander will _always_ deliver all results
|
||||
* by-value, because it would not be possible to expose a reference to some value
|
||||
* The rationale is that any further processing downstream can not tell if a data result
|
||||
* was produced by the original sequence of the expansion sequence. Notably, if one of
|
||||
* these two delivers results by-value, then the Expander will _always_ choose to deliver
|
||||
* all its results by-value, since it is impossible to expose a reference to some value
|
||||
* that was just delivered temporarily from a source iterator.
|
||||
*/
|
||||
template<class SRC, class RES>
|
||||
class Expander
|
||||
: public SRC
|
||||
{
|
||||
static_assert(can_IterForEach<SRC>::value, "Lumiera Iterator required as source");
|
||||
static_assert(can_LumieraIter<SRC>::value, "Lumiera Iterator required as source");
|
||||
|
||||
using _Trait = _ExpanderTraits<SRC,RES>;
|
||||
using ResIter = _Trait::ResIter;
|
||||
|
|
@ -622,9 +746,9 @@ namespace lib {
|
|||
|
||||
/** @note result type bindings based on a common type of source and expanded result */
|
||||
using YieldRes = _Trait::YieldRes;
|
||||
using value_type = _Trait::value_type;
|
||||
using reference = _Trait::reference;
|
||||
using pointer = _Trait::pointer;
|
||||
using value_type = meta::RefTraits<YieldRes>::value_type;
|
||||
using reference = meta::RefTraits<YieldRes>::reference;
|
||||
using pointer = meta::RefTraits<YieldRes>::pointer;
|
||||
|
||||
|
||||
bool
|
||||
|
|
@ -699,7 +823,7 @@ namespace lib {
|
|||
|
||||
/**
|
||||
* @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
|
||||
* @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
|
||||
|
|
@ -726,7 +850,7 @@ namespace lib {
|
|||
|
||||
|
||||
/**
|
||||
* @internal extension to the Expander decorator to perform expansion delayed on next iteration.
|
||||
* @internal extension to the Expander decorator to perform a delayed expansion on next iteration.
|
||||
*/
|
||||
template<class SRC>
|
||||
class ScheduledExpander
|
||||
|
|
@ -761,14 +885,94 @@ namespace lib {
|
|||
};
|
||||
|
||||
|
||||
/**
|
||||
* @internal Decorator for IterExplorer to expand one level of nested iterables.
|
||||
* This mechanism requires that the _result value_ yielded by the underlying iterator
|
||||
* is itself iterable. Applying this decorator thus has the effect to »flatten« exactly
|
||||
* one level of nesting.
|
||||
* @note The result produced by underlying \a SRC _must be a Lumiera iterator_.
|
||||
* Other kinds of nested iterables can be handled only when adapted accordingly.
|
||||
*/
|
||||
template<class SRC>
|
||||
class Flattener
|
||||
: public SRC
|
||||
{
|
||||
static_assert(can_LumieraIter<SRC>::value, "Lumiera Iterator required as source");
|
||||
|
||||
using NestedIT = iter::Yield<SRC>;
|
||||
static_assert(can_LumieraIter<NestedIT>::value, "flatten() operation expects nested iterator");
|
||||
|
||||
public:
|
||||
using value_type = meta::ValueTypeBinding<NestedIT>::value_type;
|
||||
using reference = meta::ValueTypeBinding<NestedIT>::reference;
|
||||
using pointer = meta::ValueTypeBinding<NestedIT>::pointer;
|
||||
|
||||
Flattener() =default;
|
||||
// inherited default copy operations
|
||||
|
||||
Flattener (SRC&& dataSrc)
|
||||
: SRC{move (dataSrc)}
|
||||
{
|
||||
pullNested();
|
||||
}
|
||||
|
||||
|
||||
/** refresh state when other layers manipulate the source sequence.
|
||||
* @note possibly pulls pipeline to re-establish the invariant */
|
||||
void
|
||||
expandChildren()
|
||||
{
|
||||
SRC::expandChildren();
|
||||
pullNested();
|
||||
}
|
||||
|
||||
public: /* === Iteration control API for IterableDecorator === */
|
||||
|
||||
bool
|
||||
checkPoint() const
|
||||
{
|
||||
ENSURE (not srcIter() or * srcIter());
|
||||
return bool(srcIter());
|
||||
}
|
||||
|
||||
reference
|
||||
yield() const
|
||||
{
|
||||
return **srcIter();
|
||||
}
|
||||
|
||||
void
|
||||
iterNext()
|
||||
{
|
||||
++ *srcIter();
|
||||
pullNested();
|
||||
}
|
||||
|
||||
protected:
|
||||
SRC&
|
||||
srcIter() const
|
||||
{
|
||||
return unConst(*this);
|
||||
}
|
||||
|
||||
/** @note Invariant: positioned at next nested source element, if any */
|
||||
void
|
||||
pullNested()
|
||||
{
|
||||
while (srcIter() and not * srcIter())
|
||||
++srcIter();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @internal Decorator for IterExplorer to map a transformation function on all results.
|
||||
* @internal Decorator for IterExplorer to map a transformation function onto 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.
|
||||
* @note the result-type of the #yield() function _must be_ `reference`, even when
|
||||
* @note the result-type of the #yield() function _must be_ a `reference`, even when
|
||||
* the TransformFunctor produces a value; otherwise we can not provide a safe
|
||||
* `operator->` on any iterator downstream. This is also the reason why the
|
||||
* ItemWrapper is necessary, precisely _because we want to support_ functions
|
||||
|
|
@ -782,7 +986,7 @@ namespace lib {
|
|||
class Transformer
|
||||
: public SRC
|
||||
{
|
||||
static_assert(can_IterForEach<SRC>::value, "Lumiera Iterator required as source");
|
||||
static_assert(can_LumieraIter<SRC>::value, "Lumiera Iterator required as source");
|
||||
|
||||
using TransformFunctor = function<RES(SRC&)>;
|
||||
using TransformedItem = wrapper::ItemWrapper<RES>;
|
||||
|
|
@ -790,10 +994,10 @@ namespace lib {
|
|||
TransformFunctor trafo_;
|
||||
TransformedItem treated_;
|
||||
|
||||
public:
|
||||
using value_type = meta::ValueTypeBinding<RES>::value_type;
|
||||
using reference = meta::ValueTypeBinding<RES>::reference;
|
||||
using pointer = meta::ValueTypeBinding<RES>::pointer;
|
||||
public: // Note: result might be a nested iterator -- not using ValueTypeBinding here!
|
||||
using value_type = meta::RefTraits<RES>::value_type;
|
||||
using reference = meta::RefTraits<RES>::reference;
|
||||
using pointer = meta::RefTraits<RES>::pointer;
|
||||
|
||||
|
||||
template<typename FUN>
|
||||
|
|
@ -826,6 +1030,7 @@ namespace lib {
|
|||
t1.treated_.reset();
|
||||
t2.treated_.reset();
|
||||
swap (t1.trafo_, t2.trafo_);
|
||||
swap (t1.srcIter(), t2.srcIter());
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -880,6 +1085,125 @@ namespace lib {
|
|||
|
||||
|
||||
|
||||
/**
|
||||
* Iterator adapter to package each iterator result into a **wrapper object** of type \a WRA.
|
||||
* Built as a simplified variant of the Transformer, it can not invoke an arbitrary function,
|
||||
* yet will destroy the previous wrapper and construct a new wrapper into the embedded storage
|
||||
* buffer on each iteration.
|
||||
* @note similar as the Transformer, WrapperAdapter will _re-engage_ after copy, move and swap.
|
||||
* This implies to discard a wrapped object residing in the ItermWrapper's storage; the next
|
||||
* result-pull will then cause it to be regenerated from source, since the absolute storage
|
||||
* locations both of the ItemWrapper storage, and the underlying source iterator has changed.
|
||||
* Notably this protects against insidious data corruption in cases where some part of the
|
||||
* implementation (typically indirectly) happens to take a reference into the source.
|
||||
* @warning this mechanism might have surprising ramifications, especially when the underlying
|
||||
* iterator has generated a collection by-value; this data will be discarded whenever the
|
||||
* iterator pipeline is moved, which might happen automatically when adding further layers.
|
||||
* Notably there is an interference with possible iterator decorators _added downstream_,
|
||||
* that need to pull ahead for grouping and filtering.
|
||||
* @remark Users of WrapperAdapter, and of IterExplorer are responsible in general to ensure
|
||||
* that any embodied structure preferably relies on value copy of iterator results rather,
|
||||
* and otherwise only employs references into stable memory locations.
|
||||
*/
|
||||
template<class SRC, typename WRA>
|
||||
class WrapperAdapter
|
||||
: public SRC
|
||||
{
|
||||
using WrappedItem = wrapper::ItemWrapper<WRA>;
|
||||
|
||||
WrappedItem wrapped_{/*created empty*/};
|
||||
|
||||
public: // Note: result might be a nested iterator -- not using ValueTypeBinding here!
|
||||
using value_type = meta::RefTraits<WRA>::value_type;
|
||||
using reference = meta::RefTraits<WRA>::reference;
|
||||
using pointer = meta::RefTraits<WRA>::pointer;
|
||||
|
||||
WrapperAdapter (SRC&& dataSrc)
|
||||
: SRC{move (dataSrc)}
|
||||
{ }
|
||||
|
||||
WrapperAdapter() =default;
|
||||
WrapperAdapter (WrapperAdapter const& o)
|
||||
: SRC{o}
|
||||
, wrapped_{/* deliberately empty: force re-engage */}
|
||||
{ }
|
||||
WrapperAdapter (WrapperAdapter && o)
|
||||
: SRC{move (o)}
|
||||
, wrapped_{/* deliberately empty: force re-engage */}
|
||||
{ }
|
||||
WrapperAdapter&
|
||||
operator= (WrapperAdapter changed)
|
||||
{
|
||||
swap (*this,changed);
|
||||
return *this;
|
||||
}
|
||||
friend void
|
||||
swap (WrapperAdapter& t1, WrapperAdapter& t2)
|
||||
{
|
||||
using std::swap;
|
||||
t1.wrapped_.reset();
|
||||
t2.wrapped_.reset();
|
||||
swap (t1.srcIter(), t2.srcIter());
|
||||
}
|
||||
|
||||
|
||||
/* === decorated Lumiera Forward Iterator === */
|
||||
|
||||
reference
|
||||
operator*() const
|
||||
{
|
||||
return unConst(this)->reWrapIt();
|
||||
}
|
||||
|
||||
pointer
|
||||
operator->() const
|
||||
{
|
||||
return & operator*();
|
||||
}
|
||||
|
||||
WrapperAdapter&
|
||||
operator++()
|
||||
{
|
||||
wrapped_.reset(); // discard current wrapper
|
||||
++ srcIter();
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** refresh state when other layers manipulate the source sequence */
|
||||
void
|
||||
expandChildren()
|
||||
{
|
||||
wrapped_.reset();
|
||||
SRC::expandChildren();
|
||||
}
|
||||
|
||||
|
||||
ENABLE_USE_IN_STD_RANGE_FOR_LOOPS (WrapperAdapter);
|
||||
|
||||
friend bool
|
||||
operator== (WrapperAdapter const& w1, WrapperAdapter const& w2)
|
||||
{
|
||||
return w1.srcIter() == w2.srcIter();
|
||||
}
|
||||
|
||||
private:
|
||||
SRC&
|
||||
srcIter() const
|
||||
{
|
||||
return unConst(*this);
|
||||
}
|
||||
|
||||
reference
|
||||
reWrapIt ()
|
||||
{
|
||||
if (not wrapped_) // build a wrapper instance once per src item
|
||||
wrapped_ = * srcIter();
|
||||
return *wrapped_;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @internal Decorator for IterExplorer to group consecutive elements into fixed sized chunks.
|
||||
* One group of elements is always prepared eagerly, and then the next one on iteration.
|
||||
|
|
@ -892,7 +1216,7 @@ namespace lib {
|
|||
class Grouping
|
||||
: public SRC
|
||||
{
|
||||
static_assert(can_IterForEach<SRC>::value, "Lumiera Iterator required as source");
|
||||
static_assert(can_LumieraIter<SRC>::value, "Lumiera Iterator required as source");
|
||||
|
||||
protected:
|
||||
using Group = std::array<RES, grp>;
|
||||
|
|
@ -1002,14 +1326,15 @@ namespace lib {
|
|||
|
||||
/**
|
||||
* @internal Decorator for IterExplorer to group consecutive elements controlled by some
|
||||
* grouping value \a GRP and compute an aggregate value \a AGG for each such group as
|
||||
* **grouping value** \a GRP and compute an aggregate value \a AGG for each such group as
|
||||
* iterator yield. The first group is consumed eagerly, each further group on iteration;
|
||||
* thus when the aggregate for the last group appears as result, the source iterator has
|
||||
* already been exhausted. The aggregate is default-initialised at start of each group
|
||||
* and then the computation functor \a FAGG is invoked for each consecutive element marked
|
||||
* with the same _grouping value_ — and this grouping value itself is obtained by invoking
|
||||
* the functor \a FGRP on each source value. All computation are performed on-the-fly. No
|
||||
* capturing or reordering of the source elements takes place, rather groups are formed
|
||||
* already been exhausted. The aggregate is default-initialised at start of each group,
|
||||
* followed by invoking the [computation functor](\ref #Aggregator) for each consecutive
|
||||
* element that has been marked with the same _grouping value_ — and this grouping value
|
||||
* itself is obtained by invoking the [grouping functor](\ref #Grouping) on each value
|
||||
* produced by the underlying source iterator. All computation is performed on-the-fly.
|
||||
* No capturing or reordering of the source elements takes place, rather groups are formed
|
||||
* based on the changes of the grouping value over the source iterator's result sequence.
|
||||
* @tparam AGG data type to collect the aggregate; must be default constructible and assignable
|
||||
* @tparam GRP value type to indicate a group
|
||||
|
|
@ -1018,14 +1343,14 @@ namespace lib {
|
|||
* This limitation was deemed acceptable (adapting a function with several arguments would
|
||||
* require quite some nasty technicalities). The first argument of this `aggFun` refers
|
||||
* to the accumulator by value, and thereby also implicitly defines the aggregate result type.
|
||||
* @warning the Aggregator \a AGG *must not capture references* to upstream internal state, because
|
||||
* @warning the #Aggregator *must not capture references* to upstream internal state, because
|
||||
* the overall pipeline will be moved into final location _after the initial ctor call._
|
||||
*/
|
||||
template<class SRC, typename AGG, class GRP>
|
||||
class GroupAggregator
|
||||
: public SRC
|
||||
{
|
||||
static_assert(can_IterForEach<SRC>::value, "Lumiera Iterator required as source");
|
||||
static_assert(can_LumieraIter<SRC>::value, "Lumiera Iterator required as source");
|
||||
|
||||
protected:
|
||||
using SrcValue = meta::ValueTypeBinding<SRC>::value_type;
|
||||
|
|
@ -1038,9 +1363,9 @@ namespace lib {
|
|||
Aggregator aggregate_;
|
||||
|
||||
public:
|
||||
using value_type = meta::RefTraits<AGG>::Value;
|
||||
using reference = meta::RefTraits<AGG>::Reference;
|
||||
using pointer = meta::RefTraits<AGG>::Pointer;
|
||||
using value_type = meta::RefTraits<AGG>::value_type;
|
||||
using reference = meta::RefTraits<AGG>::reference;
|
||||
using pointer = meta::RefTraits<AGG>::pointer;
|
||||
|
||||
GroupAggregator() =default;
|
||||
// inherited default copy operations
|
||||
|
|
@ -1108,13 +1433,13 @@ namespace lib {
|
|||
* 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._
|
||||
* **invariant** of this class: _if a "current element" exists, it has already been approved._
|
||||
*/
|
||||
template<class SRC>
|
||||
class Filter
|
||||
: public SRC
|
||||
{
|
||||
static_assert(can_IterForEach<SRC>::value, "Lumiera Iterator required as source");
|
||||
static_assert(can_LumieraIter<SRC>::value, "Lumiera Iterator required as source");
|
||||
|
||||
protected:
|
||||
using FilterPredicate = function<bool(SRC&)>;
|
||||
|
|
@ -1201,7 +1526,7 @@ namespace lib {
|
|||
* capturing it _by value._ After building, this remoulded version can be assigned to the
|
||||
* original filter functor, under the assumption that both are roughly compatible. Moreover,
|
||||
* since we wrap the actual lambda into an adapter, allowing for generic lambdas to be used
|
||||
* as filter predicates, this setup allows for a lot of leeway regarding the concrete predicates.
|
||||
* as filter predicates, this setup provides a lot of wiggle room for defining predicates.
|
||||
* @note whenever the filter is remoulded, the invariant is immediately
|
||||
* [re-established](\ref Filter::pullFilter() ), possibly forwarding the sequence
|
||||
* to the next element approved by the new version of the filter.
|
||||
|
|
@ -1215,7 +1540,7 @@ namespace lib {
|
|||
: public Filter<SRC>
|
||||
{
|
||||
using _Filter = Filter<SRC>;
|
||||
static_assert(can_IterForEach<SRC>::value, "Lumiera Iterator required as source");
|
||||
static_assert(can_LumieraIter<SRC>::value, "Lumiera Iterator required as source");
|
||||
|
||||
public:
|
||||
MutableFilter() =default;
|
||||
|
|
@ -1365,6 +1690,7 @@ namespace lib {
|
|||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @internal Decorator for IterExplorer to cut iteration once a predicate ceases to be true.
|
||||
* Similar to Filter, the given functor is adapted as appropriate, yet is required to yield
|
||||
|
|
@ -1379,7 +1705,7 @@ namespace lib {
|
|||
class StopTrigger
|
||||
: public IterStateCore<SRC>
|
||||
{
|
||||
static_assert(can_IterForEach<SRC>::value, "Lumiera Iterator required as source");
|
||||
static_assert(can_LumieraIter<SRC>::value, "Lumiera Iterator required as source");
|
||||
|
||||
using Core = IterStateCore<SRC>;
|
||||
using Cond = function<bool(SRC&)>;
|
||||
|
|
@ -1579,7 +1905,7 @@ namespace lib {
|
|||
class IterExplorer
|
||||
: public SRC
|
||||
{
|
||||
static_assert(can_IterForEach<SRC>::value, "Lumiera Iterator required as source");
|
||||
static_assert(can_LumieraIter<SRC>::value, "Lumiera Iterator required as source");
|
||||
|
||||
|
||||
public:
|
||||
|
|
@ -1698,6 +2024,25 @@ namespace lib {
|
|||
}
|
||||
|
||||
|
||||
/** adapt this IterExploer to flatten _one level of nested Lumiera Iterators_.
|
||||
* Obviously this processing can only be applied when the underlying pipeline yields a result
|
||||
* that can be handled as being iterable; the resulting pipeline will seamlessly produce all
|
||||
* results from all non-empty nested iterators, in sequence.
|
||||
* @remark when chained behind a suitable [transformer(fun)](\ref transform()), the processing
|
||||
* pattern is similar to the `flatMap(fun)` or `M >= fun` operation known from Monad theory.
|
||||
* @return processing pipeline with attached [flattening](\ref iter_explorer::Flattener) decorator
|
||||
*/
|
||||
auto
|
||||
flatten()
|
||||
{
|
||||
using Adapted = _FlatteningTraits<SRC>::SrcAdapter;
|
||||
using ResCore = iter_explorer::Flattener<Adapted>;
|
||||
using ResIter = _DecoratorTraits<ResCore>::SrcIter;
|
||||
|
||||
return IterExplorer<ResIter> (ResCore{Adapted{move(*this)}});
|
||||
}
|
||||
|
||||
|
||||
/** adapt this IterExplorer 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,
|
||||
|
|
@ -1760,7 +2105,7 @@ namespace lib {
|
|||
|
||||
static_assert (meta::is_BinaryFun<FAGG>());
|
||||
using ArgType1 = _Fun<FAGG>::Args::List::Head;
|
||||
using Aggregate = meta::RefTraits<ArgType1>::Value;
|
||||
using Aggregate = meta::RefTraits<ArgType1>::value_type;
|
||||
|
||||
using ResCore = iter_explorer::GroupAggregator<SRC, Aggregate, GroupVal>;
|
||||
using ResIter = _DecoratorTraits<ResCore>::SrcIter;
|
||||
|
|
@ -1905,7 +2250,7 @@ namespace lib {
|
|||
asPtr()
|
||||
{
|
||||
using Val = meta::ValueTypeBinding<SRC>::value_type;
|
||||
static_assert (not std::is_pointer_v<Val>);
|
||||
static_assert (not is_pointer_v<Val>);
|
||||
return IterExplorer::transform ([](Val& ref){ return &ref; });
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@
|
|||
|
||||
|
||||
#include "lib/meta/util.hpp"
|
||||
#include "lib/meta/function.hpp"
|
||||
#include "lib/iter-adapter.hpp"
|
||||
#include "lib/itertools.hpp"
|
||||
#include "lib/nocopy.hpp"
|
||||
|
|
@ -310,7 +311,7 @@ namespace lib {
|
|||
{
|
||||
using Src = std::remove_reference<IT>::type;
|
||||
using ResVal = lib::meta::_Fun<FUN>::Ret;
|
||||
using TransIter = TransformIter<Src, ResVal>;
|
||||
using TransIter = decltype(lib::transformIter (std::declval<IT>(), std::declval<FUN>()));
|
||||
using Iter = IterSource<ResVal>::iterator;
|
||||
};
|
||||
|
||||
|
|
@ -326,26 +327,23 @@ namespace lib {
|
|||
// we can strip the const added by the STL map types
|
||||
using KeyType = std::remove_const<ConstKeyType>::type;
|
||||
|
||||
using KeyIter = TransformIter<Src, KeyType>;
|
||||
using ValIter = TransformIter<Src, ValType>;
|
||||
|
||||
static KeyType takeFirst (PairType const& pair) { return pair.first; }
|
||||
static ValType takeSecond(PairType const& pair) { return pair.second;}
|
||||
};
|
||||
|
||||
|
||||
template<class IT>
|
||||
_PairIterT<IT>::KeyIter
|
||||
auto
|
||||
takePairFirst (IT&& source)
|
||||
{
|
||||
return transformIterator(forward<IT>(source), _PairIterT<IT>::takeFirst );
|
||||
return transformIter (forward<IT>(source), _PairIterT<IT>::takeFirst );
|
||||
}
|
||||
|
||||
template<class IT>
|
||||
_PairIterT<IT>::ValIter
|
||||
auto
|
||||
takePairSecond (IT&& source)
|
||||
{
|
||||
return transformIterator(forward<IT>(source), _PairIterT<IT>::takeSecond );
|
||||
return transformIter (forward<IT>(source), _PairIterT<IT>::takeSecond );
|
||||
}
|
||||
|
||||
} //(END) type helpers...
|
||||
|
|
@ -375,10 +373,10 @@ namespace lib {
|
|||
auto
|
||||
singleVal (VAL&& something)
|
||||
{
|
||||
using Src = decltype(singleValIterator (forward<VAL>(something)));
|
||||
using Src = decltype(singletonIter (forward<VAL>(something)));
|
||||
using Val = _IterT<Src>::Val;
|
||||
|
||||
return IterSource<Val>::build (new WrappedLumieraIter<Src>{singleValIterator (forward<VAL>(something))});
|
||||
return IterSource<Val>::build (new WrappedLumieraIter<Src>{singletonIter (forward<VAL>(something))});
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -400,7 +398,7 @@ namespace lib {
|
|||
|
||||
return IterSource<ValType>::build (
|
||||
new WrappedLumieraIter<TransIT> (
|
||||
transformIterator (forward<IT>(source), processingFunc)));
|
||||
transformIter (forward<IT>(source), processingFunc)));
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -408,7 +406,7 @@ namespace lib {
|
|||
* all the keys of the given Map or Hashtable
|
||||
*/
|
||||
template<class MAP>
|
||||
_MapT<MAP>::KeyIter
|
||||
auto
|
||||
eachMapKey (MAP& map)
|
||||
{
|
||||
using Range = RangeIter<typename MAP::iterator>;
|
||||
|
|
@ -438,13 +436,13 @@ namespace lib {
|
|||
* the distinct keys
|
||||
*/
|
||||
template<class MAP>
|
||||
_MapT<MAP>::KeyIter
|
||||
auto
|
||||
eachDistinctKey (MAP& map)
|
||||
{
|
||||
using Range = RangeIter<typename MAP::iterator>;
|
||||
|
||||
Range contents (map.begin(), map.end());
|
||||
return wrapIter (filterRepetitions (takePairFirst(contents)));
|
||||
return wrapIter (filterRepeat (takePairFirst(contents)));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@
|
|||
|
||||
#include "lib/iter-adapter.hpp"
|
||||
#include "lib/iter-explorer.hpp"
|
||||
#include "lib/meta/tuple-helper.hpp"
|
||||
#include "lib/meta/tuple-concept.hpp"
|
||||
|
||||
#include <utility>
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -18,14 +18,14 @@
|
|||
|
||||
|
||||
#include "lib/error.hpp"
|
||||
#include "include/lifecycle.h"
|
||||
#include "include/lifecycle.hpp"
|
||||
#include "lib/lifecycleregistry.hpp"
|
||||
#include "lib/util.hpp"
|
||||
|
||||
|
||||
|
||||
|
||||
namespace lumiera {
|
||||
namespace vessel {
|
||||
|
||||
// ==== implementation Lifecycle Registry =======
|
||||
|
||||
|
|
@ -64,7 +64,6 @@ namespace lumiera {
|
|||
if (isNew and eventLabel == ON_BASIC_INIT)
|
||||
callbackFun(); // when this code executes,
|
||||
// then per definition we are already post "basic init"
|
||||
// (which happens in the AppState ctor); thus fire it immediately
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -83,31 +82,4 @@ namespace lumiera {
|
|||
CStr ON_EMERGENCY ("ON_EMERGENCY");
|
||||
|
||||
|
||||
} // namespace lumiera
|
||||
|
||||
|
||||
extern "C" { /* ==== implementation C interface for lifecycle hooks ======= */
|
||||
|
||||
|
||||
const char * lumiera_ON_BASIC_INIT = lumiera::ON_BASIC_INIT;
|
||||
const char * lumiera_ON_GLOBAL_INIT = lumiera::ON_GLOBAL_INIT;
|
||||
const char * lumiera_ON_GLOBAL_SHUTDOWN = lumiera::ON_GLOBAL_SHUTDOWN;
|
||||
|
||||
const char * lumiera_ON_EMERGENCY = lumiera::ON_EMERGENCY;
|
||||
|
||||
|
||||
|
||||
void
|
||||
lumiera_LifecycleHook_add (const char* eventLabel, void callbackFun(void))
|
||||
{
|
||||
lumiera::LifecycleHook (eventLabel, callbackFun);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
lumiera_Lifecycle_trigger (const char* eventLabel)
|
||||
{
|
||||
lumiera::LifecycleRegistry::instance().execute (eventLabel);
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace vessel
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
/** @file lifecycleregistry.hpp
|
||||
** Helper for registering lifecycle event callbacks, which are
|
||||
** provided as a global service by lumiera::AppState. This service
|
||||
** provided as an application service within the »vessel«. This service
|
||||
** allows to enrol functions under a given label and then to call
|
||||
** all those registered functions.
|
||||
** @note this is in fact an event mechanism, and if we start using
|
||||
|
|
@ -22,12 +22,12 @@
|
|||
** boost::signals. (which has the downside of being an binary
|
||||
** dependency).
|
||||
**
|
||||
** @see appstate.hpp
|
||||
** @see voyage.hpp
|
||||
*/
|
||||
|
||||
|
||||
#ifndef LUMIERA_LIFECYCLEREGISTRY_H
|
||||
#define LUMIERA_LIFECYCLEREGISTRY_H
|
||||
#ifndef VESSEL_LIFECYCLE_REGISTRY_H
|
||||
#define VESSEL_LIFECYCLE_REGISTRY_H
|
||||
|
||||
|
||||
#include "lib/util.hpp"
|
||||
|
|
@ -39,7 +39,7 @@
|
|||
#include <map>
|
||||
|
||||
|
||||
namespace lumiera {
|
||||
namespace vessel {
|
||||
|
||||
using util::contains;
|
||||
using std::function;
|
||||
|
|
@ -50,7 +50,7 @@ namespace lumiera {
|
|||
/**
|
||||
* Registry of callback functions accessible by a label (ID)
|
||||
* provided at registration. Registered functions will be added
|
||||
* to a list, which can be triggered via label. Used by AppState
|
||||
* to a list, which can be triggered via label. Used by vessel::Voyage
|
||||
* to implement the lumiera lifecycle (init, shutdown) hooks.
|
||||
*/
|
||||
class LifecycleRegistry
|
||||
|
|
@ -98,5 +98,5 @@ namespace lumiera {
|
|||
|
||||
|
||||
|
||||
} // namespace lumiera
|
||||
#endif
|
||||
} // namespace vessel
|
||||
#endif /*VESSEL_LIFECYCLE_REGISTRY_H*/
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@
|
|||
** under the assumption that the allocator is a _monostate_, and
|
||||
** exploiting _empty-base-optimisation,_ footprint is minimal.
|
||||
** @note this linked list container is _intrusive_ and thus needs the help
|
||||
** of the element type, which must *provide a pointer member* `next`.
|
||||
** of the element type, which must **provide a pointer member** `next`.
|
||||
** Consequently, each such node element can not be member in
|
||||
** several collections at the same time (unless they share all elements)
|
||||
** @note as usual, any iterator relies on the continued existence and
|
||||
|
|
|
|||
179
src/lib/live-mark.hpp
Normal file
179
src/lib/live-mark.hpp
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
/*
|
||||
LIVE-MARK.hpp - safety-tag to prevent invoking a deceased partner
|
||||
|
||||
Copyright (C)
|
||||
2026, Hermann Vosseler <Ichthyostega@web.de>
|
||||
|
||||
**Lumiera** 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. See the file COPYING for further details.
|
||||
|
||||
*/
|
||||
|
||||
/** @file live-mark.hpp
|
||||
** Memory marker tag to prevent segfault in communication with a defunct receiver.
|
||||
** The tag comprises an unique hash value, that can be embedded into another object
|
||||
** and tied to the lifecycle. This setup allows to build a safe-handle, that offers
|
||||
** a liveness check of the communication partner and prevents dereferencing and
|
||||
** invoking into the stale memory of an expired receiver. Notably this allows
|
||||
** to safeguard asynchronous communication between thread-local entities,
|
||||
** even when some of the participating threads terminates unexpectedly.
|
||||
**
|
||||
** @todo WIP 5/2026 developing a thread-local allocation pool
|
||||
**
|
||||
** @see engine-buffer-manager.hpp usage example
|
||||
** @see LiveMark_test
|
||||
**
|
||||
*/
|
||||
|
||||
#ifndef LIB_LIVE_MARK_H_
|
||||
#define LIB_LIVE_MARK_H_
|
||||
|
||||
#include "lib/error.hpp"
|
||||
#include "lib/hash-combine.hpp"
|
||||
#include "lib/util.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <concepts>
|
||||
|
||||
namespace lib {
|
||||
namespace err = lumiera::error;
|
||||
|
||||
using lib::HashVal;
|
||||
|
||||
|
||||
class LiveMark;
|
||||
|
||||
template<class SUBJ>
|
||||
concept alive_marked = requires (SUBJ const& subject)
|
||||
{
|
||||
{ subject.getLiveMark() } -> std::convertible_to<LiveMark const&>;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Liveness marker tag to be incorporated into another object,
|
||||
* so that a communication link can be severed automatically when
|
||||
* the receiver object goes out of scope.
|
||||
* @see AllocReceiver
|
||||
* @see LiveMark_test
|
||||
*/
|
||||
class LiveMark
|
||||
{
|
||||
std::atomic<HashVal> id_;
|
||||
|
||||
public:
|
||||
LiveMark() = default;
|
||||
~LiveMark() { disengage(); } ///< @note the actual protection happens here
|
||||
|
||||
/**
|
||||
* Create a LiveMark that is connected
|
||||
* to the Subject to be protected.
|
||||
*/
|
||||
LiveMark (alive_marked auto& subject)
|
||||
: id_{generateID (subject)}
|
||||
{ }
|
||||
|
||||
// using default copy/assignment
|
||||
|
||||
/** deactivate and mark as dead (irreversible) */
|
||||
void disengage() { id_.store (0, std::memory_order_relaxed); }
|
||||
|
||||
|
||||
/**
|
||||
* A smart-handle for safe protected access to some object
|
||||
* that might go out of scope eventually. This works under the
|
||||
* assumption that the target object embeds a LiveMark, which
|
||||
* is expressed through the concept alive_marked. The marker
|
||||
* HashVal is unique for each instance and distinct memory
|
||||
* location, and thus even access to a new object instance
|
||||
* with the same type, placed into the same memory location
|
||||
* will be rejected, after the original target was destroyed.
|
||||
*/
|
||||
template<alive_marked SUBJ>
|
||||
class SafeHandle
|
||||
{
|
||||
SUBJ * target_{nullptr};
|
||||
HashVal check_{0};
|
||||
|
||||
public:
|
||||
SafeHandle() = default;
|
||||
// default copy operations
|
||||
|
||||
/** liveness check */
|
||||
operator bool() const
|
||||
{
|
||||
return bool(target_)
|
||||
and check_ == retrieveID (*target_);
|
||||
}
|
||||
|
||||
/* ==== subject access ==== */
|
||||
|
||||
SUBJ&
|
||||
operator*() const
|
||||
{
|
||||
if (not bool(*this))
|
||||
throw err::State{"Accessing defunct target"
|
||||
, LERR_(BOTTOM_VALUE)};
|
||||
return * target_;
|
||||
}
|
||||
|
||||
SUBJ*
|
||||
operator->() const
|
||||
{
|
||||
return & SafeHandle::operator*();
|
||||
}
|
||||
|
||||
private:
|
||||
SafeHandle (SUBJ& target)
|
||||
: target_{& target}
|
||||
, check_{retrieveID(target)}
|
||||
{ }
|
||||
|
||||
friend class LiveMark;
|
||||
|
||||
/** The heart of the implementation: peek into the
|
||||
* target location to see if a valid LiveMark is embedded.
|
||||
*/
|
||||
static HashVal
|
||||
retrieveID (SUBJ& target)
|
||||
{
|
||||
return target.getLiveMark().id_.load (std::memory_order_relaxed);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a safe smart-handle to refer to the given subject,
|
||||
* similar to a raw pointer. Yet the handle will bool-check
|
||||
* to `false` when the target object was destroyed
|
||||
*/
|
||||
template<alive_marked SUBJ>
|
||||
SafeHandle<SUBJ>
|
||||
makeHandle (SUBJ& subject)
|
||||
{
|
||||
return SafeHandle{subject};
|
||||
}
|
||||
|
||||
private:
|
||||
/** Generate unique HashVal.
|
||||
* @remark seeded by a nonce (counter) and the memory address,
|
||||
* which makes it impossible to get the same hash at the same
|
||||
* target address more than once (unless there is a collision
|
||||
* in the 64bit hash space, which is extremely rare)
|
||||
*/
|
||||
template<alive_marked X>
|
||||
static HashVal
|
||||
generateID (X const& targetAddress)
|
||||
{
|
||||
static std::atomic_size_t nonce;
|
||||
HashVal mark = nonce.fetch_add (1, std::memory_order_relaxed);
|
||||
|
||||
hash::combine (mark, util::addrID (targetAddress));
|
||||
return mark;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} // namespace lib
|
||||
#endif /*LIB_LIVE_MARK_H_*/
|
||||
86
src/lib/local-slice.hpp
Normal file
86
src/lib/local-slice.hpp
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
LOCAL-SLICE.hpp - access front-end to thread-local instances
|
||||
|
||||
Copyright (C)
|
||||
2026, Hermann Vosseler <Ichthyostega@web.de>
|
||||
|
||||
**Lumiera** 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. See the file COPYING for further details.
|
||||
|
||||
*/
|
||||
|
||||
/** @file local-slice.hpp
|
||||
** Front-end to attach to a set of thread-local instances.
|
||||
** This setup helps to adapt a given service implementation to be deployed
|
||||
** as per-thread instances, operating in a massively concurrent situation.
|
||||
** The services installed this way must be able to work as self-contained
|
||||
** instances transparently, possibly cooperating with a central hub behind
|
||||
** the scenes, which however must be coordinated by the services themselves.
|
||||
**
|
||||
** Since thread-local storage is effectively static, the configuration of
|
||||
** instances happens at some point _prior_ to the first use from within a
|
||||
** thread, and otherwise not further specified. The LocalSlice access front-end
|
||||
** is a _monostate_ and all instances thus expose the same set of per-thread
|
||||
** service instances, which are created on-demand (by the C++ runtime system).
|
||||
**
|
||||
** @warning as of 2022 the implementation of `thread_local` in the GNU runtime
|
||||
** was observed to incur seemingly unnecessary overhead, which however
|
||||
** is relevant only in a high-performance environment. However, some
|
||||
** [casual benchmarks](\ref LocalSlice_test::investigatePerformance)
|
||||
** indicated that the problem does not seem to be _that serious_...
|
||||
** @see LocalSlice_test
|
||||
** @see LocalBufferStage (usage example)
|
||||
** @see buffer-provider-load-test.cpp
|
||||
**
|
||||
*/
|
||||
|
||||
#ifndef LIB_LOCAL_SLICE_H_
|
||||
#define LIB_LOCAL_SLICE_H_
|
||||
|
||||
#include "lib/integral.hpp"
|
||||
|
||||
|
||||
namespace lib {
|
||||
|
||||
/** Extension point for LocalSlice: Service initialisation */
|
||||
template<class SRV>
|
||||
struct LocalSlice_DefaultSetup
|
||||
{
|
||||
using Service = SRV;
|
||||
static Service init() { return Service(); }
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Common access point to thread-local service instances.
|
||||
* @tparam CONF setup to initialise the service.
|
||||
*/
|
||||
template<class SRV, class CONF =LocalSlice_DefaultSetup<SRV>>
|
||||
struct LocalSlice
|
||||
{
|
||||
LocalSlice() = default;
|
||||
// using default copy/assignment
|
||||
|
||||
|
||||
using Service = CONF::Service;
|
||||
|
||||
/** service access */
|
||||
Service&
|
||||
operator*() const
|
||||
{
|
||||
thread_local Service srvInstance{CONF::init()};
|
||||
return srvInstance;
|
||||
}
|
||||
|
||||
Service*
|
||||
operator->() const
|
||||
{
|
||||
return & LocalSlice::operator*();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} // namespace lib
|
||||
#endif /*LIB_LOCAL_SLICE_H_*/
|
||||
236
src/lib/meta/common-result.hpp
Normal file
236
src/lib/meta/common-result.hpp
Normal file
|
|
@ -0,0 +1,236 @@
|
|||
/*
|
||||
COMMON-RESULT.hpp - reconciled return types
|
||||
|
||||
Copyright (C)
|
||||
2024,2026 Hermann Vosseler <Ichthyostega@web.de>
|
||||
|
||||
**Lumiera** 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. See the file COPYING for further details.
|
||||
|
||||
*/
|
||||
|
||||
/** @file common-result.hpp
|
||||
** Determine a common result type for combined functors.
|
||||
** This header defines a metafunction to determine if, and how the return types
|
||||
** of several functors or accessors can be combined. This task becomes relevant
|
||||
** when building exploration strategies defined over a functional datastructure.
|
||||
** A notable example happens when performing a _tree expansion_ from within a
|
||||
** [iterator pipeline](\ref iter-explorer.hpp) or when mapping an aggregating
|
||||
** functor onto a [tuple of values](\ref tuple-concept.hpp).
|
||||
**
|
||||
** The basic technique to find such a _reconciled type_ is to rely on the
|
||||
** _ternary operator_ (`test? yes_expr : no_expr;`), which is specified to
|
||||
** great detail in the C++ standard. However, the STDLIB provides only two
|
||||
** tools tailored for specific cases:
|
||||
** - std::common_type is what you can use to _store_ such a reconciled result,
|
||||
** and includes _decaying_ the type.
|
||||
** - std::common_reference is oriented towards passing of references,
|
||||
** and includes elaborate mapping logic for mixture of RValues and LValues.
|
||||
**
|
||||
** So there is a need to expose the foundational logic for type reconciliation,
|
||||
** without any special adaptation rules. Furthermore, direct support for passing
|
||||
** the selection of types in the form of a [Type Sequence](\ref typelist.hpp)
|
||||
** is included.
|
||||
**
|
||||
** @see CommonResult_test
|
||||
** @see lib::iter_explorer::Expander
|
||||
** @see tuple-idx-adaptor.hpp
|
||||
*/
|
||||
|
||||
|
||||
#ifndef LIB_META_COMMON_RESULT_H
|
||||
#define LIB_META_COMMON_RESULT_H
|
||||
|
||||
|
||||
#include "lib/meta/trait.hpp"
|
||||
#include "lib/meta/typelist.hpp"
|
||||
|
||||
|
||||
|
||||
namespace lib {
|
||||
namespace meta {
|
||||
|
||||
namespace detail {// Implementation: reconcile N return types...
|
||||
|
||||
/** @internal helper to find a reconciled result type.
|
||||
* This is the technical part, and relies on a SFINAE technique:
|
||||
* The test succeeds, if a ternary operator with both type results
|
||||
* can be instantiated in the signature of a `check` template.
|
||||
*
|
||||
* Furthermore, the reconciliation for a pair of types can be
|
||||
* generalised to N types by gradually reconciling the following
|
||||
* types with the already established result.
|
||||
*
|
||||
* @remark 2026 the GNU Libstdc++ relies on precisely the same
|
||||
* technique, which implies that this approach works reliably.
|
||||
*/
|
||||
template<typename X, typename U, typename...TYPES>
|
||||
class _ProbeCommonType
|
||||
{
|
||||
/******************************************************************************//**
|
||||
* Core of the type reconciliation logic: determine the _common type_.
|
||||
* The implementation relies on the **ternary operator** — the C++ language
|
||||
* specifies a detailed and elaborate algorithm for this operator to reconcile
|
||||
* the results of the "true" and "false" parts of `test? true_part : false_part`;
|
||||
* such an expression compiles if and only if these criteria are met and a common
|
||||
* type can be determined. The results of this procedure are quite solid, and
|
||||
* satisfactory in most usage situations. A mixture of references, values const
|
||||
* and volatile is handled properly. Only some rare corner cases are not covered,
|
||||
* like upcasting two subclasses to a common interface (actually in some cases,
|
||||
* involving multiple inheritance, a single authoritative solution for this
|
||||
* special problem does not exist, and thus these cases were left out
|
||||
* from the language spec).
|
||||
*/
|
||||
template<typename T1, typename T2>
|
||||
using Reconciled = decltype(true ? std::declval<T1>() : std::declval<T2>());
|
||||
|
||||
|
||||
/** Marker type: successful test */
|
||||
template<typename RES>
|
||||
struct FoundResult
|
||||
: std::true_type
|
||||
{
|
||||
using Type = RES;
|
||||
};
|
||||
struct NoResultFound
|
||||
: std::false_type
|
||||
{ };
|
||||
|
||||
|
||||
/** SFINAE setup to probe for a reconciled type */
|
||||
template<class T1
|
||||
,class T2
|
||||
,typename = Reconciled<T1,T2>>
|
||||
static FoundResult<Reconciled<T1,T2>>
|
||||
check(int);
|
||||
|
||||
template<class,class>
|
||||
static NoResultFound
|
||||
check(...);
|
||||
|
||||
/** perform the reconciliation test */
|
||||
using FirstResult = decltype(check<X,U>(42));
|
||||
|
||||
|
||||
/** Helper: Join results of pairwise reconciliation
|
||||
* to extend the result to a sequence of types */
|
||||
template<bool success, typename...TS>
|
||||
struct Join
|
||||
{
|
||||
using Result = FirstResult;
|
||||
};
|
||||
|
||||
template<typename TX, typename...TS>
|
||||
struct Join<true, TX,TS...> ///<@note matches when FirstResult was successful
|
||||
{
|
||||
using CT = FirstResult::Type;
|
||||
using Result = _ProbeCommonType<CT,TX,TS...>::Result;
|
||||
};
|
||||
|
||||
|
||||
public:
|
||||
/** Retrieve the overall result of this metafunction */
|
||||
using Result = Join<FirstResult::value, TYPES...>::Result;
|
||||
};
|
||||
|
||||
}//(End)implementation: type reconciliation
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Metafunction to find a _common result type_.
|
||||
* @tparam TYPES a variadic sequence of types to be reconciled.
|
||||
* Finding such a common result type can be relevant when results
|
||||
* from several functors shall be combined, e.g. when mapping and
|
||||
* aggregating results of a function application to a tuple.
|
||||
* It is important to note that such a function may return arbitrary
|
||||
* types, either by value or by reference, and possibly marked as const.
|
||||
*
|
||||
* \par Interface
|
||||
* The `struct` CommonResult itself is a std::bool_constant that
|
||||
* indicates if the given types can be reconciled. When successful,
|
||||
* a nested typedef `Type` is present and aliased to a type that
|
||||
* can represent all given result types as a function return.
|
||||
*
|
||||
* @remarks
|
||||
* - the C++ standard offers similar functionality as
|
||||
* + std::common_type<TYPES...>
|
||||
* + std::common_reference<TYPES...>
|
||||
* - however, the standard solution, while based on the same logic
|
||||
* as this implementation, adds additional transformations, like
|
||||
* decaying the type (for std::common_type) or handling various
|
||||
* corner cases with RValue references. The specification of
|
||||
* expected behaviour is quite elaborate for those cases
|
||||
* - the implementation presented here is focused on _returning_
|
||||
* results from a function and thus relies _solely_ on the
|
||||
* logic of the ternary operator (`? : `)
|
||||
* - when several LValue-reference types are given, the result
|
||||
* is only a reference when all results can be presented safely
|
||||
* through such a reference; in most cases the result falls back
|
||||
* to returning by-value.
|
||||
* @note Concepts are provided to require that a selection of types
|
||||
* is reconcilable, or even reconcilable into a reference type.
|
||||
* @see CommonResult_test
|
||||
*/
|
||||
template<typename...TYPES>
|
||||
struct CommonResult
|
||||
: detail::_ProbeCommonType<TYPES...>::Result
|
||||
{ };
|
||||
|
||||
template<typename T1>
|
||||
struct CommonResult<T1>
|
||||
: CommonResult<T1,T1>
|
||||
{ };
|
||||
|
||||
template<>
|
||||
struct CommonResult<>
|
||||
: std::false_type
|
||||
{ };
|
||||
|
||||
/** Specialisation to work transparently also on a type sequence,
|
||||
* passed-in as a single template parameter. */
|
||||
template<typename...TYPES>
|
||||
struct CommonResult<Types<TYPES...>>
|
||||
: CommonResult<TYPES...>
|
||||
{ };
|
||||
|
||||
|
||||
|
||||
template<typename...TYPES>
|
||||
using CommonResult_t = CommonResult<TYPES...>::Type;
|
||||
|
||||
template<typename...TYPES>
|
||||
static constexpr bool has_CommonResult_v = CommonResult<TYPES...>::value;
|
||||
|
||||
|
||||
|
||||
template<typename...TYPES>
|
||||
concept reconcilable = requires
|
||||
{
|
||||
typename CommonResult<TYPES...>::Type;
|
||||
};
|
||||
|
||||
template<typename...TYPES>
|
||||
concept reconcilable_ref = reconcilable<TYPES...>
|
||||
and isLRef_v<CommonResult_t<TYPES...>>;
|
||||
|
||||
|
||||
|
||||
template<typename...TYPES>
|
||||
struct CommonResultTraits
|
||||
{
|
||||
static_assert (reconcilable<TYPES...>, "unable to reconcile these types");
|
||||
|
||||
using CommonVal = std::common_type_t<TYPES...>;
|
||||
using ResType = CommonResult_t<TYPES...>;
|
||||
//NOTE: std::common_type decays (strips CV and ref)
|
||||
static constexpr bool makeConst = isConst_v<ResType>;
|
||||
static constexpr bool exposeRef = isLRef_v<ResType>;
|
||||
};
|
||||
|
||||
|
||||
|
||||
}} // namespace lib::meta
|
||||
#endif /*LIB_META_COMMON_RESULT_H*/
|
||||
|
|
@ -108,13 +108,15 @@
|
|||
* and check the static bool value field.
|
||||
* @warning none of these checks can detect private members
|
||||
*/
|
||||
#define META_DETECT_NESTED(_TYPE_) \
|
||||
template<typename TY> \
|
||||
class HasNested_##_TYPE_ \
|
||||
{ \
|
||||
\
|
||||
template<class X> \
|
||||
static Yes_t check(X::_TYPE_ *); \
|
||||
#define META_DETECT_NESTED(_TYPE_) \
|
||||
template<typename TY> \
|
||||
class HasNested_##_TYPE_ \
|
||||
{ \
|
||||
\
|
||||
template<class X \
|
||||
,typename = std::void_t<typename X::_TYPE_> \
|
||||
> \
|
||||
static Yes_t check(int); \
|
||||
template<class> \
|
||||
static No_t check(...); \
|
||||
\
|
||||
|
|
|
|||
237
src/lib/meta/fun-inline.hpp
Normal file
237
src/lib/meta/fun-inline.hpp
Normal file
|
|
@ -0,0 +1,237 @@
|
|||
/*
|
||||
FUN-INLINE.hpp - abstract functor with local delegate
|
||||
|
||||
Copyright (C)
|
||||
2026 Hermann Vosseler <Ichthyostega@web.de>
|
||||
|
||||
**Lumiera** 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. See the file COPYING for further details.
|
||||
|
||||
*/
|
||||
|
||||
/** @file fun-inline.hpp
|
||||
** A generic abstracted functor with the implementation stored locally.
|
||||
** This template is an implementation building block and allows to create
|
||||
** an abstraction barrier, similar to what std::function does. In contrast
|
||||
** to the latter however, FunInline places the actual implementation delegate
|
||||
** into an embedded opaque buffer, never using any heap memory.
|
||||
**
|
||||
** @todo WIP-WIP 4/2026 this is more like a conceptual draft,
|
||||
** not sure where this design exercise will lead to.
|
||||
** @warning after investigating the implementation technique to some degree,
|
||||
** it became clear that **copy support** is the greatest challenge.
|
||||
** All »the rest« might be surprisingly simple to implement, yet
|
||||
** the demand for a movable / copyable value-object effectively
|
||||
** translates into something like a VTable — and the result
|
||||
** would be similar to lib::PolymorphicValue — just with
|
||||
** some special binding to the function operator.
|
||||
** For this reason, FunInline is now declared to be util::NonCopyable
|
||||
** @todo 4/2026 I've finally got this draft hammered out.
|
||||
** It is far from what I wanted to achieve...
|
||||
** Notably a second function pointer is used
|
||||
** That is ugly, but it does the job.
|
||||
**
|
||||
** @see FunInline_test
|
||||
**
|
||||
*/
|
||||
|
||||
|
||||
#ifndef LIB_META_FUN_INLINE_H_
|
||||
#define LIB_META_FUN_INLINE_H_
|
||||
|
||||
|
||||
#include "lib/error.hpp"
|
||||
#include "lib/nocopy.hpp"
|
||||
#include "lib/meta/function.hpp"
|
||||
#include "lib/meta/typeseq-util.hpp"
|
||||
#include "lib/meta/variadic-helper.hpp"
|
||||
|
||||
#include <utility>
|
||||
|
||||
|
||||
namespace lib {
|
||||
namespace meta{
|
||||
namespace err = lumiera::error;
|
||||
|
||||
using std::forward;
|
||||
|
||||
namespace test { class FunInline_test; }
|
||||
|
||||
/**
|
||||
* An opaque front-end for a functor stored inline.
|
||||
* The type of the implementation delegate is erased
|
||||
* from the type, retaining only the generic signature.
|
||||
* @note this wrapper object is defined as NonCopyable,
|
||||
* because anything else would require a VTable,
|
||||
* and raise serious concerns regarding identity.
|
||||
* @warning deliberately **not threadsafe**
|
||||
* @see FunInline_test
|
||||
*/
|
||||
template<class SIG
|
||||
,size_t siz
|
||||
,typename ALI = int64_t
|
||||
>
|
||||
class FunInline
|
||||
{
|
||||
static_assert (!sizeof(SIG), "function signature required");
|
||||
};
|
||||
|
||||
|
||||
template<typename RET, typename...ARGS
|
||||
,size_t siz
|
||||
,typename ALI
|
||||
>
|
||||
class FunInline<RET(ARGS...), siz, ALI>
|
||||
: util::NonCopyable
|
||||
{
|
||||
using _F = _Fun<RET(ARGS...)>;
|
||||
static_assert (_F{}, "function signature required");
|
||||
|
||||
using Sig = _F::Sig;
|
||||
using Ret = _F::Ret;
|
||||
using Args = _F::Args;
|
||||
using RArgs = ElmTypes<Args>::template Apply<std::add_rvalue_reference_t>;
|
||||
using IArgs = Prepend<void*, RArgs>::Seq;
|
||||
using ISig = BuildFunType<Ret, IArgs>::Sig;
|
||||
using Dtor = void(void*);
|
||||
|
||||
/** virtualised delegation through function pointer(s) */
|
||||
ISig* invoker_{nullptr};
|
||||
Dtor* destroy_{nullptr};
|
||||
|
||||
/** opaque storage buffer to emplace the functor delegate */
|
||||
alignas(ALI) mutable
|
||||
std::byte buf_[siz];
|
||||
|
||||
|
||||
/**
|
||||
* The Invoker template is instantiated with the type of the concrete
|
||||
* delegate functor (which resides in the opaque storage buffer).
|
||||
* The purpose of this template is to cause generation of a static
|
||||
* delegation function that can be invoked through the #invoker_ pointer.
|
||||
* In the typical usage situation, the delegate type \a FUNC is implemented
|
||||
* mostly with inline functions, and thus the bulk of the actual implementation
|
||||
* code will be emitted into this static delegation function. The template
|
||||
* \ref InvokerBuilder provides the blueprint for this code generation.
|
||||
*/
|
||||
template<class FUN>
|
||||
struct Invoker
|
||||
{
|
||||
static Ret
|
||||
dispatch (void* instance, ARGS&&...args)
|
||||
{
|
||||
return functor(instance) (forward<ARGS> (args)...);
|
||||
}
|
||||
|
||||
static void
|
||||
destroy (void* instance)
|
||||
{
|
||||
if (instance)
|
||||
functor(instance).~FUN();
|
||||
}
|
||||
|
||||
static FUN&
|
||||
functor (void* instance)
|
||||
{
|
||||
REQUIRE (instance);
|
||||
return * static_cast<FUN*> (instance);
|
||||
}
|
||||
|
||||
using Sig = _F::Sig;
|
||||
using ISig = decltype(dispatch);
|
||||
};
|
||||
|
||||
/** allow the Unit test to poke into internals */
|
||||
friend class test::FunInline_test;
|
||||
|
||||
|
||||
|
||||
public:
|
||||
~FunInline(){ destroy(); }
|
||||
|
||||
FunInline() = default;
|
||||
|
||||
template<class FUNC>
|
||||
FunInline (FUNC&& functor)
|
||||
{
|
||||
using Functor = std::decay_t<FUNC>;
|
||||
emplace<Functor> (forward<FUNC> (functor));
|
||||
activate<Functor>();
|
||||
}
|
||||
|
||||
// NonCopyable
|
||||
|
||||
|
||||
operator bool() const
|
||||
{
|
||||
return bool(destroy_) and bool(invoker_);
|
||||
}
|
||||
|
||||
|
||||
/* == Functor interface == */
|
||||
|
||||
Ret
|
||||
operator() (ARGS&&... args)
|
||||
{
|
||||
if (not invoker_)
|
||||
throw err::State{"Attempt to invoke inactive function."};
|
||||
|
||||
return (*invoker_) (&buf_, forward<ARGS> (args)...);
|
||||
}
|
||||
|
||||
|
||||
private: /* == Instance management == */
|
||||
|
||||
void
|
||||
destroy()
|
||||
{
|
||||
if (destroy_)
|
||||
(*destroy_) (&buf_);
|
||||
destroy_ = nullptr;
|
||||
invoker_ = nullptr;
|
||||
}
|
||||
|
||||
template<class FUNC>
|
||||
void
|
||||
activate()
|
||||
{
|
||||
ASSERT_VALID_SIGNATURE (FUNC, Sig);
|
||||
REQUIRE (!destroy_ and !invoker_, "Lifecycle logic broken");
|
||||
|
||||
destroy_ = Invoker<FUNC>::destroy;
|
||||
invoker_ = Invoker<FUNC>::dispatch;
|
||||
}
|
||||
|
||||
/** Abbreviation for placement new */
|
||||
template<class TY, typename...INIT>
|
||||
TY&
|
||||
emplace (INIT&& ...args)
|
||||
{
|
||||
static_assert (siz >= sizeof(TY), "Embedded storage buffer too small");
|
||||
static_assert (alignof(ALI) >= alignof(TY), "Alignment requirements not fulfilled");
|
||||
|
||||
destroy();
|
||||
try {
|
||||
return *new(&buf_) TY {std::forward<INIT> (args)...};
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
ENSURE (not destroy_);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// @todo unused ... not required?
|
||||
template<typename XXX>
|
||||
XXX&
|
||||
accessAs() const
|
||||
{
|
||||
return * std::launder (reinterpret_cast<XXX*> (&buf_));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}}// namespace lib::meta
|
||||
#endif /*LIB_META_FUN_INLINE_H_*/
|
||||
125
src/lib/meta/fun-trampoline.hpp
Normal file
125
src/lib/meta/fun-trampoline.hpp
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
FUN-TRAMPOLINE.hpp - generate static functor table
|
||||
|
||||
Copyright (C)
|
||||
2026 Hermann Vosseler <Ichthyostega@web.de>
|
||||
|
||||
**Lumiera** 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. See the file COPYING for further details.
|
||||
|
||||
*/
|
||||
|
||||
/** @file fun-trampoline.hpp
|
||||
** Build a trampoline table with functors.
|
||||
** This Metaprogramming helper uses a _builder template_ to fill an embedded
|
||||
** static dispatcher table with generated functors. These can then be accessed
|
||||
** by index, typically to be invoked.
|
||||
**
|
||||
** The setup works entirely on a static level, so that any mention of some
|
||||
** FunTrampoline template instantiation causes the compiler to populate the
|
||||
** corresponding functor table. This arrangement can be used, for example,
|
||||
** to auto-generate an index accessor for a specific tuple, so that a
|
||||
** runtime subscript or iteration can be derived. Obviously doing so
|
||||
** requires the types in the tuple to be [reconcilable](\ref common-type.hpp),
|
||||
** so that a generic functor can be defined to handle all fields in the tuple
|
||||
** based on a common generic scheme. Without that, tuples can in general
|
||||
** only be iterated and transformed by compile-time metaprogramming.
|
||||
**
|
||||
** @see FunTrampoline_test
|
||||
** @see CommonResult_test
|
||||
** @see tuple-idx-adaptor.hpp
|
||||
*/
|
||||
|
||||
|
||||
#ifndef LIB_META_FUN_TRAMPOLINE_H
|
||||
#define LIB_META_FUN_TRAMPOLINE_H
|
||||
|
||||
|
||||
#include "include/logging.h"
|
||||
#include "lib/meta/function.hpp"
|
||||
#include "lib/meta/variadic-helper.hpp"
|
||||
|
||||
#include <utility>
|
||||
#include <array>
|
||||
|
||||
|
||||
namespace lib {
|
||||
namespace meta {
|
||||
|
||||
/**
|
||||
* FunTrampoline relies on a helper template
|
||||
* to be used as _trampoline builder_ to generate
|
||||
* a sequence of functors that can be placed into a
|
||||
* static dispatcher table. Thus, a trampoline_builder
|
||||
* - must be default constructible
|
||||
* - must define a Functor type...
|
||||
* - that can be used as array element
|
||||
* - and must provide a templated build<idx>() function
|
||||
*/
|
||||
template<class B>
|
||||
concept trampoline_builder = requires(B builder)
|
||||
{
|
||||
typename B::Functor;
|
||||
std::array<typename B::Functor, 1>{};
|
||||
requires std::default_initializable<B>;
|
||||
{ builder.template build<0>() } -> std::convertible_to<typename B::Functor>;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A static functor table, populated automatically.
|
||||
* @tparam BUILD helper Template to generate functors
|
||||
* that are placed into a static table
|
||||
* @tparam TSEQ sequence of types to instantiate
|
||||
* the functors with, iteratively.
|
||||
* @remarks
|
||||
* - the given type sequence is handled with the
|
||||
* ElmTypes helper template (see \ref variadic-helper.hpp)
|
||||
* - the BUILD template is instantiated with that \a TSEQ
|
||||
* - statically-for-each-index, the build<idx>() function
|
||||
* on the builder is invoked, which is expected to produce
|
||||
* an instance of BUILD::Functor
|
||||
* @warning as soon as some instance of this FunTrampoline
|
||||
* is used / referred, the compiler will emit the trampoline
|
||||
* table into static storage; the resulting increase of the
|
||||
* executable size is something to be aware of, when using
|
||||
* this construct for transient processing, driven by
|
||||
* actual argument types.
|
||||
*/
|
||||
template<template<class> class BUILD, typeseq TSEQ>
|
||||
requires trampoline_builder<BUILD<TSEQ>>
|
||||
class FunTrampoline
|
||||
{
|
||||
using AllElm = ElmTypes<TSEQ>;
|
||||
using Indices = AllElm::Idx;
|
||||
using Builder = BUILD<TSEQ>;
|
||||
using Functor = Builder::Functor;
|
||||
using Table = std::array<Functor, AllElm::SIZ>;
|
||||
|
||||
template<size_t...idx>
|
||||
static constexpr Table
|
||||
buildFunctors (std::index_sequence<idx...>)
|
||||
{
|
||||
return {Builder().template build<idx>()...};
|
||||
}
|
||||
|
||||
static constexpr Table functors = buildFunctors (Indices{});
|
||||
|
||||
public:
|
||||
static constexpr size_t size() { return functors.size(); }
|
||||
static constexpr size_t memSiz() { return sizeof(functors);}
|
||||
|
||||
static Functor const&
|
||||
dispatch (size_t idx)
|
||||
{
|
||||
ENSURE (idx < functors.size());
|
||||
return functors[idx];
|
||||
}
|
||||
|
||||
using ResType = _Fun<Functor>::Ret;
|
||||
};
|
||||
|
||||
}}// namespace lib::meta
|
||||
#endif /*LIB_META_FUN_TRAMPOLINE_H*/
|
||||
|
|
@ -41,7 +41,7 @@ This code is heavily inspired by
|
|||
** instantiation is mixed or inherited into the resulting product.
|
||||
**
|
||||
** @see generator-test.cpp
|
||||
** @see lumiera::query::ConfigRules usage example
|
||||
** @see vessel::query::ConfigRules usage example
|
||||
** @see typelist.hpp
|
||||
**
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,56 +0,0 @@
|
|||
/*
|
||||
MAYBE-COMPARE.hpp - guarded invocation of comparisons
|
||||
|
||||
Copyright (C)
|
||||
2009, Hermann Vosseler <Ichthyostega@web.de>
|
||||
|
||||
**Lumiera** 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. See the file COPYING for further details.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
/** @file maybe-compare.hpp
|
||||
** helpers for fail-safe invocation of comparison operations from generic code.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef LIB_META_MAYBE_COMPARE_H
|
||||
#define LIB_META_MAYBE_COMPARE_H
|
||||
|
||||
|
||||
|
||||
|
||||
namespace lib {
|
||||
namespace meta{
|
||||
|
||||
|
||||
/**
|
||||
* Trait template for invoking equality comparison.
|
||||
* This allows to treat some types specifically....
|
||||
*/
|
||||
template<typename X>
|
||||
struct Comparator
|
||||
{
|
||||
static bool
|
||||
equals (X const& x1, X const& x2)
|
||||
{
|
||||
return x1 == x2;
|
||||
}
|
||||
};
|
||||
|
||||
/* == add specialisations here == */
|
||||
|
||||
|
||||
template<typename X>
|
||||
inline bool
|
||||
equals_safeInvoke (X const& x1, X const& x2)
|
||||
{
|
||||
return Comparator<X>::equals(x1,x2);
|
||||
}
|
||||
|
||||
|
||||
}} // namespace lib::meta
|
||||
#endif
|
||||
147
src/lib/meta/prop-builder.hpp
Normal file
147
src/lib/meta/prop-builder.hpp
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
PROP-BUILDER.hpp - generate data record with arbitrary named fields
|
||||
|
||||
Copyright (C)
|
||||
2026, Hermann Vosseler <Ichthyostega@web.de>
|
||||
|
||||
**Lumiera** 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. See the file COPYING for further details.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
/** @file prop-builder.hpp
|
||||
** A builder for compile-time generation of data records with arbitrary named fields.
|
||||
** Generally speaking, a "record" in this context means just a struct or class with some
|
||||
** data fields, and can be easily defined to suit any needs. However, such a class definition
|
||||
** always incurs some _syntactical overhead_ — which is fine for spelling out some type in
|
||||
** general, but can be problematic when the data record is only used once, locally and
|
||||
** is without further significance beyond that single usage. Such a situation often
|
||||
** arises in the context of _policy based design_ and flexible configuration of
|
||||
** some generic facility, especially when part of this configuration involves
|
||||
** the definition of _callback functions._
|
||||
**
|
||||
** A common requirement that lends itself well to policy based design arises when such callbacks
|
||||
** establish a deeply interwoven link into local implementation details; this kind of interconnected
|
||||
** implementation can be expressed succinctly through Lambda functions with contextual capture, and
|
||||
** allows to setup a tight interconnection between implementation back-ends, without exposing any
|
||||
** of these implementation details at the interface / dependency-injection level. However, the task
|
||||
** to package several such specific callbacks into a policy instance and pass this configured policy
|
||||
** for creation of service instances, can be surprisingly complex, verbose, confusing and leads
|
||||
** to code that is hard to maintain. The reasons for this challenge are manifold:
|
||||
** - contextual capture works only well from directly within an implementation function
|
||||
** - such a setup can not be expressed well through types and interfaces, since the
|
||||
** generated closure is _essentially a value_ and its type can not be spelled out.
|
||||
** - a policy with several callbacks thus turns into a template with several type parameters,
|
||||
** and must be initialised through a likewise template based constructor; the resulting code
|
||||
** tends to become very technical, verbose, surprisingly tricky and hard to decipher for
|
||||
** any reader not familiar with all the intricate details of the implementation.
|
||||
**
|
||||
** The root of these difficulties of proper formulation seems to be related to the nature
|
||||
** of cohesive, closely interwoven implementation code. As long as this kind of structure
|
||||
** can be kept close together, it works surprisingly well and expresses intent clearly.
|
||||
** Yet as soon as it must be taken apart, expressed in names, types and interfaces,
|
||||
** a high conceptual burden is added, which leads to the danger of generalisation
|
||||
** and re-use beyond the very specific case, to which the original formulation
|
||||
** of the problem was well adapted and adequate.
|
||||
**
|
||||
** The solution promoted here thus aims to remove the need to define any type and interface
|
||||
** explicitly; a builder-expression is created rather, which allows to bind all callbacks
|
||||
** and further parameters directly inline, within a single constructor invocation.
|
||||
** Such a setup becomes possibly through a combination of some C++20 features, linked
|
||||
** together by lambda expressions generated from a _macro_.
|
||||
** - the PropBuilder acts as base class for the generated records, and is used to
|
||||
** initiate the build-up of the definition expression.
|
||||
** - several »layers« of derived classes are added step by step, where each such
|
||||
** layer adds one further _property_ attribute, that is initialised by a given value.
|
||||
** - the _junctor_ to combine those layer is provided by the template function
|
||||
** `PropBuilder::define(builder-λ, value)`. It uses the new _explicit object_
|
||||
** syntax introduced with C++20, to pick up the "self type" as template argument.
|
||||
** - by invoking this builder function, the current "this"-object is _moved_ into
|
||||
** the builder, where it is used to create and initialise a derived class.
|
||||
** - for each »layer« a specific lambda is generated by a macro — and it is this
|
||||
** technique that allows to accept a lexical token form the program text and
|
||||
** use this token as field name in the next derived class to generate.
|
||||
** @see PropBuilder_test for detailed explanation
|
||||
** @see OutputProxyProvider_test for a usage example
|
||||
*/
|
||||
|
||||
|
||||
#ifndef LIB_META_PROP_BUILDER_H
|
||||
#define LIB_META_PROP_BUILDER_H
|
||||
|
||||
|
||||
#include "lib/meta/trait.hpp"
|
||||
|
||||
#include <utility>
|
||||
|
||||
|
||||
namespace lib {
|
||||
namespace meta{
|
||||
|
||||
using std::move;
|
||||
using std::forward;
|
||||
|
||||
|
||||
/**
|
||||
* Anchor record and builder mechanics for generating data records.
|
||||
* - start a builder expression by constructing an anonymous `PropBulder()`
|
||||
* - invoke PropBuilder::define() for each further property to add
|
||||
* - pass a builder-λ as first argument, and the data value as second
|
||||
* - generate a suitable builder-λ with the macro `PROP_FIELD(fieldname)`
|
||||
* - overall, the builder expression generates a RValue, that will usually
|
||||
* be passed directly to the constructor of another class.
|
||||
* @see PropBuilder_test::simpleUsage
|
||||
*/
|
||||
struct PropBuilder
|
||||
{
|
||||
/**
|
||||
* Core builder operation: invoke a builder-λ to extend »this«
|
||||
* @param layerBuilder a specially crafted invokable, that takes _this object_
|
||||
* and the given property value and creates an instance of a derived class.
|
||||
* @note the macro \ref PROP_FIELD below will generate a suitable argument.
|
||||
* @param val an arbitrary (movable) data element to be stored as property.
|
||||
* @remark C++20 introduced the syntax for _explicit object member functions_,
|
||||
* which allow to bind the "self" object instance to an explicitly defined
|
||||
* first function parameter. Since this builder operation will be invoked
|
||||
* on the generated compound object, the template parameter \a BAS will
|
||||
* denote this compound, and the next builder-λ will derive from it.
|
||||
*/
|
||||
template<class BAS, class LAY, typename VAL>
|
||||
auto
|
||||
define (this BAS&& chain, LAY const& layerBuilder, VAL&& val)
|
||||
{
|
||||
return layerBuilder (move(chain), forward<VAL>(val));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
#define PROP_FIELD(FIELD_NAME) \
|
||||
[]<class BAS, typename VAL> \
|
||||
(BAS&& bas, VAL&& val) \
|
||||
{ \
|
||||
using BaseLayer = std::decay_t<BAS>;\
|
||||
using Property = std::decay_t<VAL>; \
|
||||
\
|
||||
struct DataLayer_ ## FIELD_NAME \
|
||||
: BaseLayer \
|
||||
{ \
|
||||
Property FIELD_NAME; \
|
||||
\
|
||||
DataLayer_ ## FIELD_NAME (BaseLayer b \
|
||||
,Property v) \
|
||||
: BaseLayer{move(b)} \
|
||||
,FIELD_NAME{move(v)} \
|
||||
{ } \
|
||||
}; \
|
||||
return DataLayer_ ## FIELD_NAME {forward<BAS>(bas)\
|
||||
,forward<VAL>(val) \
|
||||
}; \
|
||||
}
|
||||
|
||||
|
||||
|
||||
}} // namespace lib::meta
|
||||
#endif /*LIB_META_PROP_BUILDER_H*/
|
||||
|
|
@ -57,9 +57,6 @@
|
|||
|
||||
|
||||
//Forward declarations for the Unwrap helper....
|
||||
namespace boost{
|
||||
template<class X> class reference_wrapper;
|
||||
}
|
||||
namespace std {
|
||||
template<class X> class reference_wrapper;
|
||||
template<class X> class shared_ptr;
|
||||
|
|
@ -90,9 +87,7 @@ namespace meta {
|
|||
using std::remove_pointer_t;
|
||||
using std::remove_reference_t;
|
||||
using std::conditional_t;
|
||||
using std::is_reference_v;
|
||||
using std::is_lvalue_reference_v;
|
||||
using std::is_rvalue_reference_v;
|
||||
using std::is_pointer_v;
|
||||
using std::is_pointer;
|
||||
using std::is_base_of;
|
||||
using std::is_convertible;
|
||||
|
|
@ -108,13 +103,22 @@ namespace meta {
|
|||
using std::__or_;
|
||||
|
||||
template<typename T>
|
||||
static constexpr bool isConst_v = std::is_const_v<remove_reference_t<T>>;
|
||||
using isConst = std::is_const<remove_reference_t<T>>;
|
||||
template<typename T>
|
||||
static constexpr bool isLRef_v = std::is_lvalue_reference_v<T>;
|
||||
using isLRef = std::is_lvalue_reference<T>;
|
||||
template<typename T>
|
||||
static constexpr bool isRRef_v = std::is_rvalue_reference_v<T>;
|
||||
using isRRef = std::is_rvalue_reference<T>;
|
||||
template<typename T>
|
||||
static constexpr bool isRef_v = std::is_reference_v<T>;
|
||||
using isRef = std::is_reference<T>;
|
||||
|
||||
template<typename T>
|
||||
static constexpr bool isConst_v = isConst<T>::value;
|
||||
template<typename T>
|
||||
static constexpr bool isLRef_v = isLRef<T>::value;
|
||||
template<typename T>
|
||||
static constexpr bool isRRef_v = isRRef<T>::value;
|
||||
template<typename T>
|
||||
static constexpr bool isRef_v = isRef<T>::value;
|
||||
|
||||
|
||||
/**
|
||||
|
|
@ -159,19 +163,6 @@ namespace meta {
|
|||
}
|
||||
};
|
||||
|
||||
template<typename X>
|
||||
struct Unwrap<boost::reference_wrapper<X>>
|
||||
: std::true_type
|
||||
{
|
||||
using Type = X;
|
||||
|
||||
static X&
|
||||
extract (boost::reference_wrapper<X> wrapped)
|
||||
{
|
||||
return wrapped;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename X>
|
||||
struct Unwrap<std::reference_wrapper<X>>
|
||||
: std::true_type
|
||||
|
|
@ -249,8 +240,8 @@ namespace meta {
|
|||
template<typename X>
|
||||
struct Strip
|
||||
{
|
||||
using TypeUnconst = conditional_t<is_reference_v<X>
|
||||
, conditional_t<is_rvalue_reference_v<X>
|
||||
using TypeUnconst = conditional_t<isRef_v<X>
|
||||
, conditional_t<isRRef_v<X>
|
||||
, remove_cv_t<remove_reference_t<X>> &&
|
||||
, remove_cv_t<remove_reference_t<X>> & >
|
||||
, remove_cv_t<X>>;
|
||||
|
|
@ -274,33 +265,33 @@ namespace meta {
|
|||
template<typename TY>
|
||||
struct RefTraits
|
||||
{
|
||||
typedef TY Value;
|
||||
typedef TY* Pointer;
|
||||
typedef TY& Reference;
|
||||
typedef TY value_type;
|
||||
typedef TY& reference;
|
||||
typedef TY* pointer;
|
||||
};
|
||||
|
||||
template<typename TY>
|
||||
struct RefTraits<TY *>
|
||||
{
|
||||
typedef TY* Value;
|
||||
typedef TY** Pointer;
|
||||
typedef TY*& Reference;
|
||||
typedef TY* value_type;
|
||||
typedef TY*& reference;
|
||||
typedef TY** pointer;
|
||||
};
|
||||
|
||||
template<typename TY>
|
||||
struct RefTraits<TY &>
|
||||
{
|
||||
typedef TY Value;
|
||||
typedef TY* Pointer;
|
||||
typedef TY& Reference;
|
||||
typedef TY value_type;
|
||||
typedef TY& reference;
|
||||
typedef TY* pointer;
|
||||
};
|
||||
|
||||
template<typename TY>
|
||||
struct RefTraits<TY &&>
|
||||
{
|
||||
typedef TY Value;
|
||||
typedef TY* Pointer;
|
||||
typedef TY& Reference;
|
||||
typedef TY value_type;
|
||||
typedef TY& reference;
|
||||
typedef TY* pointer;
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -328,7 +319,11 @@ namespace meta {
|
|||
, is_same<I,S>
|
||||
>
|
||||
{ };
|
||||
|
||||
|
||||
template<typename S, typename I>
|
||||
inline constexpr bool is_Subclass_v = is_Subclass<S,I>();
|
||||
|
||||
|
||||
/** compare for unadorned base type, disregarding const and references */
|
||||
template<typename S, typename I>
|
||||
struct is_basically
|
||||
|
|
@ -339,8 +334,8 @@ namespace meta {
|
|||
/** verify the first (special) type can stand-in for the second */
|
||||
template<typename S, typename G>
|
||||
struct can_StandIn
|
||||
: std::is_convertible<typename RefTraits<S>::Reference
|
||||
,typename RefTraits<G>::Reference
|
||||
: std::is_convertible<typename RefTraits<S>::reference
|
||||
,typename RefTraits<G>::reference
|
||||
>
|
||||
{ };
|
||||
|
||||
|
|
@ -505,24 +500,36 @@ namespace meta {
|
|||
* This is just a heuristic, based on some common properties
|
||||
* of such iterators; it is enough to distinguish it from an
|
||||
* STL container, but can certainly be refined.
|
||||
* @note deliberately a Lumiera iterator does _not fulfil_
|
||||
* the concept std::input_iterator. And vice versa:
|
||||
* a STL iterator lacks the `bool` check for iteration end.
|
||||
* @see IterableClassification_test
|
||||
*/
|
||||
template<typename T>
|
||||
class can_IterForEach
|
||||
class can_LumieraIter
|
||||
{
|
||||
using Type = Strip<T>::Type;
|
||||
using Type = Strip<T>::TypeReferred;
|
||||
|
||||
META_DETECT_NESTED(value_type);
|
||||
META_DETECT_NESTED(reference);
|
||||
META_DETECT_OPERATOR_DEREF();
|
||||
META_DETECT_OPERATOR_INC();
|
||||
|
||||
public:
|
||||
enum{ value = std::is_constructible<bool, Type>::value
|
||||
and HasNested_value_type<Type>::value
|
||||
and HasNested_reference<Type>::value
|
||||
and HasOperator_deref<Type>::value
|
||||
and HasOperator_inc<Type>::value
|
||||
};
|
||||
};
|
||||
|
||||
template<class IT>
|
||||
constexpr bool is_LumieraIter_v = meta::can_LumieraIter<IT>::value;
|
||||
|
||||
template<class IT>
|
||||
concept lum_iter = is_LumieraIter_v<IT>;
|
||||
|
||||
|
||||
|
||||
/** Trait template to detect a type exposing a »state core« API.
|
||||
|
|
@ -530,10 +537,10 @@ namespace meta {
|
|||
* with the help of lib::IterStateWrapper or lib::IterableDecorator.
|
||||
* This check is heuristic, based on the presence of function names.
|
||||
*/
|
||||
template<typename T>
|
||||
template<typename COR>
|
||||
class is_StateCore
|
||||
{
|
||||
using Type = Strip<T>::Type;
|
||||
using Type = Strip<COR>::TypeReferred;
|
||||
|
||||
META_DETECT_FUNCTION_ARGLESS(checkPoint);
|
||||
META_DETECT_FUNCTION_ARGLESS(iterNext);
|
||||
|
|
@ -546,15 +553,24 @@ namespace meta {
|
|||
};
|
||||
};
|
||||
|
||||
template<class COR>
|
||||
constexpr bool is_StateCore_v = is_StateCore<COR>::value;
|
||||
|
||||
template<class COR>
|
||||
concept state_core = is_StateCore_v<COR>;
|
||||
|
||||
|
||||
|
||||
/** Trait template to detect a type usable with the STL for-each loop.
|
||||
* Basically we're looking for the functions to get the begin/end iterator
|
||||
* @remark many Lumiera Iterators define those additional functions any
|
||||
* can thus be used in a for-each loop, since a default-constructed
|
||||
* Lumiera Iterator can act as an "end" sentinel.
|
||||
*/
|
||||
template<typename T>
|
||||
class can_STL_ForEach
|
||||
{
|
||||
using Type = Strip<T>::Type;
|
||||
using Type = Strip<T>::TypeReferred;
|
||||
|
||||
struct is_iterable
|
||||
{
|
||||
|
|
@ -618,7 +634,7 @@ namespace meta {
|
|||
template<typename T>
|
||||
class can_STL_backIteration
|
||||
{
|
||||
using Type = Strip<T>::Type;
|
||||
using Type = Strip<T>::TypeReferred;
|
||||
|
||||
struct is_backIterable
|
||||
{
|
||||
|
|
|
|||
305
src/lib/meta/tuple-concept.hpp
Normal file
305
src/lib/meta/tuple-concept.hpp
Normal file
|
|
@ -0,0 +1,305 @@
|
|||
/*
|
||||
TUPLE-CONCEPT.hpp - metaprogramming support for tuple-like data types
|
||||
|
||||
Copyright (C)
|
||||
2025, Hermann Vosseler <Ichthyostega@web.de>
|
||||
|
||||
**Lumiera** 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. See the file COPYING for further details.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
/** @file tuple-concept.hpp
|
||||
** Metaprogramming with tuples-of-types and the `std::tuple` record.
|
||||
** This header complements typelist.hpp and provides a bridge from type sequences
|
||||
** to the tuple type provided by the standard library, including traits and
|
||||
** helpers to build tuple types from metaprogramming and to pretty-print tuples.
|
||||
**
|
||||
** Notably, a `concept tuple_like` is provided here, which is satisfied for any type
|
||||
** in compliance with the »[tuple protocol]«. This solution reaches beyond the scope
|
||||
** of the C++ standard — and in fact opposes the decision of the C++ Committee
|
||||
** _not to provide such a concept_, as this can not be defined in full genericity
|
||||
** for any type that can be handled by the _structural bindings of the C++ language.
|
||||
**
|
||||
** Since the Lumiera support library is part of a specific application framework, and
|
||||
** does not intend to be a general purpose library, we prefer a partially complete
|
||||
** definition that allows us to subsume and handle custom types under a »Tuple« concept.
|
||||
** @note Unfortunately the C++ committee did not only decline to provide a tuple_like concept,
|
||||
** rather they choose to lock-down essential parts of the accompanying library to their
|
||||
** internal and hard-wired selection of what is considered "tuple-like". This forces us
|
||||
** to redefine and replace also the crucial function `std::apply`, so that a generic
|
||||
** function can be applied to any _tuple-like_ entity.
|
||||
** @see TupleHelper_test
|
||||
** @see tuple-helper.hpp extended tuple support functions
|
||||
** [tuple protocol]: https://en.cppreference.com/w/cpp/language/structured_binding.html#Case_2:_binding_a_type_implementing_the_tuple_operations
|
||||
*/
|
||||
|
||||
|
||||
#ifndef LIB_META_TUPLE_CONCEPT_H
|
||||
#define LIB_META_TUPLE_CONCEPT_H
|
||||
|
||||
#include "lib/meta/util.hpp"
|
||||
#include "lib/meta/variadic-helper.hpp"
|
||||
|
||||
#include <type_traits>
|
||||
#include <functional>
|
||||
#include <tuple>
|
||||
|
||||
|
||||
namespace lib {
|
||||
namespace meta {
|
||||
|
||||
using std::remove_cv_t;
|
||||
using std::is_reference_v;
|
||||
using std::remove_reference_t;
|
||||
|
||||
|
||||
/** @internal building-block: a type supporting the `tuple_size` metafunction */
|
||||
template<class TUP>
|
||||
concept tuple_sized = requires
|
||||
{
|
||||
{ std::tuple_size<TUP>::value } -> std::convertible_to<size_t>;
|
||||
};
|
||||
|
||||
|
||||
/** @internal building-block: a type where elements can be accessed through a `get` friend function */
|
||||
template<class TUP, std::size_t idx>
|
||||
concept tuple_adl_accessible = requires(TUP tup)
|
||||
{
|
||||
typename std::tuple_element_t<idx, TUP>;
|
||||
{ get<idx>(tup) } -> std::convertible_to<std::tuple_element_t<idx, TUP>&>;
|
||||
};
|
||||
|
||||
/** @internal building-block: a type where elements can be accessed through a `get` member function */
|
||||
template<class TUP, std::size_t idx>
|
||||
concept tuple_mem_accessible = requires(TUP tup)
|
||||
{
|
||||
typename std::tuple_element_t<idx, TUP>;
|
||||
{ tup.template get<idx>() } -> std::convertible_to<std::tuple_element_t<idx, TUP>&>;
|
||||
};
|
||||
|
||||
template<class TUP, std::size_t idx>
|
||||
concept tuple_element_accessible = tuple_mem_accessible<TUP,idx> or tuple_adl_accessible<TUP,idx>;
|
||||
|
||||
template<class TUP>
|
||||
concept tuple_accessible =
|
||||
tuple_sized<TUP> and
|
||||
WithIdxSeq<std::tuple_size_v<TUP>>::andAll([](auto idx)
|
||||
{
|
||||
return tuple_element_accessible<TUP,idx>;
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Concept to mark any type compliant to the »tuple protocol«
|
||||
* @note a type satisfying this concept can participate in _structured bindings_,
|
||||
* yet not every type that can be used in such bindings will fulfil this concept,
|
||||
* since structured bindings can also be applied to _some POD like_ data records.
|
||||
*/
|
||||
template<class TUP>
|
||||
concept tuple_like = not is_reference_v<TUP>
|
||||
and tuple_sized<remove_cv_t<TUP>>
|
||||
and tuple_accessible<remove_cv_t<TUP>>;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Concept to require an instance of std::tuple<TYPES...>`
|
||||
* @note other than `concept` \ref tuple_like, this aims
|
||||
* explicitly at the implementation form STDLIB.
|
||||
*/
|
||||
template<class TUP>
|
||||
concept tup = requires (TUP tup)
|
||||
{
|
||||
// check that a function can be invoked with a std::tuple<TYPES>
|
||||
[]<typename...TYPES>(std::tuple<TYPES...>){/*placeholder*/}(tup);
|
||||
};
|
||||
|
||||
template<class TUP>
|
||||
struct is_Tuple : std::bool_constant<tup<TUP>> { };
|
||||
|
||||
template<class TUP>
|
||||
static constexpr bool is_Tuple_v = is_Tuple<TUP>::value;
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Helper for abstracted / unified access to member elements of any _tuple-like_
|
||||
* @remark preferably uses a `get<i>` member function, falling back to a
|
||||
* free function `get`, which is found by ADL.
|
||||
*/
|
||||
template<std::size_t idx, class TUP>
|
||||
requires tuple_like<std::remove_reference_t<TUP>>
|
||||
decltype(auto)
|
||||
getElm (TUP&& tup)
|
||||
{
|
||||
using Tup = std::remove_reference_t<TUP>;
|
||||
static_assert (0 < std::tuple_size_v<Tup>);
|
||||
if constexpr (tuple_mem_accessible<Tup,0>)
|
||||
{
|
||||
if constexpr (std::is_reference_v<TUP>)
|
||||
return tup.template get<idx>();
|
||||
else
|
||||
{ // return value copy when tuple given as RValue
|
||||
using Elm = std::tuple_element_t<idx, TUP>;
|
||||
Elm elm(tup.template get<idx>());
|
||||
return elm;
|
||||
}
|
||||
}
|
||||
else
|
||||
{ // ▽▽▽ ADL
|
||||
using std::get;
|
||||
return get<idx> (std::forward<TUP> (tup));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Specialisation of variadic access for any tuple-like
|
||||
* @see variadic-helper.hpp
|
||||
*/
|
||||
template<tuple_like TUP>
|
||||
struct ElmTypes<TUP>
|
||||
{
|
||||
template<typename>
|
||||
struct Extract;
|
||||
template<size_t...idx>
|
||||
struct Extract<std::index_sequence<idx...>>
|
||||
{
|
||||
using ElmTypes = Types<std::tuple_element_t<idx,TUP> ...>;
|
||||
};
|
||||
|
||||
static constexpr size_t SIZ = std::tuple_size_v<TUP>;
|
||||
|
||||
using Idx = std::make_index_sequence<SIZ>;
|
||||
using Seq = Extract<Idx>::ElmTypes;
|
||||
using Tup = RebindVariadic<std::tuple, Seq>::Type;
|
||||
|
||||
template<template<class> class META>
|
||||
using Apply = ElmTypes<Seq>::template Apply<META>;
|
||||
|
||||
template<template<typename...> class O>
|
||||
using Rebind = RebindVariadic<O, Seq>::Type;
|
||||
|
||||
template<template<class> class PRED>
|
||||
using AndAll = ElmTypes<Apply<PRED>>::template Rebind<std::__and_>;
|
||||
|
||||
template<template<class> class PRED>
|
||||
using OrAny = ElmTypes<Apply<PRED>>::template Rebind<std::__or_>;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
namespace { // apply to tuple-like : helpers...
|
||||
|
||||
/** @internal invocation helper similar to C++17 — but preferring a custom `get` impl */
|
||||
template<typename FUN, typename TUP, size_t...Idx>
|
||||
constexpr decltype(auto)
|
||||
__unpack_and_apply (FUN&& f, TUP&& tup, std::index_sequence<Idx...>)
|
||||
{
|
||||
return std::invoke (std::forward<FUN> (f)
|
||||
,getElm<Idx> (std::forward<TUP>(tup))...
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/** @internal invoke a metafunction with \a FUN and all element types from the _tuple-like_ \a TUP */
|
||||
template<template<typename...> class META, class FUN, class TUP>
|
||||
struct _InvokeMetafunTup
|
||||
{
|
||||
using Tupl = std::decay_t<TUP>;
|
||||
using Elms = ElmTypes<Tupl>::Seq;
|
||||
using Args = Prepend<FUN, Elms>::Seq;
|
||||
using Type = RebindVariadic<META, Args>::Type;
|
||||
};
|
||||
template<template<typename...> class META, class FUN, class TUP>
|
||||
struct _InvokeMetafunTup<META, FUN, TUP&>
|
||||
{
|
||||
using Tupl = std::decay_t<TUP>;
|
||||
using Elms = typename ElmTypes<Tupl>::template Apply<std::add_lvalue_reference_t>;
|
||||
using Args = Prepend<FUN, Elms>::Seq;
|
||||
using Type = RebindVariadic<META, Args>::Type;
|
||||
};
|
||||
|
||||
template<class FUN, class TUP>
|
||||
inline constexpr bool can_nothrow_invoke_tup = _InvokeMetafunTup<std::is_nothrow_invocable,FUN,TUP>::Type::value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replacement for `std::apply` — yet applicable to _tuple-like custom types_.
|
||||
* For unclear reasons, the standard chooses to reject such custom types, and
|
||||
* only allows a fixed set of explicitly defined facilities from the Stdlib
|
||||
* (tuple, pair, array, and some ranges stuff).
|
||||
* @todo 6/2025 as a first step, this replicates the implementation from C++17;
|
||||
* the second step would be to constrain this to a concept `tuple_like`
|
||||
*/
|
||||
template<class FUN, class TUP> requires tuple_like<remove_reference_t<TUP>>
|
||||
constexpr decltype(auto)
|
||||
apply (FUN&& f, TUP&& tup) noexcept (can_nothrow_invoke_tup<FUN,TUP> )
|
||||
{
|
||||
using Indices = std::make_index_sequence<std::tuple_size_v<std::remove_reference_t<TUP>>>;
|
||||
|
||||
return __unpack_and_apply (std::forward<FUN> (f)
|
||||
,std::forward<TUP> (tup)
|
||||
,Indices{}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tuple iteration: perform some arbitrary operation on each element of a tuple.
|
||||
* @note the given functor must be generic, since each position of the tuple
|
||||
* may hold a data element of different type.
|
||||
* @remark credits to David Vandevoorde (member of C++ committee) for using
|
||||
* std::apply to unpack the tuple's contents into an argument pack and
|
||||
* then employ a fold expression with the comma operator.
|
||||
*/
|
||||
template<class TUP, class FUN> requires tuple_like<remove_reference_t<TUP>>
|
||||
constexpr void
|
||||
forEach (TUP&& tuple, FUN fun)
|
||||
{
|
||||
lib::meta::apply ([&fun]<typename...ELMS>(ELMS&&... elms)
|
||||
{
|
||||
(fun (std::forward<ELMS>(elms)), ...);
|
||||
}
|
||||
,std::forward<TUP> (tuple));
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply some arbitrary function onto all elements of a tuple.
|
||||
* @return a new tuple constructed from the results of this function
|
||||
* @note the functor must be generic and able to work with all element types
|
||||
* @warning pay attention to references; the function can take arguments by-ref
|
||||
* and manipulate them (side-effect), and it may return references,
|
||||
* which will be placed as such into the result tuple; furthermore,
|
||||
* the argument tuple can also be taken as reference...
|
||||
* @remark The tuple constructor invocation is preceded by a regular argument evaluation,
|
||||
* which has unspecified evaluation order (even in C++17); _no assumptions_ can be
|
||||
* made regarding the order the functor will see the source tuple elements.
|
||||
* Notably this differs from #forEach, where a fold-expression with comma-operator
|
||||
* is used, which is guaranteed to evaluate from left to right.
|
||||
*/
|
||||
template<class TUP, class FUN> requires tuple_like<remove_reference_t<TUP>>
|
||||
constexpr auto
|
||||
mapEach (TUP&& tuple, FUN fun)
|
||||
{
|
||||
return lib::meta::apply ([&fun]<typename...ELMS>(ELMS&&... elms)
|
||||
{ //..construct the type explicitly (make_tuple would decay fun result types)
|
||||
using Tuple = std::tuple<decltype(fun (std::forward<ELMS>(elms))) ...>;
|
||||
return Tuple (fun (std::forward<ELMS>(elms)) ...);
|
||||
}
|
||||
,std::forward<TUP> (tuple));
|
||||
}
|
||||
|
||||
}} // namespace lib::meta
|
||||
#endif /*LIB_META_TUPLE_CONCEPT_H*/
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
TUPLE-HELPER.hpp - metaprogramming utilities for type and data tuples
|
||||
|
||||
Copyright (C)
|
||||
2016, Hermann Vosseler <Ichthyostega@web.de>
|
||||
2016,2025 Hermann Vosseler <Ichthyostega@web.de>
|
||||
|
||||
**Lumiera** is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU General Public License as published by the
|
||||
|
|
@ -18,17 +18,38 @@
|
|||
** to the tuple type provided by the standard library, including traits and
|
||||
** helpers to build tuple types from metaprogramming and to pretty-print tuples.
|
||||
**
|
||||
** Notably, a `concept tuple_like` is provided, which is satisfied for any type in compliance
|
||||
** with the »tuple protocol«. Together with a [generic accessor][\ref lib::meta::getElm],
|
||||
** this allows to handle all _tuple-like_ types uniformly.
|
||||
** @note Due to an unfortunate limitation of the standard, we're forced to provide our own alternative
|
||||
** implementation to replace `std::apply`, so that a function can be applied to any _tuple-like_
|
||||
** Notably the handling of tuples is based on a **Concept** `tuple_like`,
|
||||
** that is satisfied for any type in compliance with the »[tuple protocol]«.
|
||||
** Together with a [generic accessor][\ref lib::meta::getElm], this allows
|
||||
** to handle all _tuple-like_ types uniformly.
|
||||
** This solution reaches beyond the scope of the C++ standard, which declines
|
||||
** to define such a concept and rather provides an "exposition-only" concept
|
||||
** that is limited to a hard-wired selection of library types, yet does not
|
||||
** cover custom user provided types able to participate in _structured bindings_.
|
||||
** @note Due to that unfortunate limitation of the standard, we're forced to
|
||||
** provide our own alternative implementation of `std::apply`.
|
||||
**
|
||||
** Furthermore, a generic iteration construct is provided, to instantiate
|
||||
** a generic Lambda for each element of a given tuple, which allows to write
|
||||
** generic code »for each tuple element«.
|
||||
**
|
||||
** @see control::CommandDef usage example
|
||||
** @remark 2026-03 this header underwent some significant rework in recent years.
|
||||
** The Lumiera project had developed a `Tuple` type way before something similar
|
||||
** was considered for inclusion into the C++ language standard; flexible and
|
||||
** specifically typed storage records were used, in conjunction with LISP-style
|
||||
** metaprogramming (»Loki Typelists«), from the very start of the project.
|
||||
** Storing an arbitrary function, combined with a tuple of arguments, was a
|
||||
** major undertaking at those days, and required a huge body of support
|
||||
** functionality — which needed to be remoulded gradually, over the
|
||||
** following years, to become compatible with newer C++ features.
|
||||
** - basic handling, like access and compile-time iteration became trivial
|
||||
** - however, the project still relies on specific metaprogramming tools,
|
||||
** to simplify handling and transforming of variadic metafunctions.
|
||||
**
|
||||
** [tuple protocol]: https://en.cppreference.com/w/cpp/language/structured_binding.html#Case_2:_binding_a_type_implementing_the_tuple_operations
|
||||
** @see variadic-helper.hpp
|
||||
** @see tuple-closure.hpp : usage example
|
||||
** @see feed-manifold.hpp : use of unified variadics and tuples
|
||||
** @see TupleHelper_test
|
||||
** @see typelist.hpp
|
||||
** @see function.hpp
|
||||
|
|
@ -40,268 +61,31 @@
|
|||
#ifndef LIB_META_TUPLE_HELPER_H
|
||||
#define LIB_META_TUPLE_HELPER_H
|
||||
|
||||
#include "lib/meta/typelist.hpp"
|
||||
#include "lib/integral.hpp"
|
||||
#include "lib/meta/tuple-concept.hpp"
|
||||
#include "lib/meta/variadic-helper.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 <tuple>
|
||||
#include <utility>
|
||||
#include <sstream>
|
||||
#include <functional>
|
||||
|
||||
|
||||
namespace util { // forward declaration
|
||||
namespace util { // forward -> see lib/format-obj.hpp ...
|
||||
|
||||
template<typename TY>
|
||||
std::string
|
||||
toString (TY const& val) noexcept;
|
||||
}
|
||||
std::string toString (TY const&) noexcept;
|
||||
|
||||
template<typename X, typename COND>
|
||||
struct StringConv;
|
||||
}
|
||||
|
||||
namespace lib {
|
||||
namespace meta {
|
||||
|
||||
/** trait to detect tuple types */
|
||||
template<typename T>
|
||||
struct is_Tuple
|
||||
: std::false_type
|
||||
{ };
|
||||
|
||||
template<typename...TYPES>
|
||||
struct is_Tuple<std::tuple<TYPES...>>
|
||||
: std::true_type
|
||||
{ };
|
||||
|
||||
template<typename...TYPES>
|
||||
struct is_Tuple<const std::tuple<TYPES...>>
|
||||
: std::true_type
|
||||
{ };
|
||||
|
||||
using std::remove_cv_t;
|
||||
using std::is_reference_v;
|
||||
using std::remove_reference_t;
|
||||
|
||||
|
||||
/** @internal building-block: a type supporting the `tuple_size` metafunction */
|
||||
template<class TUP>
|
||||
concept tuple_sized = requires
|
||||
{
|
||||
{ std::tuple_size<TUP>::value } -> std::convertible_to<size_t>;
|
||||
};
|
||||
|
||||
|
||||
/** @internal building-block: a type where elements can be accessed through a `get` friend function */
|
||||
template<class TUP, std::size_t idx>
|
||||
concept tuple_adl_accessible = requires(TUP tup)
|
||||
{
|
||||
typename std::tuple_element_t<idx, TUP>;
|
||||
{ get<idx>(tup) } -> std::convertible_to<std::tuple_element_t<idx, TUP>&>;
|
||||
};
|
||||
|
||||
/** @internal building-block: a type where elements can be accessed through a `get` member function */
|
||||
template<class TUP, std::size_t idx>
|
||||
concept tuple_mem_accessible = requires(TUP tup)
|
||||
{
|
||||
typename std::tuple_element_t<idx, TUP>;
|
||||
{ tup.template get<idx>() } -> std::convertible_to<std::tuple_element_t<idx, TUP>&>;
|
||||
};
|
||||
|
||||
template<class TUP, std::size_t idx>
|
||||
concept tuple_element_accessible = tuple_mem_accessible<TUP,idx> or tuple_adl_accessible<TUP,idx>;
|
||||
|
||||
template<class TUP>
|
||||
concept tuple_accessible =
|
||||
tuple_sized<TUP> and
|
||||
WithIdxSeq<std::tuple_size_v<TUP>>::andAll([](auto idx)
|
||||
{
|
||||
return tuple_element_accessible<TUP,idx>;
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Concept to mark any type compliant to the »tuple protocol«
|
||||
*/
|
||||
template<class TUP>
|
||||
concept tuple_like = not is_reference_v<TUP>
|
||||
and tuple_sized<remove_cv_t<TUP>>
|
||||
and tuple_accessible<remove_cv_t<TUP>>;
|
||||
|
||||
|
||||
/**
|
||||
* Helper for abstracted / unified access to member elements of any _tuple-like_
|
||||
* @remark preferably uses a `get<i>` member function, falling back to a
|
||||
* free function `get`, which is found by ADL.
|
||||
*/
|
||||
template<std::size_t idx, class TUP>
|
||||
requires(tuple_like<std::remove_reference_t<TUP>>)
|
||||
decltype(auto)
|
||||
getElm (TUP&& tup)
|
||||
{
|
||||
using Tup = std::remove_reference_t<TUP>;
|
||||
static_assert (0 < std::tuple_size_v<Tup>);
|
||||
if constexpr (tuple_mem_accessible<Tup,0>)
|
||||
{
|
||||
if constexpr (std::is_reference_v<TUP>)
|
||||
return tup.template get<idx>();
|
||||
else
|
||||
{ // return value copy when tuple given as RValue
|
||||
using Elm = std::tuple_element_t<idx, TUP>;
|
||||
Elm elm(tup.template get<idx>());
|
||||
return elm;
|
||||
}
|
||||
}
|
||||
else
|
||||
{ // ▽▽▽ ADL
|
||||
using std::get;
|
||||
return get<idx> (std::forward<TUP> (tup));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
namespace { // apply to tuple-like : helpers...
|
||||
|
||||
/** @internal invocation helper similar to C++17 — but preferring a custom `get` impl */
|
||||
template<typename FUN, typename TUP, size_t...Idx>
|
||||
constexpr decltype(auto)
|
||||
__unpack_and_apply (FUN&& f, TUP&& tup, std::index_sequence<Idx...>)
|
||||
{
|
||||
return std::invoke (std::forward<FUN> (f)
|
||||
,getElm<Idx> (std::forward<TUP>(tup))...
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/** @internal invoke a metafunction with \a FUN and all element types from the _tuple-like_ \a TUP */
|
||||
template<template<typename...> class META, class FUN, class TUP>
|
||||
struct _InvokeMetafunTup
|
||||
{
|
||||
using Tupl = std::decay_t<TUP>;
|
||||
using Elms = ElmTypes<Tupl>::Seq;
|
||||
using Args = Prepend<FUN, Elms>::Seq;
|
||||
using Type = RebindVariadic<META, Args>::Type;
|
||||
};
|
||||
template<template<typename...> class META, class FUN, class TUP>
|
||||
struct _InvokeMetafunTup<META, FUN, TUP&>
|
||||
{
|
||||
using Tupl = std::decay_t<TUP>;
|
||||
using Elms = typename ElmTypes<Tupl>::template Apply<std::add_lvalue_reference_t>;
|
||||
using Args = Prepend<FUN, Elms>::Seq;
|
||||
using Type = RebindVariadic<META, Args>::Type;
|
||||
};
|
||||
|
||||
template<class FUN, class TUP>
|
||||
inline constexpr bool can_nothrow_invoke_tup = _InvokeMetafunTup<std::is_nothrow_invocable,FUN,TUP>::Type::value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replacement for `std::apply` — yet applicable to _tuple-like custom types_.
|
||||
* For unclear reasons, the standard chooses to reject such custom types, and
|
||||
* only allows a fixed set of explicitly defined facilities from the Stdlib
|
||||
* (tuple, pair, array, and some ranges stuff).
|
||||
* @todo 6/2025 as a first step, this replicates the implementation from C++17;
|
||||
* the second step would be to constrain this to a concept `tuple_like`
|
||||
*/
|
||||
template<class FUN, class TUP> requires(tuple_like<remove_reference_t<TUP>>)
|
||||
constexpr decltype(auto)
|
||||
apply (FUN&& f, TUP&& tup) noexcept (can_nothrow_invoke_tup<FUN,TUP> )
|
||||
{
|
||||
using Indices = std::make_index_sequence<std::tuple_size_v<std::remove_reference_t<TUP>>>;
|
||||
|
||||
return __unpack_and_apply (std::forward<FUN> (f)
|
||||
,std::forward<TUP> (tup)
|
||||
,Indices{}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tuple iteration: perform some arbitrary operation on each element of a tuple.
|
||||
* @note the given functor must be generic, since each position of the tuple
|
||||
* may hold a data element of different type.
|
||||
* @remark credits to David Vandevoorde (member of C++ committee) for using
|
||||
* std::apply to unpack the tuple's contents into an argument pack and
|
||||
* then employ a fold expression with the comma operator.
|
||||
*/
|
||||
template<class TUP, class FUN> requires(tuple_like<remove_reference_t<TUP>>)
|
||||
constexpr void
|
||||
forEach (TUP&& tuple, FUN fun)
|
||||
{
|
||||
lib::meta::apply ([&fun]<typename...ELMS>(ELMS&&... elms)
|
||||
{
|
||||
(fun (std::forward<ELMS>(elms)), ...);
|
||||
}
|
||||
,std::forward<TUP> (tuple));
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply some arbitrary function onto all elements of a tuple.
|
||||
* @return a new tuple constructed from the results of this function
|
||||
* @note the functor must be generic and able to work with all element types
|
||||
* @warning pay attention to references; the function can take arguments by-ref
|
||||
* and manipulate them (side-effect), and it may return references,
|
||||
* which will be placed as such into the result tuple; furthermore,
|
||||
* the argument tuple can also be taken as reference...
|
||||
* @remark The tuple constructor invocation is preceded by a regular argument evaluation,
|
||||
* which has unspecified evaluation order (even in C++17); _no assumptions_ can be
|
||||
* made regarding the order the functor will see the source tuple elements.
|
||||
* Notably this differs from #forEach, where a fold-expression with comma-operator
|
||||
* is used, which is guaranteed to evaluate from left to right.
|
||||
*/
|
||||
template<class TUP, class FUN> requires(tuple_like<remove_reference_t<TUP>>)
|
||||
constexpr auto
|
||||
mapEach (TUP&& tuple, FUN fun)
|
||||
{
|
||||
return lib::meta::apply ([&fun]<typename...ELMS>(ELMS&&... elms)
|
||||
{ //..construct the type explicitly (make_tuple would decay fun result types)
|
||||
using Tuple = std::tuple<decltype(fun (std::forward<ELMS>(elms))) ...>;
|
||||
return Tuple (fun (std::forward<ELMS>(elms)) ...);
|
||||
}
|
||||
,std::forward<TUP> (tuple));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Specialisation of variadic access for any tuple-like
|
||||
* @see variadic-helper.hpp
|
||||
*/
|
||||
template<tuple_like TUP>
|
||||
struct ElmTypes<TUP>
|
||||
{
|
||||
template<typename>
|
||||
struct Extract;
|
||||
template<size_t...idx>
|
||||
struct Extract<std::index_sequence<idx...>>
|
||||
{
|
||||
using ElmTypes = Types<std::tuple_element_t<idx,TUP> ...>;
|
||||
};
|
||||
|
||||
static constexpr size_t SIZ = std::tuple_size_v<TUP>;
|
||||
|
||||
using Idx = std::make_index_sequence<SIZ>;
|
||||
using Seq = Extract<Idx>::ElmTypes;
|
||||
using Tup = RebindVariadic<std::tuple, Seq>::Type;
|
||||
|
||||
template<template<class> class META>
|
||||
using Apply = ElmTypes<Seq>::template Apply<META>;
|
||||
|
||||
template<template<typename...> class O>
|
||||
using Rebind = RebindVariadic<O, Seq>::Type;
|
||||
|
||||
template<template<class> class PRED>
|
||||
using AndAll = ElmTypes<Apply<PRED>>::template Rebind<std::__and_>;
|
||||
|
||||
template<template<class> class PRED>
|
||||
using OrAll = ElmTypes<Apply<PRED>>::template Rebind<std::__or_>;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
namespace { // rebinding helper to create std::tuple from a type sequence
|
||||
|
||||
template<typename SEQ>
|
||||
|
|
@ -389,8 +173,8 @@ namespace meta {
|
|||
* selecting a (partial) specialisation based on the source type given.
|
||||
* @see ExtractArg
|
||||
*/
|
||||
template< typename TYPES
|
||||
, template<class,class, size_t> class _ElmMapper_
|
||||
template<typename TYPES
|
||||
,template<class, class, unsigned> class _ElmMapper_
|
||||
>
|
||||
struct TupleConstructor
|
||||
: Tuple<TYPES>
|
||||
|
|
@ -398,16 +182,18 @@ namespace meta {
|
|||
/** meta-sequence to drive instantiation of the ElmMapper */
|
||||
using SequenceIterator = BuildIdxIter<TYPES>::Ascending;
|
||||
|
||||
template<size_t idx, class SRC>
|
||||
template<unsigned idx, class SRC>
|
||||
using ElmMapperFor = _ElmMapper_<std::decay_t<SRC>, Tuple<TYPES>, idx>;
|
||||
|
||||
template<unsigned idx, class SRC>
|
||||
static auto
|
||||
mapElm (SRC&& init) ///< initialise an instance of the element-mapper
|
||||
{
|
||||
return _ElmMapper_<std::decay_t<SRC>
|
||||
,Tuple<TYPES>
|
||||
, idx
|
||||
>{std::forward<SRC> (init)};
|
||||
return ElmMapperFor<idx,SRC>{std::forward<SRC> (init)};
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected:
|
||||
template<class SRC, size_t...idx>
|
||||
TupleConstructor (SRC&& initVals, IndexSeq<idx...>)
|
||||
|
|
@ -454,135 +240,66 @@ namespace meta {
|
|||
{
|
||||
return TupleConstructor<TYPES, ExtractArg>{std::forward<SRC> (values)};
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* ======= Tuple toString rendering ======= */
|
||||
|
||||
/**
|
||||
* Decorating a tuple type with auxiliary data access operations.
|
||||
* This helper template builds up a subclass of the given TUP (base) type
|
||||
* (which is assumed to be a Tuple or at least need to be copy constructible
|
||||
* from `Tuple<TYPES>` ). The purpose is to use the Tuple as storage record, but
|
||||
* to add a layer of access functions, which in turn might rely on the exact
|
||||
* type of the individual elements within the Tuple. To achieve this, for each
|
||||
* type within the Tuple, the TUP type is decorated with an instance of the
|
||||
* template passed in as template template parameter _X_. Each of these
|
||||
* decorating instances is provided with an index number, allowing to
|
||||
* access "his" specific element within the underlying tuple.
|
||||
*
|
||||
* The decorating template _X_ need to take its own base class as template
|
||||
* parameter. Typically, operations on _X_ will be defined in a recursive fashion,
|
||||
* calling down into this templated base class. To support this, an instantiation
|
||||
* of _X_ with the empty type sequence is generated for detecting recursion end
|
||||
* (built as innermost decorator, i.e. the immediate subclass of TUP)
|
||||
* convenience function to dump a given tuple's contents, converted to string.
|
||||
* @note this is the basic functionality to place rendered data into a `ostringstream.`
|
||||
*/
|
||||
template
|
||||
< template<class,class,class, uint> class _X_ ///< user provided template<Type, Base, TupleType, arg-idx>
|
||||
, typename TYPES ///< Sequence of types to use within the Accessor
|
||||
, class TUP =Tuple<TYPES> ///< the tuple type to build on
|
||||
, uint i = 0 ///< tuple element index counter
|
||||
>
|
||||
class BuildTupleAccessor
|
||||
template<class TUP>
|
||||
inline void
|
||||
joinTuple (TUP const& tup, std::string delim, std::ostringstream& buffer)
|
||||
{
|
||||
// prepare recursion...
|
||||
using Head = Split<TYPES>::Head;
|
||||
using Tail = Split<TYPES>::Tail;
|
||||
using NextBuilder = BuildTupleAccessor<_X_, Tail,TUP, i+1>;
|
||||
using NextAccessor = NextBuilder::Product;
|
||||
public:
|
||||
|
||||
/** type of the product created by this template.
|
||||
* Will be a subclass of TUP */
|
||||
using Product = _X_< Head // the type to use for this accessor
|
||||
, NextAccessor // the base type to inherit from
|
||||
, TUP // the tuple type we build upon
|
||||
, i // current element index
|
||||
>;
|
||||
};
|
||||
forEachIDX<TUP> ([&](auto i)
|
||||
{
|
||||
using Elm = std::tuple_element_t<i, TUP>;
|
||||
if (i)
|
||||
buffer << delim;
|
||||
buffer << util::toString<Elm> (getElm<i> (tup));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
template
|
||||
< template<class,class,class, uint> class _X_
|
||||
, class TUP
|
||||
, uint i
|
||||
>
|
||||
class BuildTupleAccessor< _X_, Types<>, TUP, i>
|
||||
template<class TUP>
|
||||
inline void
|
||||
joinTuple (TUP const& tup, std::ostringstream& buffer)
|
||||
{
|
||||
public:
|
||||
using Product = _X_<Nil, TUP, TUP, i>; // Note: i == tuple size
|
||||
};
|
||||
joinTuple (tup, ",");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Helper to dump tuple contents.
|
||||
* Defined to act as "Accessor" for BuildTupleAccessor, this helper template
|
||||
* allows to create a recursive operation to invoke string conversion on
|
||||
* all elements within any given tuple.
|
||||
/** convenience function to join the tuple's contents into a single string.
|
||||
* @param delim optional delimiter to intersperse, defaults to `","`
|
||||
*/
|
||||
template
|
||||
< typename TY
|
||||
, class BASE
|
||||
, class TUP
|
||||
, uint idx
|
||||
>
|
||||
struct TupleElementDisplayer
|
||||
: BASE
|
||||
{
|
||||
using BASE::BASE;
|
||||
|
||||
std::string
|
||||
dump (std::string const& prefix ="(") const
|
||||
{
|
||||
return BASE::dump (prefix + util::toString(std::get<idx>(*this))+",");
|
||||
}
|
||||
};
|
||||
|
||||
template<class TUP, uint n>
|
||||
struct TupleElementDisplayer<Nil, TUP, TUP, n>
|
||||
: TUP
|
||||
{
|
||||
TupleElementDisplayer (TUP const& tup)
|
||||
: TUP(tup)
|
||||
{ }
|
||||
|
||||
std::string
|
||||
dump (std::string const& prefix ="(") const
|
||||
{
|
||||
if (1 < prefix.length())
|
||||
// remove the trailing comma
|
||||
return prefix.substr (0, prefix.length()-1) +")";
|
||||
else
|
||||
return prefix+")";
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* convenience function to dump a given tuple's contents.
|
||||
* Using the BuildTupleAccessor, we layer a stack of Instantiations of
|
||||
* the TupleElementDisplayer temporarily on top of the given tuple,
|
||||
* just to invoke a recursive call chain through these layers
|
||||
* and get a string representation of each element in the
|
||||
* tuple.
|
||||
*/
|
||||
template<typename...TYPES>
|
||||
template<class TUP>
|
||||
inline std::string
|
||||
dump (std::tuple<TYPES...> const& tuple)
|
||||
{
|
||||
using BuildAccessor = BuildTupleAccessor<TupleElementDisplayer, Types<TYPES...>>;
|
||||
using Displayer = BuildAccessor::Product ;
|
||||
|
||||
return static_cast<Displayer const&> (tuple)
|
||||
.dump();
|
||||
}
|
||||
|
||||
joinTuple (TUP const& tup, std::string delim =",")
|
||||
{
|
||||
std::ostringstream buffer;
|
||||
joinTuple (tup, delim, buffer);
|
||||
return buffer.str();
|
||||
}
|
||||
|
||||
template<class TUP>
|
||||
inline void
|
||||
joinTupleParen (TUP const& tup, std::ostringstream& buffer)
|
||||
{
|
||||
buffer << '(';
|
||||
joinTuple (tup, ",", buffer);
|
||||
buffer << ')';
|
||||
}
|
||||
|
||||
template<class TUP>
|
||||
inline std::string
|
||||
joinTupleParen (TUP const& tup)
|
||||
{
|
||||
std::ostringstream buffer;
|
||||
joinTupleParen (tup, buffer);
|
||||
return buffer.str();
|
||||
}
|
||||
}} // namespace lib::meta
|
||||
|
||||
|
||||
|
|
@ -595,12 +312,15 @@ namespace util {
|
|||
static std::string
|
||||
invoke (std::tuple<TYPES...> const& tuple) noexcept
|
||||
try {
|
||||
return "«"+typeStr(tuple)
|
||||
+ "»──" + lib::meta::dump (tuple);
|
||||
std::ostringstream buffer;
|
||||
buffer << "«"
|
||||
<< typeStr(tuple)
|
||||
<< "»──";
|
||||
lib::meta::joinTupleParen (tuple, buffer);
|
||||
return buffer.str();
|
||||
}
|
||||
catch(...) { return FAILURE_INDICATOR; }
|
||||
};
|
||||
|
||||
|
||||
} // namespace util
|
||||
#endif /*LIB_META_TUPLE_HELPER_H*/
|
||||
|
|
|
|||
131
src/lib/meta/tuple-idx-adaptor.hpp
Normal file
131
src/lib/meta/tuple-idx-adaptor.hpp
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
TUPLE-IDX-ADAPTOR.hpp - runtime-indexed access to tuples
|
||||
|
||||
Copyright (C)
|
||||
2026 Hermann Vosseler <Ichthyostega@web.de>
|
||||
|
||||
**Lumiera** 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. See the file COPYING for further details.
|
||||
|
||||
*/
|
||||
|
||||
/** @file tuple-idx-adaptor.hpp
|
||||
** Adaptor to provide access to tuple elements by runtime subscript.
|
||||
** Tuples are data records with distinct fields of different type; thus
|
||||
** most of indexing, iteration and transformation of tuples need to be
|
||||
** performed statically, at compile-time. However, under the proviso that
|
||||
** the types in a given tuple are somehow _reconcilable_, a dispatcher
|
||||
** table with access functors can be built, so that effectively all fields
|
||||
** can be handled by instances of a generic functor. This setup allows to
|
||||
** define a subscript `operator[]`, so that the suitable functor is picked
|
||||
** with runtime-index and applied to the tuple to yield the indicated field,
|
||||
** albeit converted into a _common type_.
|
||||
**
|
||||
** While this is certainly a very specific setup, and incurs some cost,
|
||||
** since the functor dispatcher tables must be prepared statically, such
|
||||
** a runtime-controlled index access allows for dynamic and data-driven
|
||||
** processing of tuple_like structures, which are fully type-safe,
|
||||
** compact in storage and easy to nest. Furthermore, run-time
|
||||
** processing of arguments passed to variadic functions
|
||||
** is enabled, providing a new kind of flexible API.
|
||||
**
|
||||
** @see TupleIdxAdaptor_test
|
||||
** @see FunTrampoline_test
|
||||
*/
|
||||
|
||||
|
||||
#ifndef LIB_META_TUPLE_IDX_ADAPTOR_H
|
||||
#define LIB_META_TUPLE_IDX_ADAPTOR_H
|
||||
|
||||
|
||||
#include "include/logging.h"
|
||||
#include "lib/iter-adapter.hpp"
|
||||
#include "lib/meta/tuple-helper.hpp"
|
||||
#include "lib/meta/common-result.hpp"
|
||||
#include "lib/meta/fun-trampoline.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
|
||||
namespace lib {
|
||||
namespace meta {
|
||||
|
||||
template<class TUP>
|
||||
class TupleIndexAdaptor
|
||||
: public TUP
|
||||
{
|
||||
/** Helper to generate a dispatch table for
|
||||
* this specific instance of tuple element types */
|
||||
template<typeseq SEQ>
|
||||
struct SubscriptBuilder
|
||||
{
|
||||
static_assert (reconcilable<SEQ>
|
||||
,"A common run-time index accessor can only be built "
|
||||
"if all types in the tuple can be reconciled into a "
|
||||
"common type used for the access");
|
||||
|
||||
using Tuple = RefTraits<TUP>::value_type;
|
||||
using AddRefs = ElmTypes<SEQ>::template Apply<std::add_lvalue_reference_t>;
|
||||
using ResType = CommonResult_t<AddRefs>;
|
||||
using Functor = ResType(*)(Tuple&);
|
||||
|
||||
template<size_t idx>
|
||||
constexpr auto
|
||||
build()
|
||||
{
|
||||
return [](Tuple& tuple) -> ResType
|
||||
{
|
||||
return getElm<idx> (tuple);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
using TypeSeq = ElmTypes<TUP>::Seq;
|
||||
using Accessor = FunTrampoline<SubscriptBuilder, TypeSeq>;
|
||||
|
||||
public:
|
||||
using Tuple = TUP;
|
||||
using ResType = Accessor::ResType;
|
||||
using reference = ResType;
|
||||
using value_Type = RefTraits<ResType>::value_type;
|
||||
static constexpr bool isRefAccess = isLRef_v<ResType>;
|
||||
|
||||
|
||||
/** construct by passing a source tuple */
|
||||
template<typename T>
|
||||
TupleIndexAdaptor(T&& dataSrc)
|
||||
: TUP{forward<T> (dataSrc)}
|
||||
{ }
|
||||
|
||||
/// lift all constructors of the adapted tuple
|
||||
using TUP::TUP;
|
||||
|
||||
Tuple & tuple() { return *this; }
|
||||
Tuple const& tuple() const { return *this; }
|
||||
|
||||
operator std::string() const
|
||||
{
|
||||
return "iTup" + joinTupleParen (this->tuple());
|
||||
}
|
||||
|
||||
|
||||
/* === subscriptable === */
|
||||
|
||||
static constexpr size_t size() { return Accessor::size(); }
|
||||
|
||||
ResType
|
||||
operator[] (size_t idx)
|
||||
{
|
||||
return Accessor::dispatch(idx)(*this);
|
||||
}
|
||||
};
|
||||
|
||||
/// deduction guide
|
||||
template<class TUP>
|
||||
TupleIndexAdaptor(TUP) -> TupleIndexAdaptor<std::decay_t<TUP>>;
|
||||
|
||||
|
||||
}}// namespace lib::meta
|
||||
#endif /*LIB_META_TUPLE_IDX_ADAPTOR_H*/
|
||||
|
|
@ -115,5 +115,21 @@ namespace meta {
|
|||
using Seq = Types<>;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Concept to require a generic type sequence `Types<TYPES...>`
|
||||
*/
|
||||
template<class TSEQ>
|
||||
concept typeseq = requires (TSEQ seq)
|
||||
{
|
||||
// TSEQ can invoke a function with a Types<TYPES> subclass
|
||||
[]<typename...TYPES>(Types<TYPES...>&){/*placeholder*/}(seq);
|
||||
};
|
||||
|
||||
template<class TSEQ>
|
||||
static constexpr bool is_Typeseq_v = typeseq<TSEQ>;
|
||||
|
||||
}} // namespace lib::meta
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -44,6 +44,44 @@
|
|||
namespace lib {
|
||||
namespace meta {
|
||||
|
||||
template<class T, class TYPES>
|
||||
struct Prepend;
|
||||
|
||||
|
||||
|
||||
/* ========= Specialisations ========= */
|
||||
|
||||
/**
|
||||
* Additional specialisation of the basic type sequence type,
|
||||
* allowing to re-create a (flat) type sequence from a typelist.
|
||||
* @remark recursively prepend-to-tail of the type sequence.
|
||||
*/
|
||||
template<class H, class T>
|
||||
struct Types< Node<H,T> >
|
||||
{
|
||||
using List = Node<H,T>;
|
||||
using Seq = Prepend< H
|
||||
, typename Types<T>::Seq
|
||||
>::Seq;
|
||||
};
|
||||
template<>
|
||||
struct Types<Nil>
|
||||
{
|
||||
using List = Nil;
|
||||
using Seq = Types<>;
|
||||
};
|
||||
template<>
|
||||
struct Types<NilNode>
|
||||
: Types<Nil>
|
||||
{ };
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* ========= Information functions ========= */
|
||||
|
||||
/**
|
||||
* Find the index of the first incidence of a type in a type-sequence.
|
||||
|
|
@ -82,6 +120,13 @@ namespace meta {
|
|||
{ };
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* ========= Type Sequence Manipulation ========= */
|
||||
|
||||
/**
|
||||
* Helper: prepend a type to an existing type sequence,
|
||||
* thus shifting all elements within the sequence
|
||||
|
|
@ -99,33 +144,6 @@ namespace meta {
|
|||
|
||||
|
||||
|
||||
/**
|
||||
* Additional specialisation of the basic type sequence type,
|
||||
* allowing to re-create a (flat) type sequence from a typelist.
|
||||
* @remark can now be built with the help of \ref Prepend.
|
||||
*/
|
||||
template<class H, class T>
|
||||
struct Types< Node<H,T> >
|
||||
{
|
||||
using List = Node<H,T>;
|
||||
using Seq = Prepend< H
|
||||
, typename Types<T>::Seq
|
||||
>::Seq;
|
||||
};
|
||||
template<>
|
||||
struct Types<Nil>
|
||||
{
|
||||
using List = Nil;
|
||||
using Seq = Types<>;
|
||||
};
|
||||
template<>
|
||||
struct Types<NilNode>
|
||||
: Types<Nil>
|
||||
{ };
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/** Helper: separate parts of a type sequence
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -505,19 +505,19 @@ namespace util {
|
|||
|
||||
/* === Literals for common size designations === */
|
||||
|
||||
inline uint
|
||||
constexpr inline uint
|
||||
operator""_KiB (ullong const siz)
|
||||
{
|
||||
return uint(siz) * 1024u;
|
||||
}
|
||||
|
||||
inline uint
|
||||
constexpr inline uint
|
||||
operator""_MiB (ullong const siz)
|
||||
{
|
||||
return uint(siz) * 1024u*1024u;
|
||||
}
|
||||
|
||||
inline ullong
|
||||
constexpr inline ullong
|
||||
operator""_GiB (ullong const siz)
|
||||
{
|
||||
return siz * 1024uLL*1024uLL*1024uLL;
|
||||
|
|
|
|||
|
|
@ -27,9 +27,9 @@
|
|||
** 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` (what is conceived to be "in" the container or iterator)
|
||||
** - a simple (LValue) reference to the payload
|
||||
** - a pointer at the payload.
|
||||
** - `value_type` : how to store what is conceived to be "in" the container or iterator)
|
||||
** - a simple (LValue) reference to the payload: how you access some content
|
||||
** - a to form pointer at the payload, which is relevant for `operator->`
|
||||
**
|
||||
** A custom container should likewise provide such type definitions, and the
|
||||
** type rebinding helper template defined in this header makes it easy to
|
||||
|
|
@ -37,6 +37,12 @@
|
|||
** 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.
|
||||
** @warning A special twist may happen when processing a function in an iterator (pipeline).
|
||||
** In this special case, `ValueTypeBinding` should _not_ be used, since the function
|
||||
** might yield again a nested iterator; yet we want to treat that iterator as a value
|
||||
** in that special case, and not look into the iterator or retrieve results from it.
|
||||
** Notably this situation applies to any kind of transforming iterator.
|
||||
** lib::meta::RefTraits should be used in such cases.
|
||||
**
|
||||
** The CommonResultYield type rebinding helper allows to reconcile several
|
||||
** essentially compatible result types; it is used in iterator pipelines,
|
||||
|
|
@ -53,7 +59,6 @@
|
|||
#define LIB_META_VALUE_TYPE_BINDING_H
|
||||
|
||||
|
||||
#include "lib/error.hpp"
|
||||
#include "lib/meta/trait.hpp"
|
||||
|
||||
|
||||
|
|
@ -107,64 +112,22 @@ namespace meta {
|
|||
template<typename TY, typename SEL =void>
|
||||
struct ValueTypeBinding
|
||||
{
|
||||
using value_type = RefTraits<TY>::Value;
|
||||
using reference = RefTraits<TY>::Reference;
|
||||
using pointer = RefTraits<TY>::Pointer;
|
||||
using value_type = RefTraits<TY>::value_type;
|
||||
using reference = RefTraits<TY>::reference;
|
||||
using pointer = RefTraits<TY>::pointer;
|
||||
};
|
||||
|
||||
/** specialisation for classes providing STL style type binding definitions */
|
||||
template<typename TY>
|
||||
struct ValueTypeBinding<TY, enable_if<use_ValueTypebindings<TY>> >
|
||||
{
|
||||
using _SrcType = RefTraits<TY>::Value;
|
||||
using _SrcType = RefTraits<TY>::value_type;
|
||||
|
||||
using value_type = _SrcType::value_type;
|
||||
using reference = _SrcType::reference;
|
||||
using pointer = _SrcType::pointer;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Decision helper to select between returning results by value or reference.
|
||||
* - when both types can not be reconciled, _no type result_ is provided;
|
||||
* this case can be detected by a compile-time bool-check
|
||||
* - when one of both types is `const`, the `ResType` will be const
|
||||
* - when both types are LValue-references, then the result will be a reference,
|
||||
* otherwise the result will be a value type
|
||||
* @see IterExplorer::expand()
|
||||
*/
|
||||
template<typename T1, typename T2, bool = has_TypeResult<std::common_type<T1,T2>>()>
|
||||
struct CommonResultYield
|
||||
: std::false_type
|
||||
{ };
|
||||
|
||||
template<typename T1, typename T2>
|
||||
struct CommonResultYield<T1, T2, true >
|
||||
: std::true_type
|
||||
{
|
||||
using _Common = std::common_type_t<T1,T2>;
|
||||
// NOTE: unfortunately std::common_type decays (strips cv and reference)
|
||||
static constexpr bool isConst = isConst_v<T1> or isConst_v<T2>;
|
||||
static constexpr bool isRef = isLRef_v<T1> and isLRef_v<T2>;
|
||||
|
||||
using _ConstT = std::conditional_t<isConst
|
||||
, const _Common
|
||||
, _Common
|
||||
>;
|
||||
using _ValRef = std::conditional_t<isRef
|
||||
, std::add_lvalue_reference_t<_ConstT>
|
||||
, std::remove_reference_t<_ConstT>
|
||||
>;
|
||||
|
||||
using ResType = _ValRef;
|
||||
using value_type = RefTraits<ResType>::Value;
|
||||
using reference = RefTraits<ResType>::Reference;
|
||||
using pointer = RefTraits<ResType>::Pointer;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
}} // namespace lib::meta
|
||||
#endif /*LIB_META_VALUE_TYPE_BINDING_H*/
|
||||
|
|
|
|||
|
|
@ -16,9 +16,14 @@
|
|||
** 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.
|
||||
** from variadic parameter packs. Among other things, this allows the sequence
|
||||
** of function arguments to be flexibly reshaped and remoulded at compile-time.
|
||||
** Driven by template instantiation, this enables adaptation in response to
|
||||
** specific argument types for each actual instance. Such metaprogramming
|
||||
** capabilities can be amplified significantly through the use of generic-λ.
|
||||
** @note with C++23, this framework could be expanded to cover also any
|
||||
** »tuple-like« product type, drawing from our non-standard extension
|
||||
** with the `concept tuple_like`, as defined in \ref tuple-concept.hpp.
|
||||
**
|
||||
** @remark in Lumiera, over time three different approaches were developed for
|
||||
** handling sequences of types in metaprogramming; some of these techniques
|
||||
|
|
@ -26,8 +31,10 @@
|
|||
** - templates with variadic arguments (e.g. std::tuple) can be manipulated directly
|
||||
** - a type-sequence `Types<T...>` can be primed / rebound from other variadic templates
|
||||
** - Loki-style type-lists are created from type-sequences and enable elaborate manipulations
|
||||
**
|
||||
** @see feed-manifold.hpp advanced usage example in the Render Engine
|
||||
** @see TupleHelper_test
|
||||
** @see VariadicHelper_test
|
||||
** @see tuple-concept.hpp
|
||||
** @see typelist.hpp
|
||||
** @see function.hpp
|
||||
** @see generator.hpp
|
||||
|
|
@ -42,7 +49,7 @@
|
|||
#include "lib/meta/typelist-util.hpp"
|
||||
#include "lib/meta/typeseq-util.hpp"
|
||||
#include "lib/meta/variadic-rebind.hpp"
|
||||
#include "lib/meta/util.hpp"
|
||||
#include "lib/meta/trait.hpp"
|
||||
|
||||
namespace lib {
|
||||
namespace meta {
|
||||
|
|
@ -160,7 +167,7 @@ namespace meta {
|
|||
* - the nested template #Apply wraps each type into another template
|
||||
* - #Rebind likewise instantiates another template with the element types
|
||||
* - #AndAll applies a predicate and combines the result with _logical and_
|
||||
* - #OrAll similarly evaluates _logical or_ on the application results
|
||||
* - #OrAny similarly evaluates _logical or_ on the application results
|
||||
*/
|
||||
template<class X, typename =void>
|
||||
struct ElmTypes
|
||||
|
|
@ -177,7 +184,7 @@ namespace meta {
|
|||
template<template<class> class PRED>
|
||||
using AndAll = std::__and_<PRED<X>>;
|
||||
template<template<class> class PRED>
|
||||
using OrAll = std::__or_<PRED<X>>;
|
||||
using OrAny = std::__or_<PRED<X>>;
|
||||
};
|
||||
|
||||
/** Partial specialisation to handle type sequences */
|
||||
|
|
@ -199,10 +206,23 @@ namespace meta {
|
|||
using AndAll = ElmTypes<Apply<PRED>>::template Rebind<std::__and_>;
|
||||
|
||||
template<template<class> class PRED>
|
||||
using OrAll = ElmTypes<Apply<PRED>>::template Rebind<std::__or_>;
|
||||
using OrAny = ElmTypes<Apply<PRED>>::template Rebind<std::__or_>;
|
||||
};
|
||||
|
||||
// Note: a further specialisation for any »tuple-like« is defined in tuple-helper.hpp
|
||||
// Note: a further specialisation for any »tuple-like« is defined in tuple-concept.hpp
|
||||
|
||||
|
||||
|
||||
template<class SEQ>
|
||||
using Any_Const = ElmTypes<SEQ>::template OrAny<isConst>;
|
||||
template<class SEQ>
|
||||
using All_LRef = ElmTypes<SEQ>::template AndAll<isLRef>;
|
||||
|
||||
template<class SEQ>
|
||||
static constexpr bool Any_Const_v = Any_Const<SEQ>::value;
|
||||
template<class SEQ>
|
||||
static constexpr bool All_LRef_v = All_LRef<SEQ>::value;
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
|
||||
|
||||
|
||||
namespace lumiera {
|
||||
namespace lib {
|
||||
|
||||
void
|
||||
initialise_NoBug ()
|
||||
|
|
|
|||
|
|
@ -27,17 +27,17 @@
|
|||
#ifndef NOBUG_INIT_H
|
||||
#define NOBUG_INIT_H
|
||||
|
||||
#include "include/lifecycle.h"
|
||||
#include "include/lifecycle.hpp"
|
||||
|
||||
#include <nobug.h>
|
||||
|
||||
|
||||
|
||||
namespace lumiera {
|
||||
namespace lib {
|
||||
void initialise_NoBug ();
|
||||
|
||||
namespace {
|
||||
LifecycleHook trigger_init_ (ON_BASIC_INIT, &initialise_NoBug);
|
||||
vessel::LifecycleHook trigger_init_ (vessel::ON_BASIC_INIT, &initialise_NoBug);
|
||||
} }
|
||||
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue