test-driven brainstorming: adding and removing session parts

This commit is contained in:
Fischlurch 2010-03-07 06:30:26 +01:00
parent baff536731
commit ea538d962f
6 changed files with 237 additions and 42 deletions

View file

@ -123,7 +123,7 @@ namespace asset {
ENSURE (contains (name, nameID));
Category cat (STRUCT, Traits<STRU>::catFolder);
return Asset::Ident (name, cat );
return Asset::Ident (name, cat ); ///////////////////////TICKET #565 the ID field should be just the ID, the query should go into a dedicated "capabilities" field.
}

View file

@ -59,7 +59,7 @@ namespace mobject
{
PSess sess = Session::current;
PMO clip = TestClip::create();
sess->add (clip);
sess->attach (clip);
///////////////////////////////////////////////////////////////////TICKET #499
// ASSERT (sess->currEDL().contains (clip));

View file

@ -28,13 +28,23 @@
//#include "proc/asset/timeline.hpp"
#include "proc/asset/sequence.hpp"
//#include "lib/lumitime.hpp"
#include "lib/util.hpp"
#include "lib/util-foreach.hpp"
#include "proc/mobject/session/testclip.hpp"
#include "proc/mobject/mobject-ref.hpp"
#include "proc/mobject/placement.hpp"
#include <tr1/functional>
#include <boost/ref.hpp>
#include <iostream>
#include <set>
using boost::ref;
using std::tr1::placeholders::_1;
using util::isSameObject;
using util::and_all;
using std::string;
using std::cout;
using std::set;
namespace mobject {
@ -50,14 +60,20 @@ namespace test {
// using lumiera::Time;
typedef MORef<session::Clip> RClip;
/*******************************************************************************
* @test access the current session and verify the correct
* structure of the most important components: The session
* contains an Sequence, we can get at the Fixture, we have at least
* one Track and the corresponding Track asset is available.
* @todo define further criteria to be checked
* @todo implement Sequence, Fixture, Session#rebuildFixture, asset::Track
* @test perform the most important structural modifications on a session and
* verify they're carried out properly.
* - attaching tracks
* - adding clips
*
*
* @todo check more kinds of modifications, especially moving parts
* @todo define criteria to be checked more precisely
* @todo verify the actually dispatched commands
*/
class SessionModifyParts_test : public Test
{
@ -68,14 +84,16 @@ namespace test {
CHECK (Session::current.isUp());
addTracks();
addObjects();
removeParts();
verify_dispatchedCommands();
}
void
addTracks()
{
PSess sess = Session::current;
CHECK (sess->isValid());
@ -93,38 +111,205 @@ namespace test {
RTrack track3 = seq->attachTrack("track-3", "root");
CHECK (track3 == focus.getObject());
RTrack track31 = sess->attach()
RTrack track31 = sess->attach(
asset::Struct::create (Query<asset::Track> ("id(track31)")));
CHECK (isSameObject (seq, til->getSequence()));
//verify default timeline
Axis& axis = til->getAxis();
CHECK (Time(0) == axis.origin());
CHECK (Time(0) == til->length()); ////////////////////////TICKET #177
CHECK (track31 == focus.getObject());
RTrack rootTrack = seq->rootTrack();
CHECK (3 == rootTrack->subTracks.size());
CHECK (track1 == rootTrack->subTracks[0]);
CHECK (track2 == rootTrack->subTracks[1]);
CHECK (track3 == rootTrack->subTracks[2]);
CHECK (0 == track1->subTracks.size());
CHECK (2 == track2->subTracks.size());
CHECK (track21 == track2->subTracks[0]);
CHECK (track22 == track2->subTracks[1]);
CHECK (1 == track3->subTracks.size());
CHECK (track21 == track3->subTracks[0]);
//verify global pipes
//TODO
set<RTrack> allTracks;
allTracks.insert(track1);
allTracks.insert(track2);
allTracks.insert(track21);
allTracks.insert(track22);
allTracks.insert(track3);
allTracks.insert(track31);
//verify default sequence
Track rootTrack = seq->rootTrack();
CHECK (rootTrack->isValid());
CHECK (Time(0) == rootTrack->length());
CHECK (0 == rootTrack->subTracks.size());
CHECK (0 == rootTrack->clips.size());
//TODO verify the output slots of the sequence
//TODO now use the generic query API to discover the same structure.
CHECK (til == *(sess->all<Timeline>()));
CHECK (seq == *(sess->all<Sequence>()));
CHECK (rootTrack == *(sess->all<Track>()));
CHECK (! sess->all<Clip>());
// verify we indeed covered all tracks known to the session....
CHECK (and_all (sess->all<Track>(), contains, ref(allTracks), _1 ));
}
void
addObjects()
{
PSess sess = Session::current;
CHECK (sess->isValid());
QueryFocus& focus = sess->focus();
CHECK (rootTrack == focus.getObject());
focus.navigate (til);
CHECK (til.getBinding() == focus.getObject());
CHECK (rootTrack == *(focus.children()));
CHECK (focus.getObject().isCompatible<session::Track>());
RClip clip1 = sess->attach (TestClip::create());
RTrack track31 = clip.getParent();
CHECK (track31);
CHECK ("track31" == track31->getNameID());
CHECK (1 == track31->clips.size());
CHECK (clip1 == track31->clips[0]);
RClip clip2 = track31.attach (TestClip::create());
RClip clip3 = track31.attach (clip1); // creates a clone instance
CHECK (clip1); CHECK (clip2); CHECK (clip3);
CHECK (clip1 != clip2);
CHECK (clip1 != clip3);
CHECK (clip2 != clip3);
CHECK (!isSharedPointee (clip1, clip2));
CHECK (!isSharedPointee (clip2, clip3));
CHECK ( isSharedPointee (clip1, clip3));
CHECK (isEquivalentPlacement (clip1, clip2));
CHECK (isEquivalentPlacement (clip2, clip3));
CHECK (isEquivalentPlacement (clip1, clip3));
RTrack track2 = sess->sequences[0]
->rootTrack()
->subTracks[1];
RClip clip4 = track2.attach (TestClip::create());
// now verify structure built up thus far
CHECK (focus.getObject() == track2); // focus follows point-of-mutation
CHECK (focus.contains (clip4));
CHECK (!focus.contains (clip1));
CHECK (!focus.contains (clip2));
CHECK (!focus.contains (clip3));
focus.attach (track31);
CHECK (focus.getObject() == track31);
CHECK (focus.contains (clip1));
CHECK (focus.contains (clip2));
CHECK (focus.contains (clip3));
CHECK (!focus.contains (clip4));
focus.reset();
CHECK (focus.getObject() == sess->getRoot());
CHECK (focus.contains (clip1)); // containment test includes sub-scopes
CHECK (focus.contains (clip2));
CHECK (focus.contains (clip3));
CHECK (focus.contains (clip4));
CHECK (!focus.hasChild (clip1)); // but they are indeed nested more deeply
CHECK (!focus.hasChild (clip2));
CHECK (!focus.hasChild (clip3));
CHECK (!focus.hasChild (clip4));
focus.attach (sess->sequences[0]->rootTrack()->subTracks[2]->subTracks[0]); // train wreck. Don't try it at home!
CHECK (focus.getObject() == track31); // (this test is an exception, as we know the structure precisely
// production code should always discover one level a time)
CHECK ( focus.hasChild (clip1));
CHECK ( focus.hasChild (clip2));
CHECK ( focus.hasChild (clip3));
CHECK (!focus.hasChild (clip4)); // ...because this one is on track2, not track31
}
void
removeParts()
{
PSess sess = Session::current;
CHECK (sess->isValid());
RTrack track31 = sess->sequences[0]->rootTrack()->subTracks[2]->subTracks[0];
CHECK (track31);
CHECK (3 == track31->clips.size());
RClip clip2 = track31->clips[1];
QueryFocus& focus = sess->focus();
focus.reset(); // navigate to root
CHECK (focus.contains (clip2));
CHECK (clip2);
clip2.purge();
CHECK (!clip2);
CHECK (!focus.contains (clip2));
CHECK (2 == track31->clips.size());
CHECK (clip2 != track31->clips[1]);
CHECK (focus.getObject() == track31); // focus follows point-of-mutation
// Using the query-focus to explore the contents of this current object (track31)
ScopeQuery<Clip>::iterator discoverClips = focus.explore<Clip>();
CHECK (discoverClips);
RClip clip1 = *discoverClips;
++discoverClips;
RClip clip3 = *discoverClips;
++discoverClips;
CHECK (!discoverClips);
CHECK (track31->clips[0] == clip1);
CHECK (track31->clips[1] == clip3);
/* please note: the clips aren't discovered in any defined order (hashtable!)
* especially, the order doesn't match the order of addition!
* thus, what's called clip1 here may or may not be
* what we called clip1 in addObjects()
*/
RTrack track3 = track31.getParent();
focus.reset(); // back to root
CHECK (focus.contains (clip1));
CHECK (focus.contains (clip3));
CHECK (focus.contains (track3));
CHECK (focus.contains (track31));
CHECK (clip1);
CHECK (clip3);
CHECK (track3);
CHECK (track31);
sess->purge (track31);
CHECK (focus.getObject() == track3);
focus.reset();
CHECK ( focus.contains (track3));
CHECK (!focus.contains (clip1));
CHECK (!focus.contains (clip3));
CHECK (!focus.contains (track31));
CHECK (!clip1);
CHECK (!clip3);
CHECK (!track31);
CHECK (track3);
track3.purge();
CHECK (!track3);
PSequence aSequence = sess->sequences[0];
CHECK (focus.getObject() == aSequence->rootTrack());
CHECK (2 == aSequece->rootTrack()->subTracks.size());
CHECK ( contains (sess->sequences, aSequence));
aSequence->rootTrack().purge(); // automatically kills the sequence as well (sequence == facade to the root track)
CHECK (!contains (sess->sequences, aSequence));
CHECK (0 == sess->sequences.size());
CHECK (0 == sess->timelines.size()); // killing the sequence also cascaded to the timeline and binding
CHECK (!sess->isValid()); // thus effectively the session is now invalid (no timeline)
CHECK (focus.getObject() == sess->getRoot());
sess->getRoot().purge(); // purging the root scope effectively resets the session to defaults
CHECK (sess->isValid());
CHECK (1 == sess->timelines.size());
CHECK (1 == sess->sequences.size());
CHECK (aSequence != sess->sequences[0]);
CHECK (aSequence.use_count() == 1); // we're holding the last remaining reference
}
void
verify_dispatchedCommands()
{
TODO ("verify the commands issued by this test"); ////////////////////////TICKET #567
}
};

View file

@ -46,6 +46,7 @@ namespace test {
using asset::PTimeline;
using asset::PSequence;
using asset::RTrack;
using lumiera::Time;
@ -109,7 +110,7 @@ namespace test {
//TODO
//verify default sequence
Track rootTrack = seq->rootTrack();
RTrack rootTrack = seq->rootTrack();
ASSERT (rootTrack->isValid());
ASSERT (Time(0) == rootTrack->length());
ASSERT (0 == rootTrack->subTracks.size());

View file

@ -271,10 +271,10 @@ namespace test {
RTrack someTrack = sess->sequences[0]->getTracks();
// indirectly cause a new sequence to come to life...
RTrack newTrack = sess->getRoot().attach (someTrack); /////////////////TICKET #412 does attaching new objects really work this way??
RTrack newTrack = sess->getRoot().attach (someTrack); // attach new Placement<Track> to root scope
CHECK (newTrack != someTrack); // it's a new placement
CHECK (num_sequences + 1 == sess->sequences.size());
CHECK (num_sequences + 1 == sess->sequences.size()); // this root-attachment created a new sequence by sideeffect
PSequence aSequence = sess->sequences[num_sequences];
CHECK (newTrack == aSequence->getTracks());
CHECK (newTrack);

View file

@ -4149,7 +4149,7 @@ The session and the models rely on dependent objects beeing kept updated and con
&amp;rarr; see [[details here...|ModelDependencies]]
</pre>
</div>
<div title="SessionInterface" modifier="Ichthyostega" modified="201003042334" created="200904242108" tags="SessionLogic GuiIntegration design draft discuss" changecount="36">
<div title="SessionInterface" modifier="Ichthyostega" modified="201003070527" created="200904242108" tags="SessionLogic GuiIntegration design draft discuss" changecount="41">
<pre>&quot;Session Interface&quot;, when used in a more general sense, denotes a compound of several interfaces and facilities, together forming the primary access point to the user visible contents and state of the editing project.
* the API of the session class
* the accompanying management interface (SessionManager API)
@ -4210,6 +4210,12 @@ When adding an object, a [[scope|PlacementScope]] needs to be specified. Thus it
* how to observe the requirement of ''dispatching'' mutations ([[Command]])?
* how to integrate the two possible search depths (children and all)?
* how is all of this related to the LayerSeparationInterfaces, here SessionFacade und EditFacade?
&lt;&lt;&lt;
__preliminary notes__: {{red{3/2010}}} Discovery functions accessible from the session API are always written such as to return ~MObjectRefs. These expose generic functions for modifying the structure: {{{attach(MObjectRef)}}} and {{{purge()}}}. The session API exposes variations of these functions. Actually, all these functions do dispatch the respective commands automatically. To the contrary, the raw functions for adding and removing placements are located on the PlacementIndex; the're accessible as SessionServices &amp;mdash; which are intended for Proc-Layer's internal use solely. This separation isn't meant to be airtight, just an reminder for proper use.
Currently, I'm planning to modify MObjectRef to return only a const ref to the underlying facilities by default. Then, there would be a subclass which is //mutation enabled.// But this subclass will check for the presence of a mutation-permission token &amp;mdash; which is exposed via thread local storage, but //only within a command dispatch.// Again, no attempt is made to make this barrier airtight. Indeed, for tests, the mutation-permission token can just be created in the local scope. After all, this is not conceived as an authorisation scheme, rather as a automatic sanity check. It's the liability of the client code to ensure any mutation is dispatched.
&lt;&lt;&lt;
</pre>
</div>
<div title="SessionLifecycle" modifier="Ichthyostega" modified="201001070440" created="200911070329" tags="SessionLogic spec" changecount="23">
@ -5886,7 +5892,7 @@ Of course, we can place other ~MObjects relative to some track (that's the main
&amp;rarr; [[Anatomy of the high-level model|HighLevelModel]]
</pre>
</div>
<div title="TrackHandling" modifier="Ichthyostega" modified="201003050315" created="200804110013" tags="spec" changecount="32">
<div title="TrackHandling" modifier="Ichthyostega" modified="201003070510" created="200804110013" tags="spec" changecount="35">
<pre>What //exactly&amp;nbsp;// is denoted by &amp;raquo;Track&amp;laquo; &amp;mdash; //basically&amp;nbsp;// a working area to group media objects placed at this track at various time positions &amp;mdash; varies depending on context:
* viewed as [[structural asset|StructAsset]], tracks are nothing but global identifiers (possibly with attached tags and description)
* regarding the structure //within each [[Sequence]],// tracks form a tree-like grid, the individual track being attached to this tree by a [[Placement]], thus setting up properties of placement (time reference origin, output connection, layer, pan) which will be inherited down to any objects located on this track and on child tracks, if not overridden more locally.
@ -5898,10 +5904,13 @@ Tracks thus represent a blend of several concepts, but depending on the context
Under some cincumstances though, especially from within the [[Builder]], we refer to a {{{Placement&lt;Track&gt;}}} rather, denoting a specific instantiation located at a distinct node within the tree of tracks of a given sequence. These latter referrals are always done by direct object reference, e.g. while traversing the track tree (generally there is no way to refer to a placement by name {{red{Note 3/2010 meanwhile we have placementIDs and we have ~MObjectRef. TODO better wording}}}).
!creating tracks
Similar to [[pipes|Pipe]] and [[processing patterns|ProcPatt]], track-assets need not be created, but rather leap into existence on first referral. On the contrary, you need to explicitly create the {{{Placement&lt;Track&gt;}}} for attaching it to some node within the tree of tracks of an sequence. The public access point for creating such a placement is {{{asset::Struct::create(trackID}}} (i.e. the {{{asset::StructFactory}}}), which, as a convenience shortcut, can also be accessed from the interface of the track-objects within the sequence for adding new child tracks to a given track.{{red{TODO 3/2010: not quite, the latter is an abstraction built on top. It //delegates// to the ~StructFactory internally, but also wraps into Command}}}
Similar to [[pipes|Pipe]] and [[processing patterns|ProcPatt]], track-assets need not be created, but rather leap into existence on first referral. On the contrary, you need to explicitly create the {{{Placement&lt;Track&gt;}}} for attaching it to some node within the tree of tracks of an sequence. The public access point for creating such a placement is {{{MObject::create(trackID}}} (i.e. the ~MObjectFactory. Here, the {{{trackID}}} denotes the track-asset. This placement, as returned from the ~MObjectFactory isn't attached to the session yet; {{{Session::current-&gt;attach(trackPlacement)}}} performs this step by creating a copy managed by the PlacementIndex and attaching it at the current QueryFocus, which was assumed to point to a track previously. Usually, client code would use one of the provided convenience shortcuts for this rather involved call sequence:
* the interface of the track-~MObjects exposes a function for adding new child tracks.
* the session API contains a function to attach child tracks. In both cases, existing tracks can be referred by plain textual ID.
* any MObjectRef to an object within the session allows to attach another placement or ~MObjectRef
!removal
Deleting a Track is an operation with drastic consequences, as it will cause the removal of all child tracks and the deletion of //all object placements to this track,// which could cause the resepctive objects to go out of scope (being deleted automatically by the placements or other smart pointer classes in charge of them). On the contrary, removing a {{{Placement&lt;Track&gt;}}} from the tree of tracks of an sequence will just cause all objects placed onto this track to disappear (because they are no longer reachable for the build process). On re-adding it, they will show up again. (This is how things behave based on how we defined the relations of the entities to be. Another question is if we want to make this functionality available to the user. Judging from the use of Ardour's &amp;laquo;Playlists&amp;raquo;, such a feature may be quite helpful).
Deleting a Track is an operation with drastic consequences, as it will cause the removal of all child tracks and the deletion of //all object placements to this track,// which could cause the resepctive objects to go out of scope (being deleted automatically by the placements or other smart pointer classes in charge of them). If the removed track was the root track of a sequence, this sequence and any timeline or VirtualClip binding to it will be killed as well. Deleting of objects can be achieved by {{{MObjectRef::purge()}}} or {{{Session::purge(MObjectRef)}}}
!using Tracks
The '''Track Asset''' is a rather static object with limited capabilities. It's main purpose is to be a point of referral. Track assets have a description field and you may assign a list of [[tags|Tag]] to them (which could be used for binding ConfigRules). Note that track assets are globally known within the session, they can't be limited to just one [[Sequence]] (but you are allways free not to refer to some track from a given sequence). By virtue of this global nature, you can utilize the track assets to enable/disable a bunch of objects irrespective of what sequence they are located in, and probably it's a good idea to allow the selection of specific tracks for rendering.