DOC: a gentle introduction to diff binding
...it occurred to me that very likely a casual reader of the code will encounter here the first instance of such a diff binding function. I am well aware this looks intimidating (and it is a tricky technical detail) Even more so, if what you expect is just some access to a shared data model, you might be completely puzzled by this code and nor recognise its importance.
This commit is contained in:
parent
81febc27e1
commit
bd42793db7
2 changed files with 49 additions and 13 deletions
|
|
@ -93,6 +93,42 @@ namespace timeline {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal this method is invoked by the UI-Bus when dispatching a MutationMessage...
|
||||||
|
* @remarks this is likely the first occasion a casual reader sees such a binding function,
|
||||||
|
* thus some explanations might be helpful. This is part of the »diff framework«: we use
|
||||||
|
* messages to _communicate changes on structured data._ We might as well just use a common
|
||||||
|
* object model, but we refrain from doing so, to avoid tight coupling, here between the
|
||||||
|
* core logic and the structures in the UI. Rather we assume that both sides share a
|
||||||
|
* roughly compatible understanding regarding the structure of the session model.
|
||||||
|
* Exchanging just diff messages allows us to use private implementation data structures
|
||||||
|
* in the UI as we see fit, without the danger of breaking anything in the core. And vice
|
||||||
|
* versa. You may see this as yet another way of data binding between model and view.
|
||||||
|
* The TreeMutator helps to accomplish this binding between a generic structure description,
|
||||||
|
* in our case based on GenNode elements, and the private data structure, here the private
|
||||||
|
* object fields and the collection of child objects within TimelineController. To ease this
|
||||||
|
* essentially "mechanic" and repetitive task, the TreeMutator offers some standard building
|
||||||
|
* blocks, plus a builder DSL, allowing just to fill in the flexible parts with some lambdas.
|
||||||
|
* Yet still, the technical details of getting this right can be tricky, especially since
|
||||||
|
* it is very important to set up those bindings in the right order. Basically we build
|
||||||
|
* a stack of decorators, so what is mentioned last will be checked first. Effectively
|
||||||
|
* this creates a structure of "onion layers", where each layer handles just one aspect
|
||||||
|
* of the binding. This works together with the convention that the diff message must
|
||||||
|
* mention all changes regarding one group (or kind) of elements together and completely.
|
||||||
|
* This is kind of an _object description protocol_, meaning that the diff has to mention
|
||||||
|
* the metadata (the object type) first, followed by the "attributes" (fields) and finally
|
||||||
|
* nested child objects. And nested elements can be handled with a nested diff, which
|
||||||
|
* recurses into some nested scope. In the example here, we are prepared to deal with
|
||||||
|
* two kinds of nested scope:
|
||||||
|
* - the _fork_ (that is the tree of tracks) is a nested structure
|
||||||
|
* - we hold a collection of marker child objects, each of which can be entered
|
||||||
|
* as a nested scope.
|
||||||
|
* For both cases we prepare a way to build a _nested mutator_, and in both cases this
|
||||||
|
* is simply achieved by relying on the common interface of all those "elements", which
|
||||||
|
* is gui::model::Tangible and just happens to require each such "tangible" to offer
|
||||||
|
* a mutation building method, just like this one here. Just recursive programming.
|
||||||
|
* @see DiffComplexApplication_test
|
||||||
|
*/
|
||||||
void
|
void
|
||||||
TimelineController::buildMutator (TreeMutator::Handle buffer)
|
TimelineController::buildMutator (TreeMutator::Handle buffer)
|
||||||
{
|
{
|
||||||
|
|
@ -102,31 +138,31 @@ namespace timeline {
|
||||||
TreeMutator::build()
|
TreeMutator::build()
|
||||||
.attach (collection(markers_)
|
.attach (collection(markers_)
|
||||||
.isApplicableIf ([&](GenNode const& spec) -> bool
|
.isApplicableIf ([&](GenNode const& spec) -> bool
|
||||||
{
|
{ // »Selector« : require object-like sub scope
|
||||||
return spec.data.isNested(); // »Selector« : require object-like sub scope
|
return spec.data.isNested();
|
||||||
})
|
})
|
||||||
.matchElement ([&](GenNode const& spec, PMarker const& elm) -> bool
|
.matchElement ([&](GenNode const& spec, PMarker const& elm) -> bool
|
||||||
{
|
{ // »Matcher« : how to know we're dealing with the right object
|
||||||
return spec.idi == ID(elm);
|
return spec.idi == ID(elm);
|
||||||
})
|
})
|
||||||
.constructFrom ([&](GenNode const& spec) -> PMarker
|
.constructFrom ([&](GenNode const& spec) -> PMarker
|
||||||
{
|
{ // »Constructor« : what to do when the diff mentions a new entity
|
||||||
return make_unique<MarkerWidget>(spec.idi, this->uiBus_);
|
return make_unique<MarkerWidget>(spec.idi, this->uiBus_);
|
||||||
})
|
})
|
||||||
.buildChildMutator ([&](PMarker& target, GenNode::ID const& subID, TreeMutator::Handle buff) -> bool
|
.buildChildMutator ([&](PMarker& target, GenNode::ID const& subID, TreeMutator::Handle buff) -> bool
|
||||||
{
|
{ // »Mutator« : how to apply the diff recursively to a nested scope
|
||||||
if (ID(target) != subID) return false; //require match on already existing child object
|
if (ID(target) != subID) return false; // - require match on already existing child object
|
||||||
target->buildMutator (buff); // delegate to child to build nested TreeMutator
|
target->buildMutator (buff); // - delegate to child to build nested TreeMutator
|
||||||
return true;
|
return true;
|
||||||
}))
|
}))
|
||||||
.change("name", [&](string val)
|
|
||||||
{
|
|
||||||
name_ = val;
|
|
||||||
})
|
|
||||||
.mutateAttrib("fork", [&](TreeMutator::Handle buff)
|
.mutateAttrib("fork", [&](TreeMutator::Handle buff)
|
||||||
{
|
{ // »Attribute Mutator« : how enter an object field as nested scope
|
||||||
REQUIRE (fork_);
|
REQUIRE (fork_);
|
||||||
fork_->buildMutator(buff);
|
fork_->buildMutator(buff);
|
||||||
|
})
|
||||||
|
.change("name", [&](string val)
|
||||||
|
{ // »Attribute Setter« : how assign a new value to some object field
|
||||||
|
name_ = val;
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
DiffVirtualisedApplication(Test) - apply structural changes to unspecific private data structures
|
DiffComplexApplication(Test) - apply structural changes to unspecific private data structures
|
||||||
|
|
||||||
Copyright (C) Lumiera.org
|
Copyright (C) Lumiera.org
|
||||||
2016, Hermann Vosseler <Ichthyostega@web.de>
|
2016, Hermann Vosseler <Ichthyostega@web.de>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue