lumiera_/src/steam/engine/weaving-pattern.hpp

385 lines
14 KiB
C++
Raw Normal View History

/*
WEAVING-PATTERN.hpp - Base patterns to organise data connections and render processing invocation
2010-12-17 23:28:49 +01:00
Copyright: clarify and simplify the file headers * Lumiera source code always was copyrighted by individual contributors * there is no entity "Lumiera.org" which holds any copyrights * Lumiera source code is provided under the GPL Version 2+ == Explanations == Lumiera as a whole is distributed under Copyleft, GNU General Public License Version 2 or above. For this to become legally effective, the ''File COPYING in the root directory is sufficient.'' The licensing header in each file is not strictly necessary, yet considered good practice; attaching a licence notice increases the likeliness that this information is retained in case someone extracts individual code files. However, it is not by the presence of some text, that legally binding licensing terms become effective; rather the fact matters that a given piece of code was provably copyrighted and published under a license. Even reformatting the code, renaming some variables or deleting parts of the code will not alter this legal situation, but rather creates a derivative work, which is likewise covered by the GPL! The most relevant information in the file header is the notice regarding the time of the first individual copyright claim. By virtue of this initial copyright, the first author is entitled to choose the terms of licensing. All further modifications are permitted and covered by the License. The specific wording or format of the copyright header is not legally relevant, as long as the intention to publish under the GPL remains clear. The extended wording was based on a recommendation by the FSF. It can be shortened, because the full terms of the license are provided alongside the distribution, in the file COPYING.
2024-11-17 23:42:55 +01:00
Copyright (C)
2008, Hermann Vosseler <Ichthyostega@web.de>
2024, Hermann Vosseler <Ichthyostega@web.de>
2010-12-17 23:28:49 +01:00
Copyright: clarify and simplify the file headers * Lumiera source code always was copyrighted by individual contributors * there is no entity "Lumiera.org" which holds any copyrights * Lumiera source code is provided under the GPL Version 2+ == Explanations == Lumiera as a whole is distributed under Copyleft, GNU General Public License Version 2 or above. For this to become legally effective, the ''File COPYING in the root directory is sufficient.'' The licensing header in each file is not strictly necessary, yet considered good practice; attaching a licence notice increases the likeliness that this information is retained in case someone extracts individual code files. However, it is not by the presence of some text, that legally binding licensing terms become effective; rather the fact matters that a given piece of code was provably copyrighted and published under a license. Even reformatting the code, renaming some variables or deleting parts of the code will not alter this legal situation, but rather creates a derivative work, which is likewise covered by the GPL! The most relevant information in the file header is the notice regarding the time of the first individual copyright claim. By virtue of this initial copyright, the first author is entitled to choose the terms of licensing. All further modifications are permitted and covered by the License. The specific wording or format of the copyright header is not legally relevant, as long as the intention to publish under the GPL remains clear. The extended wording was based on a recommendation by the FSF. It can be shortened, because the full terms of the license are provided alongside the distribution, in the file COPYING.
2024-11-17 23:42:55 +01:00
  **Lumiera** is free software; you can redistribute it and/or modify it
  under the terms of the GNU General Public License as published by the
  Free Software Foundation; either version 2 of the License, or (at your
  option) any later version. See the file COPYING for further details.
2010-12-17 23:28:49 +01:00
*/
/** @file weaving-pattern.hpp
** Construction set to assemble and operate a data processing scheme within a Render Node.
** This header defines part of the "glue" which holds together the render node network
** and enables to pull result frames from the nodes. Doing so requires some invocation
** local state to be maintained, especially a table of buffers used to carry out the
** calculations. Further, getting the input buffers filled requires to issue recursive
** \c pull() calls, which on the whole creates a stack-like assembly of local invocation
** state.
** The actual steps to be carried out for a \c pull() call are dependent on the configuration
** of the node to pull. Each node has been preconfigured by the builder with a Connectivity
** descriptor and a concrete type of a StateAdapter. The actual sequence of steps is defined
** in the header nodeoperation.hpp out of a set of basic operation steps. These steps all use
** the passed in Invocation object (a sub-interface of StateAdapter) to access the various
** aspects of the invocation state.
**
** # composition of the Invocation State
**
** For each individual ProcNode#pull() call, the WiringAdapter#callDown() builds an StateAdapter
** instance directly on the stack, managing the actual buffer pointers and state references. Using this
** StateAdapter, the predecessor nodes are pulled. The way these operations are carried out is encoded
** in the actual StateAdapter type known to the NodeWiring (WiringAdapter) instance. All of these actual
** StateAdapter types are built as implementing the engine::StateClosure interface.
**
** @todo relies still on an [obsoleted implementation draft](\ref bufftable-obsolete.hpp)
** @see engine::ProcNode
** @see engine::StateProxy
** @see engine::FeedManifold
** @see nodewiring.hpp interface for building/wiring the nodes
**
** @warning as of 12/2024 first complete integration round of the Render engine ////////////////////////////TICKET #1367
**
*/
#ifndef STEAM_ENGINE_WEAVING_PATTERN_H
#define STEAM_ENGINE_WEAVING_PATTERN_H
#include "steam/common.hpp"
#include "steam/engine/proc-node.hpp"
#include "steam/engine/turnout-system.hpp"
#include "steam/engine/feed-manifold.hpp"
/////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1367 : Rebuild the Node Invocation
//#include "vault/gear/job.h"
//#include "steam/engine/exit-node.hpp"
//#include "lib/time/timevalue.hpp"
//#include "lib/linked-elements.hpp"
#include "lib/several.hpp"
//#include "lib/util-foreach.hpp"
//#include "lib/iter-adapter.hpp"
#include "lib/meta/function.hpp"
//#include "lib/itertools.hpp"
//#include "lib/util.hpp" ////////OOO wegen manifoldSiz<FUN>()
#include <utility>
#include <array>
//#include <stack>
namespace steam {
namespace engine {
using std::forward;
using lib::Several;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1367 : Rebuild the Node Invocation
#if false /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #1367 : Rebuild the Node Invocation
/**
* Adapter to shield the ProcNode from the actual buffer management,
* allowing the processing function within ProcNode to use logical
* buffer IDs. StateAdapter is created on the stack for each pull()
* call, using setup/wiring data preconfigured by the builder.
* Its job is to provide the actual implementation of the Cache
* push / fetch and recursive downcall to render the source frames.
*/
class StateAdapter
: public StateClosure
{
protected:
StateClosure& parent_;
StateClosure& current_;
StateAdapter (StateClosure& callingProcess)
: parent_ (callingProcess),
current_(callingProcess.getCurrentImplementation())
{ }
virtual StateClosure& getCurrentImplementation () { return current_; }
public: /* === proxying the StateClosure interface === */
virtual void releaseBuffer (BuffHandle& bh) { current_.releaseBuffer (bh); }
virtual void is_calculated (BuffHandle const& bh) { current_.is_calculated (bh); }
virtual BuffHandle fetch (FrameID const& fID) { return current_.fetch (fID); }
virtual BuffTableStorage& getBuffTableStorage() { return current_.getBuffTableStorage(); }
// note: allocateBuffer() is chosen specifically based on the actual node wiring
};
/**
* Invocation context state.
* A ref to this type is carried through the chain of NEXT::step() functions
* which form the actual invocation sequence. The various operations in this sequence
* access the context via the references in this struct, while also using the inherited
* public State interface. The object instance actually used as Invocation is created
* on the stack and parametrised according to the necessities of the invocation sequence
* actually configured. Initially, this real instance is configured without FeedManifold,
* because the invocation may be short-circuited due to Cache hit. Otherwise, when
* the invocation sequence actually prepares to call the process function of this
* ProcNode, a buffer table chunk is allocated by the StateProxy and wired in.
*/
struct Invocation
: StateAdapter
{
Connectivity const& wiring;
const uint outNr;
FeedManifold* feedManifold;
protected:
/** creates a new invocation context state, without FeedManifold */
Invocation (StateClosure& callingProcess, Connectivity const& w, uint o)
: StateAdapter(callingProcess),
wiring(w), outNr(o),
feedManifold(0)
{ }
public:
uint nrO() const { return wiring.nrO; }
uint nrI() const { return wiring.nrI; }
uint buffTabSize() const { return nrO()+nrI(); }
/** setup the link to an externally allocated buffer table */
void setBuffTab (FeedManifold* b) { this->feedManifold = b; }
bool
buffTab_isConsistent ()
{
return (feedManifold)
&& (0 < buffTabSize())
&& (nrO()+nrI() <= buffTabSize())
&& (feedManifold->inBuff == &feedManifold->outBuff[nrO()] )
&& (feedManifold->inHandle == &feedManifold->outHandle[nrO()])
;
}
public:
/** specialised version filling in the additional information, i.e
* the concrete node id and the channel number in question */
virtual FrameID const&
genFrameID ()
{
return current_.genFrameID(wiring.nodeID, outNr);
}
virtual FrameID const&
genFrameID (NodeID const& nID, uint chanNo)
{
return current_.genFrameID (nID,chanNo);
}
};
////////////TICKET #249 this strategy should better be hidden within the BuffHandle ctor (and type-erased after creation)
struct AllocBufferFromParent ///< using the parent StateAdapter for buffer allocations
: Invocation
{
AllocBufferFromParent (StateClosure& sta, Connectivity const& w, const uint outCh)
: Invocation(sta, w, outCh) {}
virtual BuffHandle
allocateBuffer (const lumiera::StreamType* ty) { return parent_.allocateBuffer(ty); } ////////////TODO: actually implement the "allocate from parent" logic!
};
struct AllocBufferFromCache ///< using the global current StateClosure, which will delegate to Cache
: Invocation
{
AllocBufferFromCache (StateClosure& sta, Connectivity const& w, const uint outCh)
: Invocation(sta, w, outCh) {}
virtual BuffHandle
allocateBuffer (const lumiera::StreamType* ty) { return current_.allocateBuffer(ty); }
};
/**
* The real invocation context state implementation. It is created
* by the NodeWiring (Connectivity) of the processing node which
* is pulled by this invocation, hereby using the internal configuration
* information to guide the selection of the real call sequence
*
* \par assembling the call sequence implementation
* Each ProcNode#pull() call creates such a StateAdapter subclass on the stack,
* with a concrete type according to the Connectivity of the node to pull.
* This concrete type encodes a calculation Strategy, which is assembled
* as a chain of policy templates on top of OperationBase. For each of the
* possible configurations we define such a chain (see bottom of nodeoperation.hpp).
* The WiringFactory defined in nodewiring.cpp actually drives the instantiation
* of all those possible combinations.
*/
template<class Strategy, class BufferProvider>
class ActualInvocationProcess
: public BufferProvider
, private Strategy
{
public:
ActualInvocationProcess (StateClosure& callingProcess, Connectivity const& w, const uint outCh)
: BufferProvider(callingProcess, w, outCh)
{ }
/** contains the details of Cache query and recursive calls
* to the predecessor node(s), eventually followed by the
* ProcNode::process() callback
*/
BuffHandle retrieve ()
{
return Strategy::step (*this);
}
};
#endif /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #1367 : Rebuild the Node Invocation
/////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1367 : Rebuild the Node Invocation
/**
* Standard implementation for a _Weaving Pattern_ to connect
* the input and output data feeds (buffers) into a processing function.
* @tparam INVO a configuration / policy base class to _adapt for invocation_
* @note assumptions made regarding the overall structure
* - `INVO::Feed` defines an _invocation adapter_ for the processing function
* - `INVO::buildFeed()` is a functor to (repeatedly) build `Feed` instances
* - the _invocation adapter_ in turn embeds a `FeedManifold<N>` to hold
* + an array of input buffer pointers
* + an array of output buffer pointers
* + `INVO::MAX_SIZ` limits both arrays
*/
template<class INVO>
struct SimpleWeavingPattern
: INVO
{
using Feed = typename INVO::Feed;
static_assert (_verify_usable_as_InvocationAdapter<Feed>());
Several<PortRef> leadPort;
Several<BuffDescr> outTypes;
uint resultSlot{0};
/** forwarding-ctor to provide the detailed input/output connections */
template<typename...ARGS>
SimpleWeavingPattern (Several<PortRef>&& pr
,Several<BuffDescr>&& dr
,uint resultIdx
,ARGS&& ...args)
: INVO{forward<ARGS>(args)...}
, leadPort{move(pr)}
, outTypes{move(dr)}
, resultSlot{resultIdx}
{ }
Feed
mount()
{
return INVO::buildFeed();
}
void
pull (Feed& feed, TurnoutSystem& turnoutSys)
{
for (uint i=0; i<leadPort.size(); ++i)
{
BuffHandle inputData = leadPort[i].get().weave (turnoutSys);
feed.inBuff.createAt(i, move(inputData));
}
}
void
shed (Feed& feed, OptionalBuff outBuff)
{
for (uint i=0; i<outTypes.size(); ++i)
{
BuffHandle resultData =
i == resultSlot and outBuff? *outBuff
: outTypes[i].lockBuffer();
feed.outBuff.createAt(i, move(resultData));
}
feed.connect (leadPort.size(),outTypes.size());
}
void
weft (Feed& feed)
{
feed.invoke(); // process data
}
BuffHandle
fix (Feed& feed)
{
for (uint i=0; i<leadPort.size(); ++i)
{
feed.inBuff[i].release();
}
for (uint i=0; i<outTypes.size(); ++i)
{
feed.outBuff[i].emit(); // state transition: data ready
if (i != resultSlot)
feed.outBuff[i].release();
}
ENSURE (resultSlot < INVO::MAX_SIZ, "invalid result buffer configured.");
return feed.outBuff[resultSlot];
}
};
/**
* Processing structure to activate a Render Node and produce result data.
* @tparam PAT a _Weaving Pattern,_ which defines in detail how data is retrieved,
* combined and processed to yield the results; actually this implementation
* is assembled from several building blocks, in accordance to the specific
* situation as established by the _Builder_ for a given render node.
*/
template<class PAT>
class Turnout
: public Port
, public PAT
// , util::MoveOnly
{
using Feed = typename PAT::Feed;
public:
template<typename...INIT>
Turnout (ProcID& id, INIT&& ...init)
: Port{id}
, PAT{forward<INIT> (init)...}
{ }
/**
* Entrance point to the next recursive step of media processing.
* @param turnoutSys anchor context with parameters and services
* @return a BuffHandle exposing the generated result data
*/
BuffHandle
weave (TurnoutSystem& turnoutSys, OptionalBuff outBuff =std::nullopt) override
{
Feed feed = PAT::mount();
PAT::pull(feed, turnoutSys);
PAT::shed(feed, outBuff);
PAT::weft(feed);
return PAT::fix (feed);
}
};
}}// namespace steam::engine
#endif /*STEAM_ENGINE_WEAVING_PATTERN_H*/