LUMIERA.clone/src/lib/diff/mutation-message.hpp

273 lines
13 KiB
C++

/*
MUTATION-MESSAGE.hpp - message to cause changes to generic model elements
Copyright (C) Lumiera.org
2017, Hermann Vosseler <Ichthyostega@web.de>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of
the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/** @file mutation-message.hpp
** Generic Message with an embedded diff, to describe changes to model elements.
** The ability to create and apply such messages relies on the [diff framework](\ref diff-language.hpp).
** Using diff messages allows to describe and effect changes, without actually knowing much about the target.
** Sender and receiver just need to share some common assumptions about the abstract structure of the data.
**
** The challenging part with this task is the fact that we need to pass such messages over abstraction barriers
** and even schedule them into another thread (the UI event thread). Yet diff application actually is a _pull operation,_
** which means there must be a callback actually to retrieve the diff content, and this callback will happen from
** the context of the receiver.
**
** # Mutation messages on the UI-Bus
**
** The UI-Bus offers a dedicated API to direct MutationMessages towards [UI-Elements](\ref model::Tangible).
** Each _tangible element in the UI,_ be it Widget or Controller, is designated by an unique ID. Sending a
** Mutation message causes the target to alter and reshape itself, to comply to the _diff sequence_ indicated
** and transported through the message -- since a diff sequence as such is always concrete and relates to a
** a specific context, we can not represent it directly as a type on interface level. Rather, the receiver
** of a diff sequence must offer the ability to be reshaped through diff messages, which is expressed through
** the interface DiffMutable. In the case at question here, stage::model::Tangible offers this interface and thus
** the ability to construct a concrete lib::diff::TreeMutator, which in turn is bound to the internals of the
** actual UI-Element. In this framework, a diff is actually represented as a sequence of _diff verbs,_ which
** can be ``pulled'' one by one from the MutationMessage, and then applied to the target data structure
** with the help of a `DiffApplicator<DiffMutable>`, based on the TreeMutator exposed.
**
** # Mutation messages sent from the Session into the UI
**
** While components in the UI generate commands to work on the session, the effect of performing those commands
** is reflected back asynchronously into the GUI through MutationMessages. All _visible content in the UI_ is
** controlled by such messages. Initially the UI is a blank slate, and will be populated with content to reflect
** the content and structure of the session. Whenever the session changes, an incremental update is pushed into
** the UI as a diff.
**
** Hand-over and application of mutations is actually a process in two steps. The necessity to change something
** is indicated (or ``triggered'') by passing a MutationMessage through the GuiNotification::facade. We should
** note at this point that Session and UI perform each within a dedicated single thread (contrast this to the
** player and render engine, which are inherently multithreaded). The UI is loaded as plug-in and opens the
** GuiNotification::facade when the event loop is started. Thus _initiating_ the mutation process is a simple
** invocation from the session thread, which enqueues the MutationMessage and schedules the trigger over into
** the UI event thread. This starts the second stage of diff application: when the UI is about to process
** this event, the MutationMessage (which was passed through a dispatcher queue) will be forwarded over the
** UI-Bus to reach the designated target object. On reception, the receiving UI-Element builds and exposes
** its TreeMutator and the starts to _pull_ the individual DiffStep entries from the MutationMessage.
** But in fact those entries aren't stored within the message, rather a callback is invoked. When initially
** creating the message, an _opaque generation context_ was established, which now receives those callbacks
** and generates the actual sequence of diff verbs, which are immediately passed on to through the DiffApplicator
** and the TreeMutator to effect the corresponding changes in the target data structure within the UI. Care has
** to be taken when referring to session data at that point, since the pull happens from within the UI thread;
** yet in the end this remains an opaque implementation detail within the session.
**
** ## Creation of mutation messages
**
** The standard case is to build a MutationMessage by passing a heap allocated generator object. This DiffSource
** object needs to implement the interface lib::IterSource<DiffStep>, with callbacks to generate the initial
** step and further steps. Incidentally, the MutationMessage takes ownership and manages the DiffSource generator.
** Beyond this standard case, MutationMessage offers several convenience constructors to produce simple diff
** messages with a predetermined fixes sequence of DiffStep entries.
**
** @see MutationMessage_test
** @see AbstractTangible_test
** @see BustTerm_test::pushDiff()
** @see BusTerm::change()
**
*/
#ifndef LIB_DIFF_MUTATION_MESSAGE_H
#define LIB_DIFF_MUTATION_MESSAGE_H
#include "lib/error.hpp"
#include "lib/iter-source.hpp"
#include "lib/iter-adapter-stl.hpp"
#include "lib/diff/tree-diff.hpp"
#include "lib/diff/gen-node.hpp"
#include "lib/format-util.hpp"
#include "lib/meta/util.hpp"
#include <string>
namespace lib {
namespace diff{
using std::string;
using std::__and_;
using std::__not_;
using lib::meta::enable_if;
using lib::meta::can_IterForEach;
using lib::meta::can_STL_ForEach;
using DiffStep = TreeDiffLanguage::DiffStep;
using DiffSource = IterSource<DiffStep>;
/**
* Opaque message to effect a structural change on a target, which is
* likewise only known in an abstract way, as being specifically structured.
* Sending such messages typically allows some _implemention defined_ part within
* the Session to communicate structure and content to some other _implementation defined_
* part within the UI-Layer, without the necessity of both partners to be tightly coupled on
* implementation level or even know much about the other's implementation details. As motivation,
* contrast this to a naive UI implementation, which directly accesses some backend data structure;
* any change to the backend implementation typically affects the UI implementation on a detail level.
* @warning be sure to understand the fundamental problem of diff generation and application:
* the production context of diff messages need to be conserved beyond the producer's
* thread context, because it will be pulled asynchronous from within the UI event thread!
*/
struct MutationMessage
: DiffSource::iterator
{
using _FrontEnd = DiffSource::iterator;
MutationMessage() = default;
MutationMessage(MutationMessage&&) = default;
MutationMessage(MutationMessage const&) = default;
MutationMessage(MutationMessage& o) : MutationMessage((MutationMessage const&)o) { }
MutationMessage& operator= (MutationMessage const&) = default;
MutationMessage& operator= (MutationMessage &&) = default;
////////////////////////TICKET #963 Forwarding shadows copy operations
/**
* MutationMessage builder:
* take ownership of an opaque heap allocated context
* from which the concrete diff can be pulled on demand
*/
explicit
MutationMessage(DiffSource* diffGenerationContext)
: _FrontEnd{DiffSource::build (diffGenerationContext)}
{ }
/**
* Convenience builder for consuming an brace enclosed initializer_list
* @note initialiser elements will be _copied_ into a _heap alloacated_
* snapshot (vector), which is then managed by `shared_ptr`
*/
MutationMessage(std::initializer_list<DiffStep> const&& ili)
: MutationMessage{iter_stl::snapshot (move(ili))}
{ }
/**
* Convenience builder to take an arbitrary number of DiffStep arguments
* @note like for the initializer_list, arguments will be copied into
* a _heap allocated snapshot_
*/
template<typename...ARGS>
MutationMessage(ARGS&& ...args)
: MutationMessage{ {std::forward<ARGS>(args)...} }
{ }
/**
* Convenience builder to piggyback any Lumiera Forward Iterator
* @note source iterator is copied into a heap allocated IterSource
*/
template<class IT>
MutationMessage(IT&& ii, enable_if< can_IterForEach<IT>, void*> =nullptr)
: _FrontEnd{iter_source::wrapIter (std::forward<IT>(ii))}
{ }
/**
* Convenience builder to use elements form any STL-like container
* @note creating heap allocated IterSource, which _refers_ to the original container
* @warning like with any classical iterators, the container must stay alive and accessible
*/
template<class CON>
MutationMessage(CON& container, enable_if< __and_< can_STL_ForEach<CON>
,__not_<can_IterForEach<CON>>>, void*> =nullptr)
: _FrontEnd{iter_source::eachEntry(container)}
{ }
/**
* enable support to show content of the message.
* After calling this function, operator string() renders all DiffSteps
* @warning since by design a MutationMessage can only be ``pulled'' once,
* this operation needs to impose a _side effect:_ it materialises
* the complete diff sequence at once into a heap allocated buffer.
*/
MutationMessage& updateDiagnostics();
};
namespace { // Implementation: take snapshot to enable diagnostics
/** "materialised view" of the diff sequence */
struct DiffSnapshot
: std::vector<DiffStep>
{
DiffSnapshot(MutationMessage& srcMsg)
{
for ( ; srcMsg; ++srcMsg )
push_back (*srcMsg);
}
};
using _VecIter = DiffSnapshot::iterator;
using _RangeIT = RangeIter<_VecIter>;
using _Wrapped = WrappedLumieraIter<_RangeIT>;
/**
* Decorator to be layered transparently on top of MutationMessage.
* Actually, what we do is to discharge the diff generator into
* the DiffSnapshot buffer and then replace the link to the original
* generator to this decorator, which, when iterated, yields the
* contents of the DiffSnapshot one by one. But since all DiffSteps
* are now stored into that DiffSnapshot _buffer we control,_ we're
* able to produce a diagnostic listing of the complete sequence.
*/
class MaterialisedDiffBuffer
: private DiffSnapshot
, public _Wrapped
{
public:
MaterialisedDiffBuffer(MutationMessage& srcMsg)
: DiffSnapshot{srcMsg}
, _Wrapped{_RangeIT{DiffSnapshot::begin(), DiffSnapshot::end()}}
{ }
virtual
operator string() const override
{
return "Diff--{"+util::join (static_cast<DiffSnapshot> (*this))+"}";
}
};
}
/** @par operational semantics
* Since the underlying generator of the DiffStep sequence is an iterator,
* the "materialised view" can only capture what's left at the point when
* updateDiagnostics() is invoked. The captured rest sequence seamlessly
* becomes the new generator and the old generator object is released,
* since the assignment of the new backend typically removes the last
* reference in the smart-ptr managing the generation backend. This
* process can be repeated and then the [diagnostics](\ref operator string())
* will show the remainder of the sequence _left at that point._
*/
inline MutationMessage&
MutationMessage::updateDiagnostics()
{
return *this = MutationMessage{new MaterialisedDiffBuffer(*this)};
}
}} // namespace lib::diff
#endif /*LIB_DIFF_MUTATION_MESSAGE_H*/