Diff-Listener: add a variant to trigger also on value assignment (see #1206)

As it turned out, it is rather easy to extend the existing listener
for structural changes to detect also value assignments. Actually
it seems we'd need both flavours, so be it.
This commit is contained in:
Fischlurch 2020-03-15 23:11:14 +01:00
parent 31116fb079
commit dd016667ad
4 changed files with 109 additions and 28 deletions

View file

@ -60,23 +60,40 @@ namespace diff{
/**
* Decorator for TreeMutator bindings, to fire a listener function
* when the applied diff describes a structural change. By convention,
* all changes pertaining the sequence of children count as structural.
* Thus, effectively a structural change incurs usage of the `INS`, `DEL`
* `SKIP` or `FIND` verbs, which in turn are translated into the three
* API operation intercepted here.
* when the applied diff describes a relevant change. Changes can be
* _structural,_ they can be _value mutations_ or _child mutations._
* By convention, all changes pertaining the sequence of children are
* classified as structural changes. Thus, effectively a structural
* change incurs usage of the `INS`, `DEL`, `SKIP` or `FIND` verbs,
* which in turn will be translated into the three API operations
* intercepted here in the basic setup. When value assignments
* count as "relevant", then we'll also have to intercept the
* `assignElm` API operation. However, the relevance of
* mutations to child elements is difficult to assess
* on this level, since we can not see what a nested
* scope actually does to the mutated child elements.
* @tparam assign also trigger on assignments in addition to
* structural changes (which will always trigger).
* Defaults to `false`
* @note TreeMutator is a disposable one-way object;
* the triggering mechanism directly relies on that.
* The listener is invoked, whenever a scope is complete,
* including processing of any nested scopes.
*/
template<class PAR, typename LIS>
template<class PAR, typename LIS, bool assign =false>
class Detector4StructuralChanges
: public PAR
{
LIS changeListener_;
bool triggered_ = false;
void
trigger(bool relevant =true)
{
if (relevant)
triggered_ = true;
}
public:
Detector4StructuralChanges (LIS functor, PAR&& chain)
: PAR(std::forward<PAR>(chain))
@ -86,9 +103,9 @@ namespace diff{
// move construction allowed and expected to happen
Detector4StructuralChanges (Detector4StructuralChanges&&) =default;
/** once the diff is for this level is completely applied,
* the TreeMutator will be discarded, and we can fire our
* change listener at that point. */
/** once the diff for this level is completely applied,
* the TreeMutator will be discarded, and we can fire
* our change listener at that point. */
~Detector4StructuralChanges()
{
if (triggered_)
@ -99,9 +116,10 @@ namespace diff{
using Elm = GenNode const&;
bool injectNew (Elm elm) override { triggered_ = true; return PAR::injectNew (elm); }
bool findSrc (Elm elm) override { triggered_ = true; return PAR::findSrc (elm); }
void skipSrc (Elm elm) override { triggered_ = true; PAR::skipSrc (elm); }
bool injectNew (Elm elm) override { trigger(); return PAR::injectNew (elm); }
bool findSrc (Elm elm) override { trigger(); return PAR::findSrc (elm); }
void skipSrc (Elm elm) override { trigger(); PAR::skipSrc (elm); }
bool assignElm (Elm elm) override { trigger(assign); return PAR::assignElm (elm); }
};
@ -118,6 +136,19 @@ namespace diff{
return chainedBuilder<Detector4StructuralChanges<PAR,LIS>> (changeListener);
}
/** Entry point for DSL builder: attach a functor as listener to be notified after either
* a structural change, or a value assignment within the local scope of this TreeMutator.
*/
template<class PAR>
template<typename LIS>
inline auto
Builder<PAR>::onLocalChange (LIS changeListener)
{
ASSERT_VALID_SIGNATURE (LIS, void(void))
// vvvv---note: including assignments
return chainedBuilder<Detector4StructuralChanges<PAR,LIS, true>> (changeListener);
}
}//(END)Mutator-Builder decorator components...
}} // namespace lib::diff

View file

@ -458,11 +458,19 @@ namespace diff{
* be invoked _after_ applying a diff with any `INS`, `DEL`, `FIND`, `SKIP` verb.
* @remark in theory, one could also drop contents indirectly, by sending only
* part of the necessary PICK verbs (or using AFTER(...)). However,
* such a diff is formally not prohibited, and will indeed be detected as
* such a diff is formally not allowed, and will indeed be detected as
* error when leaving a scope, in ChildCollectionMutator::completeScope()
*/
template<typename LIS>
auto onSeqChange (LIS changeListener);
/** attach a listener function, to be invoked on any _local change._
* This includes the [structural changes](\ref onSeqChange()), but also
* value assignments to any attribute or element.
* @note mutation of a nested child scope will _not_ trigger this listener.
*/
template<typename LIS>
auto onLocalChange (LIS changeListener);
};
}//(END) Mutator-Builder...

View file

@ -87,7 +87,10 @@ namespace test{
* @test When creating a TreeMutator binding, a listener (lambda) can be attached,
* to be invoked on structural changes...
* - inserting, removing and reordering of children counts as "structural" change
* - whereas assignment of a new value or nested mutation does not trigger
* - whereas assignment of a new value will only trigger `onLocalChange()`
* - mutation of nested scopes does not trigger any of these listeners,
* since (within the existing framework) there is no simple way to
* intercept also the child mutation stream to check for relevance.
* @note This test binds the test class itself for diff mutation, applying changes
* onto a vector with test data. The binding itself is somewhat unusual,
* insofar it allows to re-assign elements within the vector, which can be
@ -115,10 +118,13 @@ namespace test{
{
std::vector<string> subject_;
int structChanges_ = 0;
int localChanges_ = 0;
/** rig the test class itself to receive a diff mutation.
* - bind the #subject_ data collection to be changed by diff
* - attach a listener, to be invoked on _structural changes
* - attach a listener, to be invoked on _structural changes_
* - attach another listener, activated both on structural
* changes and on _value assignment._
*/
void
buildMutator (TreeMutator::Handle buff) override
@ -137,7 +143,11 @@ namespace test{
}))
.onSeqChange ([&]()
{
++structChanges_; // Note: this lambda is the key point for this test
++structChanges_; // Note: these lambdas are the key point for this test
})
.onLocalChange ([&]()
{
++localChanges_;
})
);
}
@ -148,6 +158,7 @@ namespace test{
{
CHECK (isnil (subject_));
CHECK (0 == structChanges_);
CHECK (0 == localChanges_);
DiffApplicator<DiffTreeMutationListener_test> applicator{*this};
@ -158,6 +169,7 @@ namespace test{
}});
CHECK ("a, c, d, c" == contents(subject_));
CHECK (1 == structChanges_);
CHECK (1 == localChanges_);
applicator.consume (MutationMessage{{after(Ref::END)
, set (VAL_C_UPPER) // Note: the current element is tried first, which happens to match
@ -165,6 +177,7 @@ namespace test{
}});
CHECK ("a, c, D, C" == contents(subject_));
CHECK (1 == structChanges_); // Note: the listener has not fired, since this counts as value change.
CHECK (2 == localChanges_);
applicator.consume (MutationMessage{{pick(VAL_A)
, ins (VAL_B)
@ -174,12 +187,14 @@ namespace test{
, del (VAL_C)
}});
CHECK ("a, b, D, c" == contents(subject_));
CHECK (2 == structChanges_); // Note: this obviously is a structure change, so the listener fired.
CHECK (2 == structChanges_); // Note: this obviously is a structure change, so both listeners fired.
CHECK (3 == localChanges_);
applicator.consume (MutationMessage{{after(Ref::END)
}});
CHECK ("a, b, D, c" == contents(subject_));
CHECK (2 == structChanges_); // Note: contents confirmed as-is, listener not invoked.
CHECK (2 == structChanges_); // Note: contents confirmed as-is, none of the listeners is invoked.
CHECK (3 == localChanges_);
}
};

View file

@ -27794,8 +27794,7 @@
kann erst erfolgen, <i>wenn's soweit ist</i>
</p>
</body>
</html>
</richcontent>
</html></richcontent>
<richcontent TYPE="NOTE"><html>
<head>
@ -27805,13 +27804,38 @@
...und mu&#223; damit in den State-Change-Mechanismus f&#252;r den Pr&#228;sentationsstil verlegt werden
</p>
</body>
</html>
</richcontent>
</html></richcontent>
<icon BUILTIN="messagebox_warning"/>
<icon BUILTIN="stop-sign"/>
<node CREATED="1584289349339" ID="ID_1202416542" MODIFIED="1584289360503" TEXT="also eben doch nicht gleich beim ctor-Aufruf"/>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1584289361215" ID="ID_1687516658" MODIFIED="1584289369023" TEXT="brauche stattdessen einen Diff-Listener">
<icon BUILTIN="flag-yellow"/>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1584307692087" ID="ID_1525336492" MODIFIED="1584307714957" TEXT="gen&#xfc;gen uns Struktur-&#xc4;nderungen (onSeqChange)?">
<icon BUILTIN="help"/>
</node>
<node CREATED="1584307718907" ID="ID_211654301" MODIFIED="1584307729382" TEXT="im Moment ja, aber vermutlich l&#xe4;ngerfristig nicht">
<node COLOR="#338800" CREATED="1584309711112" ID="ID_161551669" MODIFIED="1584309897727" TEXT="Diff-Listener um assignElm() erg&#xe4;nzen">
<arrowlink COLOR="#427176" DESTINATION="ID_274485695" ENDARROW="Default" ENDINCLINATION="-1913;0;" ID="Arrow_ID_1484023114" STARTARROW="None" STARTINCLINATION="926;63;"/>
<icon BUILTIN="button_ok"/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1584309903737" ID="ID_1025527544" MODIFIED="1584309912017" TEXT="onLocalChange()-Listener installieren">
<icon BUILTIN="flag-yellow"/>
</node>
</node>
<node CREATED="1584307731586" ID="ID_1716161483" MODIFIED="1584307998380">
<richcontent TYPE="NODE"><html>
<head>
</head>
<body>
<p>
Nebenbei bemerkt: die Aktion mu&#223; <b>idempotent</b>&#160;sein
</p>
</body>
</html>
</richcontent>
<icon BUILTIN="yes"/>
</node>
</node>
</node>
<node CREATED="1584201751260" ID="ID_1492285349" MODIFIED="1584201760641" TEXT="Problem: initiale Koordinaten">
@ -27855,8 +27879,7 @@
wir verwenden das ClipDelegate aber auch, um in Clips eingebettete Effekte oder die einzelnen Spuren darzustellen. Die Logik f&#252;r den Anzeigestil mu&#223; das mit ber&#252;cksichtigen
</p>
</body>
</html>
</richcontent>
</html></richcontent>
<arrowlink COLOR="#a92d35" DESTINATION="ID_63120870" ENDARROW="Default" ENDINCLINATION="246;-353;" ID="Arrow_ID_973658371" STARTARROW="None" STARTINCLINATION="392;20;"/>
<icon BUILTIN="messagebox_warning"/>
</node>
@ -27902,8 +27925,7 @@
Letzteres gef&#228;llt mir definitiv besser
</p>
</body>
</html>
</richcontent>
</html></richcontent>
<font ITALIC="true" NAME="SansSerif" SIZE="14"/>
<icon BUILTIN="yes"/>
</node>
@ -27989,8 +28011,7 @@
...diese erstreckt sich typischerweise &#252;ber die gesamte L&#228;nge des umschlie&#223;enden Containers, und pa&#223;t sich dieser ohne weiteres dynamisch an
</p>
</body>
</html>
</richcontent>
</html></richcontent>
</node>
<node CREATED="1584229601787" ID="ID_4294703" MODIFIED="1584229614293" TEXT="Unterschied zwischen &quot;unbekannt&quot; und &quot;nicht limitiert&quot;">
<icon BUILTIN="idea"/>
@ -37943,6 +37964,12 @@
<linktarget COLOR="#519b84" DESTINATION="ID_727500820" ENDARROW="Default" ENDINCLINATION="-67;7;" ID="Arrow_ID_115955006" SOURCE="ID_1220310042" STARTARROW="None" STARTINCLINATION="71;200;"/>
<icon BUILTIN="button_ok"/>
</node>
<node COLOR="#338800" CREATED="1584309662354" ID="ID_274485695" MODIFIED="1584309897727" TEXT="onLocalChange">
<linktarget COLOR="#427176" DESTINATION="ID_274485695" ENDARROW="Default" ENDINCLINATION="-1913;0;" ID="Arrow_ID_1484023114" SOURCE="ID_161551669" STARTARROW="None" STARTINCLINATION="926;63;"/>
<icon BUILTIN="button_ok"/>
<node CREATED="1584309681495" HGAP="26" ID="ID_466790810" MODIFIED="1584309702146" TEXT="einfache Erg&#xe4;nzung" VSHIFT="14"/>
<node CREATED="1584309688390" ID="ID_9063913" MODIFIED="1584309697322" TEXT="schlie&#xdf;t auch Wert-Zuweisungen mit ein"/>
</node>
</node>
</node>
<node COLOR="#435e98" CREATED="1475449611754" FOLDED="true" HGAP="33" ID="ID_1285123321" MODIFIED="1576281597012" TEXT="Grundsatzfrage: Attribut-Map" VSHIFT="1">