finish and comment the new time::Control facility

This commit is contained in:
Fischlurch 2011-06-12 19:03:12 +02:00
parent f4d0d23e48
commit 2099ecbcac
5 changed files with 747 additions and 410 deletions

View file

@ -0,0 +1,203 @@
/*
CONTROL-IMPL.hpp - time::control implementation building blocks
Copyright (C) Lumiera.org
2011, 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 control-impl.hpp
** Implementation building blocks for time modification and propagation.
** The time::Control element allows to impose modifications to a connected
** time value entity and at the same time publish the changes to registered
** listeners. Due to the various flavours of actual time value entities, this
** is a complex undertaking, which is implemented here based on policies and
** template metaprogramming. This header/include defines two building blocks:
** - the actual Mutator to apply the changes to the target entity
** - a Propagator to register listeners and forward the changes.
**
** \par implementation technique
**
** The Mutator uses functor objects to encapsulate the actual modification
** operations. When attaching to a target time entity to be manipulated, these
** functor objects will be configured by binding them to the appropriate
** implementation function. And picking this actual implementation is done
** through a time::mutation::Policy element, using the concrete time entity
** types as template parameter. Thus, the actual implementation to be used
** is determined by the compiler, through the template specialisations
** contained in control-policy.hpp
**
** @note the header control-policy.hpp with the template specialisations
** is included way down, after the class definitions. This is done
** so for sake of readability
**
** @see TimeControl_test
**
*/
#ifndef LIB_TIME_CONTROL_IMPL_H
#define LIB_TIME_CONTROL_IMPL_H
#include "lib/error.hpp"
#include "lib/time/mutation.hpp"
#include "lib/time/timevalue.hpp"
#include <vector>
namespace lib {
namespace time {
namespace mutation {
/**
* Implementation building block: impose changes to a Time element.
* The purpose of the Mutator is to attach a target time entity,
* which then will be subject to any received value changes,
* offsets and grid nudging. The actual attachment is to be
* performed in a subclass, by using the Mutation interface.
* When attaching to a target, the Mutator will be outfitted
* with a set of suitable functors, incorporating the specific
* behaviour for the concrete combination of input changes
* ("source values") and target object type. This works by
* binding to the appropriate implementation functionality,
* guided by a templated policy class. After installing
* these functors, these decisions remains opaque and
* encapsulated within the functor objects, so the
* mutator object doesn't need to carry this
* type information on the interface
*/
template<class TI>
class Mutator
: public Mutation
{
typedef function<TI(TI const&)> ValueSetter;
typedef function<TI(Offset const&)> Ofsetter;
typedef function<TI(int)> Nudger;
protected:
mutable ValueSetter setVal_;
mutable Ofsetter offset_;
mutable Nudger nudge_;
void
ensure_isArmed() const
{
if (!setVal_)
throw error::State("feeding time/value change "
"while not (yet) connected to any target to change"
,error::LUMIERA_ERROR_UNCONNECTED);
}
template<class TAR>
void bind_to (TAR& target) const;
void unbind();
// using default construction and copy
};
/**
* Implementation building block: propagate changes to listeners.
* The Propagator manages a set of callback signals, allowing to
* propagate notifications for changed Time values.
*
* There are no specific requirements on the acceptable listeners,
* besides exposing a function-call operator to feed the changed
* time value to. Both Mutator and Propagator employ one primary
* template parameter, which is the type of the time values
* to be fed in and propagated.
*/
template<class TI>
class Propagator
{
typedef function<void(TI const&)> ChangeSignal;
typedef std::vector<ChangeSignal> ListenerList;
ListenerList listeners_;
public:
/** install notification receiver */
template<class SIG>
void
attach (SIG const& toNotify)
{
ChangeSignal newListener (ref(toNotify));
listeners_.push_back (newListener);
}
/** disconnect any observers */
void
disconnnect()
{
listeners_.clear();
}
/** publish a change */
TI
operator() (TI const& changedVal) const
{
typedef typename ListenerList::const_iterator Iter;
Iter p = listeners_.begin();
Iter e = listeners_.end();
for ( ; p!=e; ++p )
(*p) (changedVal);
return changedVal;
}
// using default construction and copy
};
}}} // lib::time::mutation
/* ===== Definition of actual operations ===== */
#include "lib/time/control-policy.hpp"
template<class TI>
template<class TAR>
void
lib::time::mutation::Mutator<TI>::bind_to (TAR& target) const
{
using lib::time::mutation::Policy;
setVal_ = Policy<TI,TI, TAR>::buildChangeHandler (target);
offset_ = Policy<TI,Offset,TAR>::buildChangeHandler (target);
nudge_ = Policy<TI,int, TAR>::buildChangeHandler (target);
}
template<class TI>
void
lib::time::mutation::Mutator<TI>::unbind()
{
setVal_ = ValueSetter();
offset_ = Ofsetter();
nudge_ = Nudger();
}
#endif

View file

@ -0,0 +1,400 @@
/*
CONTROL-POLICY.hpp - detail definition of actual time changing functionality
Copyright (C) Lumiera.org
2011, 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 control-policy.hpp
** Definition of special cases when imposing a change onto concrete time values.
** The time::Control element allows to impose modifications to a connected
** time value entity and at the same time publish the changes to registered
** listeners. Due to the various flavours of actual time value entities, this
** is a complex undertaking, which is implemented here based on policies and
** template metaprogramming.
**
** The header control-impl.hpp defines the building blocks for time::Control
** and then includes this header here to get the concrete template specialisations
** for time::mutation::Policy. This policy class is templated by time entity types
** - for \c TI, the \em nominal value type used on the time::Control interface
** - for \c SRC, the actual type of values to impose as \em change
** - for \c TAR, the target time value's type, receiving those changes.
**
** \par mutating a time value entity
**
** Actually imposing a change to the attached time value entity involves several
** steps. Each of these steps might be adapted specifically, in accordance to
** the concrete time value types involved.
** - TimeValue, Time
** - Offset
** - Duration
** - TimeSpan
** - QuTime (grid aligned time value)
** - QuTimeSpan (planned as of 6/2011)
**
** Moreover, the combination of types needs to be taken into account. For example,
** it doesn't make sense to apply a Duration value as change to a TimeValue, which
** has no duration (temporal extension). While a TimeSpan might receive a Duration
** change, but behaves differently when imposing a Time to manipulate the starting
** point of the time interval given by the TimeSpan.
**
** Incoming changes might be of any of the aforementioned types, and in addition,
** we might receive \em nudging, which means to increment or decrement the target
** time value in discrete steps. After maybe adapting these incoming change values,
** they may be actually \em imposed to the target. In all cases, this is delegated
** to the time::Mutation base class, which is declared fried to TimeValue and thus
** has the exceptional ability to manipulate time values, which otherwise are defined
** to be immutable. Additionally, these protected functions in the time::Mutation
** baseclass also know how to handle \em nudge values, either by using the native
** (embedded) time grid of a quantised time value, or by falling back to a standard
** nudging grid, defined in the session context (TODO as of 6/2011). //////////////////////TICKET #810
**
** After (maybe) imposing a change to the target, the change \em notification value
** needs to be built. This is the time value entity to be forwarded to registered
** listeners. This notification value has to be given as the type \c TI, in accordance
** to the \c time::Control<TI> frontend definition used in the concrete usage situation.
** As this type \c TI might be different to the actual target type, and again different
** to the type of the change handed in, in some cases this involves a second conversion
** step, to represent the current state of the target \c TAR in terms of the interface
** type \c TI.
**
** \par changing quantised (grid aligned) time entities
**
** The time::Control element includes the capability to handle grid aligned time values,
** both as target and as change/notification value. This ability is compiled in conditionally,
** as including mutation.hpp causes several additional includes, which isn't desirable when
** it comes just to changing plain time values. Thus, to get these additional specialisations,
** the LIB_TIME_TIMEQUQNT_H header guard needs to be defined, which happens automatically
** if lib/time/mutation.hpp is included prior to lib/time/control.hpp.
**
** As a special convention, any \em quantised (grid aligned) types involved in these
** time changes will be \em materialised, whenever a type conversion happens. Generally
** speaking, a quantised time value contains an (opaque) raw time value, plus a reference
** to a time grid definition to apply. In this context \em materialising means actually
** to apply this time grid to yield a grid aligned value. Thus, when using a quantised
** value to impose as change (or to receive a change), its grid aligning nature
** becomes effective, by applying the \em current definition of the grid to
** create a fixed (materialised) time value, aligned to that current grid.
**
** @todo 6/2011 include all the special cases for QuTimeSpan ////////////////////TICKET #760
**
** @see TimeControl_test
**
*/
#ifndef LIB_TIME_CONTROL_POLICY_H
#define LIB_TIME_CONTROL_POLICY_H
#include "lib/meta/util.hpp"
#include "lib/time/mutation.hpp"
#include "lib/time/timevalue.hpp"
#include <boost/utility/enable_if.hpp>
#include <tr1/functional>
namespace lib {
namespace time {
namespace mutation {
using boost::disable_if;
using lumiera::typelist::is_sameType;
using std::tr1::placeholders::_1;
using std::tr1::function;
using std::tr1::bind;
using std::tr1::ref;
namespace { // metaprogramming helpers to pick a suitable implementation branch...
template<class T>
inline bool
isDuration()
{
return is_sameType<T,Duration>::value;
}
template<class T>
inline bool
isTimeSpan()
{
return is_sameType<T,TimeSpan>::value;
}
template<class T>
inline T const&
maybeMaterialise (T const& non_grid_aligned_TimeValue)
{
return non_grid_aligned_TimeValue;
}
#ifdef LIB_TIME_TIMEQUQNT_H
inline QuTime
maybeMaterialise (QuTime const& alignedTime)
{
PQuant const& grid(alignedTime);
return QuTime(grid->materialise(alignedTime), grid);
}
#endif //--quantised-time-support
}
/**
* Implementation policy: how to build a new
* notification value of type \c TI, given a
* target time value entity of type \c TAR
*/
template<class TI, class TAR>
struct Builder
{
static TI
buildChangedValue (TAR const& target)
{
return TI(target);
}
};
template<class TAR>
struct Builder<TimeSpan, TAR>
{
static TimeSpan
buildChangedValue (TAR const& target)
{
return TimeSpan (target, Duration::NIL);
}
};
template<>
struct Builder<TimeSpan, Duration>
{
static TimeSpan
buildChangedValue (Duration const& targetDuration)
{
return TimeSpan (Time::ZERO, targetDuration);
}
};
template<>
struct Builder<TimeSpan, TimeSpan>
{
static TimeSpan
buildChangedValue (TimeSpan const& target)
{
return target;
}
};
#ifdef LIB_TIME_TIMEQUQNT_H
template<class TAR>
struct Builder<QuTime, TAR>
{
static QuTime
buildChangedValue (TAR const& target)
{
return QuTime (target
,getDefaultGridFallback() //////////////////TICKET #810
);
}
};
template<>
struct Builder<QuTime, QuTime>
{
static QuTime
buildChangedValue (QuTime const& target)
{
return target;
}
};
#endif //--quantised-time-support
/**
* Policy to tie the various detail policies together
* for providing actual value change operations.
* The standard case uses the (inherited) time::Mutation
* base implementation to impose a new value onto the
* target entity and then uses the Builder policy to
* create a notification value reflecting this change.
*/
template<class TI, class TAR>
struct Link
: Mutator<TI>
, Builder<TI,TAR>
{
template<class SRC>
static TI
processValueChange (TAR& target, SRC const& change) ///< standard case: plain value change
{
imposeChange (target, maybeMaterialise(change));
return buildChangedValue (maybeMaterialise(target));
}
static TI
useLengthAsChange (TAR& target, TimeSpan const& change)
{
return processValueChange(target, change.duration());
}
static TI
mutateLength (TimeSpan& target, Duration const& change)
{
Mutator<TimeSpan>::imposeChange (target.duration(), change);
return Builder<TI,TimeSpan>::buildChangedValue(target);
}
static TimeSpan
mutateTimeSpan (TimeSpan& target, TimeSpan const& change)
{
Mutator<TimeSpan>::imposeChange (target.duration(), change.duration());
Mutator<TimeSpan>::imposeChange (target,change.start());
return Builder<TimeSpan,TimeSpan>::buildChangedValue(target);
}
static TI
dontChange (TAR& target) ///< @note: not touching the target
{
return buildChangedValue(target);
}
};
/**
* Policy how to impose changes onto a connected target time value entity
* This policy will be parametrised with the concrete time entity types
* involved in the usage situation of time::Control. The purpose of the
* policy is to \em bind a functor object to the concrete implementation
* of the value change applicable for this combination of types.
* This functor will then be stored within time::Control and
* invoked for each actual value change.
* @param TI the nominal (interface) type of the change, propagated to listeners
* @param SRC the actual type of the change to be imposed
* @param TAR the actual type of the target entity to receive the changes
* @note typically either SRC is identical to TI, or it is an
* time::Offset, or an int for \em nudging the target
*/
template<class TI, class SRC, class TAR>
struct Policy
{
static function<TI(SRC const&)>
buildChangeHandler (TAR& target)
{
return bind (Link<TI,TAR>::template processValueChange<SRC>, ref(target), _1 );
}
};
// special treatment of Durations as target------------------------------------
namespace {
template<class T>
struct canMutateDuration
{
static const bool value = is_sameType<T,Duration>::value
|| is_sameType<T,Offset>::value
|| is_sameType<T,int>::value;
};
template<class T>
struct canReceiveDuration
{
static const bool value = is_sameType<T,Duration>::value
|| is_sameType<T,TimeSpan>::value;
};
}
/**
* special case: a Duration target value can't be changed by plain time values.
* This specialisation is \em not used (\c disable_if ) when the given change (SRC)
* is applicable to a Duration in a sensible way. We either define explicit
* specialisations (for TimeSpan) or fall back to the default in these cases.
*/
template<class TI, class SRC>
struct Policy<TI,SRC, typename disable_if< canMutateDuration<SRC>,
Duration>::type>
{
static function<TI(SRC const&)>
buildChangeHandler (Duration& target)
{
return bind (Link<TI,Duration>::dontChange, ref(target) );
}
};
/**
* special case: a Duration change value can't be imposed to a plain time value.
* In these cases, we even propagate a Duration::ZERO to the listeners.
* As above, there are exceptions to this behaviour, where a Duration change
* can sensibly be applied.
*/
template<class TAR>
struct Policy<Duration, typename disable_if< canReceiveDuration<TAR>,
Duration>::type, TAR>
{
static function<Duration(Duration const&)>
buildChangeHandler (TAR&)
{
return bind ( ignore_change_and_return_Zero );
}
static Duration
ignore_change_and_return_Zero()
{
return Duration::NIL;
}
};
template<class TI>
struct Policy<TI,TimeSpan,Duration>
{
static function<TI(TimeSpan const&)>
buildChangeHandler (Duration& target)
{
return bind (Link<TI,Duration>::useLengthAsChange, ref(target), _1 );
}
};
// special treatment for TimeSpan values---------------------------------------
template<class TI>
struct Policy<TI,Duration,TimeSpan>
{
static function<TI(Duration const&)>
buildChangeHandler (TimeSpan& target)
{
return bind (Link<TI,TimeSpan>::mutateLength, ref(target), _1 );
}
};
template<>
struct Policy<TimeSpan,TimeSpan,TimeSpan>
{
static function<TimeSpan(TimeSpan const&)>
buildChangeHandler (TimeSpan& target)
{
return bind (Link<TimeSpan,TimeSpan>::mutateTimeSpan, ref(target), _1 );
}
};
}}} // namespace lib::time::mutation
#endif

View file

@ -40,6 +40,46 @@
** of time-like entities -- be it the running time display in a GUI widget, a ruler marker
** which can be dragged, a modifiable selection or the animated playhead cursor.
**
** \par usage scenarios
** The time::Control element provides mediating functionality, but doesn't assume or provide
** anything special regarding the usage pattern or the lifecycle, beyond the ability to
** attach listeners, attach to a (different) target and to detach from all connections.
** Especially, no assumptions are made about which side is the server or the client
** and who owns the time::Control element.
**
** Thus an interface might accept a time::Control element \em reference (e.g. the
** lumiera::Play::Controller uses this pattern) -- meaning that the client owns the
** Control element and might attach listeners, while the implementation (server side)
** will attach the Control to mutate an time value entity otherwise not disclosed
** (e.g. the playhead position of the playback process). Of course, in this case
** the client is responsible for keeping the Control element and all listeners
** alive, and to invoke Control#disconnect prior to destroying the element.
**
** Of course, the reversed usage situation would be possible as well: an interface
** exposing a time::Control, thus allowing to attach target and listeners, while the
** actual changes will originate somewhere within the service implementation.
**
** Another usage pattern would be to expose a time::Control \c const&, allowing only to
** impose changes, but not to change the target or listener attachments. To the contrary,
** when exposing only a time::Mutation \c const& through an interface allows only to
** attach new target elements, but not to change listeners or feed any value changes.
**
** Using time::Control as an implementation building block and just exposing the
** change (function) operators or the listener attachment through an forwarding sub
** interface is another option.
**
** @note time::Control is default constructible and freely copyable.
**
**
** \par changing quantised (grid aligned) time entities
**
** The time::Control element includes the functionality to handle grid aligned time values,
** both as target and as change/notification value. This ability is compiled in conditionally,
** as including mutation.hpp causes several additional includes, which isn't desirable when
** it comes just to changing plain time values. Thus, to get these additional specialisations,
** the LIB_TIME_TIMEQUQNT_H header guard needs to be defined, which happens automatically
** if lib/time/mutation.hpp is included prior to lib/time/control.hpp.
**
** \par implementation notes
** - the validity of a given combination of change and target is checked immediately,
** when connecting to the target. Depending on the situation, the actual changes later
@ -48,406 +88,42 @@
** processed within its own call context (function invocation), parallelism is only
** a concern with respect to the value finally visible within the target.
** - the change notification is processed right away, after applying the change to the
** target; in all cases, the effective change value is what will be propagated, \em not
** the content of the target after applying the change
** target; of course there is a race between applying the value and building the
** response value passed on as notification. In all cases, the effective change
** notification value is built from the state of the target after applying
** the change, which might or might not reflect the change value passed in.
**
** @todo WIP-WIP-WIP
** @todo include support for QuTimeSpan values ////////////////////TICKET #760
**
*/
#ifndef LIB_TIME_CONTROL_H
#define LIB_TIME_CONTROL_H
#include "lib/error.hpp"
#include "lib/meta/util.hpp"
#include "lib/time/mutation.hpp"
#include "lib/time/timevalue.hpp"
#include "lib/time/control-impl.hpp"
#include <boost/utility/enable_if.hpp>
#include <tr1/functional>
#include <vector>
namespace lib {
namespace time {
namespace mutation {
using boost::enable_if;
using boost::disable_if;
using lumiera::typelist::is_sameType;
using std::tr1::placeholders::_1;
using std::tr1::function;
using std::tr1::bind;
using std::tr1::ref;
/**
* Implementation building block: impose changes to a Time element.
* The purpose of the Mutator is to attach a target time entity,
* which then will be subject to any received value changes,
* offsets and grid nudging. The actual attachment is to be
* performed in a subclass, by using the Mutation interface.
* When attaching to a target, the Mutator will be outfitted
* with a set of suitable functors, incorporating the specific
* behaviour for the concrete combination of input changes
* ("source values") and target object type. This works by
* binding to the appropriate implementation functionality,
* guided by a templated policy class. After installing
* these functors, these decisions remains opaque and
* encapsulated within the functor objects, so the
* mutator object doesn't need to carry this
* type information on the interface
*/
template<class TI>
class Mutator
: public Mutation
{
typedef function<TI(TI const&)> ValueSetter;
typedef function<TI(Offset const&)> Ofsetter;
typedef function<TI(int)> Nudger;
protected:
mutable ValueSetter setVal_;
mutable Ofsetter offset_;
mutable Nudger nudge_;
void
ensure_isArmed()
{
if (!setVal_)
throw error::State("feeding time/value change "
"while not (yet) connected to any target to change"
,error::LUMIERA_ERROR_UNCONNECTED);
}
template<class TAR>
void bind_to (TAR& target) const;
void unbind();
// using default construction and copy
};
/**
* Implementation building block: propagate changes to listeners.
* The Propagator manages a set of callback signals, allowing to
* propagate notifications for changed Time values.
*
* There are no specific requirements on the acceptable listeners,
* besides exposing a function-call operator to feed the changed
* time value to. Both Mutator and Propagator employ one primary
* template parameter, which is the type of the time values
* to be fed in and propagated.
*/
template<class TI>
class Propagator
{
typedef function<void(TI const&)> ChangeSignal;
typedef std::vector<ChangeSignal> ListenerList;
ListenerList listeners_;
public:
/** install notification receiver */
template<class SIG>
void
attach (SIG const& toNotify)
{
ChangeSignal newListener (ref(toNotify));
listeners_.push_back (newListener);
}
/** disconnect any observers */
void
disconnnect()
{
listeners_.clear();
}
/** publish a change */
TI
operator() (TI const& changedVal) const
{
typedef typename ListenerList::const_iterator Iter;
Iter p = listeners_.begin();
Iter e = listeners_.end();
for ( ; p!=e; ++p )
(*p) (changedVal);
return changedVal;
}
};
namespace { // metaprogramming helpers to pick suitable implementation branch...
template<class T>
inline bool
isDuration()
{
return is_sameType<T,Duration>::value;
}
template<class T>
inline bool
isTimeSpan()
{
return is_sameType<T,TimeSpan>::value;
}
template<class T>
inline T const&
maybeMaterialise (T const& non_grid_aligned_TimeValue)
{
return non_grid_aligned_TimeValue;
}
#ifdef LIB_TIME_TIMEQUQNT_H
inline QuTime
maybeMaterialise (QuTime const& alignedTime)
{
PQuant const& grid(alignedTime);
return QuTime(grid->materialise(alignedTime), grid);
}
#endif
}
template<class TI, class TAR>
struct Builder
{
static TI
buildChangedValue (TAR const& target)
{
return TI(target);
}
};
template<class TAR>
struct Builder<TimeSpan, TAR>
{
static TimeSpan
buildChangedValue (TAR const& target)
{
return TimeSpan (target, Duration::NIL);
}
};
template<>
struct Builder<TimeSpan, Duration>
{
static TimeSpan
buildChangedValue (Duration const& targetDuration)
{
return TimeSpan (Time::ZERO, targetDuration);
}
};
template<>
struct Builder<TimeSpan, TimeSpan>
{
static TimeSpan
buildChangedValue (TimeSpan const& target)
{
return target;
}
};
#ifdef LIB_TIME_TIMEQUQNT_H
template<class TAR>
struct Builder<QuTime, TAR>
{
static QuTime
buildChangedValue (TAR const& target)
{
return QuTime (target
,getDefaultGridFallback() //////////////////TICKET #810
);
}
};
template<>
struct Builder<QuTime, QuTime>
{
static QuTime
buildChangedValue (QuTime const& target)
{
return target;
}
};
#endif
template<class TI, class TAR>
struct Link
: Mutator<TI>
, Builder<TI,TAR>
{
template<class SRC>
static TI
processValueChange (TAR& target, SRC const& change)
{
imposeChange (target, maybeMaterialise(change));
return buildChangedValue (maybeMaterialise(target));
}
static TI
useLengthAsChange (TAR& target, TimeSpan const& change)
{
return processValueChange(target, change.duration());
}
static TI
mutateLength (TimeSpan& target, Duration const& change)
{
Mutator<TimeSpan>::imposeChange (target.duration(), change);
return Builder<TI,TimeSpan>::buildChangedValue(target);
}
static TimeSpan
mutateTimeSpan (TimeSpan& target, TimeSpan const& change)
{
Mutator<TimeSpan>::imposeChange (target.duration(), change.duration());
Mutator<TimeSpan>::imposeChange (target,change.start());
return Builder<TimeSpan,TimeSpan>::buildChangedValue(target);
}
static TI
dontChange (TAR& target)
{
// note: not touching the target
return buildChangedValue(target);
}
};
template<class TI, class SRC, class TAR>
struct Policy
{
static function<TI(SRC const&)>
buildChangeHandler (TAR& target)
{
return bind (Link<TI,TAR>::template processValueChange<SRC>, ref(target), _1 );
}
};
// special treatment of Durations as target...
namespace {
template<class T>
struct canMutateDuration
{
static const bool value = is_sameType<T,Duration>::value
|| is_sameType<T,Offset>::value
|| is_sameType<T,int>::value;
};
template<class T>
struct canReceiveDuration
{
static const bool value = is_sameType<T,Duration>::value
|| is_sameType<T,TimeSpan>::value;
};
}
template<class TI, class SRC>
struct Policy<TI,SRC, typename disable_if< canMutateDuration<SRC>,
Duration>::type>
{
static function<TI(SRC const&)>
buildChangeHandler (Duration& target)
{
return bind (Link<TI,Duration>::dontChange, ref(target) );
}
};
template<class TAR>
struct Policy<Duration, typename disable_if< canReceiveDuration<TAR>,
Duration>::type, TAR>
{
static function<Duration(Duration const&)>
buildChangeHandler (TAR&)
{
return bind ( ignore_change_and_return_Zero );
}
static Duration
ignore_change_and_return_Zero()
{
return Duration::NIL;
}
};
template<class TI>
struct Policy<TI,TimeSpan,Duration>
{
static function<TI(TimeSpan const&)>
buildChangeHandler (Duration& target)
{
return bind (Link<TI,Duration>::useLengthAsChange, ref(target), _1 );
}
};
// special treatment for TimeSpan values...
template<class TI>
struct Policy<TI,Duration,TimeSpan>
{
static function<TI(Duration const&)>
buildChangeHandler (TimeSpan& target)
{
return bind (Link<TI,TimeSpan>::mutateLength, ref(target), _1 );
}
};
template<>
struct Policy<TimeSpan,TimeSpan,TimeSpan>
{
static function<TimeSpan(TimeSpan const&)>
buildChangeHandler (TimeSpan& target)
{
return bind (Link<TimeSpan,TimeSpan>::mutateTimeSpan, ref(target), _1 );
}
};
template<class TI>
template<class TAR>
void
Mutator<TI>::bind_to (TAR& target) const
{
setVal_ = Policy<TI,TI, TAR>::buildChangeHandler (target);
offset_ = Policy<TI,Offset,TAR>::buildChangeHandler (target);
nudge_ = Policy<TI,int, TAR>::buildChangeHandler (target);
}
template<class TI>
void
Mutator<TI>::unbind()
{
setVal_ = ValueSetter();
offset_ = Ofsetter();
nudge_ = Nudger();
}
}
/**
* Frontend/Interface: controller-element to retrieve
* and change running time values
* and change running time values. time::Control is
* a mediator element, which can be attached to some
* time value entities as \em mutation, and at the
* same time allows to register listeners. When
* configured this way, \em changes may be fed
* to the function operator(s). These changes
* will be imposed to the connected target
* and the result propagated to the listeners.
*
* @see time::Mutation
* @see time::TimeSpan#accept(Mutation const&)
* @todo WIP-WIP-WIP
*/
template<class TI>
class Control
@ -460,9 +136,9 @@ namespace time {
virtual void change (QuTime&) const;
public:
void operator() (TI const&);
void operator() (Offset const&);
void operator() (int);
void operator() (TI const&) const;
void operator() (Offset const&) const;
void operator() (int) const;
/** install a callback functor to be invoked as notification
@ -481,27 +157,47 @@ namespace time {
/* === forward to implementation === */
/** impose a new value to the connected target.
* If applicable, the target will afterwards reflect
* that change, and listeners will be notified, passing
* the target's new state.
* @throw error::State if not connected to a target
* @note the actual change in the target also depends
* on the concrete target type and the type of
* the change. By default, the time value is
* changed; this may include grid alignment.
*/
template<class TI>
void
Control<TI>::operator () (TI const& newValue)
Control<TI>::operator () (TI const& newValue) const
{
this->ensure_isArmed();
notifyListeners_(
this->setVal_(newValue));
}
/** impose an offset to the connected target.
* If applicable, the target will be adjusted by the
* time offset, and listeners will be notified.
* @throw error::State if not connected to a target
*/
template<class TI>
void
Control<TI>::operator () (Offset const& adjustment)
Control<TI>::operator () (Offset const& adjustment) const
{
this->ensure_isArmed();
notifyListeners_(
this->offset_(adjustment));
}
/** nudge the connected target by the given offset steps,
* using either the target's own grid (when quantised),
* or a 'natural' nudge grid
* @throw error::State if not connected to a target
*/
template<class TI>
void
Control<TI>::operator () (int offset_by_steps)
Control<TI>::operator () (int offset_by_steps) const
{
this->ensure_isArmed();
notifyListeners_(
@ -530,6 +226,9 @@ namespace time {
notifyListeners_.attach (toNotify);
}
/* ==== Implementation of the Mutation interface ==== */
template<class TI>
void
Control<TI>::change (Duration& targetDuration) const

View file

@ -688,7 +688,22 @@ return: 0
END
PLANNED "Life changing time specifications with feedback" TimeControl_test <<END
TEST "Life changing time specifications with feedback" TimeControl_test <<END
out: Test-Case. Target=.+time.Duration. <--feed--- .+time.TimeValue.$
out: Test-Case. Target=.+time.Duration. <--feed--- .+time.Time.$
out: Test-Case. Target=.+time.Duration. <--feed--- .+time.Duration.$
out: Test-Case. Target=.+time.Duration. <--feed--- .+time.TimeSpan.$
out: Test-Case. Target=.+time.Duration. <--feed--- .+time.QuTime.$
out: Test-Case. Target=.+time.TimeSpan. <--feed--- .+time.TimeValue.$
out: Test-Case. Target=.+time.TimeSpan. <--feed--- .+time.Time.$
out: Test-Case. Target=.+time.TimeSpan. <--feed--- .+time.Duration.$
out: Test-Case. Target=.+time.TimeSpan. <--feed--- .+time.TimeSpan.$
out: Test-Case. Target=.+time.TimeSpan. <--feed--- .+time.QuTime.$
out: Test-Case. Target=.+time.QuTime. <--feed--- .+time.TimeValue.$
out: Test-Case. Target=.+time.QuTime. <--feed--- .+time.Time.$
out: Test-Case. Target=.+time.QuTime. <--feed--- .+time.Duration.$
out: Test-Case. Target=.+time.QuTime. <--feed--- .+time.TimeSpan.$
out: Test-Case. Target=.+time.QuTime. <--feed--- .+time.QuTime.$
return: 0
END

View file

@ -26,10 +26,11 @@
#include "lib/time/timevalue.hpp"
#include "lib/time/timequant.hpp"
#include "lib/time/control.hpp"
#include "proc/asset/meta/time-grid.hpp"
#include "lib/meta/generator-combinations.hpp"
#include "lib/meta/util.hpp"
#include "proc/asset/meta/time-grid.hpp"
#include "lib/scoped-holder.hpp"
#include "lib/meta/util.hpp"
#include "lib/util.hpp"
#include <boost/lexical_cast.hpp>
@ -57,7 +58,10 @@ namespace test{
using lumiera::typelist::InstantiateChainedCombinations;
using error::LUMIERA_ERROR_UNCONNECTED;
namespace {
namespace { // Test setup and helpers....
inline string
pop (Arg arg)
{
@ -105,21 +109,25 @@ namespace test{
return *received_;
}
};
}
}//(End)Test helpers
/****************************************************************
* @test use the time::Control to push a sequence of modifications
* to various time entities; in all cases, a suitable change
* should be imposed to the target and then a notification signal
/***********************************************************************
* @test use the time::Control to push a sequence of modifications to
* various time entities; in all cases, a suitable change should
* be imposed to the target and then a notification signal
* should be invoked.
* @todo cover the cases.....
* - change to a given value
* - change by an offset
* - change using a grid value
* - apply an (grid) increment
*
* After covering a simple basic case, this test uses
* template metaprogramming techniques to build a matrix of all
* possible type combinations and then performs a standard test
* sequence for each of these type combinations. Within this
* test sequence, verification functions are invoked, which
* are defined with specialisations to adapt for the various
* types to be covered.
*/
class TimeControl_test : public Test
{
@ -176,6 +184,11 @@ namespace test{
}
/** @test cover all possible combinations of input change values
* and target time value entities to be handled by time::Control.
* Each of these cases executes a standard test sequence, which is
* defined in TestCase#performTestSequence
*/
void verifyMatrix_of_MutationCases (TimeValue const& o, TimeValue const& c);
};
@ -241,7 +254,7 @@ namespace test{
return QuTime (org, "test_grid_PAL");
}
};
template<class SRC>
struct TestChange
@ -395,17 +408,21 @@ namespace test{
template<class TAR, class SRC, class BASE>
template< class TAR ///< type of the target time value entity to receive changes
, class SRC ///< type of the time value to be imposed as change
, class BASE
>
struct TestCase
: BASE
{
void
performTestCases(TimeValue const& org, TimeValue const& c)
performTestSequence(TimeValue const& org, TimeValue const& c)
{
cout << "Test-Case. Target=" << showType<TAR>()
<<" <--feed--- " << showType<SRC>()
<<endl;
<< "\t <--feed--- " << showType<SRC>()
<< endl;
// test subject
Control<SRC> controller;
TAR target = TestTarget<TAR>::build(org);
@ -453,32 +470,35 @@ namespace test{
// tail recursion: further test combinations....
BASE::performTestCases(org,c);
BASE::performTestSequence(org,c);
}
};
struct IterationEnd
{
void performTestCases(TimeValue const&, TimeValue const&) { }
void performTestSequence(TimeValue const&, TimeValue const&) { }
};
}//(End)Implementation Test-case matrix
void
TimeControl_test::verifyMatrix_of_MutationCases (TimeValue const& o, TimeValue const& c)
TimeControl_test::verifyMatrix_of_MutationCases (TimeValue const& origVal, TimeValue const& change)
{
typedef Types<Duration,TimeSpan,QuTime> KindsOfTarget;
typedef Types<Time,Duration,TimeSpan,QuTime> KindsOfSource;
typedef Types<Duration,TimeSpan,QuTime> KindsOfTarget; // time entities to receive value changes
typedef Types<TimeValue,Time,Duration,TimeSpan,QuTime> KindsOfSource; // time entities to be used as change values
typedef InstantiateChainedCombinations< KindsOfTarget
, KindsOfSource
, TestCase
, TestCase // template to be instantiated for each type
, IterationEnd > TestMatrix;
TestMatrix().performTestCases(o,c);
TestMatrix().performTestSequence(origVal, change);
}
/** Register this test class... */
LAUNCHER (TimeControl_test, "unit common");