demotool/sync.cc

768 lines
18 KiB
C++
Raw Normal View History

2017-10-07 16:56:20 +02:00
#include "externals.hh"
#include "sync.hh"
2017-10-31 14:21:42 +01:00
#include "globals.hh"
2017-11-05 21:06:38 +01:00
#include <ebcl/Files.hh>
#include <ebcl/SRDText.hh>
#include <ebcl/SRDParser.hh>
using ebcl::T_SRDParserConfig;
2017-10-31 14:21:42 +01:00
namespace {
const std::map< std::string , T_SyncSegment::E_SegmentType > SegmentTypes_( ([] {
std::map< std::string , T_SyncSegment::E_SegmentType > t;
t.emplace( "linear" , T_SyncSegment::LINEAR );
t.emplace( "ramp" , T_SyncSegment::RAMP );
t.emplace( "smooth" , T_SyncSegment::SMOOTH );
return t;
})());
}
2017-10-07 16:56:20 +02:00
2017-11-05 21:06:38 +01:00
/*= SRD parser for the curves ================================================*/
namespace {
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 )
{
T_SyncCurve& curve( data.currentData->value< T_SyncCurve >( ) );
T_SyncCurves& curves( *( data.targetData->value< T_SharedPtr< T_SyncCurves > >( ) ) );
if ( curve.segments.empty( ) ) {
T_StringBuilder sb;
sb << "curve '" << curve.name << "' is empty";
data.errors.add( std::move( sb ) , (*data.input)[ 0 ] );
}
if ( curves.curves.contains( curve.name ) ) {
T_StringBuilder sb;
sb << "duplicate curve '" << curve.name << "'";
data.errors.add( std::move( sb ) , (*data.input)[ 0 ] );
} else {
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( ) ) );
2017-11-08 09:09:21 +01:00
assert( ev );
CPHandleSegment_( (T_SyncSegment::E_SegmentType) *ev ,
2017-11-05 21:06:38 +01:00
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( ) ) );
2017-11-08 09:09:21 +01:00
assert( ev );
CPHandleSegment_( (T_SyncSegment::E_SegmentType) *ev ,
2017-11-05 21:06:38 +01:00
input[ 3 ].list( ) , input[ 2 ].list( ) ,
data.errors , data.targetData->value< T_SyncCurve >( ) );
return true;
}
T_SRDParserConfig MakeCurvesParser_( )
{
using namespace ebcl::SRD;
T_SRDParserDefs defs( "default" );
defs << OnStart( []( T_SRDParserData const& data ) -> bool {
*( data.currentData ) = NewShared< T_SyncCurves >( );
return true;
} );
defs.enumeration( "segment-type" )
<< "linear" << "ramp" << "smooth";
defs.context( "default" )
<< ( Rule( ) << Text( ) << EnterContext( "segments" )
<< OnEnter( CPEnterCurve_ ) << OnExit( CPExitCurve_ ) );
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(
2017-11-03 09:08:19 +01:00
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( ) );
}
2017-10-30 18:29:52 +01:00
/*= T_SyncCurves =============================================================*/
void T_SyncCurves::clear( )
{
curves.clear( );
}
2017-11-03 09:08:19 +01:00
void T_SyncCurves::setCurve(
T_SyncCurve curve )
{
2017-11-03 09:08:19 +01:00
curves.set( std::move( curve ) );
}
2017-11-03 09:08:19 +01:00
int32_t T_SyncCurves::indexOf(
T_String const& name ) noexcept
{
2017-11-03 09:08:19 +01:00
const auto idx( curves.indexOf( name ) );
return idx == ebcl::T_HashIndex::INVALID_INDEX ? -1 : int32_t( idx );
}
/*= T_SyncCurveCache =========================================================*/
T_SyncCurveCache::T_SyncCurveCache(
2017-11-03 09:08:19 +01:00
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 ] );
2017-11-05 21:06:38 +01:00
assert( v.durations.size( ) == v.values.size( ) - 1 );
2017-11-05 21:06:38 +01:00
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( );
}
2017-11-03 09:08:19 +01:00
segStarts.add( sStart );
segRefs.add( std::make_pair( i , j ) );
s += v.durations[ j ];
2017-11-03 09:08:19 +01:00
segEnds.add( std::min( s , time.iDuration ) * time.uDuration );
if ( s > time.iDuration ) {
return;
}
}
}
}
/*----------------------------------------------------------------------------*/
uint32_t T_SyncCurveCache::findSegment(
2017-11-03 09:08:19 +01:00
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(
2017-11-03 09:08:19 +01:00
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(
2017-11-03 09:08:19 +01:00
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(
2017-11-03 09:08:19 +01:00
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( )
{
2017-11-03 09:08:19 +01:00
values.add( 0 );
}
// ---------------------------------------------------------------------
void T_SyncValues::clear( )
{
2017-11-03 09:08:19 +01:00
index.clear( );
identifiers.clear( );
values.clear( );
overriden.clear( );
2017-11-03 09:08:19 +01:00
values.add( 0 );
}
bool T_SyncValues::addValue(
2017-11-03 09:08:19 +01:00
T_String const& name ,
const float initial ) noexcept
{
2017-11-03 09:08:19 +01:00
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 );
}
2017-11-03 09:08:19 +01:00
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(
2017-11-03 09:08:19 +01:00
T_String const& name ) const noexcept
{
2017-11-03 09:08:19 +01:00
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 );
}
2017-11-03 09:08:19 +01:00
return values.size( ) - 1;
}
2017-11-16 09:55:56 +01:00
/*= 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;
}
2017-11-16 09:55:56 +01:00
A_SyncOverride::~A_SyncOverride( ) { }
void A_SyncOverride::setup( ) noexcept
{
const auto ni( inputs_.size( ) );
assert( ni != 0 );
assert( inputPos_.size( ) == 0 );
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( Globals::Sync( ).inputPos( inputs_[ i ] ) );
}
}
char const* A_SyncOverride::buildLabel(
uint32_t& counter ,
T_StringBuilder& sb ) noexcept
{
sb.clear( ) << "temp label " << counter << '\0';
counter ++;
return sb.data( );
}
void A_SyncOverride::makeUI(
uint32_t& counter ,
T_StringBuilder& sb ) noexcept
{
using namespace ImGui;
if ( Checkbox( &title_[ 0 ] , &enabled_ ) ) {
Globals::Sync( ).setOverridesActive( enabled_ , inputPos_.size( ) , &inputPos_[ 0 ] );
}
if ( !enabled_ ) {
PushItemFlag( ImGuiItemFlags_Disabled , true );
PushStyleVar( ImGuiStyleVar_Alpha , GetStyle( ).Alpha * 0.5f );
}
Indent( );
PushItemWidth( -1 );
makeEditWidgets( counter , sb );
PopItemWidth( );
Unindent( );
if ( !enabled_ ) {
PopItemFlag( );
PopStyleVar( );
2017-11-16 09:55:56 +01:00
}
}
/*= T_SyncOverrideSection ====================================================*/
T_SyncOverrideSection::T_SyncOverrideSection(
T_String title ) noexcept
: title( std::move( title ) ) ,
cTitle( this->title.toOSString( ) )
2017-11-16 09:55:56 +01:00
{ }
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( std::move( ov ) );
}
}
void T_SyncOverrideSection::makeUI(
uint32_t& counter ,
T_StringBuilder& tempSb ,
2017-11-16 09:55:56 +01:00
const bool topLevel ) noexcept
{
const bool display( topLevel
? ImGui::CollapsingHeader( &cTitle[ 0 ] )
: ImGui::TreeNode( &cTitle[ 0 ] ) );
2017-11-16 09:55:56 +01:00
if ( !display ) {
return;
}
for ( auto& os : subsections ) {
os->makeUI( counter , tempSb );
2017-11-16 09:55:56 +01:00
}
if ( subsections.size( ) && overrides.size( ) ) {
ImGui::Separator( );
}
for ( auto& ov : overrides ) {
ov->makeUI( counter , tempSb );
2017-11-16 09:55:56 +01:00
}
if ( !topLevel ) {
ImGui::TreePop( );
}
}
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 ============================================================*/
2017-11-05 21:06:38 +01:00
T_SyncManager::T_SyncManager( )
2017-11-16 09:55:56 +01:00
: pConfig_( MakeCurvesParser_( ) ) ,
soRoot_( "*root*" )
{ }
2017-11-05 21:06:38 +01:00
/*----------------------------------------------------------------------------*/
void T_SyncManager::setDuration(
2017-11-03 09:08:19 +01:00
const float uDuration ,
const uint32_t iDuration )
{
time_.setDuration( uDuration , iDuration );
updateCurveCaches( );
}
void T_SyncManager::setTime(
2017-11-03 09:08:19 +01:00
const float time )
{
time_.setTime( time );
updateValues( );
}
void T_SyncManager::updateTime( ) noexcept
{
if ( playing_ ) {
const float time( SDL_GetTicks( ) * 1e-3 );
if ( playingPrevious_ ) {
timeDelta( time - lastFrame_ );
playing_ = !finished( );
}
lastFrame_ = time;
}
playingPrevious_ = playing_;
}
/*----------------------------------------------------------------------------*/
2017-10-31 14:21:42 +01:00
void T_SyncManager::checkCurveFile( )
{
if ( watcher_ ) {
return;
}
2017-11-05 21:06:38 +01:00
watcher_ = NewOwned< T_WatchedFiles >(
Globals::Watcher( ) ,
[this] { curvesChanged_( ); } );
watcher_->watch( "curves.srd" );
2017-10-31 14:21:42 +01:00
bool missing;
2017-11-05 21:06:38 +01:00
loadCurves_( missing );
2017-10-31 14:21:42 +01:00
}
void T_SyncManager::clearCurves( )
{
curves_.clear( );
updateCurveCaches( );
}
void T_SyncManager::setCurve(
2017-11-03 09:08:19 +01:00
T_SyncCurve curve )
{
curves_.setCurve( curve );
updateCurveCaches( );
}
2017-10-31 14:21:42 +01:00
void T_SyncManager::curvesChanged_( )
{
bool missing;
2017-11-05 21:06:38 +01:00
loadCurves_( missing );
2017-10-31 14:21:42 +01:00
}
bool T_SyncManager::loadCurves_(
2017-11-03 09:08:19 +01:00
bool& missing )
2017-10-31 14:21:42 +01:00
{
2017-11-05 21:06:38 +01:00
printf( "Loading curves data\n" );
missing = true;
2017-10-31 14:21:42 +01:00
try {
2017-11-05 21:06:38 +01:00
using namespace ebcl;
const T_SRDParserConfig cfg( MakeCurvesParser_( ) );
T_File file( "curves.srd" , E_FileMode::READ_ONLY );
file.open( );
2017-10-31 14:21:42 +01:00
missing = false;
2017-11-05 21:06:38 +01:00
T_FileInputStream fis( file );
T_SRDParser parser( cfg );
T_SRDTextReader reader( parser );
reader.read( "curves.srd" , fis );
2017-10-31 14:21:42 +01:00
2017-11-05 21:06:38 +01:00
curves_ = std::move( *parser.getData< T_SharedPtr< T_SyncCurves > >( ) );
} catch ( ebcl::X_StreamError const& e ) {
printf( "... ERR %s\n" , e.what( ) );
2017-10-31 14:21:42 +01:00
return false;
2017-11-05 21:06:38 +01:00
} 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';
2017-10-31 14:21:42 +01:00
}
2017-11-05 21:06:38 +01:00
sb << '\0';
printf( "%s" , sb.data( ) );
2017-10-31 14:21:42 +01:00
return false;
}
2017-11-05 21:06:38 +01:00
printf( "... success\n" );
2017-10-31 14:21:42 +01:00
2017-11-05 21:06:38 +01:00
updateCurveCaches( );
2017-10-31 14:21:42 +01:00
return true;
}
/*----------------------------------------------------------------------------*/
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 ) {
2017-11-03 09:08:19 +01:00
curveCaches_.addNew( );
} else {
2017-11-03 09:08:19 +01:00
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_ );
}
}
2017-11-16 09:55:56 +01:00
/*----------------------------------------------------------------------------*/
void T_SyncManager::clearOverrides( ) noexcept
2017-11-16 09:55:56 +01:00
{
soRoot_.subsections.clear( );
soRoot_.overrides.clear( );
2017-11-16 09:55:56 +01:00
}
void T_SyncManager::mergeOverrides(
T_SyncOverrideSection& overrides )
{
assert( overrides.overrides.empty( ) );
soRoot_.merge( overrides );
T_SyncOverrideVisitor v;
v.visitor.visit( &soRoot_ ,
[]( T_SyncOverrideVisitor::T_Element node , bool exit ) -> bool {
if ( exit || node.hasType< T_SyncOverrideSection* >( ) ) {
return true;
}
auto& ovr( *node.value< A_SyncOverride* >( ) );
ovr.setup( );
return false;
}
);
}
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::makeSequencerWindow( )
{
if ( !tmWindow_ ) {
return;
}
using namespace ImGui;
auto const& dspSize( GetIO( ).DisplaySize );
SetNextWindowSize( ImVec2( dspSize.x , 150 ) , ImGuiSetCond_Appearing );
SetNextWindowPos( ImVec2( 0 , dspSize.y - 150 ) , ImGuiSetCond_Appearing );
Begin( "Sequencer" , &tmWindow_ , ImGuiWindowFlags_NoCollapse );
PushID( "playing" );
if ( Button( playing_ ? "Stop" : "Play" ) ) {
playing_ = !playing_;
2017-10-23 11:03:38 +02:00
}
PopID( );
const float d( duration( ) );
float tm( time( ) );
SameLine( );
PushID( "sequencer" );
PushItemWidth( -1 );
if ( SliderFloat( "" , &tm , 0 , d , "%.1fs" ) ) {
setTime( tm );
playing_ = playing_ && !finished( );
}
PopItemWidth( );
PopID( );
End( );
2017-10-23 11:03:38 +02:00
}
void T_SyncManager::makeOverridesWindow( )
2017-10-23 11:03:38 +02:00
{
if ( !ovWindow_ ) {
return;
}
using namespace ImGui;
auto const& dspSize( GetIO( ).DisplaySize );
SetNextWindowSize( ImVec2( 300 , dspSize.y - 170 ) , ImGuiSetCond_Appearing );
SetNextWindowPos( ImVec2( 0 , 20 ) , ImGuiSetCond_Appearing );
Begin( "Input overrides" , &ovWindow_ ,
ImGuiWindowFlags_NoCollapse );
if ( soRoot_.subsections.empty( ) ) {
Text( "No overrides have been defined." );
} else {
T_StringBuilder temp;
uint32_t counter{ 0 };
for ( auto& section : soRoot_.subsections ) {
section->makeUI( counter , temp , true );
2017-10-23 11:03:38 +02:00
}
}
End( );
2017-10-23 11:03:38 +02:00
}