From b1514f6632e58a68af45a3cd72f2d6b59a34f117 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sat, 17 Dec 2022 01:15:34 +0100 Subject: [PATCH] Timeline: properly handling extreme scroll-steps --- src/lib/time/timevalue.hpp | 8 +- src/stage/model/zoom-window.hpp | 16 ++-- tests/basics/time/time-value-test.cpp | 5 ++ tests/stage/model/zoom-window-test.cpp | 60 ++++++++++---- wiki/thinkPad.ichthyo.mm | 104 ++++++++++++++++++------- 5 files changed, 145 insertions(+), 48 deletions(-) diff --git a/src/lib/time/timevalue.hpp b/src/lib/time/timevalue.hpp index c7da7e788..47de5cfd7 100644 --- a/src/lib/time/timevalue.hpp +++ b/src/lib/time/timevalue.hpp @@ -731,10 +731,12 @@ namespace time { } inline TimeSpan - TimeSpan::conform() const ///< @note: implicitly capped to Duration::MAX + TimeSpan::conform() const ///< @note: implicitly capped to Duration::MAX { - return Offset{*this} + dur_ <= Time::MAX? TimeSpan{*this} - : TimeSpan{Time::MAX-dur_, Time::MAX}; + Offset extension{dur_}; + TimeValue start{_raw(*this)}; + return Offset{start} + extension > Time::MAX? TimeSpan{Time::MAX-extension, Time::MAX} + : TimeSpan{start, extension}; } diff --git a/src/stage/model/zoom-window.hpp b/src/stage/model/zoom-window.hpp index f362c1619..245aeeb76 100644 --- a/src/stage/model/zoom-window.hpp +++ b/src/stage/model/zoom-window.hpp @@ -230,8 +230,10 @@ namespace model { , afterWin_{afterAll_} , px_per_sec_{establishMetric (pxWidth, startWin_, afterWin_)} { - conformWindowToMetricLimits (this->pxWidth()); - ensureInvariants(); + pxWidth = this->pxWidth(); + ASSERT (0 < pxWidth); + conformWindowToMetricLimits (pxWidth); + ensureInvariants(pxWidth); } ZoomWindow (TimeSpan timeline =TimeSpan{Time::ZERO, DEFAULT_CANVAS}) @@ -414,11 +416,13 @@ namespace model { /** scroll by increments of half window size, possibly expanding. */ void - nudgeVisiblePos (int steps) + nudgeVisiblePos (int64_t steps) { - FSecs dur{afterWin_-startWin_}; // navigate half window steps - setVisibleRange (TimeSpan{Time{startWin_ + (dur*steps)/2} - , dur}); + FSecs dur{afterWin_-startWin_}; + int64_t limPages = 2 * rational_cast (MAX_TIMESPAN/dur); + steps = util::limited(-limPages, steps, +limPages); + setVisibleRange (TimeSpan{Time{startWin_ + Offset{(dur*steps)/2}} + , dur}); // navigate half window steps } /** diff --git a/tests/basics/time/time-value-test.cpp b/tests/basics/time/time-value-test.cpp index da537a1b7..db13725f6 100644 --- a/tests/basics/time/time-value-test.cpp +++ b/tests/basics/time/time-value-test.cpp @@ -302,10 +302,14 @@ namespace test{ CHECK (TimeValue{_raw(Time::MAX-Time(0,1)+Time(0,3))} == Time::MAX); // clipped at max CHECK (TimeValue{_raw(Time::MIN+Time(0,5)-Time(0,9))} == Time::MIN); // clipped at min + TimeValue outlier{Time::MIN - TimeValue(1)}; + CHECK (outlier < Time::MIN); + CHECK (Duration::MAX > Time::MAX); CHECK (_raw(Duration::MAX) < std::numeric_limits::max()); CHECK (Duration::MAX == Time::MAX - Time::MIN); CHECK (-Duration::MAX == Offset{Time::MIN - Time::MAX}); + CHECK (Duration{3*Offset{Time::MAX}} == Duration::MAX); CHECK ( Time::MAX + Duration::MAX > Duration::MAX); CHECK ( Time::MIN - Duration::MAX < -Duration::MAX); @@ -318,6 +322,7 @@ namespace test{ 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(outlier, 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))); diff --git a/tests/stage/model/zoom-window-test.cpp b/tests/stage/model/zoom-window-test.cpp index da94eb45c..6be400415 100644 --- a/tests/stage/model/zoom-window-test.cpp +++ b/tests/stage/model/zoom-window-test.cpp @@ -818,11 +818,11 @@ namespace test { CHECK (win.overallSpan().end() == Time::MAX); /*--Test-3-----------*/ - win.calibrateExtension(560); + win.calibrateExtension (560); CHECK (win.visible().duration() == TimeValue(280)); // effective window dimensions unchanged CHECK (win.px_per_sec() == 2000000_r/1); // but zoom metric slightly adapted - win.setOverallDuration(Duration::MAX); // now use maximally expanded canvas + win.setOverallDuration (Duration::MAX); // now use maximally expanded canvas Duration targetDur{Duration::MAX - FSecs(23)}; win.setVisibleDuration(targetDur); // and demand the duration be expanded almost full size @@ -836,7 +836,7 @@ namespace test { CHECK (win.pxWidth() == 560); // but pixel count is matched precisely /*--Test-4-----------*/ - win.setVisiblePos(Rat{std::numeric_limits::max()-23}); + win.setVisiblePos (Rat{std::numeric_limits::max()-23}); CHECK (win.visible().duration() == targetDur); // actual duration unchanged CHECK (win.px_per_sec() == 2003_r/2199023255552); CHECK (win.pxWidth() == 560); @@ -845,7 +845,7 @@ namespace test { CHECK (win.visible().start() == TimeValue(-307445734538825860)); /*--Test-5-----------*/ - win.calibrateExtension(561); // expand by 1 pixel + win.calibrateExtension (561); // expand by 1 pixel CHECK (win.visible().duration() > targetDur); // actual duration indeed increased CHECK (win.visible().duration() == Duration::MAX); // and then capped at maximum CHECK (win.visible().end() == Time::MAX); // but while initially the upper bound is increased... @@ -854,13 +854,13 @@ namespace test { CHECK (win.pxWidth() == 561); /*--Test-6-----------*/ - win.setVisibleDuration(Duration::MAX - Duration(TimeValue(1))); // request slightly different window duration + win.setVisibleDuration (Duration::MAX - Duration(TimeValue(1))); // request slightly different window duration CHECK (win.visible().end() == Time::MAX); // by arbitrary choice, the single µ-tick was removed at start CHECK (win.visible().start() == Time::MIN + TimeValue(1)); CHECK (win.px_per_sec() == 2007_r/2199023255552); // the smoothed nominal metric was also increased slightly CHECK (win.pxWidth() == 561); - win.setVisibleDuration(Duration(TimeValue(1))); // drastically zoom-in + win.setVisibleDuration (Duration(TimeValue(1))); // drastically zoom-in CHECK (win.visible().duration() == TimeValue(281)); // ...but we get more than 1 µ-tick CHECK (561_r/_FSecs(TimeValue(1)) > ZOOM_MAX_RESOLUTION); // because the requested window would exceed maximum zoom CHECK (win.px_per_sec() == 561000000_r/281); // and this conflict was resolved by increasing the window @@ -874,13 +874,47 @@ 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()); + ZoomWindow win{ 1, TimeSpan{Time::MAX, Duration{TimeValue(1)}}}; // use window of 1px size zoomed at 1 µ-tick + CHECK (win.visible().start() == Time::MAX - TimeValue(1)); // which is aligned to the end of the time domain + CHECK (win.visible().duration() == TimeValue(1)); + + win.nudgeVisiblePos (-2); // can be nudged by one window size to the left + CHECK (win.visible().start() == Time::MAX - TimeValue(2)); + + win.offsetVisiblePos (Offset{Duration::MAX}); // but excess offset is just absorbed + CHECK (win.visible().end() == Time::MAX); // window again positioned at the limit + CHECK (win.visible().start() == Time::MAX - TimeValue(1)); + CHECK (win.visible().duration() == TimeValue(1)); + CHECK (win.overallSpan().duration() == TimeValue(2)); + CHECK (win.px_per_sec() == 1000000); + CHECK (win.pxWidth() == 1); + + win.nudgeVisiblePos (std::numeric_limits::min()); // excess nudging likewise absorbed + CHECK (win.overallSpan().duration() == Duration::MAX); + CHECK (win.visible().duration() == TimeValue(1)); + CHECK (win.visible().start() == Time::MIN); // window now positioned at lower limit + CHECK (win.visible().end() == Time::MIN + TimeValue(1)); + CHECK (win.px_per_sec() == 1000000); + CHECK (win.pxWidth() == 1); + + win.calibrateExtension (460); + win.setVisibleDuration (Duration{Time::MAX - TimeValue(1)}); // arrange window to be 1 µ-tick less than half + CHECK (win.visible().duration() == Time::MAX - TimeValue(1)); + CHECK (win.visible().start() == Time::MIN); // ...so it spans [Time::MIN ... -1] + CHECK (win.visible().end() == TimeValue(-1)); + + win.nudgeVisiblePos (+2); // thus nudging two times by half-window size... + CHECK (win.visible().end() == Time::MAX - TimeValue(2)); // ...still fits into the time domain + CHECK (win.visible().start() == TimeValue(-1)); + win.nudgeVisiblePos (-1); + CHECK (win.visible().start() == TimeValue(-153722867280912930)); // navigation within domain works as expected + CHECK (win.visible().end() == TimeValue(+153722867280912929)); + + win.nudgeVisiblePos (+1000); // requesting an excessive nudge... + CHECK (ilogb(500.0 * _raw(Time::MAX)) == 67); // which — naively calculated — would overflow 64-bit + CHECK (win.visible().start() == TimeValue(+1)); // but the window just stopped aligned to the upper limit + CHECK (win.visible().end() == Time::MAX); + CHECK (win.pxWidth() == 460); } diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 129a4dc95..1bb8e98ee 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -39536,8 +39536,8 @@ - - + + @@ -39577,10 +39577,10 @@ - - - - + + + + @@ -39600,8 +39600,31 @@ - + + + + + + + + +

+ ⟹ dann paßt der Wertebereich im Extremfall grade so rein (1px-Window mit 1µ-Tick, und das per halb-Steps von Time::MIN ⟼ Time::MAX bringen:  lb <= 62 ) +

+ +
+ +
+ + + + + + + + +
@@ -40030,8 +40053,8 @@
- - + + @@ -41332,8 +41355,16 @@ - - + + + + + + + + + + @@ -41377,17 +41408,17 @@ - - - + + + - + - + - + @@ -41502,7 +41533,7 @@ - + @@ -41804,8 +41835,8 @@ - - + + @@ -42046,13 +42077,13 @@ - - + + - - + + @@ -42688,13 +42719,34 @@ + + + + + + + + + + + + + + + + + + + + + + + - -