Indeed — this change set is kind of sad.
Because I still admire the design of the GAVL library,
and would love to use it for processing of raw video.
However, up to now, we never got to the point of actually
doing so. For the future, I am not sure if there remains
room to rely on lib-GAVL, since FFmpeg roughly covers
a similar ground (and a lot beyond that). And providing
a plug-in for FFmpeg is unavoidable, practically speaking.
So I still retain the nominal dependency on lib-GAVL
in the Build system (since it is still packaged in Debian).
But it is pointless to rely on this library just for an
external type-def `gavl_time_t`. We owe much to this
inspiration, but it can be expected that we'll wrap
these raw time-values into a dedicated marker type
soon, and we certainly won't be exposing any C-style
interface for time calculations in future, since
we do not want anyone to side-step the Lumiera
time handling framework in favour of working
„just with plain numbers“
NOTE: lib-GAVL hompage has moved to Github:
https://github.com/bplaum/gavl
1060 lines
41 KiB
Text
1060 lines
41 KiB
Text
Lumiera: The Inner Core
|
|
=======================
|
|
:Author: Lumiera_Core_Developers
|
|
:Date: Winter 2012
|
|
:toc:
|
|
|
|
[abstract]
|
|
******************************************************************************
|
|
The Lumiera Developers have a distinct vision about the core of a modern NLE.
|
|
This document outlines some of the basic technical concepts and highlights
|
|
the fundamental design decisions. New Developers may find this Document useful
|
|
to get an idea about how the different components work together.
|
|
******************************************************************************
|
|
|
|
|
|
Overview
|
|
--------
|
|
|
|
Lumiera constitutes of a broad range of subsystems. These are roughly grouped
|
|
into three layers plus some extra components. This structure is mostly kept in
|
|
the source directory structures.
|
|
|
|
This three Layers are:
|
|
|
|
The Stage Layer::
|
|
Interaction and presentation. User interfaces are implemented as plug-ins,
|
|
and while most commonly one will see the GTK UI, it would be possible to
|
|
construct a entirely different user interface, a CLI interface, or even
|
|
operate the application ``headless'', script driven.
|
|
|
|
The Steam Layer::
|
|
Keeps the Session, generates the rendering graphs for sequences,
|
|
arranges what to do when and how.
|
|
|
|
The Vault Layer::
|
|
Manages worker pools, schedules jobs, does the memory management for the
|
|
heavy multimedia data. Loads and delegates to external libraries for
|
|
media processing.
|
|
|
|
The extra components are:
|
|
|
|
Lumiera::
|
|
The main program itself, basically acts only as loader to pull the rest up.
|
|
|
|
Common::
|
|
Vital components and common services which must be available for pulling up
|
|
any part of the system.
|
|
|
|
Library::
|
|
A lot of (largely) stateless helper functionality used throughout the code.
|
|
|
|
|
|
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.
|
|
Function naming conventions and other details are also described in several RFCs.
|
|
|
|
|
|
Documentation
|
|
~~~~~~~~~~~~~
|
|
The central location for all design and technical documentation is the Lumiera
|
|
website you're reading right now. Besides that, a summary and introduction
|
|
for various components can be found in the file-level doxygen comments, while
|
|
details are usually explained in the class and function level comments.
|
|
|
|
==== the TiddlyWiki
|
|
Currently, Lumiera is still in the design- and evolution phase. Documentation
|
|
is written as an ongoing effort.
|
|
There is an embedded JavaScript wiki (TiddlyWiki) within the source tree, mostly
|
|
used as design notebook, featuring day-to-day design sketches and notes, but also
|
|
quite some more persistent planning. Finished documentation text is constantly
|
|
moved over to the documentation section(s) of the Lumiera website. +
|
|
-> access the development link:{l}/wiki/renderengine.html[TiddlyWiki online here]
|
|
|
|
|
|
Test Driven Development
|
|
~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
We strive to use _Test-Driven-Development:_ tests are to be written first,
|
|
defining the specification of the entity being tested. Then things get
|
|
implemented until they pass their tests. While increasing the initial
|
|
development effort significantly, this approach is known to lead to
|
|
clearly defined components and overall increases code quality.
|
|
In practice, this approach might not be suitable at times,
|
|
nevertheless we try to stick to it as far as possible
|
|
and maintain fairly complete test coverage.
|
|
|
|
|
|
Releases and Development Process
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
Lumiera is developed in a collaborative style. Releases are done ``when ready''.
|
|
We (the core developers) are always open for comments, new ideas and proposals.
|
|
In fact, there is even a world-pushable ``mob'' Git repository on our server:
|
|
minor contributions can be pushed there right away, we'll notice any changes.
|
|
It might be a good idea to discuss larger changes prior to jumping into the
|
|
code though. Our Mailinglist is the main information hub. Users and developers --
|
|
everyone is welcome.
|
|
|
|
Lumiera should be usable on any contemporary Linux (or similar *NIX) system.
|
|
Porting to other platforms is OK for us, but requires Someone^TM^ to volunteer.
|
|
For development and dependency management, we try to stick close to what
|
|
Debian/stable provides. Whenever we need special or more recent libraries,
|
|
we care for a backport and publish a DEB package for use on Debian/Stable.
|
|
We also run a series of Debian Repositories, and we try to build a Lumiera
|
|
package for an array of Debian-like distributions (Ubuntu, Mint). Through
|
|
these ongoing efforts, we try to keep up with the evolution of platform,
|
|
compilers, language and libraries, and we maintain our ability to
|
|
release, even while there are no public releases planned
|
|
for the foreseeable future.
|
|
|
|
|
|
|
|
Lumiera Application
|
|
-------------------
|
|
Generally speaking, the Application is comprised of several self contained
|
|
_subsystems_, which may depend on each other. Dependencies between components
|
|
are to be abstracted through interfaces. Based on the current configuration,
|
|
the application framework brings up the necessary subsystems and finally
|
|
conducts a clean shutdown. Beyond that, the application framework remains
|
|
passive, yet provides vital services commonly used.
|
|
|
|
While the core components are linked into a coherent application and may
|
|
utilise each other's services directly based on C/C++ language facilities,
|
|
several important parts of the applications are loaded as plug-ins, starting
|
|
with the GUI.
|
|
|
|
|
|
Stage: The User Interface(s)
|
|
----------------------------
|
|
|
|
The purpose of the user interface(s) is to act on the _high-level data model_
|
|
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 2018, 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
|
|
load and start this GUI as a Lumiera module from 'modules/gtk_gui.lum'
|
|
|
|
|
|
|
|
Steam Layer
|
|
-----------
|
|
|
|
High Level Model
|
|
~~~~~~~~~~~~~~~~
|
|
_tbw_
|
|
|
|
Assets
|
|
^^^^^^
|
|
_tbw_
|
|
|
|
Placement
|
|
^^^^^^^^^
|
|
_tbw_
|
|
|
|
Scoping
|
|
^^^^^^^
|
|
_tbw_
|
|
|
|
MObject References
|
|
^^^^^^^^^^^^^^^^^^
|
|
_tbw_
|
|
|
|
QueryFocus
|
|
^^^^^^^^^^
|
|
_tbw_
|
|
|
|
Output Management
|
|
~~~~~~~~~~~~~~~~~
|
|
_tbw_
|
|
|
|
Stream Type System
|
|
~~~~~~~~~~~~~~~~~~
|
|
_tbw_
|
|
|
|
Command Frontend
|
|
~~~~~~~~~~~~~~~~
|
|
_tbw_
|
|
|
|
Defaults Manager
|
|
~~~~~~~~~~~~~~~~
|
|
_tbw_
|
|
|
|
Rules System
|
|
~~~~~~~~~~~~
|
|
_tbw_
|
|
|
|
Builder
|
|
~~~~~~~
|
|
_tbw_
|
|
|
|
Low Level Model
|
|
~~~~~~~~~~~~~~~
|
|
_tbw_
|
|
|
|
|
|
Play/Rendering subsystem
|
|
~~~~~~~~~~~~~~~~~~~~~~~~
|
|
Within Lumiera, »Player« is the name for a subsystem responsible for organising and tracking
|
|
_ongoing playback and render processes._ The player subsystem does not perform or even manage
|
|
any render operations, nor does it handle the outputs directly. +
|
|
Yet it addresses some central concerns:
|
|
|
|
uniformity::
|
|
All playback and render processes are on equal footing, handled in a similar way.
|
|
|
|
integration::
|
|
The player cares for the necessary integration with the other subsystems
|
|
+
|
|
it consults the _Output Management,_ retrieves the necessary informations from the _Session_
|
|
and coordinates the forwarding of Vault-Layer calls.
|
|
|
|
time quantisation::
|
|
The player translates continuous time values into discrete frame counts.
|
|
+
|
|
To perform this _quantisation,_ the help of the session for building a TimeGrid
|
|
for each output channel is required.
|
|
|
|
|
|
The player service
|
|
^^^^^^^^^^^^^^^^^^
|
|
Client code accesses the player (subsystem) through the play-facade (`lumiera::Play`).
|
|
The exposed service allows to _set up an output connection for playback or rendering,_
|
|
resulting in a play-controller object.
|
|
|
|
.Play::Controller
|
|
This controller frontend represents the presence of such an active output connection
|
|
and incorporates a state machine supporting the usual things you'd expect to do with
|
|
a player (Play, pause, FFwd, Rew, scrubbing, jumping, looping). This controller object
|
|
is a copyable smart-handle -- all instances act as if wired in parallel.
|
|
|
|
.time control
|
|
The play-controller frontend makes heavy use of `time::Control`. This is a mediator
|
|
to accept and forward _mutations_ on time values and time ranges, possibly involving
|
|
frame quantisation. After attaching it to a target time value, it accepts changes,
|
|
offsets and nudging, translates these into the appropriate target modifications
|
|
and notifies any attached _change listeners_.
|
|
|
|
.play process
|
|
Ongoing effort to calculate a stream of frames for playback or rendering. +
|
|
The play process is an conceptual entity linking together several activities in the vault
|
|
and the render engine. It maintains a registration entry for the process to keep track of
|
|
associated entities, resources allocated and calls dispatched as a consequence. Besides
|
|
each play process is wired to at least one play-controller acting as frontend interface
|
|
and information hub for the client code.
|
|
|
|
NOTE: the player is in no way engaged in any of the actual calculation and management tasks
|
|
necessary to make the stream of calculations actually happen. The play process code contained
|
|
within the player subsystem is largely comprised of organisational concerns and not especially
|
|
performance critical.
|
|
|
|
- the vault is responsible for dispatching the calculation stream and scheduling calculation jobs
|
|
- the render engine has the ability to carry out individual frame calculations
|
|
- 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
|
|
the primary vault facilities, the Engine Interface is the primary point of service
|
|
accessed by Steam-Layer to use the vault layer services for rendering content.
|
|
|
|
.Calculation Streams
|
|
The Engine Interface is cast in terms of an _calculation stream_ entity. This is
|
|
a stream of expected and ongoing frame calculations for multiple channels, to be
|
|
managed as a compound. The calculated frames will be delivered into an output slot
|
|
right away. No assumptions are made regarding the ordering of these individual
|
|
calculations -- they may be performed in parallel, constrained by input and
|
|
resource prerequisites solely.
|
|
|
|
.Frame Dispatcher
|
|
For the actual processing, calculation streams need to be translated into individual
|
|
calculation jobs to be scheduled. For each uniform _segment of the effective timeline,_
|
|
the typical recursive descent call characteristic for _pull processing_ results in a
|
|
Job Ticket.
|
|
|
|
.Job Ticket
|
|
This structural descriptor of the actual calculations to be performed is the base
|
|
for creating individual jobs: Already specialised for a distinct segment of the
|
|
effective timeline and tailored for the needs of a given calculation stream,
|
|
the job ticket acts as blueprint for the actual jobs to be enqueued
|
|
with the _Scheduler._
|
|
|
|
|
|
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
|
|
into three basic modes of control:
|
|
|
|
Deadline::
|
|
Higher priority jobs ordered by a deadline time plus some (negative) hysteresis.
|
|
Jobs are started when they approach their deadline. When it is foreseeable that a
|
|
jobs will miss the deadline, it will be skipped, preferring to use resources on
|
|
calculations expected to be successful and on time.
|
|
|
|
Best Effort::
|
|
Background calculations will use excess resources of the system;
|
|
a typical example would be the calculation of preview images and sound waveform
|
|
outlines for the GUI.
|
|
|
|
Freewheeling::
|
|
Calculation for final rendering will use all available resources and never
|
|
skip any processing, irrespective of the time it takes to complete.
|
|
|
|
Internally, the implementation of the Scheduler is organised in two layers:
|
|
|
|
Layer-2: Coordination::
|
|
Maintain a network of interconnected activities, track dependencies and observe
|
|
timing constraints; coordinate a pool of _active Workers_ to dispatch the next activities.
|
|
|
|
Layer-1: Invocation::
|
|
Operates a low-level priority scheduling mechanism for time-bound execution of activities.
|
|
|
|
Job
|
|
^^^
|
|
A _Render Job_ is prepared as a self-contained functor, with a well defined time window
|
|
of execution. A job, once triggered, will run to completion unattended and can not be aborted.
|
|
The scheduler is able to observe and maintain dependencies between render jobs, allowing
|
|
to break down an extended calculation into a chain of small steps.
|
|
|
|
|
|
Resource Management
|
|
~~~~~~~~~~~~~~~~~~~
|
|
|
|
Running Lumiera requires a lot different resources, such as CPU-Time, Threads,
|
|
IO Bandwidth, Memory, Address space and so on. Many of this resources are rather
|
|
hard limited and the system will return errors when this limits are hit, but
|
|
often one does not even reach this hard limits because performance will
|
|
degrade way before coming into the realm of this limits. The goal for Lumiera
|
|
is to find a sweet spot for operating with optimal performance. Thus we have
|
|
some facilities to monitor and adjust resource usage depending and adapting to
|
|
the system and current circumstances.
|
|
|
|
|
|
Profiler
|
|
^^^^^^^^
|
|
|
|
Collects statistic about resource load, helps to decide if job constraints can
|
|
be fulfilled.
|
|
|
|
Things to watch:
|
|
* CPU utilisation
|
|
* memory usage (swapping, paging)
|
|
* I/O load, latency
|
|
|
|
|
|
|
|
|
|
Common Services
|
|
---------------
|
|
|
|
Subsystem runner
|
|
~~~~~~~~~~~~~~~~
|
|
Conceptually, the application is structured into layers. However, as actual runtime
|
|
entities we rely on a small number of link:{ldoc}/design/architecture/Subsystems.html[Subsystems],
|
|
which can be started and stopped and have well defined dependencies:
|
|
|
|
- Engine
|
|
- Session and Builder
|
|
- Play and Render Subsystem
|
|
- Lumiera GUI
|
|
- Script runner [yellow-background small]#planned#
|
|
- Renderfarm node [yellow-background small]#planned#
|
|
|
|
Lumiera's `main()` function defines an _Application Object_, which in turn operates
|
|
the _Subsystem Runner_. Each Subsystem implements a descriptor with the necessary
|
|
hooks callbacks to be started and stopped when necessary.
|
|
|
|
Lifecycle events
|
|
~~~~~~~~~~~~~~~~
|
|
Beyond parsing the commandline and triggering start of the subsystems, the Application
|
|
Object also issues _Lifecycle Events_. Individual components can register a callback
|
|
to be invoked when this happens. Currently (2018) the following events are known
|
|
(see 'include/lifecycle.h'):
|
|
|
|
- ON_BASIC_INIT
|
|
- ON_GLOBAL_INIT
|
|
- ON_GLOBAL_SHUTDOWN
|
|
- ON_EMERGENCY_EXIT
|
|
|
|
-> see here for
|
|
link:{ldoc}/design/application/SubsystemLifecycle.html#_initialisation_and_lifecycle[explanation in detail]
|
|
[small]#(somewhat preliminary and messy, yet still valid as of [yellow-background]##2018##)# +
|
|
-> more
|
|
link:{ldoc}/design/architecture/Subsystems.html#_lifecycle[documentation]
|
|
[small]#(likewise incomplete, but we're getting there, eventually [yellow-background]##2018##)#
|
|
|
|
Interface system
|
|
~~~~~~~~~~~~~~~~
|
|
_tbw_
|
|
|
|
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
|
|
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)
|
|
|
|
.Advice topics
|
|
Advice is organised into categories, based on the type of the advice item and
|
|
some additional symbolic identifiers. Actually these are syntactically represented
|
|
similar to the _atoms_ of a rules based system (``Prolog syntax''). Currently (2010)
|
|
only ground terms (completely static symbols) are supported. But the intention is to
|
|
extend the system to allow for variables in these terms. This will turn the matching
|
|
of advice provisions and requests into an unification, allowing the advice item to
|
|
be parametrised.
|
|
|
|
|
|
Rules system
|
|
~~~~~~~~~~~~
|
|
_tbw_
|
|
|
|
Serialiser
|
|
~~~~~~~~~~
|
|
_tbw_
|
|
|
|
Config loader
|
|
~~~~~~~~~~~~~
|
|
_tbw_
|
|
|
|
|
|
|
|
|
|
Library
|
|
-------
|
|
|
|
The Lumiera support library contains lots of helper functionality
|
|
factored out from the internals and re-used. It is extended as we go.
|
|
|
|
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.
|
|
|
|
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
|
|
support this notion.
|
|
|
|
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.
|
|
|
|
In a similar vein, the header 'lib/util-foreach.hpp' provides a generic
|
|
``for-each-element'' mechanism, which works for all STL containers, but
|
|
also for all _Lumiera Forward Iterators_. The loop body is provided
|
|
as a functor. In case this functor is a predicate (boolean result),
|
|
the +and_all+ and +has_any+ functions allow to test for conjunction
|
|
and disjunction.
|
|
|
|
|
|
Locking
|
|
~~~~~~~
|
|
General purpose Locking is based on object monitors. Performance critical code
|
|
in the Render Engine relies on ''Atomics'', or may use futures and further
|
|
synchronisation primitives through the wrappers of the C++ standard library.
|
|
|
|
NOTE: At the time when Lumiera project started, the standard library provided
|
|
no portable support for concurrency and locking. As a remedy, various
|
|
solutions were built on top of the POSIX primitives. Some of these
|
|
ad-hoc solutions turned out to be viable and were reworked and adapted,
|
|
as proper concurrency support became part of the language standard.
|
|
For new code, the direct use of POSIX primitives is discouraged.
|
|
|
|
Time
|
|
~~~~
|
|
Time values are represented by a family of opaque date types
|
|
with overloaded operators. The implementation was inspired by `gavl_time_t`
|
|
and is thus based on a µ-seccond time grid, represented as 64-bit integer.
|
|
Thus, the arithmetic on time values and time spans is limited and any Time
|
|
handling and conversion is centralised in library routines.
|
|
|
|
We distinguish between time values and a _quantisation_ into a frame
|
|
or sample grid. In any case, quantisation has to be done once, explicitly
|
|
and as late as possible. See the link:{rfc}/TimeHandling.html[Time handling RfC].
|
|
|
|
.time values
|
|
The Lumiera library defines several flavours of time values. All of
|
|
these internal time values have in common that they are _opaque_ and not
|
|
directly related to any human readable or external (wall clock) time.
|
|
Moreover, most of these time values are immutable, yet there are two
|
|
mechanisms to allow for changing time values (TimeVar and Mutation).
|
|
|
|
.quantised time
|
|
Special flavours of these time values additionally carry the reference
|
|
to an (frame) alignment grid, while being time value entities in all other
|
|
respects. But _only those quantised time values_ expose functions to
|
|
convert the internal opaque time into a human readable or externally
|
|
relevant time format -- including SMPTE or frame counts.
|
|
|
|
.time (alignment) grid
|
|
Thus, any usage of time values is forced to refer to such a time alignment
|
|
grid explicitly, at least when leaving the realm of the internal opaque
|
|
time values. This is the point where the *time quantisation* is actually
|
|
performed, imposing some information loss (as any rounding operation does). +
|
|
A time alignment grid is exactly that: a set of functions to perform
|
|
this lossy conversion. Implicitly this involves the definition of an
|
|
_time origin_ (reference point where the external time is zero), and
|
|
typically this also includes the definition of a _frame rate_ (but
|
|
in the most general case, this frame rate might be variable and
|
|
change at various places of the time axis). Consequently, all time
|
|
grids are Assets and defined as part of the concrete session.
|
|
|
|
|
|
Time Code
|
|
^^^^^^^^^
|
|
Historically, Time Code was seen as the foundation of any film editing.
|
|
Similarly, the first generation of editing systems used Time Code as a
|
|
foundation. Today, we consider such a design as questionable.
|
|
|
|
Lumiera takes a different approach here: Time code is reduced to a mere
|
|
mode of presentation, i.e. a formatting of existing time values. It is
|
|
void of any substantial meaning. To the contrary, the operation of
|
|
_frame quantisation_ (see above) is considered to be fundamental,
|
|
causing a irreversible loss of information. The design of time handling
|
|
chosen within Lumiera forces you to decide on using a specific _time grid_,
|
|
prior to being able to format an internal (opaque) time value in any kind
|
|
of time code. And only as late as when _actually retrieving_ this time code
|
|
formatted value, the actual quantisation (grid alignment) happens.
|
|
|
|
In practice, establishing a time grid requires knowledge about the output
|
|
format. Thus, an (sufficiently resolved) *output designation* is required
|
|
to perform any grid alignment a and time code formatting. Typically this
|
|
happens when a timeline or similar part of the High-Level-Model is connected
|
|
to a concrete output or an global bus defining a frame rate already. The model
|
|
contents as such are _frame rate independent_.
|
|
|
|
The following time code formats are supported, both for programmatic access
|
|
and display within the GUI
|
|
|
|
- frame count
|
|
- SMPTE
|
|
- SMPTE drop-frame _[,yellow]#TODO# as of 2011_
|
|
- hours:mins:secs _[,yellow]#TODO# as of 2011_
|
|
- fractional seconds _[,yellow]#TODO# as of 2011_
|
|
- musical bars:beats _[,yellow]#TODO# as of 2011_
|
|
|
|
As a corollary, as any rendering is based on frame numbers, it requires an
|
|
output connection or something similar to establish a frame grid.
|
|
|
|
|
|
Errors
|
|
~~~~~~
|
|
|
|
* As a Rule, Exceptions + RAII are to be preferred over error
|
|
codes and manual cleanup. At external interfaces we rely on
|
|
error states though.
|
|
* Exceptions can happen everywhere and any time
|
|
* Exceptions and Errors shall only be dealt with at locations where it's actually
|
|
possible to _handle_ them (the so called ``fail not repair'' rule)
|
|
* API functions are categorised by _error safety guarantee_
|
|
- *EX_FREE* functions won't raise an exception or set an error state,
|
|
unless the runtime system is corrupted.
|
|
- *EX_STRONG* functions are known to have no tangible effect in
|
|
case of raising an exception / error state (transactional behaviour).
|
|
- *EX_SANE* functions might leave a partial change, but care to leave
|
|
any involved objects in a sane state.
|
|
* Error states are identified by pointers to static strings.
|
|
* Error states are thread local and sticky (a new state can't be set
|
|
unless a pending state got cleared).
|
|
|
|
|
|
Exception hierarchy
|
|
^^^^^^^^^^^^^^^^^^^
|
|
Typically, when an error situation is detected, the error will be categorised
|
|
by throwing the appropriate exception. Exceptions provide a mechanism to attach
|
|
the root cause. The classification happens according to the _relevance for the
|
|
application_ as a whole -- _not_ according to the cause.
|
|
|
|
`lumiera::Error`:: root of error hierarchy. extends `std::exception`
|
|
`error::Logic`:: contradiction to internal logic assumptions detected
|
|
`error::Fatal`:: (⤷ `Logic`) unable to cope with, internal logic floundered
|
|
`error::Config`:: execution aborted due to misconfiguration
|
|
`error::State`:: unforeseen internal state (usually causes component restart)
|
|
`error::Flag`:: (⤷ `State`) non-cleared error state from C code
|
|
`error::Invalid`:: invalid input or parameters encountered
|
|
`error::External`:: failure in external service the application relies on
|
|
`error::Assertion`:: assertion failure
|
|
|
|
Please be sure to clear the error state whenever catching and handling an exception.
|
|
|
|
|
|
Error states
|
|
^^^^^^^^^^^^
|
|
Errors states get declared in headers with the `LUMIERA_ERROR_DECLARE(err)` macro.
|
|
A matching definition needs to reside in some translation unit, using the
|
|
`LUMIERA_ERROR_DEFINE(err, msg)` macro. There is no central registry, any component
|
|
can introduce its own error codes but must ensure that the error identifier is
|
|
unique.
|
|
|
|
.Error handling in C
|
|
There are two helper macro forms for setting up error conditions, one is
|
|
`LUMIERA_ERROR_SET..(flag, err, extra)` and the other one is
|
|
`LUMIERA_ERROR_GOTO..(flag, err, extra)`. Each for different logging levels.
|
|
The `SET` form just logs an error and sets it, the `GOTO` form also jumps to
|
|
an error handler. Both take a NoBug flag used for logging and a optional
|
|
`extra` c-string.
|
|
|
|
|
|
[source,C]
|
|
--------------------------------------------------------------------------------
|
|
const char*
|
|
mayfail()
|
|
{
|
|
const char* ret = foo();
|
|
if (!ret)
|
|
LUMIERA_ERROR_GOTO (flag, FOO_FAILED, 0);
|
|
|
|
if (!bar(ret))
|
|
LUMIERA_ERROR_GOTO (flag, BAR_FAILED, 1,
|
|
lumiera_tmpbuf_snprintf (256, "foo was %s", ret));
|
|
|
|
return "everything ok";
|
|
|
|
/* cleanup in reverse setup order */
|
|
BAR_FAILED1:
|
|
lumiera_free (ret);
|
|
FOO_FAILED0:
|
|
return NULL;
|
|
}
|
|
--------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
Singletons and Dependencies
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
Deliberately, Lumiera stays below the level of utilising a dependency injection framework.
|
|
Consequently, we access most services _by type name_, pulling up a single implementation
|
|
instance on demand. Yet some aspects of the *Singleton Pattern* are known to be problematic.
|
|
Rather than placing this singleton lifecycle logic into the individual implementation classes,
|
|
we use a _singleton factory_, to manage the instance creation and lifecycle of the singleton
|
|
or service instance separate from the singleton class itself. Singleton initialisation happens
|
|
lazily with this approach, and thus needs to be protected in a multithreaded environment; we
|
|
use Double Checked Locking with std::atomic for this purpose.
|
|
|
|
Singletons need to be written in a way to cope with that somewhat nondeterministic environment.
|
|
They will be created on-demand, and be destroyed _eventually,_ in the application shutdown phase.
|
|
This results in the general policy that within Lumiera, performing any ``business'' code in the
|
|
application shutdown phase (after exiting +main()+) is _strictly prohibited._ Generally speaking,
|
|
destructors _must not perform any significant work_ and are are expected to be failsafe.
|
|
|
|
.accessing the singleton instance
|
|
By convention, when clients are expected actively to access the singleton instance,
|
|
the interface class holds the singleton factory as a public static member with the
|
|
name +instance+. This allows clients to write `SomeService::instance()` to get a
|
|
reference to the implementation.
|
|
|
|
.subclasses and services
|
|
Actually, what appears as a mere ``singleton'' here is a form of _Dependency Injection,_
|
|
and can easily be extended to cover other forms of dependencies and other types of lifecycle.
|
|
Our experience indicates that this is rarely necessary, but when it happens, it's essential.
|
|
Lumiera's Dependency-Factory thus offers a configuration mechanism, which allows to switch
|
|
those modes of operation for individual types; the client consuming the ``singleton'' remains
|
|
unaware of such special setup.
|
|
|
|
.mock testing support
|
|
Moreover, the Dependency-Factory also offers a mechanism to ``push aside'' the existing singleton
|
|
or service instance and shadow it temporarily with a mock implementation. Again, it turned out that
|
|
such mocking mechanisms are used only occasionally and rarely required. Likely this is a result of
|
|
Lumiera being developed _test-driven_ -- things are being written mostly in a unit test friendly way.
|
|
|
|
-> more about link:{ldoc}/technical/library/Dependencies.html[dependency handling]
|
|
|
|
|
|
Visiting Tool
|
|
~~~~~~~~~~~~~
|
|
_tbw_
|
|
|
|
Iterators
|
|
~~~~~~~~~
|
|
Iterators serve to decouple a collection of elements from the actual data type
|
|
implementation used to manage those elements. The use of iterators is a
|
|
design pattern.
|
|
-> see link:{ldoc}/technical/library/iterator.html[detailed library documentation]
|
|
|
|
Lumiera Forward Iterator
|
|
^^^^^^^^^^^^^^^^^^^^^^^^
|
|
Within Lumiera, we don't treat _Iterator_ as a base class -- we treat it as a _concept_
|
|
for generic programming, similar to the usage in the STL. But we use our own definition
|
|
of the iterator concept, placing the primary focus on interfaces and decoupling.
|
|
Our ``Lumiera Forward Iterator'' concept deliberately removes most of the features
|
|
known from the STL. Rather, such an iterator is just the promise for pulling values
|
|
_once_. The iterator can be disposed when _exhausted_ -- there is no way of resetting,
|
|
moving backwards or doing any kind of arithmetic with such an object. The _exhausted
|
|
state can be detected by a +bool+ conversion (contrast this with STL iterators, where
|
|
you need to compare to an +end+ iterator). Beyond that, the usage is quite similar,
|
|
even compatible to +std::for_each+.
|
|
|
|
Iterator Adapters
|
|
^^^^^^^^^^^^^^^^^
|
|
We provide a collection of pre defined adapter templates to ease building
|
|
Lumiera Forward Iterators.
|
|
|
|
- a generic solution using a _iteration control_ callback API
|
|
- the `lib::RangeIter` just wraps up a pair of iterators for ``current position''
|
|
and ``and'' -- compatible with the STL
|
|
- there is a variant for automatically dereferencing pointers
|
|
- plus a set of adapters for STL containers, allowing to expose each value, each
|
|
key, distinct values and so on.
|
|
|
|
Iterator Adapters are designed for ease of use, they don't conceal the underlying
|
|
implementation (and the actual type is often quite convoluted).
|
|
|
|
Iteration Sources
|
|
^^^^^^^^^^^^^^^^^
|
|
To the contrary, the `lib::IterSource<TY>` template is an abstract base class.
|
|
This allows to expose the promise to deliver values through any kind of API, without
|
|
disclosing the actual implementation. Obviously, this requires the use of virtual
|
|
functions for the actual iteration.
|
|
|
|
Again, there are pre-defined adaptors for STL containers, but the actual container
|
|
is concealed in this case.
|
|
|
|
Itertools
|
|
^^^^^^^^^
|
|
Iterators can be used to build pipelines. This technique from functional programming
|
|
allows to abstract away the actual iteration completely, focussing only on the way
|
|
individual elements are processed. To support this programming style, several support
|
|
templates are provided to build _filtering iterators, transforming iterators,_ to pick
|
|
only _unique values,_ to _take a snapshot on-the-fly_ etc. There are convenience
|
|
builder functions for those operations, figuring out the actual source and destination
|
|
types by template metaprogramming.
|
|
|
|
Pipeline Builder
|
|
^^^^^^^^^^^^^^^^
|
|
As an extension to the iterator framework, the pipeline builder is able
|
|
to wrap arbitrary source iterators, decorate them with filters and transformations
|
|
provided as Lambda, and finally package all together into a self contained new
|
|
iterator (value object). This is a rather new feature [small]#(as of 2017)# and
|
|
we still need to determine its viability, yet it proved helpful when implementing
|
|
certain kinds of search algorithms on top of an abstracted data source exposed
|
|
as iterator. -> 'lib/iter-explorer.hpp'
|
|
|
|
|
|
Front-end for boost::format
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
Formatting values with `printf` is a notorious source for intricate errors.
|
|
Additionally, using (s|n)`printf` can be clunky in practice, and it doesn't
|
|
support custom defined string conversions, which are an important diagnostic
|
|
aid when working with objects. We might
|
|
link:{ldoc}/technical/howto/UsingBoost.html[use Boost], which provides the
|
|
`boost::format` library to address those problems. Unfortunately including this
|
|
header-only library solution incurs a significant overhead, both in terms of
|
|
compilation time and code size. While maybe still acceptable at the implementation
|
|
level, using boost::format is thus certainly a ``no go'' for any code residing
|
|
in headers frequently included.
|
|
|
|
To work around these problems, we provide a front-end wrapper, defined in
|
|
'lib/format-string.hpp'. This allows to keep the actual boost::format based
|
|
implementation confined to a single translation unit, while still being able
|
|
to use all primitive types as usual with boost::format or printf. Additionally,
|
|
our frontend automatically invokes a custom or built-in string conversion, if
|
|
applicable, it dereferences pointers and catches all errors encountered while
|
|
in formatting. So it's well suited for usage in error handling code.
|
|
|
|
[source,C]
|
|
------------------------------------------------------------
|
|
#include "lib/format-string.hpp"
|
|
using util::_Fmt;
|
|
|
|
double total = 22.9499;
|
|
const char * currency = "€";
|
|
|
|
cout << _Fmt("price %+5.2f %s") % total % currency << endl;
|
|
------------------------------------------------------------
|
|
|
|
WARNING: `boost::format` is known to be about 10 times slower than `printf` --
|
|
best to avoid it for performance critical code.
|
|
|
|
|
|
Wrappers and Opaque Holders
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
.smart handle
|
|
This pervasively used handle type is based on the reference counting
|
|
smart-pointer type of C\++ (`boost::shared_ptr` and C++11). Typically
|
|
this also includes a pointer to some kind of implementation service. +
|
|
Yet still, handles based on `lib::Handle<TY>` should not be confused with
|
|
smart pointers. Rather, we use the ref-counting mechanism to invoke a custom
|
|
clean-up callback when the last handle goes out of scope. Typically, the
|
|
implementation service is kept entirely opaque, while the copyable handle
|
|
objects also implement a front-end interface for client access.
|
|
|
|
.unified holder for value/ptr/reference
|
|
_tbw_
|
|
|
|
.ownership managing collection
|
|
When implementing services, frequently we encountered the situation that
|
|
a manager object creates and owns some component elements or sub-services.
|
|
The library provides two special collections, which also take ownership
|
|
of their contents and care for automatic clean-up. Especially, contrary
|
|
to the STL containers, those custom containers support use of
|
|
_non-copyable object_ (as a rule, all objects with reference semantics
|
|
are defined non-copyable in Lumiera).
|
|
|
|
- the `ScopedPtrVect` is a vector taking ownership of heap allocated objects
|
|
- the `ScopedCollection` is a fixed-size collection, holding all the child
|
|
objects within a single (heap allocated) storage block
|
|
|
|
|
|
.opaque holder
|
|
There is a family of holder objects, all based on placement-new of the
|
|
contained object into an embedded buffer. The purpose is to ``piggyback''
|
|
an object inline, without the need for heap allocated storage. Frequently
|
|
the motivation for this usage pattern is *type erasure*: the detailed knowledge
|
|
context used to build some kind of object is discarded prior to further use,
|
|
relying on generic information and the hidden parametrisation henceforth.
|
|
|
|
.polymorphic values
|
|
The C++ language has direct support for _value semantics_ and allows to build
|
|
value objects to be treated as first class citizens. Unfortunately this doesn't
|
|
fit well with the chosen approach to object orientation, where polymorphism relies
|
|
on reference semantics. Thus, most of the fundamental design patterns drive us into
|
|
having an object manager somewhere hidden within the implementation level, to
|
|
manage the memory for maintaining the subclass instances to be concealed
|
|
at the usage site.
|
|
|
|
To avoid this dilemma, we utilise the technique of the opaque holder to provide
|
|
objects with value semantics, while actually placing the instance of a subclass
|
|
into the inline buffer. Clients access this embedded object by automatic type
|
|
conversion to the interface type, which gives us polymorphism. While the
|
|
definition of such a beast is quite involved, the runtime overhead is
|
|
surprisingly low. When compared with standard polymorphism, creating
|
|
objects and invoking operations has zero overhead, while copying
|
|
and assignment incur the cost of an additional virtual call,
|
|
assuming the target objects cooperate by mixing in a
|
|
copy management interface.
|
|
|
|
.vector of references
|
|
a minimal interface for an array-like entity, but exposing only references
|
|
to the contained elements. Obviously this means to use a virtual call for
|
|
the subscript operation. This interface allows interfaces to expose something
|
|
_array-like_ without committing to a specific implementation type for the
|
|
exposed elements within the ``array''. The Lumiera library provides a set
|
|
of standard implementations for this +lib::RefArray+ interface, including
|
|
a vector based and a directly array based variant.
|
|
|
|
WARNING: in rework 2024 (Playback Vertical Slice)
|
|
|
|
|
|
Unique Identifiers
|
|
~~~~~~~~~~~~~~~~~~
|
|
|
|
LUID
|
|
^^^^
|
|
Generating 128 bit non cryptographic strong unique identifiers.
|
|
|
|
- having an alternative representation to store a pointer
|
|
- may be extended for a strong (slow) and a fast (weak) variant in future
|
|
|
|
EntryID
|
|
^^^^^^^
|
|
Combines an user readable ID, a (compile time) type tag and a hash-ID.
|
|
The latter is based on the symbolic ID and the type tag, which is discarded
|
|
at runtime (type erasure)
|
|
|
|
Typed Lookup
|
|
^^^^^^^^^^^^
|
|
_[,yellow]#planned#_ a system of per-type lookup tables, based on `EntryID`, together
|
|
with an type specific access functor. Together, this allows to translate
|
|
transparently and typesafe from symbolic ID to object instance, which
|
|
is an prerequisite for integrating a rules based system. Besides, these
|
|
tables allow unique IDs per type +
|
|
-> more details about this concept link:{l}/wiki/renderengine.html#EntryID%20TypedID%20TypedLookup[in the TiddlyWiki]
|
|
|
|
|
|
Allocators
|
|
~~~~~~~~~~
|
|
Lumiera utilises several custom allocators, each tailored to a specific purpose.
|
|
All these allocator-_frontends_ share a common pooling allocation backend
|
|
|
|
WARNING: currently (as of 2011) the low-level pooled allocator within the
|
|
backend isn't implemented; instead we do just heap allocations.
|
|
See Ticket #231
|
|
|
|
.Allocation Cluster
|
|
This allocation scheme is used within the context of the Builder and the
|
|
Low-Level-Model. The predominant usage trend in this realm is to create
|
|
and wire a family of small objects right after each other, within a build
|
|
process. These objects are intended to work together and will be discarded
|
|
all at once, after hot-swapping a new version of that model segment.
|
|
|
|
.Typed Allocation Manager
|
|
This allocation framework is used at various places when a large number of
|
|
similar objects is expected to be coming and going. New objects are
|
|
placement-constructed into the allocated space and immediately wrapped
|
|
with a ref-counting smart-ptr to manage ownership.
|
|
|
|
.BlockFlow (Epoch Allocator)
|
|
A custom memory management scheme for the Scheduler in the Render Engine.
|
|
The scheduler produces a _ongoing flow_ of _Render-Activity_ records, which
|
|
are added and wired, and then activated once based on temporal ordering.
|
|
For each frame calculation job and thus also for each supporting calculation
|
|
a _temporal deadline_ can be defined. This observation is exploited to order
|
|
the allocations into _extents based on deadline._ Once the deadline for such
|
|
an »epoch« has passed, all data records can be discarded without any further
|
|
checks or clean-up work.
|
|
|
|
|
|
|
|
Temporary Buffers
|
|
~~~~~~~~~~~~~~~~~
|
|
|
|
Provides a small number of round robin buffers which need not to be freed.
|
|
Must only be used locally when no deep recursion may use tmpbufs. Using these
|
|
wrong (recursively) will result in corrupted data.
|
|
|
|
Very fast and efficient from smallest too hugest allocations. No need to care
|
|
for 'free()'
|
|
|
|
|
|
Template Metaprogramming
|
|
~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
Typelists
|
|
^^^^^^^^^
|
|
_tbw_
|
|
|
|
Tuples
|
|
^^^^^^
|
|
_tbw_
|
|
|
|
Functor Utils
|
|
^^^^^^^^^^^^^
|
|
_tbw_
|
|
|
|
Duck Typing
|
|
^^^^^^^^^^^
|
|
_tbw_
|
|
|
|
|
|
|
|
Preprocessor Metaprogramming
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
ppmpl.h
|
|
|
|
|
|
Algorithms & Datastructures
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
Probabilistic Splay Tree
|
|
^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
Self optimising splay tree
|
|
|
|
Advantage:
|
|
* can be iterated
|
|
* very fast in situations where few common elements are queried most often
|
|
|
|
Disadvantages
|
|
* provides no own locking, needs to be locked from outside
|
|
* (almost) every access is a mutation and needs an exclusive lock, bad for concurrency
|
|
|
|
|
|
|
|
Hash functions
|
|
^^^^^^^^^^^^^^
|
|
_planned_
|
|
|
|
|
|
Linked Lists
|
|
^^^^^^^^^^^^
|
|
|
|
.llist
|
|
|
|
Cyclic double linked intrusive list.
|
|
|
|
|
|
|
|
//Undocumented
|
|
//access-casted.hpp
|
|
//advice.hpp symbol-impl.cpp
|
|
//allocationcluster.cpp symbol.hpp
|
|
//cmdline.cpp
|
|
//cmdline.hpp
|
|
//del-stash.hpp
|
|
//diagnostic-context.hpp
|
|
//element-tracker.hpp
|
|
//external
|
|
//factory.hpp
|
|
//format.hpp
|
|
//frameid.hpp
|
|
//functor-util.hpp
|
|
//handle.hpp
|
|
//hash-indexed.hpp
|
|
//iter-adapter-stl.hpp
|
|
//iter-adapter.hpp
|
|
//iter-source.hpp
|
|
//itertools.hpp
|
|
//lifecycle.cpp
|
|
//lifecycleregistry.hpp
|
|
//lumitime-fmt.hpp
|
|
//lumitime.cpp
|
|
//multifact-arg.hpp
|
|
//multifact.hpp
|
|
//nobug-init.cpp <<why here and not in common?
|
|
//nobug-init.hpp
|
|
//null-value.hpp
|
|
//observable-list.hpp
|
|
//opaque-holder.hpp
|
|
//p.hpp
|
|
//query.cpp
|
|
//query.hpp
|
|
//ref-array-impl.hpp
|
|
//ref-array.hpp
|
|
//result.hpp
|
|
//scoped-holder.hpp
|
|
//scoped-ptrvect.hpp
|
|
//scopedholdertransfer.hpp
|
|
//singleton-ref.hpp
|
|
//singleton-subclass.hpp
|
|
//singleton.hpp
|
|
//singletonfactory.hpp
|
|
//singletonpolicies.hpp
|
|
//singletonpreconfigure.hpp
|
|
//streamtype.cpp
|
|
//test
|
|
//thread-local.hpp
|
|
//tree.hpp
|
|
//typed-allocation-manager.hpp
|
|
//typed-counter.hpp
|
|
//util-foreach.hpp
|
|
//util.cpp
|
|
//util.hpp
|
|
//variant.hpp
|
|
//visitor-dispatcher.hpp
|
|
//visitor-policies.hpp
|
|
//visitor.hpp
|
|
//wrapper.hpp
|
|
//wrapperptr.hpp
|
|
|
|
|