diff --git a/src/lib/random.hpp b/src/lib/random.hpp index 32998df69..3a62e66bb 100644 --- a/src/lib/random.hpp +++ b/src/lib/random.hpp @@ -45,6 +45,13 @@ namespace lib { + namespace { + inline uint constexpr + _iBOUND() + { + return 1u+uint(std::numeric_limits::max()); + } + } /** Establishes a seed point for any instance or performance. */ class SeedNucleus @@ -64,26 +71,31 @@ namespace lib { */ template class RandomSequencer - : util::NonCopyable { - std::uniform_int_distribution uniformI_; - std::uniform_int_distribution uniformU_; - std::uniform_real_distribution uniformD_; - GEN generator_; public: - /** Random instances are created as part of an execution scheme */ - RandomSequencer(SeedNucleus&); + /** Build new generator, drawing seed from a virtual seed source */ + RandomSequencer (SeedNucleus&); - int i32() { return uniformI_(generator_); } - uint64_t u64() { return uniformU_(generator_); } - double uni() { return uniformD_(generator_); } + /** Build new generator, drawing a seed from given parent generator */ + template + RandomSequencer (RandomSequencer&); - friend int rani(uint); - friend double ranRange(double,double); - friend double ranNormal(double,double); - friend HashVal ranHash(); + // default copy operations (can copy and assign a state) + + + int i(uint bound =_iBOUND()); ///< drop-in replacement for `rand() % bound` + int i32(); ///< random number from _full integer range_ (incl. negative values) + uint64_t u64(); ///< random 64bit number from full range. + double uni(); ///< random double drawn from interval `[0.0 ... 1.0[` + double range (double start, double bound); ///< random double from designated interval (upper bound excluded) + double normal(double mean=0.0, double stdev=1.0); ///< normal distribution (gaussian) + HashVal hash(); ///< _non-zero_ hash value from full 64bit range + + /** generic adapter: draw next number to use the given distribution */ + template + auto distribute(DIST); /** inject controlled randomisation */ void reseed (SeedNucleus&); @@ -110,8 +122,9 @@ namespace lib { }; + /** - * PRNG engine to use by default: 64bit mersenne twister. + * PRNG engine to use by default: 64bit Mersenne twister. */ using Random = RandomSequencer; @@ -123,42 +136,35 @@ namespace lib { extern Random entropyGen; + /* ===== convenience accessors ===== */ /** @return a random integer ∈ [0 ... bound[ */ inline int - rani (uint bound =1u<<31) - { - if (!bound) ++bound; - --bound; - uint upper{std::numeric_limits::max()}; - upper = bound < upper? bound : upper; - std::uniform_int_distribution dist(0, upper); - return dist (defaultGen.generator_); - } + rani (uint bound =_iBOUND()) + { + return defaultGen.i(bound); + } /** @return a random double ∈ [start ... bound[ */ inline double ranRange (double start, double bound) - { - std::uniform_real_distribution dist{start,bound}; - return dist (defaultGen.generator_); - } + { + return defaultGen.range (start, bound); + } inline double - ranNormal (double mean =0.0, double stdev =1.0) - { - std::normal_distribution dist{mean,stdev}; - return dist (defaultGen.generator_); - } + ranNormal(double mean =0.0, double stdev =1.0) + { + return defaultGen.normal (mean, stdev); + } /** @return a random *non-zero* HashVal */ inline lib::HashVal ranHash() - { - static std::uniform_int_distribution dist{lib::HashVal(1)}; - return dist (defaultGen.generator_); - } + { + return defaultGen.hash(); + } /** inject true randomness into the #defaultGen */ @@ -169,15 +175,20 @@ namespace lib { + /* ===== Implementation details ===== */ template inline RandomSequencer::RandomSequencer (SeedNucleus& nucleus) - : uniformI_{0} - , uniformU_{0} - , uniformD_{} - , generator_{nucleus.getSeed()} + : generator_{nucleus.getSeed()} + { } + + template + template + inline + RandomSequencer::RandomSequencer (RandomSequencer& parent) + : generator_{RandomSequencer::Seed{parent}.getSeed()} { } @@ -189,5 +200,115 @@ namespace lib { } + template + template + inline auto + RandomSequencer::distribute (DIST distribution) + { + return distribution (generator_); + } + + /** @param bound upper bound (exclusive!) */ + template + inline int + RandomSequencer::i (uint bound) + { + if (!bound) bound=1; + --bound; + uint upper{std::numeric_limits::max()}; + upper = bound < upper? bound : upper; + return distribute (std::uniform_int_distribution (0, upper)); + } + + template + inline int + RandomSequencer::i32() + { + return distribute (std::uniform_int_distribution {std::numeric_limits::min() + ,std::numeric_limits::max()}); + } + + template + inline uint64_t + RandomSequencer::u64() + { + return distribute (std::uniform_int_distribution {std::numeric_limits::min() + ,std::numeric_limits::max()}); + } + + template + inline double + RandomSequencer::uni() + { + return range (0.0, 1.0); + } + + template + inline double + RandomSequencer::range (double start, double bound) + { + return distribute (std::uniform_real_distribution{start,bound}); + } + + template + inline double + RandomSequencer::normal (double mean, double stdev) + { + return distribute (std::normal_distribution{mean,stdev}); + } + + template + inline HashVal + RandomSequencer::hash() + { + return distribute (std::uniform_int_distribution{lib::HashVal(1)}); + } + + + + /** + * Adapter to protect against data corruption caused by concurrent access. + * Random number generators in general are _not thread safe;_ when used from + * several threads concurrently, it is not a question _if_, but only a question + * _when_ the internal state will become corrupted, leading to degraded and biased + * distributions of produced numbers. For some usage scenarios however, ignoring + * this fact and still using a single generator from several threads may be acceptable, + * if the quality of the distribution actually does not matter and only some diffusion + * of numbers is required (e.g. adding a random sleep interval). But there is a catch: + * whenever the value range of generated numbers is less than the total range of the used + * data representation, then corruption of the internal state may lead to producing numbers + * outside the defined range. This adapter can be used to safeguard against this scenario. + * @remark typically using a 64bit generator on a 64bit platform is inherently safe, yet + * using a 32bit generator may rely on 64bit values internally, and then this problem + * may be triggered. See RandomConcurrent_test with the 32bit Mersenne twister as demo. + */ + template + class CappedGen + : public GEN + { + public: + using GEN::GEN; + + typename GEN::result_type + operator()() + { + if constexpr (GEN::max() < std::numeric_limits::max()) + return GEN::operator()() % (GEN::max()+1); + else + return GEN::operator()(); + } + }; + + template + auto + buildCappedSubSequence (RandomSequencer& src) + { + typename RandomSequencer::Seed seedChain(src); + RandomSequencer> subSeq{seedChain}; + return subSeq; + } + + + } // namespace lib #endif /*LIB_RANDOM_H*/ diff --git a/src/lib/test/microbenchmark-adaptor.hpp b/src/lib/test/microbenchmark-adaptor.hpp index 1178f3d51..aec8eebfe 100644 --- a/src/lib/test/microbenchmark-adaptor.hpp +++ b/src/lib/test/microbenchmark-adaptor.hpp @@ -83,7 +83,7 @@ namespace microbenchmark { wrap (FUN&& fun) { return [functor=std::forward(fun)] - (size_t) + (size_t) mutable { functor(); return size_t(1); @@ -101,7 +101,7 @@ namespace microbenchmark { wrap (FUN&& fun) { return [functor=std::forward(fun)] - (size_t i) + (size_t i) mutable { return size_t(functor(i)); }; @@ -117,7 +117,7 @@ namespace microbenchmark { wrap (FUN&& fun) { return [functor=std::forward(fun)] - (size_t) + (size_t) mutable { return size_t(functor()); }; @@ -133,7 +133,7 @@ namespace microbenchmark { wrap (FUN&& fun) { return [functor=std::forward(fun)] - (size_t i) + (size_t i) mutable { functor(i); return size_t(1); diff --git a/src/lib/test/microbenchmark.hpp b/src/lib/test/microbenchmark.hpp index c2c655698..71570ffc6 100644 --- a/src/lib/test/microbenchmark.hpp +++ b/src/lib/test/microbenchmark.hpp @@ -165,9 +165,10 @@ namespace test{ struct Thread : lib::ThreadJoinable<> { - Thread(Subject const& testSubject, size_t loopCnt, SyncBarrier& testStart) + Thread(Subject const& subject, size_t loopCnt, SyncBarrier& testStart) : ThreadJoinable{"Micro-Benchmark" - ,[=, &testStart]() // local copy of the test-subject-Functor + ,[this,loopCnt, testSubject=subject, &testStart] + () mutable // local (mutable) copy of the test-subject-Functor { testStart.sync(); // block until all threads are ready auto start = steady_clock::now(); diff --git a/tests/16calculation.tests b/tests/16calculation.tests index 3e3001c53..adee06594 100644 --- a/tests/16calculation.tests +++ b/tests/16calculation.tests @@ -17,6 +17,6 @@ return: 0 END -TEST "Concurrent PRNG access" RandomConcurrent_test << END +TEST "Concurrent PRNG access" RandomConcurrent_test quick << END return: 0 END diff --git a/tests/basics/call-queue-test.cpp b/tests/basics/call-queue-test.cpp index 239aab0b1..da7998464 100644 --- a/tests/basics/call-queue-test.cpp +++ b/tests/basics/call-queue-test.cpp @@ -187,6 +187,7 @@ namespace test{ uint64_t consumerSum = 0; SyncBarrier& trigger_; + Random rand_; void countConsumerCall (uint increment) @@ -198,13 +199,13 @@ namespace test{ Worker(CallQueue& queue, SyncBarrier& commonTrigger) : ThreadJoinable{"CallQueue_test: concurrent dispatch" , [&]() { - uint cnt = rand() % MAX_RAND_STEPS; //////////////////////////////OOO brauche rani auf lokalem Generator! - uint delay = rand() % MAX_RAND_DELAY; + uint cnt = rand_.i(MAX_RAND_STEPS); + uint delay = rand_.i(MAX_RAND_DELAY); trigger_.sync(); // block until all threads are ready for (uint i=0; i { TestThread() - : ThreadJoinable("test context stack" - ,&verifyDiagnosticStack) + : ThreadJoinable{"test context stack" + ,[seed = 1+rani(MAX_RAND)] + { return descend (seed); }} { } }; - - /** the actual test operation running in a separate thread - * produces a descending number sequence, and only odd values - * will be captured into the diagnostic stack - */ - static VecI - verifyDiagnosticStack() - { - uint seed (1 + rand() % MAX_RAND); /////////////////////////OOO brauche rani() auf lokalem Generator - return descend (seed); - } - static VecI descend (uint current) { @@ -213,7 +207,7 @@ namespace test{ sleep_for (500us); - if (isOdd(current)) + if (isOdd (current)) { Marker remember(current); return descend (current+1); diff --git a/tests/basics/typed-counter-test.cpp b/tests/basics/typed-counter-test.cpp index 619a51080..72d0f2bef 100644 --- a/tests/basics/typed-counter-test.cpp +++ b/tests/basics/typed-counter-test.cpp @@ -47,6 +47,7 @@ #include "lib/random.hpp" #include "lib/util.hpp" +#include #include #include @@ -183,9 +184,11 @@ namespace test{ TypedCounter testCounter; - auto testSubject = [&, i = rani(MAX_INDEX)] - (size_t) -> size_t + auto testSubject = [& + ,gen = makeRandGen()] + (size_t) mutable -> size_t { + uint i = gen.i(MAX_INDEX); operators[i](testCounter); return 1; }; diff --git a/tests/core/steam/control/session-command-function-test.cpp b/tests/core/steam/control/session-command-function-test.cpp index d5ff5a6a5..1e5adca81 100644 --- a/tests/core/steam/control/session-command-function-test.cpp +++ b/tests/core/steam/control/session-command-function-test.cpp @@ -58,7 +58,7 @@ ** Astute readers might have noticed, that the test fixture is sloppy with respect to proper ** locking and synchronisation. Rather, some explicit sleep commands are interspersed in a way ** tuned to work satisfactory in practice. This whole approach can only work, because each - ** Posix locking call actually requires the runtime system to issue a read/write barrier, + ** POSIX locking call actually requires the runtime system to issue a read/write barrier, ** which are known to have global effects on the relevant platforms (x86 and x86_64). ** And because the production relevant code in SteamDispatcher uses sufficient (in fact ** even excessive) locking, the state variables of the test fixture are properly synced @@ -322,11 +322,12 @@ namespace test { * sequential calculation and summation */ void - perform_massivelyParallel(Arg args_for_stresstest) + perform_massivelyParallel (Arg args_for_stresstest) { - maybeOverride(NUM_THREADS_DEFAULT, args_for_stresstest, 1); - maybeOverride(NUM_INVOC_PER_THRED, args_for_stresstest, 2); - maybeOverride(MAX_RAND_DELAY_us, args_for_stresstest, 3); + seedRand(); + maybeOverride (NUM_THREADS_DEFAULT, args_for_stresstest, 1); + maybeOverride (NUM_INVOC_PER_THRED, args_for_stresstest, 2); + maybeOverride (MAX_RAND_DELAY_us, args_for_stresstest, 3); // we'll run several instances of the following thread.... @@ -336,11 +337,12 @@ namespace test { SyncBarrier& barrier_; FamilyMember id_; vector cmdIDs_; + lib::Random random_; lib::ThreadJoinable thread_; Symbol - cmdID(uint j) + cmdID (uint j) { cmdIDs_.push_back (_Fmt("%s.thread-%02d.%d") % COMMAND_ID % id_ % j); return cStr(cmdIDs_.back()); @@ -350,6 +352,7 @@ namespace test { public: InvocationProducer (SyncBarrier& trigger) : barrier_{trigger} + , random_{defaultGen} , thread_{"producer", [&]{ fabricateCommands(); }} { } @@ -376,16 +379,16 @@ namespace test { } static void - sendCommandMessage(GenNode msg) + sendCommandMessage (GenNode msg) { SessionCommand::facade().trigger (msg.idi.getSym(), msg.data.get()); } - static void + void __randomDelay() { if (not MAX_RAND_DELAY_us) return; - sleep_for (microseconds (1 + rand() % MAX_RAND_DELAY_us)); // random delay varying in steps of 1µs + sleep_for (microseconds (1 + random_.i(MAX_RAND_DELAY_us))); // random delay varying in steps of 1µs } }; diff --git a/tests/library/del-stash-test.cpp b/tests/library/del-stash-test.cpp index 45ab2bd45..2457e7adc 100644 --- a/tests/library/del-stash-test.cpp +++ b/tests/library/del-stash-test.cpp @@ -230,7 +230,7 @@ namespace test{ public: Special() : Probe<555>() - , secret_('a' + (rand() % (1+'z'-'a'))) + , secret_('a' + rani('z'-'a' +1)) { checksum += secret_; } diff --git a/tests/library/incidence-count-test.cpp b/tests/library/incidence-count-test.cpp index 50052747d..4c3a03560 100644 --- a/tests/library/incidence-count-test.cpp +++ b/tests/library/incidence-count-test.cpp @@ -222,8 +222,11 @@ namespace test{ watch.expectThreads(CONCURR) .expectIncidents(10000); - auto act = [&]{ // two nested activities with random delay - uint delay = 100 + rand() % 800; + auto act = [& + ,gen = makeRandGen()]// local random generator per thread + () mutable + { // two nested activities with random delay + uint delay = 100 + gen.i(800); watch.markEnter(); sleep_for (microseconds(delay)); watch.markEnter(2); diff --git a/tests/library/random-concurrent-test.cpp b/tests/library/random-concurrent-test.cpp index 82102efbf..9327d2a8f 100644 --- a/tests/library/random-concurrent-test.cpp +++ b/tests/library/random-concurrent-test.cpp @@ -40,9 +40,6 @@ #include #include -//#include -//using util::isLimited; -//using std::array; using std::tuple; using std::deque; using util::_Fmt; @@ -66,13 +63,62 @@ namespace test { { virtual void - run (Arg) + run (Arg arg) { seedRand(); - investigate_concurrentAccess(); + benchmark_random_gen(); + if ("quick" != firstTok (arg)) + investigate_concurrentAccess(); } + /** @test microbenchmark of various random number generators + * @remark typical values + * - `rand()` (trinomial generator) : 15ns / 10ns (O3) + * - Mersenne twister 64bit : 55ns / 25ns (O3) + * - reading /dev/urandom : 480ns / 470 (O3) + */ + void + benchmark_random_gen() + { + auto do_nothing = []{ /* take it easy */ }; + auto mersenne64 = []{ return rani(); }; + auto legacy_gen = []{ return rand(); }; + std::random_device entropySource{"/dev/urandom"}; + auto rly_random = [&]{ return entropySource(); }; + + _Fmt resultDisplay{"µ-bench(%s)%|45T.| %5.3f µs"}; + + double d1 = microBenchmark (do_nothing, NUM_INVOKES).first; + cout << resultDisplay % "(empty call)" % d1 < struct Experiment : Sync<> @@ -103,7 +149,7 @@ namespace test { double percentTilted {0.0}; bool isFailure {false}; - + /** run the experiment series */ void perform() { @@ -116,7 +162,7 @@ namespace test { auto r = generator(); if (r < GEN::min() or r > GEN::max()) ++fail; - avg += 1.0/N * r;//(r % Engine::max()); + avg += 1.0/N * r; } auto error = avg/expect - 1; recordRun (error, fail); @@ -128,7 +174,7 @@ namespace test { _Fmt resultLine{"%6.3f ‰ : %d %s"}; for (auto [err,fails] : results) { - bool isGlitch = fails or fabs(err) >0.003; + bool isGlitch = fails or fabs(err) > 3 * 1/sqrt(N); // mean of a sound distribution will remain within bounds cout << resultLine % (err*1000) % fails % (fails? "FAIL": isGlitch? " !! ":"") << endl; @@ -138,8 +184,8 @@ namespace test { } // assess overall results...... percentGlitches = 100.0 * glitches/cases; - percentTilted = 100.0 * fabs(double(lows)/cases - 0.5)*2; - isFailure = glitches or percentTilted > 30; + percentTilted = 100.0 * fabs(double(lows)/cases - 0.5)*2; // degree to which mean is biased for one side + isFailure = glitches or percentTilted > 30; // (empirical trigger criterion) cout << _Fmt{"++-------------++ %s\n" " Glitches: %5.1f %%\n" " Tilted: %5.1f %%\n" @@ -150,26 +196,35 @@ namespace test { << endl; } }; - - - /** @test examine behaviour of PRNG under concurrency stress */ + + + /** @test examine behaviour of PRNG under concurrency stress + * - running a 32bit generator single threaded should not trigger alarms + * - while under concurrent pressure several defect numbers should be produced + * - even the 64bit generator will show uneven distribution due to corrupted state + * - the 32bit generator capped to its valid range exhibits skew only occasionally + * @see lib::CappedGen + */ void investigate_concurrentAccess() { - using Mersenne32 = std::mt19937; using Mersenne64 = std::mt19937_64; + using Mersenne32 = std::mt19937; + using CappedMs32 = CappedGen; - Experiment single32{Mersenne32(defaultGen.uni())}; - Experiment concurr32{Mersenne32(defaultGen.uni())}; - Experiment concurr64{Mersenne64(defaultGen.uni())}; + Experiment single_mers32{Mersenne32(defaultGen.uni())}; + Experiment concurr_mers32{Mersenne32(defaultGen.uni())}; + Experiment concurr_mers64{Mersenne64(defaultGen.uni())}; + Experiment concCap_mers32{CappedMs32(defaultGen.uni())}; - single32.perform(); - concurr32.perform(); - concurr64.perform(); + single_mers32.perform(); + concurr_mers32.perform(); + concurr_mers64.perform(); + concCap_mers32.perform(); - CHECK (not single32.isFailure, "ALARM : single-threaded Mersenne-Twister 32bit produces skewed distribution"); - CHECK ( single32.isFailure, "SURPRISE : Mersenne-Twister 32bit encountered NO glitches under concurrent pressure"); - CHECK ( single64.isFailure, "SURPRISE : Mersenne-Twister 64bit encountered NO glitches under concurrent pressure"); + CHECK (not single_mers32.isFailure, "ALARM : single-threaded Mersenne-Twister 32bit produces skewed distribution"); + CHECK ( concurr_mers32.isFailure, "SURPRISE : Mersenne-Twister 32bit encountered NO glitches under concurrent pressure"); + CHECK ( concurr_mers64.isFailure, "SURPRISE : Mersenne-Twister 64bit encountered NO glitches under concurrent pressure"); } }; diff --git a/tests/library/sync-barrier-test.cpp b/tests/library/sync-barrier-test.cpp index e5148d231..85cd9e268 100644 --- a/tests/library/sync-barrier-test.cpp +++ b/tests/library/sync-barrier-test.cpp @@ -69,15 +69,16 @@ namespace test { public: TestThread() : Thread{"Load Test" - ,[&]() + ,[&, ran=lib::Random{seedFromDefaultGen()}] + () mutable { //-STAGE-1------------------------------ - localSum = rand() % 1000; // generate local value + localSum = ran.i(1000); // generate local value stage1.fetch_add (localSum); // book in local value interThread.sync(); // wait for all other threads to have booked in //-STAGE-2------------------------------ uint sync = stage1; // pick up compounded sum from STAGE-1 - localSum += rand() % 1000; // add further local value for STAGE-2 + localSum += ran.i(1000); // add further local value for STAGE-2 stage2.fetch_add (localSum+sync); // book in both local values and synced sum afterThread.sync(); // wait for other threads and supervisor @@ -117,6 +118,8 @@ namespace test { virtual void run (Arg) { + seedRand(); + // Launch several TestThread array threads; CHECK (0 == finish); diff --git a/tests/library/sync-classlock-test.cpp b/tests/library/sync-classlock-test.cpp index b835ec2b3..0e0997078 100644 --- a/tests/library/sync-classlock-test.cpp +++ b/tests/library/sync-classlock-test.cpp @@ -63,6 +63,8 @@ namespace test { virtual void run (Arg) { + seedRand(); + auto gen = buildCappedSubSequence(defaultGen); int contended = 0; using Threads = lib::ScopedCollection>; @@ -75,7 +77,7 @@ namespace test { ,[&]{ for (uint i=0; i guard; @@ -87,7 +89,7 @@ namespace test { }; for (auto& thread : threads) - thread.join(); // block until thread terminates + thread.join(); // block until thread terminates // @suppress("Return value not evaluated") CHECK (contended == NUM_THREADS * NUM_LOOP, "ALARM: Lock failed, concurrent modification " diff --git a/tests/stage/bus-term-test.cpp b/tests/stage/bus-term-test.cpp index 21b75dd72..6c44352cf 100644 --- a/tests/stage/bus-term-test.cpp +++ b/tests/stage/bus-term-test.cpp @@ -138,6 +138,8 @@ namespace test { virtual void run (Arg) { + seedRand(); + attachNewBusTerm(); commandInvocation(); captureStateMark(); @@ -669,17 +671,20 @@ namespace test { : ThreadJoinable{"BusTerm_test: asynchronous diff mutation" , [=] { - uint cnt = rand() % MAX_RAND_BORGS; + uint cnt = randGen_.i(MAX_RAND_BORGS); for (uint i=0; i 6'000); CHECK (1 == wof.size()); } diff --git a/tests/vault/mem/extent-family-test.cpp b/tests/vault/mem/extent-family-test.cpp index 377351699..803e40f9b 100644 --- a/tests/vault/mem/extent-family-test.cpp +++ b/tests/vault/mem/extent-family-test.cpp @@ -133,7 +133,7 @@ namespace test { Extent& extent{*it}; CHECK (10 == extent.size()); - int num = rand() % 1000; + int num = rani(1000); extent[2] = num; CHECK (num == extent[2]); @@ -173,7 +173,7 @@ namespace test { struct Probe { short val; - Probe() : val(1 + rand() % 1000) { } + Probe() : val(1 + rani(1000)) { } ~Probe() { val = 0; } }; diff --git a/wiki/renderengine.html b/wiki/renderengine.html index c6d0e794f..e96379744 100644 --- a/wiki/renderengine.html +++ b/wiki/renderengine.html @@ -7178,10 +7178,10 @@ An Activity is //performed// by invoking its {{{activate(now, ctx)}}} function - In a similar vein, also ''dependency notifications'' need to happen decoupled from the activity chain from which they originate; thus the Post-mechanism is also used for dispatching notifications. Yet notifications are to be treated specially, since they are directed towards a receiver, which in the standard case is a {{{GATE}}}-Activity and will respond by //decrementing its internal latch.// Consequently, notifications will be sent through the ''λ-post'' -- which operationally re-schedules a continuation as a follow-up job. Receiving such a notification may cause the Gate to become opened; in this case the trigger leads to //activation of the chain// hooked behind the Gate, which at some point typically enters into another calculation job. Otherwise, if the latch (in the Gate) is already zero (or the deadline has passed), nothing happens. Thus the implementation of state transition logic ensures the chain behind a Gate can only be //activated once.// -
-
At a high level, the Render Process is what „runs“ a playback or render. Using the EngineFaçade, the [[Player]] creates a descriptor for such a process, which notably defines a [[»calculation stream«|CalcStream]] for each individual //data feed// to be produced. To actually implement such an //ongoing stream of timed calculations,// a series of data frames must be produced, for which some source data must be loaded and then individual calculations must be scheduled to work on this data and deliver results within a well defined time window for each frame. Thus, on the implementation level, a {{{CalcStream}}} comprises a pipeline to define [[render jobs|RenderJob]], and a self-repeating re-scheduling mechanism to repeatedly plan and dispatch a //chunk of render jobs// to the [[Scheduler]], which cares to invoke the individual jobs in due time.
+
+
At a high level, the Render Process is what „runs“ a playback or render. Using the EngineFaçade, the [[Player]] creates a descriptor for such a process, which notably defines a [[»calculation stream«|CalcStream]] for each individual //data feed// to be produced. To actually implement such an //ongoing stream of timed calculations,// a series of data frames must be produced, for which some source data has to be loaded and then individual calculations will be scheduled to work on this data and deliver results within a well defined time window for each frame. Thus, on the implementation level, a {{{CalcStream}}} comprises a pipeline to define [[render jobs|RenderJob]], and a self-repeating re-scheduling mechanism to repeatedly plan and dispatch a //chunk of render jobs// to the [[Scheduler]], which cares to invoke the individual jobs in due time.
 
-This leads to a even more detailed implementation level of the ''render processing''. Within the [[Session]], the user has defined the »edit« or the definition of the media product as an arrangement of media elements placed and arranged into a [[Timeline]]. A repeatedly-running, demand-driven, compiler-like process in Lumiera (known as [[the Builder|Builder]]) consolidates this [[»high-level definition«|HighLevelModel]] into a [[Fixture]] and an directly attached [[network of Render Nodes|LowLevelModel]]. The Fixture hereby defines a [[segment for each part of the timeline|Segmentation]], which can be represented by a single topology of connected render nodes. So each segment spans a time range, quantised into a range of frames -- and the node network attached below this segment is capable of producing media data for each frame within definition range, when given the actual frame number, and some designation of the actual data feed required at that point. Yet it depends on the circumstances what this »data feed« //actually is;// as a rule, anything which can be produced and consumed as compound will be represented as //a single feed.// The typical video will thus comprise a video feed and a stereo sound feed, while another setup may require to deliver individual sound feeds for the left and right channel, or whatever channel layout the sound system has, and it may require two distinct beamer feeds for the two channels of stereoscopic video. However -- as a general rule of architecture -- the Lumiera Render Engine is tasked to perform //all of the processing work,// up to and including all adaptation steps to reach the desired final result. Thus, for rendering into a media container, only a single feed is required, which can be drawn from an encoder node, which in turn consumes several data feeds for its constituents.
+This leads to a even more detailed description at implementation level of the ''render processing''. Within the [[Session]], the user has defined the »edit« or the definition of the media product as a collection of media elements placed and arranged into a [[Timeline]]. A repeatedly-running, demand-driven, compiler-like process (in Lumiera known as [[the Builder|Builder]]) consolidates this [[»high-level definition«|HighLevelModel]] into a [[Fixture]] and a [[network of Render Nodes|LowLevelModel]] directly attached below. The Fixture hereby defines a [[segment for each part of the timeline|Segmentation]], which can be represented as a distinct and non-changing topology of connected render nodes. So each segment spans a time range, quantised into a range of frames -- and the node network attached below this segment is capable of producing media data for each frame within definition range, when given the actual frame number, and some designation of the actual data feed required at that point. Yet it depends on the circumstances what this »data feed« //actually is;// as a rule, anything which can be produced and consumed as compound will be represented as //a single feed.// The typical video will thus comprise a video feed and a stereo sound feed, while another setup may require to deliver individual sound feeds for the left and right channel, or whatever channel layout the sound system has, and it may require two distinct beamer feeds for the two channels of stereoscopic video. However -- as a general rule of architecture -- the Lumiera Render Engine is tasked to perform //all of the processing work,// up to and including any adaptation step required to reach the desired final result. Thus, for rendering into a media container, only a single feed is required, which can be drawn from an encoder node, which in turn consumes several data feeds for its constituents.
 
 To summarise this break-down of the rendering process defined thus far, the [[Scheduler]] ''invokes'' individual [[frame render jobs|RenderJob]], each of which defines a set of »coordinates«:
 * an ID of the calculation stream allowing to retrieve the output sink
diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm
index 656556800..ceebcd6fe 100644
--- a/wiki/thinkPad.ichthyo.mm
+++ b/wiki/thinkPad.ichthyo.mm
@@ -17172,9 +17172,7 @@
 
 
 
-  
-    
-  
+  
   
     

UIStyle: der »StyleManager« @@ -17672,9 +17670,7 @@ - - - +

  • @@ -18528,9 +18524,7 @@ - - - +

    ...unter der Annahme, daß letzlich eine "invalidation" des Widgets genügt, ließe sich das elegant lösen, indem der Canvas-Container insgesamt "invalidated" wird. @@ -18938,9 +18932,7 @@ - - - +

    BEDINGUNG: ΔName > goal @@ -19604,9 +19596,7 @@ - - - +

    ...welches u.U. zwar vom ElementBoxWidget aus eingebunden wird, aber eigentlich auf die Inhalts-Ebene delegiert @@ -20738,9 +20728,7 @@ - - - +

    aber: Binding im Diff-System durchaus möglich @@ -23084,9 +23072,7 @@ - - - +

    die betreffenden Felder sind echt optional. @@ -43042,9 +43028,7 @@ - - - +

    da die Metrik ja limitiert wurde und damit per definitionem auch sauber @@ -44373,9 +44357,7 @@ - - - +

    aus Gründen der Symmetrie kann man das gleiche Argument jeweils auch auf den Kehrwert anwenden, deshalb haben wir ja 4 Fälle (bei zwei Eingangs-Faktoren). Man muß nur ggfs. dann den Kehrwert vom Ergebnis ausgeben. Beispiel: wir nehmen den Zähler vom ersten Faktor als Quantisierer. Dann ist f1 der Nenner vom ersten Faktor, und muß daher auch im Ergebnis im Nenner landen, nicht im Zähler wie beim regulären Schema @@ -46248,9 +46230,7 @@ - - - +

    man muß die Implementierungs-Details jeder einzelnen Komponente kennen, @@ -46497,9 +46477,7 @@ - - - +

    dafür genügt der normale Reset @@ -46671,9 +46649,7 @@ - - - +

    Identität == Bus-ID @@ -46779,9 +46755,7 @@ - - - +

    nicht bool-Testbar @@ -56486,7 +56460,7 @@ - + @@ -56504,6 +56478,109 @@ + + + + + + + + +

    + es soll möglich seine, eine opaque Quelle zu übergeben und erst zur Laufzeit / dynamisch bestimmen, woher der Seed stammt; Entscheidung per late-binding, ob echte Entropie zum Einsatz kommt, oder eine reproduzierbare Sequenz oder gar ein fixierter Wert +

    + +
    + +
    + + + + + + + + + + + + + + + + + + + +

    + das ist ein Feature; man kann damit einen State speichern +

    + +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -57008,9 +57085,10 @@ - - - + + + + @@ -57279,7 +57357,7 @@ - + @@ -57350,7 +57428,7 @@ - + @@ -57437,7 +57515,8 @@ - + + @@ -57448,8 +57527,8 @@ - - + + @@ -57462,7 +57541,7 @@ - + @@ -58018,15 +58097,48 @@ - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -58047,9 +58159,8 @@ - - + @@ -58058,9 +58169,7 @@ - - - +

    diese verwendet uint_fast32_t, das aber auf meinem System ebenfalls auf unit64_t gemapped ist @@ -58071,9 +58180,8 @@ - + - @@ -58109,7 +58217,7 @@ - + @@ -58127,9 +58235,7 @@ - - - +

    schon kleine Änderungen in der Payload-Funktion können die Inzidenz der Probleme drastisch ändern; beispielsweise hat das Hinzufügen einer Begrenzung per Modulo für einen Generator die Inzidenz drastisch erhöht, für einen anderen die Probleme nahezu zum Verschwinden gebracht @@ -58147,16 +58253,22 @@ - - + + - - + + + + + + + + @@ -58172,8 +58284,8 @@ - - + + @@ -92682,7 +92794,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
    - + @@ -92691,7 +92803,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200

    - +
    @@ -139082,15 +139194,7 @@ std::cout << tmpl.render({"what", "World"}) << s - - - - -

    -   -

    - -
    + @@ -139169,11 +139273,16 @@ std::cout << tmpl.render({"what", "World"}) << s + + + - + + +