scoped tempo maps: logic fixes and some comment-documentation
This commit is contained in:
@@ -32,6 +32,7 @@
|
||||
#include "pbd/string_convert.h"
|
||||
|
||||
#include "temporal/debug.h"
|
||||
#include "temporal/scope.h"
|
||||
#include "temporal/tempo.h"
|
||||
#include "temporal/types_convert.h"
|
||||
|
||||
@@ -5172,3 +5173,61 @@ TempoMapCutBuffer::clear ()
|
||||
_bartimes.clear ();
|
||||
_points.clear ();
|
||||
}
|
||||
|
||||
void
|
||||
ScopedTempoMapOwner::start_local_tempo_map (std::shared_ptr<Temporal::TempoMap> map)
|
||||
{
|
||||
/* This one is a little complicated to explain. This is where we set
|
||||
* the _local_tempo_map pointer which holds the local tempo map that
|
||||
* this ScopedTempoMapOwner wants to use every time one of its methods
|
||||
* is used. But we must also emulate the side effects of calling
|
||||
* ::in(), which include incrementing the depth and setting the
|
||||
* thread-local tempo map pointer.
|
||||
*
|
||||
* The caller should have placed an EC_LOCAL_TEMPO_SCOPE macro before
|
||||
* the call to this method, so that when the caller returns, we will
|
||||
* call ::out() and decrement the depth back to zero.
|
||||
*
|
||||
* Subsequent to that, all later in() and out() calls will install and
|
||||
* uninstall the local tempo map as the depth transitions from and to
|
||||
* zero, and will maintain the depth counter appropriately.
|
||||
*/
|
||||
|
||||
|
||||
DEBUG_TRACE (PBD::DEBUG::ScopedTempoMap, string_compose ("%1: starting local tempo scope\n", scope_name()));
|
||||
map->set_scope_owner (*this);
|
||||
_local_tempo_map = map;
|
||||
local_tempo_map_depth = 1;
|
||||
Temporal::TempoMap::set (_local_tempo_map);
|
||||
}
|
||||
|
||||
void
|
||||
ScopedTempoMapOwner::end_local_tempo_map ()
|
||||
{
|
||||
/* Like ::start_local_tempo_map() and ::in(), this needs to emulate the
|
||||
* side effects of calling ::out(), ehich are reducing the depth
|
||||
* counter to zero and resetting the thread local tempo map pointer to
|
||||
* the current global tempo map.
|
||||
*
|
||||
* The caller need not have used EC_LOCAL_TEMPO_SCOPE before calling
|
||||
* this, however, since there are no side effects to undo when we leave
|
||||
* the caller's scope.
|
||||
*/
|
||||
|
||||
DEBUG_TRACE (PBD::DEBUG::ScopedTempoMap, string_compose ("%1: ending local tempo scope\n", scope_name()));
|
||||
assert (_local_tempo_map);
|
||||
_local_tempo_map->clear_scope_owner ();
|
||||
_local_tempo_map.reset ();
|
||||
local_tempo_map_depth = 0;
|
||||
Temporal::TempoMap::fetch ();
|
||||
}
|
||||
|
||||
/* This is defined here for use in an assert() made in tempo.h. Circular
|
||||
* dependencies between ScopedTempoMapOwner and TempoMap mean that we can't
|
||||
* place uses of a ScopedTempoMapOwner in tempo.h
|
||||
*/
|
||||
bool
|
||||
TempoMap::fetch_condition ()
|
||||
{
|
||||
return !_tempo_map_p || !_tempo_map_p->scope_owner() || _tempo_map_p->scope_owner()->depth() == 0;
|
||||
}
|
||||
|
||||
@@ -25,35 +25,59 @@
|
||||
|
||||
#include "temporal/debug.h"
|
||||
#include "temporal/tempo.h"
|
||||
#include "temporal/visibility.h"
|
||||
|
||||
namespace Temporal {
|
||||
|
||||
class ScopedTempoMapOwner
|
||||
class TempoMap;
|
||||
|
||||
/* This object aand the use of the EC_LOCAL_TEMPO_MAP_SCOPE allows derived
|
||||
* classes to have a "local" (i.e. non-global) tempo map in use. This is
|
||||
* intended for custom editing contexts where all conversion between
|
||||
* sample/pixel and beat time should use a local tempo map, rather than the
|
||||
* global one.
|
||||
*
|
||||
* The downside is a bit ugly: every method of a derived class should have a
|
||||
* call to EC_LOCAL_TEMPO_MAP_SCOPE before anything else. However, in C++ there
|
||||
* is no other way to accomplish this, since there is no method-based code
|
||||
* injection. This macro uses an RAII technique (via TempoMapScope) to call the
|
||||
* ::in() and ::out() methods of the ScopedTempoMapOwner on entry and exist
|
||||
* from the method scope.
|
||||
*
|
||||
* A derived class will call start_local_tempo_map() to provide the map it
|
||||
* should be using, and end_local_tempo_map() when for whatever reason that map
|
||||
* is no longer relevant.
|
||||
*
|
||||
* start_local_tempo_map() will set the local_tempo_map (shared) ptr, which
|
||||
* gives us an indication as we enter and leave the scope of class methods that
|
||||
* there is a map which should be in use. There is also a depth counter so that
|
||||
* we only set the thread-local tempo map pointer when we transition from
|
||||
* depth==0 to depth==1. See notes in the method definition for some important
|
||||
* caveats.
|
||||
*
|
||||
* end_local_tempo_map() is called when the object's methods should no longer
|
||||
* use the previously provided local tempo map.
|
||||
*
|
||||
* The cost of this is low (but not zero, obviously):
|
||||
*
|
||||
* - extra pointer on the stack (the scope member of TempoMapScope)
|
||||
* - two conditionals, the first of which will frequently fail
|
||||
* - writes to the thread-local pointer whenever the method dept
|
||||
* - transitions between 0 and 1 (in either direction).
|
||||
*/
|
||||
|
||||
class LIBTEMPORAL_API ScopedTempoMapOwner
|
||||
{
|
||||
public:
|
||||
ScopedTempoMapOwner () : local_tempo_map_depth (0) {}
|
||||
virtual ~ScopedTempoMapOwner () {}
|
||||
LIBTEMPORAL_API ScopedTempoMapOwner () : local_tempo_map_depth (0) {}
|
||||
virtual LIBTEMPORAL_API ~ScopedTempoMapOwner () {}
|
||||
|
||||
void start_local_tempo_map (std::shared_ptr<Temporal::TempoMap> map) {
|
||||
DEBUG_TRACE (PBD::DEBUG::ScopedTempoMap, string_compose ("%1: starting local tempo scope\n", scope_name()));
|
||||
map->set_scope_owner (*this);
|
||||
_local_tempo_map = map;
|
||||
Temporal::TempoMap::set (_local_tempo_map);
|
||||
local_tempo_map_depth = 1;
|
||||
}
|
||||
void LIBTEMPORAL_API start_local_tempo_map (std::shared_ptr<Temporal::TempoMap> map);
|
||||
void LIBTEMPORAL_API end_local_tempo_map ();
|
||||
|
||||
void end_local_tempo_map () {
|
||||
DEBUG_TRACE (PBD::DEBUG::ScopedTempoMap, string_compose ("%1: ending local tempo scope\n", scope_name()));
|
||||
assert (_local_tempo_map);
|
||||
local_tempo_map_depth = 0;
|
||||
_local_tempo_map->clear_scope_owner ();
|
||||
_local_tempo_map.reset ();
|
||||
Temporal::TempoMap::fetch ();
|
||||
}
|
||||
uint64_t LIBTEMPORAL_API depth() const { return local_tempo_map_depth; }
|
||||
|
||||
uint64_t depth() const { return local_tempo_map_depth; }
|
||||
|
||||
virtual std::string scope_name() const = 0;
|
||||
virtual LIBTEMPORAL_API std::string scope_name() const = 0;
|
||||
|
||||
protected:
|
||||
mutable std::shared_ptr<Temporal::TempoMap> _local_tempo_map;
|
||||
@@ -64,8 +88,10 @@ class ScopedTempoMapOwner
|
||||
|
||||
void in () const {
|
||||
if (_local_tempo_map && local_tempo_map_depth++ == 0 ) {
|
||||
DEBUG_TRACE (PBD::DEBUG::ScopedTempoMap, string_compose ("%1: in to local tempo %2\n", scope_name(), local_tempo_map_depth));
|
||||
DEBUG_TRACE (PBD::DEBUG::ScopedTempoMap, string_compose ("%1: in to local tempo %2, TMAP set\n", scope_name(), local_tempo_map_depth));
|
||||
Temporal::TempoMap::set (_local_tempo_map);
|
||||
} else {
|
||||
DEBUG_TRACE (PBD::DEBUG::ScopedTempoMap, string_compose ("%1: in to local tempo %2, no tm set\n", scope_name(), local_tempo_map_depth));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,5 +122,4 @@ struct TempoMapScope {
|
||||
|
||||
} // namespace
|
||||
|
||||
// #define EC_LOCAL_TEMPO_SCOPE Temporal::TempoMapScope __tms (*this);
|
||||
#define EC_LOCAL_TEMPO_SCOPE
|
||||
#define EC_LOCAL_TEMPO_SCOPE Temporal::TempoMapScope __tms (*this);
|
||||
|
||||
@@ -739,12 +739,13 @@ class /*LIBTEMPORAL_API*/ TempoMap : public PBD::StatefulDestructible
|
||||
private:
|
||||
static thread_local SharedPtr _tempo_map_p;
|
||||
static SerializedRCUManager<TempoMap> _map_mgr;
|
||||
static bool fetch_condition ();
|
||||
public:
|
||||
LIBTEMPORAL_API static void init ();
|
||||
|
||||
LIBTEMPORAL_API static void update_thread_tempo_map() { _tempo_map_p = _map_mgr.reader(); }
|
||||
LIBTEMPORAL_API static SharedPtr use() { assert (_tempo_map_p); return _tempo_map_p; }
|
||||
LIBTEMPORAL_API static SharedPtr fetch() { assert (!_tempo_map_p || !_tempo_map_p->scope_owner()); update_thread_tempo_map(); return _tempo_map_p; }
|
||||
LIBTEMPORAL_API static SharedPtr fetch() { assert (fetch_condition()); update_thread_tempo_map(); return _tempo_map_p; }
|
||||
|
||||
/* Used only by the ARDOUR::AudioEngine API to reset the process thread
|
||||
* tempo map only when it has changed.
|
||||
@@ -1010,8 +1011,8 @@ class /*LIBTEMPORAL_API*/ TempoMap : public PBD::StatefulDestructible
|
||||
|
||||
static void map_assert (bool expr, char const * exprstr, char const * file, int line);
|
||||
|
||||
void set_scope_owner (ScopedTempoMapOwner&);
|
||||
void clear_scope_owner ();
|
||||
void LIBTEMPORAL_API set_scope_owner (ScopedTempoMapOwner&);
|
||||
void LIBTEMPORAL_API clear_scope_owner ();
|
||||
ScopedTempoMapOwner* scope_owner() const { return _scope_owner; }
|
||||
|
||||
private:
|
||||
|
||||
Reference in New Issue
Block a user