2023-11-23 23:42:55 +01:00
|
|
|
/*
|
|
|
|
|
LAZY-INIT.hpp - a self-initialising functor
|
|
|
|
|
|
|
|
|
|
Copyright (C) Lumiera.org
|
|
|
|
|
2023, Hermann Vosseler <Ichthyostega@web.de>
|
|
|
|
|
|
|
|
|
|
This program 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.
|
|
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
|
along with this program; if not, write to the Free Software
|
|
|
|
|
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/** @file lazy-init.hpp
|
|
|
|
|
** Building block to allow delayed initialisation of infrastructure tied to a functor.
|
|
|
|
|
** This solution is packaged as a mix-in template and engages a hidden mechanism with
|
|
|
|
|
** considerable trickery. It attempts to solve a problem arising notoriously when building
|
|
|
|
|
** elaborate processing by composing functions and user-provided configuration lambdas;
|
|
|
|
|
** the very point of this construction style is to tap into internal context to involve
|
|
|
|
|
** deep details of the implementation without the need to represent these as structures
|
|
|
|
|
** on API level. Unfortunately this has the consequence that capture-by-reference is
|
|
|
|
|
** all over the place, breeding instability. The only solution to defeat this instability
|
|
|
|
|
** is to lock an enclosing implementation scope into a fixed memory location, which boils
|
|
|
|
|
** down to using non-copyable classes. This solution may be in conflict to the intended
|
|
|
|
|
** use, especially when building DSLs, configuration frameworks or symbolic processing,
|
|
|
|
|
** where entities are _value like_ from a semantic point of view. The solution pursued
|
|
|
|
|
** here is to define some linkage for operational state, which allows to lock a scope
|
|
|
|
|
** to a fixed memory location. Assuming that a typical usage scenario will first
|
|
|
|
|
** require setup, then proceed to processing, this solution attempts to tie
|
|
|
|
|
** the usage restrictions to the lifecycle — hopefully hiding the concern
|
|
|
|
|
** from users sight altogether.
|
|
|
|
|
**
|
|
|
|
|
** # Initialisation mechanism
|
|
|
|
|
** This mix-in assumes that there is a function somewhere, which activates the actual
|
|
|
|
|
** processing, and this processing requires initialisation to be performed reliably
|
|
|
|
|
** before first use. Thus, a _»trojan functor«_ is placed into this work-function,
|
|
|
|
|
** with the goal to activate a „trap“ on first use. This allows to invoke the actual
|
|
|
|
|
** initialisation, which is also configured as a functor, and which is the only part
|
|
|
|
|
** the client must provide actively, to activate the mechanism.
|
|
|
|
|
**
|
|
|
|
|
** There is one _gory detail_ however: the initialisation hook needs the actual instance
|
|
|
|
|
** pointer valid *at the time of actual initialisation*. And since initialisation shall
|
|
|
|
|
** be performed automatically, the trap mechanism needs a way to derive this location,
|
|
|
|
|
** relying on minimal knowledge only. This challenge can only be overcome by assuming
|
|
|
|
|
** that the »trojan functor« itself is stored somehow embedded into the target object
|
|
|
|
|
** to be initialised. If there is a fixed distance relation in memory, then the target
|
|
|
|
|
** can be derived from the self-position of the functor; if this assumption is broken
|
2023-11-25 03:36:19 +01:00
|
|
|
** however, memory corruption and SEGFAULT may be caused.
|
2023-11-23 23:42:55 +01:00
|
|
|
**
|
|
|
|
|
** @todo 11/2023 at the moment I am just desperately trying to get a bye-product of my
|
|
|
|
|
** main effort into usable shape and salvage an design idea that sounded clever
|
|
|
|
|
** on first thought. I am fully aware that »lazy initialisation« is something
|
|
|
|
|
** much more generic, but I am also aware of the potential of the solution
|
|
|
|
|
** coded here. Thus I'll claim that generic component name, assuming that
|
|
|
|
|
** time will tell if we need a more generic framework to serve this
|
|
|
|
|
** purpose eventually....
|
|
|
|
|
** @see LazyInit_test
|
|
|
|
|
** @see lib::RandomDraw usage example
|
|
|
|
|
** @see vault::gear::TestChainLoad::Rule where this setup matters
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#ifndef LIB_LAZY_INIT_H
|
|
|
|
|
#define LIB_LAZY_INIT_H
|
|
|
|
|
|
|
|
|
|
|
2023-11-25 03:36:19 +01:00
|
|
|
#include "lib/error.h"
|
2023-11-23 23:42:55 +01:00
|
|
|
#include "lib/meta/function.hpp"
|
2023-11-24 21:16:54 +01:00
|
|
|
#include "lib/opaque-holder.hpp"
|
|
|
|
|
#include "lib/util.hpp"
|
2023-11-23 23:42:55 +01:00
|
|
|
|
2023-11-25 03:36:19 +01:00
|
|
|
#include <functional>
|
2023-11-24 03:17:27 +01:00
|
|
|
#include <utility>
|
|
|
|
|
#include <memory>
|
2023-11-23 23:42:55 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
namespace lib {
|
2023-11-24 21:16:54 +01:00
|
|
|
namespace err = lumiera::error;
|
2023-11-23 23:42:55 +01:00
|
|
|
|
2023-11-24 03:17:27 +01:00
|
|
|
using lib::meta::_Fun;
|
2023-11-25 03:36:19 +01:00
|
|
|
using lib::meta::_FunArg;
|
2023-11-24 03:17:27 +01:00
|
|
|
using lib::meta::has_Sig;
|
2023-11-24 21:16:54 +01:00
|
|
|
using util::unConst;
|
2023-11-25 03:36:19 +01:00
|
|
|
using std::function;
|
2023-11-24 03:17:27 +01:00
|
|
|
using std::forward;
|
|
|
|
|
using std::move;
|
|
|
|
|
|
|
|
|
|
using RawAddr = void const*;
|
|
|
|
|
|
2023-11-25 03:36:19 +01:00
|
|
|
|
2023-11-24 21:16:54 +01:00
|
|
|
namespace {// the anonymous namespace of horrors...
|
|
|
|
|
|
|
|
|
|
inline ptrdiff_t
|
|
|
|
|
captureRawAddrOffset (RawAddr anchor, RawAddr subject)
|
|
|
|
|
{
|
|
|
|
|
// Dear Mr.Compiler, please get out of my way.
|
2023-11-25 03:36:19 +01:00
|
|
|
// I just genuinely want to shoot myself into my foot...
|
2023-11-24 21:16:54 +01:00
|
|
|
char* anchorAddr = reinterpret_cast<char*> (unConst(anchor));
|
|
|
|
|
char* subjectAddr = reinterpret_cast<char*> (unConst(subject));
|
|
|
|
|
return subjectAddr - anchorAddr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template<class TAR>
|
|
|
|
|
inline static TAR*
|
|
|
|
|
relocate (RawAddr anchor, ptrdiff_t offset)
|
|
|
|
|
{
|
|
|
|
|
char* anchorAddr = reinterpret_cast<char*> (unConst(anchor));
|
|
|
|
|
char* adjusted = anchorAddr + offset;
|
|
|
|
|
void* rawTarget = reinterpret_cast<void*> (adjusted);
|
2023-11-24 23:26:41 +01:00
|
|
|
return static_cast<TAR*> (rawTarget);
|
2023-11-24 21:16:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @internal *implementation defined* : offset of a payload placed directly
|
|
|
|
|
* into a std::function when the _small object optimisation_ applies.
|
|
|
|
|
* @warning relies on implementation behaviour not guaranteed by the standard,
|
|
|
|
|
* while supported by all known implementations today. This is
|
|
|
|
|
* exploited as a trick to allow for automatic late initialisation
|
|
|
|
|
* in a situation, were a functor needs to capture references.
|
|
|
|
|
*/
|
2023-11-24 23:26:41 +01:00
|
|
|
const ptrdiff_t FUNCTOR_PAYLOAD_OFFSET =
|
2023-11-24 21:16:54 +01:00
|
|
|
[]{
|
|
|
|
|
size_t slot{42};
|
|
|
|
|
std::function<RawAddr(void)> probe = [slot]{ return RawAddr(&slot); };
|
|
|
|
|
RawAddr functor = &probe;
|
|
|
|
|
RawAddr payload = probe();
|
|
|
|
|
if (not util::isCloseBy(functor, payload))
|
|
|
|
|
throw err::Fatal{"Unable to use lib::LazyInit because std::function does not "
|
|
|
|
|
"apply small-object optimisation with inline storage."};
|
|
|
|
|
return captureRawAddrOffset (functor,payload);
|
|
|
|
|
}();
|
2023-11-25 03:36:19 +01:00
|
|
|
//
|
|
|
|
|
}//(End)low-level manipulations
|
|
|
|
|
|
|
|
|
|
|
2023-11-24 21:16:54 +01:00
|
|
|
|
2023-11-24 03:17:27 +01:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* »Trojan Function« builder.
|
|
|
|
|
* Generates a Lambda to invoke a delegate for the actual computation.
|
|
|
|
|
* On invocation, the current storage location of the λ is determined.
|
|
|
|
|
*/
|
|
|
|
|
template<class SIG>
|
|
|
|
|
class TrojanFun
|
|
|
|
|
{
|
|
|
|
|
template<class DEL, typename RET, typename... ARGS>
|
|
|
|
|
static auto
|
2023-11-24 15:43:26 +01:00
|
|
|
buildTrapActivator (DEL* delegate, _Fun<RET(ARGS...)>)
|
2023-11-24 03:17:27 +01:00
|
|
|
{
|
2023-11-24 15:43:26 +01:00
|
|
|
return [delegate]
|
2023-11-24 03:17:27 +01:00
|
|
|
(ARGS ...args) -> RET
|
|
|
|
|
{
|
2023-11-24 15:43:26 +01:00
|
|
|
auto currLocation = &delegate;
|
|
|
|
|
auto& functor = (*delegate) (currLocation);
|
2023-11-24 03:17:27 +01:00
|
|
|
//
|
|
|
|
|
return functor (forward<ARGS> (args)...);
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
public:
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Invocation: build a Lambda to activate the »Trap«
|
|
|
|
|
* and then to forward the invocation to the actual
|
|
|
|
|
* function, which should have been initialised
|
|
|
|
|
* by the delegate invoked.
|
2023-11-25 03:36:19 +01:00
|
|
|
* @param delegate a functor object to forward invocation;
|
2023-11-24 03:17:27 +01:00
|
|
|
* the delegate must return a reference to the
|
|
|
|
|
* actual function implementation to invoke.
|
|
|
|
|
* Must be heap-allocated.
|
|
|
|
|
* @return a lightweight lambda usable as trigger.
|
|
|
|
|
*/
|
|
|
|
|
template<class DEL>
|
|
|
|
|
static auto
|
|
|
|
|
generateTrap (DEL* delegate)
|
|
|
|
|
{
|
|
|
|
|
static_assert (_Fun<DEL>(), "Delegate must be function-like");
|
|
|
|
|
using Ret = typename _Fun<DEL>::Ret;
|
|
|
|
|
static_assert (_Fun<Ret>(), "Result from invoking delegate must also be function-like");
|
|
|
|
|
static_assert (has_Sig<Ret, SIG>(), "Result from delegate must expose target signature");
|
|
|
|
|
|
|
|
|
|
REQUIRE (delegate);
|
|
|
|
|
|
2023-11-24 15:43:26 +01:00
|
|
|
return buildTrapActivator (delegate, _Fun<SIG>());
|
2023-11-24 03:17:27 +01:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
2023-11-23 23:42:55 +01:00
|
|
|
|
2023-11-25 03:36:19 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
struct EmptyBase { };
|
|
|
|
|
|
|
|
|
|
/**************************************************************//**
|
|
|
|
|
* Mix-in for lazy/delayed initialisation of an embedded functor.
|
|
|
|
|
* This allows to keep the overall object (initially) copyable,
|
|
|
|
|
* while later preventing copy once the functor was »engaged«.
|
|
|
|
|
* Initially, only a »trap« is installed into the functor,
|
|
|
|
|
* invoking an initialisation closure on first use.
|
2023-11-23 23:42:55 +01:00
|
|
|
*/
|
2023-11-25 03:36:19 +01:00
|
|
|
template<class PAR =EmptyBase>
|
2023-11-23 23:42:55 +01:00
|
|
|
class LazyInit
|
2023-11-24 21:16:54 +01:00
|
|
|
: public PAR
|
2023-11-23 23:42:55 +01:00
|
|
|
{
|
2023-11-24 21:16:54 +01:00
|
|
|
template<class SIG>
|
|
|
|
|
using DelegateType = std::function<std::function<SIG>&(RawAddr)>;
|
|
|
|
|
|
|
|
|
|
using PlaceholderType = DelegateType<void(void)>;
|
2023-11-25 01:09:37 +01:00
|
|
|
using HeapStorage = InPlaceBuffer<PlaceholderType>;
|
2023-11-24 21:16:54 +01:00
|
|
|
using PendingInit = std::shared_ptr<HeapStorage>;
|
|
|
|
|
|
2023-11-25 03:36:19 +01:00
|
|
|
/** manage heap storage for a pending initialisation closure */
|
2023-11-24 21:16:54 +01:00
|
|
|
PendingInit pendingInit_;
|
|
|
|
|
|
2023-11-25 03:36:19 +01:00
|
|
|
|
2023-11-24 21:16:54 +01:00
|
|
|
PendingInit const&
|
|
|
|
|
__trapLocked (PendingInit const& init)
|
|
|
|
|
{
|
|
|
|
|
if (not init)
|
|
|
|
|
throw err::State{"Component was already configured with a processing function, "
|
|
|
|
|
"which binds into a fixed object location. It can not be moved anymore."
|
|
|
|
|
, err::LUMIERA_ERROR_LIFECYCLE};
|
|
|
|
|
return init;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PendingInit&&
|
|
|
|
|
__trapLocked (PendingInit && init)
|
|
|
|
|
{
|
|
|
|
|
if (not init)
|
|
|
|
|
throw err::State{"Component was already configured with a processing function, "
|
|
|
|
|
"which binds into a fixed object location. It can not be moved anymore."
|
|
|
|
|
, err::LUMIERA_ERROR_LIFECYCLE};
|
|
|
|
|
return move (init);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2023-11-25 03:36:19 +01:00
|
|
|
|
|
|
|
|
protected:
|
|
|
|
|
struct MarkDisabled{};
|
|
|
|
|
|
|
|
|
|
/** @internal allows derived classes to leave the initialiser deliberately disabled */
|
|
|
|
|
template<typename...ARGS>
|
|
|
|
|
LazyInit (MarkDisabled, ARGS&& ...parentCtorArgs)
|
|
|
|
|
: PAR(forward<ARGS> (parentCtorArgs)...)
|
|
|
|
|
, pendingInit_{}
|
|
|
|
|
{ }
|
|
|
|
|
|
|
|
|
|
|
2023-11-24 21:16:54 +01:00
|
|
|
public:
|
2023-11-25 03:36:19 +01:00
|
|
|
/** prepare an initialiser to be activated on first use */
|
2023-11-24 21:16:54 +01:00
|
|
|
template<class SIG, class INI, typename...ARGS>
|
|
|
|
|
LazyInit (std::function<SIG>& targetFunctor, INI&& initialiser, ARGS&& ...parentCtorArgs)
|
|
|
|
|
: PAR(forward<ARGS> (parentCtorArgs)...)
|
2023-11-25 19:22:10 +01:00
|
|
|
, pendingInit_{}
|
|
|
|
|
{
|
|
|
|
|
installInitialiser (targetFunctor, forward<INI> (initialiser));
|
|
|
|
|
}
|
2023-11-24 21:16:54 +01:00
|
|
|
|
2023-11-25 03:36:19 +01:00
|
|
|
|
2023-11-24 21:16:54 +01:00
|
|
|
LazyInit (LazyInit const& ref)
|
|
|
|
|
: PAR{ref}
|
|
|
|
|
, pendingInit_{__trapLocked (ref.pendingInit_)}
|
|
|
|
|
{ }
|
|
|
|
|
|
|
|
|
|
LazyInit (LazyInit && rref)
|
|
|
|
|
: PAR{move (rref)}
|
|
|
|
|
, pendingInit_{__trapLocked (move(rref.pendingInit_))}
|
|
|
|
|
{ }
|
|
|
|
|
|
|
|
|
|
LazyInit&
|
|
|
|
|
operator= (LazyInit const& ref)
|
|
|
|
|
{
|
|
|
|
|
if (not util::isSameObject (ref, *this))
|
|
|
|
|
{
|
|
|
|
|
PAR::operator= (ref);
|
|
|
|
|
pendingInit_ = __trapLocked (ref.pendingInit_);
|
|
|
|
|
}
|
|
|
|
|
return *this;
|
|
|
|
|
}
|
2023-11-23 23:42:55 +01:00
|
|
|
|
2023-11-24 21:16:54 +01:00
|
|
|
LazyInit&
|
|
|
|
|
operator= (LazyInit && rref)
|
|
|
|
|
{
|
|
|
|
|
if (not util::isSameObject (rref, *this))
|
|
|
|
|
{
|
|
|
|
|
PAR::operator= (move (rref));
|
|
|
|
|
pendingInit_ = __trapLocked (move (rref.pendingInit_));
|
|
|
|
|
}
|
|
|
|
|
return *this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2023-11-25 03:36:19 +01:00
|
|
|
bool
|
|
|
|
|
isInit() const
|
|
|
|
|
{
|
|
|
|
|
return not pendingInit_;
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-24 23:26:41 +01:00
|
|
|
template<class SIG>
|
|
|
|
|
void
|
|
|
|
|
installEmptyInitialiser()
|
|
|
|
|
{
|
|
|
|
|
pendingInit_.reset (new HeapStorage{emptyInitialiser<SIG>()});
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-25 03:36:19 +01:00
|
|
|
template<class SIG, class INI>
|
|
|
|
|
void
|
|
|
|
|
installInitialiser (std::function<SIG>& targetFunctor, INI&& initialiser)
|
|
|
|
|
{
|
|
|
|
|
pendingInit_ = prepareInitialiser (targetFunctor, forward<INI> (initialiser));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private: /* ========== setup of the initialisation mechanism ========== */
|
2023-11-24 21:16:54 +01:00
|
|
|
template<class SIG>
|
|
|
|
|
DelegateType<SIG>
|
|
|
|
|
emptyInitialiser()
|
|
|
|
|
{
|
|
|
|
|
using TargetFun = std::function<SIG>;
|
|
|
|
|
return DelegateType<SIG>([disabledFunctor = TargetFun()]
|
|
|
|
|
(RawAddr) -> TargetFun&
|
|
|
|
|
{
|
|
|
|
|
return disabledFunctor;
|
|
|
|
|
});
|
|
|
|
|
}
|
2023-11-24 23:26:41 +01:00
|
|
|
|
|
|
|
|
template<class SIG, class INI>
|
|
|
|
|
PendingInit
|
|
|
|
|
prepareInitialiser (std::function<SIG>& targetFunctor, INI&& initialiser)
|
2023-11-24 21:16:54 +01:00
|
|
|
{
|
Chain-Load: provide a scheme for repeated init
For context: I've engaged into writing a `LazyInit` helper component,
to resolve the inner contradiction between DSL use of `RandomDraw`
(implying value semantics) and the design of a processing pipeline,
which quite naturally leads to binding by reference into the enclosing
implementation.
In most cases, this change (to lazy on-demand initialisation) should be
transparent for the complete implementation code in `RandomDraw` -- with
one notable exception: when configuring an elaborate pipeline, especially
with dynamic changes of the probability profile during the simulation run,
then then obviously there is the desire to use the existing processing
pipeline from the reconfiguration function (in fact it would be quite
hard to explain why and where this should be avoided). `LazyInit` breaks
this usage scenario, since -- at the time the reconfiguration runs --
now the object is not initialised at all, but holds a »Trojan« functor,
which will trigger initialisation eventually.
After some headaches and grievances (why am I engaging into such an
elaborate solution for such an accidental and marginal topic...),
unfortunately it occurred to me that even this problem can be fixed,
with yet some further "minimal" adjustments to the scheme: the LazyInit
mechanism ''just needs to ensure'' that the init-functor ''sees the
same environment as in eager init'' -- that is, it must clear out the
»Trojan« first, and it ''could apply any previous pending init function''
fist. That is, with just a minimal change, we possibly build a chain
of init functors now, and apply them in given order, so each one
sees the state the previous one created -- as if this was just
direct eager object manipulation...
2023-11-25 17:18:26 +01:00
|
|
|
if (isInit() and targetFunctor)
|
|
|
|
|
{// object is already »engaged« — no need to delay init
|
|
|
|
|
using ExpectedArg = _FunArg<INI>;
|
|
|
|
|
initialiser (static_cast<ExpectedArg> (this));
|
|
|
|
|
return PendingInit(); // keep engaged; no pending init
|
|
|
|
|
}
|
|
|
|
|
// else: prepare delayed init...
|
2023-11-24 23:26:41 +01:00
|
|
|
PendingInit storageHandle{
|
|
|
|
|
new HeapStorage{
|
|
|
|
|
buildInitialiserDelegate (targetFunctor, forward<INI> (initialiser))}};
|
|
|
|
|
// place a »Trojan« into the target functor to trigger initialisation on invocation...
|
|
|
|
|
targetFunctor = TrojanFun<SIG>::generateTrap (getPointerToDelegate<SIG> (*storageHandle));
|
|
|
|
|
return storageHandle;
|
2023-11-24 21:16:54 +01:00
|
|
|
}
|
|
|
|
|
|
2023-11-24 23:26:41 +01:00
|
|
|
template<class SIG>
|
Chain-Load: provide a scheme for repeated init
For context: I've engaged into writing a `LazyInit` helper component,
to resolve the inner contradiction between DSL use of `RandomDraw`
(implying value semantics) and the design of a processing pipeline,
which quite naturally leads to binding by reference into the enclosing
implementation.
In most cases, this change (to lazy on-demand initialisation) should be
transparent for the complete implementation code in `RandomDraw` -- with
one notable exception: when configuring an elaborate pipeline, especially
with dynamic changes of the probability profile during the simulation run,
then then obviously there is the desire to use the existing processing
pipeline from the reconfiguration function (in fact it would be quite
hard to explain why and where this should be avoided). `LazyInit` breaks
this usage scenario, since -- at the time the reconfiguration runs --
now the object is not initialised at all, but holds a »Trojan« functor,
which will trigger initialisation eventually.
After some headaches and grievances (why am I engaging into such an
elaborate solution for such an accidental and marginal topic...),
unfortunately it occurred to me that even this problem can be fixed,
with yet some further "minimal" adjustments to the scheme: the LazyInit
mechanism ''just needs to ensure'' that the init-functor ''sees the
same environment as in eager init'' -- that is, it must clear out the
»Trojan« first, and it ''could apply any previous pending init function''
fist. That is, with just a minimal change, we possibly build a chain
of init functors now, and apply them in given order, so each one
sees the state the previous one created -- as if this was just
direct eager object manipulation...
2023-11-25 17:18:26 +01:00
|
|
|
static DelegateType<SIG>*
|
2023-11-25 03:36:19 +01:00
|
|
|
getPointerToDelegate (HeapStorage& buffer)
|
2023-11-24 23:26:41 +01:00
|
|
|
{
|
|
|
|
|
return reinterpret_cast<DelegateType<SIG>*> (&buffer);
|
|
|
|
|
}
|
2023-11-24 21:16:54 +01:00
|
|
|
|
Chain-Load: provide a scheme for repeated init
For context: I've engaged into writing a `LazyInit` helper component,
to resolve the inner contradiction between DSL use of `RandomDraw`
(implying value semantics) and the design of a processing pipeline,
which quite naturally leads to binding by reference into the enclosing
implementation.
In most cases, this change (to lazy on-demand initialisation) should be
transparent for the complete implementation code in `RandomDraw` -- with
one notable exception: when configuring an elaborate pipeline, especially
with dynamic changes of the probability profile during the simulation run,
then then obviously there is the desire to use the existing processing
pipeline from the reconfiguration function (in fact it would be quite
hard to explain why and where this should be avoided). `LazyInit` breaks
this usage scenario, since -- at the time the reconfiguration runs --
now the object is not initialised at all, but holds a »Trojan« functor,
which will trigger initialisation eventually.
After some headaches and grievances (why am I engaging into such an
elaborate solution for such an accidental and marginal topic...),
unfortunately it occurred to me that even this problem can be fixed,
with yet some further "minimal" adjustments to the scheme: the LazyInit
mechanism ''just needs to ensure'' that the init-functor ''sees the
same environment as in eager init'' -- that is, it must clear out the
»Trojan« first, and it ''could apply any previous pending init function''
fist. That is, with just a minimal change, we possibly build a chain
of init functors now, and apply them in given order, so each one
sees the state the previous one created -- as if this was just
direct eager object manipulation...
2023-11-25 17:18:26 +01:00
|
|
|
template<class SIG>
|
|
|
|
|
static std::function<SIG>
|
|
|
|
|
maybeInvoke (PendingInit const& pendingInit, RawAddr location)
|
|
|
|
|
{
|
|
|
|
|
if (not pendingInit) // no pending init -> empty TargetFun
|
|
|
|
|
return std::function<SIG>();
|
|
|
|
|
auto* pendingDelegate = getPointerToDelegate<SIG>(*pendingInit);
|
|
|
|
|
return (*pendingDelegate) (location); // invoke to create new TargetFun
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-24 21:16:54 +01:00
|
|
|
template<class SIG, class INI>
|
|
|
|
|
DelegateType<SIG>
|
|
|
|
|
buildInitialiserDelegate (std::function<SIG>& targetFunctor, INI&& initialiser)
|
|
|
|
|
{
|
|
|
|
|
using TargetFun = std::function<SIG>;
|
2023-11-25 03:36:19 +01:00
|
|
|
using ExpectedArg = _FunArg<INI>;
|
2023-11-24 21:16:54 +01:00
|
|
|
return DelegateType<SIG>{
|
|
|
|
|
[performInit = forward<INI> (initialiser)
|
Chain-Load: provide a scheme for repeated init
For context: I've engaged into writing a `LazyInit` helper component,
to resolve the inner contradiction between DSL use of `RandomDraw`
(implying value semantics) and the design of a processing pipeline,
which quite naturally leads to binding by reference into the enclosing
implementation.
In most cases, this change (to lazy on-demand initialisation) should be
transparent for the complete implementation code in `RandomDraw` -- with
one notable exception: when configuring an elaborate pipeline, especially
with dynamic changes of the probability profile during the simulation run,
then then obviously there is the desire to use the existing processing
pipeline from the reconfiguration function (in fact it would be quite
hard to explain why and where this should be avoided). `LazyInit` breaks
this usage scenario, since -- at the time the reconfiguration runs --
now the object is not initialised at all, but holds a »Trojan« functor,
which will trigger initialisation eventually.
After some headaches and grievances (why am I engaging into such an
elaborate solution for such an accidental and marginal topic...),
unfortunately it occurred to me that even this problem can be fixed,
with yet some further "minimal" adjustments to the scheme: the LazyInit
mechanism ''just needs to ensure'' that the init-functor ''sees the
same environment as in eager init'' -- that is, it must clear out the
»Trojan« first, and it ''could apply any previous pending init function''
fist. That is, with just a minimal change, we possibly build a chain
of init functors now, and apply them in given order, so each one
sees the state the previous one created -- as if this was just
direct eager object manipulation...
2023-11-25 17:18:26 +01:00
|
|
|
,previousInit = move (pendingInit_)
|
2023-11-24 21:16:54 +01:00
|
|
|
,targetOffset = captureRawAddrOffset (this, &targetFunctor)]
|
|
|
|
|
(RawAddr location) -> TargetFun&
|
|
|
|
|
{// apply known offset backwards to find current location of the host object
|
2023-11-24 23:26:41 +01:00
|
|
|
TargetFun* target = relocate<TargetFun> (location, -FUNCTOR_PAYLOAD_OFFSET);
|
2023-11-24 21:16:54 +01:00
|
|
|
LazyInit* self = relocate<LazyInit> (target, -targetOffset);
|
|
|
|
|
REQUIRE (self);
|
Chain-Load: provide a scheme for repeated init
For context: I've engaged into writing a `LazyInit` helper component,
to resolve the inner contradiction between DSL use of `RandomDraw`
(implying value semantics) and the design of a processing pipeline,
which quite naturally leads to binding by reference into the enclosing
implementation.
In most cases, this change (to lazy on-demand initialisation) should be
transparent for the complete implementation code in `RandomDraw` -- with
one notable exception: when configuring an elaborate pipeline, especially
with dynamic changes of the probability profile during the simulation run,
then then obviously there is the desire to use the existing processing
pipeline from the reconfiguration function (in fact it would be quite
hard to explain why and where this should be avoided). `LazyInit` breaks
this usage scenario, since -- at the time the reconfiguration runs --
now the object is not initialised at all, but holds a »Trojan« functor,
which will trigger initialisation eventually.
After some headaches and grievances (why am I engaging into such an
elaborate solution for such an accidental and marginal topic...),
unfortunately it occurred to me that even this problem can be fixed,
with yet some further "minimal" adjustments to the scheme: the LazyInit
mechanism ''just needs to ensure'' that the init-functor ''sees the
same environment as in eager init'' -- that is, it must clear out the
»Trojan« first, and it ''could apply any previous pending init function''
fist. That is, with just a minimal change, we possibly build a chain
of init functors now, and apply them in given order, so each one
sees the state the previous one created -- as if this was just
direct eager object manipulation...
2023-11-25 17:18:26 +01:00
|
|
|
// setup target as it would be with eager init
|
|
|
|
|
(*target) = maybeInvoke<SIG> (previousInit, location);
|
2023-11-25 03:36:19 +01:00
|
|
|
// invoke init, possibly downcast to derived *self
|
|
|
|
|
performInit (static_cast<ExpectedArg> (self));
|
|
|
|
|
self->pendingInit_.reset(); // release storage
|
|
|
|
|
return *target; // invoked by the »Trojan« to yield first result
|
2023-11-24 21:16:54 +01:00
|
|
|
}};
|
|
|
|
|
}
|
2023-11-23 23:42:55 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} // namespace lib
|
|
|
|
|
#endif /*LIB_LAZY_INIT_H*/
|