Commit graph

1676 commits

Author SHA1 Message Date
96d30cb5e7 Invocation: further considerations regarding diagnostics
Requirement analysis indicates that a »Node ID« is rather tangential
to the core operation of calculating media; the only infliction point
seems to be the generation of ''systematic cache keys.''

A spec — especially for the `Turnout` however is very relevant for
diagnostics, error reporting and unit testing. So we are in the
difficult situation where rather elaborate functionality is
required only for a secondary concern, and moreover the
node data structure imposes a critical memory leverage.
2024-11-02 02:57:35 +01:00
aab8278579 Invocation: Analysis regarding node and turnout identity
The immediate next goal is to verify properties of render nodes
generated by the builder framework; two kinds of validations
can be distinguished
 * structural aspects of the wiring
 * the fact that processing functionality is invoked in proper order

Looking into the structural aspects brings about the necessity
to identify the actual processing function bound into some functor.
Some recapitulation of goals and requirements revealed, that this
can not be a merely technical identity record — because the intention
is to base the ''cache key'' on chained processing node identities,
so that the key is stable as long as the user-visible results will be
equivalent. And while structural data can be aggregated, at the
core this information must be provided by the scheme embedded
into the domain ontology, which is tasked with invoking the
builder in order to implement a ''specific processing-asset''
2024-11-01 03:51:53 +01:00
9022a69a71 Invocation: simplest render-node test PASS
Review the achievements from the last days and map out the further path
for test-driven build-up of a render-node network and invocation.

Notably ''several layers of prototyping'' are in the works now;
it is important to understand the purpose of each such round of
prototyping and to draw the necessary conclusions after closing out.

The next topic to investigate relates to the ''identity'' of nodes and
ports within nodes; this entails to generate a ''symbolic spec'' that
can be verified and used as base for a systematic hash-ID and cache-key...
2024-10-27 02:45:15 +02:00
c29c10fd62 Invocation: runtime error checks for auto-wiring
Since it would in fact be possible to access and write beyond the configured storage,
simply by using the builder API without considering consistency,
it seems advisable to use explicit runtime checks here, instead of
only assertions, and to throw an exception when violating bounds.

Moreover, unsuccessfully attempted to better arrange the functionality
between PortBuilder and WeavingBuilder; seemingly we have an rather tight
coupling here, and also the expectations regarding the processing function
seem to be too tight (but that's the reason why it's an prototype...)
2024-10-26 04:11:36 +02:00
d91d0b5926 Invocation: provide functionality to connect lead ports explicitly
...which then also allow to fill in the missing parts for the
default 1:1 wiring scheme, which connects each »input slot«
of the processing function with the corresponding ''lead node''
2024-10-25 18:13:55 +02:00
43373e11e7 Invocation: prepare mechanism for default input wiring
The intention is to offer an automatic 1:1 association
between the »input parameter slots« of the processing function
and the ''lead nodes,'' thereby always using the same default
port, corresponding to the current port number under construction.

Unfortunately, the preceding refactoring removed the information
necessary for a simple implementation, as the port array is now
built up late, in the final build() function...
2024-10-25 05:10:16 +02:00
0144049f9d Invocation: reconsider data access and allocator usage from Builder
The next step is to round out the first prototypical implementation,
which requires access to ''lead node ports'' and thereby generally
places focus on the interplay of ''data builders'' within the ongoing
build process. While the prototype still uses the fall-back to simple
heap allocation, notably the intended usage will require to wire-through
the connection to a single `AllocationCluster`. This poses some
challenge, since further ''data builders'' will be added step-wise,
implying that this wiring can not be completed at construction time.

Thus it seems indicated to slightly open-up the internal allocator
policy base template used by `lib::SeveralBuilder` to allow for some
kind of ''cross building'' based on a shared compatible base allocator
type, so that the allocation policy wiring can be passed-on from an
existing `SeveralBuilder`
2024-10-23 16:27:09 +02:00
554a64e212 Invocation: solve passing of the function definition
- the chaining constructor is picked reliably when the
  slicing is done by a direct static_cast

- the function definition can be passed reliably in all cases
  after it has been ''decayed,'' which is done here simply by
  taking it by-value. This is adequate, since the function
  definition must be copied / inlined for each invocation.

With these fixes, the simplest test case now for the first time
**runs through without failure**
2024-10-22 05:59:00 +02:00
df37fec500 Invocation: switch WeavingBuilder to produce the result via λ
This change allows to disentangle the usages of `lib::SeveralBuilder`,
so that at any time during the build process only a single instance is
actively populated, all in one row — and thus the required storage can
either be pre-allocated, or dynamically extended and shrinked (when
filling elements into the last `SeveralBuilder` currently activated)

By packaging into a λ-closure, the building of the actual `Port`
implementation objects (≙ `Turnout` instances) is delayed until the
very end of the build process, and then unloaded into yet another
`lib::Several` in one strike. Temporarily, those building functor
objects are „hidden“ in the current stack frame, as a new `NodeBuilder`
instance is dropped off with an adapted type parameter (embedding the
λ-type produced by the last nested `PortBuilder` invocation, while
inheriting from previous ones.

However, defining a special constructor to cause this »chaining«
poses some challenge (regarding overload resolution). Moreover,
since the actual processing function shall be embedded directly
(as opposed to wrapping it into a `std::function`), further problems
can arise when this function is given as a ''function reference''
2024-10-22 03:20:50 +02:00
0de7905444 Invocation: introduce pattern data holder into NodeBuilder
- add an unspecified data holder type into the NodeBuilder
- establish ability to cross-build a `NodeBuilder` with adapted data holder
2024-10-21 04:40:51 +02:00
b4aee6fba8 Invocation: work out solution for a precisely fitting allocation
Conduct in-depth analysis to handle a secondary, implementation-related
(and frankly quite challenging) concern regarding the placement of node
and port connectivity data in memory. The intention is for the low-level
model to use a custom data structure based on `lib::Several`, allowing for
flexible and compact arrangement of the connectivity descriptors within
tiled memory blocks, which can then later be discarded in bulk, whenever
a segment of the render graph is superseded. Yet since the generated
descriptors are heterogeneous and, due to virtual functions, can not be
trivially copied, the corresponding placement invocations on the
data builder API must not be mixed, but rather given in ordered strikes
and preceded by a dimensioning call to pre-reserve a bulk of storage

However, doing so directly would jeopardise the open and flexible nature
of the node builder API, thereby creating a dangerous coupling between
the implementation levels of the node graph and of prospective library
wrapper plug-ins in charge of controlling details of the graph layout.

The solution devised here entails a functional helper data structure
created temporarily within the builder API stack frames; the detailed
and local type information provided from within the library plug-in
can thereby be embedded into opaque builder functors, allowing to
delay the actual data generation up until the final builder step,
at which point the complete number and size requirements of
connectivity data is known and can be used for dimensioning.
2024-10-20 21:52:00 +02:00
634df743f0 Invocation: investigate Problems with allocation-growing
This investigation was set off by a warning regarding an
unused argument in `SeveralBuilder`, using `AllocationPolicy::moveElem()`
This warning is correct and easy to fix, but (luckily) it brought my
attention to the fact that a `SeveralBuilder<Port>` can not grow dynamically,
which is somewhat mitigated by the default policy to pre-allocate several
elements, which would work to some degree but waste a lot of memory.

This points to a deeper problem with the implementation pattern used for
all those Builders: they create their product by-value, which must then
be moved into the intended target location.
And doing so is **extremely dangerous**, given that our very goal is to
build a complex data structure internally connected by direct references
and ideally also allocated with a high degree of memory locality.

Unfortunately I do not see any favourable alternative yet;
Ideally all products should be `NonCopyable` — but then, the builder
implementation scheme would become even more complicated and less intuitive
and additionally the client code would need to pre-declare the number of
expected Leads and Ports (not clear if this is even feasible)
2024-10-14 18:29:29 +02:00
4a963c9fee Invocation: draft how the 1:1-fallback wiring could work
...and as expected, this turns up quite some inconsistencies,
especially regarding usage of the »buffer types«.

Basically, the `PortBuilder` is responsible for the high-level functionality
and thus must ensure the nested `WiringBuilder` is addressed and parameterised
properly to connect all »slots« of the processing function.
 - can use a helper function in the WiringBuilder to fill in connections
 - but the actual buffer types passed over these connectinos are totally
   unchecked at that level, and can not see yet how this danger can be
   mitigated one level above, where the PortBuilder is used.
 - it is still unclear what a »buffer type« actually means; it could
   be the pointer type, but it could also imply a class or struct type
   to be emplaced into the buffer, which is a special extension to the
   `BufferProvider` protocol, yet seems to be used here rather to transport
   specific data types required by the actual media handling library (e.g. FFmpeg)
2024-10-14 04:07:47 +02:00
4df4ff2792 Invocation: consider minimal test setup and verification
__Analysis__: what kind of verifications are sensible to employ
to cover building, wiring and invocation of render nodes?
Notably, a test should cover requirements and observable functionality,
while ''avoiding direct hard coupling to implementation internals...''

__Draft__: the most simple node builder invocation conceivable...
2024-10-13 03:49:01 +02:00
bb9d0107cd Invocation: setup preliminary engine-context
* decision how to provide a default service for tests
   while also allow for configuration of more specific services
 * as starting point for the prototype: use the `TrackingHeapBlockProvider`
   (simply because this is the only implementation available and tested)
2024-10-12 04:17:39 +02:00
4a83f97c9e Invocation: draft setup for engine context and config
Prototyping and analysis revealed that some aspects of the render node wiring
refers to effectively global services and can thus be taken out of the picture
by relying on classical ''Dependency Injection''

Consequently, `EngineCtx` needs a default implementation, which brings up
a simplistic fall-back version of those services in support for prototyping.
Moreover, dedicated lifecycle functionality must be provided to bring up
and shut down the actual service instances intended for operational use.
2024-10-11 16:57:10 +02:00
a02873a015 Invocation: pass the actual processing function
...need to pass a binding for the actual processing function
in a way that it acts as a ''prototype'' — since the `Feed`,
i.e. the ''Invocation Adapter'' must be generated for each
invocation anew within the current stack frame
(so to avoid spurious heap allocations)
2024-10-11 04:39:40 +02:00
9490192ef8 Invocation: integrate WeavingBuilder into PortBuilder
...seems that the former is well suited to serve as detail builder
used internally by the latter to provide a simplified standard adaptation
for a given processing function.

The integration can be achieved to layer a specialised detail builder class
on top, which can be entered only by specifying the concrete function or lambda
to wrap for the processing; the further builder-API-functions to control
the wiring in detail become thus only accessible after the function type
is known; this allows to place the detail builder as member into the
enclosing port builder and thus to allocate everything within the current
stack frame invoking the builder chain.
2024-10-11 02:45:51 +02:00
5f0b8b8a81 Invocation: usage analysis for prototype
...after having determined the several levels of prototyping
currently employed, an important step ahead could be achieved
by analysing the intended and implied usage context of this
builder scheme, while still assuming the simplifications
related to prototyping.

It can be assumed that
 * the Level-2 builder object is ''somehow provided''
 * the invocation happens from within a media-handling lib-plugin
 * alongside with the desired `ProcAsset` spec, an `ExpectationContext`
   will be provided, allowing to pass-through additional semantic tags

The implementation in the lib-plugin is then able to draw from specific
knowledge related to the **Domain Ontology** for ''especially for this library''
and provide the necessary wrappers and parameter mapping information.

⟹ the **Level-2**-builder should thus expose an API to
 * set up a straight forward mapping, based on a given wrapper functor
   to delegate to the actual library invocation
 * allow optionally to override some of the input connections
 * alternatively allow to use a complete `InvocationAdapter`,
   including a `FeedManifold`, as provided directly by the library-plugin
2024-10-10 21:35:02 +02:00
bad6751aae (after extended break)
...caused by personal circumstances
...attempt to understand the context I was working on

 * Integration is driven by the `NodeLinkage_test`
 * the near-term goal is to ''get any node built'' — simplified
 * the outline of the `NodeBuilder` and `PortBuilder` is settled
 * the task at hand is how to fill in the definition of a `Port`
 * which in turn ''requires prototyping'' — to establish a kind of weaving-pattern
 * the immediate next thing to do is to ''build an `InvocationAdapter` within the »test-ontology«''
2024-10-09 03:48:18 +02:00
409f2f20b8 Invocation: introduce engine-context for dependency injection
...by relying on DI for some effectively global services, notably
the cache provider, the API for building and wiring render nodes
can be simplified to cover only the actual node connectivity
2024-08-03 03:21:59 +02:00
38dd3a738e Invocation: introduce optional output buffer
Doing so directly seems to be a better solution than to inject an OutputBufferProvider;
the latter will still be needed, yet will not be part of the regular weaving pattern,
but used directly at top-level to obtain the output `BuffHandle`, which is then
passed to the `Port::weave()` call
2024-07-31 19:20:44 +02:00
4bda550f68 Invocation: investigate ways to configure the output-info
...still not convinced that this is a good design,
since it seems to subvert the general design to treat one special case.
However, I can't see a good way to address this special case directly
2024-07-30 23:44:55 +02:00
a4ff2081b9 Invocation: explore variants to pass output connection
There might be one specific output result buffer at top level
for each invocation, which must be delivered into a prepared
output sink. This amounts to one special case, cross-cutting
an otherwise completely generic data flow scheme.

After considering several solutions, it seems most straight-forward
to configure a specific `OutputBufferProvider` to serve as a proxy for
the `OutputSlot` / `DataSink` provided at top-level to the Render-Job.

As an asside, this analyis reveals that the result-slot number does
not belong into the `FeedManifold`, which is dynamic (on the stack);
rather, it's a fixed value configured as part of the `WeavingPattern`
2024-07-30 20:05:48 +02:00
9a23aa773b Invocation: analyse usage of buffer metadata entries
Code clean-up: mark all buffers with a dedicated tagging type


The point in question is: if we work the LocalTag into the type-hash,
could it be possible to miss an existing entry in the metadata registry?
This could cause two entries to be locked for a single buffer address,
leading to data corruption.

As far as I can see, in the current usage this would not happen,
but unfortunately this problem can not be ruled out, since the BufferProvider
API and protocol is designed to be open for various usage patterns.

However, the same potentially disastrous pattern could also materialise
when registering two different buffer types, and then locking each
for the same buffer location.
2024-07-28 19:29:27 +02:00
72c7386435 Invocation: apply a consistent ordering on the chained hash calculation
...this seems to be a tricky aspect; we use hash-chaining to create
derived entries, which may cause the identity of an entry to depend
on the order of specialisation. Looked through the possible code paths,
but these seem to be quite complicated; I see the lurking danger of
creating a second entry (with a different hash), and then in worst case
even locking/unlocking a given buffer twice....
2024-07-27 23:53:27 +02:00
6d7a814495 Invocation: settle upon a way to mark the output buffer
...this is a surprisingly tricky issue, since it undercuts the
generic and recursive implementation of buffer handling;

fortunately I've foreseen such demands may arise down the road
and I've reserved an »Local Key« (now renamed into `LocalTag`),
whose meaning is implementation defined and interpreted by
the specific `BufferProvider`
2024-07-27 17:17:02 +02:00
ea183086ca Invocation: Prototyping to clarify buffer type marking
Requirement analysis shows that the ''actual buffer provider'' to use
constitutes yet another independent degree of freedom, which conceivably
must be handled by the Builder internals rather than by the Domain Ontology.

Thus the simple solution to use a `BuffDescr` to mark the type must be augmented
to also allow configuration of the underlying `BufferProvider`, which generates
the descriptor and can later be invoked with this descriptor to ''lock  an actual Buffer.''

In some cases, setup of the buffer types could even be more complicated and require
access to the actual (runtime) invocaton context; such extreme cases however
could be rendered as an extension of the scheme established here,
by storing the (up to now transient) constructor functors persistently.
Which leads to the decision not to care for those extremely complicated
corner cases right now, and thus to construct all buffer descriptors
in the `build()` call
2024-07-24 20:29:37 +02:00
42f8f8d5af Invocation: Prototyping how to setup invocation wiring
...still fighting to find a suitable API to define
how inputs and outputs are connected and mapped to function parameters.

The solution drafted here uses the reshaped `DataBuilder` (≙`lib::SeveralBuilder`)
to add up connections for each »slot«, disregarding the possibility of permutations.
Similar to `NodeBuilder`, a policy template is used to pass down the setup
for an actual custom allocator.
2024-07-24 03:52:44 +02:00
352ef31ab0 Invocation: further condense the allocator-selector definition
After applying all the preceding refactorings, it turns out that
the `DataBuilder` defined here ''is essentially `lib::SeveralBuilder`'',
only with a different arrangement of the type parameters, due to the
specific usage context here.

It is thus possible to replace all the interim / helper / rebinding templates
by simple templated typedefs. The only tangible difference is that for
usage in the Builder, a ''selector policy'' is passed as a simple type argument,
which in practice wires the concrete allocator information down into each
sub-builder created during the ongoing construction of a node structure.
2024-07-23 02:38:06 +02:00
1705c40dc2 Invocation: rearrange SeveralBuilder policy
redefine the policy for `lib::SeveralBuilder` to be a template-template parameter.
In fact it should have been this way from start, yet defining this kind of
very elaborate code bottom-up lets you sometime miss the wood for the trees

So to restate: `lib::SeveralBuilder` takes a ''policy template,''
which then in turn will be instantiated with the same types `I` (interface)
and `E` (element type) used on `SeveralBuilder` itself. Obviously, there can be
further types involved and thus additional type parameters may be necessary,
notably the ''Allocator'' — yet these are better injected when ''defining''
the policy template itself.

The default binding for this policy template is defined as `allo::HeapOwn`,
which causes the builder to allocate the storage extents through the standard
heap allocator, and for the created `lib::Several` to take full ownership of
embedded objects, invoking their destructors when falling out of scope.
2024-07-17 01:43:17 +02:00
2fd3629d80 Invocation: difficulties with defining a suitable DataBuilder
As a direct consequence of the insights regarding Dependency-Injection,
a ''Builder Toolkit'' is required, which can be used to adapt various
kinds of ''Weaving Patterns'' — since obviously it is not possible to
settle down on a single Pattern, and thus several ''families of builders''
will emerge, one for each ''line of construction'' for ''Weaving Patterns''.

To stress this point, what I am coding here is a prototype, aimed at
being used as part of a **Test Domain Ontology** — and other Domain Ontologies
(e.g. für FFmpeg) will certainly require other construction schemes
for their Weaving Patterns. So this is an open field, and can not be
settled once and for all.

This immediately leads to another, rather technical problem:
If we're about to work with ''delegate Builders,'' then also
a way to pass-down the allocator configuration is required.

We had settled on a preliminary solution with the helper `DataBuilder`,
yet this solution looks like it defines how `lib::SeveralBuilder`
should be used in most of the cases. So there is now a conflict
between the existing definition scheme for `lib::SeveralBuilder`,
which was achieved in a bottom-up way, and a slightly different
definition scheme ''as it should be''

Starting to attack this latter detail problem, as a first step,
the definition of `DataBuilder` can be simplified by collapsing
it with the `lib::allo::SetupSeveral`
2024-07-17 02:35:05 +02:00
fc9ff9252a Invocation: clarify role of Buffer-Descriptor and Dependency-Injection
It became clear that a secondary system of connections must be added,
running top-down from a global model context, and thus contrary to the
regular orientation of the node network, which connects upwards from
predecessor to successor, in accordance with the pull principle.

If we accept this wiring as part of the primary structure, it can be
established immediately while building the nodes, thus adding a preconfigured
''pattern of Buffer Descriptors'' to each node, since there is no further
''moving part'' — beyond the wiring to the `BufferProvider`, which thus
becomes part of a global `ModelContext`

As an immediate consequence, the storage for this configuraion should
also be switched to `lib::Several` and handled similar to the primary
node wiring in the Builder...
2024-07-15 18:52:59 +02:00
968bfb8fab Invocation: look for ways how to build the Turnout
It seems we need a `WeavingPattern`-Builder, which obviously
must be rather flexible, since those patterns are to be composed
from several layers, which should be extensible within a given ''Domain Ontology''

So this seems to lead to a builder-DSL which creates »**onion layers**«
of builders, with the ability to extend and specialise the type on each layer.

''As it will be quite challenging to get this into usable shape,
it seems best to approach this step by step through prototyping...''
2024-07-15 02:26:53 +02:00
95046489d2 Invocation: release buffer after completed calculation
Not entirely sure how to use the `emit()` call properly,
assuming that it means that data is complete in buffer,
but can still be read after that point
2024-07-15 00:36:20 +02:00
133dd9e8ce Invocation: now able to implement connecting of buffers
* at least for a simple, prototypical setup
 * and actually shifting the onerous into the Level-1 builder \\
   ''(which is precisely the intention here)''
2024-07-12 03:06:11 +02:00
1955d28087 Invocation: use introspection to setup an example
The deeper problem is that we must not engage into any premature decisions
regarding the structure or layout of the actual processing function invocation.

Thus attempting to create a kind of »firewall« of sorts, by connecting
the building blocks strictly through template parameter and preferably
figuring out any detailed knowledge locally, through ''compile-time introspection...''
2024-07-11 18:35:17 +02:00
e7b68427d3 Invocation: look for ways to build an example InvocationAdapter
...even the initial effort to stub its operation turns into a
challenge, since honestly there is near nothing we can assume safely,
without sliding into uncovered provisions regarding the ''Domain Ontology''
2024-07-11 16:31:11 +02:00
0d7d4b5afa Invocation: consider how to arrange the InvocationAdapter
- it is clear that this adaptor will be a ''Concept''
- yet it must in some way access the `FeedManifold` and also control additional storage
- a rather obvious solution is to layer it ''on top'' of the manifold
2024-07-11 02:41:33 +02:00
ec65e2b7b9 Invocation: continue draft of a simple 1:1 WeavingPattern
...which brings about various (preliminary) decisions regarding
Metadata storage in the `Turnout`-object, which acts as a guidance
and specification for the actual invocation for this specific node.

As starting point, I choose the ''KISS'' solution of embedding some
blocks of `UninitialisedStorage` directly into the `Turnout`; obviously
these blocks must be oversized, since we can not effort emitting a
dedicated template instance for each different count of input / output
feeds. Moreover, these data buffers are assumed to be filled with
valid objects by the builder ''(this is a lurking danger)''
2024-07-10 03:35:51 +02:00
0b938320ea Invocation: draft a simplified prototype for an invocation
...attempt to somehow get my foot into the door...
2024-07-09 21:06:38 +02:00
3d6515acca Invocation: further analysis of invocation structure
...turns out that the intended structure is still too fine grained
and explicit and many operational steps can be collapsed into a single
virtual scope, wherein they can be deemed implementation detail...
2024-07-09 18:03:13 +02:00
d09b061434 Invocation: add nested builder to configure a port
...which brings us right into the middle of the task of building a Turnout...
2024-07-08 19:24:03 +02:00
d3344e7dd3 Invocation: improve notation by using a wrapper
...so the solution is to build up the working data as `lib::SeveralBuilder`;
however, a more concise notation can be achieved with a suitably configured
wrapping subclass; together with the cross-builder trick, this allows
to write the allocation configuration in a clearly libelled way,
while the field definition and the builder constructor hides the
complexities of picking up the extension point and passing on the
wiring to the allocator instance.
2024-07-08 05:41:31 +02:00
cedb1830dc Invocation: work out solution for builder initialisation
...turns out to be surprisingly tricky, since the nested
lib::SeveralBuilder instances require parametrisation by a
''policy template,'' which in turn relies on the actual allocator.
And we want to provide the allocator as a constructor parameter,
including the ability to pick up a custom specialisation for
some specific allocator (notably AllocationCluster requires
to hook into this kind of extension point, to be able to
employ its dedicated API for dynamic allocation adjustment)
2024-07-08 03:56:38 +02:00
b01fc6e350 Invocation: adjustments to lib::Several to prepare for allocator use
* conduct analysis regarding allocator handling in the Builder
 * turns out we'll have to keep around two different allocators while building
 * ⟹ establish the goal to confine usage of the Node allocator to the lower Levels
 * consequently must open up the `lib::SeveralBuilder` to be usable
   as an intermediary data structure, while building up the target data
 * in the initial design, the `SeveralBuilder` was kept opaque, since
   contents can be expected to be re-located frequently and thus exposing
   elements and taking references could be dangerous — yet this is also
   true for `std::vector` however, so people are assumed to know
   when they want to shoot themselves into their own foot
2024-07-07 16:12:22 +02:00
58a955a879 Invocation: first draft of the node builder invocation 2024-07-06 21:31:03 +02:00
7c554caf08 Invocation: clarify further requirements for the Level-2 builder
...especially what is necessary to represent at this level and what information
is implicit; notably there will be an implicit default wiring, but we allow
for case-by-case deviations
2024-07-06 04:37:36 +02:00
1f7ddbe5ec Invocation: draft possible syntactic structure based on these conjectures
The Builder will have to perform several passes, gradually refining
the model into the low-level Render Node network. Right now, some
guesses regarding the last steps of this process are possible,
thus defining the lowest level of a model builder structure
 * Level-3 : mapping data flow paths
 * Level-2 : detailed configuration of data buffer passing
 * Level-1 : build the actual parameter structures for invocation

In the current »Vertical Slice« we're able to fully define Level-1
and maybe Level-2
2024-07-06 01:28:18 +02:00
ce9bf7f143 Invocation: conjectures pertaining an implementation of Node-Graph generation
To escape a possible deadlock in analysis, I resort to developing
some kind of free-wheeling presupposition how the **Builder** could
be implemented — a centrepiece of the Lumiera architecture envisioned
thus far — which ''unfortunately'' can only be planned and developed
in a more solid way ''after'' the current »Vertical Slice« is completed.

Thus I find myself in the uncomfortable situation of having to work towards
a core piece, which can not yet be built, since it relies heavily on
the very structures to be built...
2024-07-06 01:13:23 +02:00
604c4b580b Invocation: painstaking analysis of calculation requirements
...the complexity of details is a nightmare
...still fighting to grasp a generic structure allowing to ''fold down''
   the details into the specific ''domain ontologies'' for the media libraries
2024-07-03 04:34:04 +02:00
8c536fc637 Invocation: consider what is required to setup a FeedManifold
...and this line of analysis brings us deep into the ''Buffer Provider''
concept developed in 2012 — which appears to be very well to the point
and stands the test of time.

Adding some ''variadic arguments'' at the right place surprisingly leads
to an ''extension point'' — which in turn directly taps into the
still quite uncharted territory interfacing to a **Domain Ontology**;
the latter is assumed to define how to deal with entities and relationships
defined by some media handling library like e.g. FFmpeg.
So what we're set to do here is actually ''ontology mapping....''
2024-06-29 04:22:23 +02:00
717af81986 Invocation: Identify parts relevant for a node builder
The immediate next step is to build some render nodes directly
in a test setting, without using any kind of ''node factory.''
Getting ahead with this task requires to identify the constituents
to be represented on the first code layer for the reworked code
(here ''first layer'' means any part that are ''not'' supplied
by generic, templated building blocks).

Notably we need to build a descriptor for the `FeedManifold` —
which in turn implies we have to decide on some fundamental aspects
of handling buffers in the render process.

To allow rework of the `ProcNode` connectivity, a lot of presumably obsoleted
draft code from 2011 has to be detached, to be able to keep it in-tree
for further reference (until the rework and refactoring is settled).
2024-06-25 04:54:39 +02:00
9f233f1e90 Invocation: Detail-planning of node invocation
* consider which operations to provide where
 * collect components to be built for a basic node wiring
 * define an entrance point for node invocation
2024-06-23 19:40:43 +02:00
17dcb7495f Invocation: establish a concept for the rework
As outlined in #1367, the integration effort requires some rework
of existing code, which will be driven ahead by the `NodeLinkage_test`
 * redefine Node Connectivity
 * build simple `ProcNode` directly in scope
 * create an `TurnoutSystem` instance
 * perform a ''dummy Node-Invocation''
2024-06-21 16:22:58 +02:00
f632701f48 Library: lib::Several complete and tested (see #473)
As a replacement for the `RefArray` a new generic container
has been implemented and tested, in interplay with `AllocationCluster`
 * the front-end container `lib::Several<I>` exposes only a reference
   to the ''interface type'' `I`, while hiding any storage details
 * data can only be populated through the `lib::SeveralBuilder`
 * a lot of flexibility is allowed for the actual element data types
 * element storage is maintained in a storage extent, managed through
   a custom allocator (defaulting to `std::allocator` ⟹ heap storage)
2024-06-19 19:40:03 +02:00
cf6abf6a3b Library: observe allocator limits on exponential expansion
The `SeveralBuilder` employs the same tactic as `std::vector`,
by over-allocating a reserve buffer, which grows in exponential
increments, to amortise better the costs of re-allocation.

This tactic does not play well with space limited allocators
like `AllocationCluster` however; it is thus necessary to provide
an extension point where the actuall allocator's limitation can be
queried, allowing to use what is available as reserve, but not more.

With these adaptations, a full usage cycle backed by `AllocationCluster`
can be demonstrated, including variations of dynamic allocation adjustment.
2024-06-19 17:35:46 +02:00
39e9ecd90e Library: AllocationCluster and SeveralBuilder logic tweaks
...identified as part of bug investigation

 * make clear that reserve() prepares for an absolute capacity
 * clarify that, to the contrary, ensureStorageCapaciy() means the delta

Moreover, it turns out that the assertion regarding storage limits
triggers frequently while writing the test code; so we can conclude
that the `AllocationCluster` interface lures into allocating without
previous check. Consequently, this check now throws a runtime exception.

As an aside, the size limitation should be accessible on the interface,
similar to `std::vector::max_size()`
2024-06-19 15:45:12 +02:00
9709309186 Library: setup adaptor for dynamic adjustments
By means of the extension point, which produces a dedicated policy
for use with `AllocationCluster`, it becomes possible to use the
specialised API to adjust the latest allocation in the cluster.
When this is not actually usable, the policy will fall back
on the standard implementation (which is wasteful when
applied to `AllocationCluster`, since memory for the
obsoleted, smaller blocks not de-allocated then...
2024-06-19 03:26:36 +02:00
7d066a85ee Library: now use AllocationCluster as custom allocator
* this validates usage of the extension point
 * however, there is no special treatment yet,
   and thus a re-alloc leves the previoius block as waste
2024-06-19 01:29:46 +02:00
aacea3c10a Library: lib::Several container now passes test with TrackingAllocator
- decided to allow creating empty lib::Several;
  no need to be overly rigid in this point,
  since it is move-assignable anyway...

- populate with enough elements to provoke several reallocations
  with copying over the existing elements
- precisely calculate and verify the expected allocation size
- verify the use-count due to dedicated allocator instances
  being embedded into both the builder and hidden in the deleter
- move-assign data
- all checksums go to zero at end
2024-06-18 19:09:21 +02:00
50306db164 Library: more stringent deleter logic
The setup for `ArrayBucket` is special, insofar it shell de-allocate itself,
which creates the danger of re-entrant calls, or to the contrary, the danger
to invoke this clean-up function without actually invoking the destructor.

These problems become relevant once the destructor function itself is statefull,
as is the case when embedding a non-trivial, instance bound allocator
to be used for the clean-up work. Using the new `lib::TrackingAllocator`
highlighted this potential problem, since the allocator maintains a use-count.

Thus I decided to move the »destruction mechanics« one level down into
a dedicated and well encapsulated base class; invoking ArrayBucket's destructor
thereby becomes the only way to trigger the clean-up, and even ElementFactory::destroy()
can now safely check if the destructor was already invoked, and otherwise
re-invoke itself through this embedded destructor function. Moreover,
as an additional safety measure, the actual destructor function is now
moved into the local stack frame of the object's destructor call, removing
any possibility for the de-allocation to interfere with the destructor
invokation itself
2024-06-18 18:15:58 +02:00
31c24e0017 Library: investigate discrepancy in allocator
part of the observed deviation stems form bugs in logging and checksum calculation;
but there seems to be a real problem hidden in the allocator usage of the
new component, since the use-cnt of the handle does not drop to zero
2024-06-18 17:20:23 +02:00
09c8c2a29f Library: better handle the alignment issues explicitly
While there might be the possibility to use the magic of the standard library,
it seems prudent rather to handle this insidious problem explicitly,
to make clear what is going on here.

To allow for such explicit alignment handling, I have now changed the
scheme of the storage definition; the actual buffer now starts ''behind''
the `ArrayBucket<I>` object, which thereby becomes a metadata managing header.

__To summarise the problem__: since we are maintaining a dynamically sized buffer,
and since we do not want to expose the actual element type through the
front-end object, we're necessarily bound to perform a raw-memory allocation.
This is denoted in bytes, and thus the allocator can no longer manage
the proper alignment automatically. Rather, we get a storage buffer with
just ''some accidental'' alignment, and we must care to request a sufficient
overhead to be able to shift the actual storage area forward to the next
proper alignment boundary. Obviously this also implies that we must
store this individual padding adjustment somewhere in the metadata,
in order to be able to report the correct size of the block later
on de-allocation.
2024-06-18 03:16:26 +02:00
dc6c8e0858 Library: investigate alignment issues
The solution implemented thus far turns out to be not sufficient
for ''over-aligned-data'', as the raw-allocator can not perform the
''magic work'' because we're exposing only `std::byte` data.
2024-06-17 16:58:07 +02:00
055df59dde Library: tracking diagnostic allocator now complete and tested 2024-06-17 01:55:49 +02:00
10edc31eac Library: build adaptor for automated unique-ownership
This adaptor works in concert with the generic allocator
building blocks (prospective ''Concepts'') and automatically
registers a either static or dynamic back-link to the factory
for clean-up.

Use this wrapper fore more in-depth test of the new `TrackingAllocator`
and verify proper behaviour through the `EventLog`
2024-06-16 19:31:16 +02:00
be3cf61111 Library: verify fundamental properties of TrackingAllocator
* implement some further statistic and diagnostic helpers
 * explicitly create and discard a base allocation for test
2024-06-16 15:44:43 +02:00
32bea9521b Library: get the simple testcase to work
- create two vectors, attached to the `TrackingAllocator`
- emplace Tracker-Objects
- move an object to the other vector
- destroy the containers

🠲 Event-Log looks plausible!
2024-06-16 04:23:06 +02:00
a3fb6f46ed Library: implement the MemoryPool for TrackingAllocator
- use a meta-registry of pools
- retrieve and manage the `MemoryPool` instances by shared_ptr, with a weak registry entry
- use a hastable for the allocations, keyed by the allocated memory address
2024-06-16 04:22:29 +02:00
ad90b7d687 Library: define requirements for tracking test-allocator
- ability to verify a hash-checksum
- ability to watch number of allocations and allotted bytes
- using either a common global pool or a separate dedicated pool
- log all operations into a common `EventLog` instance
- front-end adaptors for use as C++ custom allocator
2024-06-16 04:22:29 +02:00
e82dd86b39 Library: reorganise test helpers and cover logging tracker object
...these features are now used quite regularly,
and so a dedicated documentation test seems indicated.

Actually my intention is to add a tracking allocator to these test helpers
(and then to use that to verify the custom allocator usage of `lib::Several`)
2024-06-16 04:22:29 +02:00
d327094603 Library: draft a scheme to configure lib::Several with a custom allocator
Phew... this was a tough one — and not sure yet if this even remotely works...

Anyway, the `lib::SeveralBuilder` is already prepared for collaboration with a
custom allocator, since it delegates all memory handling through a base policy,
which in turn relies on std::allocator_traits.

The challenge however is to find a way...
 * to make this clear and easy to use
 * to expose an extension point for specific tweaks
 * and to make all this work without excessive header cross dependencies
2024-06-16 04:22:28 +02:00
bb164e37c8 Library: allow for dynamic adjustments in AllocationCluster
This is a low-level interface to allow changing the size of
the currently latest allocation in `AllocationCluster`; a client
aware of this capability can perform a real »in-place re-alloc«,
assuming the very specific usage constraints can be met.

`lib::Several<X>` will use this feature when attached to an
`AllocationCluster`; with this special setup, an previously
unknown number of non-copyable objects can be built without
wasting any storage, as long as the storage reserve in the
current extent of the `AllocationCluster` is sufficient.
2024-06-16 04:22:28 +02:00
3bbdf40c32 Library: verify element placement into storage
...use some pointer arithmetic for this test to verify
some important cases of object placement empirically.

Note: there is possibly a very special problematic case
when ''over aligned objects'' are not placed in accordance
to their alignment requirements. Fixing this problem would
be non-trivial, and thus I have only left a note in #1204
2024-06-16 04:22:28 +02:00
fd1ed7e78f Library: finish coverage of element handling limits and failures
...including the interesting cases where objects are relocated
and the element spread is changed. With the help of the checksum
feature built into the test-dummy objects, the properly balanced
invocation of constructors can be demonstrated


PS: for historical context...
Last week the "Big F**cking Rocket" successfully performed the
test flight 4; both booster and Starship made it back to the
water surface and performed a soft splash-down after decelerating
to speed zero. The Starship was even able to maintain control
in spite of quite some heat damage on the steering flaps.
Yes ... all techies around the world are thrilled...
2024-06-16 04:22:28 +02:00
00287360be Library: rework handling of resize and spread changes
- spread change now retains the nominal element reserve
- `capacity()` and `capReserve()` now exposed on the builder API
- factor out the handling check safety functions
- rewrite the `resize()` builder function to be more generic

__Test now covers__ example with trivial data type, which can
indeed be resized and allows to grow buffer on-the fly without
requiring any knowledge of the actual type (due to using `memmove`)
2024-06-16 04:22:28 +02:00
89dd35e70d Library: cover handling limits for virtual baseclass scenario
building on the preceding analysis, we can now demonstrate that
the container is initially able to grow, but looses this capability
after accepting one element of unknown subclass type...
2024-06-16 04:22:28 +02:00
85e3780a34 Library: reassess logic to reject some types for existing container
`lib::Several` is designed to be highly adaptable, allowing for
several quite distinct usage styles. On the downside, this requires
to perform some checks at runtime only, since the ability to handle
some element depends on specific circumstances.

This is a notable difference to `std::vector`, which is simply not capable
of handling ''non-copyable'' types, even if given an up-front memory reservation.

The last test case provided with the previous changeset did not trigger
an exception, but closer investigation revealed that this is correct,
since in this specific situation the container can accept this object type,
thereby just loosing the ability to move-relocate further objects.

A slightly re-arranged test scenario can be used to demonstrate this fine point.
2024-06-16 04:22:28 +02:00
d9f86ad891 Library: investigate case with known element type
- the test-dummy objects need a `noexcept` move ctor
- **bug** here: need an explicit check to prevent other types
  than the known element type from ''sneaking in''
2024-06-16 04:22:28 +02:00
006809712e Library: some coverage for rejected type placements
The `SeveralBuilder` is very flexible with respect to added elements,
but it will investigate the provided type information and reject any
further build operation that can not be carried out safely.
2024-06-16 04:22:28 +02:00
1169b6272e Library: test coverage for some ''special'' builder usages 2024-06-16 04:22:28 +02:00
601a555e6c Library: builder to add heterogeneous elements
...turns out that we must ensure to pass a plain "object" type
to the standard allocator framework (no const, no references).
Here, ''object in C++ terminology'' means a scalar or record type,
but no functor, no references and no void,
2024-06-16 04:22:28 +02:00
1a76fb46f3 Library: elaborate SeveralBuilder operations
Consider what (not) to support.
Notably I decided ''not to support'' moving out of an iterator,
since doing so would contradict the fundamental assumptions of
the »Lumiera Forward Iterator« Concept.

Start verifying some variations of element placement,
still focussing on the simple cases
2024-06-16 04:22:28 +02:00
773325f1bc Library: rearrange strategy code
Parts of the decision logic for element handling was packaged
as separate »strategy« class — but this turned out to be neither
a real abstraction, nor configurable in any way. Thus it is better
to simplify the structure and turn these type predicates into simple
private member functions of the SeveralBuilder itself
2024-06-16 04:22:28 +02:00
6f3bfb5ff3 Library: better alignment handling
Elements maintained within the storage should be placed such
as to comply with their alignment requirements; the element spacing
thus must be increased to be a multiple of the given type's alignment.

This solution works in most common cases, where the alignement is
not larger as the platform's bus width (typically 64bit); but for
''over-aligned types'' this scheme may still generate wrong object
start positions (a completely correct solution would require to
add a fixed offset to the beginning of the storage array and also
to capture the alignment requirements during population and to
re-check for each new type.
2024-06-16 04:22:28 +02:00
66a1f6f8ab Library: add iteration capability to the Several-container
...and the nice thing is, the recently built `IterIndex` iteration wrapper
covers this functionality right away, simply because `lib::Several`
is a generic container with subscript operator.
2024-06-16 04:22:27 +02:00
a3e8579e4a Library: basic functionality of the Several-container working
...passes the simplest unit test
 * create a Several<int>
 * populate from `std::initializer_list`
 * random-access to elements

''next step would be to implement iteration''
2024-06-16 04:22:27 +02:00
8534914c71 Library: work out a solution how to store a Deleter functor
After some fruitless attempts, I settled for using std::function directly,
in order to establish a working baseline of this (tremendously complicated)
allocation logic. Storing a std::function in the ArrayBucket is certainly
wasteful (it costs 4 »slots« of memory), but has the upside that
it handles all those tricky corner cases magically; notably
the functor can be stored completely inline in the most relevant
case where the allocator is a monostate; moreover we bind a lambda,
which can be optimised very effectively, so that in the simplest case
there will be only the single indirection through the ''invoker''.

This **completes the code path for a simple usage cycle**

🠲 ''and hooray ... the test crashes with a double-free''
2024-06-09 23:45:24 +02:00
954a399b1c Library: logic to select a suitable deleter
- ensure the ''deleter function'' is invoked
- care for proper ''deleter'' setup in case of exception while copying
- need to »lock-in« on one specific kind of ''destructor invocation scheme,''
  since we do not keep track of individual concrete element types
2024-06-09 17:19:40 +02:00
54bd568714 Library: integrate storage-management logic
Parts of this logic were first coded down in the `realloc` template method,
where it did not really belong; thus reintegrate similar logic one level above,
in the SeveralBuilder::adjustStorage(). Moreover, for performance reasons,
always start with an initial chunk, similar to what `std::vector` does...
2024-06-09 03:00:12 +02:00
e99f4d531b Library: simplified and generic realloc
since this is meant as a policy implementation, reduce it to the bare operation;
the actual container storage handling logic shall be implemented in the container
and based on those primitive and configurable base operations
2024-06-09 02:06:41 +02:00
3a263c8c63 Library: rearrange logic for trivial move detection
...still fighting to get the design of the `AllocationPolicy`
settled to work well with `AllocationCluster` while also allowing
to handle data types which are (not) trivially copyable.

This changeset attempts to turn the logic round: now we capture
an ''move exclusion flag'' and otherwise allow the Policy to
decide on its own, based on the ''element type''
2024-06-09 00:58:42 +02:00
446f133c09 Library: logic to accept further elements
- verifies if new element can just fit in
- otherwise ensure the storage adjustments are basically possible
- throw exception in case the new element can not be accommodated
- else request possible storage adjustments
- and finally let the allocator place the new element
2024-06-08 17:35:14 +02:00
0a788570a9 Library: integrate strategy for acceptable element types
Draft skeleton of the logic for element creation.
This turns out to be a rather challenging piece of code,
since we have to rely on logical reasoning about properties
of the element types in order to decide if and how these
elements can be emplaced, including the possibility to
re-allocate and move existing data to a new location.

- if we know the exact element type, we can handle any
  copyable or movable object
- however, if the container is filled with a mixture of types,
  we can not re-allocate or grow dynamically, unless all data
  is trivially copyable (and can thus be handled through memmove)
- moreover we must ensure the ability to invoke the proper destructor
2024-06-08 03:52:05 +02:00
bbec35ce65 Library: switch rest of implementation
...and remove now obsolete metadata fields in the collection and builder classes
2024-06-08 02:03:07 +02:00
deaabcda6e Library: adapt allocation and realloc to new layout
significantly simplifies both API and calculations,
since all necessary data is now within the ArrayBucket
2024-06-08 01:50:50 +02:00
130a021020 Library: rearrange storage layout
In-depth analysis of storage management revealed a misconception
with respect to possible storage optimisations, requiring more
metadata fields to handle all corner cases correctly.

It seems prudent to avoid any but the most obvious optimisations
and wait for real-world usage for a better understanding of the
prevalent access patterns. However, in preparation for any future
optimisations, all access coordination and storage metadata is
now relocated into the `ArrayBucket`, and thus resides within the
managed allocation, allowing for localised layout optimisations.

To place this into context: the expected prevalent use case is
for the »Render Nodes Network«, which relies on `AllocationCluster`
for storage management; most nodes will have only a single predecessor
or successor, leading to a large number of lib::Several intsances
populated with a single data element. In such a scenario, it is
indeed rather wasteful to allocate four »slot« of metadata for
each container instance; even more so since most of this
metadata is not even required in such a scenario.
2024-06-08 00:23:42 +02:00
154a7018be Library: attempt to build a re-alloc on top of the new adapter
...which basically ''seems doable'' now, yet turns up several unsolved problems
- need a way to handle excess storage for the raw allocation
- generally should relocate all metadata into the ArrayBucket
- mismatch at various APIs; must re-think where to pass size explicitly
- unclear yet how and where to pass the actual element type to create
2024-06-07 19:04:06 +02:00
24b3e5ceba Library: work out adaptor solution for custom allocator
...turns out to be rather challenging, due to the far reaching requirements
 * the default case (heap allocation) ''must work out-of-the box''
 * optionally a C++ standard conformant `Allocator` can be adapted
 * which works correct even in case this allocator is ''not a monostate''
 * **essential requirement** is to pass an `AllocationCluster` reference directly
 * need a ''generic extension point'' to adapt to similar elaborate custom schemes

__Note__: especially we want to create a direct collaboration between the allocation policy and the underlying allocator to allow support for a dedicate ''realloc operation''
2024-06-07 03:36:25 +02:00