928 lines
22 KiB
C++
928 lines
22 KiB
C++
#include "externals.hh"
|
|
|
|
#include "common.hh"
|
|
#include "c-sync.hh"
|
|
#include "c-syncedit.hh"
|
|
#include "c-undo.hh"
|
|
|
|
#include <ebcl/Files.hh>
|
|
#include <ebcl/SRDText.hh>
|
|
#include <ebcl/SRDParser.hh>
|
|
using ebcl::T_SRDParserConfig;
|
|
|
|
|
|
/*= SRD parser for the curves ================================================*/
|
|
|
|
namespace {
|
|
|
|
struct T_ParserOutput_
|
|
{
|
|
T_SyncCurves curves;
|
|
T_Optional< T_SyncTime > time;
|
|
ebcl::T_SRDLocation tLocation;
|
|
};
|
|
|
|
using namespace ebcl;
|
|
|
|
bool CPEnterCurve_( T_SRDParserData const& data )
|
|
{
|
|
*( data.targetData ) = T_SyncCurve{ (*data.input)[ 0 ].stringValue( ) };
|
|
return true;
|
|
}
|
|
|
|
bool CPExitCurve_( T_SRDParserData const& data )
|
|
{
|
|
auto& curve( data.currentData->value< T_SyncCurve >( ) );
|
|
auto& po( *( data.targetData->value< T_SharedPtr< T_ParserOutput_ > >( ) ) );
|
|
|
|
if ( curve.segments.empty( ) ) {
|
|
T_StringBuilder sb;
|
|
sb << "curve '" << curve.name << "' is empty";
|
|
data.errors.add( std::move( sb ) , (*data.input)[ 0 ] );
|
|
}
|
|
|
|
if ( po.curves.curves.contains( curve.name ) ) {
|
|
T_StringBuilder sb;
|
|
sb << "duplicate curve '" << curve.name << "'";
|
|
data.errors.add( std::move( sb ) , (*data.input)[ 0 ] );
|
|
} else {
|
|
po.curves.setCurve( std::move( curve ) );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void CPHandleSegment_(
|
|
const T_SyncSegment::E_SegmentType type ,
|
|
T_SRDList const& lValues ,
|
|
T_SRDList const& lDurations ,
|
|
T_SRDErrors& errors ,
|
|
T_SyncCurve& curve )
|
|
{
|
|
bool failed = false;
|
|
if ( lDurations.size( ) != lValues.size( ) - 1 ) {
|
|
errors.add( "values / durations count mismatch" , lValues[ 0 ] );
|
|
failed = true;
|
|
}
|
|
|
|
// Check durations
|
|
const auto nd( lDurations.size( ) );
|
|
for ( auto i = 1u ; i < nd ; i ++ ) {
|
|
auto const& tok( lDurations[ i ] );
|
|
const uint64_t v( tok.longValue( ) );
|
|
if ( v < 1 || v > UINT32_MAX ) {
|
|
errors.add( "invalid duration" , tok );
|
|
failed = true;
|
|
}
|
|
}
|
|
|
|
if ( !failed ) {
|
|
T_SyncSegment& segment( curve.segments.addNew( ) );
|
|
segment.type = type;
|
|
segment.durations.ensureCapacity( nd - 1 );
|
|
for ( auto i = 1u ; i < nd ; i ++ ) {
|
|
auto const& tok( lDurations[ i ] );
|
|
segment.durations.add( uint32_t( tok.longValue( ) ) );
|
|
}
|
|
|
|
segment.values.ensureCapacity( nd );
|
|
for ( auto i = 1u ; i <= nd ; i ++ ) {
|
|
auto const& tok( lValues[ i ] );
|
|
segment.values.add( tok.floatValue( ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
bool CPSegmentVD_( T_SRDParserData const& data )
|
|
{
|
|
auto const& input( *data.input );
|
|
const auto ev( data.config.enumValue( "segment-type" , input[ 1 ].stringValue( ) ) );
|
|
assert( ev );
|
|
CPHandleSegment_( (T_SyncSegment::E_SegmentType) *ev ,
|
|
input[ 2 ].list( ) , input[ 3 ].list( ) ,
|
|
data.errors , data.targetData->value< T_SyncCurve >( ) );
|
|
return true;
|
|
}
|
|
|
|
bool CPSegmentDV_( T_SRDParserData const& data )
|
|
{
|
|
auto const& input( *data.input );
|
|
const auto ev( data.config.enumValue( "segment-type" , input[ 1 ].stringValue( ) ) );
|
|
assert( ev );
|
|
CPHandleSegment_( (T_SyncSegment::E_SegmentType) *ev ,
|
|
input[ 3 ].list( ) , input[ 2 ].list( ) ,
|
|
data.errors , data.targetData->value< T_SyncCurve >( ) );
|
|
return true;
|
|
}
|
|
|
|
bool CPSetDuration_( T_SRDParserData const& data )
|
|
{
|
|
auto const& input( *data.input );
|
|
auto& po( *( data.currentData->value< T_SharedPtr< T_ParserOutput_ > >( ) ) );
|
|
if ( po.time ) {
|
|
T_StringBuilder eb;
|
|
eb << "duplicate duration specification; previous: "
|
|
<< po.tLocation;
|
|
data.errors.add( std::move( eb ) , input[ 0 ].location( ) );
|
|
} else {
|
|
po.time = T_SyncTime( );
|
|
po.time->uDuration = std::min( 2. , std::max( 1. / 60. , input[ 1 ].floatValue( ) ) );
|
|
po.time->iDuration = std::max( 1l , input[ 2 ].longValue( ) );
|
|
}
|
|
return true;
|
|
}
|
|
|
|
T_SRDParserConfig MakeCurvesParser_( )
|
|
{
|
|
using namespace ebcl::SRD;
|
|
|
|
T_SRDParserDefs defs( "default" );
|
|
defs << OnStart( []( T_SRDParserData const& data ) -> bool {
|
|
*( data.currentData ) = NewShared< T_ParserOutput_ >( );
|
|
return true;
|
|
} );
|
|
|
|
defs.enumeration( "segment-type" )
|
|
<< "linear" << "ramp" << "smooth";
|
|
|
|
defs.context( "default" )
|
|
<< ( Rule( ) << Text( ) << EnterContext( "segments" )
|
|
<< OnEnter( CPEnterCurve_ ) << OnExit( CPExitCurve_ ) )
|
|
<< ( Rule( ) << "duration" << Float( ) << Int32( )
|
|
<< CPSetDuration_ );
|
|
defs.context( "segments" )
|
|
<< ( Rule( ) << "segment" << Enum( "segment-type" )
|
|
<< ( List( ) << "values" << ( AtLeast( 2 ) << Numeric( ) ) )
|
|
<< ( List( ) << "durations" << ( AtLeast( 1 ) << Integer( ) ) )
|
|
<< CPSegmentVD_ )
|
|
<< ( Rule( ) << "segment" << Enum( "segment-type" )
|
|
<< ( List( ) << "durations" << ( AtLeast( 1 ) << Integer( ) ) )
|
|
<< ( List( ) << "values" << ( AtLeast( 2 ) << Numeric( ) ) )
|
|
<< CPSegmentDV_ );
|
|
|
|
return defs;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/*= T_SyncTime ===============================================================*/
|
|
|
|
void T_SyncTime::setDuration(
|
|
const float uDuration ,
|
|
const uint32_t iDuration )
|
|
{
|
|
this->uDuration = std::max( 1e-3f , uDuration );
|
|
this->iDuration = std::max( 1u , iDuration );
|
|
time = std::min( time , duration( ) );
|
|
}
|
|
|
|
|
|
/*= T_SyncSegment ============================================================*/
|
|
|
|
uint32_t T_SyncSegment::findTimeOfPoint(
|
|
const uint32_t index ) const noexcept
|
|
{
|
|
assert( index <= durations.size( ) );
|
|
uint32_t t{ 0 };
|
|
for ( auto i = 0u ; i < index ; i ++ ) {
|
|
t += durations[ i ];
|
|
}
|
|
return t;
|
|
}
|
|
|
|
float T_SyncSegment::computeValue(
|
|
const float relTimeUnits ) const noexcept
|
|
{
|
|
uint32_t part{ 0 } , begin{ 0 } , end{ durations[ 0 ] };
|
|
while ( end < relTimeUnits ) {
|
|
part ++;
|
|
begin = end;
|
|
end += durations[ part ];
|
|
}
|
|
|
|
const float v0{ ( relTimeUnits - begin ) / ( end - begin ) };
|
|
float v = v0;
|
|
if ( type != T_SyncSegment::LINEAR ) {
|
|
v *= v0;
|
|
if ( type == T_SyncSegment::SMOOTH ) {
|
|
v *= 3 - 2 * v0;
|
|
}
|
|
}
|
|
|
|
const float sv{ values[ part ] };
|
|
return v * ( values[ part + 1 ] - sv ) + sv;
|
|
}
|
|
|
|
M_LSHIFT_OP( T_StringBuilder , T_SyncSegment::E_SegmentType )
|
|
{
|
|
switch ( value ) {
|
|
case T_SyncSegment::LINEAR:
|
|
obj << "linear";
|
|
break;
|
|
case T_SyncSegment::RAMP:
|
|
obj << "ramp";
|
|
break;
|
|
case T_SyncSegment::SMOOTH:
|
|
obj << "smooth";
|
|
break;
|
|
}
|
|
return obj;
|
|
}
|
|
|
|
|
|
/*= T_SyncCurve ==============================================================*/
|
|
|
|
float T_SyncCurve::computeValue(
|
|
const float timeUnits ) const noexcept
|
|
{
|
|
assert( timeUnits >= 0 );
|
|
|
|
uint32_t segStart{ 0 } , prevSegStart{ 0 };
|
|
auto it{ segments.begin( ) };
|
|
while ( segStart < timeUnits && it != segments.end( ) ) {
|
|
prevSegStart = segStart;
|
|
for ( auto d : it->durations ) {
|
|
segStart += d;
|
|
}
|
|
++ it;
|
|
}
|
|
-- it;
|
|
if ( segStart < timeUnits ) {
|
|
return it->values.last( );
|
|
}
|
|
return it->computeValue( timeUnits - prevSegStart );
|
|
}
|
|
|
|
E_SyncCurveMatch T_SyncCurve::matches(
|
|
T_SyncCurve const& other ) const noexcept
|
|
{
|
|
const auto tSegs{ this->segments.size( ) };
|
|
const auto oSegs{ other.segments.size( ) };
|
|
const auto nSegs{ std::min( tSegs , oSegs ) };
|
|
for ( auto i = 0u ; i < nSegs ; i ++ ) {
|
|
auto const& tDur{ this->segments[ i ].durations };
|
|
auto const& oDur{ other.segments[ i ].durations };
|
|
const bool lastSeg{ i == nSegs - 1 };
|
|
if ( !lastSeg && tDur.size( ) != oDur.size( ) ) {
|
|
return E_SyncCurveMatch::MISMATCH;
|
|
}
|
|
|
|
const auto nd{ std::min( tDur.size( ) , oDur.size( ) ) };
|
|
for ( auto j = 0u ; j < nd ; j ++ ) {
|
|
if ( tDur[ j ] != oDur[ j ] ) {
|
|
return E_SyncCurveMatch::MISMATCH;
|
|
}
|
|
}
|
|
if ( tDur.size( ) != oDur.size( ) ) {
|
|
return tDur.size( ) < oDur.size( )
|
|
? E_SyncCurveMatch::LASTSEG_SHORT
|
|
: E_SyncCurveMatch::LASTSEG_LONG;
|
|
}
|
|
}
|
|
if ( tSegs == oSegs ) {
|
|
return E_SyncCurveMatch::IDENTICAL;
|
|
}
|
|
return tSegs < oSegs
|
|
? E_SyncCurveMatch::MATCHING_SHORT
|
|
: E_SyncCurveMatch::MATCHING_LONG;
|
|
}
|
|
|
|
uint32_t T_SyncCurve::points( ) const noexcept
|
|
{
|
|
uint32_t np{ 1 };
|
|
for ( auto const& s : segments ) {
|
|
np += s.durations.size( );
|
|
}
|
|
return np;
|
|
}
|
|
|
|
|
|
/*= T_SyncCurves =============================================================*/
|
|
|
|
int32_t T_SyncCurves::indexOf(
|
|
T_String const& name ) noexcept
|
|
{
|
|
const auto idx( curves.indexOf( name ) );
|
|
return idx == ebcl::T_HashIndex::INVALID_INDEX ? -1 : int32_t( idx );
|
|
}
|
|
|
|
|
|
/*= T_SyncCurveCache =========================================================*/
|
|
|
|
T_SyncCurveCache::T_SyncCurveCache(
|
|
T_SyncTime const& time ,
|
|
T_SyncCurves const& curves ,
|
|
const uint32_t curve ) noexcept
|
|
: curve( curve ) , curPos( 0 )
|
|
{
|
|
auto const& c( curves.curves[ curve ] );
|
|
const auto ns( c.segments.size( ) );
|
|
assert( ns > 0 );
|
|
|
|
uint32_t s = 0;
|
|
for ( auto i = 0u ; i < ns ; i ++ ) {
|
|
auto const& v( c.segments[ i ] );
|
|
assert( v.durations.size( ) == v.values.size( ) - 1 );
|
|
|
|
const auto nd( v.durations.size( ) );
|
|
for ( auto j = 0u ; j < nd ; j ++ ) {
|
|
const auto sStart( s * time.uDuration );
|
|
if ( time.time >= sStart ) {
|
|
curPos = segStarts.size( );
|
|
}
|
|
segStarts.add( sStart );
|
|
segRefs.add( std::make_pair( i , j ) );
|
|
s += v.durations[ j ];
|
|
segEnds.add( std::min( s , time.iDuration ) * time.uDuration );
|
|
if ( s > time.iDuration ) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
uint32_t T_SyncCurveCache::findSegment(
|
|
const float time ) const noexcept
|
|
{
|
|
const auto ns( segStarts.size( ) );
|
|
for ( auto i = 0u ; i < ns ; i ++ ) {
|
|
if ( segStarts[ i ] <= time && segEnds[ i ] > time ) {
|
|
return i;
|
|
}
|
|
}
|
|
return ns;
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
float T_SyncCurveCache::valueAt(
|
|
T_SyncTime const& time ,
|
|
T_SyncCurves const& curves ,
|
|
const float position ) const noexcept
|
|
{
|
|
return segmentValue( time.time , findSegment( position ) ,
|
|
curves.curves[ curve ].segments );
|
|
}
|
|
|
|
float T_SyncCurveCache::value(
|
|
T_SyncTime const& time ,
|
|
T_SyncCurves const& curves ) noexcept
|
|
{
|
|
const auto t( time.time );
|
|
|
|
// Check / update curPos
|
|
const float ss0( curPos == segStarts.size( )
|
|
? time.duration( )
|
|
: segStarts[ curPos ] );
|
|
if ( ss0 > t ) {
|
|
curPos = findSegment( t );
|
|
} else {
|
|
while ( curPos < segStarts.size( ) && t >= segEnds[ curPos ] ) {
|
|
curPos ++;
|
|
}
|
|
}
|
|
|
|
// We got the actual index in curPos, now compute the value.
|
|
return segmentValue( t , curPos , curves.curves[ curve ].segments );
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
float T_SyncCurveCache::segmentValue(
|
|
float time ,
|
|
uint32_t segIndex ,
|
|
T_Array< T_SyncSegment > const& segments ) const noexcept
|
|
{
|
|
const auto sss( segStarts.size( ) );
|
|
if ( segIndex >= sss ) {
|
|
assert( sss != 0 );
|
|
segIndex = sss - 1;
|
|
time = segEnds[ segIndex ];
|
|
}
|
|
|
|
auto const& idxp( segRefs[ segIndex ] );
|
|
auto const& seg( segments[ idxp.first ] );
|
|
|
|
// Interpolation factor
|
|
const float st( segStarts[ segIndex ] );
|
|
const float et( segEnds[ segIndex ] );
|
|
const float v0 = ( time - st ) / ( et - st );
|
|
float v = v0;
|
|
if ( seg.type != T_SyncSegment::LINEAR ) {
|
|
v *= v0;
|
|
if ( seg.type == T_SyncSegment::SMOOTH ) {
|
|
v *= 3 - 2 * v0;
|
|
}
|
|
}
|
|
|
|
const auto pid( idxp.second );
|
|
const float sv( seg.values[ pid ] );
|
|
const float ev( seg.values[ pid + 1 ] );
|
|
#if 0
|
|
printf( "[%.2f] gidx %d - seg %d idx %d - %f\n" , time , segIndex ,
|
|
idxp.first , idxp.second , v * ( ev - sv ) + sv );
|
|
#endif
|
|
return v * ( ev - sv ) + sv;
|
|
}
|
|
|
|
|
|
/*= T_SyncValues =============================================================*/
|
|
|
|
T_SyncValues::T_SyncValues( )
|
|
{
|
|
values.add( 0 );
|
|
}
|
|
|
|
// ---------------------------------------------------------------------
|
|
|
|
void T_SyncValues::clear( )
|
|
{
|
|
index.clear( );
|
|
identifiers.clear( );
|
|
values.clear( );
|
|
overriden.clear( );
|
|
values.add( 0 );
|
|
}
|
|
|
|
bool T_SyncValues::addValue(
|
|
T_String const& name ,
|
|
const float initial ) noexcept
|
|
{
|
|
const uint32_t hash{ ComputeHash( name ) };
|
|
uint32_t existing{ index.first( hash ) };
|
|
while ( existing != T_HashIndex::INVALID_INDEX ) {
|
|
if ( name == identifiers[ existing ] ) {
|
|
return false;
|
|
}
|
|
existing = index.next( existing );
|
|
}
|
|
|
|
const auto li( values.size( ) );
|
|
index.add( hash );
|
|
identifiers.add( name );
|
|
values.add( initial );
|
|
std::swap( values[ li ] , values[ li - 1 ] );
|
|
overriden.add( false );
|
|
return true;
|
|
}
|
|
|
|
uint32_t T_SyncValues::indexOf(
|
|
T_String const& name ) const noexcept
|
|
{
|
|
const uint32_t hash{ ComputeHash( name ) };
|
|
uint32_t existing{ index.first( hash ) };
|
|
while ( existing != T_HashIndex::INVALID_INDEX ) {
|
|
if ( name == identifiers[ existing ] ) {
|
|
return existing;
|
|
}
|
|
existing = index.next( existing );
|
|
}
|
|
return values.size( ) - 1;
|
|
}
|
|
|
|
|
|
/*= A_SyncOverride ===========================================================*/
|
|
|
|
A_SyncOverride::A_SyncOverride(
|
|
char const* const type ,
|
|
T_String const& title ) noexcept
|
|
: type_( T_String::Pooled( type ) ) , title_( title.size( ) + 1 )
|
|
{
|
|
char const* src( title.data( ) );
|
|
for ( auto i = 0u ; i < title_.size( ) - 1 ; i ++ ) {
|
|
title_[ i ] = src[ i ];
|
|
}
|
|
title_[ title_.size( ) - 1 ] = 0;
|
|
}
|
|
|
|
A_SyncOverride::~A_SyncOverride( ) { }
|
|
|
|
void A_SyncOverride::setup( ) noexcept
|
|
{
|
|
const auto ni( inputs_.size( ) );
|
|
assert( ni != 0 );
|
|
assert( inputPos_.size( ) == 0 );
|
|
|
|
T_StringBuilder sb;
|
|
inputPos_.ensureCapacity( ni );
|
|
for ( auto i = 0u ; i < ni ; i ++ ) {
|
|
// FIXME: insufficient; the manager should be made aware of
|
|
// the presence of an override for that value (and it should
|
|
// fail for missing values).
|
|
inputPos_.add( Common::Sync( ).inputPos( inputs_[ i ] ) );
|
|
if ( sb.size( ) ) {
|
|
sb << ';';
|
|
}
|
|
sb << inputs_[ i ];
|
|
}
|
|
id_ = std::move( sb );
|
|
}
|
|
|
|
|
|
/*= T_SyncOverrideSection ====================================================*/
|
|
|
|
T_SyncOverrideSection::T_SyncOverrideSection(
|
|
T_String title ) noexcept
|
|
: title( std::move( title ) ) ,
|
|
cTitle( this->title.toOSString( ) )
|
|
{ }
|
|
|
|
void T_SyncOverrideSection::merge(
|
|
T_SyncOverrideSection& other ) noexcept
|
|
{
|
|
for ( auto& os : other.subsections ) {
|
|
section( os->title ).merge( *os );
|
|
}
|
|
for ( auto& ov : other.overrides ) {
|
|
overrides.add( ov->clone( ) );
|
|
}
|
|
}
|
|
|
|
T_SyncOverrideSection& T_SyncOverrideSection::section(
|
|
T_String const& name ) noexcept
|
|
{
|
|
for ( auto& ov : subsections ) {
|
|
if ( ov->title == name ) {
|
|
return *ov;
|
|
}
|
|
}
|
|
return *subsections[ subsections.add(
|
|
NewOwned< T_SyncOverrideSection >( name ) ) ];
|
|
}
|
|
|
|
T_SyncOverrideSection const* T_SyncOverrideSection::section(
|
|
T_String const& name ) const noexcept
|
|
{
|
|
for ( auto& ov : subsections ) {
|
|
if ( ov->title == name ) {
|
|
return ov.get( );
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
|
|
/*= T_SyncOverrideVisitor ====================================================*/
|
|
|
|
T_SyncOverrideVisitor::T_OpElement T_SyncOverrideVisitor::nodeBrowser(
|
|
const T_Element element ,
|
|
const uint32_t child )
|
|
{
|
|
if ( element.hasType< A_SyncOverride* >( ) ) {
|
|
return {};
|
|
}
|
|
|
|
auto& section( *element.value< T_SyncOverrideSection* >( ) );
|
|
const auto nss( section.subsections.size( ) );
|
|
if ( child < nss ) {
|
|
return T_OpElement{ section.subsections[ child ].get( ) };
|
|
}
|
|
if ( child - nss < section.overrides.size( ) ) {
|
|
return T_OpElement{ section.overrides[ child - nss ].get( ) };
|
|
}
|
|
return {};
|
|
}
|
|
|
|
|
|
/*= T_SyncManager ============================================================*/
|
|
|
|
T_SyncManager::T_SyncManager( ) noexcept
|
|
: pConfig_( MakeCurvesParser_( ) ) ,
|
|
watcher_{ Common::Watcher( ) , [this](){ curvesChanged_( ); } } ,
|
|
soRoot_( "*root*" )
|
|
{
|
|
auto& p{ Common::Project( ) };
|
|
p.addListener( this );
|
|
curvesFile_ = p.pathOf( "curves.srd" );
|
|
watcher_.watch( curvesFile_ );
|
|
loadCurves( false );
|
|
}
|
|
|
|
T_SyncManager::~T_SyncManager( )
|
|
{
|
|
Common::Project( ).removeListener( this );
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
void T_SyncManager::setDuration(
|
|
const float uDuration ,
|
|
const uint32_t iDuration )
|
|
{
|
|
time_.setDuration( uDuration , iDuration );
|
|
updateCurveCaches( );
|
|
}
|
|
|
|
void T_SyncManager::setTime(
|
|
const float time )
|
|
{
|
|
time_.setTime( time );
|
|
updateValues( );
|
|
playing_ = playing_ && !finished( );
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
void T_SyncManager::clearInputs( ) noexcept
|
|
{
|
|
clearOverrides( );
|
|
values_.clear( );
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
void T_SyncManager::clearCurves( )
|
|
{
|
|
curves_.clear( );
|
|
updateCurveCaches( );
|
|
modified_ = true;
|
|
}
|
|
|
|
void T_SyncManager::setCurve(
|
|
T_SyncCurve curve )
|
|
{
|
|
curves_.setCurve( curve );
|
|
updateCurveCaches( );
|
|
modified_ = true;
|
|
}
|
|
|
|
void T_SyncManager::removeCurve(
|
|
T_String const& curve ) noexcept
|
|
{
|
|
if ( curves_.removeCurve( curve ) ) {
|
|
updateCurveCaches( );
|
|
modified_ = true;
|
|
}
|
|
}
|
|
|
|
T_SyncCurve const* T_SyncManager::getCurve(
|
|
T_String const& name ) const noexcept
|
|
{
|
|
return curves_.curves.get( name );
|
|
}
|
|
|
|
void T_SyncManager::curvesChanged_( )
|
|
{
|
|
if ( saving_ ) {
|
|
saving_ = false;
|
|
return;
|
|
}
|
|
if ( modified_ ) {
|
|
fileChanged_ = true;
|
|
} else {
|
|
loadCurves( );
|
|
}
|
|
}
|
|
|
|
bool T_SyncManager::loadCurves(
|
|
const bool undoable )
|
|
{
|
|
printf( "Loading curves data\n" );
|
|
T_SharedPtr< T_ParserOutput_ > p;
|
|
try {
|
|
using namespace ebcl;
|
|
const T_SRDParserConfig cfg( MakeCurvesParser_( ) );
|
|
T_File file( curvesFile_ , E_FileMode::READ_ONLY );
|
|
file.open( );
|
|
|
|
T_FileInputStream fis( file );
|
|
T_SRDParser parser( cfg );
|
|
T_SRDTextReader reader( parser );
|
|
reader.read( curvesFile_ , fis );
|
|
|
|
p = parser.getData< T_SharedPtr< T_ParserOutput_ > >( );
|
|
} catch ( ebcl::X_StreamError const& e ) {
|
|
printf( "... ERR %s\n" , e.what( ) );
|
|
return false;
|
|
|
|
} catch ( ebcl::X_SRDErrors const& e ) {
|
|
T_StringBuilder sb;
|
|
auto const ne( e.errors.size( ) );
|
|
for ( auto i = 0u ; i < ne ; i ++ ) {
|
|
auto const& err( e.errors[ i ] );
|
|
sb << "... ERR " << err.location( ) << ": " << err.error( ) << '\n';
|
|
}
|
|
sb << '\0';
|
|
printf( "%s" , sb.data( ) );
|
|
return false;
|
|
}
|
|
printf( "... success\n" );
|
|
|
|
assert( p );
|
|
if ( undoable ) {
|
|
addReloadUndoData_( (void*)(T_ParserOutput_*) p );
|
|
}
|
|
curves_ = std::move( p->curves );
|
|
if ( p->time ) {
|
|
time_.iDuration = p->time->iDuration;
|
|
time_.uDuration = p->time->uDuration;
|
|
} else {
|
|
time_.iDuration = 3600;
|
|
time_.uDuration = 1.f / 60.f;
|
|
}
|
|
|
|
updateCurveCaches( );
|
|
modified_ = fileChanged_ = false;
|
|
return true;
|
|
}
|
|
|
|
bool T_SyncManager::saveCurves( )
|
|
{
|
|
T_StringBuilder sb;
|
|
sb << "(duration " << time_.uDuration << ' ' << time_.iDuration
|
|
<< ")\n";
|
|
for ( auto const& curve : curves_.curves.values( ) ) {
|
|
sb << "\n(" << curve.name << '\n';
|
|
for ( auto const& s : curve.segments ) {
|
|
sb << "\t(segment " << s.type << "\n\t\t(values";
|
|
for ( auto v : s.values ) {
|
|
sb << ' ' << v;
|
|
}
|
|
sb << ")\n\t\t(durations";
|
|
for ( auto d : s.durations ) {
|
|
sb << ' ' << d;
|
|
}
|
|
sb << ")\n\t)\n";
|
|
}
|
|
sb << ")\n";
|
|
}
|
|
|
|
saving_ = true;
|
|
printf( "Saving curves...\n" );
|
|
try {
|
|
T_File out{ "curves.srd" , E_FileMode::OVERWRITE };
|
|
out.write( sb.data( ) , sb.size( ) );
|
|
} catch ( ebcl::X_StreamError const& e ) {
|
|
printf( "... ERR %s\n" , e.what( ) );
|
|
return false;
|
|
}
|
|
printf( "... OK\n" );
|
|
fileChanged_ = modified_ = false;
|
|
return true;
|
|
}
|
|
|
|
void T_SyncManager::addReloadUndoData_(
|
|
void* const data ) const noexcept
|
|
{
|
|
T_ParserOutput_& p{ *(T_ParserOutput_*)data };
|
|
auto& undo{ Common::Undo( ).add< T_UndoDurationChanges >(
|
|
p.time ? p.time->iDuration : 3600 ,
|
|
time_.iDuration ,
|
|
p.time ? p.time->uDuration : ( 1.f / 60.f ) ,
|
|
time_.uDuration ) };
|
|
|
|
auto& nCurves{ p.curves.curves };
|
|
for ( auto const& curve : curves_.curves.values( ) ) {
|
|
auto const* const nc{ nCurves.get( curve.name ) };
|
|
if ( nc ) {
|
|
undo.curveReplacement( curve , *nc );
|
|
} else {
|
|
undo.curveDeletion( curve );
|
|
}
|
|
}
|
|
for ( auto const& nc : nCurves.values( ) ) {
|
|
if ( !curves_.curves.contains( nc.name ) ) {
|
|
undo.curveCreation( nc );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
void T_SyncManager::updateCurveCaches( )
|
|
{
|
|
curveCaches_.clear( );
|
|
const uint32_t nv( values_.identifiers.size( ) );
|
|
for ( auto i = 0u ; i < nv ; i ++ ) {
|
|
auto const& id( values_.identifiers[ i ] );
|
|
const auto cp( curves_.indexOf( id ) );
|
|
if ( cp < 0 ) {
|
|
curveCaches_.addNew( );
|
|
} else {
|
|
curveCaches_.add( NewOwned< T_SyncCurveCache >(
|
|
time_ , curves_ , cp
|
|
) );
|
|
}
|
|
}
|
|
updateValues( );
|
|
}
|
|
|
|
void T_SyncManager::updateValues( )
|
|
{
|
|
const auto nv( values_.identifiers.size( ) );
|
|
assert( nv == curveCaches_.size( ) );
|
|
for ( auto i = 0u ; i < nv ; i ++ ) {
|
|
auto const& cc( curveCaches_[ i ] );
|
|
if ( !cc || values_.overriden[ i ] ) {
|
|
continue;
|
|
}
|
|
values_.values[ i ] = cc->value( time_ , curves_ );
|
|
}
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
void T_SyncManager::clearOverrides( ) noexcept
|
|
{
|
|
soRoot_.subsections.clear( );
|
|
soRoot_.overrides.clear( );
|
|
soTable_.clear( );
|
|
}
|
|
|
|
void T_SyncManager::mergeOverrides(
|
|
T_SyncOverrideSection& overrides )
|
|
{
|
|
assert( overrides.overrides.empty( ) );
|
|
soRoot_.merge( overrides );
|
|
|
|
T_StringBuilder sb;
|
|
T_AutoArray< uint32_t , 16 > sbParts;
|
|
soVisitor_.visitor.visit( &soRoot_ ,
|
|
[&]( T_SyncOverrideVisitor::T_Element node , bool exit ) -> bool {
|
|
if ( node.hasType< T_SyncOverrideSection* >( ) ) {
|
|
T_SyncOverrideSection const& sos{
|
|
*node.value< T_SyncOverrideSection* >( )
|
|
};
|
|
if ( sos.title == "*root*" ) {
|
|
return true;
|
|
}
|
|
|
|
if ( exit ) {
|
|
sb.truncate( sbParts.last( ) );
|
|
sbParts.removeLast( );
|
|
} else {
|
|
sbParts.add( sb.length( ) );
|
|
sb << sos.title << " \u00bb ";
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if ( exit ) {
|
|
return true;
|
|
}
|
|
|
|
auto& ovr( *node.value< A_SyncOverride* >( ) );
|
|
ovr.setup( );
|
|
soTable_.add( ovr.id( ) , &ovr );
|
|
|
|
const uint32_t sblen{ sb.length( ) };
|
|
sb << ovr.title( );
|
|
ovr.fullTitle( sb );
|
|
sb.truncate( sblen );
|
|
|
|
return false;
|
|
}
|
|
);
|
|
}
|
|
|
|
bool T_SyncManager::overrideExists(
|
|
T_String const& id ) const noexcept
|
|
{
|
|
return soTable_.contains( id );
|
|
}
|
|
|
|
A_SyncOverride* T_SyncManager::getOverride(
|
|
T_String const& id ) const noexcept
|
|
{
|
|
const auto rv{ soTable_.get( id ) };
|
|
return rv ? *rv : nullptr;
|
|
}
|
|
|
|
void T_SyncManager::setOverridesActive(
|
|
const bool active ,
|
|
const uint32_t n ,
|
|
uint32_t const* const pos )
|
|
{
|
|
for ( auto i = 0u ; i < n ; i ++ ) {
|
|
assert( values_.overriden[ pos[ i ] ] != active );
|
|
values_.overriden[ pos[ i ] ] = active;
|
|
}
|
|
if ( !active ) {
|
|
for ( auto i = 0u ; i < n ; i ++ ) {
|
|
const auto p{ pos[ i ] };
|
|
auto const& cc( curveCaches_[ p ] );
|
|
if ( cc ) {
|
|
values_.values[ p ] = cc->value( time_ , curves_ );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void T_SyncManager::visitOverrides(
|
|
T_SyncOverrideVisitor::F_NodeAction visitor )
|
|
{
|
|
soVisitor_.visitor.visit( &soRoot_ , visitor );
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
void T_SyncManager::projectPathChanged( ) noexcept
|
|
{
|
|
curvesFile_ = Common::Project( ).pathOf( "curves.srd" );
|
|
watcher_.clear( );
|
|
watcher_.watch( curvesFile_ );
|
|
loadCurves( false );
|
|
}
|