technical overview: document some library and support facilities
This commit is contained in:
parent
336d4cb33f
commit
d6f5ed3282
1 changed files with 200 additions and 11 deletions
|
|
@ -389,6 +389,27 @@ 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_
|
||||
|
|
@ -406,6 +427,7 @@ Lua Scripting
|
|||
_tbw_
|
||||
|
||||
|
||||
|
||||
Library
|
||||
-------
|
||||
|
||||
|
|
@ -426,15 +448,80 @@ Intentionally no semaphores.
|
|||
|
||||
Time
|
||||
~~~~
|
||||
Time values are represented by an opaque date type `lumiera::Time`
|
||||
Time values are represented by a family of opaque date types
|
||||
with overloaded operators. The implementation is based on `gavl_time_t`,
|
||||
an integral (µsec) time tick value. Any Time handling and conversions
|
||||
is centralised in library routines.
|
||||
an integral (µsec) time tick value. 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
|
||||
~~~~~~
|
||||
|
|
@ -523,7 +610,33 @@ mayfail()
|
|||
|
||||
Singletons
|
||||
~~~~~~~~~~
|
||||
_tbw_
|
||||
Deliberately, Lumiera stays below the level of utilising a dependency injection framework.
|
||||
Consequently, we access most services _by type name_, pulling up a single implementation
|
||||
instance on demand. Rather than placing this singleton lifecycle logic into the individual
|
||||
implementation classes, we use a _singleton factory_, managing the lifecycle through static
|
||||
variables and placing the singleton object into a static memory block. Singleton initialisation
|
||||
is protected by a monitor per singleton type, while shutdown is triggered by the clean-up of
|
||||
a static variable. This results in the general policy that within Lumiera, performing any
|
||||
``business'' code in the application shutdown phase (after exiting +main()+) is _strictly
|
||||
prohibited._ Generally speaking, destructors _must not perform any significant work_ and
|
||||
are are expected to be failsafe.
|
||||
|
||||
.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 mock testing support
|
||||
There is a mechanism to ``push aside'' the existing singleton instance and shadow
|
||||
it temporarily with a mock implementation. Besides, there is a variation of the
|
||||
general singleton factory, allowing to fabricate a specific subclass of the
|
||||
exposed interface. Both of these facilities are rather technically involved
|
||||
and require some specific set-up -- fortunately it turned out that they are
|
||||
used only occasionally and rarely required. (Probably this is a result of
|
||||
Lumiera being developed _test-driven_ -- things are being written mostly
|
||||
in a unit test friendly fashion).
|
||||
|
||||
|
||||
Extensible Factory
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
|
@ -550,12 +663,58 @@ _tbw_
|
|||
|
||||
Wrappers and Opaque Holders
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
- smart handle
|
||||
- unified value/ptr/reference holder
|
||||
- ownership managing collection
|
||||
- opaque holder to ``piggypack'' an object inline,
|
||||
without the need for heap allocated storage
|
||||
- vector of references
|
||||
.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
|
||||
_tbw_
|
||||
|
||||
.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.
|
||||
|
||||
|
||||
Unique Identifiers
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
|
@ -584,7 +743,32 @@ tables allow unique IDs per type
|
|||
|
||||
Allocators
|
||||
~~~~~~~~~~
|
||||
_tbw_
|
||||
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.
|
||||
|
||||
.Simple Allocator
|
||||
Based on the allocator interface of the STL, allowing just for plain
|
||||
allocations and de-allocations without any further instance and lifecycle
|
||||
management. Currently (as of 2011) this allocator isn't used much -- it is
|
||||
conceivable that later we'll detect some specific STL based containers to be
|
||||
performance critical with respect to allocation.
|
||||
|
||||
|
||||
Memory Pools
|
||||
|
|
@ -623,6 +807,11 @@ Functor Utils
|
|||
^^^^^^^^^^^^^
|
||||
_tbw_
|
||||
|
||||
Duck Typing
|
||||
^^^^^^^^^^^
|
||||
_tbw_
|
||||
|
||||
|
||||
|
||||
Preprocessor Metaprogramming
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
|
|
|||
Loading…
Reference in a new issue