From 068549dacee1a59ef03dcaae3a34675a9460e02d Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Thu, 8 Dec 2022 18:06:37 +0100 Subject: [PATCH] Timeline: now able to handle maximal zoom-out ...even zooming out to span the complete time domain (~19000 years). But only under the condition that the display window is sufficiently large in terms of pixels, so we can handle the computation without glitches. This should not be a relevant limitation in practice, since a window size of some 100 pixels is enough to handle Duration::MAX. Needless to add that it's hard to imagine a media timeline of such tremendous size... building on these Library changes, plus the safe-add function developed some days ago, it is now possible to mark a large displacement as `time::Offset`, and apply this to yield any valid time position, even extreme negative values --- src/stage/model/zoom-window.hpp | 45 ++++- tests/stage/model/zoom-window-test.cpp | 59 ++++-- wiki/thinkPad.ichthyo.mm | 251 +++++++++++++++++++++++-- 3 files changed, 319 insertions(+), 36 deletions(-) diff --git a/src/stage/model/zoom-window.hpp b/src/stage/model/zoom-window.hpp index 0c9b490fb..e38d24198 100644 --- a/src/stage/model/zoom-window.hpp +++ b/src/stage/model/zoom-window.hpp @@ -229,6 +229,7 @@ namespace model { , afterWin_{afterAll_} , px_per_sec_{establishMetric (pxWidth, startWin_, afterWin_)} { + conformWindowToMetricLimits (this->pxWidth()); ensureInvariants(); } @@ -676,6 +677,14 @@ namespace model { : start + Time{DEFAULT_CANVAS}; } + /** window size beyond that limit would yield + * numerically dangerous zoom factors */ + static FSecs + maxSaneWinExtension (uint pxWidth) + { + return FSecs{LIM_HAZARD * pxWidth, Time::SCALE}; + } + static Rat establishMetric (uint pxWidth, Time startWin, Time afterWin) { @@ -711,7 +720,7 @@ namespace model { dur = Rat(pxWidth) / detox (changedMetric); dur = min (dur, MAX_TIMESPAN); dur = max (dur, MICRO_TICK); // prevent window going void - dur = detox (dur); // prevent integer wrap in time conversion + dur = detox (dur); // prevent integer wrap in time conversion TimeVar timeDur{dur}; // prefer bias towards increased window instead of increased metric if (not isMicroGridAligned (dur)) @@ -722,7 +731,27 @@ namespace model { // re-check metric to maintain precise pxWidth px_per_sec_ = conformMetricToWindow (pxWidth); ENSURE (_FSecs(afterWin_-startWin_) < MAX_TIMESPAN); - ENSURE (px_per_sec_<= changedMetric); // bias towards increased window + auto sizeAtRequestedScale = approx(changedMetric)*approx(dur); + ENSURE (abs(pxWidth - sizeAtRequestedScale) <= 1); + } + + /** + * The zoom metric factor must not become "poisonous". + * This leads to a minimum possible zoom factor for a given pixWidth, + * thereby effectively limiting the maximum window extension. This + * constraint is enforced by reducing the current window size. + */ + void + conformWindowToMetricLimits (uint pxWidth) + { + REQUIRE (pxWidth > 0); + FSecs dur{afterWin_-startWin_}; + if (dur > maxSaneWinExtension (pxWidth)) + { + dur = maxSaneWinExtension (pxWidth); + placeWindowRelativeToAnchor (dur); + establishWindowDuration (dur); + } } void @@ -819,6 +848,7 @@ namespace model { uint px{pxWidth()}; startWin_ = window.start(); afterWin_ = ensureNonEmpty (startWin_, window.end()); + conformWindowToMetricLimits (px); startAll_ = min (startAll_, startWin_); afterAll_ = max (afterAll_, afterWin_); px_per_sec_ = conformMetricToWindow (px); @@ -835,6 +865,7 @@ namespace model { afterAll_ = ensureNonEmpty (startAll_, canvas.end()); startWin_ = window.start(); afterWin_ = ensureNonEmpty (startWin_, window.end()); + conformWindowToMetricLimits (px); px_per_sec_ = conformMetricToWindow (px); ensureInvariants (px); } @@ -845,10 +876,11 @@ namespace model { void mutateScale (Rat changedMetric) { + uint px{pxWidth()}; + changedMetric = max (changedMetric, px / maxSaneWinExtension(px)); changedMetric = min (detox(changedMetric), ZOOM_MAX_RESOLUTION); if (changedMetric == px_per_sec_) return; - uint px{pxWidth()}; Rat changeFactor{changedMetric / px_per_sec_}; FSecs dur{afterWin_ - startWin_}; dur /= changeFactor; @@ -868,10 +900,12 @@ namespace model { void mutateDuration (FSecs duration, uint px =0) { - if (duration <= 0) - duration = DEFAULT_CANVAS; if (px==0) px = pxWidth(); + if (duration <= 0) + duration = DEFAULT_CANVAS; + else if (duration > maxSaneWinExtension (px)) + duration = maxSaneWinExtension (px); Rat changedMetric = Rat(px) / duration; conformWindowToMetric (changedMetric); } @@ -884,6 +918,7 @@ namespace model { pxWidth = util::limited (1u, pxWidth, MAX_PX_WIDTH); FSecs adaptedWindow{Rat{pxWidth} / px_per_sec_}; adaptedWindow = max (adaptedWindow, MICRO_TICK); // prevent void window + adaptedWindow = min (adaptedWindow, maxSaneWinExtension (pxWidth)); establishWindowDuration (adaptedWindow); px_per_sec_ = conformMetricToWindow (pxWidth); ensureInvariants (pxWidth); diff --git a/tests/stage/model/zoom-window-test.cpp b/tests/stage/model/zoom-window-test.cpp index bd5b7b6f7..46ba63d13 100644 --- a/tests/stage/model/zoom-window-test.cpp +++ b/tests/stage/model/zoom-window-test.cpp @@ -529,11 +529,6 @@ namespace test { CHECK (win.pxWidth() == 1); CHECK (TimeSpan(_t(10), _t(0)).duration() == Duration(FSecs(10))); // TimeSpan is always properly oriented by construction - Time negaTime = _t(-10); - 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! } @@ -685,14 +680,44 @@ namespace test { void 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()); -// CHECK (win.visible() == TimeSpan(_t(0), _t(23))); -// CHECK (win.px_per_sec() == 25); -// CHECK (win.pxWidth() == 575); + ZoomWindow win{3, TimeSpan{_t(-1,2), _t(1,2)}}; // setup ZoomWindow to very small pixel size (3px) + CHECK (win.overallSpan().duration() == _t(1)); + CHECK (win.px_per_sec() == 3_r/1); + CHECK (win.pxWidth() == 3); + win.setOverallRange(TimeSpan{Time::MIN, Time::MAX}); // ...and then also expand canvas to maximal size + CHECK (_raw(win.overallSpan().duration()) == 614891469123651720); + CHECK (_raw(win.visible().duration()) == 1000000); + CHECK (win.px_per_sec() == 3_r/1); + CHECK (win.pxWidth() == 3); + + Rat bruteZoom{3_r/(int64_t{1}<<60)}; + win.setMetric (bruteZoom); // zoom out beyond what is possible and to a toxic factor + + CHECK (_raw(win.overallSpan().duration()) == 614891469123651720); // canvas size not changed + CHECK (_raw(win.visible().duration()) == 3298534883328); // window was expanded, + CHECK (_raw(win.visible().duration()) < int64_t{1}<<60 ); // ...but not as much as demanded + CHECK (_raw(win.visible().duration()) == 3 * LIM_HAZARD); // In fact it was capped at a built-in limit based on pixel size, + // to prevent formation of dangerous numbers within metric calculations + CHECK (win.visible().start() == - win.visible().end()); // window has been expanded symmetrically to existing position + CHECK (win.px_per_sec() > bruteZoom); // the actual zoom factor also reflects the applied limitation, + CHECK (win.px_per_sec() == 15625_r/17179869184); // to ensure the denominator does not exceed LIM_HAZARD + CHECK (win.px_per_sec() == 1000000_r/LIM_HAZARD); + CHECK (win.px_per_sec() == 3 / _FSecs(win.visible().duration())); // and this value also conforms with the pixel size and window duration + CHECK (win.pxWidth() == 3); + + win.setMetric (5_r/std::numeric_limits::max()); // same limiting applies to even more nasty values + CHECK (_raw(win.visible().duration()) == 3298534883328); // still unchanged at limit + CHECK (win.px_per_sec() == 15625_r/17179869184); + CHECK (win.pxWidth() == 3); + + win.setMetric (1000001_r/LIM_HAZARD); // but zooming in more than that limit will be honored + CHECK (_raw(win.visible().duration()) == 3298531584796); // ...window now slightly reduced in size + CHECK (_raw(win.visible().duration()) < 3 * LIM_HAZARD); + CHECK (win.px_per_sec() = 750000_r/824632896199); // ...yet effective zoom factor was still marginally adjusted for safety + CHECK (win.px_per_sec() > 1000000_r/LIM_HAZARD); + CHECK (win.px_per_sec() > 1000001_r/LIM_HAZARD); // (this is what was requested) + CHECK (win.px_per_sec() < 1000002_r/LIM_HAZARD); // (thus result was slightly adjusted upwards) + CHECK (win.pxWidth() == 3); } @@ -701,6 +726,14 @@ namespace test { void safeguard_extremeTimePos() { +// 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()); +// CHECK (win.visible() == TimeSpan(_t(0), _t(23))); +// CHECK (win.px_per_sec() == 25); +// CHECK (win.pxWidth() == 575); } diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index b5fb84485..880744bac 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -39706,15 +39706,64 @@ - + - + + + + + + + + + + + + + + +

+ wenn der Zoom-Faktor einen größeen Nenner bekommt, wird er nach den inzwischen etablierten Richtlinien als »toxisch« betrachtet, und das heißt, die typischen Berechnungen im ZoomWindow könnten entgleisen +

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

+ hinreichend große Fenster (ab ein paar hunder Pixeln) können die ganze Zeitdomäne abdecken +

+ +
+ + + + + + + + +

+ alle die hier angewendeten Heuristiken werden im normalen Einsatz niemals aktiviert, und daher profitieren wir normalerweise von der extremen Präzision der Integer-Bruchrechnung +

+ +
+ +
+
+
@@ -39776,7 +39825,7 @@ - + @@ -39904,9 +39953,175 @@ - - - + + + + + + + + + + + +

+ Problem: detox() macht den zu Null +

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

+ "quasi statisch" meint hier: hängt vom pxWith-Parameter ab, ist aber ansonsten unveränderlich (aber eben keine compile-time Konstante) +

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

+ also davor eine neue Funktion: conformWindowToMetricLimits(px) +

+ +
+ + + + + +

+ aus praktischen Gründen erfolgt die Rechnung in FSecs, und das ist hier nicht gefährlich, weil der Nenner dieses Wertes in der Metrik-Berechnung nur mit den Pixeln in Berührung kommt +

+

+ Hilfsfunktion maxSaneWinExtension(px) = FSecs{LIM_HAZARD * pxWidth, Time::SCALE}; +

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

+ ...zwar ist der Bias in Richtung auf ein größeres Fenster weiterhin im Code gegeben, aber dieser Fall hier zeigt eben, daß er durch andere Rundungs-Effekte sogar übersteuert werden kann; aber wir können sogar eine stärkere Bedinung errichten, nämlich daß wir nicht zu weit von der Vorgabe abweichen +

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

+ ...damit meine ich: ausrechnen wie viele Pixel die jetzt eingestelle Fenstergröße in der ursprünglich geforderten Metrik einnehmen würde..... +

+ +
+
+ + + + + + +
+
+ + + +
@@ -39917,8 +40132,8 @@ - - + + @@ -39926,7 +40141,7 @@ - + @@ -39936,8 +40151,8 @@ - - + + @@ -40530,7 +40745,7 @@ - + @@ -40917,7 +41132,7 @@ - + @@ -40936,7 +41151,7 @@ - + @@ -40954,8 +41169,8 @@ - - + + @@ -41072,7 +41287,7 @@ - + @@ -41220,8 +41435,8 @@ - +