Timeline: stress-test with excessive zoom-out reveals further weakness
As it turns out, the calculation path initially choosen for the mutateScale(Rat) was needlessly indirect, and also duplicated several of the safeguards, meanwhile implemented way better in conformWindowToMetric(Rat) Thus, instead of relatively re-scaling the window, now we just limit the given zoomFactor and pass it to conformWindowToMetric()
This commit is contained in:
parent
ce3713d872
commit
40f003a962
3 changed files with 198 additions and 41 deletions
|
|
@ -679,39 +679,21 @@ namespace model {
|
|||
|
||||
/** Assertion helper: resulting pxWidth matches expectations */
|
||||
static void
|
||||
__assertMatchesExpectedPixWidth (Rat zoomFactor, FSecs duration, uint pxWidth)
|
||||
ENSURE_matchesExpectedPixWidth (Rat zoomFactor, FSecs duration, uint pxWidth)
|
||||
{
|
||||
auto sizeAtRequestedScale = calcPixelsForDurationAtScale (zoomFactor, duration);
|
||||
auto sizeAtRequestedScale = approx(zoomFactor) * approx(duration);
|
||||
ENSURE (abs(pxWidth - sizeAtRequestedScale) <= 1
|
||||
,"ZoomWindow: established size or metric misses expectation "
|
||||
"by more than 1px. %upx != %1.6f expected pixel."
|
||||
, pxWidth, sizeAtRequestedScale);
|
||||
}
|
||||
|
||||
static void
|
||||
__assertMetricConforms2pxWidth (Rat zoomFactor, FSecs duration, uint pxWidth)
|
||||
{
|
||||
if (util::can_represent_Product(zoomFactor, duration))
|
||||
{
|
||||
ENSURE (pxWidth == rational_cast<uint> (zoomFactor*duration)
|
||||
,"ZoomWindow: established zoom factor misses expected "
|
||||
"width in pixels: %upx != %upx (resulting)"
|
||||
, pxWidth, rational_cast<uint> (zoomFactor*duration));
|
||||
}
|
||||
else
|
||||
{
|
||||
auto sizeAtRequestedScale = approx(zoomFactor) * approx(duration);
|
||||
ENSURE (pxWidth == uint(sizeAtRequestedScale + 0.5)
|
||||
,"ZoomWindow: established zoom factor misses expected "
|
||||
"width in pixels: %upx != %1.8f px (resulting)"
|
||||
, pxWidth, sizeAtRequestedScale);
|
||||
}
|
||||
}
|
||||
|
||||
/** @remark indirect calculation path to avoid overflow on large durations */
|
||||
/** calculate `rational_cast<uint> (zoomFactor * duration)`
|
||||
* @remark indirect calculation path to avoid overflow on large durations
|
||||
*/
|
||||
static int64_t
|
||||
calcPixelsForDurationAtScale (Rat zoomFactor, FSecs duration)
|
||||
{// calculate rational_cast<uint> (zoomFactor * duration)
|
||||
{// break down the integer division into several steps...
|
||||
auto zn = zoomFactor.numerator();
|
||||
auto zd = zoomFactor.denominator();
|
||||
auto dn = duration.numerator();
|
||||
|
|
@ -776,7 +758,7 @@ namespace model {
|
|||
REQUIRE (changedMetric > 0);
|
||||
REQUIRE (afterWin_> startWin_);
|
||||
FSecs dur{afterWin_-startWin_};
|
||||
uint pxWidth = rational_cast<uint> (px_per_sec_*dur);
|
||||
uint pxWidth = calcPixelsForDurationAtScale (px_per_sec_, dur);
|
||||
dur = Rat(pxWidth) / detox (changedMetric);
|
||||
dur = min (dur, MAX_TIMESPAN);
|
||||
dur = max (dur, MICRO_TICK); // prevent window going void
|
||||
|
|
@ -791,7 +773,7 @@ namespace model {
|
|||
// re-check metric to maintain precise pxWidth
|
||||
px_per_sec_ = conformMetricToWindow (pxWidth);
|
||||
ENSURE (_FSecs(afterWin_-startWin_) < MAX_TIMESPAN);
|
||||
__assertMatchesExpectedPixWidth (changedMetric, dur, pxWidth);
|
||||
ENSURE_matchesExpectedPixWidth (changedMetric, afterWin_-startWin_, pxWidth);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -939,18 +921,7 @@ namespace model {
|
|||
changedMetric = max (changedMetric, px / maxSaneWinExtension(px));
|
||||
changedMetric = min (detox(changedMetric), ZOOM_MAX_RESOLUTION);
|
||||
if (changedMetric == px_per_sec_) return;
|
||||
|
||||
Rat changeFactor{changedMetric / px_per_sec_};
|
||||
FSecs dur{afterWin_ - startWin_};
|
||||
dur /= changeFactor;
|
||||
if (dur > FSecs{afterAll_ - startAll_})
|
||||
{// limit to the overall timespan...
|
||||
startWin_ = startAll_;
|
||||
afterWin_ = afterAll_;
|
||||
px_per_sec_ = conformMetricToWindow(px);
|
||||
}
|
||||
else
|
||||
mutateDuration (dur, px);
|
||||
conformWindowToMetric (changedMetric);
|
||||
ensureInvariants (px);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -743,6 +743,14 @@ namespace test {
|
|||
CHECK (rational_cast<float>(100000000000_r/109951052826533333) == 9.09495611e-07f); // theoretical value
|
||||
CHECK (rational_cast<float>(win.px_per_sec()) == 9.09503967e-07f); // value actually chosen
|
||||
CHECK (win.px_per_sec() == 508631_r/559239974093);
|
||||
|
||||
/*--Test-6-----------*/
|
||||
win.setMetric (bruteZoom); // And now put one on top by requesting excessive zoom-out:
|
||||
CHECK (_raw(win.overallSpan().duration()) == 614891469123651720); // overall canvas duration not changed
|
||||
CHECK (_raw(win.visible().duration()) == 109951162777600000); // window duration only slightly increased
|
||||
CHECK (508631_r/559239974093 > 15625_r/17179869184); // zoom factor slightly reduced
|
||||
CHECK (win.px_per_sec() == 15625_r/17179869184); // and now hitting again the minimum limit
|
||||
CHECK (win.pxWidth() == MAX_PX_WIDTH); // pixel count unchanged at maximum
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -40057,7 +40057,9 @@
|
|||
<linktarget COLOR="#4660c4" DESTINATION="ID_979012602" ENDARROW="Default" ENDINCLINATION="-136;-164;" ID="Arrow_ID_456720693" SOURCE="ID_999425219" STARTARROW="None" STARTINCLINATION="115;6;"/>
|
||||
<node CREATED="1670285974347" ID="ID_1007233105" MODIFIED="1670286288243" TEXT="pixel/maxExtension > 1/ LIM_HAZARD"/>
|
||||
<node CREATED="1670285726380" ID="ID_1300128015" MODIFIED="1670286035624" TEXT="⟹ maxExtension < pxWidth * (1<<40)"/>
|
||||
<node CREATED="1670286048745" ID="ID_735928158" MODIFIED="1670286420499" TEXT="und zwar in µ-Ticks gemessen!"/>
|
||||
<node CREATED="1670286048745" ID="ID_735928158" MODIFIED="1670286420499" TEXT="und zwar in µ-Ticks gemessen!">
|
||||
<linktarget COLOR="#6ca8d7" DESTINATION="ID_735928158" ENDARROW="Default" ENDINCLINATION="-761;71;" ID="Arrow_ID_907454112" SOURCE="ID_804263127" STARTARROW="None" STARTINCLINATION="1521;-73;"/>
|
||||
</node>
|
||||
<node CREATED="1670286453362" ID="ID_1224708034" MODIFIED="1670286466277" TEXT="das sind etwa 35 Jahre pro Pixel">
|
||||
<icon BUILTIN="idea"/>
|
||||
</node>
|
||||
|
|
@ -40227,17 +40229,18 @@
|
|||
</p>
|
||||
</body>
|
||||
</html></richcontent>
|
||||
<linktarget COLOR="#c23775" DESTINATION="ID_1337555507" ENDARROW="Default" ENDINCLINATION="-42;54;" ID="Arrow_ID_222968818" SOURCE="ID_619135931" STARTARROW="None" STARTINCLINATION="-229;-15;"/>
|
||||
<icon BUILTIN="clanbomber"/>
|
||||
<node CREATED="1670533870555" ID="ID_649716018" MODIFIED="1670533882170" TEXT="nach meinem Verständnis ist aber die Duration stets berechnet"/>
|
||||
<node CREATED="1670533882974" ID="ID_1544775451" MODIFIED="1670533906598" TEXT="und daher kann der Zähler nicht > LIM_HAZARD sein"/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1670533969244" ID="ID_509879073" MODIFIED="1670534000608" TEXT="Problem: detox verfälscht die pxWidth">
|
||||
<node COLOR="#435e98" CREATED="1670533969244" ID="ID_509879073" MODIFIED="1670620313585" TEXT="Problem: detox verfälscht die pxWidth">
|
||||
<icon BUILTIN="messagebox_warning"/>
|
||||
<node CREATED="1670534010893" ID="ID_719080732" MODIFIED="1670534023047" TEXT="in conformMetricToWindow(pxWidth)"/>
|
||||
<node CREATED="1670534028996" ID="ID_218226232" MODIFIED="1670534040364" TEXT="detox(metric) ist unverzichtbar"/>
|
||||
<node COLOR="#338800" CREATED="1670534041313" ID="ID_1590617796" MODIFIED="1670608092950" TEXT="muß dann aber korrigieren">
|
||||
<node COLOR="#338800" CREATED="1670534041313" FOLDED="true" ID="ID_1590617796" MODIFIED="1670608092950" TEXT="muß dann aber korrigieren">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
<node CREATED="1670535342258" ID="ID_1603322916" MODIFIED="1670535392115" TEXT="kann nun erst mal ein Delta (zuverlässig) ausrechnen">
|
||||
<arrowlink COLOR="#984981" DESTINATION="ID_1459847631" ENDARROW="Default" ENDINCLINATION="37;46;" ID="Arrow_ID_1118525297" STARTARROW="None" STARTINCLINATION="-82;-3;"/>
|
||||
|
|
@ -40362,6 +40365,181 @@
|
|||
</node>
|
||||
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1670520779106" ID="ID_442688956" MODIFIED="1670608133729" TEXT="dann nochmal giftigen maximal-Zoom-out">
|
||||
<icon BUILTIN="pencil"/>
|
||||
<node CREATED="1670615758976" ID="ID_1452400924" MODIFIED="1670615767846" TEXT="erst mal bruteZoom verwenden">
|
||||
<node COLOR="#435e98" CREATED="1670615776976" ID="ID_1512494689" MODIFIED="1670615803740" TEXT="Fehler im abschlißenden Plausibilitätscheck">
|
||||
<icon BUILTIN="broken-line"/>
|
||||
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1670615806180" ID="ID_619135931" MODIFIED="1670620369556" TEXT="in der Tat: die schrittweise Integer-Division kann auch wrappen">
|
||||
<arrowlink COLOR="#c23775" DESTINATION="ID_1337555507" ENDARROW="Default" ENDINCLINATION="-42;54;" ID="Arrow_ID_222968818" STARTARROW="None" STARTINCLINATION="-229;-15;"/>
|
||||
<icon BUILTIN="messagebox_warning"/>
|
||||
</node>
|
||||
<node COLOR="#435e98" CREATED="1670615839039" ID="ID_1944815131" MODIFIED="1670615993483" TEXT="dieser Gebrauch hier ist aber klar außerhalb der definierten Grenzen">
|
||||
<icon BUILTIN="yes"/>
|
||||
<node CREATED="1670615926323" ID="ID_1960899448" MODIFIED="1670615939582" TEXT="Duration muß aus time::Duration errechnet sein"/>
|
||||
<node CREATED="1670615942711" ID="ID_255892393" MODIFIED="1670615951220" TEXT="der ZoomFaktor muß entgiftet sein"/>
|
||||
<node CREATED="1670615952000" ID="ID_1758205571" MODIFIED="1670615971194" TEXT="dadurch ist zd*dd handhabbar"/>
|
||||
<node CREATED="1670615972022" ID="ID_1327396184" MODIFIED="1670615981992" TEXT="genau das (und Weiteres) ist hier nicht gegeben"/>
|
||||
</node>
|
||||
<node CREATED="1670615994715" ID="ID_193734845" MODIFIED="1670616015887" TEXT="die vorherige Lösung war besser: näherungsweise Rechnung per floating-point">
|
||||
<icon BUILTIN="idea"/>
|
||||
</node>
|
||||
<node COLOR="#338800" CREATED="1670616019359" ID="ID_1695051977" MODIFIED="1670616022048" TEXT="revert">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1670616045807" ID="ID_1657986247" MODIFIED="1670616133443" TEXT="außerdem: das ist gar nicht was ich testen wollte">
|
||||
<icon BUILTIN="stop-sign"/>
|
||||
<node CREATED="1670616061533" ID="ID_1889255548" MODIFIED="1670616137418" TEXT="das wäre ein weiterer guter Testfall">
|
||||
<icon BUILTIN="idea"/>
|
||||
</node>
|
||||
<node CREATED="1670616069498" ID="ID_1354086365" MODIFIED="1670616142124" TEXT="aber es ist ein extremes zoom-in">
|
||||
<icon BUILTIN="yes"/>
|
||||
</node>
|
||||
</node>
|
||||
<node COLOR="#435e98" CREATED="1670616082383" ID="ID_156266762" MODIFIED="1670620297299" TEXT="die berechneten Zoom-Faktoren und Durations ändern sich unterwegs ganz kraß">
|
||||
<icon BUILTIN="messagebox_warning"/>
|
||||
<node COLOR="#435e98" CREATED="1670616117714" ID="ID_309015495" MODIFIED="1670617416589" TEXT="korrekt oder Fehlverhalten?">
|
||||
<icon BUILTIN="help"/>
|
||||
<node COLOR="#435e98" CREATED="1670616704876" ID="ID_1622141133" MODIFIED="1670617395856" TEXT="Untersuchung">
|
||||
<node CREATED="1670616712931" ID="ID_1501827077" MODIFIED="1670616746582" TEXT="metricVorher: 0,00000090950401180624"/>
|
||||
<node CREATED="1670616748326" ID="ID_367770862" MODIFIED="1670616775054" TEXT="metricGesetzt: 0,00000090949470177293"/>
|
||||
<node CREATED="1670616817724" ID="ID_1415764699" MODIFIED="1670616822521" TEXT="changeFactor">
|
||||
<node CREATED="1670616823604" ID="ID_1357255245" MODIFIED="1670616824954" TEXT="8738124595203125/8738214042927104"/>
|
||||
<node CREATED="1670616832746" ID="ID_1244959877" MODIFIED="1670616837612" TEXT="= 0.99998976361490579406"/>
|
||||
</node>
|
||||
<node CREATED="1670617088809" ID="ID_1991508385" MODIFIED="1670617093931" TEXT="durVorher: 109951052826,533333"/>
|
||||
<node CREATED="1670617154682" ID="ID_225306013" MODIFIED="1670617200454" TEXT="durModifiziert">
|
||||
<node CREATED="1670617174581" ID="ID_969572944" MODIFIED="1670617177190" TEXT="2054375346307858432/7405988284081966813"/>
|
||||
<node CREATED="1670617161148" ID="ID_1958223126" MODIFIED="1670617162826" TEXT="= 0,27739381531610337409"/>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#cd158d" CREATED="1670617179460" ID="ID_1631410208" MODIFIED="1670617208435" TEXT="tja">
|
||||
<icon BUILTIN="smiley-oh"/>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1670617402798" ID="ID_30984099" MODIFIED="1670618258486">
|
||||
<richcontent TYPE="NODE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
eindeutig: <b>wrap-around</b> in der relativen Änderung
|
||||
</p>
|
||||
</body>
|
||||
</html></richcontent>
|
||||
<icon BUILTIN="forward"/>
|
||||
</node>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1670617418716" ID="ID_520825252" MODIFIED="1670617441582" TEXT="relative Anwendung des Zoom-Faktors scheitert">
|
||||
<icon BUILTIN="broken-line"/>
|
||||
<node CREATED="1670617477125" ID="ID_1008445724" MODIFIED="1670617487798" TEXT="auch detox() hie und da hilft nix"/>
|
||||
<node CREATED="1670617500514" ID="ID_42571289" MODIFIED="1670617536811" TEXT="grade wenn Duration groß ist reicht detox() nicht"/>
|
||||
<node CREATED="1670617542996" ID="ID_890256504" MODIFIED="1670617548121" TEXT="...und ist außerdem ungenau"/>
|
||||
</node>
|
||||
<node COLOR="#435e98" CREATED="1670617465750" ID="ID_1125263407" MODIFIED="1670618244591" TEXT="ist dieser Ansatz überhaupt sinnvoll">
|
||||
<icon BUILTIN="help"/>
|
||||
<node CREATED="1670617550570" ID="ID_794382292" MODIFIED="1670617586580" TEXT="dieser Rechenweg stammt noch aus der initialien Implementierung">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
...welche ich inzwischen nahezu komplett einmal umgepflügt habe...
|
||||
</p>
|
||||
</body>
|
||||
</html></richcontent>
|
||||
</node>
|
||||
<node CREATED="1670617587918" ID="ID_1941807837" MODIFIED="1670617700006" TEXT="auf den ersten Blick cool...">
|
||||
<icon BUILTIN="ksmiletris"/>
|
||||
</node>
|
||||
<node CREATED="1670617617322" ID="ID_154517336" MODIFIED="1670617693674" TEXT="aber numerisch ein Albtraum!">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
weil hier...
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
zum Einen eine sehr große Zahl auf einen beliebigen Bruchfaktor trifft
|
||||
</li>
|
||||
<li>
|
||||
und außerdem als Resultat eine Beliebige Zahl entsteht, die nicht auf Time::SCALE quantisiert ist
|
||||
</li>
|
||||
</ul>
|
||||
</body>
|
||||
</html></richcontent>
|
||||
</node>
|
||||
<node CREATED="1670617722212" ID="ID_552344182" MODIFIED="1670618342028">
|
||||
<richcontent TYPE="NODE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
<u>und <b>redundant</b></u>: macht nicht <font face="Monospaced" color="#461bac">conformWindowToMetric()</font>  inzwischen genau dassselbe??
|
||||
</p>
|
||||
</body>
|
||||
</html></richcontent>
|
||||
<arrowlink COLOR="#3489d0" DESTINATION="ID_1728003116" ENDARROW="Default" ENDINCLINATION="7;-26;" ID="Arrow_ID_32836248" STARTARROW="None" STARTINCLINATION="-90;3;"/>
|
||||
<icon BUILTIN="idea"/>
|
||||
</node>
|
||||
</node>
|
||||
<node COLOR="#338800" CREATED="1670617949506" ID="ID_1728003116" MODIFIED="1670618335701" TEXT="implementieren ohne Rückgrif auf mutatDuration()">
|
||||
<linktarget COLOR="#3489d0" DESTINATION="ID_1728003116" ENDARROW="Default" ENDINCLINATION="7;-26;" ID="Arrow_ID_32836248" SOURCE="ID_552344182" STARTARROW="None" STARTINCLINATION="-90;3;"/>
|
||||
<icon BUILTIN="button_ok"/>
|
||||
<node CREATED="1670618047801" ID="ID_981414972" MODIFIED="1670618077348" TEXT="puh... da fällt ja fast alles weg">
|
||||
<icon BUILTIN="smiley-oh"/>
|
||||
</node>
|
||||
<node CREATED="1670618058280" ID="ID_1146416916" MODIFIED="1670618073226" TEXT="und die ganzen Fallen, um die ich mich so geplagt habe">
|
||||
<icon BUILTIN="smiley-angry"/>
|
||||
</node>
|
||||
<node CREATED="1670618082836" ID="ID_958310234" MODIFIED="1670618112665" TEXT="was zusätzlich bleibt, ist nur die Limitierung der Metrik">
|
||||
<icon BUILTIN="info"/>
|
||||
</node>
|
||||
<node CREATED="1670618093994" ID="ID_833295341" MODIFIED="1670618108468" TEXT="die allerdings ist eine Errungenschaft!">
|
||||
<icon BUILTIN="yes"/>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1670618161449" ID="ID_676303735" MODIFIED="1670618174884" TEXT="kaum macht ma's richtig, schon funktioniert's">
|
||||
<icon BUILTIN="ksmiletris"/>
|
||||
<node CREATED="1670618176639" ID="ID_393011013" MODIFIED="1670618207065">
|
||||
<richcontent TYPE="NODE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
und die Testsuite ist auf Anhieb <b><font color="#0ba911">GRÜN</font></b>
|
||||
</p>
|
||||
</body>
|
||||
</html></richcontent>
|
||||
</node>
|
||||
<node CREATED="1670618211155" ID="ID_1878857756" MODIFIED="1670618231508" TEXT="wie gut, daß ich betreits so viele penible Tests gebaut habe">
|
||||
<icon BUILTIN="idea"/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1670620383399" ID="ID_674544235" MODIFIED="1670620401994" TEXT="hätte Zoom auf gesamten Canvas erwartet">
|
||||
<icon BUILTIN="yes"/>
|
||||
<node CREATED="1670620405080" ID="ID_1202192964" MODIFIED="1670620418967" TEXT="...nachdem jetzt die Zahl der Pixel so drastisch erhöht wurde"/>
|
||||
<node CREATED="1670621505792" ID="ID_645635162" MODIFIED="1670621528305" TEXT="aber der definierte Grenzfaktor verhindert das">
|
||||
<icon BUILTIN="info"/>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#f0d5c5" COLOR="#990033" CREATED="1670621532941" ID="ID_1641685049" MODIFIED="1670621550148" TEXT="ist der Grenzfaktor korrekt berechnet?">
|
||||
<icon BUILTIN="help"/>
|
||||
<node CREATED="1670621551706" ID="ID_804263127" MODIFIED="1670621631745" TEXT="konzeptionell hatte ich etwas Anderes in Erinnerung">
|
||||
<arrowlink COLOR="#6ca8d7" DESTINATION="ID_735928158" ENDARROW="Default" ENDINCLINATION="-761;71;" ID="Arrow_ID_907454112" STARTARROW="None" STARTINCLINATION="1521;-73;"/>
|
||||
<icon BUILTIN="messagebox_warning"/>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1670623877667" ID="ID_1836625419" MODIFIED="1670623900060" TEXT="möglicherweise genau um Time::SCALE zu klein?">
|
||||
<icon BUILTIN="idea"/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
|
|
|
|||
Loading…
Reference in a new issue