diff --git a/src/lib/rational.hpp b/src/lib/rational.hpp index f1bd84851..863cdbc8d 100644 --- a/src/lib/rational.hpp +++ b/src/lib/rational.hpp @@ -184,7 +184,9 @@ namespace util { // construct approximation quantised to 1/u f128 frac = f128(r) / den; int64_t res = d*u + int64_t(frac*u * ROUND_ULP); - ENSURE (abs (f128(res)/u - rational_cast(Rat{num,den})) <= 1.0/abs(u)); + ENSURE (abs (f128(res)/u - rational_cast(Rat{num,den})) <= 1.0/abs(u) + ,"Requantisation error exceeded num=%lu / den=%lu -> res=%lu / quant=%lu" + , num, den, res, u); return res; } diff --git a/src/lib/time/time.cpp b/src/lib/time/time.cpp index cb38b3a2a..384c76fa1 100644 --- a/src/lib/time/time.cpp +++ b/src/lib/time/time.cpp @@ -130,6 +130,11 @@ namespace time { : TimeValue(lumiera_rational_to_time (fractionalSeconds)) { } + Offset::Offset (FSecs const& delta_in_secs) + : TimeValue{buildRaw_(symmetricLimit (lumiera_rational_to_time (delta_in_secs) + ,Duration::MAX))} + { } + /** @note recommendation is to use TCode for external representation * @remarks this is the most prevalent internal diagnostics display @@ -266,29 +271,29 @@ namespace time { boost::rational distance (this->t_); distance *= factor; gavl_time_t microTicks = floordiv (distance.numerator(), distance.denominator()); - return Offset(TimeValue(microTicks)); + return Offset{buildRaw_(microTicks)}; } /** offset by the given number of frames. */ Offset::Offset (FrameCnt count, FrameRate const& fps) - : TimeValue (count? (count<0? -1:+1) * lumiera_framecount_to_time (::abs(count), fps) - : _raw(Duration::NIL)) + : TimeValue{buildRaw_( + count? (count<0? -1:+1) * lumiera_framecount_to_time (::abs(count), fps) + :_raw(Duration::NIL))} { } - /** duration of the given number of frames. - * @note always positive; count used absolute */ - Duration::Duration (FrameCnt count, FrameRate const& fps) - : TimeValue (count? lumiera_framecount_to_time (abs(count), fps) : _raw(Duration::NIL)) - { } /** constant to indicate "no duration" */ const Duration Duration::NIL {Time::ZERO}; /** maximum possible temporal extension */ - const Duration Duration::MAX {Offset{Time::MIN, Time::MAX}}; - + const Duration Duration::MAX = []{ + auto maxDelta {Time::MAX - Time::MIN}; + // bypass limit check, which requires Duration::MAX + return reinterpret_cast (maxDelta); + }(); + }} // namespace lib::Time diff --git a/src/lib/time/timevalue.hpp b/src/lib/time/timevalue.hpp index 85946fcfb..2fd570c52 100644 --- a/src/lib/time/timevalue.hpp +++ b/src/lib/time/timevalue.hpp @@ -122,6 +122,7 @@ namespace time { // forwards... class FrameRate; + class Duration; class TimeSpan; class Mutation; @@ -160,17 +161,24 @@ namespace time { /** some subclasses may receive modification messages */ friend class Mutation; + /** explicit limit of allowed time range */ + static gavl_time_t limitedTime (gavl_time_t raw); + /** safe calculation of explicitly limited time offset */ + static gavl_time_t limitedDelta (gavl_time_t origin, gavl_time_t target); + + /** @internal for Offset and Duration entities built on top */ + TimeValue (TimeValue const& origin, TimeValue const& target) + : t_{limitedDelta (origin.t_, target.t_)} + { } + public: /** Number of micro ticks (µs) per second as basic time scale */ static const gavl_time_t SCALE; - /** explicit limit of allowed time range */ - static gavl_time_t limited (gavl_time_t raw); - explicit - TimeValue (gavl_time_t val=0) ///< time given in µ ticks here - : t_(limited (val)) + TimeValue (gavl_time_t val) ///< time given in µ ticks here + : t_{limitedTime (val)} { } /** copy initialisation allowed */ @@ -234,7 +242,7 @@ namespace time { > > { public: - TimeVar (TimeValue const& time = TimeValue()) + TimeVar (TimeValue const& time = TimeValue(0)) : TimeValue(time) { } @@ -349,6 +357,8 @@ namespace time { * to build derived values, including * - the _absolute (positive) distance_ for this offset: #abs * - a combined offset by chaining another offset + * @note on construction, Offset values are checked and limited + * to be within [-Duration::MAX ... +Duration::MAX] */ class Offset : public TimeValue @@ -366,24 +376,21 @@ namespace time { public: explicit - Offset (TimeValue const& distance =Time::ZERO) - : TimeValue(distance) - { } + Offset (TimeValue const& distance =Time::ZERO); + + explicit + Offset (FSecs const& delta_in_secs); + + Offset (FrameCnt count, FrameRate const& fps); Offset (TimeValue const& origin, TimeValue const& target) - : TimeValue(TimeVar(target) -= origin) + : TimeValue{origin, target} { } - Offset (FrameCnt count, FrameRate const& fps); - static const Offset ZERO; - - TimeValue - abs() const - { - return TimeValue(std::llabs (t_)); - } + /** interpret the distance given by this offset as a time duration */ + Duration abs() const; /** @internal stretch offset by a possibly fractional factor, * and quantise into raw (micro tick) grid */ @@ -446,6 +453,7 @@ namespace time { * promoted from an offset. While Duration generally * is treated as immutable value, there is the * possibility to send a _Mutation message_. + * @note Duration relies on Offset being limited */ class Duration : public TimeValue @@ -455,29 +463,36 @@ namespace time { public: Duration() - : Duration(Time::ZERO) + : TimeValue{Time::ZERO} { } Duration (Offset const& distance) - : TimeValue(distance.abs()) + : TimeValue{buildRaw_(llabs (_raw(distance)))} { } explicit Duration (TimeValue const& timeSpec) - : TimeValue(Offset(timeSpec).abs()) + : Duration{Offset{timeSpec}} { } explicit Duration (FSecs const& timeSpan_in_secs) - : TimeValue(Offset(Time(timeSpan_in_secs)).abs()) + : Duration{Offset{timeSpan_in_secs}} + { } + + /** duration of the given number of frames. + * @note always positive; count used absolute */ + Duration (FrameCnt count, FrameRate const& fps) + : Duration{Offset{count,fps}} { } Duration (TimeSpan const& interval); - Duration (FrameCnt count, FrameRate const& fps); Duration (Duration const& o) - : Duration{Offset(o)} - { } + : TimeValue{o} + {// assuming that negative Duration can not be constructed.... + REQUIRE (t_ >= 0, "Copy rejected: negative Duration %lu", o.t_); + } static const Duration NIL; static const Duration MAX ; @@ -665,6 +680,14 @@ namespace time { , error::LERR_(BOTTOM_VALUE)); return n; } + + inline gavl_time_t + symmetricLimit (gavl_time_t raw, TimeValue lim) + { + return raw > lim? _raw(lim) + : -raw > lim? -_raw(lim) + : raw; + } }//(End) implementation helpers @@ -673,25 +696,41 @@ namespace time { * raw time value to keep it within the arbitrary * boundaries defined by (Time::MAX, Time::MIN). * While Time entities are \c not a "safeInt" - * implementation, we limit new values and - * establish this safety margin to prevent - * wrap-around during time quantisation */ + * implementation, we limit new values to + * lower the likelihood of wrap-around */ inline gavl_time_t - TimeValue::limited (gavl_time_t raw) + TimeValue::limitedTime (gavl_time_t raw) { - return raw > Time::MAX? Time::MAX.t_ - : raw < Time::MIN? Time::MIN.t_ - : raw; + return symmetricLimit (raw, Time::MAX); } + inline gavl_time_t + TimeValue::limitedDelta (gavl_time_t origin, gavl_time_t target) + { + if (0 > (origin^target)) + {// prevent possible numeric wrap + origin = symmetricLimit (origin, Duration::MAX); + target = symmetricLimit (target, Duration::MAX); + } + gavl_time_t res = target - origin; + return symmetricLimit (res, Duration::MAX); + } + + inline TimeVar::TimeVar (FSecs const& fractionalSeconds) : TimeVar{Time(fractionalSeconds)} { } + inline + Offset::Offset (TimeValue const& distance) + : TimeValue{buildRaw_(symmetricLimit(_raw(distance) + , Duration::MAX))} + { } + inline Duration::Duration (TimeSpan const& interval) - : TimeValue(interval.duration()) + : Duration{interval.duration()} { } inline @@ -715,6 +754,11 @@ namespace time { return boost::rational_cast (*this); } + inline Duration + Offset::abs() const + { + return Duration{*this}; + } diff --git a/src/stage/model/zoom-window.hpp b/src/stage/model/zoom-window.hpp index 5e0383491..650c1ce49 100644 --- a/src/stage/model/zoom-window.hpp +++ b/src/stage/model/zoom-window.hpp @@ -732,6 +732,8 @@ namespace model { REQUIRE (dur < MAX_TIMESPAN); REQUIRE (Time::MIN <= startWin_); REQUIRE (afterWin_ <= Time::MAX); + startAll_ = max (startAll_, Time::MIN); + afterAll_ = min (afterAll_, Time::MAX); if (dur <= _FSecs(afterAll_-startAll_)) {//possibly shift into current canvas if (afterWin_ > afterAll_) diff --git a/src/vault/real-clock.cpp b/src/vault/real-clock.cpp index cc627f298..bd84c1612 100644 --- a/src/vault/real-clock.cpp +++ b/src/vault/real-clock.cpp @@ -54,7 +54,7 @@ namespace vault { gavl_time_t ticksSince1970 = now.tv_sec * TimeValue::SCALE + now.tv_nsec / MICRO_TICS_PER_NANOSECOND; - ENSURE (ticksSince1970 == Time::limited (ticksSince1970)); + ENSURE (ticksSince1970 == _raw(TimeValue{ticksSince1970})); return TimeValue::buildRaw_(ticksSince1970); // bypassing the limit check } diff --git a/tests/basics/time/quantiser-basics-test.cpp b/tests/basics/time/quantiser-basics-test.cpp index b1eb73b35..cfe544735 100644 --- a/tests/basics/time/quantiser-basics-test.cpp +++ b/tests/basics/time/quantiser-basics-test.cpp @@ -183,7 +183,7 @@ namespace test{ CHECK (Time::MIN == case2.gridAlign( secs(-1) )); // resulting values will already exceed CHECK (Time::MIN == case2.gridAlign( secs(-2) )); // allowed range and thus will be clipped - // maximum frame size is half the time range + // use very large frame with size of half the time range Duration hugeFrame(Time::MAX); FixedFrameQuantiser case3 (hugeFrame); CHECK (Time::MIN == case3.gridAlign(Time::MIN )); @@ -205,9 +205,45 @@ namespace test{ CHECK (TimeValue(0) == case4.gridAlign(Time::MAX -TimeValue(1) )); CHECK (TimeValue(0) == case4.gridAlign(Time::MAX )); //.......still truncated down to frame #0 - // larger frames aren't possible - Duration not_really_larger(secs(10000) + hugeFrame); - CHECK (hugeFrame == not_really_larger); + // think big... + Duration superHuge{secs(12345) + hugeFrame}; + Duration extraHuge{2*hugeFrame}; + CHECK (extraHuge == Duration::MAX); + + // Time::MAX < superHuge < Duration::Max is possible, but we can accommodate only one + FixedFrameQuantiser case5 (superHuge); + CHECK (TimeValue(0) == case5.gridAlign(Time::MAX )); + CHECK (TimeValue(0) == case5.gridAlign(Time::MAX -TimeValue(1) )); + CHECK (TimeValue(0) == case5.gridAlign( secs( 1) )); + CHECK (TimeValue(0) == case5.gridAlign( secs( 0) )); + CHECK (Time::MIN == case5.gridAlign( secs(-1) )); + CHECK (Time::MIN == case5.gridAlign(Time::MIN +TimeValue(1) )); + CHECK (Time::MIN == case5.gridAlign(Time::MIN )); + + // now with offset + FixedFrameQuantiser case6 (superHuge, Time::MAX-secs(1)); + CHECK (TimeValue(0) == case6.gridAlign(Time::MAX )); + CHECK (TimeValue(0) == case6.gridAlign(Time::MAX -TimeValue(1) )); + CHECK (TimeValue(0) == case6.gridAlign(Time::MAX -secs(1) )); + CHECK (Time::MIN == case6.gridAlign(Time::MAX -secs(2) )); + CHECK (Time::MIN == case6.gridAlign( secs( 1) )); + CHECK (Time::MIN == case6.gridAlign( secs(-12345) )); + CHECK (Time::MIN == case6.gridAlign( secs(-12345-1) )); + CHECK (Time::MIN == case6.gridAlign( secs(-12345-2) )); // this would be one frame lower, but is clipped + CHECK (Time::MIN == case6.gridAlign(Time::MIN +TimeValue(1) )); + CHECK (Time::MIN == case6.gridAlign(Time::MIN )); // same... unable to represent time points before Time::MIN + + // maximum frame size is spanning the full time range + FixedFrameQuantiser case7 (extraHuge, Time::MIN+secs(1)); + CHECK (TimeValue(0) == case7.gridAlign(Time::MAX )); // rounded down one frame, i.e. to origin + CHECK (TimeValue(0) == case7.gridAlign( secs( 0) )); + CHECK (TimeValue(0) == case7.gridAlign(Time::MIN+secs(2) )); + CHECK (TimeValue(0) == case7.gridAlign(Time::MIN+secs(1) )); // exactly at origin + CHECK (Time::MIN == case7.gridAlign(Time::MIN )); // one frame further down, but clipped to Time::MIN + + // even larger frames aren't possible + Duration not_really_larger(secs(10000) + extraHuge); + CHECK (extraHuge == not_really_larger); // frame sizes below the time micro grid get trapped long subAtomic = 2*TimeValue::SCALE; // too small for this universe... diff --git a/tests/basics/time/time-value-test.cpp b/tests/basics/time/time-value-test.cpp index 5a563ef40..b0e44b87c 100644 --- a/tests/basics/time/time-value-test.cpp +++ b/tests/basics/time/time-value-test.cpp @@ -88,7 +88,7 @@ namespace test{ void checkBasicTimeValues (TimeValue org) { - TimeValue zero; + TimeValue zero(0); TimeValue one (1); TimeValue max (Time::MAX); TimeValue min (Time::MIN); @@ -248,7 +248,7 @@ namespace test{ void buildDuration (TimeValue org) { - TimeValue zero; + TimeValue zero(0); TimeVar point(org); point += TimeValue(5); CHECK (org < point); @@ -313,7 +313,6 @@ namespace test{ void buildTimeSpan (TimeValue org) { - TimeValue zero; TimeValue five(5); TimeSpan interval (Time(org), Duration(Offset (org,five))); diff --git a/tests/stage/model/zoom-window-test.cpp b/tests/stage/model/zoom-window-test.cpp index f7e562d70..993d75e9b 100644 --- a/tests/stage/model/zoom-window-test.cpp +++ b/tests/stage/model/zoom-window-test.cpp @@ -533,7 +533,7 @@ namespace test { CHECK (negaTime < Time::ZERO); Duration& evilDuration = reinterpret_cast (negaTime); // attempt fabricate a subverted TimeSpan CHECK (evilDuration < Time::ZERO); // ...sneak in a negative value - CHECK (TimeSpan(_t(20), evilDuration) == TimeSpan(_t(20),_t(30))); // .....yet won't make it get past Duration copy ctor! +// CHECK (TimeSpan(_t(20), evilDuration) == TimeSpan(_t(20),_t(30))); // .....yet won't make it get past Duration copy ctor! } @@ -601,6 +601,7 @@ namespace test { Rat poison{_raw(Time::MAX)-101010101010101010, _raw(Time::MAX)+23}; CHECK (0 < poison and poison < 1); + /*--Test-1-----------*/ win.setMetric (poison); // inject an evil new value for the metric CHECK (win.visible() == win.overallSpan()); // however, nothing happens CHECK (win.visible().duration() == _t(23)); // since the window is confined to overall canvas size @@ -613,6 +614,7 @@ namespace test { CHECK (win.overallSpan().duration() == Time::MAX); CHECK (win.visible().duration() == _t(23)); // while the visible part remains unaltered + /*--Test-2-----------*/ win.setMetric (poison); // Now attempt again to poison the zoom calculations... CHECK (win.overallSpan().duration() == Time::MAX); // overall canvas unchanged CHECK (win.visible().duration() == TimeValue{856350691}); // visible window expanded (a zoom-out, as required) @@ -636,6 +638,7 @@ namespace test { CHECK (win.overallSpan().duration() == TimeValue{307445734561825860}); CHECK (win.visible().duration() == TimeValue{856350691}); + /*--Test-3-----------*/ win.setVisiblePos (poison); // Yet another way to sneak in our toxic value... CHECK (win.overallSpan().start() == Time::ZERO); CHECK (win.overallSpan().duration() == TimeValue{307445734561825860}); // However, all base values turn out unaffected @@ -652,6 +655,31 @@ namespace test { CHECK (win.px_per_sec() == 575000000_r/856350691); // metric and pixel width are retained CHECK (win.pxWidth() == 575); + + + win.setOverallStart(Time::MAX - TimeValue(23)); // preparation for Test-4 : shift canvas to end of time + CHECK (win.overallSpan() == win.visible()); // consequence: window has been capped to canvas size + CHECK (win.overallSpan().start() == TimeValue{307445734561825572}); // window now also located at extreme values + CHECK (win.overallSpan().end() == TimeValue{307445734561825860}); + CHECK (win.overallSpan().duration() == TimeValue{288}); // window (and canvas) were expanded to comply to maximum zoom factor + CHECK (win.px_per_sec() == 17968750_r/9); // zoom factor was then slightly reduced to match next pixel boundary + CHECK (win.pxWidth() == 575); // established pixel size was retained + SHOW_EXPR(win.overallSpan()); + SHOW_EXPR(_raw(win.overallSpan().start())); + SHOW_EXPR(_raw(win.overallSpan().end())); + SHOW_EXPR(_raw(win.overallSpan().duration())); + SHOW_EXPR(_raw(win.visible().duration())); + + /*--Test-4-----------*/ + win.setVisiblePos(Time{Time::MIN + TimeValue(13)}); // Test: implicitly provoke poisonous factor through extreme offset + + SHOW_EXPR(win.overallSpan()); + SHOW_EXPR(_raw(win.overallSpan().start())); + SHOW_EXPR(_raw(win.overallSpan().end())); + SHOW_EXPR(_raw(win.overallSpan().duration())); + SHOW_EXPR(_raw(win.visible().duration())); + SHOW_EXPR(win.px_per_sec()); + SHOW_EXPR(win.pxWidth()); } @@ -662,6 +690,7 @@ namespace test { safeguard_extremeZoomOut() { // SHOW_EXPR(win.overallSpan()); +// SHOW_EXPR(_raw(win.overallSpan().duration())); // SHOW_EXPR(_raw(win.visible().duration())); // SHOW_EXPR(win.px_per_sec()); // SHOW_EXPR(win.pxWidth()); diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 377101fb3..a18f551bd 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -40893,6 +40893,286 @@ + + + + + + + + + + + + +

+ also offensichtlich erkennt boost::rational die Möglichkeit, den gemeinsamen Faktor 1e6 aus Zähler und Nenner wegzukürzen. Da wir aber hier einen FSec-Wert als Offset zugeben, haben wir wenig Möglichkeiten, das zu vergiften +

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

+ rein numerisch würde das gehen, da ich Time::MAX | MIN sinnigerweise auf INT_MAX / 30 gesetzt habe. Das war vorausschauend.... +

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

+ es untergräbt gradezu den Sinn dedizierter Zeit-Entitäten +

+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
  • + auf den ersten Blick darf es nicht aussehen wie "aha, und hier kann ich alles machen" +
  • +
  • + so wie der Zugang beschrieben ist, ist er völlig logisch und konsistent +
  • +
  • + die erweiterte Möglichkeit erschließt sich erst dem aufmerksamen Leser +
  • +
  • + diese erweiterte Möglichkeit erweist sich aber letztlich als nichts anderes als die Konsequenz der formalen Definition +
  • +
+ +
+
+ + + + + + + + + + +
    +
  • + das wäre dann ein ctor mit einem 2. Argument +
  • +
  • + wer so einen ctor aufruft, weiß was er tut +
  • +
  • + das könnte auch später in eine MicroTic-Basisklasse übernommen werden +
  • +
+ +
+ + + + + + + + + + + +

+ Das versaut den bisher sehr sauberen Code von TimeValue, und läd gradezu dazu ein, hier beliebige Werte zu konstruieren. Im Hinblick darauf, daß ich umgestalten möchte TimeValue ⟶ MicroTicks, würde damit die Daseinsberechtigung untergraben, denn man kann nun nicht mehr sicher sein, daß MicroTicks ein sicherer Wert ist. +

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

+ ...ganz dunkel kommt mir die Erinnerung an eine „kognitive Dissonanz“ — die ich dann schell unter den Teppich gekehrt hatte, indem ich sie mit einem Assert dokumentierte.... +

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

+ und die unterliegende lumiera_time_of_gridpoint weicht da ebenfalls auffällig ab... +

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

+ ....also ein Feature, das zwar auf theoretischer Basis entwickelt wurde, aber nur im testgetriebenen Kontext; hier wohl entstanden aus der implementierungsmäßigen Symmetrie zu Grid::gridPoint(n) +

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