From 52d3231226251983842ad5a924543f5b3d9004fc Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sun, 18 Dec 2022 03:47:40 +0100 Subject: [PATCH] Timeline: finish ZoomWindow implementation and boundrary tests --- research/try.cpp | 7 +- src/lib/test/diagnostic-output.hpp | 68 +++++++++++++++++ src/lib/time/time.cpp | 9 +++ src/stage/model/zoom-window.hpp | 12 ++- tests/stage/model/zoom-window-test.cpp | 102 ++++++++++++++++++++----- wiki/thinkPad.ichthyo.mm | 57 ++++++++++++-- 6 files changed, 220 insertions(+), 35 deletions(-) create mode 100644 src/lib/test/diagnostic-output.hpp diff --git a/research/try.cpp b/research/try.cpp index 4f27b9394..4dc56f707 100644 --- a/research/try.cpp +++ b/research/try.cpp @@ -64,17 +64,12 @@ typedef unsigned int uint; #include "lib/format-cout.hpp" #include "lib/test/test-helper.hpp" +#include "lib/test/diagnostic-output.hpp" #include "lib/util.hpp" #include -#include -using std::string; -#define SHOW_TYPE(_TY_) \ - cout << "typeof( " << STRINGIFY(_TY_) << " )= " << lib::meta::typeStr<_TY_>() < +#include + + +using std::string; + +namespace lib { +namespace test{ + +}} // namespace lib::test + + + + +/* === test helper macros === */ + +/** + * Macro to print types and expressions to STDOUT, + * using Lumiera's string conversion framework + */ +#define SHOW_TYPE(_TY_) \ + cout << "typeof( " << STRINGIFY(_TY_) << " )= " << lib::test::showType<_TY_>() < lib::time::FSEC_MAX) + return (fractionalSeconds < 0? -1:+1) + * std::numeric_limits::max(); + return gavl_time_t(util::reQuant (fractionalSeconds.numerator() ,fractionalSeconds.denominator() ,lib::time::TimeValue::SCALE diff --git a/src/stage/model/zoom-window.hpp b/src/stage/model/zoom-window.hpp index c5a1e4903..2721e15ff 100644 --- a/src/stage/model/zoom-window.hpp +++ b/src/stage/model/zoom-window.hpp @@ -50,6 +50,9 @@ ** - the overall TimeSpan of the timeline, defining a start and end time ** - the visible interval („window“), likewise modelled as time::TimeSpan ** - the scale defined as pixels per second + ** @todo as of 12/2022 it rather seems the more general navigation should be abstracted + ** at a higher level, leaving ZoomWindow mostly focused on time scale handling. + ** ** ** # Interactions ** @@ -199,7 +202,10 @@ namespace model { } - /** + + + + /******************************************************//** * A component to ensure uniform handling of zoom scale * and visible interval on the timeline. Changes through * the mutator functions are validated and harmonised to @@ -237,7 +243,7 @@ namespace model { } ZoomWindow (TimeSpan timeline =TimeSpan{Time::ZERO, DEFAULT_CANVAS}) - : ZoomWindow{0, timeline} //see ensureConsistent() + : ZoomWindow{0, timeline} //see establishMetric() { } TimeSpan @@ -499,7 +505,7 @@ namespace model { * is thus specific to the ZoomWindow implementation. To sanitise, the denominator * is reduced logarithmically (bit-shift) sufficiently and then used as new quantiser, * thus ensuring that both denominator (=quantiser) and numerator are below limit. - * @warning the rational number must not be to large overall; this heuristic will fail + * @warning the rational number must not be too large overall; this heuristic will fail * on fractions with very large numerator and small denominator — however, for * the ZoomWindow, this case is not relevant, since the zoom factor is limited, * and other usages of rational numbers can be range checked explicitly. diff --git a/tests/stage/model/zoom-window-test.cpp b/tests/stage/model/zoom-window-test.cpp index 378701187..b431a0386 100644 --- a/tests/stage/model/zoom-window-test.cpp +++ b/tests/stage/model/zoom-window-test.cpp @@ -25,18 +25,51 @@ ** The timeline uses the abstraction of an »Zoom Window« ** to define the scrolling and temporal scaling behaviour uniformly. ** This unit test verifies this abstracted behaviour against the spec. + ** + ** # Fractional Seconds + ** + ** A defining trait of the ZoomWindow implementation — as it stands 12/2022 — is the use + ** of integer fractions for most scale and time interval calculations. The typical media + ** handling operations often rely on denomination into a divisor defined scale — be it + ** seconds divided by frame count (25fps), or be it audio samples like 1/96000 sec. + ** And for presentation in the UI, these uneven fractions need to be broken down into + ** a fixed pixel count, while the zoom factor can vary over several orders of magnitude. + ** Integer fractions are a technically brilliant solution to cope with this challenge, + ** without rounding discrepancies and accumulation of errors. + ** + ** However, there is a catch: The way fractional arithmetics are handled leads to lots + ** of multiplications, with the tendency to build up very large irreducible numbers, both in + ** numerator and denominator. In worst case, numeric wrap-around can happen even at seemingly + ** innocuous places. In an attempt to maintain the benefits of integer fraction arithmetics, + ** for ZoomWindow a set of »coping strategies« was developed, to detect and control the cases + ** when numbers „go south“. This approach is based on the observation that almost all + ** everyday time calculations happen within a rather limited domain, while the extended + ** time domain of years and centuries rather serves as a theoretical headroom. Thus it + ** seems reasonable to benefit from integer fractions within this everyday range, under + ** the condition that computations can be kept from derailing totally, when entering + ** the extended domain. + ** + ** To this end, we use the trick of introducing a minute numeric error, by re-quantising + ** huge numbers into a scale with a smaller denominator. We introduce the notion of »toxic« + ** numbers, which are defined by figures above 2^40 — irrespective if in numerator or in + ** denominator. This rather arbitrary choice is based on the observation that most + ** computation paths require to multiply with Time::SCALE (the µ-tick scale of 10^6), + ** which together with 2^40 just fits into the value range of int64_t. Thus, into all + ** crucial computation paths, a function `detox()` is wired, which remains inactive for + ** regular values, but automatically _sanitises extreme values._ Together with the + ** safety headroom built into the limits of the Lumiera lib::time::Time domain, + ** this allows to handle all valid time points and represent even the largest + ** possible lib::time::Duration::MAX. + ** + ** A major part of this test is dedicated to covering those hypothetical corner cases + ** and to ensure the defined behaviour can be maintained even under extreme conditions. + ** */ #include "lib/test/run.hpp" #include "lib/test/test-helper.hpp" #include "stage/model/zoom-window.hpp" -#include "lib/format-cout.hpp"//////////////TODO -#define SHOW_TYPE(_TY_) \ - cout << "typeof( " << STRINGIFY(_TY_) << " )= " << lib::meta::typeStr<_TY_>() <(poison * Time::SCALE)); // naive conversion to µ-ticks would leads to overflow + CHECK (-6 == rational_cast(poison * Time::SCALE)); // naive conversion to µ-ticks would lead to overflow CHECK (671453 == _raw(Time(FSecs(poison)))); // however the actual conversion routine is safeguarded CHECK (671453.812f == rational_cast(poison)*Time::SCALE); @@ -598,6 +632,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 @@ -922,14 +957,45 @@ namespace test { */ 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()); + { // for setup, request a window crossing time domain bounds + ZoomWindow win{ 1, TimeSpan{Time::MAX - TimeValue(23), Duration::MAX}}; + CHECK (win.overallSpan().duration() == Duration::MAX); // we get a canvas with the requested extension Duration::MAX + CHECK (win.overallSpan().end() == Time::MAX); // but shifted into domain to fit + CHECK (win.visible().duration() == LIM_HAZARD * 1000); // the visible window however is limited to be smaller + CHECK (win.visible().start()+win.visible().end() == Time::ZERO); // and (since this is a zoom-in) it is centred at origin + CHECK (win.px_per_sec() == 1_r/(LIM_HAZARD*1000)*Time::SCALE); // Zoom metric is likewise limited, to keep the numbers manageable + CHECK (win.px_per_sec() == 125_r/137438953472); + CHECK (win.pxWidth() == 1); + + win.nudgeVisiblePos (+1); // can work with this tiny window as expected + CHECK (win.visible().start() == Time::ZERO); + CHECK (win.visible().end() == LIM_HAZARD*1000); + CHECK (win.px_per_sec() == 125_r/137438953472); + CHECK (win.pxWidth() == 1); + + win.nudgeMetric (-1); // can not zoom out further + CHECK (win.px_per_sec() == 125_r/137438953472); + win.nudgeMetric (+1); // but can zoom in + CHECK (win.px_per_sec() == 125_r/68719476736); + CHECK (win.visible().start() == TimeValue(274877908523000)); + CHECK (win.visible().end() == TimeValue(824633722411000)); + CHECK (win.visible().duration() == LIM_HAZARD * 1000 / 2); + CHECK (win.pxWidth() == 1); + + win.setVisiblePos (Time{Time::MAX - TimeValue(23)}); + CHECK (win.visible().end() == Time::MAX); + CHECK (win.visible().duration() == LIM_HAZARD * 1000 / 2); + CHECK (win.px_per_sec() == 2_r/(LIM_HAZARD*1000)*Time::SCALE); + CHECK (win.pxWidth() == 1); + + win.setVisibleRange (TimeSpan{Time::MAX - TimeValue(23) // request a window exceeding domain, + ,FSecs{LIM_HAZARD, 1001}}); // but with a zoom slightly above minimal-zoom + CHECK (win.visible().end() == Time::MAX); // Resulting window is shifted into domain + CHECK (win.visible().duration() == Duration(FSecs{LIM_HAZARD, 1001})); // and has the requested extension + CHECK (win.visible().duration() == TimeValue(1098413214561438)); + CHECK ( FSecs(LIM_HAZARD, 1000) > FSecs(LIM_HAZARD, 1001)); // which is indeed smaller than the maximum duration + CHECK (win.px_per_sec() == 2003_r/2199023255552); + CHECK (win.pxWidth() == 1); } diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index f468e734e..1bbc6e2ac 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -38219,6 +38219,7 @@ + @@ -38469,9 +38470,34 @@ + + + + + + +

+ Zoom-Metrik verwendet integer-Brüche +

+ +
+ + + - - + + + + + + + + + + +
+ + @@ -39562,8 +39588,8 @@ - - + + @@ -41466,8 +41492,8 @@ - - + + @@ -41477,16 +41503,27 @@ - + + + + - + + + + + + + + + @@ -42830,6 +42867,10 @@ + + + +