WIP: start with drafting the GUI diff representation
...first step is to design a generic linearised list diff representation. Basically just need to pull together the theoretical work of the last weeks. Next steps will be to extend to typed ordered trees.
This commit is contained in:
parent
3dc5c83b33
commit
f4cb2896b7
2 changed files with 135 additions and 6 deletions
129
tests/library/diff-list-application-test.cpp
Normal file
129
tests/library/diff-list-application-test.cpp
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
DiffListApplication(Test) - demonstrate linearised representation of list diffs
|
||||
|
||||
Copyright (C) Lumiera.org
|
||||
2014, 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.
|
||||
|
||||
* *****************************************************/
|
||||
|
||||
|
||||
#include "lib/test/run.hpp"
|
||||
#include "lib/verb-token.hpp"
|
||||
#include "lib/util.hpp"
|
||||
//#include "lib/format-string.hpp"
|
||||
|
||||
//#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using util::isnil;
|
||||
using std::string;
|
||||
//using util::_Fmt;
|
||||
using std::vector;
|
||||
//using std::cout;
|
||||
|
||||
|
||||
namespace lib {
|
||||
namespace test{
|
||||
|
||||
|
||||
class Receiver
|
||||
{
|
||||
public:
|
||||
virtual ~Receiver() { } ///< this is an interface
|
||||
|
||||
virtual string woof() =0;
|
||||
virtual string honk() =0;
|
||||
virtual string moo() =0;
|
||||
virtual string meh() =0;
|
||||
};
|
||||
|
||||
namespace {
|
||||
const string BEGINNING("silence");
|
||||
|
||||
using Verb = VerbToken<Receiver, string(void)>;
|
||||
using VerbSeq = vector<Verb>;
|
||||
|
||||
|
||||
Verb VERB(Receiver, woof);
|
||||
Verb VERB(Receiver, honk);
|
||||
Verb VERB(Receiver, moo);
|
||||
Verb VERB(Receiver, meh);
|
||||
|
||||
#define TOK(id) id(STRINGIFY(id))
|
||||
|
||||
string TOK(a1), TOK(a2), TOK(a3), TOK(a4), TOK(a5);
|
||||
string TOK(b1), TOK(b2), TOK(b3), TOK(b4);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* a receiver of verb-tokens,
|
||||
* which renders them verbosely
|
||||
*/
|
||||
class VerboseRenderer
|
||||
: public Receiver
|
||||
{
|
||||
string woof() { return "Woof-Woof!"; }
|
||||
string honk() { return "Honk-Honk!"; }
|
||||
string moo() { return "Moo-Moo!"; }
|
||||
string meh() { return "Meh!"; }
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/***********************************************************************//**
|
||||
* @test Demonstration/Concept: a description language for list differences.
|
||||
* The representation is given as a linearised sequence of verb tokens.
|
||||
* This test demonstrates the application of such a diff representation
|
||||
* to a given source list, transforming this list to hold the intended
|
||||
* target list contents.
|
||||
*
|
||||
* @see session-structure-mapping-test.cpp
|
||||
*/
|
||||
class DiffListApplication_test : public Test
|
||||
{
|
||||
|
||||
virtual void
|
||||
run (Arg)
|
||||
{
|
||||
DataSeq src{a1,a2,a3,a4,a5};
|
||||
DiffSeq diff = generate_test_diff();
|
||||
CHECK (!isnil (diff));
|
||||
|
||||
DataSeq target = src;
|
||||
DiffApplicator<DataSeq> application(target);
|
||||
application.consume(diff);
|
||||
|
||||
CHECK (isnil (diff));
|
||||
CHECK (!isnil (target));
|
||||
CHECK (src != target);
|
||||
CHECK (target == DataSeq({b1,a3,a5,b2,b3,a4,b4}));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** Register this test class... */
|
||||
LAUNCHER (DiffListApplication_test, "unit common");
|
||||
|
||||
|
||||
|
||||
}} // namespace lib::test
|
||||
|
|
@ -7703,7 +7703,7 @@ Using transitions is a very basic task and thus needs viable support by the GUI.
|
|||
Because of this experience, ichthyo wants to support a more general case of transitions, which have N output connections, behave similar to their "simple" counterpart, but leave out the mixing step. As a plus, such transitions can be inserted at the source ports of N clips or between any intermediary or final output pipes as well. Any transition processor capable of handling this situation should provide some flag, in order to decide if he can be placed in such a manner. (wichin the builder, encountering a inconsistently placed transition is just an [[building error|BuildingError]])
|
||||
</pre>
|
||||
</div>
|
||||
<div title="TreeDiffFundamentals" creator="Ichthyostega" modifier="Ichthyostega" created="201411070156" modified="201411070257" tags="Concepts design" changecount="6">
|
||||
<div title="TreeDiffFundamentals" creator="Ichthyostega" modifier="Ichthyostega" created="201411070156" modified="201412010102" tags="Concepts design" changecount="11">
|
||||
<pre>At some points within the implementation we encounter problems of structural difference computation, like finding the effective changes in a tree data structure, or using a diff represetation as notification message format. Pointing out some observations and relating our approach(es) to the problem as generally known might be in place.
|
||||
|
||||
!classical diff problem
|
||||
|
|
@ -7714,14 +7714,14 @@ There is very wide spread use of a quite special flavour of diff calculations: t
|
|||
|
||||
!challenge of the diff problem
|
||||
While, from a mathematical point of view, the above optimisation problem can be considered solved, in practice the available solutions are far from perfect. The fundamental assumption of a linear sequence to base the diff turns out as an oversimplification -- real world data carries meaning and can be judged by imposing additional structure. There can be structure violating diffs and there can be nonsensical and misguiding diffs. In the light of this notion, every diff is in fact a structural diff.
|
||||
Unfortunately, attempts to amend the beautiful mathematical solution by incorporating this additional structure turns out to be incredible hard. Either, the general case problem can be proven to be ~NP-hard, or, exploiting some special structural properties renders the meaning of an "optimal" diff solution more or less arbitrary. The quest for a generic diff problem and an universal solution turns out to be a dead end.
|
||||
Unfortunately, attempts to amend the beautiful mathematical solution by incorporating this additional structure turns out to be incredible hard. Either, the general case problem can be proven to be ~NP-hard, or, at least, exploiting some special structural properties successfully renders the meaning of an "optimal" diff solution more or less arbitrary. The quest for a generic diff problem and an universal solution turns out to be a dead end.
|
||||
|
||||
!fundamentals of diff handling
|
||||
The most fundamental distinction is the difference between //finding// a diff and //representing// a diff. The former is concerned with uncovering structural relations, while the latter deals with knowledge about structural relations and thus is more general. It is possible to capture structural relations while they emerge -- this way describing a process of transformation. A likewise fundamental distinction is between //reordering// elements and //mutating// them. This is related to the notion of //identity,// which in turn implies an underlying model of the elements and entities to be considered for diffing. If, as an example, we model the tokens, functions and classes of program code as mere characters, we will be happily matching curly braces and can not expect meaningful diffs. So we need a theory about what can possibly happen, and we use this as a foundation to establish the representation of such possible processes. The choices taken here can make all the difference towards efficient and usable methods. The //matching problem// should be viewed from here: a matching is actually a hypothesis about possible processes of transformation -- this observation explains why the matching strategy is at the core of any diff algorithm. If we use some arbitrary notation of optimality, we have to consider endless combinations and end up with a lot of accidental complexity. Yet if we manage to base the calculation on a representation well aligned with the nature of the entities in question, the matching can be as simple as retrieving a given entry by ID. The actual work is reduced to extracting the actual changes in data.
|
||||
Before we can consider a diffing technique, we need to clarify the primitive operations used as foundation. These primitives form a language. This incurs a problem and choice of context. We might base the representation language on the situation where the diff is applied, we may care for the conciseness of the representation, or the effort and space required to apply it. We should consider if the focus is on reading and understanding the diff, if we intend to derive something directly from the diff representation -- say, if and what and when something is changed -- and we should consider how the context where the diff is established relates to the context where it is applied.
|
||||
The most fundamental distinction is the difference between //finding// a diff and //representing// a diff. The former is concerned with uncovering structural relations, while the latter deals with knowledge about structural relations and thus is more general. It is possible to capture structural relations while they emerge -- this way describing a process of transformation. A likewise fundamental distinction is between //reordering// elements and //mutating// them. This is related to the notion of //identity,// which in turn implies an underlying model of the elements and entities to be considered for diffing. If, as an example, we model the tokens, functions and classes of program code as mere characters, we will be happily matching curly braces and can not expect meaningful diffs. So first we need a theory about what can possibly happen, and we use this as a foundation to establish the representation of such possible processes. The choices taken here can make all the difference towards efficient and usable methods. The //matching problem// should be viewed from here: a matching is actually a hypothesis about possible processes of transformation -- this observation explains why the matching strategy is at the core of any diff algorithm. If we use some arbitrary notation of optimality, we have to consider endless combinations and end up with a lot of accidental complexity. Yet if we manage to base the calculation on a representation well aligned with the nature of the entities in question, the matching can be as simple as retrieving a given entry by ID. The actual work is reduced to extracting the raw changes in data.
|
||||
Before we can consider a diffing technique, we need to clarify the primitive operations used as foundation. These primitives form //a language.// This incurs a problem and choice of context. We might base the representation language on the situation where the diff is applied, we may care for the conciseness of the representation, or the effort and space required to apply it. We should consider if the focus is on reading and understanding the diff, if we intend to derive something directly from the diff representation -- say, if and what and when something is changed -- and we should consider how the context where the diff is established relates to the context where it is applied.
|
||||
</pre>
|
||||
</div>
|
||||
<div title="TreeDiffModel" creator="Ichthyostega" modifier="Ichthyostega" created="201410270313" modified="201411070254" tags="Model GuiPattern spec draft" changecount="40">
|
||||
<div title="TreeDiffModel" creator="Ichthyostega" modifier="Ichthyostega" created="201410270313" modified="201412010038" tags="Model GuiPattern spec draft" changecount="41">
|
||||
<pre>for the purpose of handling updates in the GUI timeline display efficiently, we need to determine and represent //structural differences//
|
||||
We build a slightly abstracted representation of tree changes and use this to propagate //change notifications// to the actual widgets. To keep the whole process space efficient, a demand-driven, stateless implementation approach is chosen. This reduces the problem into several layered stages.
|
||||
* our model is a heterogeneous tree &rArr; use demand-driven recursion
|
||||
|
|
@ -7762,7 +7762,7 @@ Thus, for our specific usage scenario, the foremost relevant question is //how t
|
|||
|{{{ins}}}(b~~4~~) |!| (b~~1~~, a~~3~~, a~~5~~, b~~2~~, b~~3~~, a~~4~~, b~~4~~)|() |
|
||||
|
||||
!!!extension to tree changes
|
||||
Basically we could send messages for recursive descent right after each {{{upd}}} token -- yet, while minimal, such a representation would be unreadable, and requires a dedicated stack storage on both sides. Thus we arrange for the //recursive treatment of children// to be sent //postfix,// after the messages for the current node. Recursive descent is indicated by explicit (and slightly redundant) //bracketing tokens://
|
||||
Basically we could send messages for recursive descent right after each {{{pick}}} token -- yet, while minimal, such a representation would be unreadable, and requires a dedicated stack storage on both sides. Thus we arrange for the //recursive treatment of children// to be sent //postfix,// after the messages for the current node. Recursive descent is indicated by explicit (and slightly redundant) //bracketing tokens://
|
||||
*{{{open}}}(node-ID) : recurse into the designated node, which must be present already as result of the preceding changes
|
||||
*{{{close}}}(node-ID) : close the current node context and return one step up; the node-ID is given for verification, but can be used to restore the working position at parent level
|
||||
In addition, we might consider to introduce up/down folding primitives
|
||||
|
|
|
|||
Loading…
Reference in a new issue