WIP design the ScopePath API by unit test

This commit is contained in:
Fischlurch 2009-10-18 19:08:21 +02:00
parent 474c293197
commit f0c9beb5c6
6 changed files with 253 additions and 28 deletions

View file

@ -25,7 +25,8 @@
#define MOBJECT_SESSION_SCOPE_PATH_H
//#include "proc/mobject/mobject.hpp"
#include "proc/mobject/placement-ref.hpp"
//#include "proc/mobject/placement-ref.hpp"
#include "proc/mobject/session/scope.hpp"
#include <vector>
//#include <string>

View file

@ -65,6 +65,21 @@ namespace session {
}
Scope const&
Scope::containing (RefPlacement const& refPlacement)
{
return containing (*refPlacement);
}
PlacementMO&
Scope::getTop() const
{
ASSERT (anchor_);
return *anchor_;
}
/** retrieve the parent scope which encloses this scope.
* @throw error::Invalid if this is the root scope
*/

View file

@ -50,6 +50,10 @@ namespace session {
/**
* TODO type comment
* @note Scope is a passive entity,
* basically just wrapping up a Scope-top Placement.
* Contrast this to QueryFocus, which actively
* maintains the current focus location.
*/
class Scope
{
@ -85,20 +89,5 @@ namespace session {
inline Scope const&
Scope::containing (RefPlacement const& refPlacement)
{
return containing (*refPlacement);
}
inline PlacementMO&
Scope::getTop() const
{
ASSERT (anchor_);
return *anchor_;
}
}} // namespace mobject::session
#endif

View file

@ -22,28 +22,30 @@
#include "lib/test/run.hpp"
#include "lib/test/test-helper.hpp"
#include "proc/mobject/session/test-scopes.hpp"
//#include "lib/lumitime.hpp"
//#include "proc/mobject/placement-ref.hpp"
//#include "proc/mobject/placement-index.hpp"
//#include "proc/mobject/test-dummy-mobject.hpp"
//#include "lib/util.hpp"
#include "lib/util.hpp"
//#include <iostream>
//#include <string>
//using util::isSameObject;
//using lumiera::Time;
//using std::string;
//using std::cout;
//using std::endl;
namespace mobject {
namespace session {
namespace test {
using util::isSameObject;
//using lumiera::Time;
//using std::string;
//using std::cout;
//using std::endl;
// using namespace mobject::test;
using lumiera::error::LUMIERA_ERROR_LOGIC;
/***************************************************************************
@ -62,8 +64,209 @@ namespace test {
{
// Prepare an (test)Index backing the PlacementRefs
PPIdx index = build_testScopes();
PMO& startPlacement = *(index->query<TestSubMO1>(index->getRoot()));
ASSERT (startPlacement);
UNIMPLEMENTED ("unit test regarding placement scope handling");
ScopePath testPath = buildPath (startPlacement);
checkRelations (testPath,startPlacement);
invalildPath (testPath,startPlacement);
check_Identity_and_Copy (startPlacement);
navigate (testPath, index);
}
ScopePath
buildPath (PMO& startPla)
{
Scope startScope (startPla);
ScopePath path (startScope);
ASSERT (path);
ASSERT (path.contains (startScope));
return path;
ScopePath path (startScope);
ScopePath path2 (startScope);
ScopePath path3 (path2);
}
void
checkRelations (ScopePath path1, PMO& refPla)
{
ASSERT (path1.contains (refPla));
Scope refScope (refPla);
ASSERT (path1.contains (refScope));
ASSERT (path1.endsAt (refScope));
ScopePath path2 (refScope);
ASSERT (path2.contains (refScope));
ASSERT (path2.endsAt (refScope));
ASSERT (path1 == path2);
ASSERT (!isSameObject (path,path2));
Scope parent = path2.moveUp();
ASSERT (path2.endsAt (parent));
ASSERT (path1.endsAt (refScope));
ASSERT (parent == refScope.getParent());
ASSERT (path1 != path2);
ASSERT (path2 != path1);
ASSERT (path1.contains (path2)); ////////////////////TODO: not clear if we really need to implement those relations
ASSERT (!disjoint(path1,path2));
ASSERT (path2 == commonPrefix(path1,path2));
ASSERT (path2 == commonPrefix(path2,path1));
ASSERT (path1 != commonPrefix(path1,path2));
ASSERT (path1 != commonPrefix(path2,path1));
}
void
invalidPath (ScopePath refPath, PMO& refPla)
{
ASSERT (refPath);
ASSERT (!ScopePath::INVALID);
ScopePath invalidP (ScopePath::INVALID);
ASSERT (!invalildP);
ASSERT (invalildP == ScopePath::INVALID);
ASSERT (!isSameObject (invalidP, ScopePath::INVALID));
ASSERT (refPath.contains (refPla));
ASSERT (!invalidP.contains (refPla));
Scope refScope (refPla);
ASSERT (!invalidP.contains (refScope));
ASSERT (!invalidP.endsAt (refScope));
ASSERT (refPath.contains (invalidP)); // If the moon consists of green cheese, I'll eat my hat!
ASSERT (!invalidP.contains (refPath));
ASSERT (invalidP == commonPrefix(refPath,invalidP));
ASSERT (invalidP == commonPrefix(invalidP,refPath));
VERIFY_ERROR (LOGIC, invalidP.moveUp());
Scope root = refPath.goRoot();
ASSERT (1 == refPath.length());
Scope nil = refPath.moveUp();
ASSERT (!refPath);
ASSERT (!nil.isValid());
ASSERT (refPath == invalidP);
ASSERT (invalidP.contains (nil));
refPath.navigate(root);
ASSERT (refPath != invalidP);
ASSERT (refPath);
//ScopePath::INVALID.navigate(root); // doesn't compile
}
void
check_Identity_and_Copy (PMO& refPla)
{
Scope startScope (startPla);
ScopePath path1 (startScope);
ScopePath path2 (startScope);
ScopePath path3 (path2);
ASSERT (path1.contains (startScope));
ASSERT (path2.contains (startScope));
ASSERT (path3.contains (startScope));
ASSERT (path1 == path2);
ASSERT (path2 == path3);
ASSERT (path1 == path3);
ASSERT (!isSameObject (path1,path2));
ASSERT (!isSameObject (path2,path3));
ASSERT (!isSameObject (path1,path3));
Scope parent = path3.moveUp();
ASSERT (path1 == path2);
ASSERT (path2 != path3);
ASSERT (path1 != path3);
path2 = path3;
ASSERT (path1 != path2);
ASSERT (path2 == path3);
ASSERT (path1 != path3);
path1 = ScopePath::INVALID;
ASSERT (path1 != path2);
ASSERT (path2 != path3);
ASSERT (path1 != path3);
}
/** @test modify a path by \em navigating it.
* - move one step above the current leaf
* - move up to the root element
* - move back to the parent and verify we're just above the leaf
* - attach a new sibling node and move the path down to there
* - extract the common prefix, which should again point to the parent
* - find a placement in a completely separate branch (only sharing the
* root node). Navigate to there and verify root is the common prefix.
*/
void
navigate (const ScopePath refPath, PPIdx index)
{
ScopePath path (refPath);
ASSERT (path == refPath);
Scope leaf = path.getLeaf();
Scope parent = path.moveUp();
ASSERT (path != refPath);
ASSERT (refPath.contains (path));
ASSERT (refPath.endsAt (leaf));
ASSERT (path.endsAt (parent));
ASSERT (parent == leaf.getParent());
ASSERT (parent == path.getLeaf());
Scope root = path.goRoot();
ASSERT (path != refPath);
ASSERT (path.endsAt (root));
ASSERT (refPath.contains (path));
ASSERT (!path.endsAt (parent));
ASSERT (!path.endsAt (leaf));
path.navigate (parent);
ASSERT (path.endsAt (parent));
ASSERT (!path.endsAt (root));
ASSERT (!path.endsAt (leaf));
PMO newNode (*new DummyMO);
index->insert (newNode, parent); // place as sibling of "leaf"
path.navigate (newNode);
Scope sibling = path.getLeaf();
ASSERT (parent == sibling.getParent());
ASSERT (path.endsAt (sibling));
ASSERT (path.contains (parent));
ASSERT (path.contains (root));
ASSERT (!refPath.contains (path));
ASSERT (!path.contains (refPath));
ASSERT (!disjoint (path,refPath));
ASSERT (!disjoint (refPath,path));
Path prefix = commonPrefix (path,refPath);
ASSERT (prefix == commonPrefix (refPath,path));
ASSERT (prefix.endsAt (parent));
ASSERT (!prefix.contains (leaf));
ASSERT (!prefix.contains (sibling));
path.navigate (prefix.getLeaf());
ASSERT (path == prefix);
// now explore a completely separate branch....
PMO& separatePlacement = *index->query<TestSubMO21> (
*index->query<TestSubMO21> (
*index->query<TestSubMO21> (root)));
path.navigate (separatePlacement);
ASSERT (path);
ASSERT (disjoint (path,refPath));
ASSERT (path.contains(separatePlacement));
Scope other = path.getLeaf();
ASSERT (other.getTop() == separatePlacement);
ScopePath rootPrefix = commonPrefix (path,refPath);
ASSERT (rootPrefix.endsAt (root));
}
};

View file

@ -67,8 +67,8 @@ namespace test {
PDum p5(*new TestSubMO21);
PDum ps1(*new DummyMO);
PDum ps2(*new TestSubMO1);
PDum ps2(*new TestSubMO2);
PDum ps3(*new TestSubMO1);
// Prepare an (test)Index backing the PlacementRefs
PPIdx index (PlacementIndex::create().get(), &remove_testIndex); // taking ownership
@ -83,7 +83,7 @@ namespace test {
index->insert (ps1,root);
index->insert (ps2,root);
index->insert (ps3,root);
index->insert (ps3, ps2);
return index;
}

View file

@ -767,6 +767,22 @@ config.macros.timeline.handler = function(place,macroName,params,wikifier,paramS
On the implementation side, we use a special kind of MObject, acting as an anchor and providing an unique identity. Like any ~MObject, actually a placement establishes the connection and the scope, and typically constitutes a nested scope (e.g. the scope of all objects //within// the sequence to be bound into a timeline)
</pre>
</div>
<div title="BindingScopeProblem" modifier="Ichthyostega" modified="200910181440" created="200910181435" tags="SessionLogic spec" changecount="4">
<pre>There is some flexibility in the HighLevelModel, allowing to attach the same [[Sequence|EDL]] onto multiple [[timelines|Timeline]] or even into a [[meta-clip|VirtualClip]]. Thus, while there is always an containment relation which can be used to define the current PlacementScope, we can't always establish an unique path from any given location up to the model root. In the most general case, we have to deal with a DAG, not a tree.
!solution idea
Transform the DAG into a tree by //focussing//&amp;nbsp; on the current situation and context. Have a state containing the //current path.// &amp;rarr; QueryFocus
!!detail decisions
!!!!how to represent scoping
We us a 2-layer approach: initially, containment is implemented through a registration in the PlacementIndex. Building on that, scope is layered on top as an abstraction, which uses the registered containment relation, but takes the current access path into account at the critical points (where a [[binding|BindingMO]] comes into play)
!!!!the basic containment tree
each Placement (with the exception of the root) gets registered as contained in yet another Placement. This registration is entirely a tree, and thus differs from the real scope nesting at the Sequence level: The scopes constituting Sequences and Timelines are registered as siblings, immediately below the root. This has some consequences
# Sequences as well as Timelines can be discovered as contents of the model root
# ScopePath digresses at Sequence level from the basic containment tree
</pre>
</div>
<div title="BuildProcess" modifier="Ichthyostega" modified="200906071813" created="200706190658" tags="dynamic Builder img" changecount="30">
<pre>All decisions on //how // the RenderProcess has to be carried out are concentrated in this rather complicated Builder Subsystem. The benefit of this approach is, besides decoupling of subsystems, to keep the actual performance-intensive video processing code as simple and transparent as possible. The price, in terms of increased complexity &amp;mdash; to pay in the Builder &amp;mdash; can be handled by making the Build Process generic to a large degree. Using a Design By Contract approach we can decompose the various decisions into small decision modules without having to trace the actual workings of the Build Process as a whole.
@ -3052,7 +3068,7 @@ Placement references mimic the behaviour of a real placement, i.e. they proxy th
{{red{WIP}}}
</pre>
</div>
<div title="PlacementScope" modifier="Ichthyostega" modified="200910162011" created="200905120304" tags="SessionLogic spec img" changecount="16">
<div title="PlacementScope" modifier="Ichthyostega" modified="200910181415" created="200905120304" tags="SessionLogic spec img" changecount="18">
<pre>MObjects are attached into the [[Session]] by adding a [[Placement]]. Because this especially includes the case of //grouping or container objects,// e.g. tracks or [[meta-clips|VirtualClip]], any placement may optionally define and root a scope, and every placement is at least contained in one encompassing scope &amp;mdash; of course with the exception of the absolute top level, which can be thought off as being contained in a scope of handling rules.
Thus, while the [[sequences (former called EDL)|EDL]] act as generic container holding a pile of placments, actually there is a more fine grained structure based on the nesting of the tracks, which especially in Lumiera's HighLevelModel belong to the sequence (they aren't a property of the top level timeline as one might expect). Building upon these observations, we actually require each addition of a placement to specify a scope. Consequently, for each Placement at hand it is possible to determine an //containing scope,// which in turn is associated with some Placement of a top-level ~MObject for this scope. The latter is called the ''scope top''. An example would be the {{{Placement&lt;Track&gt;}}} acting as scope of all the clips placed onto this track. The //implementation//&amp;nbsp; of this tie-to-scope is provided by the same mechanism as utilised for relative placements, i.e. an directional placement relation. Actually, this relation is implemented by the PlacementIndex within the current [[Session]].
@ -3067,6 +3083,7 @@ There is only a limited number of situations constituting a scope
* tracks may contain nested sub tracks.
* clips and (track-level) effects likewise are associated with an enclosing track.
* an important special case of relative placement is when an object is [[attached|AttachedPlacementProblem]] to another leading object, like e.g. an effect modifying a clip
__note__: attaching a Sequence in multiple ways &amp;rarr; [[causes scoping problems|BindingScopeProblem]]
!Purpose of Placement scoping
Similar to the common mechanisms of object visibility in programming languages, placement scopes guide the search for and resolution of properties of placement. Any such property //not defined locally// within the placement is queried ascending through the sequence of nested scopes. Thus, global definitions can be shadowed by local ones.
@ -3882,7 +3899,7 @@ It will contain a global video and audio out pipe, just one EDL with a single tr
&amp;rarr; see [[Relation of Project, Timelines and Sequences|TimelineSequences]]
</pre>
</div>
<div title="SessionStructureQuery" modifier="Ichthyostega" modified="200910171255" created="200910112322" tags="SessionLogic design draft discuss" changecount="14">
<div title="SessionStructureQuery" modifier="Ichthyostega" modified="200910181417" created="200910112322" tags="SessionLogic design draft discuss" changecount="15">
<pre>The frontside interface of the session allows to query for contained objects; it is used to discover the structure and contents of the currently opened session/project. Access point is the public API of the Session class, which, besides exposing those queries, also provides functionality for adding and removing session contents.
!discovering structure
@ -3894,7 +3911,7 @@ So, clearly, there are two flavours of such an contents exploration query: it co
The (planned) session structure of Lumiera allows for quite some flexibility, which, of course comes at a price tag. Especially, as there can be multiple independent top level timelines, where a given sequence can be used simultaneously within multiple timelines and even as virtual media within a [[meta-clip|VirtualClip]], and moreover, as properties of any Placement are rather queried and discovered within the PlacementScope of this object &amp;mdash; consequently the discovered values may depend on //how you look at this object.// More specifically, it depends on the ''access path'' used to discover this object, because this path constitutes the actual scope visible to this object.
To give an example, let's assume a clip within a sequence, and this sequence is both linked to the top-level timeline, but also used within a meta-clip. (see the drawing &amp;rarr; [[here|PlacementScope]])
In this case, the sequence has an 1:n [[binding|BindingMO]]. A binding is (by definition) also a PlacementScope, and, incidentally, in the case of binding the sequence into a timeline, the binding also translates //logical// output designations into global pipes found within the timeline, while otherwise they get mapped onto &quot;channels&quot; of the virtual media used by the virtual clip. Thus, the absolute time position as well as the output connection of a given clip within this sequence //depends on how we look at this clip.// Does this clip apear as part of the global timeline, or did we discover it as contained within the meta-clip?
In this case, the sequence has an 1:n [[binding|BindingMO]]. A binding is (by definition) also a PlacementScope, and, incidentally, in the case of binding the sequence into a timeline, the binding also translates //logical// output designations into global pipes found within the timeline, while otherwise they get mapped onto &quot;channels&quot; of the virtual media used by the virtual clip. Thus, the absolute time position as well as the output connection of a given clip within this sequence //depends on how we look at this clip.// Does this clip apear as part of the global timeline, or did we discover it as contained within the meta-clip? &amp;rarr; see [[discussion of this scope-binding problem|BindingScopeProblem]]
!!solution requirements
The baseline of any solution to this problem is clear: at the point where the query is issued, a context information is necessary; this context yields an access path from top level down to the object to be queried, and this access path constitutes the effective scope this object can utilise for resolving the query.