From 5e595c57caf8227f92f529f1755c45f28c64ce87 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Wed, 14 Dec 2022 03:00:07 +0100 Subject: [PATCH] Timeline: automatically orient and shift into allowed time domain Note: changing behaviour of TimeSpan to possibly flip start and end, and also to use Offset as Offset and then re-orient, since this seems the least surprising behaviour. These changes carry over into changed default and limiting on ZoomWindow constructor and various mutators, and most notably shifting the time span always into allowed domain. --- src/lib/time/timevalue.hpp | 27 +++-- src/stage/model/zoom-window.hpp | 38 ++++--- tests/basics/time/time-value-test.cpp | 17 +++- tests/stage/model/zoom-window-test.cpp | 61 +++++++++-- wiki/thinkPad.ichthyo.mm | 134 ++++++++++++++----------- 5 files changed, 181 insertions(+), 96 deletions(-) diff --git a/src/lib/time/timevalue.hpp b/src/lib/time/timevalue.hpp index 926d9ea0e..e497979df 100644 --- a/src/lib/time/timevalue.hpp +++ b/src/lib/time/timevalue.hpp @@ -565,25 +565,25 @@ namespace time { , dur_(length) { } - TimeSpan(TimeValue const& start, Offset const& reference_distance) - : Time(start) - , dur_(reference_distance) - { } - - TimeSpan(TimeValue const& start, TimeValue const& end) - : Time(start) - , dur_(Offset(start,end)) - { } - TimeSpan(TimeValue const& start, FSecs(duration_in_secs)) : Time(start) , dur_(duration_in_secs) { } + TimeSpan(TimeValue const& start, TimeValue const& end) + : Time(start<=end? start:end) + , dur_(Offset(start,end)) + { } + + TimeSpan(TimeValue const& start, Offset const& reference_distance) + : TimeSpan{start, Time{start} + reference_distance} + { } + TimeSpan() : TimeSpan(Time::ZERO, Time::ZERO) { } + TimeSpan conform() const; ///< @return a copy conformed to time domain limits Duration& duration() @@ -715,6 +715,13 @@ namespace time { return symmetricLimit (res, Duration::MAX); } + inline TimeSpan + TimeSpan::conform() const ///< @note: implicitly capped to Duration::MAX + { + return Offset{*this} + dur_ <= Time::MAX? TimeSpan{*this} + : TimeSpan{Time::MAX-dur_, Time::MAX}; + } + inline TimeVar::TimeVar (FSecs const& fractionalSeconds) diff --git a/src/stage/model/zoom-window.hpp b/src/stage/model/zoom-window.hpp index 776befd69..54603036d 100644 --- a/src/stage/model/zoom-window.hpp +++ b/src/stage/model/zoom-window.hpp @@ -224,8 +224,8 @@ namespace model { public: ZoomWindow (uint pxWidth, TimeSpan timeline =TimeSpan{Time::ZERO, DEFAULT_CANVAS}) - : startAll_{timeline.start()} - , afterAll_{ensureNonEmpty(startAll_, timeline.end())} + : startAll_{ensureNonEmpty(timeline).start()} + , afterAll_{ensureNonEmpty(timeline).end()} , startWin_{startAll_} , afterWin_{afterAll_} , px_per_sec_{establishMetric (pxWidth, startWin_, afterWin_)} @@ -673,11 +673,13 @@ namespace model { * - visibleWindow ⊂ Canvas */ - static TimeValue - ensureNonEmpty (Time start, TimeValue endPoint) + static TimeSpan + ensureNonEmpty (TimeSpan const& span) { - return (start < endPoint)? endPoint - : start + Time{DEFAULT_CANVAS}; + return TimeSpan{span.start() + ,util::isnil(span.duration())? Duration{DEFAULT_CANVAS} + : span.duration() + }.conform(); } /** Assertion helper: resulting pxWidth matches expectations */ @@ -847,8 +849,6 @@ namespace model { { FSecs dur{afterWin_-startWin_}; 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_)) @@ -874,6 +874,8 @@ namespace model { } ENSURE (startAll_ <= startWin_); ENSURE (afterWin_ <= afterAll_); + ENSURE (Time::MIN <= startWin_); + ENSURE (afterWin_ <= Time::MAX); } void @@ -922,8 +924,8 @@ namespace model { void mutateCanvas (TimeSpan canvas) { - startAll_ = canvas.start(); - afterAll_ = ensureNonEmpty (startAll_, canvas.end()); + startAll_ = ensureNonEmpty(canvas).start(); + afterAll_ = ensureNonEmpty(canvas).end(); ensureInvariants(); } @@ -934,12 +936,11 @@ namespace model { mutateWindow (TimeSpan window) { uint px{pxWidth()}; - startWin_ = window.start(); - afterWin_ = ensureNonEmpty (startWin_, window.end()); + startWin_ = ensureNonEmpty(window).start(); + afterWin_ = ensureNonEmpty(window).end(); conformWindowToMetricLimits (px); startAll_ = min (startAll_, startWin_); afterAll_ = max (afterAll_, afterWin_); - px_per_sec_ = conformMetricToWindow (px); ensureInvariants (px); } @@ -949,12 +950,11 @@ namespace model { mutateRanges (TimeSpan canvas, TimeSpan window) { uint px{pxWidth()}; - startAll_ = canvas.start(); - afterAll_ = ensureNonEmpty (startAll_, canvas.end()); - startWin_ = window.start(); - afterWin_ = ensureNonEmpty (startWin_, window.end()); + startAll_ = ensureNonEmpty(canvas).start(); + afterAll_ = ensureNonEmpty(canvas).end(); + startWin_ = ensureNonEmpty(window).start(); + afterWin_ = ensureNonEmpty(window).end(); conformWindowToMetricLimits (px); - px_per_sec_ = conformMetricToWindow (px); ensureInvariants (px); } @@ -997,7 +997,6 @@ namespace model { adaptedWindow = max (adaptedWindow, MICRO_TICK); // prevent void window adaptedWindow = min (adaptedWindow, maxSaneWinExtension (pxWidth)); establishWindowDuration (adaptedWindow); - px_per_sec_ = conformMetricToWindow (pxWidth); ensureInvariants (pxWidth); } @@ -1020,7 +1019,6 @@ namespace model { establishWindowDuration (duration); startAll_ = min (startAll_, startWin_); afterAll_ = max (afterAll_, afterWin_); - px_per_sec_ = conformMetricToWindow (px); ensureInvariants (px); } diff --git a/tests/basics/time/time-value-test.cpp b/tests/basics/time/time-value-test.cpp index a3c611c7d..da537a1b7 100644 --- a/tests/basics/time/time-value-test.cpp +++ b/tests/basics/time/time-value-test.cpp @@ -312,6 +312,16 @@ namespace test{ CHECK ( Offset{Time::MAX + Duration::MAX} == Duration::MAX); // clipped at max CHECK ( Offset{Time::MIN - Duration::MAX} == -Duration::MAX); // clipped at min CHECK (Duration{Offset{Time::MIN - Duration::MAX}} == Duration::MAX); // duration is absolute + + CHECK (TimeSpan(Time::MIN, Time::MAX) == TimeSpan(Time::MAX, Time::MIN)); + CHECK (TimeSpan(Time::MAX, Duration::MAX).start() == Time::MAX); + CHECK (TimeSpan(Time::MAX, Duration::MAX).end() == Time::MAX + Duration::MAX); // note: end() can yield value beyond [Time::MIN...Time::MAX] + CHECK (TimeSpan(Time::MAX, Duration::MAX).duration() == Duration::MAX); + CHECK (TimeSpan(Time::MAX, Duration::MAX).conform() == TimeSpan(Time::MIN,Duration::MAX)); + CHECK (TimeSpan(Time::MAX, Offset(FSecs(-1))) == TimeSpan(Time::MAX-Offset(FSecs(1)), FSecs(1))); + CHECK (TimeSpan(Time::MAX, FSecs(5)).start() == Time::MAX); + CHECK (TimeSpan(Time::MAX, FSecs(5)).duration() == Duration(FSecs(5))); + CHECK (TimeSpan(Time::MAX, FSecs(5)).conform() == TimeSpan(Time::MAX-Offset(FSecs(5)), FSecs(5))); } @@ -368,9 +378,9 @@ namespace test{ compareTimeSpan (Time const& org) { TimeSpan span1 (org, org+org); // using the distance between start and end point - TimeSpan span2 (org, Offset(org, Time::ZERO)); // note: the offset is taken absolute, as Duration + TimeSpan span2 (org+org, org); // note: TimeSpan is oriented automatically TimeSpan span3 (org, FSecs(5,2)); // Duration given explicitly, in seconds - TimeSpan span4 (org, FSecs(5,-2)); // again: the Duration is taken absolute + TimeSpan span4 (org, FSecs(5,-2)); // note: fractional seconds taken absolute, as Duration CHECK (span1 == span2); CHECK (span2 == span1); @@ -386,8 +396,7 @@ namespace test{ CHECK (span2 != span4); CHECK (span4 != span2); - // especially note that creating a TimeSpan - // based on offset will take the offset absolute + // note that TimeSpan is oriented at creation CHECK (span1.end() == span2.end()); CHECK (span3.end() == span4.end()); diff --git a/tests/stage/model/zoom-window-test.cpp b/tests/stage/model/zoom-window-test.cpp index c1f6f38bc..6b02e5591 100644 --- a/tests/stage/model/zoom-window-test.cpp +++ b/tests/stage/model/zoom-window-test.cpp @@ -94,6 +94,8 @@ namespace test { safeguard_extremeZoomOut(); safeguard_extremeTimePos(); safeguard_extremeOffset(); + safeguard_verySmall(); + safeguard_veryDeep(); } @@ -523,7 +525,7 @@ namespace test { ZoomWindow win{1}; win.setVisibleDuration(Duration{FSecs(1,25)}); win.setOverallRange(TimeSpan(_t(10), _t(0))); // set an "reversed" overall time range - CHECK (win.overallSpan() == TimeSpan(_t(10), _t(20))); // range has been flipped forward + CHECK (win.overallSpan() == TimeSpan(_t(0), _t(10))); // range has been oriented forward CHECK (win.visible().duration() == Time(40,0)); CHECK (win.px_per_sec() == 25); CHECK (win.pxWidth() == 1); @@ -652,7 +654,7 @@ namespace test { CHECK (win.pxWidth() == 575); - win.setOverallStart(Time::MAX - TimeValue(23)); // preparation for Test-4 : shift canvas to end of time + win.setOverallRange(TimeSpan{Time::MAX, Offset{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}); @@ -784,19 +786,29 @@ namespace test { } - /** @test verify ZoomWindow code can navigate extremal time positions. + /** @test verify ZoomWindow code can navigate extremal time positions, + * thereby observing domain bounds without numeric wrap. */ void safeguard_extremeTimePos() { + ZoomWindow win{559, TimeSpan{Time::MAX, Duration{TimeValue(3)}}}; // setup a very small window clinging to Time::MAX + CHECK (win.visible().duration() == TimeValue(280)); // duration expanded due to MAX_ZOOM limit + CHECK (win.visible().start() == TimeValue(307445734561825580)); // and properly oriented and aligned within domain + CHECK (win.visible().end() == TimeValue(307445734561825860)); + CHECK (win.visible().end() == Time::MAX); + CHECK (win.visible() == win.overallSpan()); + CHECK (win.px_per_sec() == 559_r/280*Time::SCALE); + CHECK (win.px_per_sec() == 13975000_r/7); + CHECK (win.pxWidth() == 559); + // SHOW_EXPR(win.overallSpan()); // SHOW_EXPR(_raw(win.overallSpan().duration())); // SHOW_EXPR(_raw(win.visible().duration())); +// SHOW_EXPR(_raw(win.visible().start())); +// SHOW_EXPR(_raw(win.visible().end())); // SHOW_EXPR(win.px_per_sec()); // SHOW_EXPR(win.pxWidth()); -// CHECK (win.visible() == TimeSpan(_t(0), _t(23))); -// CHECK (win.px_per_sec() == 25); -// CHECK (win.pxWidth() == 575); } @@ -805,6 +817,43 @@ namespace test { void safeguard_extremeOffset() { +// SHOW_EXPR(win.overallSpan()); +// SHOW_EXPR(_raw(win.overallSpan().duration())); +// SHOW_EXPR(_raw(win.visible().duration())); +// SHOW_EXPR(_raw(win.visible().start())); +// SHOW_EXPR(_raw(win.visible().end())); +// SHOW_EXPR(win.px_per_sec()); +// SHOW_EXPR(win.pxWidth()); + } + + + /** @test verify ZoomWindow can handle excessively small windows. + */ + void + safeguard_verySmall() + { +// SHOW_EXPR(win.overallSpan()); +// SHOW_EXPR(_raw(win.overallSpan().duration())); +// SHOW_EXPR(_raw(win.visible().duration())); +// SHOW_EXPR(_raw(win.visible().start())); +// SHOW_EXPR(_raw(win.visible().end())); +// SHOW_EXPR(win.px_per_sec()); +// SHOW_EXPR(win.pxWidth()); + } + + + /** @test verify ZoomWindow and handle extreme zoom-in. + */ + void + safeguard_veryDeep() + { +// SHOW_EXPR(win.overallSpan()); +// SHOW_EXPR(_raw(win.overallSpan().duration())); +// SHOW_EXPR(_raw(win.visible().duration())); +// SHOW_EXPR(_raw(win.visible().start())); +// SHOW_EXPR(_raw(win.visible().end())); +// SHOW_EXPR(win.px_per_sec()); +// SHOW_EXPR(win.pxWidth()); } }; diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 14f856604..00476d992 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -39541,11 +39541,21 @@ - - + + + + - - + + + + + + + + + + @@ -39559,14 +39569,20 @@ - + + + + + + + - + @@ -39653,10 +39669,19 @@ - - + + + + + + + +

+ der Nenner ist limitiert (kleiner Time::SCALE) und nicht toxisch +

+ +
- @@ -39664,7 +39689,7 @@ - + @@ -39709,6 +39734,7 @@ + @@ -39750,7 +39776,7 @@ - + @@ -39812,40 +39838,11 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + @@ -39930,6 +39927,20 @@ + + + + + + +

+ ...und das betrachte ich als gutmütig und hinreichend abgesichert... +

+ +
+ + +
@@ -40024,7 +40035,7 @@ - + @@ -40141,7 +40152,7 @@ - + @@ -40192,7 +40203,7 @@ - + @@ -40700,7 +40711,7 @@ - + @@ -40733,7 +40744,7 @@ - + @@ -40875,7 +40886,7 @@ - + @@ -41233,21 +41244,32 @@ - + + + + + + - - + + + + + - - + + + + + - +