Library: rectify clipping of time::Duration (see #1263)

This is a deep refactoring to allow to represent the distance
between all valid time points as a time::Offset or time::Duration.

By design this is possible, since Time::MAX was defined as 1/30 of
the maximum value technically representable as int64_t. However,
introducing a different limiter for offsets and durations turns
out difficult, due to the inconsistencies in the exiting hierarchy
of temporal entities. Which in turn seems to stem from the unfortunate
decision to make time entities immutable, see #1261

Since the limiter is hard wired into the `time::TimeValue` constructor,
we are forced to create a "backdoor" of sorts, to pass up values
with different limiting from child classes. This would not be so
much of a problem if calculations weren't forced to go through `TimeVar`,
which does not distinguish between time points and time durations.

This solution rearranges all checks to be performed now by time::Offset,
while time::Duration will only take the absolute value at construction,
based on the fact that there is no valid construction path to yield
a duration which does not go through an offset first.

Later, when we're ready to sort out the implementation base of time values
(see #1258), this design issue should be revisited
- either we'll allow derived classes explicitly to invoke the limiter functions
- or we may be able to have an automatic conversion path from clearly
  marked base implementation types, in which case we wouldn't use the
  buildRaw_() and _raw() "backdoor" functions any more...
This commit is contained in:
Fischlurch 2022-12-05 00:58:32 +01:00
parent 4d79bdce5f
commit 50c602ec3f
9 changed files with 450 additions and 53 deletions

View file

@ -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<f128>(Rat{num,den})) <= 1.0/abs(u));
ENSURE (abs (f128(res)/u - rational_cast<f128>(Rat{num,den})) <= 1.0/abs(u)
,"Requantisation error exceeded num=%lu / den=%lu -> res=%lu / quant=%lu"
, num, den, res, u);
return res;
}

View file

@ -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<int64_t> 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<Duration const&> (maxDelta);
}();
}} // namespace lib::Time

View file

@ -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<double> (*this);
}
inline Duration
Offset::abs() const
{
return Duration{*this};
}

View file

@ -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_)

View file

@ -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
}

View file

@ -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...

View file

@ -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)));

View file

@ -533,7 +533,7 @@ namespace test {
CHECK (negaTime < Time::ZERO);
Duration& evilDuration = reinterpret_cast<Duration&> (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());

View file

@ -40893,6 +40893,286 @@
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1670016158206" ID="ID_1376502254" MODIFIED="1670016169130" TEXT="setVisiblePos(weit-weg)">
<icon BUILTIN="flag-yellow"/>
<node CREATED="1670017946731" ID="ID_410930969" MODIFIED="1670017960838" TEXT="erst mal das visibleWin nahe an Time::MAX"/>
<node CREATED="1670017961849" ID="ID_1322153373" MODIFIED="1670017996849" TEXT="dann als gew&#xfc;nschte Position Time::MIN"/>
<node CREATED="1670024841228" ID="ID_327609209" MODIFIED="1670024849028" TEXT="ist gar nicht so wirklich toxisch">
<node CREATED="1670024857602" ID="ID_10543296" MODIFIED="1670024880586" TEXT="oder meine Funktionen sind inzwischen hinreichend abgesichert">
<icon BUILTIN="ksmiletris"/>
</node>
<node CREATED="1670024990169" ID="ID_737996846" MODIFIED="1670025107388" TEXT="die Division f&#xfc;r den posFactor funktioniert (&#xfc;berraschenderweise)">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<p>
also offensichtlich erkennt boost::rational die M&#246;glichkeit, den gemeinsamen Faktor 1e6 aus Z&#228;hler und Nenner wegzuk&#252;rzen. Da wir aber hier einen FSec-Wert als Offset zugeben, haben wir wenig M&#246;glichkeiten, das zu <i>vergiften</i>
</p>
</body>
</html></richcontent>
<icon BUILTIN="idea"/>
</node>
<node CREATED="1670024963460" ID="ID_16615073" MODIFIED="1670024981796" TEXT="parabolicAnchorRule kappt den Wert (da au&#xdf;erhalb 0...1)">
<icon BUILTIN="messagebox_warning"/>
</node>
<node COLOR="#435e98" CREATED="1670025133845" ID="ID_1582569279" MODIFIED="1670025153962" TEXT="infolgedessen sind alle weiteren Berechnungen trivial / no-op"/>
</node>
<node BACKGROUND_COLOR="#fdfdcf" COLOR="#ff0000" CREATED="1670025155874" ID="ID_725576556" MODIFIED="1670025170589" TEXT="aber: der Offset wird gekappt">
<icon BUILTIN="broken-line"/>
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1670025172247" ID="ID_962949808" MODIFIED="1670025190742" TEXT="konzeptionelles Problem im Time-handling-Framework">
<icon BUILTIN="messagebox_warning"/>
</node>
<node CREATED="1670025194925" ID="ID_674387327" MODIFIED="1670025296382" TEXT="Offsets und Durations k&#xf6;nnten 2*Time::MAX (bzw MIN) akzeptieren">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<p>
rein numerisch w&#252;rde das gehen, da ich Time::MAX | MIN sinnigerweise auf INT_MAX / 30 gesetzt habe. Das war vorausschauend....
</p>
</body>
</html></richcontent>
<icon BUILTIN="idea"/>
</node>
<node CREATED="1670025229777" ID="ID_1471304702" MODIFIED="1670025251370" TEXT="aber sie gehen alle stur durch TimeValue, und der kappt auf 1*Time::MAX (bzw MIN)"/>
<node CREATED="1670025302707" ID="ID_408211429" MODIFIED="1670025314899" TEXT="L&#xf6;sung: Loch bohren">
<icon BUILTIN="yes"/>
<node CREATED="1670025330923" ID="ID_664088863" MODIFIED="1670025372823" TEXT="das aktuelle Verhalten ist widersinnig und &#xfc;berraschend">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<p>
es untergr&#228;bt gradezu den Sinn dedizierter Zeit-Entit&#228;ten
</p>
</body>
</html></richcontent>
<icon BUILTIN="stop-sign"/>
</node>
<node CREATED="1670025386651" ID="ID_1227027753" MODIFIED="1670025402822" TEXT="Idee: der copy-ctor ist nicht limitiert">
<icon BUILTIN="idea"/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1670025414136" ID="ID_1966020819" MODIFIED="1670027497697" TEXT="Probleme und Aufgaben">
<icon BUILTIN="flag-yellow"/>
<node COLOR="#338800" CREATED="1670025543263" ID="ID_528446508" MODIFIED="1670120677591" TEXT="neue Limitierungs-Funktion schaffen (&#xb1; Duration::MAX)">
<icon BUILTIN="button_ok"/>
</node>
<node COLOR="#338800" CREATED="1670025513339" ID="ID_1622564783" MODIFIED="1670120675981" TEXT="der Differenz-ctor mu&#xdf; gr&#xf6;&#xdf;ere Offsets konstruieren k&#xf6;nnen">
<icon BUILTIN="button_ok"/>
</node>
<node COLOR="#338800" CREATED="1670025579708" ID="ID_967783770" MODIFIED="1670120673953" TEXT="neuer ctor f&#xfc;r FSecs">
<icon BUILTIN="button_ok"/>
</node>
<node COLOR="#338800" CREATED="1670025655456" ID="ID_79471093" MODIFIED="1670120670775" TEXT="die Funktion Offset::abs() neu definieren">
<icon BUILTIN="button_ok"/>
<node CREATED="1670025671469" ID="ID_807444515" MODIFIED="1670025688298" TEXT="bisher konstruiert sie einen TimeValue, und kappt deshalb">
<icon BUILTIN="messagebox_warning"/>
</node>
<node CREATED="1670025690862" ID="ID_1913450557" MODIFIED="1670080620421" TEXT="Idee: sie konstruiert eine Duration">
<arrowlink COLOR="#fefdb9" DESTINATION="ID_482497794" ENDARROW="Default" ENDINCLINATION="-192;-7;" ID="Arrow_ID_1014085079" STARTARROW="None" STARTINCLINATION="-207;16;"/>
<icon BUILTIN="idea"/>
</node>
</node>
<node COLOR="#338800" CREATED="1670025987045" ID="ID_1491328900" MODIFIED="1670120834295" TEXT="Duration braucht eine &#xbb;Hintert&#xfc;r&#xab;">
<icon BUILTIN="button_ok"/>
<node CREATED="1670026021871" ID="ID_1872786617" MODIFIED="1670026051423" TEXT="....durch welche man explizit einen Wert einbringen kann"/>
<node CREATED="1670026054003" ID="ID_873519529" MODIFIED="1670026069149" TEXT="und der nicht durch die Limitierung der Basis-Klasse TimeValue l&#xe4;uft"/>
<node CREATED="1670026074664" ID="ID_1460012408" MODIFIED="1670027376431" TEXT="diese Hintert&#xfc;r darf nicht offensichtlich sein">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<ul>
<li>
auf den ersten Blick darf es nicht aussehen wie &quot;aha, und hier kann ich alles machen&quot;
</li>
<li>
so wie der Zugang beschrieben ist, ist er v&#246;llig logisch und konsistent
</li>
<li>
die erweiterte M&#246;glichkeit erschlie&#223;t sich erst dem aufmerksamen Leser
</li>
<li>
diese erweiterte M&#246;glichkeit erweist sich aber letztlich als nichts anderes als die Konsequenz der formalen Definition
</li>
</ul>
</body>
</html></richcontent>
</node>
<node CREATED="1670026089995" ID="ID_5043644" MODIFIED="1670026098048" TEXT="und sie darf keinen Mi&#xdf;brauch gestatten">
<node CREATED="1670026101388" ID="ID_848547621" MODIFIED="1670026120917" TEXT="also die &#xb5;-Ticks m&#xfc;ssen nachher positiv sein"/>
<node CREATED="1670026121626" ID="ID_1970749903" MODIFIED="1670026130300" TEXT="und limitiert auf Duration::MAX"/>
</node>
<node COLOR="#690f14" CREATED="1670026155933" ID="ID_1357490453" MODIFIED="1670079761362" TEXT="Idee: die Limitierung auf TimeValue per ctor-Parameter steuerbar machen">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<ul>
<li>
das w&#228;re dann ein ctor mit einem 2. Argument
</li>
<li>
wer so einen ctor aufruft, wei&#223; was er tut
</li>
<li>
das k&#246;nnte auch sp&#228;ter in eine MicroTic-Basisklasse &#252;bernommen werden
</li>
</ul>
</body>
</html></richcontent>
<icon BUILTIN="idea"/>
<icon BUILTIN="button_cancel"/>
<node CREATED="1670027393582" ID="ID_630920961" MODIFIED="1670027410287" TEXT="damit w&#xfc;rden wir &#xfc;ber den normalen Basis-ctor eintreten"/>
<node CREATED="1670027412179" ID="ID_558474187" MODIFIED="1670027420848" TEXT="und Werte &#xfc;ber den copy-ctor &#xfc;bernehmen k&#xf6;nnen"/>
<node CREATED="1670027457629" ID="ID_730521318" MODIFIED="1670027484830" TEXT="der TimeValue-ctor k&#xf6;nnte dann sogar tats&#xe4;chlich die Limitierung enforcen"/>
<node CREATED="1670079580092" ID="ID_1783175998" MODIFIED="1670079740173" TEXT="Idee verworfen! das wird zu explizit und zu offen">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<p>
Das versaut den bisher sehr sauberen Code von TimeValue, und l&#228;d gradezu dazu ein, hier beliebige Werte zu konstruieren. Im Hinblick darauf, da&#223; ich umgestalten m&#246;chte TimeValue &#10230; MicroTicks, w&#252;rde damit die Daseinsberechtigung untergraben, denn man kann nun nicht mehr sicher sein, da&#223; MicroTicks ein <i>sicherer Wert </i>ist.
</p>
</body>
</html></richcontent>
<icon BUILTIN="stop-sign"/>
</node>
</node>
<node COLOR="#338800" CREATED="1670079773858" ID="ID_1230606534" MODIFIED="1670120643313" TEXT="protected Delta-Konstruktur">
<icon BUILTIN="button_ok"/>
<node CREATED="1670079791241" ID="ID_1046215971" MODIFIED="1670079812814" TEXT="wasseridcht, da nur von abgeleiteten Klassen aufgerufen"/>
<node CREATED="1670079817657" ID="ID_1263461807" MODIFIED="1670079837199" TEXT="nimmt zwei Argumente und macht unmittelbar die limitierte Offset-Berechnung"/>
</node>
<node COLOR="#435e98" CREATED="1670079953176" ID="ID_482497794" MODIFIED="1670120655974" TEXT="Duration mu&#xdf; selber f&#xfc;r die Implementierung des Absolutbetrages sorgen">
<linktarget COLOR="#fefdb9" DESTINATION="ID_482497794" ENDARROW="Default" ENDINCLINATION="-192;-7;" ID="Arrow_ID_1014085079" SOURCE="ID_1913450557" STARTARROW="None" STARTINCLINATION="-207;16;"/>
<icon BUILTIN="yes"/>
<node COLOR="#338800" CREATED="1670080498535" ID="ID_1961138610" MODIFIED="1670120658621" TEXT="der ctor Duration(Offset) macht diese Umwandlung">
<icon BUILTIN="button_ok"/>
</node>
<node COLOR="#338800" CREATED="1670080522251" ID="ID_1399417381" MODIFIED="1670120660266" TEXT="alle anderen Konstruktoren werden darauf aufgebaut">
<icon BUILTIN="button_ok"/>
</node>
</node>
</node>
<node CREATED="1670120920391" ID="ID_1544328925" MODIFIED="1670120940299" TEXT="bin nicht wirklich gl&#xfc;cklich mit dem Ergebnis">
<icon BUILTIN="smily_bad"/>
<node CREATED="1670120943775" ID="ID_1968873669" MODIFIED="1670120966072" TEXT="wir verwenden nun doch an einigen Stellen buildRaw_() und _raw()"/>
<node CREATED="1670120968395" ID="ID_189852460" MODIFIED="1670121014289" TEXT="noch schlimmer: ich mu&#xdf; jetzt Duration::MAX explizit per reinterpret_cast konstruieren"/>
<node CREATED="1670121025020" ID="ID_1030076787" MODIFIED="1670121088591" TEXT="&#x27f9; abgeleitete Klassen haben nicht wirklich den Zugangs-Weg, den sie brauchen"/>
<node CREATED="1670121089738" ID="ID_1726489244" MODIFIED="1670121157461" TEXT="und der Umweg &#xfc;ber Raw ist notwendig, die Zeit-Rechenfunktionen reichen nicht"/>
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1670121166296" ID="ID_1078651329" MODIFIED="1670121217977" TEXT="hei&#xdf;t: die Limitierung sitzt an der falschen Stelle">
<icon BUILTIN="messagebox_warning"/>
</node>
</node>
<node COLOR="#435e98" CREATED="1670120849448" ID="ID_633048851" MODIFIED="1670197941515" TEXT="QuantiserBasics_test scheitert">
<icon BUILTIN="broken-line"/>
<node CREATED="1670120873385" ID="ID_1718461090" MODIFIED="1670120892890" TEXT="er hat die bisher bestehende Limitierung von Duration explizit abgetestet"/>
<node CREATED="1670120893850" ID="ID_788600909" MODIFIED="1670120906512" TEXT="gut so, aber jetzt mu&#xdf; ich diesen Test wieder verstehen....">
<icon BUILTIN="smiley-neutral"/>
<node CREATED="1670191477920" ID="ID_1536550302" MODIFIED="1670191505143" TEXT="der Test ist ja wirklich sch&#xf6;n &#x201e;basic&#x201c;">
<icon BUILTIN="ksmiletris"/>
</node>
<node CREATED="1670191507368" ID="ID_1611277271" MODIFIED="1670191525161" TEXT="was hier scheitert ist eine Nebensache"/>
<node CREATED="1670191526749" ID="ID_1074184920" MODIFIED="1670191648400" TEXT="dokumentiert lediglich diese unsinnige Limitierung von Offset | Duration">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<p>
...ganz dunkel kommt mir die Erinnerung an eine &#8222;kognitive Dissonanz&#8220; &#8212; die ich dann schell unter den Teppich gekehrt hatte, indem ich sie mit einem Assert dokumentierte....
</p>
</body>
</html></richcontent>
</node>
<node CREATED="1670191650385" ID="ID_1618349097" MODIFIED="1670191678988" TEXT="mu&#xdf; also eigentlich nur diese nebenbei-Assertion der besseren L&#xf6;sung anpassen"/>
</node>
<node COLOR="#338800" CREATED="1670191680385" ID="ID_1961622718" MODIFIED="1670197933608" TEXT="sollte bei der Gelegenheit den Test erweitern und die Grenzen ausreizen">
<icon BUILTIN="button_ok"/>
<node COLOR="#338800" CREATED="1670191697554" ID="ID_1632102794" MODIFIED="1670191731569" TEXT="zwei weitere Test-Intervalle">
<icon BUILTIN="button_ok"/>
<node COLOR="#338800" CREATED="1670191712394" ID="ID_1670947823" MODIFIED="1670191745192" TEXT="gr&#xf6;&#xdf;er als Time::MAX aber kleiner als Duration::MAX">
<icon BUILTIN="button_ok"/>
</node>
<node COLOR="#338800" CREATED="1670191723322" ID="ID_299394278" MODIFIED="1670191744039" TEXT="exakt Duration::MAX">
<icon BUILTIN="button_ok"/>
</node>
</node>
<node COLOR="#338800" CREATED="1670191732657" ID="ID_1301403557" MODIFIED="1670191739848" TEXT="kombinieren mit Offset">
<icon BUILTIN="button_ok"/>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1670191753771" ID="ID_838942043" MODIFIED="1670191797559" TEXT="Nebenbei bemerkt: das Grid::gridAligned() - API ist &#x201e;&#xfc;berraschend&#x201c;">
<icon BUILTIN="messagebox_warning"/>
<node CREATED="1670191803951" ID="ID_62422663" MODIFIED="1670191874006" TEXT="das Ergebnis ist relativ zum Grid-Origin">
<icon BUILTIN="info"/>
</node>
<node CREATED="1670191815870" ID="ID_1441457979" MODIFIED="1670191824182" TEXT="tats&#xe4;chlich so, ist auch so dokumentiert"/>
<node CREATED="1670191825469" ID="ID_64245414" MODIFIED="1670191888904" TEXT="trotzdem schr&#xe4;g &#x2014; Input absolut, output relativ zum Origin">
<icon BUILTIN="stop-sign"/>
</node>
<node COLOR="#435e98" CREATED="1670191890964" ID="ID_77008687" MODIFIED="1670192169799" TEXT="gibts daf&#xfc;r einen Grund?">
<icon BUILTIN="help"/>
<node CREATED="1670191912001" ID="ID_1930426088" MODIFIED="1670191918387" TEXT="Verwendungen: nur Tests">
<icon BUILTIN="idea"/>
</node>
<node CREATED="1670191945933" ID="ID_838705253" MODIFIED="1670192032827" TEXT="Grid::timeOf() funktioniert andres (und wie erwartet)">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<p>
und die unterliegende lumiera_time_of_gridpoint weicht da ebenfalls auff&#228;llig ab...
</p>
</body>
</html></richcontent>
<icon BUILTIN="messagebox_warning"/>
</node>
<node CREATED="1670192005076" ID="ID_1578782665" MODIFIED="1670192067197" TEXT="Grid::timeOf() hat reale Verwendung in der Timecode-Handhabung"/>
<node CREATED="1670192068956" ID="ID_1880098778" MODIFIED="1670192159480" TEXT="Fazit: das ist ein &#xbb;hexagonales Rad&#xab;">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<p>
....also ein Feature, das zwar auf theoretischer Basis entwickelt wurde, aber nur im testgetriebenen Kontext; hier wohl entstanden aus der implementierungsm&#228;&#223;igen Symmetrie zu Grid::gridPoint(n)
</p>
</body>
</html></richcontent>
<icon BUILTIN="forward"/>
</node>
</node>
<node BACKGROUND_COLOR="#fdfdcf" COLOR="#ff0000" CREATED="1670192170956" ID="ID_1026606570" MODIFIED="1670192201815" TEXT="API &#xe4;ndern: gridAligned() soll den Origin mit einschlie&#xdf;en">
<icon BUILTIN="yes"/>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1670192209793" ID="ID_1904533160" MODIFIED="1670192229367" TEXT="Limitierung bedenken">
<icon BUILTIN="flag-yellow"/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1670192204551" ID="ID_1757335290" MODIFIED="1670192230352" TEXT="Implementierung anpassen">
<icon BUILTIN="flag-yellow"/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1670192239363" ID="ID_968994817" MODIFIED="1670192243152" TEXT="dokumentieren">
<icon BUILTIN="flag-yellow"/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1670192231262" ID="ID_185488622" MODIFIED="1670192236941" TEXT="Test anpassen">
<icon BUILTIN="flag-yellow"/>
</node>
</node>
</node>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1670027504991" ID="ID_824635341" MODIFIED="1670027509353" TEXT="Tests erg&#xe4;nzen">
<icon BUILTIN="flag-yellow"/>
</node>
</node>
</node>
</node>
</node>
</node>