lumiera_/src/lib/time/control-policy.hpp
Ichthyostega ff7ac5523f clean-up(#985): tighten basic header lib/meta/util.hpp
This header shall provide only very fundamental
metaprogramming helpers, since it is included pervasively
2016-01-05 22:00:53 +01:00

401 lines
13 KiB
C++

/*
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 _nominal_ value type used on the time::Control interface
** - for \c SRC, the actual type of values to impose _as change_
** - for \c TAR, the target time value's type, receiving those changes.
**
** ## 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 *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 _imposed_ on 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 _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 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 `TI`, in accordance
** to the `time::Control<TI>` frontend definition used in the concrete usage situation.
** As this type `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 `TAR` in terms of the interface
** type `TI`.
**
** ## 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_TIMEQUANT_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/time/mutation.hpp"
#include "lib/time/timevalue.hpp"
#include <boost/utility/enable_if.hpp>
#include <type_traits>
#include <functional>
namespace lib {
namespace time {
namespace mutation {
using boost::disable_if;
using std::__or_;
using std::is_same;
using std::placeholders::_1;
using std::function;
using std::bind;
using std::ref;
namespace { // metaprogramming helpers to pick a suitable implementation branch...
template<class T>
inline bool
isDuration()
{
return is_same<T, Duration>::value;
}
template<class T>
inline bool
isTimeSpan()
{
return is_same<T, TimeSpan>::value;
}
template<class T>
inline T const&
maybeMaterialise (T const& non_grid_aligned_TimeValue)
{
return non_grid_aligned_TimeValue;
}
#ifdef LIB_TIME_TIMEQUANT_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_TIMEQUANT_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
: Mutation
, Builder<TI,TAR>
{
template<class SRC>
static TI
processValueChange (TAR& target, SRC const& change) ///< standard case: plain value change
{
imposeChange (target, maybeMaterialise(change));
return Link::buildChangedValue (maybeMaterialise(target));
}
static TI
useLengthAsChange (TAR& target, TimeSpan const& change)
{
return processValueChange(target, change.duration());
}
static TI
mutateLength (TimeSpan& target, Duration const& change)
{
imposeChange (target.duration(), change);
return Builder<TI,TimeSpan>::buildChangedValue(target);
}
static TimeSpan
mutateTimeSpan (TimeSpan& target, TimeSpan const& change)
{
imposeChange (target.duration(), change.duration());
imposeChange (target,change.start());
return Builder<TimeSpan,TimeSpan>::buildChangedValue(target);
}
static TI
dontChange (TAR& target) ///< @note: not touching the target
{
return Link::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
: __or_< is_same<T,Duration>
, is_same<T,Offset>
, is_same<T,int>
>
{ };
template<class T>
struct canReceiveDuration
: __or_< is_same<T,Duration>
, is_same<T,TimeSpan>
>
{ };
}
/**
* 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