Chain-Load: now able to define RandomDraw rules
...all existing tests reproduced
...yet notation is hopefully more readable
Old:
graph.expansionRule([](size_t h,double){ return Cap{8, h%16, 63}; })
New:
graph.expansionRule(graph.rule().probability(0.5).maxVal(4))
This commit is contained in:
parent
f1c156b4cd
commit
dbe71029b7
7 changed files with 144 additions and 106 deletions
|
|
@ -143,6 +143,12 @@ namespace meta{
|
|||
: _Fun<SIG>
|
||||
{ };
|
||||
|
||||
/** Specialisation to strip spurious const for type analysis */
|
||||
template<typename SIG>
|
||||
struct _Fun<SIG const&>
|
||||
: _Fun<SIG>
|
||||
{ };
|
||||
|
||||
/** Specialisation when using a function reference */
|
||||
template<typename SIG>
|
||||
struct _Fun<SIG&>
|
||||
|
|
|
|||
|
|
@ -135,6 +135,12 @@ namespace meta {
|
|||
|
||||
|
||||
|
||||
/** helper to prevent a template constructor from shadowing inherited copy ctors */
|
||||
template<typename ARG, class SELF>
|
||||
using disable_if_self = disable_if<std::is_same<std::remove_reference_t<ARG>, SELF>>;
|
||||
|
||||
|
||||
|
||||
/** detect possibility of a conversion to string.
|
||||
* Naive implementation, which first attempts to build a string instance by
|
||||
* implicit conversion, and then tries to invoke an explicit string conversion.
|
||||
|
|
|
|||
|
|
@ -69,9 +69,23 @@
|
|||
** specialisations for various function signatures. These adaptors are used to
|
||||
** conform any mapping function and thus allow to simplify or widen the
|
||||
** possible configurations at usage site.
|
||||
**
|
||||
** ## Copy inhibition
|
||||
** The configuration of the RandomDraw processing pipeline makes heavy use of function composition
|
||||
** and adaptation to handle a wide selection of input types and usage patterns. Unfortunately this
|
||||
** requires to like the generated configuration-λ to the object instance (capturing by reference);
|
||||
** not allowing this would severely limit the possible configurations. This implies that an object
|
||||
** instance must not be moved anymore, once the processing pipeline has been configured. And this
|
||||
** in turn would severely limit it's usage in a DSL. As a compromise, RandomDraw relies on
|
||||
** [lazy on-demand initialisation](\ref lazy-init.hpp): as long as the processing function has
|
||||
** not been invoked, the internal pipeline is unconfigured, and the object can be moved and copied.
|
||||
** Once invoked, the prepared configuration is assembled and the function »engaged«; from this point
|
||||
** on, any attempt to move or copy the object will throw an exception, while it is still possible
|
||||
** to assign other RandomDraw instances to this object.
|
||||
** @todo 11/2023 This is a first draft and was extracted from an actual usage scenario.
|
||||
** It remains to be seen if the scheme as defined is of any further use henceforth.
|
||||
** @see RandomDraw_test
|
||||
** @see lazy-init.hpp
|
||||
** @see TestChainLoad_test
|
||||
** @see SchedulerStress_test
|
||||
*/
|
||||
|
|
@ -82,7 +96,7 @@
|
|||
|
||||
|
||||
#include "lib/error.h"
|
||||
#include "lib/nocopy.hpp"
|
||||
#include "lib/lazy-init.hpp"
|
||||
#include "lib/meta/function.hpp"
|
||||
#include "lib/meta/function-closure.hpp"
|
||||
#include "lib/util-quant.hpp"
|
||||
|
|
@ -95,6 +109,7 @@
|
|||
namespace lib {
|
||||
namespace err = lumiera::error;
|
||||
|
||||
using lib::meta::disable_if_self;
|
||||
using lib::meta::_Fun;
|
||||
using std::function;
|
||||
using std::forward;
|
||||
|
|
@ -165,9 +180,11 @@ namespace lib {
|
|||
*/
|
||||
template<class POL>
|
||||
class RandomDraw
|
||||
: public POL
|
||||
, util::MoveOnly
|
||||
: public LazyInit<POL>
|
||||
{
|
||||
using Lazy = LazyInit<POL>;
|
||||
using Disabled = typename Lazy::MarkDisabled;
|
||||
|
||||
using Sig = typename _Fun<POL>::Sig;
|
||||
using Fun = function<Sig>;
|
||||
using Tar = typename _Fun<POL>::Ret;
|
||||
|
|
@ -176,11 +193,6 @@ namespace lib {
|
|||
Tar minResult_{Tar::minVal()}; ///< minimum result val actually to produce > min
|
||||
double probability_{0}; ///< probability that value is in [min .. max] \ neutral
|
||||
|
||||
/** due to function binding, this object
|
||||
* should actually be non-copyable,
|
||||
* which would defeat DLS use */
|
||||
bool fixedLocation_{false};
|
||||
|
||||
|
||||
/** @internal quantise into limited result value */
|
||||
Tar
|
||||
|
|
@ -253,8 +265,10 @@ namespace lib {
|
|||
* Drawing is _disabled_ by default, always yielding "zero"
|
||||
*/
|
||||
RandomDraw()
|
||||
: POL{adaptOut(POL::defaultSrc)}
|
||||
{ }
|
||||
: Lazy{Disabled()}
|
||||
{
|
||||
mapping (POL::defaultSrc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a RandomDraw by attaching a value-processing function,
|
||||
|
|
@ -265,32 +279,14 @@ namespace lib {
|
|||
* - `void(RandomDraw&, ...)` : the function manipulates the current
|
||||
* instance, to control parameters dynamically, based on input.
|
||||
*/
|
||||
template<class FUN>
|
||||
template<class FUN, typename =disable_if_self<FUN, RandomDraw>>
|
||||
RandomDraw(FUN&& fun)
|
||||
: POL{}
|
||||
: Lazy{Disabled()}
|
||||
, probability_{1.0}
|
||||
, fixedLocation_{true} // prevent move-copy (function binds to *this)
|
||||
{
|
||||
mapping (forward<FUN> (fun));
|
||||
}
|
||||
|
||||
/**
|
||||
* Object can be move-initialised, unless
|
||||
* a custom processing function was installed.
|
||||
*/
|
||||
RandomDraw(RandomDraw && rr)
|
||||
: POL{adaptOut(POL::defaultSrc)}
|
||||
, maxResult_{move(rr.maxResult_)}
|
||||
, minResult_{move(rr.minResult_)}
|
||||
, probability_{rr.probability_}
|
||||
, fixedLocation_{rr.fixedLocation_}
|
||||
{
|
||||
if (fixedLocation_)
|
||||
throw err::State{"RandomDraw was already configured with a processing function, "
|
||||
"which binds into a fixed object location. It can not be moved anymore."
|
||||
, err::LUMIERA_ERROR_LIFECYCLE};
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* ===== Builder API ===== */
|
||||
|
|
@ -325,8 +321,12 @@ namespace lib {
|
|||
mapping (FUN&& fun)
|
||||
{
|
||||
Fun& thisMapping = static_cast<Fun&> (*this);
|
||||
thisMapping = adaptOut(adaptIn(std::forward<FUN> (fun)));
|
||||
fixedLocation_ = true; // function will bind to *this
|
||||
Lazy::installInitialiser (thisMapping
|
||||
,[theFun = forward<FUN> (fun)]
|
||||
(RandomDraw* self)
|
||||
{ // when lazy init is performed....
|
||||
self->installAdapted (theFun);
|
||||
});
|
||||
return move (*this);
|
||||
}
|
||||
|
||||
|
|
@ -345,6 +345,16 @@ namespace lib {
|
|||
|
||||
|
||||
|
||||
/** @internal adapt a function and install it to control drawing and mapping */
|
||||
template<class FUN>
|
||||
void
|
||||
installAdapted (FUN&& fun)
|
||||
{
|
||||
Fun& thisMapping = static_cast<Fun&> (*this);
|
||||
thisMapping = adaptOut(adaptIn(std::forward<FUN> (fun)));
|
||||
}
|
||||
|
||||
|
||||
/** @internal adapt input side of a given function to conform to the
|
||||
* global input arguments as defined in the Policy base function.
|
||||
* @return a function pre-fitted with a suitable Adapter from the Policy */
|
||||
|
|
@ -359,7 +369,6 @@ namespace lib {
|
|||
using Sig = typename _Fun::Sig;
|
||||
using Args = typename _Fun::Args;
|
||||
using BaseIn = typename lib::meta::_Fun<POL>::Args;
|
||||
using Adaptor = typename POL::template Adaptor<Sig>;
|
||||
|
||||
if constexpr (std::is_same_v<Args, BaseIn>)
|
||||
// function accepts same arguments as this RandomDraw
|
||||
|
|
@ -369,7 +378,10 @@ namespace lib {
|
|||
// function wants to manipulate *this dynamically...
|
||||
return adaptIn (applyFirst (forward<FUN> (fun), *this));
|
||||
else
|
||||
return Adaptor::build (forward<FUN> (fun));
|
||||
{// attempt to find a custom adaptor via Policy template
|
||||
using Adaptor = typename POL::template Adaptor<Sig>;
|
||||
return Adaptor::build (forward<FUN> (fun));
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal adapt output side of a given function, allowing to handle it's results
|
||||
|
|
@ -429,7 +441,7 @@ namespace lib {
|
|||
getCurrMapping()
|
||||
{
|
||||
Fun& currentProcessingFunction = *this;
|
||||
if (fixedLocation_ and currentProcessingFunction)
|
||||
if (currentProcessingFunction)
|
||||
return Fun{currentProcessingFunction};
|
||||
else
|
||||
return Fun{adaptOut(POL::defaultSrc)};
|
||||
|
|
|
|||
|
|
@ -41,11 +41,8 @@ namespace test{
|
|||
|
||||
using util::isSameObject;
|
||||
using lib::meta::isFunMember;
|
||||
using lib::meta::disable_if;
|
||||
using lib::meta::disable_if_self;
|
||||
using err::LUMIERA_ERROR_LIFECYCLE;
|
||||
|
||||
using std::is_same;
|
||||
using std::remove_reference_t;
|
||||
using std::make_unique;
|
||||
|
||||
|
||||
|
|
@ -360,7 +357,7 @@ namespace test{
|
|||
installInitialiser(fun, buildInit([](int){ return 0; }));
|
||||
}
|
||||
// prevent this ctor from shadowing the copy ctors //////TICKET #963
|
||||
template<typename FUN, typename =disable_if<is_same<remove_reference_t<FUN>, LazyDemo>>>
|
||||
template<typename FUN, typename =disable_if_self<FUN, LazyDemo>>
|
||||
LazyDemo (FUN&& someFun)
|
||||
: LazyInit{MarkDisabled()}
|
||||
, fun{}
|
||||
|
|
|
|||
|
|
@ -222,7 +222,7 @@ namespace test {
|
|||
{
|
||||
auto graph = TestChainLoad<32>{};
|
||||
|
||||
graph.expansionRule([](size_t h,double){ return Cap{8, h%16, 63}; })
|
||||
graph.expansionRule(graph.rule().probability(0.5).maxVal(4))
|
||||
.buildToplolgy()
|
||||
.printTopologyDOT();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,7 +49,10 @@
|
|||
** predecessor nodes; additionally, new chains can be spawned (to simulate the effect of
|
||||
** data loading Jobs without predecessor). The computation always begins with the _root
|
||||
** node_, proceeds over the node links and finally leads to the _top node,_ which connects
|
||||
** all chains of computation, leaving no dead end.
|
||||
** all chains of computation, leaving no dead end. The probabilistic rules controlling the
|
||||
** topology can be configured using the lib::RandomDraw component, allowing either just
|
||||
** to set a fixed probability or to define elaborate dynamic configurations based on the
|
||||
** graph height or node connectivity properties.
|
||||
**
|
||||
** ## Usage
|
||||
** A TestChainLoad instance is created with predetermined maximum fan factor and a fixed
|
||||
|
|
@ -68,6 +71,7 @@
|
|||
**
|
||||
** @see TestChainLoad_test
|
||||
** @see SchedulerStress_test
|
||||
** @see random-draw.hpp
|
||||
*/
|
||||
|
||||
|
||||
|
|
@ -131,49 +135,14 @@ namespace test {
|
|||
namespace { // Default definitions for topology generation
|
||||
const size_t DEFAULT_FAN = 16;
|
||||
const size_t DEFAULT_SIZ = 256;
|
||||
|
||||
const double CAP_EPSILON = 0.001; ///< tiny bias to absorb rounding problems
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Helper to cap and map to a value range.
|
||||
*/
|
||||
struct Cap
|
||||
{
|
||||
double lower{0};
|
||||
double value{0};
|
||||
double upper{1};
|
||||
|
||||
Cap (int i) : value(i){ }
|
||||
Cap (size_t s) : value(s){ }
|
||||
Cap (double d) : value{d}{ }
|
||||
|
||||
template<typename NL, typename NV, typename NU>
|
||||
Cap (NL l, NV v, NU u)
|
||||
: lower(l)
|
||||
, value(v)
|
||||
, upper(u)
|
||||
{ }
|
||||
|
||||
size_t
|
||||
mapped (size_t scale)
|
||||
{
|
||||
if (value==lower)
|
||||
return 0;
|
||||
value -= lower;
|
||||
value /= upper-lower;
|
||||
value *= scale;
|
||||
value += CAP_EPSILON;
|
||||
value = limited (size_t(0), value, scale);
|
||||
return size_t(value);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
/***********************************************************************//**
|
||||
* A Generator for synthetic Render Jobs for Scheduler load testing.
|
||||
* Allocates a fixed set of #numNodes and generates connecting toplology.
|
||||
* Allocates a fixed set of #numNodes and generates connecting topology.
|
||||
* @tparam maxFan maximal fan-in/out from a node, also limits maximal parallel strands.
|
||||
* @see TestChainLoad_test
|
||||
*/
|
||||
|
|
@ -282,13 +251,12 @@ namespace test {
|
|||
private:
|
||||
using NodeTab = typename Node::Tab;
|
||||
using NodeStorage = std::array<Node, numNodes>;
|
||||
using CtrlRule = std::function<Cap(size_t, double)>;
|
||||
|
||||
std::unique_ptr<NodeStorage> nodes_;
|
||||
|
||||
CtrlRule seedingRule_ {[](size_t, double){ return 0; }};
|
||||
CtrlRule expansionRule_{[](size_t, double){ return 0; }};
|
||||
CtrlRule reductionRule_{[](size_t, double){ return 0; }};
|
||||
Rule seedingRule_ {};
|
||||
Rule expansionRule_{};
|
||||
Rule reductionRule_{};
|
||||
|
||||
public:
|
||||
TestChainLoad()
|
||||
|
|
@ -313,24 +281,26 @@ namespace test {
|
|||
|
||||
/* ===== topology control ===== */
|
||||
|
||||
static Rule rule() { return Rule(); }
|
||||
|
||||
TestChainLoad&&
|
||||
seedingRule (CtrlRule r)
|
||||
seedingRule (Rule&& r)
|
||||
{
|
||||
seedingRule_ = r;
|
||||
seedingRule_ = move(r);
|
||||
return move(*this);
|
||||
}
|
||||
|
||||
TestChainLoad&&
|
||||
expansionRule (CtrlRule r)
|
||||
expansionRule (Rule&& r)
|
||||
{
|
||||
expansionRule_ = r;
|
||||
expansionRule_ = move(r);
|
||||
return move(*this);
|
||||
}
|
||||
|
||||
TestChainLoad&&
|
||||
reductionRule (CtrlRule r)
|
||||
reductionRule (Rule&& r)
|
||||
{
|
||||
reductionRule_ = r;
|
||||
reductionRule_ = move(r);
|
||||
return move(*this);
|
||||
}
|
||||
|
||||
|
|
@ -346,7 +316,11 @@ namespace test {
|
|||
*next{&b}; // the next set of nodes connected to current
|
||||
Node* node = &nodes_->front();
|
||||
size_t level{0};
|
||||
size_t expectedLevel = max (1u, numNodes/maxFan); // guess, typically too low
|
||||
|
||||
// local copy of all rules (they are non-copyable, once engaged)
|
||||
Rule expansionRule = expansionRule_;
|
||||
Rule reductionRule = reductionRule_;
|
||||
Rule seedingRule = seedingRule_;
|
||||
|
||||
// prepare building blocks for the topology generation...
|
||||
auto moreNext = [&]{ return next->size() < maxFan; };
|
||||
|
|
@ -358,14 +332,9 @@ namespace test {
|
|||
n->level = level;
|
||||
return n;
|
||||
};
|
||||
auto height = [&](double level)
|
||||
auto apply = [&](Rule& rule, Node* n)
|
||||
{
|
||||
return level/expectedLevel;
|
||||
};
|
||||
auto apply = [&](CtrlRule& rule, Node* n)
|
||||
{
|
||||
Cap param = rule (n->hash, height(level));
|
||||
return param.mapped (maxFan);
|
||||
return rule(n);
|
||||
};
|
||||
|
||||
addNode(); // prime next with root node
|
||||
|
|
@ -381,8 +350,8 @@ namespace test {
|
|||
for (Node* o : *curr)
|
||||
{ // follow-up on all Nodes in current level...
|
||||
o->calculate();
|
||||
size_t toSeed = apply (seedingRule_, o);
|
||||
size_t toExpand = apply (expansionRule_,o);
|
||||
size_t toSeed = apply (seedingRule, o);
|
||||
size_t toExpand = apply (expansionRule,o);
|
||||
while (0 < toSeed and spaceLeft())
|
||||
{ // start a new chain from seed
|
||||
Node* n = addNode();
|
||||
|
|
@ -398,7 +367,7 @@ namespace test {
|
|||
if (not toReduce)
|
||||
{ // carry-on chain from o
|
||||
r = spaceLeft()? addNode():nullptr;
|
||||
toReduce = apply (reductionRule_, o);
|
||||
toReduce = apply (reductionRule, o);
|
||||
}
|
||||
else
|
||||
--toReduce;
|
||||
|
|
@ -503,8 +472,9 @@ namespace test {
|
|||
*/
|
||||
template<size_t numNodes, size_t maxFan>
|
||||
class TestChainLoad<numNodes,maxFan>::NodeControlBinding
|
||||
: protected std::function<Param(Node*)>
|
||||
: public std::function<Param(Node*)>
|
||||
{
|
||||
protected:
|
||||
/** by default use Node-hash directly as source of randomness */
|
||||
static size_t
|
||||
defaultSrc (Node* node)
|
||||
|
|
|
|||
|
|
@ -96262,9 +96262,9 @@ Date:   Thu Apr 20 18:53:17 2023 +0200<br/>
|
|||
<icon BUILTIN="stop-sign"/>
|
||||
</node>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1700240155314" ID="ID_6655140" MODIFIED="1700243338694" TEXT="Hilfsmittel: Regel-Builder">
|
||||
<node BACKGROUND_COLOR="#c8c0b6" COLOR="#435e98" CREATED="1700240155314" ID="ID_6655140" MODIFIED="1700969828908" TEXT="Hilfsmittel: Regel-Builder">
|
||||
<linktarget COLOR="#fef7b5" DESTINATION="ID_6655140" ENDARROW="Default" ENDINCLINATION="159;-197;" ID="Arrow_ID_1782877370" SOURCE="ID_697379621" STARTARROW="None" STARTINCLINATION="-63;175;"/>
|
||||
<icon BUILTIN="flag-yellow"/>
|
||||
<icon BUILTIN="yes"/>
|
||||
<node COLOR="#338800" CREATED="1700264860430" ID="ID_911248195" MODIFIED="1700713935881" TEXT="nochmal umstülpen....">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
<node CREATED="1700264870441" ID="ID_1812170860" MODIFIED="1700491182299">
|
||||
|
|
@ -98410,8 +98410,8 @@ Date:   Thu Apr 20 18:53:17 2023 +0200<br/>
|
|||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1700713941245" ID="ID_1647717999" MODIFIED="1700713949917" TEXT="die neue Lib-Komponente einbauen">
|
||||
<icon BUILTIN="flag-yellow"/>
|
||||
<node COLOR="#338800" CREATED="1700713941245" ID="ID_1647717999" MODIFIED="1700969800622" TEXT="die neue Lib-Komponente einbauen">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
<node CREATED="1700763561056" ID="ID_1249462296" MODIFIED="1700764033885" TEXT="Bisherige Lösung mit Hilfsklasse Cap fällt weg">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head/>
|
||||
|
|
@ -98439,11 +98439,14 @@ Date:   Thu Apr 20 18:53:17 2023 +0200<br/>
|
|||
<node COLOR="#338800" CREATED="1700769068457" ID="ID_476049536" MODIFIED="1700770746672" TEXT="Typ für »config-rules« auf RandomDraw aufbauen">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node COLOR="#338800" CREATED="1700969876652" ID="ID_1123165498" MODIFIED="1700969885748" TEXT="Tests laufen wieder wie zuvor">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1700240183293" ID="ID_1092101612" MODIFIED="1700240190782" TEXT="Regel-Baukasten">
|
||||
<icon BUILTIN="flag-yellow"/>
|
||||
<node BACKGROUND_COLOR="#f8f1cb" COLOR="#a50125" CREATED="1700770889303" ID="ID_1475798694" MODIFIED="1700770937999" TEXT="Non-copyable Rules — problematisch für DSL">
|
||||
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1700770889303" ID="ID_1475798694" MODIFIED="1700969773151" TEXT="Non-copyable Rules — problematisch für DSL">
|
||||
<icon BUILTIN="messagebox_warning"/>
|
||||
<node COLOR="#5b280f" CREATED="1700770943791" ID="ID_1199767820" MODIFIED="1700770980394" TEXT="sie müssen non-copyable sein wegen internem Function-binding">
|
||||
<icon BUILTIN="broken-line"/>
|
||||
|
|
@ -98465,6 +98468,50 @@ Date:   Thu Apr 20 18:53:17 2023 +0200<br/>
|
|||
<icon BUILTIN="idea"/>
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node COLOR="#437298" CREATED="1700969491009" FOLDED="true" ID="ID_1984571390" MODIFIED="1700969900504">
|
||||
<richcontent TYPE="NODE"><html>
|
||||
<head/>
|
||||
<body>
|
||||
<p>
|
||||
löst das Problem <i>fast...</i>
|
||||
</p>
|
||||
</body>
|
||||
</html></richcontent>
|
||||
<icon BUILTIN="smiley-oh"/>
|
||||
<node CREATED="1700969510140" ID="ID_1447787379" MODIFIED="1700969626628">
|
||||
<richcontent TYPE="NODE"><html>
|
||||
<head/>
|
||||
<body>
|
||||
<p>
|
||||
nachdem man nämlich <font face="Monospaced">buildTopology()</font> aufgerufen hat,
|
||||
</p>
|
||||
<p>
|
||||
explodiert TestChainLoad am Ende der DSL-Methode.
|
||||
</p>
|
||||
<p>
|
||||
Denn da steht ganz verträumt...
|
||||
</p>
|
||||
<p>
|
||||
<font face="Monospaced" color="#a33434">return move(*this)</font>
|
||||
</p>
|
||||
</body>
|
||||
</html></richcontent>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#e6d5a6" COLOR="#fa002a" CREATED="1700969637165" ID="ID_899591341" MODIFIED="1700969754144" TEXT="irgendwie wollen die Windmühlen nicht recht sterben...">
|
||||
<icon BUILTIN="broken-line"/>
|
||||
</node>
|
||||
<node CREATED="1700969657586" ID="ID_347427762" MODIFIED="1700969694884">
|
||||
<richcontent TYPE="NODE"><html>
|
||||
<head/>
|
||||
<body>
|
||||
<p>
|
||||
nun gut: dann verwendet buildTopology() eben <b>lokale Kopien</b>
|
||||
</p>
|
||||
</body>
|
||||
</html></richcontent>
|
||||
</node>
|
||||
<node CREATED="1700969673859" ID="ID_1388522888" MODIFIED="1700969690306" TEXT="...da RandomDraw jetzt so wunderbar elegant kopierbar ist..."/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
|
|
|
|||
Loading…
Reference in a new issue