These are generic vehicles to verify computation structures.
Since they are in no way related to our models, they belong into »Vault« layer
Furthermore this reorganisation of test support components is prerequisite
to also move further generic facilities down into the Vault
This resolves a long-standing issue:
we could not use proper include paths for test-helper code that was defined alongside the tests.
Since some test-helper code must now be relocated into the »Vault«-Layer,
I took the opportunity to finally resolve this annoyance. The solution is two-fold
* introduce a dedicated test-support library
* remove the quirks that were necessary to support some plain-C tests
Together, this allows to raise the include-directory by one level,
and then to adjust all folder names so that they match the namespace
This is something I wanted to clean-up since a very long time indeed,
but always postponed it, since it requires a lot of nasty clean-up and disentangling....
Several parts had been stashed away into `src/lib/test`, which are not really related:
* the testrunner and testsuite builder
* generic helper utilities
* specialised test-support facilities, e.g. the EventLog
This changeset introduces a new top-level test folder and a namespace `::test`
Code from this folder is linked into a shared library `libtestsupport.so`,
which resides in the linkage hierarchy between »Vessel« and »Vault«,
yet is no longer part of the Lumiera application and only linked into the test-suite.
The next step with the `BufferProvider` and `OutputSlot` refactoring
will require to create new definition headers and to move those self-contained
services down into the »Vault« layer. In preparation, I decided deliberately to
abandon that old idea of a ''minimal bootstrap'' of arbitrary components in
favour of building the application as a coherent whole, with well-defined
boundaries and a backbone structure, marked as such explicitly by name
and arrangement of the code.
The changes were documented and applied to the integration branch first,
and now it's time to join that effort with the ongoing work on `BufferProvider`.
During the last months, a lot of gradual changes to policies and guidelines
had been finalised and documented. Moreover, recent clarifications regarding
memory management in the render engine will lead to a clearer definition
of the »Vault« Layer's role, with some ''engine code'' expected to move down.
On this occasion, I reworked relevant sections in the documentation to
confirm the role that now falls to central parts of the overall architecture.
Notably I decided to rename those parts related to the application backbone
as »Vessel« — to underpin the more active and defining role they assumed.
Explored further aspects of this new design;
as an aside, I tried to build a Concept to describe the policy,
which howerer turned out to be much more challenging than expected.
Lastly, I could now complete the prototype of `BufferProxyAdaptor`
While it is trivially simple to define some struct with callback functions,
such a definition turns out to be obnoxiously complex and hard to read in pracice,
thereby nullifying any benefit from the shorthand λ-based notation.
This kind of problem with the readability at client-side is quite common
when using callbacks for a policy-based design. To overcome this difficutly,
it would be necessary to define the policy and the callbacks in one
succinct builder expression. Unfortunately such a builder syntax
typically requires to write a lot of boilerplate template code.
After several failed experiments, I have found a bold new way to
exploit features of C++20 in conjunction with a macro, allowing
to generate a suitable data record directly form an C++ expression.
Now I have a working setup for a `BufferProxyAdaptor`.
Unfortunately this is still confusingly complex.
This is due to my intention to build a generic template,
that can be configured with a policy to invoke arbitrary callbacks,
whenever one of the passed-through buffers transitions through its lifecycle.
* we need to configure the `BufferProviderSetup` to use our specific implementation classes
* and the second one of these, our `BufferStore` implementation needs to invoke the callbacks
This closes a ''conceptual gap.''
There was a `lib::TestFrame::reseed()` function, which should be invoked
whenever a clean / deterministinc seed is required for computations involving
`TestFrame` data.
On occasion I noticed that most actual usages of `TestFrame`, which became quite common
lately, were missing this `reseed()` call, while the enclosing test
had indeed a proper Test::seedRand() call.
So my conclusion is: if even ''I myself'' miss that fine point frequently,
it is justified to automated this setup, even while `TestFrame` is right now
the only client of such a setup-facility.
There is a twist though.
`TestFrame` is part of the ''Engine'' test coverage, i.e. it is linked
into `libtest-core.so`. While the support for random numbers and reseeding
is obviously a very basic library feature, and linked into `liblumiera.so`
**Oh well**.
So now we have ''yet another'' static registration service.
I kept everything as simple as possible, which means, we continue
to use the ''common, global random generator'' from the library,
and there is no protection against some other code using those
library functions in a non-deterministic way.
`lib/random-reseed.cpp` is just a glorified hard-wired setup, beware!
This is an implementation experiment to validate the improvements
and internal clean-up. Previously, I was unable to implement such a
forwarding proxy buffer provider, as would be required for the
intended harmonisation of the `OutputSlot` interface with `BufferProvider`
With the reworked verison of the BufferProvider standard implementation
it seems straight-forward to...
* define a »Buffer Type« that marks any of the prepared buffers to pass-through
* use the resulting Buffer Descriptor together with a rigged storage backend,
that just returns the ''local tag'' as buffer adress
The first round of the [ticket:1410 BufferProvider clean-up] is roughly complete.
* a much cleaner diagnostic interface has been defined and implemented
* this allowed to modernise the `DiagnosticBufferProvider_test`
and the `BufferProviderProtocol_test`
* the implementation scheme has been split in two realms
* the old heap-memory based implementation has been re-arranged,
so that the aforementioned tests are **green** again.
Now is the point to draft a solution to get the interfaces of
`BufferProvider` and `OutputSlot` aligned, so that the latter
can expose its lifecycle through the former.
Today, I have analysed various aspects and developed a pathway
to get into that task, and next I will reshape the previously
failed `OutputProxyProvider_test` into a prototype to
validate this idea
* write down some first considerations regarding requirements
of the actual production-strength implementation of buffer management
* consider relevance of the part of the API that was retained
* write a new series of test cases to cover handling of embedded objects,
using the test support for tracking object creation
Some weeks ago, at the time where I started to dig into this topic,
I extended the BufferProviderProtocol_test to perform a buffer usage pattern
that mimics the usage in the real Render Engine, as implemented last year.
For this, I used the `TestFrame` and the chained-hash dummy computations.
Now, after reordering and clean-up of the `BufferProvider` implementation interfaces,
it can be showed with this computation scheme that
- all buffers are indeed allocated and handed out as expected
- the metadata handling is correct and points to the processed buffers
- the complete memory used in this test is uncorruped and holds correct numbers
It covers now roughly the same procedures as before,
yet the scheme how to do verification is quite different.
Furthermore, we allocate large random-sized buffers now
and implant `TestFrame` instances, and so also the verification
covers a significant storage area.
* can now replace old verification code by new diagnostic functions
* test case runs without failure
''UNRESOLVED:'' failure in verification of memory contents
In the end I refrained from applying most of the changes I had intended.
I am aware that `BufferProvider` is still quite a massive structure,
yet with the implementation refactorings done thus far, it seems
to be reasonably clear and self-contained now.
It would be possible to totally overthrow the grown structure and
expose only the two handles (BuffDescr and BuffHandle), together with
a factory function, but I cannot see how this would help to make the
code clearer and more approachable. Maybe it would make the usage
more obvious from the client's perspective — yet, to be honest,
there are not ''that much clients'' to justify such a move.
Furthermore, the implementation is far from finished: for both of
the back-end lines, a fully production-strength implementation
must be created, that is able to operate in a highly concurrent
environment; very likely some lock-free messaging and hand-over
between threads will become necessary, adding to the complexity.
Thus it seems better to note the ''design critique'', but to
postpone any further refactorings to a point where we can watch
a fully developed Render Engine under production load.
These were defined in a confusing and implementation-oriented way.
Since we have now mostly abstracted away the metadata handling from the
`BufferProvider` front-end, a more succinct definition was chosen
* a ''Descriptor'' is valid when the lookup succeeds
* a ''Handle'' is likewise valid when the underlying
state-Key can be resolved, and the memory address is given
Some aspects regarding the state remain unresolved though.
Ideally we'd want a `BuffHandle` to be valid preciselc when
it ''is in valid state to be accessed''
Now able to remove some obsoleted part of the old implementation.
The existing implementation of the ''Buffer Provider Protocol'' is
quite old and dated meanwhile; it was more like an explorative prototype
and mixed up implementation and test code.
At that time, notably the assumption was that the core render engine
will be implemented in plain-C and will push around `void*` all over the place.
The `BufferProvider` was meant as an adaptor, while questions of typing
were added as an afterthought. Especially some functionality to use
the `BuffHandle` in the way of a smart-ptr are completely obsoleted
by the reworked design of the Render Engine (2024).
Since the BufferStore implementation component is now supposed
to handle only the memory allocation, a complete end-to end data flow
must be ensured, so that allocation / deallocation functions get all
the relevant information. Notably, the `LocalTag` is now used as an
implementation-defined conduit for further (opaque) marker data.
Since all data passed over this API are simple values (even `LocalTag`
is effectively a 64bit integer), we can now avoid relying on output
parameters or similar side-effect tricks.
...just enough to be able to write the assertions in the first test case
(..and the test case fails still, due to the pending work on sorting out
the implementation APIs between `BufferStage` und `BufferStore`)
To push ahead with the refactoring,
I will build the test/diagnostics API completely anew,
and attach it solely to an instance of `DiagnosticBufferProvider`.
Unfortunately this ''leads us into chainsaw surgery...''
* it uncovers a host of conceptual contradictions
* actually, the `HeapMemProvider` was more an implementation
of diagnostic tracking, than it was an actual `BufferProvider`
implementation (hence the old name: `TrackingHeapBlockProivder`)
* and this lured me into using these tracking functions directly,
while the `DiagnosticBufferProvider` remained in unfinished state.
So basically I have now **disabled** all verification from the Unit-tests.
I'll have to rebuild them from scratch, thereby reproducing the logic/intention
- write some documentation
- identify some add-on API that must be retracted
- consider further how to disentangle ''diagnostics'' and ''implementation''
More or less forcibly squeeze all the existing implementation
into the new basic structure with two separate »pillars«
**Breakages**
- existing code expected the `BufferStore` to transition to ''locked'' state
- existing code expected `provideLockedBuffer(typeID)` to tail-call the
`buildHandle()` function of the enclosing `BufferProvider` base implementation
- various diagnostic usages bypassed the official APIs and called directly
into the implementation. This is no longer possible; rather, `DiagnosticBufferProvider`
needs to provide additional diagnostic API to suit those purposes
The outward interface has stood the test of time quite well.
But the back-side towards the implementation is a tangled mess,
presumably due to being developed from prototyping code: the
actual implementation is completely overgrown by diagnostics
and test instrumentation, since running in unit test setup
was the only actual usage of this code ever since.
My first attempt with decomposing into a decorator-chain failed.
Thus the new line of attack is to introduce two implementation
pillars right from top-level
- `BufferStage` : the state and metadata handling
- `BufferStore` : the storage backend
These are defined as nested interfaces, and the further refactoring
starts with subclassing everything, nested as it is.
The intention is to build the tracking functionality from scratch,
while wrapping the old implementation as delegate. Hopefully this allows
to reduce the complexity of the existing implementation step by step,
until I'm ''able to grasp what this implementation does''.
This old implementation works, but is highly problematic code.
The Git history shows that I had already problems to understand this code
while building the DiagnosticOutputSlot, one year later.
To my defense, in both cases (2011 and 2012), I had no clear idea yet
how the processing within the engine would work, and thus I considered
that Buffer Type registry a mere implementation detail, that can be
lumped into the »code behind«.
The prototype implementations of BuferProvider and OutputSlot are confusing;
in-depth analysis shows that this is related to intermingling use
of test instrumentation and features of a prototypical default.
To get anywhere with the existing code, it seems we'd need to split off
the test instrumentation (i.e. `DiagnosticBufferProvider`) altogether,
even if this causes duplication of implementation code. Many years ago,
when first implementing this prototype, I had no clear idea what the usage
in the Render Engine entails, yet I noticed that some basic buffer »tracking«
could be used as implementation primitive to simplify the prototyping work.
This might be one of the reasons, why the "code behind" in the protocol
implementations both in `BufferProvider` and `OutputSlot` became so strongly
tangled, so that I way unable to make both protocols orthogonally composable.
The implementation code for `DiagnosticOutputProvider` and `TrackingHeapBlockProvider`
look very confusing and tangled. It seems, instrumentation for tests
got mixed up with the effort to provide a simplistic prototype implementation.
What we need foremost here is the latter; most tests that import the
DiagnosticOutputProvider do not even use any instrumentation....
It seems evident that the the problems with the existing BufferProvider implementation
are rooted in a lack of understanding of the actual usage situation at that time(2011).
This caused a »hexagonal design«, i.e. something entirely driven by uint testing
for the sake of unit testing.
So to identify the unsuitable decisions, some degree of deductive work is necessary,
based on our much better understanding of the Render Engine.
A crucial challenge is that buffer management must perform well
within a massively concurrent environment, and do so without excessive latency.
This picks up the work for [ticket:1395 resolve integration of BufferProvider with DataSink (#1395)]:
Almost a year ago, before the long hiatus due to important general clean-up
and maintenance work, and the research regarding Video output —
a discrepancy was discovered between the »Buffer Provider Protocol«
and the »Output Slot Protocol«. This mismatch prevents us from building
a simple preliminary `BufferProvider` to be used for the engine integration
as part of the [ticket:1228 »Playback Vertical Slice«].
After re-establisihing the work context, I have read through the new
Render Node code built last year, and distilled the sequence of
buffer management operations that actually happen during a recursive
`pull()` call. With this kowledge, I am now able to fill in the
''standard case for rendering'' — which I was unable to formulate
during the first attempt of building the Render Engine in 2011.
__Next Steps__: Redesign of the standard implementation base of the
`BufferProvider` to prepare for a massively concurrent usage.
* Simplify some constructs from the Python-3 migration
* update copyright, since I did maintain the SCons build continuosly
* remove unused method from Buildhelper
Furthermore, the documentation page for our build system
https://lumiera.org/documentation/technical/build/SCons.html
has been reworked, to add a synopsis, some further background
information about the internal structure of SCons and about
the specific conventions and definitions used for Lumiera
...funny enough I never noticed this obvious mistake,
since I never install software directly into my system,
while the DEB-build does not use absolute paths...
Solution:
* SCons has this speical convention that a path prefixed with '#'
is resolved relative to the root of the build (where the SConstruct resides)
* now we apply this automatically to the two relevant settings
** INSTALLDIR
** TARGDIR
* but only (conditionally) if the configured path is relative, not absolute
As a consequence, most other hard-coded usages of the '#'-prefix can then be dropped
A long-standing unnecessary mutual dependency caused rebuild
of most tests in the standard target, because the ''valgrind suppression''
executable was classified as »tool«, while also depending on the libraies
with the test code.
...this bug was spotted as the test failed reproducibly
when built with `-fstack-protector-strong` — which adds
additional "canary" markers to some kinds of problematic
stack storage; this seemed to have the effect that now
the second test uses exactly the same location as the
preceding test, and thus finds valid data in the newly
created work buffers.
There is no reason for using uninitialised storage in this
test (I can recall that I wanted to build a generic helper
and intended to use that as ''virtual overlay'' over existing
memory — but that usage never took place, and the `struct Buffer`
is not a general-purpose tool now, but only made for this
specific test. Thus memory can be easily zero-initialised.
- reorganise the navigation tab structure
- place all further index lists into a common "Index" tab
- include a list of concepts
- remove the various "member" sub-tabs, these are not helpful
- the "modules" tab is now called "topics" (since C++ has now Modules!)
- generally re-sync DoxygenLayout.xml with a current pristine template
- comb though the Doxygen Warnings and fix a lot of small problems
Notably some links on the **Gnome documentation** pages are problematic,
since there was seemingly an infrastructure change -- which unfortunately
leads to loosing several deep-links into the GTK-3 related documentation
and tutorials.
While the differences to GTK-4 are often rather minimal, I would still prefer
to link to those documentation pages used as reference at implementation time
of our related library and GUI functionality.
In several cases I have thus looked up the old URLs at Archive.org
Yet another chainsaw massacre.
One of the most obnoxious annoyances with C++ metaprogramming
is the need to insert `typename` and `template` qualifiers into
most definitions, to help the compiler to cope with the syntax,
which is not context-free.
The recent standards adds several clarifications, so that most
of these qualifiers are redundant now, at least at places where
it is unambiguously clear that only a type can be given.
GCC already supports most of these relaxing rules
(Clang unfortunately lags way behind with support of newer language features...)
Now able to remove most complicated comparison operators and most usages of boost::operators...
In most cases it is sufficient just to define one ''spaceship operator'',
and often even that one can be synthesised.
However — we still use boost::operators for arithmetic types,
notably the `lib::time::TimeValue`, which is addable and mutipliable
Only minor rearrangements necessary to make that possible with C++20
And while at this change (which requires a full rebuild of Lumiera)
- simplify the defined comparison operators, as C++20 can infer most variations
- also mark various usages of `const char*` either as Literal or CStr
Remark: regarding copyright, up to now this is entirely my work,
with two major creation steps in 2008 (conception) and
in 2017 (introduction of a symbol table)
This changeset removes various heuristics and marker-traits
by a constraint to tuple_like types. Furthermore, several usages
of `apply` can thereby be generalised to work on any tuple_like.
This generalisation is essential for the passing generic data blocks
via `FeedManifold` into the node invocation
- integrate the concept definition into tuple-helper.hpp
- use it to replace the `is_Structured` traits check
- do not need `enable_if_TupleProtocol` any more
Integrate test coverage of the concept metafunctions
and the generalised get accessor
''This changeset was made at LAC 2025 in Lyon, France''
...to rely on the new formulation and the extended template `WithIdxSeq`
This is in preparation to use this new iteration scheme also from the tuple_like concept
Motivated by the difficulties encountered with `std::apply` —
which basically forced us to define our own alternative with
conceptually more adequate limitations....
...so these are the first attempts towards building a C++20 concept.
The Lumiera »Reference Platform« is now upgraded to Debian/Buster, which provides GCC-14 and Clang-20.
Thus the compiler support for C++20 language features seems solid enough, and C++23,
while still in ''experimental stage'' can be seen as a complement and addendum.
This changeset
* upgrades the compile switches for the build system
* provides all the necessary adjustments to keep the code base compilable
Notable changes:
* λ-capture by value now requires explicit qualification how to handle `this`
* comparison operators are now handled transparently by the core language,
largely obsoleting boost::operators. This change incurs several changes
to implicit handling rules and causes lots of ambiguities — which typically
pinpoint some long standing design issues, especially related to MObjects
and the ''time entities''. Most tweaks done here can be ''considered preliminary''
* unfortunately the upgraded standard ''fails'' to handle **tuple-like** entities
in a satisfactory way — rather an ''exposition-only'' concept is introduced,
which applies solely to some containers from the STL, thereby breaking some
very crucial code in the render entities, which was built upon the notion of
''tuple-like'' entities and the ''tuple protocol''. The solution is to
abandon the STL in this respect and **provide an alternative implementation**
of the `apply` function and related elements.
This resolves an intricate problem related to metaprogramming with
variadic templates and function signatures. Due to exceptional complexity,
a direct solution was blocked for several years, and required a better
organisation of the support code involved; several workarounds were
developed, gradually leading to a transition path, which could now
be completed in an focused clean-up effort over the last week.
Metaprogramming with sequences of types is organised into three layers:
- simple tasks can be solved with the standard facilities of the language,
using pattern match with variadic template specialisations
- the ''type-sequence'' construct `Types<T...>` takes the centre stage
for the explicit definition of collections of types; it can be re-bound
to other variadic templates and supports simple direct manipulation
- for more elaborate and advanced processing tasks, a ''Loki-style type list''
can be obtained from a type-sequence, allowing to perform recursive
list processing task with a technique similar to LISP.
after all the relevant library components do support both kinds of
type sequences transparently, any usages in core code can now be
switched over to the new, variadic type sequences.
A very performance relevant shortcoming of the existing implementation
of partial function closure is that the result is always wrapped into a
std::function, which typically causes a heap allocation when more than
a single pre-bound argument must be stored — which is annoying,
since the underlying Binder provides inline storage and thus
could be handled directly as a value object.
However, returning the Binder directly is also problematic, since
this object is outfitted with several overloaded function call operators,
which defeats most techniques to detect a function signature. Notably,
relevant down-stream metaprogramming code, like the tuple-closure used
in the `NodeBuilder` would break when being confronted directly with
a binder object.
An investigation shows that there is no direct remedy, short of
wrapping the binder into another functor. This can be accomplished
with a helper template, that generates a wrapper; however, this
wrapper builder must be supplied with explicit type information
regarding the function arguments (precisely because this type
signature can not be picked up from the Binder object itself)
This is a rather intricate and technical change, but allows in the end
to switch back all usages to a main implementation patch, which is now
based on `func::BindToArgument` — so this could become the final
implementation core and replace the old `PApply` template eventually...
Largely, these changes are related to allow for ''perfect forwarding''
of the functor and the argument values to be closed; these will be
copied into the ''Binder object'' created by `std::bind`.
Notably the `TupleConstructor` was changed to perfect-forward its »source«
into the specialised `ElmMapper`; this is possible since the latter
already receives a `SRC` template parameter, which can be supplied
with whatever base type the `std::forward` invocation will expose.
In the specialisation relevant here, template `PartiallyInitTuple`,
now an ''universal reference'' is stored and passed to `std::get`,
so that (depending on the input used), either a LValue or an
RValue reference is used for the extracted data elements.
After these changes, all existing usages of `applyFirst()` or `applyLast()`
can be replaced by this modernised implementation back-end, thus obsoleting
the various hard-coded workaround added during the last years.
A lot of repetitive, pre C++11 metaprogramming code can now be removed,
and several helper constructs, which were needed to handle generic
function application and passing a tuple of values to create a binder.
Note however, the highly complex and technical core of this header
still remains intact; which is to create a ''partial closure'' over
some arguments of a function, while keeping the remaining arguments
open as parameters for invocation.
TODO: Even in the remaining code there is a lot of redundancy
and helper construct which are no longer necessary
...because swapping in the new standards-based implementation
leads to compile failures on tests to cover out-of-bounds cases.
Under the (wrong) assumption, that some mistake must be hidden in
the Splice-metafunction, I first provided a complete test coverage;
while the actual problem was right below my nose, and quite obvious...
The old implementation, being based on a case distinction over the argument count,
simply was not able even to notice excess arguments; other the new implementation,
based on variadics and `std::apply`, which is fully generic and thus
passes excess arguments to `std::bind` when a position beyond the actual
argument list is specified to be closed.
The old behaviour was to silently ignore such an out-of-bounds spec,
and this can be reinstated by explicitly capping the prepared tuple
of binders and actual arguments passed to `std::bind`
Another question of course is, if being tolerant here is a good idea.
And beyond that, function-closure.hpp is still terrifyingly complex,
unorganised and use-case driven, to start with....
...can now be formulated in a single function,
based on the apply-to-λ technique invented by David Vandervoorde.
WARNING: the rewritten version of BindToArgument<...>::reduced()
does not compile in the out-of-bounds case, revealing a possibly
long standing defect in the typelist-metafunction Splice
This library header was developed at a time, where C++ had no built-in support
for so called "invokables"; `std::invoke` and `std::apply` were added much later;
So in that early version that was a significant technical hurdle to overcome.
seems like it might be possible to get rid of the TupleApplicator alltogether?
This is one of the most problematic headers, because it is highly complex
and comprises tightly interwoven definitions (in functional programming style),
which in turn are used deep within other features.
What concerns me is that this header is very much tangled
and pushes me (as the author) to my mental limits.
And on top of this comes that this code has to deal with intricate aspects
like perfect forwarding, and proper handling of binder instances and
function argument copying (which basically should be left to `std::bind`)
Fortunately, the changes ''for this specific topic'' are transparent:
Type sequences are not used on the API for function closure and composition,
but only as an internal tool to assemble argument tuples used for either
binding or invocation of the resulting (partially closed) function.
To bootstrap this tricky refactoring, initially a bridge definition
was used, with a variadic argument pack, but delegating to the old
non-variadic type sequence and from there further into LISP style
list processing of types and meta definitions, as pioneered by the
Loki libarary. Luimiera uses this technique since a long time to
perform the complex tasks sometimes required for code generation
and generic function and type adaptation.
with this changeset, a direct variadics based entrance into
type list processing is provided, so that the old definition
is now completely separate and can be removed eventually.
Most of the type-list and type-sequence related eccosystem can be
just switched over, after having added the conversion variants for
the new-style variadic type sequences
Again this was used as opportunity to improve readability of related tests
As expected, these work on the new-style variadic type sequences
equally well than on the old ones (tail-filled with `Nil` markers).
On that occasion, a complete makeover of the huge test case was carried out,
now relying on `ExpectString` instead of printing to STDOUT. This has the
benefit of showing the expectation immediately next to the code to be tested,
and thus makes it much easier to ''actually see'' how these meta-functions
operate on their parameters (which in fact are types in a type list)
- provide complete conversion paths old-style ⟷ new-style
- switch the basic tests to the new variadic sequences
- modernise the code; replace typedefs by `using`
- change some struct-style ''meta-functions'' into
constexpr or compile-time constants
Since I've convinced myself during the last years that this kind
of typelist programming is ''not a workaround'' — it is even
superior to pattern matching on variadics for certain kinds
of tasks — the empty struct defined as `NullType` got into
more widespread use as a marker type in the Lumiera code base.
It seems adequate though to give it a much more evocative name
Attempting to reduce the remaining pre-C++11 workarounds before upgrade to C++20...
As a first step: rename the old type-sequence implementation into `TyOLD`
to make it clearly distinguishable; a new variadic implementation `TySeq`
was already introduced as partial workaround, and the next steps
will be to switch over essential parts of the type-sequence library.
After the leftovers of the first Render-Engine implementation attempt were removed,
only one further usage of `RefArray` remains to be sorted out: the ''Session Element Tracker''.
Luckily, this one did not actually make any use of the abstraction abilities
of the `RefArray` — rather it basically stated that ''the interface is a data structure...''
After considering ''what kind of data'' can be expected to live in this structure,
it became clear that ''this will be a symbolic representation''
And thus the container can be simply switched to a `std::vector`.
This change allows to retain the existing placeholder-implementation unaffected,
while it would be possible to maintain algebraic terms here, in future.
__As an asside__: in order to decide about a suitable replacement in the Session,
I had to consier a first draft regarding the intended usage
and the prospective way of content representation
Investigated this topic again...
* these were initially created before C++11
* at that time, ''non-copyable'' objects were not common place
* but we embraced that concept already, and thus had quite some pain
when attempting to use such objects in STL containers
* with C++11 and ''move semantics'' these problems basically evaporated
* most usages were already upgraded and resolved
* another use case is to handle a state variable, which is based on
an immutable entity (like Time entities); `ItemWrapper` can be used
as a remedy in such a situation
This was a pre-C++11 implementation, and at that time,
I developed the ScopedHolder to allow handling non-copyable objects in STL containers
Meanwhile we have move semantics to achieve the same goal;
and since `ScopedPtrVect` shall be retained, it should be upgraded,
using the copy-and-swap approach
This is a plausible concept, and without obvious replacement
(letting aside `boost::ptr_vector`). It has a small number
of usages, and provides a dedicated API to show the
semantics when used as implementation of an ''Object Manager''
The original implementation used private inheritance from `std::vector`,
which is not really justified here, since we neither use the ''template method''
pattern, nor want to gain access to protected internals.
So this can be replaced with a private member.
...initially, this header was a collection of small helpers made on occasion;
one of them, the `ItemWrapper` used in transforming pipelines came
into widespread use and was much augmented and improved over the years.
many other tiny helpers could be replaced by standard library facilities...
Now looking into largely obsolete library facilities...
Starting from `ScopedHolder`, I found a surprising problem with ''perfect forwarding....''
...which however turned out to be the result of ''sloppy programming'' on my side.
At that time, in 2017, seemingly I was under pressure to define a Session-Command,
which captures a Time-entity as »State Memento«. Which turned out to be impossible,
since the Time entities were conceived as being immutable -- a questionable design
decision (see #1261), which already generated quite some additional complexities.
In the course of this »exercise«, I could again clarify that the implementation
of perfect forwarding works as expected on modern compilers — confusion may arrise
sometimes due to ''copy elision'' (which the compiler is required to perform
since C++17, even when the elided constructor has observable side effects).
And it can be derailed when (as was the case here) a »grab everything« constructor
accidentally ends up generating a copy- or move-constructor for the container class
itself. This is an instance of the problem documented with #963 ...
.... and the best solution is to abide by the rule-of-five (and a half)
and to put that `ReplacableItem` to where it belongs...
During the early stage of the Project, at some point I attempted
to »attack« the topic of Engine and Render Nodes following a ''top down path.''
This effort went into a dead end eventually — due to the total lack
of tangible reference points to relate to. However, the implementation
at that time prompted the development of several supporting facilities,
which remain relevant until today. And it resulted in a ''free wheeling''
compound of implementation structures, which could even be operated
through some highly convoluted unit test.
This piece of implementation code was valuable as starting point for th
»Playback Vertical Slice« in 2024 — resulting in a new design which was
''re-oriented'' towards a new degree of freedom (the »Domain Ontology«)
while handling the configuration and connectivity of Render Nodes in
a rather fixed and finite way. This new approach seems to be much more
successful, as we're now able to build, connect and invoke Render Nodes,
thereby mapping the processing through a functor binding into some
arbitrary, external processing function (which will later be supplied
by a media processing library — and thus be part of some »Domain Ontology«)
The `FixedFrameQuantiser` relied on three functions from the raw-time handling library.
Since this (and NTSC drop-frame) are the only usages, these functions
can be relocated into the implemntation translation unit `lib/time/quantiser.cpp`
On closer inspection, this reveals some room for improvements:
Instead of relying on raw-computation functions written in C,
we could rather revert the dependency and express these computations
in terms of our Time-entities, which are written in C++, are much more
systematic and provide consistency checks and protection against numeric
overflow, all integrated with linear arithmetic and concise notation.
After performing these rearrangements,
most of the functions can be collapsed into ''almost nothing''.
This was taken as opportunity to re-check and improve the remaining
implementation core of the `FixedFrameQuantiser` -- the handling of
extreme corner cases can be much improved, now representing the
"grid-local time" as `Offset`, which doubles the possible value range.
The reworked unit test shows that, with this change, now the limitation
happens prior to quantisation, meaning that we always get a grid-aligned
result, even in the most extreme corner cases.
...extract these functions and the associated test
from the low-level C time handling library and
document them with a dedicated C++ header and test.
''This is unfinished work'' —
the extracted functions provide only the low level computations;
actually, a specialised time quantisation or time code would be required.
------------
Note though,
after extracting these functions, the rest of the plain-C test
can be removed, since equivalent functionality is covered in
much more detail by the tests of the C++ time handling framework.
Notably this allows to get rid of the direct component accessor functions.
------------
__Remark__: the base implementation of many time conversion functions
and especially NTSC drop-frame was provided by Stefan Kangas
See:
6a44134833
While these function may seem superficially plausible,
I more and more come to the conclusion that offering such
function as ''basic building blocks'' is in itself an
ill-guided approach to handling of time entities.
Time is neither „just a number“ — nor does it „contain“ hours, minutes and seconds.
It is possible to ''represent'' it through a **time-code**, which incurs
a quantisation step and implies a reference grid.
Thus Lumiera ''should not offer'' a »basic time handling library«.
Doing so would be just an invitation to bypass proper time handling
and avoid the use of more demanding but also more adequate mental concepts.
So the next step will be to remove functions not deemed adequate, and
better directly inline the respective modulus based computations.
Other functions can be integrated into the respective implementation
translation units for time quantisation and timecode representation.
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
This is a first step towards the ability to produce several different output formats...
Refactor the code to separate
- the double buffering
- the actual image generation, which works in RGB
- the conversion routine
Furthermore, replace unsigned char by std::byte
and introduce std::array and structured binding
to avoid many usages of pointers; hopefully this
makes the intention of the code clearer.
Verified and cross-checked the actual converion logic;
in fact this is a conversion to "YUV" as used by MPEG,
which in more precise terms is Y'CrCb with Rec.601 colour space
and a scan range limitation (16...235) on the Luma component.
...to be more compliant to the »Lumiera Forward Iterator« concept.
This can be easily achieved by inheriting from util::RegexSearchIter,
similar to the example in CSV.hpp
Regarding #896, I changed the string rendering to use fs::path::generic_string
where appropriate, which means that we're using the normalised path rendering
Since C++17 we can use the std::filesystem instead (and we ''do use it'' indeed)
- relocate the `/lib/file.hpp` header
- adapt the self-discovery of the executable to using std::filesystem
Furthermore, some recherche regarding XVideo and Video Output
- remove obsolete configuration settings
- walk through all settings according to the documentation
https://www.doxygen.nl/manual/config.html
- now try to use the new feature to rely on Clang for C++ parsing
- walk through the doxygen-warnings.txt and fix some obvious misspellings
and structural problems in the documentation comments.
With Debian-Trixie, we are now using Doxygen 1.9.8 —
which produces massively better results in various fine points.
However, there are still problems with automatic cross links,
especially from implementation to the corresponding test classes.
The Boost-Libraries changed their internal implementation
of the formula to chain hash values.
Fortunately, we had already extracted the existing implementation
from Boost 1.67 and incorporated it in-tree, in the Lumiera support libary.
After switching to that `lib:#️⃣:combine()` function, all the graph
computations related to the Scheduler-test-load can be shown to be identical.
So at the moment, the impact is still limited, but this incident highlights
the importance of a controlled, stable (and ideally also portable) hash implementation.
seems that I've played too much with »undefined behaviour« in this test;
basically we can not assume ''any'' specific placement of local variables
in a stack frame....
In this test, what I wanted to demonstrate is that the overflow-block can reside
just »anywhere«, and that HeteraoData is just a light-weight front-End and accessor.
However, I can just demonstrate that without totally ''undefined behaviour;''
placement-new can be used to force the storage at a specific location (in the UninitialiesdStorage);
continue to access and use that data after leaving the nested scope is still
kind-of borderline, yet demonstrates that the data itself is just residing in a storage block...
- with Debian 12/13, the top-level `/bin`, `/sbin` and `/lib`
are collapsed into `/usr`. Seemingly this has prompted changes
to the way the shell prints some error messages. This broke
the expectation of some test of the test-framework itself.
- SCons always had the policy to ''sanitise'' the invocation environment,
to prevent unintended impact of environment settings to the test subject.
Seemingly this now also leads to `$HOME` not being defined.
Our file handling framework however expects to be able to expand "~"
- An old-style cast in the constructor lib::diff::Record(Mutator const&)
is now translated into a static_cast (≙conversion); and since the appropriate
conversion operator is missing on Mutator, the constructor attempts to
create a temporary, by re-invoking the same constructor ⟹ Stackoverflow ↯
- conversion from pointer to bool now counts as ''narrowing conversion''
- constructor names must not include template arguments (enforced with C++20)
- better use std::array for some dummy test code
Several further warnings are due to known obsoleted or questionable constructs
and were left as-is (e.g. for ScopedHolder) or just commented for later referral
This is an advanced diagnostics added (presumably) with GCC-13
and attempts to protect against an insidious side-effect of ''overload resolution''
Basically C++ (like its ancestor C) is oriented towards direct linkage and adds
the OO-style dynamic dispatch (through virtual functions and a VTable)
only as an extension, which must be requested explicitly.
Thus the resolution of ''overloads'' (as opposed to ''overridden'' virtual functions)
always takes precedence and happens within the directly visible scope,
which can cause the compiler to perform an implicit conversion instead of
invoking a different virtual function, which is defined in a base class.
However, this diagnostics seems to be implemented in an overly zealous way:
The compiler warns at the time of the type instantiation, and even in cases
where it is effectively impossible to encounter this dangerous shadowing situation.
See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=109740
This leads to several ill-guided warnings in the Lumiera code base, which unfortunately
can only be addressed by disabling this diagnostics for all usages of some header.
The reason is, we often generate chains of template instantiations driven by type lists,
and in such usage pattern, it is not even possible to bring all the other inherited overloads
into scope (with a using `BASE::func` clause), because such a specification would be ambiguous
and result in a real compile error, because even the interface is generated from a chain of mix-in templates
Future C++ versions will no longer generate default copy operations
once any single one was defined explicitly. So the goal is to kind-of
''enforce the rule of five'' (if you define one, define them all).
However, sometimes one of these special operators must be defined for a different reason,
e.g. because it is defined as protected, yet should not be exposed on the public API.
In such cases, any other copy operation which still is valid in the default form
must be declared explicitly ''as defaulted''
Overall this seems to be quite an improvement --
and it highlights (again) some known instances of questionable design,
which are mostly obsoleted and require clean-up anyway, or (as in the case of the
Placements) indicate »placeholder code« where the actual solution still needs to be worked out
Oh this is an interesting one...
GCC now highlights situations with `-Wpessimizing-move`,
where an overly zealous developer attempts to optimise by `std::move`,
which however prevents the compiler from applying the ''Return Value Optimisation''
The latter is mandatory since C++17, and essentially means that a value object
created within a function and then returned (by value) will actually be created
directly in the target location, possibly eliding a whole chain of
delegating value returns.
Thus: if we write `std::move(value)`, we change the returned type into an RValue reference,
and thereby ''force the compiler'' to invoke a move-ctor....
* need to upgrade our custom packages to current standards
* switch those packages from CDBS to dh
* re-build on Trixie and upgrade the Lumiera DEB-Depot
After these (in detail quite expensive) preparations,
build with Scons and GCC-14 can be started.
Fix some further (basically trivial) compile problems,
uncovered by the improved type checking of modern compilers.
Note: a tremendous amount of warnings (and depreciations) is
also indicated, which will be addressed later....
Based on the building blocks developed thus far,
it was possible to assemble a typical media processing chain
* two source nodes
* one of these passes data through a filter
* a mixer node on top to combine both chains
* time-based automation for processing parameters
As actual computation, hash-chaining on blocks of
reproducible random data was used, allowing to verify
for every data word that expected computations were
carried out, in the expected order.
Using basically the same topology as in the preceding test, which focused on connectivity. However, in this case we retrieve actual processing functions from the »Test-Rand« ontology in order to perform hash-chaining computations on full data blocks. And, in addition, a »Param Agent Node« is used.
While initially intended as introductory test, it meanwhile
focuses on intricate technical details on the level of
basic building blocks, notably the `FeedManifold`
Now I have added a simple end-to-end demonstration example
how a Render Node is built from scratch, leaving out all
technical details and all convenience front-ends like
the `NodeBuilder` — just one dummy port invoked directly.
NodeBase_test demonstrates the building blocks of a Render Node,
and verifies low-level mechanics of those building blocks, which
can be quite technical. At the top of this test however are some
very basic interactions, which serve as an introduction.
__Remark__: renamed the low-level technical dispatch-access
for the parameter-accessors in `TurnoutSystem` to be more obvious,
and added comment (I was confused myself how to use them properly)
This is a crucial feature, discovered only late, while building
an overall integration test: it is quite common for processing functionality
to require both a technical, and an artistic parametrisation. Obviously,
both are configured from quite different sources, and thus we need a way
to pre-configure ''some parameter values,'' while addressing other ones
later by an automation function. Probably there will be further similar
requirements, regarding the combination of automation and fixed
user-provided settings (but I'll leave that for later to settle).
On a technical level, wiring such independent sources of information
can be quite a challenging organisational problem — which however can be
decomposed using ''partial function closure'' (as building a value tuple
can be packaged into a builder function). Thus in the end I was able to
delegate a highly technical problem to an existing generic library function.
* now able to demonstrate close-front, close-back and close-argument
* can also apply the same cases to `std::array`, with input and
output type seamlessly adapted to `std::array`
With these additions, all conceivable cases are basically addressed.
Take this as opportunity to investigate how the existing implementation
transports values into the Binder, where they will be stored as data fields.
Notably the mechanism of the `TupleConstructor` / `ElmMapper` indeed
''essentially requires'' to pass the initialisers ''by-reference'',
because otherwise there would be limitations on possible mappings.
This implies that not much can be done for ''perfect forwarding'' of initialisers,
but at least the `BindToArgument` can be simplified to take the value directly.
...which should ''basically work,'' since `std::array` is ''»tuple-like«'' —
BUT unfortunately it has a quite distinct template signature which does not fit
into the generic scheme of a product type.
Obviously we'd need a partial specialisation, but even re-implementing this
turns out to be damn hard, because there is no way to generate a builder method
with a suitable explicit type signature directly, because such a builder would
need to accept precisely N arguments of same type. This leads to a different
solution approach: we can introduce an ''adapter type'', which will be layered
on top of `std::array` and just expose the proper type signature so that the
existing Implementation can handle the array, relying on the tuple-protocol.
__Note__: this changeset adds a convenient pretty-printer for `std::array`,
based on the same forward-declaration trick employed recently for `lib::Several`.
You need to include 'lib/format-util.hpp' to actually use it.
What emerges here, seems to be a generic helper to handle
partial closure of ''tuple-like'' data records. In any case,
this is highly technical meta-programming code and mandates
extraction into a separate header — simplifying `NodeBuilder`
Likely the most widely used facility, which enters into meta-programming
with type sequences, is our function-signature-detector `_Fun<X>`,
which returns a argument type-sequence.
After adding some bridges for cross-compatibility,
function-arguments are now extracted as a new-style,
''variadic sequence'' without trailing `NullType`.
Doing so required to augment some of the most widely used
sequence-processing helpers to work seamlessly also with the
new-style variadic sequences with a definition variant based
on variadics, which typically will later-on obsolete the original
solution, which at that time needed to be tediously coded as a
series of explicit specialisations for N arguments.
...on top of the parameter-decorating functionality developed thus far.
The idea is to allow in the `NodeBuilder` to supply ''some parameters''
directly, while the remaining parameters will be drawn from automation.
Several years ago, I developed some helpers for partial function closure.
Unfortunately these utils are somewhat limited, and rely on some pre-C++11
constructs, yet seem to be usable for the task at hand, since parameters
are always expected as value objects by definition.
This changeset shows a working proof-of concept for left-closing a
parameter tuple with 5 elements; this turns out to surprisingly difficult
due to the full genericity of the acceptable parameter-aggregates...
seemingly the definition can not be much simplified,
since there is no way around handling several definition flavours
of the processing-functor distinctly.
However, the definitions can be rearranged to be clearer,
the resulting type of the `FeedPrototype` can be deduced from the
builder function, and more stringent assertions can be added