Merge integration of placement scopes and QueryFocus
This commit is contained in:
commit
bb3eb8f3aa
45 changed files with 1348 additions and 488 deletions
|
|
@ -63,11 +63,13 @@
|
|||
** \em dereferenced to yield the "current" value.
|
||||
** - moreover, iterators may be incremented until exhaustion.
|
||||
**
|
||||
** @todo WIP WIP WIP
|
||||
** @todo see Ticket #182
|
||||
** @todo naming of the iteration control function: TICKET #410
|
||||
**
|
||||
** @see scoped-ptrvect.hpp
|
||||
** @see iter-adapter-test.cpp
|
||||
** @see itertools.hpp
|
||||
** @see IterSource (completely opaque iterator)
|
||||
** @see iter-type-binding.hpp
|
||||
**
|
||||
*/
|
||||
|
||||
|
||||
|
|
@ -77,54 +79,25 @@
|
|||
|
||||
#include "lib/error.hpp"
|
||||
#include "lib/bool-checkable.hpp"
|
||||
#include "lib/iter-type-binding.hpp"
|
||||
|
||||
#include <boost/type_traits/remove_const.hpp>
|
||||
|
||||
|
||||
|
||||
namespace lib {
|
||||
|
||||
|
||||
namespace {
|
||||
/**
|
||||
* Helper for creating nested typedefs
|
||||
* within the iterator adaptor, similar to what the STL does.
|
||||
*/
|
||||
template<typename TY>
|
||||
struct IterTraits
|
||||
{
|
||||
typedef typename TY::pointer pointer;
|
||||
typedef typename TY::reference reference;
|
||||
typedef typename TY::value_type value_type;
|
||||
};
|
||||
|
||||
template<typename TY>
|
||||
struct IterTraits<TY *>
|
||||
{
|
||||
typedef TY value_type;
|
||||
typedef TY& reference;
|
||||
typedef TY* pointer;
|
||||
};
|
||||
|
||||
template<typename TY>
|
||||
struct IterTraits<const TY *>
|
||||
{
|
||||
typedef TY value_type;
|
||||
typedef const TY& reference;
|
||||
typedef const TY* pointer;
|
||||
};
|
||||
|
||||
|
||||
namespace { // internal helpers
|
||||
void
|
||||
_throwIterExhausted()
|
||||
{
|
||||
throw lumiera::error::Invalid ("Can't iterate further",
|
||||
lumiera::error::LUMIERA_ERROR_ITER_EXHAUST);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Adapter for building an implementation of the lumiera forward iterator concept.
|
||||
* The "current position" is represented as an opaque element (usually an nested iterator),
|
||||
|
|
@ -145,15 +118,16 @@ namespace lib {
|
|||
* -# it should be copy constructible
|
||||
* -# when IterAdapter is supposed to be assignable, then POS should be
|
||||
* -# it should provide embedded typedefs for pointer, reference and value_type,
|
||||
* or alternatively resolve these types through a specialisation if IterTraits.
|
||||
* or alternatively resolve these types through specialisation of iter::TypeBinding.
|
||||
* -# it should be convertible to the pointer type it declares
|
||||
* -# dereferencing it should yield type that is convertible to the reference type
|
||||
* -# dereferencing should yield a type that is convertible to the reference type
|
||||
* - CON points to the data source of this iterator (typically a data container type)
|
||||
* We store a pointer-like backlink to invoke a special iteration control API:
|
||||
* We store a pointer-like backlink to invoke a special iteration control API: //////////////TICKET #410
|
||||
* -# \c hasNext yields true iff the source has yet more result values to yield
|
||||
* -# \c iterNext advances the POS to the next element
|
||||
*
|
||||
* @see scoped-ptrvect.hpp usage example
|
||||
* @see iter-type-binding.hpp
|
||||
* @see iter-adaptor-test.cpp
|
||||
*/
|
||||
template<class POS, class CON>
|
||||
|
|
@ -164,9 +138,9 @@ namespace lib {
|
|||
mutable POS pos_;
|
||||
|
||||
public:
|
||||
typedef typename IterTraits<POS>::pointer pointer;
|
||||
typedef typename IterTraits<POS>::reference reference;
|
||||
typedef typename IterTraits<POS>::value_type value_type;
|
||||
typedef typename iter::TypeBinding<POS>::pointer pointer;
|
||||
typedef typename iter::TypeBinding<POS>::reference reference;
|
||||
typedef typename iter::TypeBinding<POS>::value_type value_type;
|
||||
|
||||
IterAdapter (CON src, POS const& startpos)
|
||||
: source_(src)
|
||||
|
|
@ -299,9 +273,9 @@ namespace lib {
|
|||
IT e_;
|
||||
|
||||
public:
|
||||
typedef typename IterTraits<IT>::pointer pointer;
|
||||
typedef typename IterTraits<IT>::reference reference;
|
||||
typedef typename IterTraits<IT>::value_type value_type;
|
||||
typedef typename iter::TypeBinding<IT>::pointer pointer;
|
||||
typedef typename iter::TypeBinding<IT>::reference reference;
|
||||
typedef typename iter::TypeBinding<IT>::value_type value_type;
|
||||
|
||||
RangeIter (IT const& start, IT const& end)
|
||||
: p_(start)
|
||||
|
|
|
|||
|
|
@ -255,6 +255,13 @@ namespace lib {
|
|||
typedef typename IterSource<Val>::iterator Iter;
|
||||
};
|
||||
|
||||
template<class IT>
|
||||
struct _RangeT
|
||||
{
|
||||
typedef typename IT::value_type Val;
|
||||
typedef typename IterSource<Val>::iterator Iter;
|
||||
};
|
||||
|
||||
template<class MAP>
|
||||
struct _MapT
|
||||
{
|
||||
|
|
@ -400,6 +407,21 @@ namespace lib {
|
|||
return IterSource<ValType>::build (new WrappedLumieraIterator<Range>(contents));
|
||||
}
|
||||
|
||||
|
||||
/** @return a Lumiera Forward Iterator yielding all values
|
||||
* defined by a classical Iterator range.
|
||||
*/
|
||||
template<class IT>
|
||||
typename _RangeT<IT>::Iter
|
||||
eachEntry (IT const& begin, IT const& end)
|
||||
{
|
||||
typedef typename _RangeT<IT>::Val ValType;
|
||||
typedef RangeIter<IT> Range;
|
||||
|
||||
Range contents (begin, end);
|
||||
return IterSource<ValType>::build (new WrappedLumieraIterator<Range>(contents));
|
||||
}
|
||||
|
||||
}
|
||||
using iter_source::wrapIter;
|
||||
using iter_source::eachMapKey;
|
||||
|
|
|
|||
84
src/lib/iter-type-binding.hpp
Normal file
84
src/lib/iter-type-binding.hpp
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
ITER-TYPE-BINDING.hpp - control type variations for IterAdapter
|
||||
|
||||
Copyright (C) Lumiera.org
|
||||
2010, 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 iter-type-binding.hpp
|
||||
** Type re-binding helper template for IterAdapter and friends.
|
||||
** This header defines a trait template which is used by the Iterator
|
||||
** adapters to figure out the value-, pointer- and reference types
|
||||
** when wrapping iterators or containers. For extending the use of
|
||||
** Iterator adapters and Iter-tools to situations involving const
|
||||
** iterators or custom containers, explicit specialisations
|
||||
** might be injected prior to instantiating the Iterator adapter
|
||||
** template.
|
||||
**
|
||||
** @see iter-adapter.hpp
|
||||
** @see scope-path.hpp usage example (explicit specialisation)
|
||||
*/
|
||||
|
||||
|
||||
#ifndef LIB_ITER_TYPE_BINDING_H
|
||||
#define LIB_ITER_TYPE_BINDING_H
|
||||
|
||||
|
||||
#include "lib/error.hpp"
|
||||
|
||||
|
||||
|
||||
namespace lib {
|
||||
namespace iter {
|
||||
|
||||
/**
|
||||
* Type re-binding helper template for creating nested typedefs
|
||||
* for use by IterAdapter or similar "Lumiera Forward Iterators".
|
||||
* This trait provides a value-, reference- and pointer type,
|
||||
* similar to what the STL does.
|
||||
* @note client code might define specialisations
|
||||
* to handle tricky situations (like const_reverse_iter)
|
||||
*/
|
||||
template<typename TY>
|
||||
struct TypeBinding
|
||||
{
|
||||
typedef typename TY::pointer pointer;
|
||||
typedef typename TY::reference reference;
|
||||
typedef typename TY::value_type value_type;
|
||||
};
|
||||
|
||||
template<typename TY>
|
||||
struct TypeBinding<TY *>
|
||||
{
|
||||
typedef TY value_type;
|
||||
typedef TY& reference;
|
||||
typedef TY* pointer;
|
||||
};
|
||||
|
||||
template<typename TY>
|
||||
struct TypeBinding<const TY *>
|
||||
{
|
||||
typedef TY value_type;
|
||||
typedef const TY& reference;
|
||||
typedef const TY* pointer;
|
||||
};
|
||||
|
||||
|
||||
|
||||
}} // namespace lib
|
||||
#endif
|
||||
|
|
@ -129,15 +129,16 @@ namespace test{
|
|||
* an assertion failure. In case of an exception, the #lumiera_error
|
||||
* state is checked, cleared and verified.
|
||||
*/
|
||||
#define VERIFY_ERROR(ERROR_ID, ERRONEOUS_STATEMENT) \
|
||||
try \
|
||||
{ \
|
||||
ERRONEOUS_STATEMENT ; \
|
||||
NOTREACHED("'%s' resisted to fail", #ERRONEOUS_STATEMENT); \
|
||||
} \
|
||||
catch (...) \
|
||||
{ \
|
||||
CHECK (lumiera_error_expect (LUMIERA_ERROR_##ERROR_ID)); \
|
||||
#define VERIFY_ERROR(ERROR_ID, ERRONEOUS_STATEMENT) \
|
||||
try \
|
||||
{ \
|
||||
ERRONEOUS_STATEMENT ; \
|
||||
NOTREACHED("expected '%s' failure in: %s", \
|
||||
#ERROR_ID, #ERRONEOUS_STATEMENT); \
|
||||
} \
|
||||
catch (...) \
|
||||
{ \
|
||||
CHECK (lumiera_error_expect (LUMIERA_ERROR_##ERROR_ID));\
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@
|
|||
* *****************************************************/
|
||||
|
||||
|
||||
/** @file command.cpp
|
||||
** Implementation of the command frontend.
|
||||
/** @file command.cpp
|
||||
** Implementation of the command frontend.
|
||||
** Within this file, the implementation level of the command frontend
|
||||
** is linked to the implementation of the command registry. Client code
|
||||
** is shielded from those implementation classes and need only include
|
||||
|
|
|
|||
|
|
@ -23,9 +23,11 @@
|
|||
|
||||
#include "proc/mobject/mobject.hpp"
|
||||
#include "proc/mobject/session/mobjectfactory.hpp"
|
||||
#include "lib/util.hpp"
|
||||
|
||||
namespace mobject
|
||||
{
|
||||
using util::isnil;
|
||||
|
||||
namespace mobject {
|
||||
|
||||
using ::NOBUG_FLAG(memory);
|
||||
NOBUG_CPP_DEFINE_FLAG_PARENT(mobjectmem, memory);
|
||||
|
|
@ -35,6 +37,25 @@ namespace mobject
|
|||
*/
|
||||
session::MObjectFactory MObject::create;
|
||||
|
||||
|
||||
MObject::MObject()
|
||||
: length_()
|
||||
, shortID_()
|
||||
{ }
|
||||
|
||||
|
||||
MObject::~MObject() { };
|
||||
|
||||
|
||||
|
||||
string const&
|
||||
MObject::shortID() const
|
||||
{
|
||||
if (isnil (shortID_))
|
||||
shortID_ = initShortID();
|
||||
return shortID_;
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // namespace mobject
|
||||
|
|
|
|||
|
|
@ -34,10 +34,9 @@
|
|||
|
||||
#include <boost/noncopyable.hpp>
|
||||
#include <boost/operators.hpp>
|
||||
#include <list>
|
||||
#include <string>
|
||||
|
||||
|
||||
using std::list;
|
||||
|
||||
#include "proc/assetmanager.hpp"
|
||||
using proc_interface::IDA; //TODO finally not needed?
|
||||
|
|
@ -46,6 +45,7 @@ using proc_interface::AssetManager;
|
|||
|
||||
namespace mobject {
|
||||
|
||||
using std::string;
|
||||
using lumiera::P;
|
||||
|
||||
//NOBUG_DECLARE_FLAG (mobjectmem);
|
||||
|
|
@ -71,10 +71,12 @@ namespace mobject {
|
|||
typedef lumiera::Time Time;
|
||||
|
||||
// TODO: how to represent time intervals best?
|
||||
Time length;
|
||||
Time length_;
|
||||
|
||||
MObject() {}
|
||||
virtual ~MObject() {};
|
||||
mutable string shortID_;
|
||||
|
||||
MObject() ;
|
||||
virtual ~MObject() ;
|
||||
|
||||
friend class session::MObjectFactory;
|
||||
|
||||
|
|
@ -82,12 +84,22 @@ namespace mobject {
|
|||
public:
|
||||
static session::MObjectFactory create;
|
||||
|
||||
/** a short readable ID as a single name-token,
|
||||
* denoting both the kind of MObject and some sort of
|
||||
* instance identity. Not necessarily unique but should
|
||||
* be reasonable unique in most cases */
|
||||
string const& shortID() const;
|
||||
|
||||
/** MObject self-test (usable for asserting) */
|
||||
virtual bool isValid() const =0;
|
||||
|
||||
virtual Time& getLength() =0; ///< @todo how to deal with the time/length field?? ////TICKET #448
|
||||
|
||||
virtual bool operator== (const MObject& oo) const =0; ///< needed for handling by lumiera::P
|
||||
|
||||
virtual bool operator== (const MObject& oo) const =0;
|
||||
protected:
|
||||
|
||||
virtual string initShortID() const =0;
|
||||
|
||||
};
|
||||
|
||||
|
|
@ -98,10 +110,3 @@ namespace mobject {
|
|||
|
||||
} // namespace mobject
|
||||
#endif
|
||||
/*
|
||||
// Local Variables:
|
||||
// mode: C++
|
||||
// c-file-style: "gnu"
|
||||
// indent-tabs-mode: nil
|
||||
// End:
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -22,22 +22,40 @@
|
|||
|
||||
|
||||
#include "proc/mobject/session/abstractmo.hpp"
|
||||
#include "lib/util.hpp"
|
||||
|
||||
namespace mobject
|
||||
#include <boost/format.hpp>
|
||||
|
||||
using boost::format;
|
||||
using util::isnil;
|
||||
|
||||
namespace mobject {
|
||||
namespace session {
|
||||
|
||||
/** default/fallback implementation of equality
|
||||
* using literal object identity (same address).
|
||||
* Required to enable handling by lumiera::P
|
||||
*/
|
||||
bool
|
||||
AbstractMO::operator== (const MObject& oo) const
|
||||
{
|
||||
namespace session
|
||||
{
|
||||
|
||||
/** default/fallback implementation of equality
|
||||
* using literal object identity (same address)
|
||||
*/
|
||||
bool
|
||||
AbstractMO::operator== (const MObject& oo) const
|
||||
{
|
||||
return (this == &oo);
|
||||
}
|
||||
|
||||
|
||||
} // namespace mobject::session
|
||||
|
||||
} // namespace mobject
|
||||
return (this == &oo);
|
||||
}
|
||||
|
||||
|
||||
|
||||
string
|
||||
AbstractMO::buildShortID (lib::Literal typeID, string suffix) const
|
||||
{
|
||||
static uint i=0;
|
||||
static format namePattern ("%s.%03d");
|
||||
|
||||
REQUIRE (!isnil (typeID));
|
||||
if (!isnil (suffix))
|
||||
return typeID+"."+suffix;
|
||||
else
|
||||
return str(namePattern % typeID % (++i) );
|
||||
}
|
||||
|
||||
|
||||
}} // namespace mobject::session
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@
|
|||
#define MOBJECT_SESSION_ABSTRACTMO_H
|
||||
|
||||
#include "proc/mobject/mobject.hpp"
|
||||
#include "lib/symbol.hpp"
|
||||
|
||||
|
||||
|
||||
|
|
@ -36,18 +37,30 @@ namespace session {
|
|||
* abstract base class of all MObjects for providing common services.
|
||||
* @todo seems that we don't need this intermediate class...
|
||||
*/
|
||||
class AbstractMO : public MObject
|
||||
class AbstractMO
|
||||
: public MObject
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
/* some dummy implementations used to make the code compile... */
|
||||
|
||||
virtual Time& getLength() { return length; }
|
||||
|
||||
/* === some default implementations === */
|
||||
|
||||
DEFINE_PROCESSABLE_BY (builder::BuilderTool);
|
||||
|
||||
virtual bool operator== (const MObject& oo) const;
|
||||
string
|
||||
initShortID() const
|
||||
{
|
||||
return buildShortID("MObject");
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
Time&
|
||||
getLength()
|
||||
{
|
||||
return length_;
|
||||
}
|
||||
|
||||
bool operator== (const MObject& oo) const;
|
||||
|
||||
protected:
|
||||
void
|
||||
|
|
@ -58,6 +71,7 @@ namespace session {
|
|||
"or similarly broken internal assumptions.");
|
||||
}
|
||||
|
||||
string buildShortID (lib::Literal typeID, string suffix ="") const;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -42,6 +42,12 @@ namespace mobject
|
|||
template<class VAL>
|
||||
class Auto : public Meta, public ParamProvider<VAL>
|
||||
{
|
||||
string
|
||||
initShortID() const
|
||||
{
|
||||
return buildShortID("Auto");
|
||||
}
|
||||
|
||||
public:
|
||||
//////////////////////////////TICKET #566
|
||||
|
||||
|
|
|
|||
|
|
@ -50,6 +50,12 @@ namespace session {
|
|||
{
|
||||
PSequence boundSequence_;
|
||||
|
||||
string
|
||||
initShortID() const
|
||||
{
|
||||
return buildShortID("Binding");
|
||||
}
|
||||
|
||||
bool isValid() const;
|
||||
|
||||
public:
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ namespace session {
|
|||
Clip::isValid () const
|
||||
{
|
||||
TODO ("check consistency of clip length def, implies accessing the underlying media def");
|
||||
return length > Time(0);
|
||||
return length_ > Time(0);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -58,7 +58,7 @@ namespace session {
|
|||
Clip::setupLength()
|
||||
{
|
||||
TODO ("really calculate the length of a clip and set length field");
|
||||
this->length = mediaDef_.getLength();
|
||||
this->length_ = mediaDef_.getLength();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -52,8 +52,19 @@ namespace session {
|
|||
* because it depends on the actual media type, and we want to encapsulate
|
||||
* all these details as much as possible.
|
||||
*/
|
||||
class Clip : public AbstractMO
|
||||
class Clip
|
||||
: public AbstractMO
|
||||
{
|
||||
string
|
||||
initShortID() const
|
||||
{
|
||||
return buildShortID("Clip");
|
||||
}
|
||||
|
||||
void setupLength();
|
||||
|
||||
|
||||
|
||||
protected:
|
||||
/** start position in source */
|
||||
Time start_;
|
||||
|
|
@ -72,10 +83,8 @@ namespace session {
|
|||
friend class MObjectFactory;
|
||||
|
||||
|
||||
virtual void setupLength();
|
||||
|
||||
public:
|
||||
virtual bool isValid() const;
|
||||
bool isValid() const;
|
||||
|
||||
/** access the underlying media asset */
|
||||
PMedia getMedia () const;
|
||||
|
|
|
|||
|
|
@ -42,6 +42,12 @@ namespace session {
|
|||
|
||||
class Effect : public AbstractMO
|
||||
{
|
||||
string
|
||||
initShortID() const
|
||||
{
|
||||
return buildShortID("Effect");
|
||||
}
|
||||
|
||||
protected:
|
||||
/** Identifier of the Plug-in to be used */
|
||||
string pluginID;
|
||||
|
|
|
|||
|
|
@ -46,10 +46,16 @@ namespace session {
|
|||
class Label : public Meta
|
||||
{
|
||||
///////////TODO: timespan fields here or already in class Meta??
|
||||
|
||||
|
||||
Symbol typeID_;
|
||||
|
||||
virtual bool isValid() const;
|
||||
|
||||
string
|
||||
initShortID() const
|
||||
{
|
||||
return buildShortID("Label");
|
||||
}
|
||||
|
||||
bool isValid() const;
|
||||
|
||||
public:
|
||||
Label (Symbol type)
|
||||
|
|
|
|||
|
|
@ -43,6 +43,12 @@ namespace session {
|
|||
{
|
||||
///////////
|
||||
//////////////////////////////TICKET #448 what to do with the length here??
|
||||
|
||||
string
|
||||
initShortID() const
|
||||
{
|
||||
return buildShortID("MetaMO");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -112,6 +112,7 @@ namespace session {
|
|||
QueryFocusStack::clear ()
|
||||
{
|
||||
paths_.clear();
|
||||
openDefaultFrame();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,12 @@
|
|||
|
||||
|
||||
#include "proc/mobject/session/query-focus.hpp"
|
||||
#include "proc/mobject/mobject.hpp"
|
||||
|
||||
#include <boost/format.hpp>
|
||||
|
||||
using boost::format;
|
||||
using boost::str;
|
||||
|
||||
|
||||
namespace mobject {
|
||||
|
|
@ -122,6 +128,21 @@ namespace session {
|
|||
}
|
||||
|
||||
|
||||
/** push the "current QueryFocus" aside and open a new focus frame,
|
||||
* which starts out at the same location as the original */
|
||||
QueryFocus
|
||||
QueryFocus::push ()
|
||||
{
|
||||
Scope currentLocation (ScopeLocator::instance().currPath().getLeaf());
|
||||
ENSURE (currentLocation.isValid());
|
||||
|
||||
QueryFocus newFocus (ScopeLocator::instance().pushPath());
|
||||
newFocus.attach (currentLocation);
|
||||
return newFocus;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** cease to use \em this specific reference to the current frame.
|
||||
* This operation immediately tries to re-attach to what is "current"
|
||||
* and readjusts the internal handle. But when the previously released
|
||||
|
|
@ -139,4 +160,14 @@ namespace session {
|
|||
|
||||
|
||||
|
||||
/** diagnostic self-display based on the ScopePath */
|
||||
QueryFocus::operator string() const
|
||||
{
|
||||
static format display("Focus(%d)--->%s");
|
||||
return str ( display % ScopeLocator::instance().stackSize()
|
||||
% string (*focus_));
|
||||
}
|
||||
|
||||
|
||||
|
||||
}} // namespace mobject::session
|
||||
|
|
|
|||
|
|
@ -25,9 +25,11 @@
|
|||
#define MOBJECT_SESSION_QUERY_FOCUS_H
|
||||
|
||||
#include "proc/mobject/session/scope-path.hpp"
|
||||
#include "proc/mobject/session/scope-query.hpp"
|
||||
#include "proc/mobject/session/scope-locator.hpp"
|
||||
|
||||
#include <boost/intrusive_ptr.hpp>
|
||||
#include <string>
|
||||
|
||||
namespace mobject {
|
||||
namespace session {
|
||||
|
|
@ -53,10 +55,15 @@ namespace session {
|
|||
* Alternatively, through the static factory function #push(), a new
|
||||
* focus location may be opened, thereby pushing the currently used
|
||||
* focus location aside. This new focus location will remain the
|
||||
* current focus, while any handles referring to it is still in use.
|
||||
* current focus, until all handles referring to it go out of scope.
|
||||
*
|
||||
* Using an existing QueryFocus (handle), the current focus may be
|
||||
* shifted to another scope within the current session.
|
||||
* shifted to another scope within the current session. This
|
||||
* »navigating« operation will use the current focus position as
|
||||
* point of departure, thus retaining a similar access path to any
|
||||
* nested sequences. (These might be attached multiple times within
|
||||
* the same session, each attachment constituting a different
|
||||
* context scope. Navigating tries to retain the current context)
|
||||
*
|
||||
* The templated query functions allow to issue specifically typed
|
||||
* queries to retrieve all children (immediately contained in a
|
||||
|
|
@ -73,7 +80,8 @@ namespace session {
|
|||
* are delivered without any defined order (implementation is
|
||||
* hashtable based)
|
||||
*
|
||||
* @see query-focus-test.cpp
|
||||
* @see QueryFocus_test
|
||||
* @see scope-path.hpp (concept: path of scopes)
|
||||
*/
|
||||
class QueryFocus
|
||||
{
|
||||
|
|
@ -82,11 +90,13 @@ namespace session {
|
|||
public:
|
||||
QueryFocus();
|
||||
|
||||
ScopePath currentPath() const;
|
||||
operator Scope() const;
|
||||
ScopePath const& currentPath() const;
|
||||
operator Scope() const;
|
||||
operator string() const;
|
||||
|
||||
QueryFocus& attach (Scope const&);
|
||||
static QueryFocus push (Scope const&);
|
||||
static QueryFocus push ();
|
||||
QueryFocus& reset ();
|
||||
QueryFocus& pop ();
|
||||
|
||||
|
|
@ -99,6 +109,9 @@ namespace session {
|
|||
typename ScopeQuery<MO>::iterator
|
||||
explore() const;
|
||||
|
||||
lib::IterSource<const Scope>::iterator
|
||||
locate (Scope const& toTarget);
|
||||
|
||||
|
||||
private:
|
||||
QueryFocus (ScopePath&);
|
||||
|
|
@ -116,14 +129,19 @@ namespace session {
|
|||
*/
|
||||
inline QueryFocus::operator Scope() const
|
||||
{
|
||||
return currPath().getLeaf();
|
||||
return focus_->getLeaf();
|
||||
}
|
||||
|
||||
/**@note returning a copy */
|
||||
inline ScopePath
|
||||
/** @return ref to internal datastructure
|
||||
* @warning don't store it directly. Rather
|
||||
* copy it or store a QueryFocus instance.
|
||||
* @note use #attach if the intention is
|
||||
* to \em manipulate the current
|
||||
* focus location */
|
||||
inline ScopePath const&
|
||||
QueryFocus::currentPath() const
|
||||
{
|
||||
return currPath();
|
||||
return *focus_;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -134,7 +152,7 @@ namespace session {
|
|||
inline typename ScopeQuery<MO>::iterator
|
||||
QueryFocus::query() const
|
||||
{
|
||||
ScopeLocator::instance().query<MO> (*this);
|
||||
return ScopeLocator::instance().query<MO> (*this);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -145,7 +163,22 @@ namespace session {
|
|||
inline typename ScopeQuery<MO>::iterator
|
||||
QueryFocus::explore() const
|
||||
{
|
||||
ScopeLocator::instance().explore<MO> (*this);
|
||||
return ScopeLocator::instance().explore<MO> (*this);
|
||||
}
|
||||
|
||||
|
||||
/** shift or navigate the current focus to point at
|
||||
* the given target scope. In case of multiple possible
|
||||
* access paths, the \em current location is taken into
|
||||
* account, trying to reach the new location in a
|
||||
* \em similar fashion as much as possible.
|
||||
* @note moves the current focus as side-effect
|
||||
* @return the effective / virtual new access path
|
||||
* leading to the new target focus scope */
|
||||
inline lib::IterSource<const Scope>::iterator
|
||||
QueryFocus::locate (Scope const& toTarget)
|
||||
{
|
||||
return ScopeLocator::instance().locate (toTarget);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ namespace session {
|
|||
PReso
|
||||
QueryResolver::issue (Goal const& query) const
|
||||
{
|
||||
TODO ("ensure proper initialisation");
|
||||
REQUIRE (!dispatcher_->empty(), "attempt to issue a query without having installed any resolver (yet)");
|
||||
|
||||
if (!canHandle (query))
|
||||
throw lumiera::error::Invalid ("unable to resolve this kind of query"); ////TICKET #197
|
||||
|
|
|
|||
|
|
@ -54,6 +54,12 @@ namespace session {
|
|||
///////////TODO: timespan fields here or already in class Meta??
|
||||
///////////TODO: any idea about the purpose of root's "timespan"?? ///////TICKET #448
|
||||
|
||||
string
|
||||
initShortID() const
|
||||
{
|
||||
return buildShortID("Root","(✼)");
|
||||
}
|
||||
|
||||
virtual bool isValid() const;
|
||||
|
||||
public:
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@
|
|||
#include "proc/mobject/session/scope.hpp"
|
||||
#include "proc/mobject/session/scope-query.hpp"
|
||||
#include "proc/mobject/placement.hpp"
|
||||
#include "lib/iter-source.hpp" ////////////////////TICKET #493 : the bare interface would be sufficient here
|
||||
#include "lib/singleton.hpp"
|
||||
|
||||
#include <boost/scoped_ptr.hpp>
|
||||
|
|
@ -68,18 +69,27 @@ namespace session {
|
|||
|
||||
template<typename MO>
|
||||
typename ScopeQuery<MO>::iterator
|
||||
explore (Scope);
|
||||
explore (Scope const&);
|
||||
|
||||
template<typename MO>
|
||||
typename ScopeQuery<MO>::iterator
|
||||
query (Scope);
|
||||
query (Scope const&);
|
||||
|
||||
template<typename MO>
|
||||
typename ScopeQuery<MO>::iterator
|
||||
locate (Scope scope);
|
||||
getRawPath (Scope const&);
|
||||
|
||||
ScopeQuery<MObject>::iterator
|
||||
getRawPath (Scope const&);
|
||||
|
||||
lib::IterSource<const Scope>::iterator
|
||||
locate (Scope const& target);
|
||||
|
||||
size_t stackSize() const;
|
||||
|
||||
|
||||
~ScopeLocator();
|
||||
|
||||
|
||||
protected:
|
||||
ScopeLocator();
|
||||
|
||||
|
|
@ -97,7 +107,7 @@ namespace session {
|
|||
*/
|
||||
template<typename MO>
|
||||
inline typename ScopeQuery<MO>::iterator
|
||||
ScopeLocator::explore (Scope scope)
|
||||
ScopeLocator::explore (Scope const& scope)
|
||||
{
|
||||
return ScopeQuery<MO> (scope.getTop(), CHILDREN).resolveBy (theResolver());
|
||||
}
|
||||
|
|
@ -108,21 +118,33 @@ namespace session {
|
|||
*/
|
||||
template<typename MO>
|
||||
inline typename ScopeQuery<MO>::iterator
|
||||
ScopeLocator::query (Scope scope)
|
||||
ScopeLocator::query (Scope const& scope)
|
||||
{
|
||||
return ScopeQuery<MO> (scope.getTop(), CONTENTS).resolveBy (theResolver());
|
||||
}
|
||||
|
||||
|
||||
/** use the contents-resolving facility exposed by the session
|
||||
* to discover the path up from the given scope to model root
|
||||
* to discover the path up from the given scope to model root.
|
||||
* @note this yields the \em raw path (basic containment hierarchy),
|
||||
* as opposed to an effective or virtual path, which should reflect
|
||||
* the attachment of Sequences to Timelines or meta-clips. That is,
|
||||
* you'll always get the top-level track of any sequence as direct
|
||||
* child of the root node and timelines (BindingMO) just appear
|
||||
* to be "dead ends"
|
||||
*/
|
||||
template<typename MO>
|
||||
inline typename ScopeQuery<MO>::iterator
|
||||
ScopeLocator::locate (Scope scope)
|
||||
ScopeLocator::getRawPath (Scope const& scope)
|
||||
{
|
||||
return ScopeQuery<MO> (scope.getTop(), PATH).resolveBy (theResolver());
|
||||
}
|
||||
|
||||
inline ScopeQuery<MObject>::iterator
|
||||
ScopeLocator::getRawPath (Scope const& scope)
|
||||
{
|
||||
return ScopeQuery<MObject> (scope.getTop(), PATH).resolveBy (theResolver());
|
||||
}
|
||||
|
||||
|
||||
}} // namespace mobject::session
|
||||
|
|
|
|||
|
|
@ -26,24 +26,20 @@
|
|||
#include "proc/mobject/session/scope-locator.hpp"
|
||||
#include "proc/mobject/session/session-service-explore-scope.hpp"
|
||||
#include "proc/mobject/mobject.hpp"
|
||||
#include "lib/util-foreach.hpp"
|
||||
#include "lib/itertools.hpp"
|
||||
#include "lib/symbol.hpp"
|
||||
#include "lib/error.hpp"
|
||||
#include "lib/util.hpp"
|
||||
|
||||
#include <tr1/functional>
|
||||
#include <algorithm>
|
||||
|
||||
namespace mobject {
|
||||
namespace session {
|
||||
|
||||
using std::reverse;
|
||||
|
||||
using std::tr1::bind;
|
||||
using std::tr1::function;
|
||||
using std::tr1::placeholders::_1;
|
||||
using lib::append_all;
|
||||
using util::and_all;
|
||||
using util::isSameObject;
|
||||
using util::isnil;
|
||||
|
||||
using namespace lumiera;
|
||||
|
||||
|
|
@ -54,12 +50,12 @@ namespace session {
|
|||
|
||||
namespace { // Helpers and shortcuts....
|
||||
|
||||
/** issue a query to discover the path to root,
|
||||
/** issue a query to discover the (raw) path to root,
|
||||
* starting with the given scope */
|
||||
inline ScopeQuery<MObject>::iterator
|
||||
discoverScopePath (Scope const& leaf)
|
||||
{
|
||||
return ScopeLocator::instance().locate<MObject> (leaf);
|
||||
return ScopeLocator::instance().getRawPath (leaf);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -68,8 +64,8 @@ namespace session {
|
|||
{
|
||||
REQUIRE (path);
|
||||
if (path->empty())
|
||||
throw error::Invalid (operation_descr+" an empty placement scope path"
|
||||
, LUMIERA_ERROR_EMPTY_SCOPE_PATH);
|
||||
throw error::Logic (operation_descr+" an empty placement scope path"
|
||||
, LUMIERA_ERROR_EMPTY_SCOPE_PATH);
|
||||
}
|
||||
}//(End) helpers
|
||||
|
||||
|
|
@ -105,10 +101,34 @@ namespace session {
|
|||
: refcount_(0)
|
||||
, path_()
|
||||
{
|
||||
if (!leaf.isValid()) return; // invalid leaf defines invalid path....
|
||||
if (leaf == Scope::INVALID) return; // invalid leaf defines invalid path....
|
||||
|
||||
append_all (discoverScopePath(leaf), path_);
|
||||
reverse (path_.begin(), path_.end());
|
||||
clear();
|
||||
navigate (leaf);
|
||||
}
|
||||
|
||||
|
||||
ScopePath::ScopePath (ScopePath const& o)
|
||||
: refcount_(0)
|
||||
, path_(o.path_)
|
||||
{ }
|
||||
|
||||
/**
|
||||
* Copy from existing path
|
||||
* @throw error::Logic when current path has nonzero refcount
|
||||
*/
|
||||
ScopePath&
|
||||
ScopePath::operator= (ScopePath const& ref)
|
||||
{
|
||||
if (0 < refcount_)
|
||||
throw error::Logic ("Attempt to overwrite a ScopePath with nonzero refcount");
|
||||
|
||||
if (!isSameObject (*this, ref))
|
||||
{
|
||||
path_ = ref.path_;
|
||||
ENSURE (0 == refcount_);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -122,14 +142,34 @@ namespace session {
|
|||
const ScopePath ScopePath::INVALID = ScopePath(Scope());
|
||||
|
||||
|
||||
/** ScopePath diagnostic self display.
|
||||
* Implemented similar to a filesystem path, where the
|
||||
* path elements are based on the self-display of the MObject
|
||||
* attached through the respective scope top placement. */
|
||||
ScopePath::operator string() const
|
||||
{
|
||||
if (isnil (path_)) return "!";
|
||||
if (1 == length()) return "/";
|
||||
|
||||
string res;
|
||||
vector<Scope>::const_iterator node (path_.begin());
|
||||
while (++node != path_.end())
|
||||
{
|
||||
res += "/";
|
||||
res += string (*node);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
/** a \em valid path consists of more than just the root element.
|
||||
* @note contrary to this, an \em empty path doesn't even contain a root element
|
||||
*/
|
||||
bool
|
||||
ScopePath::isValid() const
|
||||
{
|
||||
return (0 < length())
|
||||
#ifndef NDEBUG
|
||||
return (1 < length())
|
||||
#if NOBUG_MODE_ALPHA
|
||||
&& hasValidRoot()
|
||||
#endif
|
||||
;
|
||||
|
|
@ -177,6 +217,8 @@ namespace session {
|
|||
bool
|
||||
ScopePath::contains (Scope const& aScope) const
|
||||
{
|
||||
if (aScope == Scope::INVALID) return true; // bottom is contained everywhere
|
||||
|
||||
for (iterator ii = this->begin(); ii; ++ii)
|
||||
if (aScope == *ii)
|
||||
return true;
|
||||
|
|
@ -188,8 +230,8 @@ namespace session {
|
|||
bool
|
||||
ScopePath::contains (ScopePath const& otherPath) const
|
||||
{
|
||||
if ( empty()) return false;
|
||||
if (!otherPath.isValid()) return true;
|
||||
if ( empty()) return false;
|
||||
if (!isValid()) return false;
|
||||
|
||||
ASSERT (1 < length());
|
||||
|
|
@ -243,20 +285,19 @@ namespace session {
|
|||
}
|
||||
|
||||
|
||||
Scope&
|
||||
Scope const&
|
||||
ScopePath::moveUp()
|
||||
{
|
||||
___check_notBottom (this, "Navigating");
|
||||
static Scope invalidScope;
|
||||
|
||||
path_.resize (length()-1);
|
||||
|
||||
if (empty()) return invalidScope;
|
||||
if (empty()) return Scope::INVALID;
|
||||
else return path_.back();
|
||||
}
|
||||
|
||||
|
||||
Scope&
|
||||
Scope const&
|
||||
ScopePath::goRoot()
|
||||
{
|
||||
___check_notBottom (this, "Navigating");
|
||||
|
|
@ -271,8 +312,22 @@ namespace session {
|
|||
ScopePath::navigate (Scope const& target)
|
||||
{
|
||||
___check_notBottom (this, "Navigating");
|
||||
*this = ScopePath(target); //////////////////////////////TICKET #424
|
||||
}
|
||||
if (!target.isValid())
|
||||
throw error::Invalid ("can't navigate to a target scope outside the model"
|
||||
, LUMIERA_ERROR_INVALID_SCOPE);
|
||||
|
||||
std::vector<Scope> otherPath;
|
||||
append_all (discoverScopePath(target), otherPath);
|
||||
reverse (otherPath.begin(), otherPath.end());
|
||||
////////////////////////////TICKET #663 extension point for meta-clip support
|
||||
ASSERT (path_[0] == otherPath[0]); // sharing the root element
|
||||
this->path_ = otherPath; // TODO really relate the two paths, including a treatment for meta-clips
|
||||
// - if both are in the same sequence (same head element): just attach the tail of the other
|
||||
// - if the other path points into a sequence which is attached as meta-clip to the current sequence,
|
||||
// then attach the other path below that meta-clip (problem: resolve multiple attachments)
|
||||
// - otherwise use the first timeline, to which the other path's sequence is attached
|
||||
// - otherwise, if all else fails, use the raw otherPath
|
||||
} ////////////////////////////////////TICKET #672
|
||||
|
||||
|
||||
void
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@
|
|||
** An Object representing a sequence of nested scopes within the Session.
|
||||
** MObjects are being attached to the model by Placements, and each Placement
|
||||
** is added as belonging \em into another Placement, which defines the Scope
|
||||
** of the addition. There is one (abstract) root element, containing the timelines;
|
||||
** of the addition. There is one (formal) root element, containing the timelines;
|
||||
** from there a nested sequence of scopes leads down to each Placement.
|
||||
** Ascending this path yields all the scopes to search or query in proper order
|
||||
** to be used when resolving some attribute of placement. Placements use visibility
|
||||
|
|
@ -36,15 +36,15 @@
|
|||
** A scope path is represented as sequence of scopes, where each Scope is implemented
|
||||
** by a PlacementRef pointing to the »scope top«, i.e. the placement in the session
|
||||
** constituting this scope. The leaf of this path can be considered the current scope.
|
||||
** ScopePath is intended to remember a \em current location within the model, to be
|
||||
** used for resolving queries and discovering contents.
|
||||
** ScopePath is intended to be used for remembering a \em current location within the
|
||||
** model, usable for resolving queries and discovering contents.
|
||||
**
|
||||
** \par operations and behaviour
|
||||
**
|
||||
** In addition to some search and query functions, a scope path has the ability to
|
||||
** \em navigate to a given target scope, which must be reachable by ascending and
|
||||
** descending into the branches of the overall tree or DAG (in the general case).
|
||||
** Navigating changes the current path, which usually happens when the current
|
||||
** Navigating is a mutating operation which usually happens when the current
|
||||
** "focus" shifts while operating on the model.
|
||||
**
|
||||
** - ScopePath can be default constructed, yielding an \em invalid path.
|
||||
|
|
@ -55,6 +55,8 @@
|
|||
** - ScopePaths are intended to be handled <b>by value</b> (as are Scopes and
|
||||
** PlacementRefs). They are equality comparable and provide several specialised
|
||||
** relation predicates.
|
||||
** - while generally copying is permitted, you may not overwrite an ScopePath
|
||||
** which is attached (referred by a QueryFocus, see below)
|
||||
** - all implementations are focused on clarity, not uttermost performance, as
|
||||
** the assumption is for paths to be relatively short and path operations to
|
||||
** be executed rather in a GUI action triggered context.
|
||||
|
|
@ -71,7 +73,9 @@
|
|||
** Each of these stack frames represents the current location for some evaluation
|
||||
** context; it is organised as stack to allow intermediate evaluations. Management
|
||||
** of these stack frames is automated, with the assistance of ScopePath by
|
||||
** incorporating a ref-count.
|
||||
** incorporating a ref-count. Client code usually accesses this mechanism
|
||||
** through QueryFocus objects as frontend, which is reflected in the
|
||||
** mentioned embedded refcount
|
||||
**
|
||||
** @see scope-path-test.cpp
|
||||
** @see Scope
|
||||
|
|
@ -89,8 +93,31 @@
|
|||
#include "lib/error.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
|
||||
namespace lib {
|
||||
namespace iter{
|
||||
|
||||
using mobject::session::Scope;
|
||||
|
||||
/**
|
||||
* this explicit specialisation allows to build a RangeIter
|
||||
* to yield const Scope elements, based on the const_reverse_iterator
|
||||
* used internally within ScopePath. This specialisation needs to be
|
||||
* injected prior to actually building the iterator type of ScopePath
|
||||
* @see iter-type-binding.hpp
|
||||
* @see iter-adapter.hpp
|
||||
*/
|
||||
template<>
|
||||
struct TypeBinding<vector<Scope>::const_reverse_iterator>
|
||||
{
|
||||
typedef const Scope value_type;
|
||||
typedef Scope const& reference;
|
||||
typedef const Scope* pointer;
|
||||
};
|
||||
}}
|
||||
|
||||
namespace mobject {
|
||||
namespace session {
|
||||
|
||||
|
|
@ -123,15 +150,21 @@ namespace session {
|
|||
ScopePath ();
|
||||
ScopePath (Scope const& leaf);
|
||||
|
||||
ScopePath (ScopePath const&);
|
||||
ScopePath&
|
||||
operator= (ScopePath const&);
|
||||
|
||||
static const ScopePath INVALID;
|
||||
|
||||
|
||||
/* == state diagnostics == */
|
||||
bool isValid() const;
|
||||
bool empty() const;
|
||||
bool isRoot() const;
|
||||
size_t size() const;
|
||||
size_t length() const;
|
||||
size_t ref_count()const;
|
||||
////////////////////////////////////////TICKET #429 : diagnostic output to be added later
|
||||
operator string() const;
|
||||
|
||||
/// Iteration is always ascending from leaf to root
|
||||
typedef _IterType iterator;
|
||||
|
|
@ -156,8 +189,8 @@ namespace session {
|
|||
|
||||
/* == mutations == */
|
||||
void clear();
|
||||
Scope& moveUp();
|
||||
Scope& goRoot();
|
||||
Scope const& moveUp();
|
||||
Scope const& goRoot();
|
||||
void navigate (Scope const&);
|
||||
|
||||
|
||||
|
|
@ -236,8 +269,22 @@ namespace session {
|
|||
return path_.empty();
|
||||
}
|
||||
|
||||
inline bool
|
||||
ScopePath::isRoot() const
|
||||
{
|
||||
return (1 == size())
|
||||
#if NOBUG_MODE_ALPHA
|
||||
&& path_[0].isRoot()
|
||||
#endif
|
||||
;
|
||||
}
|
||||
|
||||
inline ScopePath::iterator
|
||||
|
||||
|
||||
/** @note actually this is an Lumiera Forward Iterator,
|
||||
* yielding the path up to root as a sequence of
|
||||
* const Scope elements */
|
||||
inline ScopePath::iterator
|
||||
ScopePath::begin() const
|
||||
{
|
||||
return iterator (path_.rbegin(), path_.rend());
|
||||
|
|
|
|||
|
|
@ -184,12 +184,6 @@ namespace session {
|
|||
{
|
||||
return bind (&PlacementMO::isCompatible<MO>, _1 );
|
||||
}
|
||||
|
||||
void
|
||||
resetResultIteration (iterator const& newQueryResults)
|
||||
{
|
||||
static_cast<iterator&> (*this) = newQueryResults;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -215,5 +209,7 @@ namespace session {
|
|||
};
|
||||
|
||||
|
||||
|
||||
|
||||
}} // namespace mobject::session
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -21,21 +21,44 @@
|
|||
* *****************************************************/
|
||||
|
||||
|
||||
/** @file scope.cpp
|
||||
** Implementation of placement scopes and scope locator.
|
||||
** This translation unit embeds the (hidden) link to the session implementation
|
||||
** used to establish the position of a given placement within the hierarchy
|
||||
** of nested scopes. The rest of the model implementation code mostly builds
|
||||
** on top of this access point, when it comes to discovering contents or
|
||||
** navigating within the model. Especially the ScopeLocator singleton
|
||||
** defined here plays the role of linking together the system of nested scopes,
|
||||
** the current QueryFocus and the actual session implementation and storage
|
||||
** (PlacementIndex)
|
||||
**
|
||||
** @see command.hpp
|
||||
** @see command-registry.hpp
|
||||
**
|
||||
*/
|
||||
|
||||
|
||||
#include "proc/mobject/session/scope.hpp"
|
||||
#include "proc/mobject/session/scope-locator.hpp"
|
||||
#include "proc/mobject/session/query-focus-stack.hpp"
|
||||
#include "proc/mobject/session/session-service-explore-scope.hpp"
|
||||
#include "proc/mobject/mobject.hpp"
|
||||
//#include "proc/mobject/session/track.hpp"
|
||||
//#include "proc/mobject/placement.hpp"
|
||||
//#include "proc/mobject/session/mobjectfactory.hpp"
|
||||
#include "lib/iter-source.hpp" ////////////////////TICKET #493 : using the IterSource adapters here
|
||||
|
||||
#include <vector>
|
||||
|
||||
using std::string;
|
||||
using std::vector;
|
||||
using lib::IterSource;
|
||||
using lib::iter_source::wrapIter;
|
||||
|
||||
namespace mobject {
|
||||
namespace session {
|
||||
|
||||
|
||||
LUMIERA_ERROR_DEFINE (INVALID_SCOPE, "Placement scope invalid an not locatable within model");
|
||||
|
||||
LUMIERA_ERROR_DEFINE (INVALID_SCOPE, "Placement scope invalid and not locatable within model");
|
||||
LUMIERA_ERROR_DEFINE (NO_PARENT_SCOPE, "Parent scope of root not accessible");
|
||||
|
||||
|
||||
|
||||
/** conversion of a scope top (placement) into a Scope.
|
||||
|
|
@ -43,9 +66,7 @@ namespace session {
|
|||
* to the session, which will be checked by index access */
|
||||
Scope::Scope (PlacementMO const& constitutingPlacement)
|
||||
: anchor_(constitutingPlacement)
|
||||
{
|
||||
|
||||
}
|
||||
{ }
|
||||
|
||||
|
||||
Scope::Scope ()
|
||||
|
|
@ -57,23 +78,28 @@ namespace session {
|
|||
|
||||
Scope::Scope (Scope const& o)
|
||||
: anchor_(o.anchor_)
|
||||
{ }
|
||||
{
|
||||
ENSURE (anchor_.isValid());
|
||||
}
|
||||
|
||||
|
||||
Scope&
|
||||
Scope::operator= (Scope const& o)
|
||||
{
|
||||
anchor_ = o.anchor_; ////////////////////////////TODO verify correctness
|
||||
anchor_ = o.anchor_; // note: actually we're just assigning an hash value
|
||||
ENSURE (o.isValid());
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
/** constant \em invalid scope token. */
|
||||
const Scope Scope::INVALID = Scope();
|
||||
|
||||
|
||||
|
||||
ScopeLocator::ScopeLocator()
|
||||
: focusStack_(new QueryFocusStack)
|
||||
{
|
||||
TODO ("anything in initialise here?");
|
||||
}
|
||||
{ }
|
||||
|
||||
ScopeLocator::~ScopeLocator() { }
|
||||
|
||||
|
|
@ -95,6 +121,13 @@ namespace session {
|
|||
}
|
||||
|
||||
|
||||
size_t
|
||||
ScopeLocator::stackSize() const
|
||||
{
|
||||
return focusStack_->size();
|
||||
}
|
||||
|
||||
|
||||
/** establishes the \em current query focus location.
|
||||
* Relies on the state of the QueryFocusStack.
|
||||
* If there is no current focus location, a new
|
||||
|
|
@ -123,16 +156,37 @@ namespace session {
|
|||
}
|
||||
|
||||
|
||||
|
||||
/** discover the enclosing scope of a given Placement */
|
||||
Scope const&
|
||||
Scope::containing (PlacementMO const& aPlacement)
|
||||
/** navigate the \em current QueryFocus scope location. The resulting
|
||||
* access path to the new location is chosen such as to be most closely related
|
||||
* to the original location; this includes picking a timeline or meta-clip
|
||||
* attachment most similar to the one used in the original path. So effectively
|
||||
* you'll see things through the same "scoping perspective" as given by the
|
||||
* original path, if possible to the new location
|
||||
* given as parameter. use the contents-resolving facility exposed by the session
|
||||
* @note changes the \em current QueryFocus as a sideeffect
|
||||
* @param scope the new target location to navigate
|
||||
* @return an iterator yielding the nested scopes from the new location
|
||||
* up to root, in a way likely to be similar to the original location
|
||||
*/
|
||||
IterSource<const Scope>::iterator
|
||||
ScopeLocator::locate (Scope const& scope)
|
||||
{
|
||||
UNIMPLEMENTED ("scope discovery");
|
||||
ScopePath& currentPath = focusStack_->top();
|
||||
currentPath.navigate (scope);
|
||||
return wrapIter (currentPath.begin());
|
||||
}
|
||||
|
||||
|
||||
Scope const&
|
||||
|
||||
/** discover the enclosing scope of a given Placement */
|
||||
Scope
|
||||
Scope::containing (PlacementMO const& aPlacement)
|
||||
{
|
||||
return SessionServiceExploreScope::getScope (aPlacement);
|
||||
}
|
||||
|
||||
|
||||
Scope
|
||||
Scope::containing (RefPlacement const& refPlacement)
|
||||
{
|
||||
return containing (*refPlacement);
|
||||
|
|
@ -150,10 +204,14 @@ namespace session {
|
|||
/** retrieve the parent scope which encloses this scope.
|
||||
* @throw error::Invalid if this is the root scope
|
||||
*/
|
||||
Scope const&
|
||||
Scope
|
||||
Scope::getParent() const
|
||||
{
|
||||
UNIMPLEMENTED ("retrieve the enclosing parent scope");
|
||||
if (isRoot())
|
||||
throw lumiera::error::Invalid ("can't get parent of root scope"
|
||||
, LUMIERA_ERROR_NO_PARENT_SCOPE);
|
||||
|
||||
return SessionServiceExploreScope::getScope (*anchor_);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -161,7 +219,7 @@ namespace session {
|
|||
bool
|
||||
Scope::isRoot() const
|
||||
{
|
||||
UNIMPLEMENTED ("detection of root scope");
|
||||
return *anchor_ == SessionServiceExploreScope::getScopeRoot();
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -175,14 +233,16 @@ namespace session {
|
|||
}
|
||||
|
||||
|
||||
/** enumerate the path of nested scopes up to root scope.
|
||||
* @return an iterator which starts with this scope and
|
||||
* successively yields outer scopes, stopping at root.
|
||||
*/
|
||||
Scope::IterType_
|
||||
Scope::ascend() const
|
||||
/** Scope diagnostic self display.
|
||||
* Implemented based on the self-display of the MObject
|
||||
* attached through the scope top placement. Usually this
|
||||
* should yield a reasonably unique, descriptive string. */
|
||||
Scope::operator string() const
|
||||
{
|
||||
UNIMPLEMENTED ("ascend scope hierarchy up to root");
|
||||
string res("[");
|
||||
res += anchor_->shortID();
|
||||
res += "]";
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -24,45 +24,50 @@
|
|||
#ifndef MOBJECT_SESSION_SCOPE_H
|
||||
#define MOBJECT_SESSION_SCOPE_H
|
||||
|
||||
//#include "proc/mobject/mobject.hpp"
|
||||
#include "proc/mobject/placement.hpp"
|
||||
#include "proc/mobject/placement-ref.hpp"
|
||||
//#include "proc/mobject/session/query-resolver.hpp" ///////////TODO: really?
|
||||
#include "lib/iter-adapter.hpp"
|
||||
#include "lib/error.hpp"
|
||||
//#include "lib/singleton.hpp"
|
||||
|
||||
//#include <boost/operators.hpp>
|
||||
//#include <boost/scoped_ptr.hpp>
|
||||
//#include <tr1/memory>
|
||||
//#include <vector>
|
||||
//#include <string>
|
||||
#include <string>
|
||||
|
||||
//using std::vector;
|
||||
//using std::string;
|
||||
|
||||
namespace mobject {
|
||||
namespace session {
|
||||
|
||||
using lib::IterAdapter;
|
||||
|
||||
LUMIERA_ERROR_DECLARE (INVALID_SCOPE); ///< Placement scope invalid an not locatable within model
|
||||
LUMIERA_ERROR_DECLARE (NO_PARENT_SCOPE); ///< Parent scope of root not accessible
|
||||
LUMIERA_ERROR_DECLARE (INVALID_SCOPE); ///< Placement scope invalid and not locatable within model
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* TODO type comment
|
||||
* A Placement scope within the high-level-model.
|
||||
* Within the Session/Model, Placements are used to attach
|
||||
* MObjects; but beyond that, each Placement can \em contain
|
||||
* other Placements, effectively forming a scope. Thus Scope
|
||||
* is basically another view on Placements <i>which are attached
|
||||
* to the session.</i> This (hidden) link to the session is utilised
|
||||
* to establish the nesting of scopes and allow querying and navigating.
|
||||
*
|
||||
* Actually, Scope is implemented through a PlacementRef pointing to
|
||||
* the Placement which \em constitutes this Scope. We call this Placement
|
||||
* the "scope top". A track e.g. can \em contain several clips, but also
|
||||
* nested sub tracks, all of which would be within the scope of this track.
|
||||
* This scoping relation plays an important role when it comes to \em resolving
|
||||
* properties of placement, like e.g. the output designation, overlay mode,
|
||||
* sound pan position etc -- properties from enclosing scopes will be
|
||||
* inherited unless \em shaded by local definitions, similar to the
|
||||
* behaviour known from most programming languages when referring
|
||||
* to local variables.
|
||||
* @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.
|
||||
* maintains the current focus location
|
||||
* and exposes query facilities.
|
||||
*/
|
||||
class Scope
|
||||
{
|
||||
RefPlacement anchor_;
|
||||
|
||||
typedef IterAdapter<PlacementMO*, Scope> IterType_;
|
||||
|
||||
public:
|
||||
Scope (PlacementMO const& constitutingPlacement);
|
||||
Scope (); ///< unlocated NIL scope
|
||||
|
|
@ -70,25 +75,25 @@ namespace session {
|
|||
Scope (Scope const&);
|
||||
Scope& operator= (Scope const&);
|
||||
|
||||
static Scope const& containing (PlacementMO const& aPlacement); //////////////TODO really returning a const& here??
|
||||
static Scope const& containing (RefPlacement const& refPlacement);
|
||||
static const Scope INVALID;
|
||||
|
||||
Scope const& getParent() const;
|
||||
static Scope containing (PlacementMO const& aPlacement);
|
||||
static Scope containing (RefPlacement const& refPlacement);
|
||||
|
||||
operator std::string() const;
|
||||
Scope getParent() const;
|
||||
PlacementMO& getTop() const;
|
||||
bool isValid() const;
|
||||
bool isRoot() const;
|
||||
|
||||
typedef IterType_ iterator;
|
||||
iterator ascend() const;
|
||||
bool isValid() const;
|
||||
bool isRoot() const;
|
||||
|
||||
friend bool operator== (Scope const&, Scope const&);
|
||||
friend bool operator!= (Scope const&, Scope const&);
|
||||
};
|
||||
|
||||
|
||||
///////////////////////////TODO currently just fleshing the API
|
||||
|
||||
|
||||
|
||||
|
||||
/** as scopes are constituted by a "scope top" element (placement)
|
||||
* registered within the PlacementIndex of the current session,
|
||||
* equality is defined in terms of this defining placement.
|
||||
|
|
@ -96,13 +101,13 @@ namespace session {
|
|||
inline bool
|
||||
operator== (Scope const& scope1, Scope const& scope2)
|
||||
{
|
||||
return scope1.anchor_ == scope2.anchor_;
|
||||
return scope1.anchor_ == scope2.anchor_;
|
||||
}
|
||||
|
||||
inline bool
|
||||
operator!= (Scope const& scope1, Scope const& scope2)
|
||||
{
|
||||
return scope1.anchor_ != scope2.anchor_;
|
||||
return scope1.anchor_ != scope2.anchor_;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -179,6 +179,12 @@ namespace session {
|
|||
{
|
||||
return resolvingWrapper_;
|
||||
}
|
||||
|
||||
PlacementMO&
|
||||
getScope (PlacementMO const& placement2locate)
|
||||
{
|
||||
return IMPL::getPlacementIndex().getScope(placement2locate);
|
||||
}
|
||||
|
||||
PlacementMO&
|
||||
getScopeRoot()
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ namespace session {
|
|||
{
|
||||
static QueryResolver const& getResolver();
|
||||
|
||||
static PlacementMO& getScope (PlacementMO const&);
|
||||
static PlacementMO& getScopeRoot();
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -156,6 +156,14 @@ namespace session {
|
|||
|
||||
/** @return root scope of the current model (session datastructure) */
|
||||
PlacementMO&
|
||||
SessionServiceExploreScope::getScope (PlacementMO const& placementToLocate)
|
||||
{
|
||||
return SessionImplAPI::current->getScope(placementToLocate);
|
||||
}
|
||||
|
||||
|
||||
/** @return root scope of the current model (session datastructure) */
|
||||
PlacementMO&
|
||||
SessionServiceExploreScope::getScopeRoot()
|
||||
{
|
||||
return SessionImplAPI::current->getScopeRoot();
|
||||
|
|
|
|||
|
|
@ -76,6 +76,15 @@ namespace session {
|
|||
Time start_;
|
||||
TrackID id_;
|
||||
|
||||
|
||||
string
|
||||
initShortID() const
|
||||
{
|
||||
return buildShortID("Fork");
|
||||
}
|
||||
|
||||
bool isValid() const;
|
||||
|
||||
protected:
|
||||
Track (TrackID const&);
|
||||
friend class MObjectFactory;
|
||||
|
|
@ -87,7 +96,6 @@ namespace session {
|
|||
|
||||
bool isSameID (string const&);
|
||||
|
||||
virtual bool isValid() const;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -81,8 +81,9 @@ namespace test {
|
|||
|
||||
DEFINE_PROCESSABLE_BY (BuilderTool);
|
||||
|
||||
virtual bool isValid() const { return true;}
|
||||
virtual operator string() const { return display("DummyMO"); }
|
||||
virtual bool isValid() const { return true;}
|
||||
virtual string initShortID() const { return buildShortID("DummyMO"); }
|
||||
virtual operator string() const { return display("DummyMO"); }
|
||||
static void killDummy (MObject* dum) { delete (DummyMO*)dum; }
|
||||
|
||||
protected:
|
||||
|
|
|
|||
|
|
@ -25,6 +25,13 @@ PLANNED "DeleteClip_test" DeleteClip_test <<END
|
|||
END
|
||||
|
||||
|
||||
TEST "verify MObject interface" MObjectInterface_test <<END
|
||||
out: Clip\.[0-9]{3}
|
||||
out: Label\.[0-9]{3}
|
||||
out: DummyMO\.[0-9]{3}
|
||||
END
|
||||
|
||||
|
||||
PLANNED "SessionElementTracker_test" SessionElementTracker_test <<END
|
||||
END
|
||||
|
||||
|
|
@ -157,14 +164,39 @@ out: --------------------------------Test-10: same path, but filtered to TestSub
|
|||
out: Placement<.+TestSubMO21.>
|
||||
out: --------------------------------Test-11: continue exploring partially used TestSubMO2 iterator
|
||||
out: Placement<.+TestSubMO21.>
|
||||
return: 0
|
||||
END
|
||||
|
||||
|
||||
PLANNED "Placement search scope" PlacementScope_test <<END
|
||||
TEST "Placement search scope" PlacementScope_test <<END
|
||||
out: Scope: \[Label\.[0-9]{3}\]
|
||||
out: Placement<.+TestSubMO21.>
|
||||
out: Scope: \[DummyMO\.[0-9]{3}\]
|
||||
out: Placement<.+TestSubMO21.>
|
||||
out: Scope: \[DummyMO\.[0-9]{3}\]
|
||||
out: Placement<.+TestSubMO21.>
|
||||
out: Scope: \[DummyMO\.[0-9]{3}\]
|
||||
out: Placement<.+TestSubMO21.>
|
||||
out: Scope: \[DummyMO\.[0-9]{3}\]
|
||||
out: Placement<.+TestSubMO21.>
|
||||
out: Scope: \[Label\.[0-9]{3}\]
|
||||
out: Placement<.+TestSubMO2.>
|
||||
out: Scope: \[DummyMO\.[0-9]{3}\]
|
||||
out: Placement<.+TestSubMO1.>
|
||||
out: Scope: \[Label\.[0-9]{3}\]
|
||||
out: Placement<.+DummyMO.>
|
||||
return: 0
|
||||
END
|
||||
|
||||
|
||||
PLANNED "Path of nested scopes" ScopePath_test <<END
|
||||
TEST "Path of nested scopes" ScopePath_test <<END
|
||||
out: Step\(1\): /\[DummyMO\.[0-9]{3}\]/\[DummyMO\.[0-9]{3}\]
|
||||
out: Step\(2\): /\[DummyMO\.[0-9]{3}\]
|
||||
out: Step\(3\): /
|
||||
out: Step\(4\): /\[DummyMO\.[0-9]{3}\]
|
||||
out: Step\(5\): /\[DummyMO\.[0-9]{3}\]/\[DummyMO\.[0-9]{3}\]
|
||||
out: Step\(6\): /\[DummyMO\.[0-9]{3}\]
|
||||
return: 0
|
||||
END
|
||||
|
||||
|
||||
|
|
@ -188,11 +220,23 @@ out: ^Lumiera
|
|||
END
|
||||
|
||||
|
||||
PLANNED "Query focus management" QueryFocus_test <<END
|
||||
TEST "Query focus management" QueryFocus_test <<END
|
||||
out: Focus\(2\)--->/\[DummyMO....\]
|
||||
out: Focus\(3\)--->/$
|
||||
out: Focus\(3\)--->/\[DummyMO....\]$
|
||||
out: Focus\(3\)--->/\[DummyMO....\]/\[DummyMO....\]$
|
||||
out: Focus\(3\)--->/\[DummyMO....\]/\[DummyMO....\]/\[DummyMO....\]$
|
||||
out: Focus\(3\)--->/\[DummyMO....\]/\[DummyMO....\]/\[DummyMO....\]/\[DummyMO....\]$
|
||||
out: Focus\(3\)--->/\[DummyMO....\]/\[DummyMO....\]/\[DummyMO....\]/\[DummyMO....\]/\[DummyMO....\]$
|
||||
out: Focus\(3\)--->/\[DummyMO....\]/\[DummyMO....\]/\[DummyMO....\]/\[DummyMO....\]/\[DummyMO....\]<<<--discovery exhausted
|
||||
out: Focus\(2\)--->/\[DummyMO....\]<<<--after pop
|
||||
out: Focus\(2\)--->/\[DummyMO....\]
|
||||
return: 0
|
||||
END
|
||||
|
||||
|
||||
PLANNED "Query focus stack" QueryFocusStack_test <<END
|
||||
TEST "Query focus stack" QueryFocusStack_test <<END
|
||||
return: 0
|
||||
END
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ test_components_SOURCES = \
|
|||
$(testcomponents_srcdir)/proc/mobject/builder/buildertooltest.cpp \
|
||||
$(testcomponents_srcdir)/proc/mobject/builder/buildsegmenttest.cpp \
|
||||
$(testcomponents_srcdir)/proc/mobject/controller/rendersegmenttest.cpp \
|
||||
$(testcomponents_srcdir)/proc/mobject/mobject-interface-test.cpp \
|
||||
$(testcomponents_srcdir)/proc/mobject/mobject-ref-test.cpp \
|
||||
$(testcomponents_srcdir)/proc/mobject/placement-basic-test.cpp \
|
||||
$(testcomponents_srcdir)/proc/mobject/placement-hierarchy-test.cpp \
|
||||
|
|
@ -84,10 +85,12 @@ test_components_SOURCES = \
|
|||
$(testcomponents_srcdir)/proc/mobject/session/deletecliptest.cpp \
|
||||
$(testcomponents_srcdir)/proc/mobject/session/placement-index-query-test.cpp \
|
||||
$(testcomponents_srcdir)/proc/mobject/session/placement-index-test.cpp \
|
||||
$(testcomponents_srcdir)/proc/mobject/session/placement-scope-test.cpp \
|
||||
$(testcomponents_srcdir)/proc/mobject/session/query-focus-stack-test.cpp \
|
||||
$(testcomponents_srcdir)/proc/mobject/session/query-focus-test.cpp \
|
||||
$(testcomponents_srcdir)/proc/mobject/session/query-resolver-test.cpp \
|
||||
$(testcomponents_srcdir)/proc/mobject/session/rebuildfixturetest.cpp \
|
||||
$(testcomponents_srcdir)/proc/mobject/session/scope-path-test.cpp \
|
||||
$(testcomponents_srcdir)/proc/mobject/session/scope-query-test.cpp \
|
||||
$(testcomponents_srcdir)/proc/mobject/session/session-service-access-test.cpp \
|
||||
$(testcomponents_srcdir)/proc/mobject/session/sessionmanagertest.cpp \
|
||||
|
|
@ -101,4 +104,5 @@ noinst_HEADERS += \
|
|||
$(testcomponents_srcdir)/proc/asset/testasset.hpp \
|
||||
$(testcomponents_srcdir)/proc/asset/testclipasset.hpp \
|
||||
$(testcomponents_srcdir)/proc/mobject/session/testclip.hpp \
|
||||
$(testcomponents_srcdir)/proc/mobject/session/testsession1.hpp
|
||||
$(testcomponents_srcdir)/proc/mobject/session/testsession1.hpp \
|
||||
$(testcomponents_srcdir)/proc/mobject/session/test-scope-invalid.hpp
|
||||
|
|
|
|||
|
|
@ -24,9 +24,9 @@
|
|||
/** @file mediaacessmock.cpp
|
||||
** Mock implementation of the Interface normally used to query media file
|
||||
** informations from the data backend. The Mock implementation instead holds
|
||||
** a map of fixed response which will be deliverd when querying some magic
|
||||
** a map of fixed response which will be delivered when querying some magic
|
||||
** filenames.
|
||||
**
|
||||
**
|
||||
** @see mediaaccessmocktest.cpp validating the Mock
|
||||
** @see MediaAccessFactory the real thing
|
||||
**
|
||||
|
|
@ -51,75 +51,72 @@ using std::vector;
|
|||
using std::map;
|
||||
|
||||
|
||||
namespace backend_interface
|
||||
{
|
||||
namespace test
|
||||
{
|
||||
namespace backend_interface {
|
||||
namespace test {
|
||||
|
||||
typedef MediaAccessFacade::FileHandle FileHandle;
|
||||
typedef MediaAccessFacade::ChanHandle ChanHandle;
|
||||
|
||||
|
||||
namespace // implementation details
|
||||
{
|
||||
typedef vector<ChanDesc> Response;
|
||||
const ChanDesc NULLResponse;
|
||||
|
||||
|
||||
namespace { // implementation details
|
||||
|
||||
struct TestCases : map<string,Response>
|
||||
{
|
||||
TestCases ()
|
||||
{
|
||||
// ------------------------------------------------------------------TESTCASES
|
||||
(*this)["test-1"].push_back (ChanDesc ("video","ID", genH()));
|
||||
|
||||
(*this)["test-2"].push_back (ChanDesc ("video","H264", genH()));
|
||||
(*this)["test-2"].push_back (ChanDesc ("audio-L","PCM", genH()));
|
||||
(*this)["test-2"].push_back (ChanDesc ("audio-R","PCM", genH()));
|
||||
// ------------------------------------------------------------------TESTCASES
|
||||
}
|
||||
|
||||
bool known (string key)
|
||||
{
|
||||
const_iterator i = find (key);
|
||||
return (i != end());
|
||||
}
|
||||
private:
|
||||
int _i_;
|
||||
ChanHandle genH()
|
||||
{
|
||||
return reinterpret_cast<ChanHandle> (++_i_);
|
||||
}
|
||||
};
|
||||
|
||||
// instantiate TestCasses table
|
||||
TestCases testCases;
|
||||
|
||||
} // (end) implementation namespace
|
||||
|
||||
|
||||
FileHandle
|
||||
MediaAccessMock::queryFile (const char* name) throw(Invalid)
|
||||
{
|
||||
if (isnil (name))
|
||||
throw Invalid ("empty filename passed to MediaAccessFacade.");
|
||||
typedef vector<ChanDesc> Response;
|
||||
const ChanDesc NULLResponse;
|
||||
|
||||
if (!testCases.known(name))
|
||||
return 0;
|
||||
else
|
||||
return reinterpret_cast<void*> (&testCases[name]);
|
||||
}
|
||||
|
||||
ChanDesc
|
||||
MediaAccessMock::queryChannel (FileHandle h, uint chanNo) throw()
|
||||
{
|
||||
const Response* res (reinterpret_cast<Response*> (h));
|
||||
struct TestCases : map<string,Response>
|
||||
{
|
||||
TestCases ()
|
||||
{
|
||||
// ------------------------------------------------------------------TESTCASES
|
||||
(*this)["test-1"].push_back (ChanDesc ("video","ID", genH()));
|
||||
|
||||
(*this)["test-2"].push_back (ChanDesc ("video","H264", genH()));
|
||||
(*this)["test-2"].push_back (ChanDesc ("audio-L","PCM", genH()));
|
||||
(*this)["test-2"].push_back (ChanDesc ("audio-R","PCM", genH()));
|
||||
// ------------------------------------------------------------------TESTCASES
|
||||
}
|
||||
|
||||
bool known (string key)
|
||||
{
|
||||
const_iterator i = find (key);
|
||||
return (i != end());
|
||||
}
|
||||
private:
|
||||
int _i_;
|
||||
ChanHandle genH()
|
||||
{
|
||||
return reinterpret_cast<ChanHandle> (++_i_);
|
||||
}
|
||||
};
|
||||
|
||||
if (!res || res->size() <= chanNo)
|
||||
return NULLResponse;
|
||||
else
|
||||
return (*res)[chanNo];
|
||||
}
|
||||
|
||||
|
||||
} // namespace test
|
||||
|
||||
} // namespace backend_interface
|
||||
// instantiate TestCasses table
|
||||
TestCases testCases;
|
||||
|
||||
} // (end) implementation namespace
|
||||
|
||||
|
||||
FileHandle
|
||||
MediaAccessMock::queryFile (const char* name) throw(Invalid)
|
||||
{
|
||||
if (isnil (name))
|
||||
throw Invalid ("empty filename passed to MediaAccessFacade.");
|
||||
|
||||
if (!testCases.known(name))
|
||||
return 0;
|
||||
else
|
||||
return reinterpret_cast<void*> (&testCases[name]);
|
||||
}
|
||||
|
||||
ChanDesc
|
||||
MediaAccessMock::queryChannel (FileHandle h, uint chanNo) throw()
|
||||
{
|
||||
const Response* res (reinterpret_cast<Response*> (h));
|
||||
|
||||
if (!res || res->size() <= chanNo)
|
||||
return NULLResponse;
|
||||
else
|
||||
return (*res)[chanNo];
|
||||
}
|
||||
|
||||
|
||||
}} // namespace backend_interface::test
|
||||
|
|
|
|||
|
|
@ -30,23 +30,20 @@
|
|||
|
||||
|
||||
|
||||
namespace backend_interface
|
||||
{
|
||||
namespace test
|
||||
namespace backend_interface {
|
||||
namespace test {
|
||||
|
||||
/**
|
||||
* Mock implementation of the MediaAccessFacade.
|
||||
* Provides preconfigured responses for some Test-Filenames.
|
||||
*/
|
||||
class MediaAccessMock : public MediaAccessFacade
|
||||
{
|
||||
/**
|
||||
* Mock implementation of the MediaAccessFacade.
|
||||
* Provides preconfigured responses for some Test-Filenames.
|
||||
*/
|
||||
class MediaAccessMock : public MediaAccessFacade
|
||||
{
|
||||
public:
|
||||
FileHandle queryFile (const char* name) throw(lumiera::error::Invalid);
|
||||
ChanDesc queryChannel (FileHandle, uint chanNo) throw();
|
||||
};
|
||||
|
||||
|
||||
} // namespace test
|
||||
|
||||
} // namespace backend_interface
|
||||
public:
|
||||
FileHandle queryFile (const char* name) throw(lumiera::error::Invalid);
|
||||
ChanDesc queryChannel (FileHandle, uint chanNo) throw();
|
||||
};
|
||||
|
||||
|
||||
}} // namespace backend_interface::test
|
||||
#endif
|
||||
|
|
|
|||
120
tests/components/proc/mobject/mobject-interface-test.cpp
Normal file
120
tests/components/proc/mobject/mobject-interface-test.cpp
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
MObjectInterface(Test) - covers behaviour common to all MObjects
|
||||
|
||||
Copyright (C) Lumiera.org
|
||||
2010, 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/lumitime.hpp"
|
||||
#include "lib/symbol.hpp"
|
||||
|
||||
#include "proc/asset/media.hpp"
|
||||
#include "proc/mobject/mobject.hpp"
|
||||
#include "proc/mobject/session/mobjectfactory.hpp"
|
||||
//#include "proc/mobject/mobject-ref.hpp"
|
||||
#include "proc/mobject/placement.hpp"
|
||||
//#include "proc/mobject/placement-ref.hpp"
|
||||
//#include "proc/mobject/session/placement-index.hpp"
|
||||
//#include "proc/mobject/session/session-service-mock-index.hpp"
|
||||
//#include "proc/mobject/session/clip.hpp"
|
||||
//#include "proc/mobject/explicitplacement.hpp"
|
||||
#include "proc/mobject/test-dummy-mobject.hpp"
|
||||
//#include "lib/test/test-helper.hpp"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
|
||||
|
||||
namespace mobject {
|
||||
namespace test {
|
||||
|
||||
// using lib::test::showSizeof;
|
||||
using std::string;
|
||||
using std::cout;
|
||||
using std::endl;
|
||||
|
||||
using lib::Symbol;
|
||||
|
||||
|
||||
using lumiera::Time;
|
||||
// using session::Clip;
|
||||
// using session::PMedia;
|
||||
|
||||
|
||||
using namespace mobject::test;
|
||||
typedef TestPlacement<DummyMO> PDummy;
|
||||
|
||||
|
||||
/*********************************************************************************
|
||||
* @test cover the common behaviour of all MObjects.
|
||||
* @note the MObject interface is still very preliminary (as of 10/10).
|
||||
* It is expected to support some kind of metadata and object serialisation
|
||||
*
|
||||
* @see mobject::MObject
|
||||
* @see mobject::Placement
|
||||
*/
|
||||
class MObjectInterface_test : public Test
|
||||
{
|
||||
|
||||
|
||||
|
||||
virtual void
|
||||
run (Arg)
|
||||
{
|
||||
PMO testClip1 = asset::Media::create("test-1", asset::VIDEO)->createClip();
|
||||
PMO testClip2 = asset::Media::create("test-2", asset::VIDEO)->createClip();
|
||||
|
||||
// set up a tie to fixed start positions (i.e. "properties of placement")
|
||||
testClip1.chain(Time(10));
|
||||
testClip2.chain(Time(20));
|
||||
|
||||
Symbol labelType ("dummyLabel");
|
||||
PMO testLabel1 = MObject::create (labelType);
|
||||
|
||||
testLabel1.chain(Time(30));
|
||||
|
||||
PDummy testDummy1(*new DummyMO);
|
||||
PDummy testDummy2(*new TestSubMO1);
|
||||
|
||||
ASSERT (testClip1->isValid());
|
||||
ASSERT (testClip2->isValid());
|
||||
ASSERT (testLabel1->isValid());
|
||||
ASSERT (testDummy1->isValid());
|
||||
ASSERT (testDummy2->isValid());
|
||||
|
||||
Time lenC1 = testClip1->getLength();
|
||||
Time lenC2 = testClip2->getLength();
|
||||
Time lenL1 = testLabel1->getLength();
|
||||
|
||||
cout << testClip1->shortID() << endl;
|
||||
cout << testClip2->shortID() << endl;
|
||||
cout << testLabel1->shortID() << endl;
|
||||
cout << testDummy1->shortID() << endl;
|
||||
cout << testDummy2->shortID() << endl;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/** Register this test class... */
|
||||
LAUNCHER (MObjectInterface_test, "unit session");
|
||||
|
||||
|
||||
}} // namespace mobject::test
|
||||
|
|
@ -23,31 +23,40 @@
|
|||
|
||||
#include "lib/test/run.hpp"
|
||||
#include "lib/test/test-helper.hpp"
|
||||
#include "proc/mobject/mobject.hpp"
|
||||
#include "proc/mobject/session/scope.hpp"
|
||||
#include "proc/mobject/session/test-scopes.hpp"
|
||||
//#include "lib/lumitime.hpp"
|
||||
//#include "proc/mobject/placement-ref.hpp"
|
||||
//#include "proc/mobject/session/placement-index.hpp"
|
||||
//#include "proc/mobject/test-dummy-mobject.hpp"
|
||||
#include "proc/mobject/session/scope-locator.hpp"
|
||||
#include "lib/util.hpp"
|
||||
|
||||
#include <iostream>
|
||||
//#include <string>
|
||||
|
||||
using util::isSameObject;
|
||||
|
||||
|
||||
namespace mobject {
|
||||
namespace session {
|
||||
namespace test {
|
||||
|
||||
// using namespace mobject::test;
|
||||
using lumiera::error::LUMIERA_ERROR_INVALID;
|
||||
namespace { // Helper to enumerate Contents
|
||||
// of the test-dummy session
|
||||
typedef _ScopeIterMO _Iter;
|
||||
|
||||
_Iter
|
||||
contents_of_testSession (PPIdx testSession)
|
||||
{
|
||||
return ScopeLocator::instance().query<MObject> (testSession->getRoot());
|
||||
}
|
||||
|
||||
_Iter
|
||||
pathToRoot (PlacementMO& elm)
|
||||
{
|
||||
Scope startScope(elm);
|
||||
return ScopeLocator::instance().getRawPath (startScope);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
using util::isSameObject;
|
||||
//using lumiera::Time;
|
||||
//using std::string;
|
||||
using std::cout;
|
||||
using std::endl;
|
||||
|
||||
|
||||
|
||||
|
|
@ -69,54 +78,47 @@ namespace test {
|
|||
virtual void
|
||||
run (Arg)
|
||||
{
|
||||
// Prepare an (test)Index backing the PlacementRefs
|
||||
// Prepare an (test)Session
|
||||
// with some dummy contents
|
||||
PPIdx index = build_testScopes();
|
||||
|
||||
verifyEquality();
|
||||
UNIMPLEMENTED ("function test of placement scope interface");
|
||||
#if false //////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET 384 !!!!!!!!!
|
||||
verifyLookup (index);
|
||||
verifyNavigation (index);
|
||||
}
|
||||
|
||||
|
||||
typedef Query<Placement<DummyMO> >::iterator _Iter;
|
||||
|
||||
_Iter
|
||||
getContents(PPIdx index)
|
||||
{
|
||||
return index->query<DummyMO>(index->getRoot());
|
||||
#endif //////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET 384 !!!!!!!!!
|
||||
}
|
||||
|
||||
/** @test for each Placement in our test "session",
|
||||
* find the scope and verify it's in line with the index
|
||||
*/
|
||||
void
|
||||
verifyLookup (PPIdx ref_index)
|
||||
verifyLookup (PPIdx sess)
|
||||
{
|
||||
#if false //////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET 384 !!!!!!!!!
|
||||
for (_Iter elm = getContents(ref_index); elm; ++elm)
|
||||
for (_Iter ii = contents_of_testSession(sess); ii; ++ii)
|
||||
{
|
||||
ASSERT (elm->isValid());
|
||||
cout << string(*elm) << endl;
|
||||
Scope const& scope1 = Scope::containing(*elm);
|
||||
PlacementMO& elm = *ii;
|
||||
ASSERT (elm.isValid());
|
||||
Scope const& scope1 = Scope::containing(elm);
|
||||
std::cout << "Scope: " << string(scope1) << std::endl;
|
||||
std::cout << string(elm) << std::endl;
|
||||
|
||||
RefPlacement ref (*elm);
|
||||
RefPlacement ref (elm);
|
||||
Scope const& scope2 = Scope::containing(ref);
|
||||
|
||||
// verify this with the scope registered within the index...
|
||||
PlacementMO& scopeTop = ref_index->getScope(*elm);
|
||||
PlacementMO& scopeTop = sess->getScope(elm);
|
||||
ASSERT (scope1 == scopeTop);
|
||||
ASSERT (scope2 == scopeTop);
|
||||
ASSERT (scope1 == scope2);
|
||||
|
||||
ASSERT (isSameObject (scope1,scope2));
|
||||
ASSERT (!isSameObject (scope1,scope2));
|
||||
}
|
||||
#endif //////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET 384 !!!!!!!!!
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** @test equality of scopes is based on the ID of the scope top (Placement) */
|
||||
void
|
||||
verifyEquality ()
|
||||
|
|
@ -130,55 +132,66 @@ namespace test {
|
|||
ASSERT (scope1 != nil); ASSERT (nil != scope1);
|
||||
ASSERT (scope2 != nil); ASSERT (nil != scope2);
|
||||
|
||||
ASSERT (aPlac == scope1); ASSERT (scope1 == aPlac);
|
||||
ASSERT (aPlac == scope2); ASSERT (scope2 == aPlac);
|
||||
ASSERT (aPlac == scope1); ASSERT (scope1 == aPlac);
|
||||
ASSERT (aPlac == scope2); ASSERT (scope2 == aPlac);
|
||||
ASSERT (aPlac != nil); ASSERT (nil != aPlac);
|
||||
|
||||
Scope par (scope1.getParent());
|
||||
ASSERT (scope1 != par); ASSERT (par != scope1);
|
||||
ASSERT (scope2 != par); ASSERT (par != scope2);
|
||||
|
||||
PlacementMO& plac2 (scope2.getTop());
|
||||
ASSERT (aPlac.getID() == plac2.getID());
|
||||
PlacementMO& placm2 (scope2.getTop());
|
||||
ASSERT (aPlac.getID() == placm2.getID());
|
||||
|
||||
PlacementMO& parPlac (par.getTop());
|
||||
ASSERT (aPlac.getID() != parPlac.getID());
|
||||
}
|
||||
|
||||
|
||||
/** @test navigate to root, starting from each Placement */
|
||||
|
||||
/** @test for each element in our test session,
|
||||
* establish the scope and retrieve the path to root,
|
||||
* verifying the parent relationships as we go up.
|
||||
* @note this is the "raw" path, i.e as stored in the
|
||||
* PlacementIndex, as opposed to the effective
|
||||
* path, which might digress for meta-clips
|
||||
*/
|
||||
void
|
||||
verifyNavigation (PPIdx ref_index)
|
||||
verifyNavigation (PPIdx sess)
|
||||
{
|
||||
#if false //////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET 384 !!!!!!!!!
|
||||
for (_Iter elm = getContents(ref_index); elm; ++elm)
|
||||
for (_Iter elm = contents_of_testSession(sess); elm; ++elm)
|
||||
{
|
||||
Scope const& scope = Scope::containing(*elm);
|
||||
ASSERT (scope == *scope.ascend());
|
||||
for (Scope::iterator sco = scope.ascend(); sco; ++sco)
|
||||
if (sco->isRoot())
|
||||
{
|
||||
VERIFY_ERROR (INVALID, sco->getParent() );
|
||||
RefPlacement top = sco->getTop();
|
||||
RefPlacement root = ref_index->getRoot();
|
||||
|
||||
ASSERT (isSameObject (top,root));
|
||||
}
|
||||
else
|
||||
{
|
||||
Scope& parent = sco->getParent();
|
||||
RefPlacement top = sco->getTop();
|
||||
Scope& parentsScope = Scope::containing(top);
|
||||
RefPlacement topsTop = ref_index->getScope(top); ///////////////////TODO impact of Binding a Sequence? see Ticket #311
|
||||
ASSERT (topsTop == parentsScope);
|
||||
ASSERT (isSameObject (topsTop, parentsScope.getTop()));
|
||||
} }
|
||||
#endif //////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET 384 !!!!!!!!!
|
||||
_Iter pathIter = pathToRoot(*elm);
|
||||
Scope const& enclosing = Scope::containing(*elm);
|
||||
ASSERT (enclosing == Scope(*elm).getParent());
|
||||
ASSERT (*pathIter == Scope(*elm));
|
||||
|
||||
for ( ; pathIter; ++pathIter)
|
||||
{
|
||||
Scope sco(*pathIter);
|
||||
if (sco.isRoot())
|
||||
{
|
||||
VERIFY_ERROR (NO_PARENT_SCOPE, sco.getParent() );
|
||||
PlacementMO& top = sco.getTop();
|
||||
PlacementMO& root = sess->getRoot();
|
||||
|
||||
ASSERT (isSameObject (top,root));
|
||||
}
|
||||
else
|
||||
{
|
||||
Scope parent = sco.getParent();
|
||||
PlacementMO& top = sco.getTop();
|
||||
Scope parentsScope = Scope::containing(top);
|
||||
PlacementMO& topsTop = sess->getScope(top); ///////////////////TODO impact of Binding a Sequence? see Ticket #311
|
||||
ASSERT (topsTop == parentsScope);
|
||||
ASSERT (isSameObject (topsTop, parentsScope.getTop()));
|
||||
}}}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
/** Register this test class... */
|
||||
LAUNCHER (PlacementScope_test, "function session");
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@
|
|||
#include "lib/test/test-helper.hpp"
|
||||
#include "proc/mobject/session/test-scopes.hpp"
|
||||
#include "proc/mobject/session/query-focus-stack.hpp"
|
||||
#include "proc/mobject/session/test-scope-invalid.hpp"
|
||||
#include "lib/util.hpp"
|
||||
|
||||
|
||||
|
|
@ -46,6 +47,9 @@ namespace test {
|
|||
* the stack frames (ScopePath instances) and cleans up unused frames.
|
||||
* Similar to the ScopePath_test, we use a pseudo-session to create
|
||||
* some path frames to play with.
|
||||
* @note this test executes a lot of functionality in a manual by-hand way,
|
||||
* which in the actual application is accessed and utilised through
|
||||
* QueryFocus objects as frontend.
|
||||
*
|
||||
* @see mobject::session::QueryFocusStack
|
||||
* @see mobject::session::ScopePath
|
||||
|
|
@ -56,7 +60,8 @@ namespace test {
|
|||
virtual void
|
||||
run (Arg)
|
||||
{
|
||||
// Prepare an (test)Index backing the PlacementRefs
|
||||
// Prepare an (test)Index and
|
||||
// set up dummy session contents
|
||||
PPIdx index = build_testScopes();
|
||||
|
||||
createStack();
|
||||
|
|
@ -74,7 +79,7 @@ namespace test {
|
|||
|
||||
ASSERT (!isnil (stack));
|
||||
ASSERT (!isnil (stack.top()));
|
||||
ASSERT (stack.top().getLeaf().isRoot());
|
||||
ASSERT (stack.top().isRoot());
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -106,7 +111,7 @@ namespace test {
|
|||
|
||||
// can use/navigate the stack top frame
|
||||
stack.top().goRoot();
|
||||
ASSERT (isnil (stack.top())); // now indeed at root == empty path
|
||||
ASSERT (!stack.top()); // now indeed at root == no path
|
||||
ASSERT (secondFrame.getLeaf().isRoot());
|
||||
ASSERT (secondFrame == stack.top());
|
||||
|
||||
|
|
@ -155,7 +160,7 @@ namespace test {
|
|||
ScopePath& anotherFrame = stack.push(startPoint);
|
||||
ASSERT (0 == anotherFrame.ref_count());
|
||||
ASSERT (1 == firstFrame.ref_count());
|
||||
intrusive_ptr_release (&anotherFrame);
|
||||
intrusive_ptr_release (&firstFrame);
|
||||
ASSERT (0 == firstFrame.ref_count());
|
||||
ASSERT (firstFrame.getLeaf() == startPoint);
|
||||
|
||||
|
|
@ -188,16 +193,16 @@ namespace test {
|
|||
intrusive_ptr_add_ref (&firstFrame);
|
||||
|
||||
ScopePath beforeInvalidNavigation = firstFrame;
|
||||
Scope unrelatedScope (TestPlacement<> (*new DummyMO));
|
||||
Scope const& unrelatedScope = fabricate_invalidScope();
|
||||
|
||||
// try to navigate to an invalid place
|
||||
VERIFY_ERROR (INVALID, stack.top().navigate (unrelatedScope) );
|
||||
VERIFY_ERROR (INVALID_SCOPE, stack.top().navigate (unrelatedScope) );
|
||||
ASSERT (1 == stack.size());
|
||||
ASSERT (1 == firstFrame.ref_count());
|
||||
ASSERT (stack.top().getLeaf() == startPoint);
|
||||
|
||||
// try to push an invalid place
|
||||
VERIFY_ERROR (INVALID, stack.push (unrelatedScope) );
|
||||
VERIFY_ERROR (INVALID_SCOPE, stack.push (unrelatedScope) );
|
||||
ASSERT (1 == stack.size());
|
||||
ASSERT (1 == firstFrame.ref_count());
|
||||
ASSERT (stack.top().getLeaf() == startPoint);
|
||||
|
|
|
|||
|
|
@ -22,13 +22,10 @@
|
|||
|
||||
|
||||
#include "lib/test/run.hpp"
|
||||
//#include "lib/lumitime.hpp"
|
||||
//#include "proc/mobject/placement-ref.hpp"
|
||||
#include "proc/mobject/session/test-scopes.hpp"
|
||||
#include "proc/mobject/session/placement-index.hpp"
|
||||
#include "proc/mobject/session/query-focus.hpp"
|
||||
#include "proc/mobject/session/scope.hpp"
|
||||
//#include "lib/util.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
|
@ -39,30 +36,44 @@ namespace mobject {
|
|||
namespace session {
|
||||
namespace test {
|
||||
|
||||
//using util::isSameObject;
|
||||
//using lumiera::Time;
|
||||
namespace {
|
||||
/** Helper: extract the refcount
|
||||
* of the current path referred by the given focus
|
||||
*/
|
||||
inline size_t
|
||||
refs (QueryFocus const& focus)
|
||||
{
|
||||
return focus.currentPath().ref_count();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
using std::string;
|
||||
using std::cout;
|
||||
using std::endl;
|
||||
|
||||
|
||||
/**********************************************************************************
|
||||
/***********************************************************************************
|
||||
* @test handling of current query focus when navigating a system of nested scopes.
|
||||
* Using a pseudo-session (actually just a PlacementIndex), this test
|
||||
* creates some nested scopes and then checks moving the "current scope".
|
||||
* accesses some nested scopes and then checks moving the "current scope".
|
||||
* Moreover a (stack-like) sub-focus is created, temporarily moving aside
|
||||
* the current focus and returning later on.
|
||||
*
|
||||
* @see mobject::PlacementIndex
|
||||
* @see mobject::session::ScopePath
|
||||
* @see mobject::session::QueryFocus
|
||||
*/
|
||||
class QueryFocus_test : public Test
|
||||
class QueryFocus_test
|
||||
: public Test
|
||||
{
|
||||
|
||||
virtual void
|
||||
run (Arg)
|
||||
{
|
||||
|
||||
// Prepare an (test)Index backing the PlacementRefs
|
||||
// Prepare a (test)Session with
|
||||
// some nested dummy placements
|
||||
PPIdx index = build_testScopes();
|
||||
PMO& root = index->getRoot();
|
||||
|
||||
|
|
@ -78,10 +89,13 @@ namespace test {
|
|||
QueryFocus currentFocus;
|
||||
ASSERT (scopePosition == Scope(currentFocus));
|
||||
ASSERT (currentFocus == theFocus);
|
||||
ASSERT (2 == refs(currentFocus));
|
||||
ASSERT (2 == refs(theFocus));
|
||||
}
|
||||
|
||||
|
||||
/** @test move the current focus to various locations
|
||||
|
||||
/** @test move the current focus to different locations
|
||||
* and discover contents there. */
|
||||
void
|
||||
checkNavigation (QueryFocus& focus)
|
||||
|
|
@ -89,8 +103,7 @@ namespace test {
|
|||
focus.reset();
|
||||
ASSERT (Scope(focus).isRoot());
|
||||
|
||||
#ifdef false ////////////////////////////////////////////////////////////////////////////////TICKET 384
|
||||
PMO& someObj = focus.query<TestSubMO1>();
|
||||
PMO& someObj = *focus.query<TestSubMO1>();
|
||||
// by construction of the test fixture,
|
||||
// we know this object is root -> ps2 -> ps3
|
||||
|
||||
|
|
@ -99,32 +112,36 @@ namespace test {
|
|||
ASSERT (!Scope(focus).isRoot());
|
||||
ScopePath path = focus.currentPath();
|
||||
ASSERT (someObj == path.getLeaf());
|
||||
ASSERT (path.getParent().getParent().isRoot());
|
||||
ASSERT (Scope(focus).getParent().getParent().isRoot());
|
||||
|
||||
focus.attach (path.getParent());
|
||||
ASSERT (Scope(focus) == path.getParent());
|
||||
focus.attach (path.getLeaf().getParent());
|
||||
ASSERT (Scope(focus) == path.getLeaf().getParent());
|
||||
ASSERT (someObj != Scope(focus));
|
||||
ASSERT (path.contains (focus.currentPath()));
|
||||
ASSERT (focus.currentPath().getParent().isRoot());
|
||||
#endif
|
||||
ASSERT (focus.currentPath().getLeaf().getParent().isRoot());
|
||||
|
||||
// as the focus now has been moved up one level,
|
||||
// we'll re-discover the original starting point as immediate child
|
||||
CHECK (someObj == *focus.explore<TestSubMO1>());
|
||||
}
|
||||
|
||||
|
||||
/** @test side-effect free manipulation of a sub-focus */
|
||||
|
||||
/** @test side-effect free manipulation of a sub-focus,
|
||||
* while the original focus is pushed aside (stack) */
|
||||
void
|
||||
manipulate_subFocus()
|
||||
{
|
||||
#ifdef false ////////////////////////////////////////////////////////////////////////////////TICKET 384
|
||||
QueryFocus original; // automatically attaches to current stack top
|
||||
uint num_refs = original.ref_count();
|
||||
ASSERT (num_refs > 1);
|
||||
QueryFocus original; // automatically attaches to current stack top
|
||||
uint num_refs = refs (original);
|
||||
ASSERT (num_refs > 1); // because the run() function also holds a ref
|
||||
|
||||
QueryFocus subF = QueryFocus::push();
|
||||
cout << string(subF) << endl;
|
||||
ASSERT (subF == original);
|
||||
|
||||
ASSERT ( 1 == subF.ref_count());
|
||||
ASSERT (num_refs == original.ref_count());
|
||||
ASSERT ( 1 == refs(subF) );
|
||||
ASSERT (num_refs == refs(original));
|
||||
|
||||
{ // temporarily creating an independent focus attached differently
|
||||
QueryFocus subF2 = QueryFocus::push(Scope(subF).getParent());
|
||||
|
|
@ -132,33 +149,33 @@ namespace test {
|
|||
ASSERT (subF == original);
|
||||
cout << string(subF2) << endl;
|
||||
|
||||
Iterator ii = subF2.query<TestSubMO21>();
|
||||
while (ii)
|
||||
ScopeQuery<TestSubMO21>::iterator ii = subF2.explore<TestSubMO21>();
|
||||
while (ii) // drill down depth first
|
||||
{
|
||||
subF2.attach(*ii);
|
||||
cout << string(subF2) << endl;
|
||||
ii = subF2.query<TestSubMO21>();
|
||||
ii = subF2.explore<TestSubMO21>();
|
||||
}
|
||||
cout << string(subF2) << "<<<--discovery exhausted" << endl;
|
||||
|
||||
subF2.pop(); // releasing this focus and re-attaching to what's on stack top
|
||||
cout << string(subF2) << "<<<--after pop()" << endl;
|
||||
ASSERT (subF2 == subF);
|
||||
ASSERT (2 == subF2.ref_count()); // both are now attached to the same path
|
||||
ASSERT (2 == subF.ref_count());
|
||||
ASSERT (2 == refs(subF2)); // both are now attached to the same path
|
||||
ASSERT (2 == refs(subF));
|
||||
}
|
||||
// subF2 went out of scope, but no auto-pop happens (because subF is still there)
|
||||
cout << string(subF) << endl;
|
||||
|
||||
ASSERT ( 1 == subF.ref_count());
|
||||
ASSERT (num_refs == original.ref_count());
|
||||
ASSERT ( 1 == refs(subF));
|
||||
ASSERT (num_refs == refs(original));
|
||||
// when subF goes out of scope now, auto-pop will happen...
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
/** Register this test class... */
|
||||
LAUNCHER (QueryFocus_test, "unit session");
|
||||
|
||||
|
|
|
|||
|
|
@ -24,37 +24,38 @@
|
|||
#include "lib/test/run.hpp"
|
||||
#include "lib/test/test-helper.hpp"
|
||||
#include "proc/mobject/session/test-scopes.hpp"
|
||||
#include "proc/mobject/session/placement-index.hpp"
|
||||
#include "proc/mobject/session/scope-path.hpp"
|
||||
//#include "lib/lumitime.hpp"
|
||||
//#include "proc/mobject/placement-ref.hpp"
|
||||
//#include "proc/mobject/session/placement-index.hpp"
|
||||
//#include "proc/mobject/test-dummy-mobject.hpp"
|
||||
#include "proc/mobject/session/test-scope-invalid.hpp"
|
||||
#include "lib/util.hpp"
|
||||
|
||||
//#include <iostream>
|
||||
//#include <string>
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
|
||||
namespace mobject {
|
||||
namespace session {
|
||||
namespace test {
|
||||
|
||||
using std::cout;
|
||||
using std::endl;
|
||||
using std::string;
|
||||
using util::isnil;
|
||||
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;
|
||||
using lumiera::error::LUMIERA_ERROR_INVALID;
|
||||
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
* @test properties and behaviour of the path of nested scopes.
|
||||
* Using a pseudo-session (actually just a PlacementIndex), this test
|
||||
* creates some nested scopes and executes navigation moves on them.
|
||||
* Using a pseudo-session (actually just a PlacementIndex),
|
||||
* this test creates some nested scopes, builds scope paths
|
||||
* and executes various comparisons navigation moves on them.
|
||||
* Especially detection of invalid scopes and paths and the
|
||||
* special handling of empty and root paths is covered.
|
||||
* @see mobject::Placement
|
||||
* @see mobject::session::ScopePath
|
||||
* @see mobject::session::QueryFocus
|
||||
|
|
@ -70,14 +71,15 @@ namespace test {
|
|||
PMO& startPlacement = retrieve_startElm();
|
||||
ASSERT (startPlacement.isValid());
|
||||
|
||||
checkInvalidScopeDetection();
|
||||
ScopePath testPath = buildPath (startPlacement);
|
||||
checkRelations (testPath,startPlacement);
|
||||
invalidPath (testPath,startPlacement);
|
||||
rootPath (testPath);
|
||||
check_Identity_and_Copy (startPlacement);
|
||||
check_RefcountProtection (startPlacement);
|
||||
navigate (testPath, index);
|
||||
clear (testPath, index);
|
||||
////////////////////////////////////////TICKET #429 : verify diagnostic output (to be added later)
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -94,13 +96,30 @@ namespace test {
|
|||
ASSERT ( path.getLeaf() == path2.getLeaf());
|
||||
ASSERT (path2.getLeaf() == path3.getLeaf());
|
||||
|
||||
Scope unrelatedScope (TestPlacement<> (*new DummyMO));
|
||||
VERIFY_ERROR (INVALID, ScopePath(unrelatedScope) );
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
checkInvalidScopeDetection()
|
||||
{
|
||||
// verify detection of illegal scopes and paths...
|
||||
TestPlacement<> notRelated2anything (*new DummyMO);
|
||||
VERIFY_ERROR (NOT_IN_SESSION, Scope invalid (notRelated2anything) );
|
||||
|
||||
Scope const& scopeOfEvil = fabricate_invalidScope();
|
||||
REQUIRE (!scopeOfEvil.isValid());
|
||||
|
||||
VERIFY_ERROR (INVALID_SCOPE, ScopePath outsideCurrentModel (scopeOfEvil) );
|
||||
|
||||
// but there is one exception to this rule...
|
||||
ScopePath theInvalidToken (Scope::INVALID);
|
||||
CHECK (!theInvalidToken.isValid());
|
||||
CHECK (theInvalidToken.empty());
|
||||
}
|
||||
|
||||
|
||||
|
||||
void
|
||||
checkIteration (ScopePath path, PMO& refPlacement)
|
||||
{
|
||||
|
|
@ -137,7 +156,7 @@ namespace test {
|
|||
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 (path1.contains (path2));
|
||||
ASSERT (!disjoint(path1,path2));
|
||||
ASSERT (path2 == commonPrefix(path1,path2));
|
||||
ASSERT (path2 == commonPrefix(path2,path1));
|
||||
|
|
@ -168,6 +187,7 @@ namespace test {
|
|||
ASSERT (refPath);
|
||||
ASSERT (!ScopePath::INVALID);
|
||||
ASSERT (isnil (ScopePath::INVALID));
|
||||
ASSERT ("!" == string(ScopePath::INVALID));
|
||||
|
||||
ScopePath invalidP (ScopePath::INVALID);
|
||||
ASSERT (isnil (invalidP));
|
||||
|
|
@ -179,28 +199,28 @@ namespace test {
|
|||
|
||||
Scope refScope (refPlacement);
|
||||
ASSERT (!invalidP.contains (refScope));
|
||||
ASSERT (!invalidP.endsAt (refScope));
|
||||
VERIFY_ERROR (EMPTY_SCOPE_PATH, invalidP.endsAt (refScope) ); // Logic: can't inspect the end of nothing
|
||||
|
||||
ASSERT (refPath.contains (invalidP)); // If the moon consists of green cheese, I'll eat my hat!
|
||||
ASSERT (refPath.contains (invalidP)); // If the moon is made 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());
|
||||
VERIFY_ERROR (EMPTY_SCOPE_PATH, invalidP.moveUp() );
|
||||
Scope root = refPath.goRoot();
|
||||
ASSERT (1 == refPath.length());
|
||||
|
||||
Scope nil = refPath.moveUp();
|
||||
Scope const& nil = refPath.moveUp();
|
||||
ASSERT (refPath.empty());
|
||||
ASSERT (!nil.isValid());
|
||||
ASSERT (refPath == invalidP);
|
||||
ASSERT (invalidP.contains (nil));
|
||||
ASSERT (invalidP.contains (refPath));
|
||||
ASSERT (!invalidP.contains (refScope));
|
||||
|
||||
refPath.navigate(root);
|
||||
ASSERT (refPath != invalidP);
|
||||
ASSERT (!isnil (refPath));
|
||||
VERIFY_ERROR (EMPTY_SCOPE_PATH, refPath.navigate(root) );
|
||||
|
||||
//ScopePath::INVALID.navigate(root); // doesn't compile
|
||||
//ScopePath::INVALID.navigate(root); // doesn't compile: INVALID is immutable
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -233,13 +253,50 @@ namespace test {
|
|||
ASSERT (path2 == path3);
|
||||
ASSERT (path1 != path3);
|
||||
|
||||
path1 = ScopePath::INVALID;
|
||||
path2 = ScopePath::INVALID;
|
||||
ASSERT (path1 != path2);
|
||||
ASSERT (path2 != path3);
|
||||
ASSERT (path1 != path3);
|
||||
}
|
||||
|
||||
|
||||
/** @test the embedded refcount is handled sensibly
|
||||
* when it comes to copying. (This refcount
|
||||
* is used by QueryFocusStack) */
|
||||
void
|
||||
check_RefcountProtection (PMO& refPlacement)
|
||||
{
|
||||
Scope startScope (refPlacement);
|
||||
ScopePath path1 (startScope);
|
||||
ScopePath path2 (path1);
|
||||
|
||||
path1 = path2;
|
||||
CHECK (!isSameObject (path1,path2));
|
||||
CHECK (0 == path1.ref_count());
|
||||
CHECK (0 == path2.ref_count());
|
||||
|
||||
intrusive_ptr_add_ref (&path2);
|
||||
CHECK (0 == path1.ref_count());
|
||||
CHECK (0 < path2.ref_count());
|
||||
|
||||
ScopePath path3 (path2);
|
||||
CHECK (0 == path3.ref_count()); // refcount not copied
|
||||
|
||||
path3.moveUp();
|
||||
|
||||
VERIFY_ERROR (LOGIC, path2 = path3 ); // overwriting of path with refcount is prohibited
|
||||
CHECK (path1 != path3);
|
||||
path1 = path2; // but path without refcount may be overwritten
|
||||
path1 = path3;
|
||||
CHECK (path1 == path3);
|
||||
|
||||
intrusive_ptr_release (&path2); // refcount drops to zero...
|
||||
CHECK (0 == path1.ref_count());
|
||||
CHECK (0 == path2.ref_count());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** @test modify a path by \em navigating it.
|
||||
* - move one step above the current leaf
|
||||
* - move up to the root element
|
||||
|
|
@ -247,16 +304,18 @@ namespace test {
|
|||
* - 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.
|
||||
* root node). Navigate to there and verify root is the common prefix.
|
||||
*/
|
||||
void
|
||||
navigate (const ScopePath refPath, PPIdx index)
|
||||
{
|
||||
ScopePath path (refPath);
|
||||
#define __SHOWPATH(N) cout << "Step("<<N<<"): "<< string(path) << endl;
|
||||
|
||||
ScopePath path (refPath); __SHOWPATH(1)
|
||||
ASSERT (path == refPath);
|
||||
|
||||
Scope leaf = path.getLeaf();
|
||||
Scope parent = path.moveUp();
|
||||
Scope parent = path.moveUp(); __SHOWPATH(2)
|
||||
ASSERT (path != refPath);
|
||||
ASSERT (refPath.contains (path));
|
||||
ASSERT (refPath.endsAt (leaf));
|
||||
|
|
@ -264,23 +323,26 @@ namespace test {
|
|||
ASSERT (parent == leaf.getParent());
|
||||
ASSERT (parent == path.getLeaf());
|
||||
|
||||
Scope root = path.goRoot();
|
||||
Scope root = path.goRoot(); __SHOWPATH(3)
|
||||
ASSERT (path != refPath);
|
||||
ASSERT (path.endsAt (root));
|
||||
ASSERT (refPath.contains (path));
|
||||
ASSERT (!path.endsAt (parent));
|
||||
ASSERT (!path.endsAt (leaf));
|
||||
|
||||
path.navigate (parent);
|
||||
path.navigate (parent); __SHOWPATH(4)
|
||||
ASSERT (path.endsAt (parent));
|
||||
ASSERT (!path.endsAt (root));
|
||||
ASSERT (!path.endsAt (leaf));
|
||||
|
||||
TestPlacement<> newNode (*new DummyMO);
|
||||
PMO& parentRefPoint = parent.getTop();
|
||||
index->insert (newNode, parentRefPoint); // place as sibling of "leaf"
|
||||
path.navigate (newNode);
|
||||
Scope newLocation =
|
||||
index->find( // place newNode as sibling of "leaf"
|
||||
index->insert (newNode, parentRefPoint));
|
||||
path.navigate (newLocation); __SHOWPATH(5)
|
||||
Scope sibling = path.getLeaf();
|
||||
ASSERT (sibling == newLocation);
|
||||
ASSERT (parent == sibling.getParent());
|
||||
ASSERT (path.endsAt (sibling));
|
||||
ASSERT (path.contains (parent));
|
||||
|
|
@ -295,13 +357,13 @@ namespace test {
|
|||
ASSERT (prefix.endsAt (parent));
|
||||
ASSERT (!prefix.contains (leaf));
|
||||
ASSERT (!prefix.contains (sibling));
|
||||
path.navigate (prefix.getLeaf());
|
||||
path.navigate (prefix.getLeaf()); __SHOWPATH(6)
|
||||
ASSERT (path == prefix);
|
||||
|
||||
// try to navigate to an unconnected location...
|
||||
ScopePath beforeInvalidNavigation = path;
|
||||
Scope unrelatedScope (TestPlacement<> (*new DummyMO));
|
||||
VERIFY_ERROR (INVALID, path.navigate (unrelatedScope) );
|
||||
Scope const& unrelatedScope (fabricate_invalidScope());
|
||||
VERIFY_ERROR (INVALID_SCOPE, path.navigate (unrelatedScope) );
|
||||
ASSERT (path == beforeInvalidNavigation); // not messed up by the incident
|
||||
|
||||
// now explore a completely separate branch....
|
||||
|
|
@ -321,11 +383,12 @@ namespace test {
|
|||
}
|
||||
|
||||
|
||||
|
||||
void
|
||||
clear (ScopePath& path, PPIdx index)
|
||||
{
|
||||
ASSERT (path);
|
||||
PMO rootNode = index->getRoot();
|
||||
PMO& rootNode = index->getRoot();
|
||||
ASSERT (path.getLeaf() != rootNode);
|
||||
|
||||
path.clear();
|
||||
|
|
@ -333,10 +396,10 @@ namespace test {
|
|||
ASSERT (!isnil (path));
|
||||
ASSERT (path.getLeaf() == rootNode);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
/** Register this test class... */
|
||||
LAUNCHER (ScopePath_test, "unit session");
|
||||
|
||||
|
|
|
|||
63
tests/components/proc/mobject/session/test-scope-invalid.hpp
Normal file
63
tests/components/proc/mobject/session/test-scope-invalid.hpp
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
TEST-SCOPE-INVALID.hpp - helper for placement scope and scope stack tests
|
||||
|
||||
Copyright (C) Lumiera.org
|
||||
2010, 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.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
#ifndef MOBJECT_SESSION_TEST_SCOPE_INVALID_H
|
||||
#define MOBJECT_SESSION_TEST_SCOPE_INVALID_H
|
||||
|
||||
|
||||
#include "proc/mobject/placement.hpp"
|
||||
#include "proc/mobject/session/scope.hpp"
|
||||
|
||||
|
||||
|
||||
namespace mobject {
|
||||
namespace session {
|
||||
namespace test {
|
||||
|
||||
|
||||
namespace { // nifty subversive test helper...
|
||||
|
||||
Scope const&
|
||||
fabricate_invalidScope()
|
||||
{ /**
|
||||
* assumed to have identical memory layout
|
||||
* to a Scope object, as the latter is implemented
|
||||
* by a PlacementRef, which in turn is just an
|
||||
* encapsulated Placement-ID
|
||||
*/
|
||||
struct Ambush
|
||||
{
|
||||
/** random ID assumed to be
|
||||
* nowhere in the model */
|
||||
PlacementMO::ID derailed_;
|
||||
};
|
||||
|
||||
static Ambush _kinky_;
|
||||
return *reinterpret_cast<Scope*> (&_kinky_);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}}} // namespace mobject::session::test
|
||||
#endif
|
||||
|
|
@ -41,6 +41,7 @@ namespace test {
|
|||
typedef TestPlacement<DummyMO> PDum;
|
||||
|
||||
typedef std::tr1::shared_ptr<PlacementIndex> PPIdx;
|
||||
typedef ScopeQuery<MObject>::iterator _ScopeIterMO;
|
||||
|
||||
|
||||
|
||||
|
|
@ -67,7 +68,7 @@ namespace test {
|
|||
* Usually, clients would use QueryFocus or ScopeLocator to perform this task,
|
||||
* but for the purpose of testing we're better off to invoke the query directly
|
||||
*/
|
||||
ScopeQuery<MObject>::iterator explore_testScope (PlacementMO const& scopeTop);
|
||||
_ScopeIterMO explore_testScope (PlacementMO const& scopeTop);
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -183,17 +183,22 @@ namespace test{
|
|||
// build the test data sources
|
||||
WrappedList customList(NUM_ELMS);
|
||||
TestSource dedicatedSource(NUM_ELMS);
|
||||
list<int>& rawList(customList.data_);
|
||||
|
||||
IntIter iii (eachEntry (customList));
|
||||
IntIter isi (eachEntry (rawList.begin(), rawList.end()));
|
||||
StrIter cii (IterSource<CStr>::build(dedicatedSource));
|
||||
|
||||
ASSERT (!isnil (iii));
|
||||
ASSERT (!isnil (isi));
|
||||
ASSERT (!isnil (cii));
|
||||
|
||||
pullOut (iii);
|
||||
pullOut (isi);
|
||||
pullOut (cii);
|
||||
|
||||
ASSERT (!iii);
|
||||
ASSERT (!isi);
|
||||
ASSERT (!cii);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1045,12 +1045,17 @@ Meanwhile I've settled down on implementing the [[top-level entities|Timeline]]
|
|||
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">
|
||||
<div title="BindingScopeProblem" modifier="Ichthyostega" modified="201010021417" created="200910181435" tags="SessionLogic spec" changecount="20">
|
||||
<pre>There is some flexibility in the HighLevelModel, allowing to attach the same [[Sequence]] 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//&nbsp; on the current situation and context. Have a state containing the //current path.// &rarr; QueryFocus
|
||||
|
||||
Incidentally, this problem is quite similar to file system navigation involving ''symlinks''.
|
||||
* under which circumstances shall discovery follow symlinks?
|
||||
* where do you get by {{{cd ..}}} &mdash; especially when you went down following a symlink?
|
||||
This leads us to building our solution here to match a similar behaviour pattern, according to the principle of least surprise. That is, disovery shall follow the special [[bindings|BindingMO]] only when requested explicitly, but the current "shell" (QueryFocus) should maintain a virtual/effective path to root scope.
|
||||
|
||||
!!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)
|
||||
|
|
@ -1059,6 +1064,25 @@ We us a 2-layer approach: initially, containment is implemented through a regist
|
|||
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
|
||||
|
||||
!!!!locating a placement
|
||||
constituting the effective logical position of a placement poses sort-of a chicken or egg problem: We need already a logical position to start with. In practice, this is done by recurring on the QueryFocus, which is a stack-like state and automatically follows the access or query operations on the session. //Locating a placement//&nbsp; is done by //navigating the current query focus.//
|
||||
|
||||
!!!!navigating a scope path location
|
||||
As the current query focus stack top always holds a ScopePath, the ''navigating operation'' on ScopePath is the key for managing this logical view onto the "current" location.
|
||||
* first, try to locate the new scope in the same sequence as the current scope, resulting in a common path prefix
|
||||
* in case the new scope belongs to a different sequence, this sequence might be connected to the current one as a meta-clip, again resulting in a common prefix
|
||||
* otherwise use the first possible binding according to the ordering of timelines as a prefix
|
||||
* use the basic containment path as a fallback if no binding exists
|
||||
|
||||
!!{{red{WIP 9/10}}}Deficiencies
|
||||
To buy us some time, analysing and implementing the gory details of scope path navigation and meta-clips was skipped for now.
|
||||
Please note the shortcomings and logical contradictions in the solution currently in code:
|
||||
* the {{{ScopePath::navigate()}}}-function was chosen as the location to implement the translation logic. //But actually this translation logic is missing.//
|
||||
* so basically we're just using the raw paths of the basic containment tree; more specifically, the BindingMO (=Timeline) isn't part of the derived ScopePath
|
||||
* this will result in problems even way before we implement meta-clips (because the Timeline is assumed to provide output routing information) to the Placements
|
||||
* QueryFocus, with the help of ScopeLocator exposes the query services of the PlacementIndex. So actually it's up to the client code to pick the right functions. This might get confusing
|
||||
* The current design rather places the implementation according to the roles of the involved entities, which causes some ping-pong on the implementation level. Especially the ScopeLocator singleton can be accessed multiple times. This is the usual clarity vs. performance tradeoff. Scope resolution is assumed rather to be //not performance critical.//
|
||||
</pre>
|
||||
</div>
|
||||
<div title="BuildProcess" modifier="Ichthyostega" modified="200906071813" created="200706190658" tags="Builder operational img" changecount="30">
|
||||
|
|
@ -1413,7 +1437,7 @@ More often than not, these emerge from immediate solutions, being percieved as e
|
|||
&rarr; [[accessing and integrating configuration queries|ConfigQueryIntegration]]
|
||||
</pre>
|
||||
</div>
|
||||
<div title="ConfigQueryIntegration" modifier="Ichthyostega" modified="201004030043" created="200804070247" tags="overview draft impl img" changecount="23">
|
||||
<div title="ConfigQueryIntegration" modifier="Ichthyostega" modified="201010140133" created="200804070247" tags="overview draft impl img" changecount="24">
|
||||
<pre>* planning to embed a YAP Prolog engine
|
||||
* currently just integrated by a table driven mock
|
||||
* the baseline is a bit more clear by now (4/08)
|
||||
|
|
@ -1441,7 +1465,7 @@ At various places, instead of requiring a fixed set of capabilities, it is possi
|
|||
<br/>
|
||||
<br/>
|
||||
|
||||
Access point is the interface {{{ConfigRules}}}, which allowes to resolve a ConfigQuery resulting in an object with properties configured such as to fulfill the query. This whole subsystem employes quite some generic programming, because actually we don't deal with "objects", but rather with similar instantiations of the same functionality for a collection of different object types. For the purpose of resolving these queries, the actual kind of object is not so much of importance, but on the caller sinde, of course we want to deal with the result of the queries in a typesafe manner.
|
||||
Access point is the interface {{{ConfigRules}}}, which allowes to resolve a ConfigQuery resulting in an object with properties configured such as to fulfill the query. This whole subsystem employes quite some generic programming, because actually we don't deal with "objects", but rather with similar instantiations of the same functionality for a collection of different object types. For the purpose of resolving these queries, the actual kind of object is not so much of importance, but on the caller side, of course we want to deal with the result of the queries in a typesafe manner.
|
||||
Examples for //participating object kinds// are [[pipes|Pipe]], [[processing patterns|ProcPatt]], effect instances, [[tags|Tag]], [[labels|Label]], [[automation data sets|AutomationData]],...
|
||||
@@clear(right):display(block):@@
|
||||
For this to work, we need each of the //participating object types// to provide the implementation of a generic interface {{{TypeHandler}}}, which allows to access actual C/C++ implementations for the predicates usable on objects of this type within the Prolog rules. The implementation has to make sure that, alongside with each config query, there are additional //type constraints// to be regarded. For example, if the client code runs a {{{Query<Pipe>}}}, an additional //type guard// (implemented by a predicate {{{type(pipe)}}} has to be inserted, so only rules and facts in accordance with this type will be used for resolution.
|
||||
|
|
@ -1531,10 +1555,10 @@ As we don't have a Prolog interpreter on board yet, we utilize a mock store with
|
|||
{{{default(Obj)}}} is a predicate expressing that the object {{{Obj}}} can be considered the default setup under the given conditions. Using the //default// can be considered as a shortcut for actually finding a exact and unique solution. The latter would require to specify all sorts of detailed properties up to the point where only one single object can satisfy all conditions. On the other hand, leaving some properties unspecified would yield a set of solutions (and the user code issuing the query had to provide means for selecting one soltution from this set). Just falling back on the //default// means that the user code actually doesn't care for any additional properties (as long as the properties he //does// care for are satisfied). Nothing is said specifically on //how//&nbsp; this default gets configured; actually there can be rules //somewhere,// and, additionally, anything encountered once while asking for a default can be re-used as default under similar circumstances.
|
||||
&rarr; [[implementing defaults|DefaultsImplementation]]</pre>
|
||||
</div>
|
||||
<div title="DesignDecisions" modifier="Ichthyostega" modified="200910312026" created="200801062209" tags="decision design discuss Concepts" changecount="25">
|
||||
<div title="DesignDecisions" modifier="Ichthyostega" modified="201009240010" created="200801062209" tags="decision design discuss Concepts" changecount="28">
|
||||
<pre>Along the way of working out various [[implementation details|ImplementationDetails]], decisions need to be made on how to understand the different facilities and entities and how to tackle some of the problems. This page is mainly a collection of keywords, summaries and links to further the discussion. And the various decisions should allways be read as proposals to solve some problem at hand...
|
||||
|
||||
''Everything is an object'' &mdash; of course, that's a //no-brainer,// todays. Rather, important is what is not "an object", meaning it can't be arranged arbitrarily
|
||||
''Everything is an object'' &mdash; yes of course, that's a //no-brainer,// todays. Rather, important is to note what is not "an object", meaning it can't be arranged arbitrarily
|
||||
* we have one and only one global [[Session]] which directly contains a collection of multiple [[Sequences|Sequence]] and is associated with a globally managed collection of [[assets|Asset]]. We don't utilise scoped variables here (no "mandantisation"); if e.g. a media has been //opened,// it is just plain //globally known//&nbsp; as asset.
|
||||
* the [[knowledge base|ConfigRules]] is just available globally. Obviously, the session gets a chance to install rules into this knowledge base, but we don't stress ownership here.
|
||||
* we have a [[Fixture]] which acts as isolation layer towards the render engine and is (re)built automatically.
|
||||
|
|
@ -1546,6 +1570,8 @@ An [[Sequence]] is just a collection of configured and placed objects (and has n
|
|||
|
||||
Actual ''media data and handling'' is abstracted rigorously. Media is conceived as being stream-like data of distinct StreamType. When it comes to more low-level media handling, we build on the DataFrame abstraction. Media processing isn't the focus of Lumiera; we organise the processing but otherwise ''rely on media handling libraries.'' In a similar vein, multiplicity is understood as type variation. Consequently, we don't build an audio and video "section" and we don't even have audio tracks and video tracks. Lumiera uses tracks and clips, and clips build on media, but we're able to deal with multichannel mixed-typed media.
|
||||
|
||||
Lumiera is not a connection manager, it is not an audio-visual real time instrument, and it doesn't aim at running presentations. It's an ''environment for assembling and building up'' something (an edit, a session, a piece of media work). This decision is visible at various levels and contexts, like a reserved attitude towards hardware acceleration (it //will// be supported, but reliable proxy editing has a higher priority), or the decision, not to incorporate system level ports and connections directly into the session model (they are mapped to [[output designations|OutputDesignation]] rather)
|
||||
|
||||
''State'' is rigorously ''externalized'' and operations are to be ''scheduled'', to simplify locking and error handling. State is either treated similar to media stream data (as addressable and cacheable data frame), or is represented as "parameter" to be served by some [[parameter provider|ParamProvider]]. Consequently, [[Automation]] is just another kind of parameter, i.e. a function &mdash; how this function is calculated is an encapsulated implementation detail (we don't have "bezier automation", and then maybe a "linear automation", a "mask automation" and yet another way to handle transitions)
|
||||
|
||||
Deliberately there is an limitaion on the flexibility of what can be added to the system via Plugins. We allow configuration and parametrisation to be extended and we allow processing and data handling to be extended, but we disallow extensions to the fundamental structure of the system by plugins. They may provide new implementations for already known subsystems, but they can't introduce new subsystems not envisioned in the general design of the application.
|
||||
|
|
@ -1731,15 +1757,20 @@ Some further details
|
|||
* a special case of this factory use is the [[Singleton]] factory, which is used a lot within the Proy-Layer code
|
||||
</pre>
|
||||
</div>
|
||||
<div title="Fixture" modifier="Ichthyostega" modified="201007110035" created="200706220324" tags="def" changecount="9">
|
||||
<div title="Fixture" modifier="Ichthyostega" modified="201007160052" created="200706220324" tags="def" changecount="10">
|
||||
<pre>a specially configured sequence list
|
||||
* all MObjects have their position, length and configuration set up ready for rendering.
|
||||
* any nested sequences (or other kinds of indirections) have been resolved.
|
||||
* every MObject is associated with an ExplicitPlacement, which declares a fixed position (Time, [[Pipe-ID|OutputDesignation]])
|
||||
* these ~ExplicitPlacements are contained in a ordered List, sometimes denoted as the //effective timeline.//
|
||||
* besides, there is a collection of all effective, possibly externally visible [[model ports|ModelPorts]]
|
||||
* besides, there is a collection of all effective, possibly externally visible [[model ports|ModelPort]]
|
||||
|
||||
As the builder and thus render engine //only consults the fixture,// while all editing operations finally propagate to the fixture as well, we get an isolation layer between the high level part of the Proc layer (editing, object manipulation) and the render engine. Creating the Fixture can be seen as a preprocessing step to simplify the build process. For this reason, the process of [[(re)building the fixture|PlanningBuildFixture]] has been designed together with the [[Builder]]</pre>
|
||||
As the builder and thus render engine //only consults the fixture,// while all editing operations finally propagate to the fixture as well, we get an isolation layer between the high level part of the Proc layer (editing, object manipulation) and the render engine. Creating the Fixture can be seen as a preprocessing step to simplify the build process. For this reason, the process of [[(re)building the fixture|PlanningBuildFixture]] has been designed together with the [[Builder]]
|
||||
|
||||
!{{red{WIP}}} Structure of the fixture
|
||||
The fixture is like a grid, where one dimension is given by the [[model ports|ModelPort]], and the other dimension extends in time. The time axis is grouped in segments of constant structure.
|
||||
A problem yet to be solved is how the fixture relates to the mulitude of top-level timelines, without generating a too fine grained segmentation.
|
||||
</pre>
|
||||
</div>
|
||||
<div title="ForwardIterator" modifier="Ichthyostega" modified="200912190027" created="200910312114" tags="Concepts def spec" changecount="17">
|
||||
<pre>The situation focussed by this concept is when an API needs to expose a sequence of results, values or objects, instead of just yielding a function result value. As the naive solution of passing an pointer or array creates coupling to internals, it was superseded by the ~GoF [[Iterator pattern|http://en.wikipedia.org/wiki/Iterator]]. Iteration can be implemented by convention, polymorphically or by generic programming; we use the latter approach.
|
||||
|
|
@ -1863,17 +1894,29 @@ For this Lumiera design, we could consider making GOP just another raw media dat
|
|||
&rarr;see in [[Wikipedia|http://en.wikipedia.org/wiki/Group_of_pictures]]
|
||||
</pre>
|
||||
</div>
|
||||
<div title="GlobalPipe" modifier="Ichthyostega" modified="201007120219" created="201007110200" tags="Model design spec draft" changecount="5">
|
||||
<div title="GlobalPipe" modifier="Ichthyostega" modified="201007190107" created="201007110200" tags="Model design spec draft" changecount="22">
|
||||
<pre>Each [[Timeline]] has an associated set of global [[pipes|Pipe]] (global busses), similar to the subgroups of a sound mixing desk.
|
||||
In the typical standard configuration, there is (at least) a video master and a sound master pipe. Like any pipe, ingoing connections attach to the input side, attached effects form a chain, where the last node acts as exit node. The ~Pipe-ID of such a global bus can be used to route media streams, allowing the global pipe to act as a summation bus bar.
|
||||
|
||||
!{{red{WIP}}} Design problem with global Pipes
|
||||
actually building up the implementation of global pipes seems to pose a rather subtle design problem: it is difficult to determine how to do it //right.//
|
||||
To start with, we need the ability to attach effects to global pipes. There is already an obvious way how to attach effects to clips (=local pipes), and thus it's desirable to do handle it the same way for global pipes. At least there should be a really good reason //not//&nbsp; to do it the same way. Thus, we're going to attach these effects by placement into the scope of another placed MObject. And, moreover, this other object should be part of the HighLevelModel's tree, to allow using the PlacementIndex as implementation. So this reasoning brings us to inventing or postulating some kind of object, without having any existing concept to justify the existence of the corresponding class or shaping its properties on itself. Which means &mdash; from a design view angle &mdash; we're entering slippery ground.
|
||||
!!~Model-A: dedicated object per pipe
|
||||
Just for the sake of symmetry, for each global pipe we'd attach some ~MObject as child of the BindingMO representing the timeline. If no further specific properties or functionality is required, we could use Track objects, which are generally used as containers within the model. Individual effects would then be attached as children, while output routing would be done within the attaching placement, the same way as it's done with clips or tracks in general. As the pipe asset itself already stores a StreamType reference, all we'd need is some kind of link to the pipe asset or pipe-ID, and maybe some façade functions within the binding to support the handling.
|
||||
!!~Model-B: attaching to the container
|
||||
Acknowledging the missing justification, we could instead use //something to attach// &mdash; and actually handle the real association elsewhere. The obvious "something" in this case would be the BindingMO, which already acts as implementation of the timeline (which is a façade asset). Thus, for this approach, the bus-level effects would be attached as direct children of the {{{Placement<BindingMO>}}}, just for the sake of beeing attached and stored within the session, with an additional convention for the actual ordering and association to a specific pipe. The Builder then would rather query the ~BindingMO to discover and build up the implementation of the global pipes in terms of the render nodes.
|
||||
To start with, we need the ability to attach effects to global pipes. There is already an obvious way how to attach effects to clips (=local pipes), and thus it's desirable to handle it the same way for global pipes. At least there should be a really good reason //not//&nbsp; to do it the same way. Thus, we're going to attach these effects by placement into the scope of another placed MObject. And, moreover, this other object should be part of the HighLevelModel's tree, to allow using the PlacementIndex as implementation. So this reasoning brings us to re-using or postulating some kind of object, while lacking a point or reference //outside this design considerations//&nbsp; to justify the existence of the corresponding class or shaping its properties on itself. Which means &mdash; from a design view angle &mdash; we're entering slippery ground.
|
||||
!!!~Model-A: dedicated object per pipe
|
||||
Just for the sake of symmetry, for each global pipe we'd attach some suitable ~MObject as child of the BindingMO representing the timeline. If no further specific properties or functionality is required, we could use Track objects, which are generally used as containers within the model. Individual effects would then be attached as children, while output routing could be specified within the attaching placement, the same way as it's done with clips or tracks in general. As the pipe asset itself already stores a StreamType reference, all we'd need is some kind of link to the pipe asset or pipe-ID, and maybe façade functions within the binding to support the handling.
|
||||
!!!~Model-B: attaching to the container
|
||||
Acknowledging the missing justification, we could instead use //just something to attach// &mdash; and actually handle the real association elsewhere. The obvious "something" in this case would be the BindingMO, which already acts as implementation of the timeline (which is a façade asset). Thus, for this approach, the bus-level effects would be attached as direct children of the {{{Placement<BindingMO>}}}, just for the sake of beeing attached and stored within the session, with an additional convention for the actual ordering and association to a specific pipe. The Builder then would rather query the ~BindingMO to discover and build up the implementation of the global pipes in terms of the render nodes.
|
||||
|
||||
!!!Comparision
|
||||
While there might still be some compromises or combined solutions &mdash; to support the decision, the following table detailes the handling in each case
|
||||
|>| !~Model-B|!~Model-A |
|
||||
|Association: | by plug in placement|by scope |
|
||||
|Output: | entry in routing table|by plug in placement |
|
||||
|Ordering: |>| stored in placement |
|
||||
|Building: | sort children by plug+order<br/>query output from routing|build like a clip |
|
||||
|Extras: | ? |tree-like bus arrangement,<br/> multi-stream bus |
|
||||
|
||||
So through this detailed comparison ''~Model-A looks favourable'': while the other model requires us to invent a good deal of the handling specifically for the global pipes, the former can be combined from patterns and solutions already used in other parts of the model, plus it allows some interesting extensions.
|
||||
On a second thought, the fact that the Bus-~MObject is rather void of any specific meaning doesn't weight so much: As the Builder is based on the visitor pattern, the individual objects can be seen as //algebraic data types.// Besides, there is at least one little bit of specific functionality: a Bus object actually needs to //claim//&nbsp; to be the OutputDesignation, by referring to the same ~Pipe-ID used in other parts of the model to request output routing to this Bus. Without this match on both ends, an ~OutputDesignation may be mentioned at will, but no connection whatsoever will happen.
|
||||
</pre>
|
||||
</div>
|
||||
<div title="GuiCommunication" modifier="Ichthyostega" modified="200812050555" created="200812050543" tags="GuiIntegration draft" changecount="2">
|
||||
|
|
@ -2809,7 +2852,7 @@ The operation point is provided by the current BuilderMould and used by the [[pr
|
|||
This is possible because the operation point has been provided (by the mould) with informations about the media stream type to be wired, which, together with information accessible at the the [[render node interface|ProcNode]] and from the [[referred processing assets|ProcAsset]], with the help of the [[connection manager|ConManager]] allows to figure out what's possible and how to do the desired connections. Additionally, in the course of deciding about possible connections, the PathManager is consulted to guide strategic decisions regarding the [[render node configuration|NodeConfiguration]], possible type conversions and the rendering technology to employ.
|
||||
</pre>
|
||||
</div>
|
||||
<div title="OutputDesignation" modifier="Ichthyostega" modified="201007110046" created="201006220126" tags="Model draft design discuss" changecount="24">
|
||||
<div title="OutputDesignation" modifier="Ichthyostega" modified="201009242320" created="201006220126" tags="Model draft design discuss" changecount="42">
|
||||
<pre>__6/2010__: an ever recurring (and yet unsolved) problem in the design of Luimiera's ~Proc-Layer is how to refer to output destinations, and how to organise them.
|
||||
To get ahead with this problem, I'll start collecting observations, relations and drafts on this tiddler.
|
||||
|
||||
|
|
@ -2830,6 +2873,7 @@ To get ahead with this problem, I'll start collecting observations, relations an
|
|||
|
||||
!Conclusions
|
||||
* there are //direct, indirect//&nbsp; and //relative//&nbsp; referrals to an output designation
|
||||
* //indirect// means to derive the destination transitively (looking at the destination's output designation and so on)
|
||||
* //relative// in this context means that we refer to "the N^^th^^ of this kind" (e.g. the second video out)
|
||||
* we need to attach some metadata with an output; especially we need an associated StreamType
|
||||
* output designation is structured into several //levels://
|
||||
|
|
@ -2838,15 +2882,28 @@ To get ahead with this problem, I'll start collecting observations, relations an
|
|||
** there might be //master pipes//
|
||||
** finally, there is the hardware output or the distinct channel within the rendered result &mdash; never to be referred explicitly
|
||||
!!!relation to Pipes
|
||||
in almost every case mentioned above, the output designation is identical with the starting point of a [[Pipe]]. This might be a global pipe or a ClipSourcePort. Thus it sounds reasonable to use pipe-~IDs directly as output designation. Pipes, as an accountable entity (=asset) just //leap into existence by being referred.// On the other hand, the //actual// pipe is a semantic concept, a specific structural arrangement of objects. Basically it means that some object acts as attachment point and thereby //claims//&nbsp; to be the entrance side of a pipe, while other processor objects chain up in sequence.
|
||||
in almost every case mentioned above, the output designation is identical with the starting point of a [[Pipe]]. This might be a global pipe or a ClipSourcePort. Thus it sounds reasonable to use pipe-~IDs directly as output designation. Pipes, as an accountable entity (=asset) just //leap into existence by being referred.// On the other hand, the //actual//&nbsp; pipe is a semantic concept, a specific structural arrangement of objects. Basically it means that some object acts as attachment point and thereby //claims//&nbsp; to be the entrance side of a pipe, while other processor objects chain up in sequence.
|
||||
!!!system outputs
|
||||
System level output connections are the exception to the above rule. There is no pipe at an external port, and these externally visible connection points can appear and disappear, driven by external conditions. So the question is if system outputs shall be directly addressable at all as output designation? Rather, there should be a separate OutputManagement to handle external outputs and displays, both in windows or full screen.
|
||||
System level output connections seem to be an exception to the above rule. There is no pipe at an external port, and these externally visible connection points can appear and disappear, driven by external conditions. But the question is if system outputs shall be directly addressable at all as output designation? Generally speaking, Lumiera is not intended to be a system wide connection manager or a real time performance software. Thus it's advisable to refrain from direct referrals to system level connections from within the model. Rather, there should be a separate OutputManagement to handle external outputs and displays, both in windows or full screen. So these external outputs become rather a matter of application configuration &mdash; and for all the other purposes we're free to ''use pipe-~IDs as output designation''.
|
||||
!!!consequences of mentioning an output designation
|
||||
The immediate consequence is that an connection to this designation is wired, using an appropriate [[processing pattern|ProcPatt]]. A further consequence is for the mentioned output designation to appear as exit node of the built render nodes network. (not sure if thats always the case, or only when the correponding pipe is an end point without further outgoing connections)
|
||||
The immediate consequence is that a [[Pipe]] with the given ID exists as an accountable entity. Only if &mdash; additionally &mdash; a suitable object within the model //claims to root this pipe,// a connection to this designation is wired, using an appropriate [[processing pattern|ProcPatt]]. A further consequence then is for the mentioned output designation to become a possible candidate for a ModelPort, an exit node of the built render nodes network. By default, only those designations without further outgoing connections actually become active model ports (but an existing and wired pipe exit node can be promoted to a model port explicitly).
|
||||
&rarr; OutputManagement
|
||||
|
||||
!Mapping of output designations
|
||||
An entire sequence can be embedded within another sequence as a VirtualClip. While for a simple clip there is a Clip-~MObject placed into the model, holding an reference to a media asset, in case of a virtual clip an BindingMO takes on the role of the clip object. Note that BindingMO also acts as [[Timeline]] implementation &mdash; indeed the same sequence might be bound simultaneously as a timeline and into a virtual clip. This flexibility creates a special twist, as the contents of this sequence have no way of finding out if they're used as timeline or embedded virtual clip. So parts of this sequence might mention a OutputDesignation which, when using the sequence as virtual clip, needs to be translated into a virtual media channel, which can be treated in the same fashion as any channel (video, audio,...) found within real media. Especially, a new output designation has to be derived in a sensible way, which largely depends on how the original output designation has been specified
|
||||
* a direct output designation specicfiation from within the sequence is captured and translated into a summation pipe at a position corresponding to the global pipes when using the same sequence as timeline. The output of this summation pipe is treated like a media channel
|
||||
** now, if a distinct output designation can be determined at this placement of the virtual clip, it will be used for the further routing
|
||||
** otherwise, the routing will be done as if the original output designation was given at this placement of the virtual clip.
|
||||
* a relative output designation (the N^^th^^ channel of this kind) is just carried over to the enclosing scope.
|
||||
|
||||
!Using output designations
|
||||
While actually data frames are //pulled,// on a conceptual level data is assumed to "flow" through the pipes from source to output. This conceptual ("high level") model of data flow is comprised of the pipes (which are rather rigid), and flexible interconnections. The purpose of an output designation is to specify where the data should go, especially through these flexible interconnections. Thus, when reaching the exit point of a pipe, an output designation will be //queried.// Finding a suitable output designation involves two parts:
|
||||
* first of all, we need to know //what to route// &mdash; kind of the traits of the data. This is given by the //current pipe.//
|
||||
* then we'll need to determine an output designation //suitable for this data.// This is given by a "Plug" (WiringRequest) in the placement, and may be derived.
|
||||
As both of these specifications are given by [[Pipe]]-~IDs, the actual designation information may be reduced. Much can be infered from the circumstances, because any pipe includes a StreamType, and an output designation for an incompatible stream type (e.g. and audio output when the pipe currently in question deals with video) is irrelevant.
|
||||
</pre>
|
||||
</div>
|
||||
<div title="OutputManagement" modifier="Ichthyostega" modified="201007110112" created="201007090155" tags="Model Rendering Player spec draft" changecount="8">
|
||||
<div title="OutputManagement" modifier="Ichthyostega" modified="201007190254" created="201007090155" tags="Model Rendering Player spec draft" changecount="17">
|
||||
<pre>//writing down some thoughts//
|
||||
|
||||
* ruled out the system outputs as OutputDesignation.
|
||||
|
|
@ -2861,6 +2918,11 @@ From the implementation side, the only interesting exit nodes are the ones to be
|
|||
* __rendering__ happens immediately at the output of a GlobalPipe (i.e. at a [[Timeline]], which is top-level)
|
||||
* __playback__ always happens at a viewer element
|
||||
|
||||
!Attaching and mapping of exit nodes
|
||||
[[Output designations|OutputDesignation]] are created by using a [[Pipe]]-ID and &mdash; at the same time &mdash; by some object //claiming to root this pipe.// The applicability of this pattern is figured out dynamically while building the render network, resulting in a collection of model ports as part of the current [[Fixture]]. A RenderProcess can be started to pull from these active exit points of a given timeline. Besides, when the timeline enclosing these model ports is [[connected to a viewer|ViewerPlayConnection]], an //output network is built,// to allow hooking exit points to the viewer component. Both cases encompass a mapping of exit nodes to actual output channels. Usually, this mapping relies on relative addressing of the output sinks, starting connections at the "first of each kind".
|
||||
|
||||
We should note that in both cases this mapping operation is controlled and driven by the output side of the connection: A viewer has fixed output capabilities, and rendering targets a specific container format, again with fixed and pre-settled channel configuration (when configurting a render process, it might be necessary to account for //possible kinds of output streams,// so to provide a sensible pre-selection of possible output container formats for the user to select from). Thus, as a starting point, we'll create a default configured mapping, assigning channels in order. This mapping then should be exposed to modification and tweaking by the user. For rendering, this is part of the render options dialog, while in case of a viwer connection, a switch board is created to allow modifying the default mapping.
|
||||
|
||||
!Connection to external outputs
|
||||
External output destinations are never addressed directly from within the model. This is an design decision. Rather, model parts connect to an OutputDesignation, and these in turn may be [[connected to a viewer element|ViewerPlayConnection]]. At this point, related to the viewer element, there is a mapping to external destination(s): for images, a viewer typically has an implicit, natural destination (read, there is a corresponding viewer window or widget), while for sound we use an mapping rule, which could be overridden locally in the viewer.
|
||||
|
||||
|
|
@ -3456,7 +3518,7 @@ The GUI can connect the viewer(s) to some pipe (and moreover can use [[probe poi
|
|||
|
||||
</pre>
|
||||
</div>
|
||||
<div title="PipeHandling" modifier="Ichthyostega" modified="201006270108" created="200801101352" tags="spec" changecount="15">
|
||||
<div title="PipeHandling" modifier="Ichthyostega" modified="201007210309" created="200801101352" tags="spec" changecount="16">
|
||||
<pre>!Identification
|
||||
Pipes are distinct objects and can be identified by their asset ~IDs. Besides, as for all [[structural assets|StructAsset]] there are extended query capabilities, including a symbolic pipe-id and a media (stream) type id. Any pipe can accept and deliver exactly one media stream kind (which may be inherently structured though, e.g. spatial sound systems or stereoscopic video)
|
||||
|
||||
|
|
@ -3467,7 +3529,7 @@ Pipe assets are created automatically by being used and referred. Each [[Timelin
|
|||
Deleting a Pipe is an advanced operation, because it includes finding and "detaching" all references, otherwise the pipe will leap back into existence immediately. Thus, global pipe entries in the Session and pipe references in [[locating pins|LocatingPin]] within any placement have to be removed, while clips using a given source port will be disabled. {{red{todo: implementation deferred}}}
|
||||
|
||||
!using Pipes
|
||||
there is not much you can do directly with a pipe asset. It is an point of reference, after all. Any connection to some pipe is only temporarily done by a placement in some part of the timeline, so it isn't stored with the pipe. You can edit the (user visible) description an you can globally disable a pipe asset. The pipe's ID and media stream type of course are fixed, because any connection and referral (via the asset ID) is based on them. Later on, we should provide a {{{rewire(oldPipe, newPipe)}}} to search any ref to the {{{oldPipe}}} and try to rewrite it to use the {{{newPipe}}}, possibly with a new media stream type.
|
||||
there is not much you can do directly with a pipe asset. It is an point of reference, after all. Any connection to some pipe is only temporarily done by a placement in some part of the timeline (&rarr; OutputDesignation), so it isn't stored with the pipe. You can edit the (user visible) description an you can globally disable a pipe asset. The pipe's ID and media stream type of course are fixed, because any connection and referral (via the asset ID) is based on them. Later on, we should provide a {{{rewire(oldPipe, newPipe)}}} to search any ref to the {{{oldPipe}}} and try to rewrite it to use the {{{newPipe}}}, possibly with a new media stream type.
|
||||
Pipes are integrated with the [[management of defaults|DefaultsManagement]]. For example, any pipe implicitly uses some [[processing pattern|ProcPatt]] &mdash; it may default to the empty pattern. This way, any kind of standard wiring might be applied to the pipes (e.g a fader for audio, similar to the classic mixing consoles). This //is // a global property of the pipe, but &mdash; contrary to the stream type &mdash; this pattern may be switched {{red{really?}}}
|
||||
</pre>
|
||||
</div>
|
||||
|
|
@ -3939,7 +4001,7 @@ The stack of scopes must not be confused with the ScopePath. Each single frame o
|
|||
The full implementation of this scope navigation is tricky, especially when it comes to determining the relation of two positions. It should be ''postponed'' and replaced by a ''dummy'' (no-op) implementation for the first integration round.
|
||||
</pre>
|
||||
</div>
|
||||
<div title="QueryFocusStack" modifier="Ichthyostega" modified="200911220509" created="200910200158" tags="SessionLogic spec operational" changecount="8">
|
||||
<div title="QueryFocusStack" modifier="Ichthyostega" modified="201009300225" created="200910200158" tags="SessionLogic spec operational" changecount="9">
|
||||
<pre>The ScopeLocator uses a special stack of ScopePath &raquo;frames&laquo; to maintain the //current focus.//
|
||||
What is the ''current'' QueryFocus and why is it necessary? There is a state-dependent part involved, inasmuch the effective ScopePath depends on how the invoking client has navigated the //current location// down into the HighLevelModel structures. Especially, when a VirtualClip is involved, there can be discrepancies between the paths resulting when descending down through different paths. (See &rarr; BindingScopeProblem).
|
||||
|
||||
|
|
@ -3949,7 +4011,7 @@ Thus, doing something with the current location, and especially descending or qu
|
|||
As long as client code is just interested to use the current query location, we can provide a handle referring to it. But when a query needs to be run without side effect on the current location, we //push//&nbsp; it aside and start using a new QueryFocus on top, which starts out at a new initial location. Client code again gets a handle (smart-ptr) to this location, and additionally may access the new //current location.// When all references are out of scope and gone, we'll drop back to the focus put aside previously.
|
||||
|
||||
!implementation of ref-counting and clean-up
|
||||
Actually, client code should use QueryFocus instances as frontend to access this &raquo;current focus&laquo;. Each ~QueryFocus instance incorporates a smart-ptr. But as in this case we're not managing objects allocated somewhere, we use an {{{boost::intrusive_ptr}}} and maintain the ref-count immediately within the target objects to be managed. These target objects are ScopePath instances and are living within the QueryFocusStack, which in turn in managed by the ScopeLocator singleton (see the UML diagram &rarr;[[here|QueryFocus]]). We use an (hand-written) stack implementation to ensure the memory locations of these ScopePath &raquo;frames&laquo; remain valid (and also to help with providing strong exception guarantees). The stack is aware of these ref-count and takes it into account on performing the {{{pop_unused()}}} operation: any unused frame on top will be evicted, stopping at the first frame still in use (which may be even just the old top). This cleanup also happens automatically when accessing the current top, re-initialising an potentially empty stack with a default-constructed new frame if necessary. This way, just accessing the stack top always yields the ''current focus location'', which thereby is //defined as the most recently used focus location still referred.//
|
||||
Actually, client code should use QueryFocus instances as frontend to access this &raquo;current focus&laquo;. Each ~QueryFocus instance incorporates a smart-ptr. But as in this case we're not managing objects allocated somewhere, we use an {{{boost::intrusive_ptr}}} and maintain the ref-count immediately within the target objects to be managed. These target objects are ScopePath instances and are living within the QueryFocusStack, which in turn is managed by the ScopeLocator singleton (see the UML diagram &rarr;[[here|QueryFocus]]). We use an (hand-written) stack implementation to ensure the memory locations of these ScopePath &raquo;frames&laquo; remain valid (and also to help with providing strong exception guarantees). The stack is aware of these ref-count and takes it into account on performing the {{{pop_unused()}}} operation: any unused frame on top will be evicted, stopping at the first frame still in use (which may be even just the old top). This cleanup also happens automatically when accessing the current top, re-initialising an potentially empty stack with a default-constructed new frame if necessary. This way, just accessing the stack top always yields the ''current focus location'', which thereby is //defined as the most recently used focus location still referred.//
|
||||
|
||||
!concurrency
|
||||
This concept deliberately ignores parallelism. But, as the current path state is already encapsulated (and ref-counting is in place), the only central access point is to reach the current scope. Instead of using a plain-flat singleton here, this access can easily be routed through thread local storage.
|
||||
|
|
@ -4353,8 +4415,8 @@ __see also__
|
|||
&rarr; the protocol [[how to operate the nodes|NodeOperationProtocol]]
|
||||
</pre>
|
||||
</div>
|
||||
<div title="RenderProcess" modifier="Ichthyostega" modified="200806130009" created="200706190705" tags="Rendering operational" changecount="27">
|
||||
<pre>For each segment (of the effective timeline), there is a Processor holding the exit node(s) of a processing network, which is a "Directed Acyclic Graph" of small, preconfigured, stateless [[processing nodes|ProcNode]]. This network is operated according to the ''pull principle'', meaning that the rendering is just initiated by "pulling" output from the exit node, causing a cascade of recursive downcalls. Each node knows its predecessor(s) an can pull the necessary input from there. Consequently, there is no centralized "engine object" which may invoke nodes iteratively or table driven &mdash; rather, the rendering can be seen as a passive service provided for the backend, which may pull from the exit nodes at any time, in any order (?), and possibly multithreaded.
|
||||
<div title="RenderProcess" modifier="Ichthyostega" modified="201007170307" created="200706190705" tags="Rendering operational" changecount="28">
|
||||
<pre>For each segment (of the effective timeline), there is a Processor holding the exit node(s) of a processing network, which is a "Directed Acyclic Graph" of small, preconfigured, stateless [[processing nodes|ProcNode]]. This network is operated according to the ''pull principle'', meaning that the rendering is just initiated by "pulling" output from the exit node, causing a cascade of recursive downcalls or prerequisite calculations to be scheduled as separate jobs. Each node knows its predecessor(s), thus the necessary input can be pulled from there. Consequently, there is no centralized "engine object" which may invoke nodes iteratively or table driven &mdash; rather, the rendering can be seen as a passive service provided for the backend, which may pull from the exit nodes at any time, in any order (?), and possibly multithreaded.
|
||||
All State necessary for a given calculation process is encapsulated and accessible by a StateProxy object, which can be seen as the representation of "the process". At the same time, this proxy provides the buffers holding data to be processed and acts as a gateway to the backend to handle the communication with the Cache. In addition to this //top-level State,// each calculation step includes a small [[state adapter object|StateAdapter]] (stack allocated), which is pre-configured by the builder and serves the purpose to isolate the processing function from the detals of buffer management.
|
||||
|
||||
|
||||
|
|
@ -4394,10 +4456,13 @@ In the general case, this user visible high-level-model of the [[objects|MObject
|
|||
While there //is// a "current state" involved, the effect of concurrent access deliberately remains unspecified, because access is expected to be serialised on a higher level. If this assumption were to break, then probably the ScopeLocator would involve some thread local state.
|
||||
</pre>
|
||||
</div>
|
||||
<div title="ScopePath" modifier="Ichthyostega" modified="201001070848" created="200911202124" tags="def spec" changecount="3">
|
||||
<div title="ScopePath" modifier="Ichthyostega" modified="201010011555" created="200911202124" tags="def spec" changecount="8">
|
||||
<pre>The sequence of nested [[placement scopes|PlacementScope]] leading from the root (global) scope down to a specific [[Placement]] is called ''scope path''. Ascending this path yields all the scopes to search or query in proper order to be used when resolving some attribute of placement. Placements use visibility rules comparable to visibility of scoped definitions in common programming languages or in cascading style sheets, where a local definition can shadow a global one. In a similar way, properties not defined locally may be resolved by querying up the sequence of nested scopes.
|
||||
|
||||
A scope path is a sequence of scopes, where each scope is implemented by a PlacementRef pointing to the &raquo;scope top&laquo;, i.e. the placement in the session //constituting this scope.// Each Placement is registered with the session as belonging to a scope, and each placement can contain other placements and thus form a scope. Thus, the ''leaf'' of this path can be considered the current scope. In addition to some search and query functions, a scope path has the ability to ''navigate'' to a given target scope, which must be reachable by ascending and descending into the branches of the overall tree or DAG. Navigating changes the current path. ({{red{WIP 11/09}}} navigation to scopes outside the current path and the immediate children of the current leaf is left out for now. We'll need it later, when actually implementing [[meta-clips|VirtualClip]].)
|
||||
A scope path is a sequence of scopes, where each scope is implemented by a PlacementRef pointing to the &raquo;scope top&laquo;, i.e. the placement in the session //constituting this scope.// Each Placement is registered with the session as belonging to a scope, and each placement can contain other placements and thus form a scope. Thus, the ''leaf'' of this path can be considered the current scope. In addition to some search and query functions, a scope path has the ability to ''navigate'' to a given target scope, which must be reachable by ascending and descending into the branches of the overall tree or DAG. Navigating changes the current path. ({{red{WIP 11/09}}} navigation to scopes outside the current path and the immediate children of the current leaf is left out for now. We'll need it later, when actually implementing meta-clips. &rarr; see BindingScopeProblem)
|
||||
|
||||
!access path and session structure
|
||||
ScopePath represents an ''effective scoping location'' within the model &mdash; it is not necessarily identical to the storage structure (&rarr; PlacementIndex) used to organise the session. While similar in most cases, binding a sequence into multiple timelines or meta-clips will cause the internal and the logical (effective) structure to digress (&rarr; BindingScopeProblem). An internal singleton service, the ScopeLocator is used to constitute the logical (effective) position for a given placement (which in itself defines a position in the session datastructure). This translation involves a [[current focus|QueryFocus]] remembering the access path used to reach the placement in question. Actually, this translation is built on top of the //navigation//-Operation of ScopePath, which thus forms the foundation to provide such a logical view on the "current" location.
|
||||
|
||||
!Operations
|
||||
* the default scope path contains just the root (of the implicit PlacementIndex, i.e. usually the root of the model in the session)
|
||||
|
|
@ -6378,7 +6443,7 @@ Just an ''registration scheme'' should be implemented right now, working complet
|
|||
see [[implementation planning|TypedLookup]]
|
||||
</pre>
|
||||
</div>
|
||||
<div title="TypedLookup" modifier="Ichthyostega" modified="201006250025" created="201004031607" tags="Rules spec impl draft" changecount="18">
|
||||
<div title="TypedLookup" modifier="Ichthyostega" modified="201010140129" created="201004031607" tags="Rules spec impl draft" changecount="19">
|
||||
<pre>TypedID is a registration service to associate object identities, symbolic identifiers and types. It acts as frontend to the TypedLookup service within Proc-Layer, at the implementation level. While TypedID works within a strictly typed context, this type information is translated into an internal index on passing over to the implementation, which manages a set of tables containing base entries with an combined symbolic+hash ID, plus an opaque buffer. Thus, the strictly typed context is required to re-access the stored data. But the type information wasn't erased entirely, so this typed context can be re-gained with the help of an internal type index. All of this is considered implementation detail and may be subject to change without further notice; any access is assumed to happen through the TypedID frontend. Besides, there are two more specialised frontends.
|
||||
|
||||
!Front-ends
|
||||
|
|
@ -6399,7 +6464,7 @@ In most cases, the //actually usable instance// of an entity isn't identical to
|
|||
!basic usage patterns
|
||||
* ''Assets'' are maintained by the AssetManager, which always holds a smart-ptr to the managed asset. Assets include explicit links to dependent assets. Thus, there is no point in interfering with lifecylce management, so we store just a ''weak reference'' here, which the access functor turns back into a smart-ptr, sharing ownership.
|
||||
* Plain ''~MObjects'' are somewhat similar, but there is no active lifecycle management &mdash; they are always tied either to a placement of linked from within the assets or render processes. When done, they just go out of scope. Thus we too use a ''weak reference'' here, thereby expecting the respective entity to mix in {{{TypedID::Link}}}
|
||||
* Active ''Placements'' of an MObject behave like //object instances// within the model/session. They live within the PlacementIndex and cary an unique {{{LUID}}}-ID. Thus, it's sufficient to store this ''Placement-ID'', which can be used by the access functor to fetch the corresponding Placement from the session.
|
||||
* Active ''Placements'' of an MObject behave like //object instances// within the model/session. They live within the PlacementIndex and cary an unique {{{LUID}}}-ID. Thus, it's sufficient to store this ''~Placement-ID'', which can be used by the access functor to fetch the corresponding Placement from the session.
|
||||
Obviously, the ~TypedLookup system is open for addition of completely separate and different types.
|
||||
[>img[TypedLookup implementation sketch|uml/fig140293.png]]
|
||||
|>| !Entity |!pattern |
|
||||
|
|
@ -6525,6 +6590,14 @@ In case it's not already clear: we don't have "the" Render Engine, rat
|
|||
The &raquo;current setup&laquo; of the objects in the session is sort of a global state. Same holds true for the Controller, as the Engine can be at playback, it can run a background render or scrub single frames. But the whole complicated subsystem of the Builder and one given Render Engine configuration can be made ''stateless''. As a benefit of this we can run this subsystems multi-threaded without the need of any precautions (locking, synchronizing). Because all state information is just passed in as function parameters and lives in local variables on the stack, or is contained in the StateProxy which represents the given render //process// and is passed down as function parameter as well. (note: I use the term "stateless" in the usual, slightly relaxed manner; of course there are some configuration values contained in instance variables of the objects carrying out the calculations, but this values are considered to be constant over the course of the object usage).
|
||||
</pre>
|
||||
</div>
|
||||
<div title="Wiring" modifier="Ichthyostega" modified="201009250224" created="201009250223" tags="Concepts Model design draft" changecount="2">
|
||||
<pre>within Lumiera's Proc-Layer, on the conceptual level there are two kinds of connections: data streams and control connections. The wiring details on how these connections are defined, how they are to be detected by the builder and finally implemented by links in the RenderEngine.
|
||||
|
||||
!Stream connections
|
||||
|
||||
!Control connections
|
||||
</pre>
|
||||
</div>
|
||||
<div title="WiringDescriptor" modifier="Ichthyostega" modified="200807132352" created="200807132338" tags="Rendering operational impl spec" changecount="3">
|
||||
<pre>Each [[processing node|ProcNode]] contains a stateless ({{{const}}}) descriptor detailing the inputs, outputs and predecessors. Moreover, this descriptor contains the configuration of the call sequence yielding the &raquo;data pulled from predecessor(s)&laquo;. The actual type of this object is composed out of several building blocks (policy classes) and placed by the builder as a template parameter on the WiringDescriptor of the individual ProcNode. This happens in the WiringFactory in file {{{nodewiring.cpp}}}, which consequently contains all the possible combinations (pre)generated at compile time.
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue