Library: improve initial population of ScopedCollection

This is a collection with unique-ownership,
and the ability to create all content objects in ''RAII style''
from the constructor call, using a ''population functor''.

It is often used to maintain a collection of resource handles,
and especially to start a series of threads (for testing).

With the variadic λ-captures from C++20, it is possible to rewrite
the most frequently used population functors to be easier to use.
Notably we can now bind constructor arguments, that will be passed
to each of the created elements — a prominent example being the
thread function when launching several threads. Furthermore,
I added a constructor variant that consumes a Lumiera Iterator.
Adding this kind of generic variants is possible now with C++20,
since overload resolution takes concepts into account.
This commit is contained in:
Fischlurch 2026-05-14 19:30:22 +02:00
parent 3a491855e7
commit 7448b81d49
8 changed files with 358 additions and 175 deletions

View file

@ -88,6 +88,33 @@ test with random numbers::
several threads; each thread should use its own generator then (yet this can be handled sloppy if
the _quality_ of the random number actually does not matter!)
special test setup::
It can be very helpful to use _policy based design_ for some complex evaluation or algorithm,
even in cases where it is clear that the actual usage will only ever involve one specific data type
and configuration. Not only does such a structured approach help with clarifying the design itself,
since the core of the evaluation logic can be abstracted from data representation, but only the
testing and verification of correctness is simplified, as the test can be written to use simple,
human-readable data (strings, symbols), and, furthermore, many kinds of instrumentation can be
included into the test data to gain detailed insight what is happening in the test subject.
verify behaviour sequence::
Use the `lib::EventLog`:
+
- within the test, notable points are marked into the log, possibly with arguments
- after the test, the Event Log can be verified with a search-sequence
- this way it is possible to ensure that some events did happen, and happened
in a very specific order, or that some event did _not_ happen after another event
- the `test::Tracker` defined in 'src/test/tracking-dummy.hpp' will mark all constructor,
destructor and copy events into an embedded Event Log instance. By inheriting or mix-in
from that type, the allocation and instance behaviour of some service can be scrutinised.
verify incidences and concurrency::
Aside from using some generic tracing utility, sometimes it is much easier to setup
a _specifically tailored instrumentation_. This can be achieved with 'incidence-count.hpp'
+
- for instrumentation, the `markEnter()` and `markLeave()` functions need to be invoked
- after the test run, various statistics can be computed and evaluated, including
cumulated time and concurrency for the "cases" marked with enter / leave.
Common Tasks
@ -109,6 +136,12 @@ structured data::
- also useful as output format, both for debugging and because it can be matched against
an expected structure, which is generated with the DSL notation.
- [yellow-background]#(planned)# will be able to map this from/to JSON easily.
scoped collection::
unique-ownership collection that holds a sequence of objects in a preallocated fixed
storage block. Notably even non-copyable objects can be managed (which is not possible
with a `std::vector`), and type-erased instances of subclasses can be mixed. Furthermore
there is a constructor variant that allows to populate a `ScopedCollection` in RAII-style.
This can e.g. be used to fire-off a set of Threads or open a set of wrapped connections.
Iterating
~~~~~~~~~
@ -134,6 +167,26 @@ IterSource::
even the production-state can be hooked up into the IterSource instance (with a smart-ptr).
This allows e.g. to send a Diff to the UI through the UI-Bus, while the actual generation
and application both happen demand-driven or _lazy..._
State Core::
When some algorithm involves a statefull evaluation, it might be helpful to package that into
a _State Core_ container. This is a class that is expected to expose three operations, which
allow to build an iteration on top. The header 'iter-adapter.hpp' provides several adapters
and wrappers, notable `lib::IterStateWrapper` wraps any _state core_ and provides a
Lumiera Iterator interface; likewise, `IterExplorer` heavily relies on that concept and
is able to accept and iterate any state core in a pipeline.
special iterators::
Some additional adaptors are provided as building block so that specific evaluations
can be made to conform with the »Lumiera Iterator« concept
+
- several given iterators can be combined into a iteration over tuples with 'lib/iter-zip.hpp'
- there is also a variation 'lib::izip' that adds a running index into the _zipping operation_
- IterExplorer provides a `flatten()` operation, that can be used to join an iterator-of-iterators
into a single seamless iteration lazily.
- when a STL container also supports _backwards iteration_, then a lumiera iterator can be
created that can _switch direction_ dynamically, with the help of 'iter-cursor.hpp'
- a STL »deque« container can be used both as a stack and a queue; the header 'iter-stack.hpp'
provides an iteration adapter for both usage schemes, so that either the stack or the queue
can be _"discharged"_ as a Lumiera Iterator (possibly with an IterExplorer pipeline)
Formatting
@ -155,6 +208,25 @@ Formatting
because there are way more capable solutions _out there_ ☺)
Setup and wiring
~~~~~~~~~~~~~~~~
Generally speaking, we prefer _constructor wiring_ whenever possible, to avoid creating
unnecessary statefulness (as would be the case when connecting dependencies through setters).
This design also favours decomposition into self-contained components with a single purpose,
and simplifies error handling with the help of the RAII pattern.
However, sometimes a piece of code depends on some remote and even global service; in these
cases, the `lib::Depend` framework can be used to access that service via type name. The
actual service can either be created on-demand as a singleton, or it can be installed
explicitly as a service instance. Furthermore, it is possible to "push aside" the
current service instance temporarily and replace it by a _test mock_.
One point to note is that `lib::Depend` is a _monostate_, and not a _singleton_.
The actual service should be written as a free-standing class, that can be instantiated
directly for the purpose of testing.
Language constructs
-------------------
Templates
@ -240,7 +312,7 @@ index and iterate over tuple elements::
This is another example of bridging the compile-time / runtime barrier, using a trampoline.
Obviously such a solution requires that it is somehow possible to define a generic functor / λ that
can accept any of the tuple elements. And, in order to build a runtime index access, we additionally
need to convert each of the tuple elements into a _common reconciled type.
need to convert each of the tuple elements into a _common reconciled type_.
+
- If such a type exists, can be figured out with the help of the `meta::CommonReturn` _metafunction_
- `lib::meta::TupleIdxAdaptor` provides the necessary machinery to define a subscript `operator[]`

View file

@ -2,7 +2,7 @@
SCOPED-COLLECTION.hpp - managing a fixed collection of noncopyable polymorphic objects
Copyright (C)
2012, Hermann Vosseler <Ichthyostega@web.de>
2012,2026 Hermann Vosseler <Ichthyostega@web.de>
  **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
@ -13,8 +13,8 @@
/** @file scoped-collection.hpp
** Manage a collection of non-copyable polymorphic objects in compact storage.
** This helper supports common situation where a service implementation has to
** manages a fixed collection of implementation related sub-components, treated
** This helper supports the common situation where a service implementation has
** to manage a fixed collection of implementation related sub-components, treated
** with reference semantics. Typically, those objects are used polymorphically,
** and often even with _type erasure_ and added step by step through a builder.
** The storage to hold all those child objects is allocated in one chunk
@ -31,7 +31,7 @@
** the managing scope is alive and able to access them.
**
** There are two different usage patterns for populating a ScopedCollection
** - the "stack style" usage creates an empty container (using the one arg
** - the **stack style** usage creates an empty container (using the one arg
** ctor to specify the maximum size only). The storage capacity to hold
** a number of objects, up to the predefined maximum, is (heap) allocated
** immediately at construction of the container, yet no child objects are
@ -39,11 +39,11 @@
** by invoking #emplaceElement() to create a new element of the default
** type `I`) or #emplace<Type>(args) to create some subtype. This way,
** the container is gradually filled up.
** - the "RAII style" usage strives to create all of the content objects
** right away, immediately after the memory allocation. This usage pattern
** avoids any kind of "lifecycle state". Either the container comes up sane
** and fully populated, or the ctor call fails and any already created
** objects are discarded.
** - the **RAII-style** usage attempts to create all the contained objects
** right away, immediately after memory allocation. This usage pattern
** avoids any kind of _lifecycle state_. Either the container is in sane
** state and fully populated from the outset, or the call to the ctor fails
** and all objects that have already been created are discarded.
** @note intentionally there is no operation to discard individual objects,
** the only option is to #clear() the whole container.
** @note the container can hold instances of a subclass of the type defined
@ -69,7 +69,7 @@
#include "lib/iter-adapter.hpp"
#include <cstddef>
#include <type_traits>
#include <concepts>
namespace lib {
@ -78,6 +78,7 @@ namespace lib {
using LERR_(INDEX_BOUNDS);
using LERR_(CAPACITY);
using meta::lum_iter;
/**
@ -100,8 +101,8 @@ namespace lib {
public:
/**
* Storage Frame to hold one Child object.
* The storage will be an heap allocated
* array of such Wrapper objects.
* The storage will be an heap allocated array
* of such Wrapper objects.
* @note doesn't manage the Child
*/
class ElementHolder
@ -126,7 +127,6 @@ namespace lib {
/** place object of type TY, forwarding ctor arguments */
template<class TY, typename...ARGS>
TY&
@ -150,6 +150,9 @@ namespace lib {
clear();
}
/** create an empty ScopedCollection with given max capacity
* @remark this ctor variant is intended for _stack-like_ usage
*/
explicit
ScopedCollection (size_t maxElements)
: level_(0)
@ -164,7 +167,7 @@ namespace lib {
* It gets an ElementHolder& as parameter, and should
* use this to create an object of suitable I-subclass
*/
template<class CTOR>
template<std::invocable<ElementHolder&> CTOR>
ScopedCollection (size_t maxElements, CTOR builder)
: level_(0)
, capacity_(maxElements)
@ -190,22 +193,44 @@ namespace lib {
populate_by (builder,instance);
}
/** variation of RAII-style: "pull" elements from an iterator.
* Each element will be created directly by invoking either the copy ctor,
* or a suitable 1-argument ctor, depending on the type yielded by the iterator.
* No further elements will be created however, once the iterator is exhausted.
* @note anything in accordance to the Lumiera Forward concept is acceptable.
* This rules out just passing a plain STL iterator (because these can't
* tell for themselves when they're exhausted). Use an suitable iter-adapter
* instead, e.g. by invoking lib::iter_stl::eachElm(stl_container)
*/
template<lum_iter IT>
ScopedCollection (size_t maxElements, IT&& iter)
: level_(0)
, capacity_(maxElements)
, elements_(new ElementHolder[maxElements])
{
pull_from (std::forward<IT> (iter));
}
/* == some pre-defined Builders == */
class FillAll; ///< fills the ScopedCollection with default constructed I-instances
/** Builder: ills the given storage slot with a I-instance
* constructed from copies of the given arguments.
* @return builder-λ to pass into ScopeCollection's ctor. */
template<typename...ARGS>
static auto fill (ARGS&&... args);
template<typename TY>
class FillWith; ///< fills the ScopedCollection with default constructed TY-instances
/** Builder: fills the given storage slot with a TY-instance
* constructed from copies of the given arguments. */
template<typename TY, typename...ARGS>
static auto fillWith (ARGS&&... args);
template<typename IT>
class PullFrom; ///< fills by copy-constructing values pulled from the iterator IT
/** Builder: fills the given storage slot from invoking the
* provided factory functor, with either an I-element or a
* I-subtype, depending on the functor's result type.
* @note the functor will be copied in the resulting λ */
template<std::invocable<> FUN>
static auto invoke (FUN&& factory);
template<typename IT>
static PullFrom<IT>
pull (IT iter) ///< convenience shortcut to pull from any given Lumiera Forward Iterator
{
return PullFrom<IT> (iter);
}
void
clear()
@ -247,7 +272,7 @@ namespace lib {
* ElementHolder#create function, which places a new element
* into the storage frame passed as parameter.
*/
template<class CTOR>
template<std::invocable<ElementHolder&> CTOR>
void
populate_by (CTOR builder)
try {
@ -282,11 +307,46 @@ namespace lib {
catch(...)
{
WARN (progress, "Failure while populating ScopedCollection. "
"All elements will be discarded");
"All elements will be discarded.");
clear();
throw;
}
template<lum_iter IT>
struct _BuildFromIter ///< @internal helper to determine what to build from an iterator
{
using Yield = iter::Yield<IT>;
using Value = meta::ValueTypeBinding<IT>::value_type;
using Type = std::conditional_t<meta::is_Subclass_v<Value, I>, Value, I>;
static_assert (std::is_constructible_v<Type, Yield>
,"Iterator must produce something "
"that can be used to build contents of this container");
};
/** variation of element initialisation,
* creating further elements from the results of an iterator,
* until either the container is filled up or the iterator exhausted.
*/
template<lum_iter IT>
void
pull_from (IT iter)
try {
while (iter and level_ < capacity_)
{
using Element = _BuildFromIter<IT>::Type;
ElementHolder& storageFrame (elements_[level_]);
storageFrame.template create<Element> (*iter);
++level_;
++iter;
} }
catch(...)
{
WARN (progress, "Failure while pulling Iterator into ScopedCollection. "
"All elements will be discarded.");
clear();
throw;
}
/** push a new element of default type
@ -340,8 +400,8 @@ namespace lib {
size_t size () const { return level_; }
size_t capacity () const { return capacity_; }
bool empty () const { return 0 == level_; }
size_t capacity() const { return capacity_; }
bool empty() const { return 0 == level_; }
// use in standard range for loops...
@ -354,7 +414,7 @@ namespace lib {
private:
/* ==== Storage: heap allocated array of element buffers ==== */
typedef std::unique_ptr<ElementHolder[]> ElementStorage;
using ElementStorage = std::unique_ptr<ElementHolder[]>;
size_t level_;
size_t capacity_;
@ -417,66 +477,66 @@ namespace lib {
/* === Supplement: pre-defined element builders === */
/** \par usage
* Pass an instance of this builder functor as 2nd parameter
* to ScopedCollections's ctor. (an anonymous instance is OK).
* On invocation, this function generates a builder-λ to pass as
* the 2nd parameter to ScopedCollections's ctor. When arguments are given,
* these are perfect-forwarded into a copy in the closure, which implies that
* all instances will receive either a copy or const-reference to these values.
* (It is not possible to pass references this way).
* Using this variant of the ctor switches the collection to RAII-style:
* It will immediately try to create all the embedded objects, invoking this
* It will immediately try to create all the embedded objects, invoking the
* builder functor for each "slot" to hold such an embedded object. Actually,
* this "slot" is an ElementHolder instance, which provides functions for
* placement-creating objects into this embedded buffer.
* @note allows also to default-create or copy-create
*/
template<class I, size_t siz>
class ScopedCollection<I,siz>::FillAll
{
public:
void
operator() (ScopedCollection<I,siz>::ElementHolder& storage)
{
storage.template create<I>();
}
};
template<typename...ARGS>
auto
ScopedCollection<I,siz>::fill (ARGS&&... args)
{
return [... args = std::forward<ARGS> (args)]
(ScopedCollection<I,siz>::ElementHolder& storage)
{
storage.template create<I> (args...);
};
}
template<class I, size_t siz>
template<typename TY>
class ScopedCollection<I,siz>::FillWith
{
public:
void
operator() (ScopedCollection<I,siz>::ElementHolder& storage)
{
storage.template create<TY>();
}
};
template<typename TY, typename...ARGS>
auto
ScopedCollection<I,siz>::fillWith (ARGS&&... args)
{
return [... args = std::forward<ARGS> (args)]
(ScopedCollection<I,siz>::ElementHolder& storage)
{
storage.template create<TY> (args...);
};
}
/** \par usage
* This variant allows to "pull" elements from an iterator.
* Actually, the collection will try to create each element right away,
* by invoking the copy ctor and passing the value yielded by the iterator.
* @note anything in accordance to the Lumiera Forward Iterator pattern is OK.
* This rules out just passing a plain STL iterator (because these can't
* tell for themselves when they're exhausted). Use an suitable iter-adapter
* instead, e.g. by invoking lib::iter_stl::eachElm(stl_container)
/**
* @remark this builder handles the technical aspect of creating elements
* into the slots of the ElementHolder storage, so that client code
* has only to define the constructor argument, which notably can
* also be a subclass of \a I (assuming the storage size suffices)
* @return builder-λ that can be passed into ScopeCollection's ctor.
*/
template<class I, size_t siz>
template<typename IT>
class ScopedCollection<I,siz>::PullFrom
{
IT iter_;
using ElementType = meta::ValueTypeBinding<IT>::value_type;
public:
PullFrom (IT source)
: iter_(source)
{ }
void
operator() (ScopedCollection<I,siz>::ElementHolder& storage)
{
storage.template create<ElementType> (*iter_);
++iter_;
}
};
template<std::invocable<> FUN>
auto
ScopedCollection<I,siz>::invoke (FUN&& factory)
{
using Res = std::invoke_result_t<FUN>;
using Elem = std::conditional_t<meta::is_Subclass_v<Res, I>, Res, I>;
static_assert (std::is_constructible_v<Elem, Res>
,"Iterator must produce something "
"that can be used to build contents of this container");
return [fac = std::forward<FUN> (factory)]
(ScopedCollection<I,siz>::ElementHolder& storage) mutable
{
storage.template create<Elem> (fac());
};
}

View file

@ -451,6 +451,8 @@ namespace lib {
* and possibly configure further details, depending on the actual Policy used.
* @remark the primary ThreadLifecycle-ctor accepts such a Launch-instance
* and invokes a chain of λ-functions collected in the member #launch
* @note a Launch instance can be built by direct conversion from an arg-less
* thread function (because the ctor is _not_ explicit)
*/
struct Launch
: util::MoveOnly
@ -571,6 +573,10 @@ namespace lib {
* Primary constructor: Launch the new thread with flexible configuration.
* @param launcher a #Launch builder with a λ-chain to configure and
* finally trigger start of the thread
* @note The Launch instance can be built by implicit conversion from any
* arg-less invocable; this implies that ThreadLifecycle (and its
* relevant subclasses) can be built without much ado directly
* from a Lambda that defines the thread-function.
*/
ThreadLifecycle (Launch launcher)
: Policy{launcher.id}
@ -644,6 +650,12 @@ namespace lib {
* thread has already copied the arguments and indeed actively started to run.
* @warning The destructor waits for a short grace period of 20ms, but calls
* `std::terminate` afterwards, should the thread still be active then.
* @remark creating a Thread instance immediately starts the thread;
* it can be built from
* - any argument-less invocable or lambda (that is invoked in the thread)
* - a string argument for the thread-id, an invocable, further arguments...
* - a member pointer and further arguments; useful for derived classes
* - a ThreadLifecycle::Launch, which allows fine grained configuration
*/
class Thread
: public thread::ThreadLifecycle<thread::PolicyLaunchOnly>

View file

@ -129,19 +129,19 @@ namespace test {
// launch a significant number of threads,
// each verifying the same invocation sequence
lib::ScopedCollection<Thread> threads{NUM_THREADS};
for (uint i=1; i<=NUM_THREADS; ++i)
threads.emplace ([&]{
CHECK (i1 == myDummy.invoke());
sleep_for (100us);
CHECK (i2 == myDummy.invoke());
sleep_for (100us);
CHECK (i3 == myDummy.invoke());
});
using Threads = lib::ScopedCollection<Thread>;
Threads threads{NUM_THREADS
,Threads::fill ([&]{
CHECK (i1 == myDummy.invoke());
sleep_for (100us);
CHECK (i2 == myDummy.invoke());
sleep_for (100us);
CHECK (i3 == myDummy.invoke());
})};
while (explore(threads).has_any())
yield(); // wait for all threads to finish

View file

@ -379,9 +379,9 @@ namespace test{
{
CHECK (0 == Dummy::checksum());
CollD coll (25, CollD::FillAll() );
CollD coll (25, CollD::fill() );
CHECK (!isnil (coll));
CHECK (not isnil (coll));
CHECK (25 == coll.size());
CHECK (0 != Dummy::checksum());
@ -393,14 +393,16 @@ namespace test{
}
/** @test use a "populator" that creates a subclass and binds arguments */
void
verify_subclassPopulator()
{
CHECK (0 == Dummy::checksum());
CollD coll (25, CollD::FillWith<SubDummy>() );
const uint arg{42};
CollD coll (25, CollD::fillWith<SubDummy> (arg, 55) );
CHECK (!isnil (coll));
CHECK (not isnil (coll));
CHECK (25 == coll.size());
CHECK (0 != Dummy::checksum());
@ -409,29 +411,32 @@ namespace test{
}
/** @test verify dynamic pulling of elements
* - the first step uses a "populator" that invokes a λ
* - the second step then uses the source container's iterator,
* which happens to be a _Lumiera Forward Iterator_ to pull
* copies into a second container.
* - even while the second container is dimensioned to be larger,
* only the number of elements yielded by the iterator are created.
*/
void
verify_iteratorPopulator()
{
typedef ScopedCollection<uint> CollI;
CollI source (25);
for (uint i=0; i < source.capacity(); ++i)
source.emplace<uint>(i); // holding the numbers 0..24
CollI coll (20, CollI::pull(source.begin()));
// this immediately pulls in the first 20 elements
CHECK (!isnil (coll));
CollI source (20, CollI::invoke([i=0] mutable { return i++; }));
// filled with numbers 0..19
CollI coll (30, source.begin()); // this immediately pulls in the first 20 elements
CHECK (not isnil (coll));
CHECK (20 == coll.size());
CHECK (25 == source.size());
CHECK (20 == source.size());
CHECK (30 == coll.capacity());
for (uint i=0; i < coll.size(); ++i)
{
CHECK (coll[i] == i );
CHECK (coll[i] == source[i]);
}
// note: the iterator is assumed to deliver a sufficient amount of elements
VERIFY_ERROR (ITER_EXHAUST, CollI (50, CollI::pull (source.begin())));
}

View file

@ -19,10 +19,12 @@
#include "test/run.hpp"
#include "lib/sync-classlock.hpp"
#include "lib/scoped-collection.hpp"
#include "lib/iter-explorer.hpp"
#include "lib/thread.hpp"
using test::Test;
//using vault::ThreadJoinable; //////////////////WIP
using lib::explore;
using std::this_thread::yield;
namespace lib {
namespace test {
@ -58,29 +60,25 @@ namespace test {
auto gen = buildCappedSubSequence(defaultGen);
int contended = 0;
using Threads = lib::ScopedCollection<ThreadJoinable<>>;
using Threads = lib::ScopedCollection<Thread>;
// Start a bunch of threads with random access pattern
Threads threads{NUM_THREADS,
[&](Threads::ElementHolder& storage)
{
storage.create<ThreadJoinable<>> ("Sync-ClassLock stress test"
,[&]{
for (uint i=0; i<NUM_LOOP; ++i)
{
uint delay = gen.i(10);
usleep (delay);
{
ClassLock<void> guard;
++contended;
}
}
});
}
};
// Start a bunch of threads with random access pattern...
Threads::fill ("Sync-ClassLock stress test"
,[&]{
for (uint i=0; i<NUM_LOOP; ++i)
{
uint delay = gen.i(10);
usleep (delay);
{
ClassLock<void> guard;
++contended;
}
}
})};
for (auto& thread : threads)
thread.join(); // block until thread terminates // @suppress("Return value not evaluated")
while (explore(threads).has_any())
yield(); // wait for all threads to terminate
CHECK (contended == NUM_THREADS * NUM_LOOP,
"ALARM: Lock failed, concurrent modification "

View file

@ -225,11 +225,9 @@ namespace test {
CHECK (myPool.empty()); // no further pending / duplicate Alloc in-queue
};
using Threads = lib::ScopedCollection<Thread>;
auto startThread = [&](auto& storage){ storage.template create<Thread> (workSeq); };
// Start a collection of workers....
Threads threads{NUM_THREADS, startThread};
using Threads = lib::ScopedCollection<Thread>;
Threads threads{NUM_THREADS, Threads::fill(workSeq)};
// after that barrier, all threads send back their allocation
afterThread.sync();

View file

@ -145915,7 +145915,8 @@ StM_bind(Builder&lt;R1&gt; b1, Extension&lt;R1,R2&gt; extension)
<node CREATED="1777024713120" ID="ID_24309556" MODIFIED="1777024728172" TEXT="eigentlich keine Anpassung notwendig">
<icon BUILTIN="idea"/>
</node>
<node CREATED="1777024729820" ID="ID_1492141284" MODIFIED="1777024764811" TEXT="man k&#xf6;nnte....">
<node BACKGROUND_COLOR="#d2beaf" COLOR="#5c4d6e" CREATED="1777024729820" ID="ID_1492141284" MODIFIED="1778788703833" TEXT="man k&#xf6;nnte....">
<icon BUILTIN="hourglass"/>
<node CREATED="1777024765911" ID="ID_596148209" MODIFIED="1777024771000" TEXT="Eintr&#xe4;ge stehen lassen"/>
<node CREATED="1777024772191" ID="ID_645148091" MODIFIED="1777024802721" TEXT="erneute Allokation mit gleicher Adresse aus FREE-State zulassen"/>
</node>
@ -145924,8 +145925,8 @@ StM_bind(Builder&lt;R1&gt; b1, Extension&lt;R1,R2&gt; extension)
<icon BUILTIN="yes"/>
<node CREATED="1777050440887" ID="ID_1006627416" MODIFIED="1777066692859" TEXT="Verhalten spezifizieren">
<icon BUILTIN="yes"/>
<node CREATED="1777050520227" ID="ID_796153621" MODIFIED="1777325912544" TEXT="Use-Cases">
<linktarget COLOR="#36506f" DESTINATION="ID_796153621" ENDARROW="Default" ENDINCLINATION="-452;0;" ID="Arrow_ID_1304449539" SOURCE="ID_1675354420" STARTARROW="Default" STARTINCLINATION="-32;-602;"/>
<node CREATED="1777050520227" ID="ID_796153621" MODIFIED="1778788680440" TEXT="Use-Cases">
<linktarget COLOR="#36506f" DESTINATION="ID_796153621" ENDARROW="Default" ENDINCLINATION="-246;327;" ID="Arrow_ID_1304449539" SOURCE="ID_1675354420" STARTARROW="Default" STARTINCLINATION="-44;-241;"/>
<node CREATED="1777068198295" ID="ID_1010651012" MODIFIED="1777068390012" TEXT="Allokationsbedarf ank&#xfc;ndigen">
<richcontent TYPE="NOTE"><html>
<head/>
@ -145954,7 +145955,7 @@ StM_bind(Builder&lt;R1&gt; b1, Extension&lt;R1,R2&gt; extension)
<icon BUILTIN="yes"/>
</node>
</node>
<node COLOR="#338800" CREATED="1777050453251" ID="ID_1529547535" MODIFIED="1777317280989" TEXT="Implementieren">
<node COLOR="#338800" CREATED="1777050453251" FOLDED="true" ID="ID_1529547535" MODIFIED="1778788655476" TEXT="Implementieren">
<icon BUILTIN="button_ok"/>
<node COLOR="#338800" CREATED="1777052048904" ID="ID_1403107789" MODIFIED="1777084853433" TEXT="die Eingangs-Queue wird komplett (transparent) integriert">
<icon BUILTIN="button_ok"/>
@ -146162,8 +146163,8 @@ StM_bind(Builder&lt;R1&gt; b1, Extension&lt;R1,R2&gt; extension)
</node>
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1776819109351" ID="ID_1998897998" MODIFIED="1777339324992" TEXT="EngineBufferManager">
<icon BUILTIN="yes"/>
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1776819156872" ID="ID_1476925601" MODIFIED="1777339332224" TEXT="Grundkonzeption schaffen">
<icon BUILTIN="pencil"/>
<node COLOR="#338800" CREATED="1776819156872" ID="ID_1476925601" MODIFIED="1778788622379" TEXT="Grundkonzeption schaffen">
<icon BUILTIN="button_ok"/>
<node CREATED="1777325396485" ID="ID_947969066" MODIFIED="1777325400377" TEXT="Interface">
<node CREATED="1777325407524" ID="ID_1333052454" MODIFIED="1777325421966" TEXT="Zugang via lib::Depend&lt;EngineBufferManager&gt;">
<node CREATED="1777325446047" ID="ID_1500798718" MODIFIED="1777325451218" TEXT="geht das &#xfc;berhaupt?"/>
@ -146200,8 +146201,8 @@ StM_bind(Builder&lt;R1&gt; b1, Extension&lt;R1,R2&gt; extension)
<icon BUILTIN="idea"/>
</node>
</node>
<node CREATED="1777325872302" ID="ID_1675354420" MODIFIED="1777325912544" TEXT="Use-Cases">
<arrowlink COLOR="#36506f" DESTINATION="ID_796153621" ENDARROW="Default" ENDINCLINATION="-452;0;" ID="Arrow_ID_1304449539" STARTARROW="Default" STARTINCLINATION="-32;-602;"/>
<node CREATED="1777325872302" ID="ID_1675354420" MODIFIED="1778788680440" TEXT="Use-Cases">
<arrowlink COLOR="#36506f" DESTINATION="ID_796153621" ENDARROW="Default" ENDINCLINATION="-246;327;" ID="Arrow_ID_1304449539" STARTARROW="Default" STARTINCLINATION="-44;-241;"/>
<node CREATED="1777326001051" ID="ID_825262980" MODIFIED="1777326053002" TEXT="Allokation(en) asynchron beauftragen"/>
<node CREATED="1777326037424" ID="ID_613961876" MODIFIED="1777326058745" TEXT="Allokation(en) synchron beauftragen"/>
<node CREATED="1777326109630" ID="ID_1521192845" MODIFIED="1777326120671" TEXT="Allokation asynchron zur&#xfc;ckgeben"/>
@ -146212,7 +146213,8 @@ StM_bind(Builder&lt;R1&gt; b1, Extension&lt;R1,R2&gt; extension)
</node>
</node>
</node>
<node CREATED="1777326654102" ID="ID_499885447" MODIFIED="1777326656831" TEXT="Struktur">
<node COLOR="#435e98" CREATED="1777326654102" ID="ID_499885447" MODIFIED="1778788640455" TEXT="Struktur">
<icon BUILTIN="forward"/>
<node CREATED="1777326657789" ID="ID_831464061" MODIFIED="1777326715033" TEXT="L&#xe4;ngerfristig: Policy-based-Design">
<richcontent TYPE="NOTE"><html>
<head/>
@ -146223,7 +146225,7 @@ StM_bind(Builder&lt;R1&gt; b1, Extension&lt;R1,R2&gt; extension)
</body>
</html></richcontent>
</node>
<node BACKGROUND_COLOR="#fdfdcf" COLOR="#ff0000" CREATED="1777326753646" ID="ID_448684473" MODIFIED="1777326779620" TEXT="doch zun&#xe4;chst einmal: alles direkt coden">
<node COLOR="#435e98" CREATED="1777326753646" ID="ID_448684473" MODIFIED="1778788617177" TEXT="doch zun&#xe4;chst einmal: alles direkt coden">
<icon BUILTIN="yes"/>
<node CREATED="1777339002955" ID="ID_758544304" MODIFIED="1777339007206" TEXT="typedef BufferAllocator"/>
<node CREATED="1777339008730" ID="ID_1534654283" MODIFIED="1777339172707" TEXT="verwende fix eine Tile-Size von 64 Bytes">
@ -146367,7 +146369,7 @@ StM_bind(Builder&lt;R1&gt; b1, Extension&lt;R1,R2&gt; extension)
</node>
</node>
</node>
<node CREATED="1778118771505" ID="ID_1314536256" MODIFIED="1778118805988" TEXT="also &#x27f9; sinnvollerweise die Eingangs- und Ausgangs-Seite dediziert und separat bauen">
<node BACKGROUND_COLOR="#c8c0b6" COLOR="#435e98" CREATED="1778118771505" ID="ID_1314536256" MODIFIED="1778788608420" TEXT="also &#x27f9; sinnvollerweise die Eingangs- und Ausgangs-Seite dediziert und separat bauen">
<icon BUILTIN="yes"/>
<node CREATED="1778118817300" ID="ID_1259663169" MODIFIED="1778118842536" TEXT="Ausgang &#x2259; Allokations-Requests">
<node CREATED="1778118984391" ID="ID_231045425" MODIFIED="1778118994533" TEXT="angefordert wird nur eine size"/>
@ -146409,8 +146411,8 @@ StM_bind(Builder&lt;R1&gt; b1, Extension&lt;R1,R2&gt; extension)
</node>
</node>
</node>
<node BACKGROUND_COLOR="#fdfdcf" COLOR="#ff0000" CREATED="1778119450587" ID="ID_1806367243" MODIFIED="1778119470264" TEXT="Anordnung im Code">
<icon BUILTIN="flag-pink"/>
<node COLOR="#338800" CREATED="1778119450587" FOLDED="true" ID="ID_1806367243" MODIFIED="1778788593642" TEXT="Anordnung im Code">
<icon BUILTIN="button_ok"/>
<node CREATED="1778119474043" ID="ID_898200002" MODIFIED="1778119501037">
<richcontent TYPE="NODE"><html>
<head/>
@ -146431,7 +146433,8 @@ StM_bind(Builder&lt;R1&gt; b1, Extension&lt;R1,R2&gt; extension)
<node CREATED="1778119639862" ID="ID_1359278383" MODIFIED="1778119728301" TEXT="Konsequenz: der EngineBufferManager mu&#xdf; den LocalBufferStore sehen"/>
</node>
</node>
<node CREATED="1778199712170" ID="ID_334307615" MODIFIED="1778199836250" TEXT="aufl&#xf6;sen durch getrennte Translation-Units">
<node COLOR="#435e98" CREATED="1778199712170" ID="ID_334307615" MODIFIED="1778788535095" TEXT="aufl&#xf6;sen durch getrennte Translation-Units">
<icon BUILTIN="yes"/>
<node COLOR="#5b280f" CREATED="1778199840557" ID="ID_444542765" MODIFIED="1778200791662" TEXT="hier nochmal die Diskussion: Abstraktion einf&#xfc;hren?">
<linktarget COLOR="#694543" DESTINATION="ID_444542765" ENDARROW="Default" ENDINCLINATION="-442;-19;" ID="Arrow_ID_1733074661" SOURCE="ID_845392021" STARTARROW="None" STARTINCLINATION="183;9;"/>
<icon BUILTIN="help"/>
@ -146451,7 +146454,7 @@ StM_bind(Builder&lt;R1&gt; b1, Extension&lt;R1,R2&gt; extension)
</html></richcontent>
</node>
</node>
<node BACKGROUND_COLOR="#f0d5c5" COLOR="#990033" CREATED="1778200776431" ID="ID_633380599" MODIFIED="1778200789719" TEXT="damit dann die Frage: wer steht &#xbb;vorne&#xab;?">
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#3f0f69" CREATED="1778200776431" ID="ID_633380599" MODIFIED="1778788526626" TEXT="damit dann die Frage: wer steht &#xbb;vorne&#xab;?">
<icon BUILTIN="help"/>
<node CREATED="1778200986313" ID="ID_833445396" MODIFIED="1778201001726" TEXT="render-environment.cpp : orchestriert alle Komponenten"/>
<node CREATED="1778201611985" ID="ID_915606652" MODIFIED="1778201663551" TEXT="local-buffer-store.hpp : mu&#xdf; Interface + Impl sichtbar includieren">
@ -146473,7 +146476,7 @@ StM_bind(Builder&lt;R1&gt; b1, Extension&lt;R1,R2&gt; extension)
<node CREATED="1778201747177" ID="ID_1235232289" MODIFIED="1778201802499" TEXT="&#x27f9; engine-buffer-manager.hpp : sichtbares Backend-Service-Interface"/>
</node>
</node>
<node BACKGROUND_COLOR="#f8f1cb" COLOR="#a50125" CREATED="1778203953066" ID="ID_570673188" MODIFIED="1778203985427" TEXT="brauche dann aber doch eine Konvention / Abstraktion f&#xfc;r den Callback">
<node BACKGROUND_COLOR="#c8c0b6" COLOR="#435e98" CREATED="1778203953066" ID="ID_570673188" MODIFIED="1778788509519" TEXT="brauche dann aber doch eine Konvention / Abstraktion f&#xfc;r den Callback">
<icon BUILTIN="messagebox_warning"/>
<node CREATED="1778203987341" ID="ID_384285004" MODIFIED="1778204998986" TEXT="und zwar gibt es eine zus&#xe4;tzliche Komplikation durch das Thread-Local-Slice">
<richcontent TYPE="NOTE"><html>
@ -146725,8 +146728,8 @@ StM_bind(Builder&lt;R1&gt; b1, Extension&lt;R1,R2&gt; extension)
</p>
</body>
</html></richcontent>
<linktarget COLOR="#b9104e" DESTINATION="ID_985414238" ENDARROW="Default" ENDINCLINATION="653;64;" ID="Arrow_ID_649016741" SOURCE="ID_398699629" STARTARROW="Default" STARTINCLINATION="646;0;"/>
<linktarget COLOR="#fe590e" DESTINATION="ID_985414238" ENDARROW="Default" ENDINCLINATION="0;-19;" ID="Arrow_ID_928400072" SOURCE="ID_1297893109" STARTARROW="None" STARTINCLINATION="64;21;"/>
<linktarget COLOR="#b9104e" DESTINATION="ID_985414238" ENDARROW="Default" ENDINCLINATION="653;64;" ID="Arrow_ID_649016741" SOURCE="ID_398699629" STARTARROW="Default" STARTINCLINATION="646;0;"/>
<icon BUILTIN="broken-line"/>
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1778684714648" HGAP="44" ID="ID_314091864" MODIFIED="1778692175930" TEXT="genaue Anforderungs-Analyse" VSHIFT="2">
<linktarget COLOR="#dd0609" DESTINATION="ID_314091864" ENDARROW="Default" ENDINCLINATION="21;-45;" ID="Arrow_ID_425697321" SOURCE="ID_494756239" STARTARROW="None" STARTINCLINATION="-13;40;"/>
@ -146938,8 +146941,7 @@ StM_bind(Builder&lt;R1&gt; b1, Extension&lt;R1,R2&gt; extension)
im Grunde m&#252;&#223;te man nur den Zugang zum Memory-Manager selber locken, denn die ganzen Queue-Operationen sind ja schon lock-free und threadsafe; auch k&#246;nnten diese Locks dann non-recursive sein
</p>
</body>
</html>
</richcontent>
</html></richcontent>
</node>
<node CREATED="1778630869382" ID="ID_1688710426" MODIFIED="1778631446175" TEXT="m&#xf6;glichst wenig Locks">
<richcontent TYPE="NOTE"><html>
@ -146949,8 +146951,7 @@ StM_bind(Builder&lt;R1&gt; b1, Extension&lt;R1,R2&gt; extension)
...andererseits ist jeder (Mutex)-Lock ein Kernel-Aufruf, und wir erwarten durchaus ganze B&#252;ndel an Requests. Au&#223;erdem wird mit wenigen Locks auf API-Level viel klarer, da&#223; das Locking vollst&#228;ndig, und der Zustand konsistent ist.
</p>
</body>
</html>
</richcontent>
</html></richcontent>
</node>
</node>
<node BACKGROUND_COLOR="#ccb59b" COLOR="#6e2a38" CREATED="1778631447430" ID="ID_1196517656" MODIFIED="1778631809891" TEXT="entscheide mich (nach Bauchgef&#xfc;hl) f&#xfc;r &#xbb;wenig Locks&#xab;">
@ -146967,16 +146968,15 @@ StM_bind(Builder&lt;R1&gt; b1, Extension&lt;R1,R2&gt; extension)
Den Ausschlag gegeben hat aber eine andere Hinsicht: Klarheit und Wartbarkeit sind wichtiger, spezielle Performance-Tweaks sollte man empirisch beweisen.
</p>
</body>
</html>
</richcontent>
</html></richcontent>
<font ITALIC="true" NAME="SansSerif" SIZE="14"/>
<icon BUILTIN="yes"/>
</node>
<node CREATED="1778631791231" ID="ID_118677112" MODIFIED="1778631843980" TEXT="in #1429 notiert"/>
</node>
</node>
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1777324560690" ID="ID_318881353" MODIFIED="1778507667074" TEXT="Rahmen per Test abstecken">
<icon BUILTIN="pencil"/>
<node COLOR="#338800" CREATED="1777324560690" ID="ID_318881353" MODIFIED="1778788472215" TEXT="Rahmen per Test abstecken">
<icon BUILTIN="yes"/>
<node COLOR="#9b0b61" CREATED="1778591274938" ID="ID_540360328" MODIFIED="1778591296515" TEXT="EngineBufferManager_test">
<icon BUILTIN="info"/>
</node>
@ -147021,8 +147021,7 @@ StM_bind(Builder&lt;R1&gt; b1, Extension&lt;R1,R2&gt; extension)
<node CREATED="1778635075937" ID="ID_1005055901" MODIFIED="1778635080660" TEXT="alles wieder freigeben"/>
<node CREATED="1778635083791" ID="ID_227876159" MODIFIED="1778635088455" TEXT="Diagnostik pr&#xfc;fen"/>
</node>
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1778599975152" ID="ID_1790536650" MODIFIED="1778641424697" TEXT="verify_asyncRequest">
<icon BUILTIN="pencil"/>
<node COLOR="#338800" CREATED="1778599975152" ID="ID_1790536650" MODIFIED="1778788462054" TEXT="verify_asyncRequest">
<node CREATED="1778641426736" ID="ID_460139955" MODIFIED="1778641441187" TEXT="sollte hier gleich etwas &#xbb;massives&#xab; machen"/>
<node CREATED="1778641442270" ID="ID_1826595532" MODIFIED="1778641456169" TEXT="f&#xfc;hre eine typische usage-sequence in jedem Thread aus">
<node CREATED="1778641462236" ID="ID_51963277" MODIFIED="1778641469666" TEXT="fordere eine Allokation"/>
@ -147035,16 +147034,16 @@ StM_bind(Builder&lt;R1&gt; b1, Extension&lt;R1,R2&gt; extension)
<node CREATED="1778641528055" ID="ID_572369413" MODIFIED="1778641532894" TEXT="&#x2014; delay &#x2014;"/>
<node CREATED="1778641533746" ID="ID_1255059129" MODIFIED="1778641540762" TEXT="schicke die Allokation zur&#xfc;ck"/>
</node>
<node CREATED="1778641582324" ID="ID_723485846" MODIFIED="1778641604860" TEXT="danach sollten....">
<node BACKGROUND_COLOR="#c8c0b6" COLOR="#435e98" CREATED="1778641582324" ID="ID_723485846" MODIFIED="1778788451349" TEXT="danach sollten....">
<node CREATED="1778641588959" ID="ID_857089658" MODIFIED="1778641600492" TEXT="NUM_THREADS Allokationen erfolgt sein"/>
<node CREATED="1778641607696" ID="ID_514432880" MODIFIED="1778641618195" TEXT="alle diese Allokationen wieder in der Queue sitzen"/>
<node COLOR="#435e98" CREATED="1778642703060" ID="ID_378615107" MODIFIED="1778694539272" TEXT="funktioniert nicht...">
<node COLOR="#435e98" CREATED="1778642703060" FOLDED="true" ID="ID_378615107" MODIFIED="1778788384414" TEXT="funktioniert nicht...">
<icon BUILTIN="broken-line"/>
<node CREATED="1778642715157" ID="ID_723545359" MODIFIED="1778694786375" TEXT="Beobachtungen">
<icon BUILTIN="info"/>
<node CREATED="1778642721100" ID="ID_454509509" MODIFIED="1778642737405" TEXT="einige Threads sind anscheinend schon fertig bevor andere &#xfc;berhaupt erst anfangen"/>
<node CREATED="1778642739266" ID="ID_1618120303" MODIFIED="1778642753195" TEXT="damit werden nat&#xfc;rlich einige Allokationen bereits wegger&#xe4;umt"/>
<node BACKGROUND_COLOR="#f0d5c5" COLOR="#990033" CREATED="1778642777488" ID="ID_735364209" MODIFIED="1778642799183" TEXT="unklar: warum sind nach processPending() noch Allocs offen?">
<node BACKGROUND_COLOR="#d3a387" COLOR="#990033" CREATED="1778642777488" ID="ID_735364209" MODIFIED="1778788369851" TEXT="unklar: warum sind nach processPending() noch Allocs offen?">
<icon BUILTIN="help"/>
</node>
</node>
@ -147063,8 +147062,7 @@ StM_bind(Builder&lt;R1&gt; b1, Extension&lt;R1,R2&gt; extension)
Jedenfalls in dem Setup, das ich jetzt hier f&#252;r diesen neuen Test gebaut habe, verwenden alle Threads dasselbe Lamba f&#252;r die Thread-Funktion <i>gemeinsam</i>. Das bedeutet, das makeRandomGen() wird tats&#228;chlich nur einmal aufgerufen, und daher haben wir nur einen einzigen Seed (selbst wenn die Lambdas dann im Thread-Wrapper nochmal kopiert werden)
</p>
</body>
</html>
</richcontent>
</html></richcontent>
<icon BUILTIN="messagebox_warning"/>
<node CREATED="1778643262156" ID="ID_46315611" MODIFIED="1778643274742" TEXT="sehe in allen Threads exakt den gleichen Delay"/>
<node CREATED="1778643275676" ID="ID_284051539" MODIFIED="1778643284600" TEXT="daher wachen die auch alle gemeinsam auf"/>
@ -147090,13 +147088,16 @@ StM_bind(Builder&lt;R1&gt; b1, Extension&lt;R1,R2&gt; extension)
</node>
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1778681131699" ID="ID_725298648" MODIFIED="1778681149966" TEXT="nebenbei bemerkt: das API f&#xfc;r ScopedCollection ist unvollst&#xe4;ndig">
<icon BUILTIN="broken-line"/>
<node CREATED="1778681154739" ID="ID_1527328731" MODIFIED="1778681164103" TEXT="das Erzeugen der Instanz ist zu umst&#xe4;ndlich"/>
<node CREATED="1778681181873" ID="ID_262040421" MODIFIED="1778709901385" TEXT="es fehlt das Hinterlegen fester Argumente">
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1778681154739" ID="ID_1527328731" MODIFIED="1778783685746" TEXT="das Erzeugen der Instanz ist zu umst&#xe4;ndlich">
<icon BUILTIN="messagebox_warning"/>
</node>
<node COLOR="#338800" CREATED="1778681181873" ID="ID_262040421" MODIFIED="1778783692002" TEXT="es fehlt das Hinterlegen fester Argumente">
<icon BUILTIN="button_ok"/>
<node CREATED="1778709995012" ID="ID_1485556701" MODIFIED="1778710018906" TEXT="w&#xe4;re m&#xf6;glich durch Erweiterung von FillWith">
<node CREATED="1778710037003" ID="ID_730979662" MODIFIED="1778710085583" TEXT="m&#xfc;&#xdf;te dazu ein Argument-Tupel materialisieren"/>
<node CREATED="1778710769213" ID="ID_410726998" MODIFIED="1778710789818" TEXT="bisher wird &lt;tuple&gt; nicht includiert"/>
</node>
<node CREATED="1778711002374" ID="ID_839332664" MODIFIED="1778711024630" TEXT="k&#xf6;nnte man auch als &#x3bb;-builder neu formulieren">
<node COLOR="#435e98" CREATED="1778711002374" ID="ID_839332664" MODIFIED="1778783672948" TEXT="k&#xf6;nnte man auch als &#x3bb;-builder neu formulieren">
<node CREATED="1778711483235" ID="ID_1590205155" LINK="https://stackoverflow.com/a/49902823/444796" MODIFIED="1778711541326" TEXT="in C++20 gibt es perfect &#x3bb;-capture"/>
<node CREATED="1778711904212" ID="ID_1775952563" LINK="https://en.cppreference.com/cpp/language/lambda#Lambda_capture" MODIFIED="1778711918406" TEXT="ist m&#xf6;glich sowohl f&#xfc;r Referenz, alsauch Copy-Capture"/>
</node>
@ -147104,14 +147105,51 @@ StM_bind(Builder&lt;R1&gt; b1, Extension&lt;R1,R2&gt; extension)
<icon BUILTIN="idea"/>
</node>
</node>
<node CREATED="1778681164947" ID="ID_281208279" MODIFIED="1778681175454" TEXT="es fehlt das Aufrufen eines Lambda"/>
<node CREATED="1778727780762" ID="ID_1017169041" MODIFIED="1778727790117" TEXT="Iterator-pull ist limitiert">
<node COLOR="#338800" CREATED="1778681164947" ID="ID_281208279" MODIFIED="1778783693544" TEXT="es fehlt das Aufrufen eines Lambda">
<icon BUILTIN="button_ok"/>
</node>
<node COLOR="#338800" CREATED="1778727780762" ID="ID_1017169041" MODIFIED="1778783695258" TEXT="Iterator-pull ist limitiert">
<icon BUILTIN="button_ok"/>
<node CREATED="1778727795148" ID="ID_699461971" MODIFIED="1778727812229" TEXT="k&#xf6;nnte sowohl copy-Konstruktion, alsauch Value-Konstruktion machen"/>
<node CREATED="1778727813246" ID="ID_791306906" MODIFIED="1778727831783" TEXT="sollte mit dem Populieren aufh&#xf6;ren, wenn der Iterator ersch&#xf6;pft ist"/>
<node COLOR="#435e98" CREATED="1778727839622" ID="ID_576015934" MODIFIED="1778727896555" TEXT="Mit einem Concept k&#xf6;nnte das direkt eine Konstruktor-Variante sein">
<icon BUILTIN="idea"/>
</node>
</node>
<node COLOR="#435e98" CREATED="1778783706927" ID="ID_909107103" MODIFIED="1778788348871" TEXT="Formulierung diverser Tests nun stark vereinfacht">
<icon BUILTIN="idea"/>
<node CREATED="1778783718253" ID="ID_271640023" MODIFIED="1778783732071" TEXT="speziell die F&#xe4;lle wo wir einen Satz an Threads starten"/>
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1778788061645" ID="ID_92554859" MODIFIED="1778788084275" TEXT="aber Vorsicht &#x2014; nicht immer m&#xf6;glich">
<icon BUILTIN="messagebox_warning"/>
<node CREATED="1778788087357" ID="ID_502254218" MODIFIED="1778788224843" TEXT="manchmal m&#xfc;ssen Referenzen an den Konstruktor &#xfc;bergeben werden">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<p>
...und das geht nicht mit dem fill()-Populator, denn dieser materialisiert alle Argumente als Kopien. Man k&#246;nnte das &#228;ndern, m&#252;&#223;te dann aber explizit ein perfect-forwarding-Tupel bauen. Das war mir jetzt alles zu m&#252;hsam
</p>
</body>
</html>
</richcontent>
</node>
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#b6020c" CREATED="1778788232367" ID="ID_134775139" MODIFIED="1778788330594" TEXT="manchmal ist es essentiell, f&#xfc;r jeden Thread ein separates Lambda zu konstruieren">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<p>
das ist im Besonderen relevant, wenn die captures nicht-triviale Operationen in der Initialisierung verwenden. Da der fill()-Popoulator kopiert, w&#252;rden sich alle Threads das gleiche Lambda teilen, und daher auf den gleichen Werten arbeiten. Das ist <b>eine subtile Falle</b>
</p>
</body>
</html>
</richcontent>
<icon BUILTIN="clanbomber"/>
</node>
</node>
</node>
</node>
</node>
<node COLOR="#435e98" CREATED="1778680084927" ID="ID_831309457" MODIFIED="1778694776376" TEXT="Anpassungen">
@ -147282,9 +147320,10 @@ StM_bind(Builder&lt;R1&gt; b1, Extension&lt;R1,R2&gt; extension)
<icon BUILTIN="button_ok"/>
</node>
</node>
<node CREATED="1778695283214" ID="ID_978534045" MODIFIED="1778695295656" TEXT="Vorsicht: Testsuite ist auf 8MiB limitiert">
<node CREATED="1778695297087" ID="ID_1119237552" MODIFIED="1778695308343" TEXT="dieser test alloziert &#xbb;mal eben&#xab; 100 MiB"/>
<node CREATED="1778695331666" ID="ID_1196538153" MODIFIED="1778695331666" TEXT=""/>
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1778695283214" ID="ID_978534045" MODIFIED="1778788447228" TEXT="Vorsicht: dieser test alloziert &#xbb;mal eben&#xab; 100 MiB">
<icon BUILTIN="messagebox_warning"/>
<node CREATED="1778695297087" ID="ID_1119237552" MODIFIED="1778788425737" TEXT="kein Problem ... die Testsuite ist auf 8 GiB limitiert"/>
<node CREATED="1778695331666" ID="ID_1196538153" MODIFIED="1778788441455" TEXT="ein moderner PC steckt sowas locker weg"/>
</node>
</node>
</node>
@ -172016,8 +172055,7 @@ std::cout &lt;&lt; tmpl.render({&quot;what&quot;, &quot;World&quot;}) &lt;&lt; s
...n&#228;mlich indem der Job letztlich auf den synchronen Fall zur&#252;ckgreift, dieser aber durch die gleiche Mechanik implementiert ist, wie der asynchrone Fall, und dabei gleich alle aufgesammelten Requests in den Queues mit verarbeitet. Dadruch wird effektiv ein Batch an Allokator-Aufwand in denjenigen Worker geschoben, der rein zuf&#228;llig als erster auf eine nicht bediente Allokation st&#246;&#223;t. Das Sch&#246;ne an dieser Code-Struktur ist, da&#223; die M&#246;glichkeit offen bleibt, sp&#228;ter zur Performance-Optimierung (oder nur in bestimmten Situationen) dennoch etwas speziell einzurichten / schedulen / auszuf&#252;hren, ohne da&#223; dazu der Code im EngineBufferManager ge&#228;ndert werden mu&#223;
</p>
</body>
</html>
</richcontent>
</html></richcontent>
<arrowlink COLOR="#cffd68" DESTINATION="ID_1723782536" ENDARROW="Default" ENDINCLINATION="2031;68;" ID="Arrow_ID_1674470149" STARTARROW="None" STARTINCLINATION="1004;42;"/>
<font NAME="SansSerif" SIZE="11"/>
<icon BUILTIN="idea"/>