diff --git a/trinity/Controllers/ITr2Controller.h b/trinity/Controllers/ITr2Controller.h index 101b0b247..85822aedb 100644 --- a/trinity/Controllers/ITr2Controller.h +++ b/trinity/Controllers/ITr2Controller.h @@ -53,6 +53,8 @@ BLUE_INTERFACE( ITr2Controller ) : } }; +void UpdateSpaceObjectParentRoot( std::vector>& roots, IRoot* owner ); + // A controller that supports controller actions BLUE_INTERFACE( ITr2ActionController ) : public ITr2Controller diff --git a/trinity/Controllers/Tr2BindingPoint.cpp b/trinity/Controllers/Tr2BindingPoint.cpp index defe70f64..99fd47060 100644 --- a/trinity/Controllers/Tr2BindingPoint.cpp +++ b/trinity/Controllers/Tr2BindingPoint.cpp @@ -142,8 +142,8 @@ const char* MatchProperty( const char* path ) { return path; } - ++path; - return MatchRoot( path ); + auto nameEnd = MatchRoot( path + 1 ); + return nameEnd == path + 1 ? path : nameEnd; } const char* MatchNumericIndex( const char* path ) @@ -304,7 +304,7 @@ void Tr2BindingPoint::Unlink() bool Tr2BindingPoint::IsValid() const { - return m_destination != nullptr && GetBoundObject() != nullptr; + return m_destination != nullptr && ( m_path.empty() || !!m_resolvedObject ); } void Tr2BindingPoint::SetValue( float value ) const @@ -487,6 +487,32 @@ IRoot* Tr2BindingPoint::GetBoundObject() const return m_object; } +const char* Tr2BindingPoint::MatchPath( const char* path ) +{ + auto end = MatchRoot( path ); + if( end == path ) + { + return path; + } + while( true ) + { + auto next = MatchProperty( end ); + if( next == end ) + { + next = MatchNumericIndex( end ); + } + if( next == end ) + { + next = MatchNameIndex( end ); + } + if( next == end ) + { + return end; + } + end = next; + } +} + IRootPtr ResolveObjectPath( const std::string& reference, const std::map& roots ) { diff --git a/trinity/Controllers/Tr2BindingPoint.h b/trinity/Controllers/Tr2BindingPoint.h index 6de329cf8..94c31b16c 100644 --- a/trinity/Controllers/Tr2BindingPoint.h +++ b/trinity/Controllers/Tr2BindingPoint.h @@ -18,6 +18,8 @@ class Tr2BindingPoint IRoot* GetBoundObject() const; + static const char* MatchPath( const char* path ); + std::string m_path; IRootPtr m_object; std::string m_attribute; diff --git a/trinity/Controllers/Tr2Controller.cpp b/trinity/Controllers/Tr2Controller.cpp index 445937637..97421bb0e 100644 --- a/trinity/Controllers/Tr2Controller.cpp +++ b/trinity/Controllers/Tr2Controller.cpp @@ -8,6 +8,7 @@ #include "Include/ITr2Updateable.h" #include "../Tr2ExpressionTermInfo.h" #include "ContinueOnMainThread.h" +#include "Eve/SpaceObject/EveSpaceObject2.h" CCP_STATS_DECLARE( controllerUpdateTime, "Trinity/Controllers/UpdateTime", true, CST_TIME, "Cumulative per-frame time for controller update" ); CCP_STATS_DECLARE( controllerUpdateablesTime, "Trinity/Controllers/UpdateablesTime", true, CST_TIME, "Cumulative per-frame time for controller updates tick" ); @@ -18,6 +19,45 @@ CCP_STATS_DECLARE( controllerLinkCount, "Trinity/Controllers/LinkCount", false, CcpMutex g_controllerMutex( "", "g_controllerMutex" ); +namespace +{ +const char* const SPACE_OBJECT_PARENT = "SpaceObjectParent"; + +bool TryGetSpaceObjectParent( IRoot* owner, IRoot*& spaceObjectParent ) +{ + if( EveSpaceObjectChildPtr child = BlueCastPtr( owner ) ) + { + auto spaceObject = child->GetOwner(); + spaceObjectParent = spaceObject ? spaceObject->GetRootObject() : nullptr; + return true; + } + if( IEveSpaceObject2Ptr spaceObject = BlueCastPtr( owner ) ) + { + spaceObjectParent = spaceObject->GetRootObject(); + return true; + } + return false; +} +} + +void UpdateSpaceObjectParentRoot( std::vector>& roots, IRoot* owner ) +{ + IRoot* spaceObjectParent = nullptr; + if( !TryGetSpaceObjectParent( owner, spaceObjectParent ) ) + { + return; + } + for( auto& root : roots ) + { + if( root.first == SPACE_OBJECT_PARENT ) + { + root.second = spaceObjectParent; + return; + } + } + roots.push_back( { SPACE_OBJECT_PARENT, spaceObjectParent } ); +} + Tr2Controller::Tr2Controller( IRoot* lockobj ) : PARENTLOCK( m_stateMachines ), PARENTLOCK( m_variables ), @@ -323,6 +363,8 @@ std::optional Tr2Controller::GetFloatVariableByName( const char* name ) c void Tr2Controller::GetExpressionTermInfo( std::vector& out ) const { + out.push_back( Tr2ExpressionTermInfo::Variable( "Bindings", "Owner", "the object this controller is attached to - follow it with a path to a float attribute, e.g. Owner.translation.x" ) ); + out.push_back( Tr2ExpressionTermInfo::Variable( "Bindings", SPACE_OBJECT_PARENT, "the space object this controller's owner belongs to, or the owner itself if it is a space object" ) ); for( auto it = begin( m_variables ); it != end( m_variables ); ++it ) { out.push_back( Tr2ExpressionTermInfo::Variable( "Variables", ( *it )->GetName().c_str(), "controller variable" ) ); @@ -362,16 +404,21 @@ const std::vector>& Tr2Controller::GetBindingPath { if( m_bindingPathRoots.empty() ) { - m_bindingPathRoots.reserve( 1 + m_variables.size() ); + m_bindingPathRoots.reserve( 2 + m_variables.size() ); if( m_owner ) { m_bindingPathRoots.push_back( { "Owner", m_owner } ); } + UpdateSpaceObjectParentRoot( m_bindingPathRoots, m_owner ); for( auto& var : m_variables ) { m_bindingPathRoots.push_back( { var->GetName(), var->GetRawRoot() } ); } } + else + { + UpdateSpaceObjectParentRoot( m_bindingPathRoots, m_owner ); + } return m_bindingPathRoots; } diff --git a/trinity/Controllers/Tr2ControllerExpression.cpp b/trinity/Controllers/Tr2ControllerExpression.cpp index 95f6af4eb..e83874930 100644 --- a/trinity/Controllers/Tr2ControllerExpression.cpp +++ b/trinity/Controllers/Tr2ControllerExpression.cpp @@ -13,6 +13,7 @@ #include "Tr2ExpressionTermInfo.h" #include "TriSettingsRegistrar.h" #include +#include bool g_controllerFunctionOverrideEnabled = false; @@ -211,28 +212,75 @@ float BoosterIntensity( void* ctx ) return 0.0f; } -bool IsValidVariableName( const char* name ) +bool IsIdentifierStart( char ch ) { - auto isLetter = []( char x ) { - return ( x >= 'a' && x <= 'z' ) || ( x >= 'A' && x <= 'Z' ) || ( x == '_' ); - }; - auto isDigit = []( char x ) { - return x >= '0' && x <= '9'; - }; - if( !isLetter( *name ) ) + return ( ch >= 'a' && ch <= 'z' ) || ( ch >= 'A' && ch <= 'Z' ) || ch == '_'; +} + +bool IsIdentifierChar( char ch ) +{ + return IsIdentifierStart( ch ) || ( ch >= '0' && ch <= '9' ); +} + +bool IsIdentifier( const char* begin, const char* end ) +{ + return begin != end && IsIdentifierStart( *begin ) && std::all_of( begin, end, IsIdentifierChar ); +} + +const char* SkipIdentifier( const char* text ) +{ + while( IsIdentifierChar( *text ) ) { - return false; + ++text; } - ++name; - while( *name ) + return text; +} + +using BindingPathRoots = std::vector>; + +const std::pair* FindBindingRoot( const BindingPathRoots& roots, std::string_view name ) +{ + auto found = std::find_if( begin( roots ), end( roots ), [&]( const auto& root ) { + return root.first == name; + } ); + return found == end( roots ) ? nullptr : &*found; +} + +bool HasBindableAttribute( const std::string& path ) +{ + auto dot = path.rfind( '.' ); + return dot != std::string::npos && IsIdentifier( path.c_str() + dot + 1, path.c_str() + path.length() ); +} + +bool HasUnattachedRoot( const BindingPathRoots& roots, const std::string& path ) +{ + auto rootEnd = SkipIdentifier( path.c_str() ); + auto root = FindBindingRoot( roots, std::string_view( path.c_str(), size_t( rootEnd - path.c_str() ) ) ); + return root && !root->second; +} + +bool LinkReference( Tr2BindingPoint& binding, const std::string& path, const BindingPathRoots& roots ) +{ + if( !HasBindableAttribute( path ) ) { - if( !isLetter( *name ) && !isDigit( *name ) ) - { - return false; - } - ++name; + return false; } - return true; + auto linkAt = [&]( size_t dot ) { + binding.m_path = path.substr( 0, dot ); + binding.m_attribute = path.substr( dot + 1 ); + binding.Link( roots ); + return binding.IsValid(); + }; + + auto attributeDot = path.rfind( '.' ); + auto swizzleDot = path.length() - attributeDot == 2 ? path.rfind( '.', attributeDot - 1 ) : std::string::npos; + bool hasSwizzle = swizzleDot != std::string::npos && IsIdentifier( path.c_str() + swizzleDot + 1, path.c_str() + attributeDot ); + return ( hasSwizzle && linkAt( swizzleDot ) ) || linkAt( attributeDot ); +} + +std::string CannotBindError( const std::string& path ) +{ + return "cannot bind \"" + path + "\" to a float attribute"; } #ifdef _WIN32 @@ -492,17 +540,19 @@ struct ParserObserver : public CcpParser::Observer void OnVariable( const CcpParser::Variable* variable ) override { + std::less less; + if( less( variable, m_variables.data ) || !less( variable, m_variables.data + m_variables.count ) ) + { + return; + } auto offset = variable - m_variables.data; - if( offset >= 0 && offset < ptrdiff_t( m_variables.count ) ) + if( offset >= 64 ) { - if( offset >= 64 ) - { - m_maskOverflow = true; - } - else - { - m_mask |= 1ull << offset; - } + m_maskOverflow = true; + } + else + { + m_mask |= 1ull << offset; } } @@ -518,6 +568,7 @@ struct ParserObserver : public CcpParser::Observer Tr2ControllerExpression::Tr2ControllerExpression() : + m_hasPendingReferences( false ), m_stateMachine( nullptr ), m_controller( nullptr ), m_variableMask( 0 ) @@ -542,23 +593,132 @@ std::string Tr2ControllerExpression::SetExpr( const char* expression, const ITr2 std::string Tr2ControllerExpression::CreateParser( const char* expression, const CcpParser::FunctionView& extraFunctions ) { + std::string rewritten; + auto error = BindReferences( expression, rewritten ); + if( !error.empty() ) + { + ClearReferences(); + return error; + } + CcpParser::Externals externals; - CcpParser::VariableView varViews[] = { m_controller->GetVariableView() }; + CcpParser::VariableView varViews[] = { m_controller->GetVariableView(), m_referenceVariables }; externals.variables = varViews; CcpParser::FunctionView funcViews[2] = { extraFunctions, s_functions }; externals.functions = { funcViews, 2 }; ParserObserver observer; observer.m_variables = varViews[0]; - auto parsed = CcpParser::Parse( expression, externals, m_program, &observer ); + auto parsed = CcpParser::Parse( rewritten.c_str(), externals, m_program, &observer ); if( parsed ) { m_controller->EnsureTempArenaSize( m_program.GetTempArenaSize() ); - m_variableMask = observer.m_maskOverflow || observer.m_hasNonPureFunctions ? 0ull : observer.m_mask; + bool maskIsUsable = !observer.m_maskOverflow && !observer.m_hasNonPureFunctions && m_references.empty(); + m_variableMask = maskIsUsable ? observer.m_mask : 0ull; return std::string(); } else { - return ToString( parsed, expression ); + ClearReferences(); + return ToString( parsed, rewritten.c_str() ); + } +} + +std::string Tr2ControllerExpression::BindReferences( const char* expression, std::string& rewritten ) +{ + const auto& roots = m_controller->GetBindingPathRoots(); + rewritten.clear(); + for( const char* p = expression; *p; ) + { + if( *p == '"' ) + { + auto close = strchr( p + 1, '"' ); + auto literalEnd = close ? close + 1 : p + strlen( p ); + rewritten.append( p, literalEnd ); + p = literalEnd; + continue; + } + bool startsToken = p == expression || ( !IsIdentifierChar( p[-1] ) && p[-1] != '.' ); + if( !startsToken || !IsIdentifierStart( *p ) ) + { + rewritten.push_back( *p++ ); + continue; + } + auto rootEnd = SkipIdentifier( p ); + auto pathEnd = Tr2BindingPoint::MatchPath( p ); + if( pathEnd == rootEnd || !FindBindingRoot( roots, std::string_view( p, size_t( rootEnd - p ) ) ) ) + { + // variable or function name + rewritten.append( p, rootEnd ); + p = rootEnd; + continue; + } + + std::string path( p, pathEnd ); + auto found = std::find_if( begin( m_references ), end( m_references ), [&]( const Reference& reference ) { + return reference.path == path; + } ); + if( found == end( m_references ) ) + { + auto error = AddReference( path ); + if( !error.empty() ) + { + return error; + } + found = end( m_references ) - 1; + } + rewritten += found->name; + p = pathEnd; + } + + m_referenceVariables.clear(); + m_referenceVariables.reserve( m_references.size() ); + for( size_t i = 0; i < m_references.size(); ++i ) + { + m_referenceVariables.push_back( { m_references[i].name.c_str(), REFERENCE_BUFFER_INDEX, CcpParser::OffsetType( i * sizeof( float ) ) } ); + } + m_referenceValues.assign( m_references.size(), 0.f ); + return std::string(); +} + +std::string Tr2ControllerExpression::AddReference( const std::string& path ) +{ + const auto& roots = m_controller->GetBindingPathRoots(); + auto binding = std::make_unique(); + bool linked = LinkReference( *binding, path, roots ); + bool pending = !linked && HasUnattachedRoot( roots, path ); + if( !linked && !pending ) + { + return CannotBindError( path ); + } + + m_references.push_back( Reference{ path, "__ref" + std::to_string( m_references.size() ), std::move( binding ), pending } ); + m_hasPendingReferences = m_hasPendingReferences || pending; + return std::string(); +} + +void Tr2ControllerExpression::ClearReferences() +{ + m_references.clear(); + m_hasPendingReferences = false; + m_referenceVariables.clear(); + m_referenceValues.clear(); +} + +void Tr2ControllerExpression::ResolvePendingReferences() const +{ + if( !m_hasPendingReferences ) + { + return; + } + const auto& roots = m_controller->GetBindingPathRoots(); + m_hasPendingReferences = false; + for( auto& reference : m_references ) + { + if( reference.pending ) + { + reference.pending = !LinkReference( *reference.binding, reference.path, roots ); + m_hasPendingReferences = m_hasPendingReferences || reference.pending; + } } } @@ -568,8 +728,16 @@ std::pair Tr2ControllerExpression::Eval( void* extraBuffer ) const { return std::make_pair( false, 0.f ); } + ResolvePendingReferences(); + for( size_t i = 0; i < m_references.size(); ++i ) + { + if( !m_references[i].binding->GetValue( m_referenceValues[i] ) ) + { + m_referenceValues[i] = 0.f; + } + } auto owner = m_controller->GetOwner(); - void* externals[] = { m_controller->GetVariableBuffer(), &owner, (void*)&m_stateMachine, extraBuffer }; + void* externals[] = { m_controller->GetVariableBuffer(), &owner, (void*)&m_stateMachine, extraBuffer, m_referenceValues.data() }; float result = m_program.Eval( externals, m_controller->GetTempArena() ); return std::make_pair( true, result ); } @@ -580,8 +748,10 @@ void Tr2ControllerExpression::Clear() { m_program = CcpParser::Program(); } + ClearReferences(); m_stateMachine = nullptr; m_controller = nullptr; + m_variableMask = 0; } bool Tr2ControllerExpression::IsExpressionValid() const diff --git a/trinity/Controllers/Tr2ControllerExpression.h b/trinity/Controllers/Tr2ControllerExpression.h index d4a3ee276..d676c1be4 100644 --- a/trinity/Controllers/Tr2ControllerExpression.h +++ b/trinity/Controllers/Tr2ControllerExpression.h @@ -3,6 +3,7 @@ #pragma once #include +#include "Tr2BindingPoint.h" BLUE_DECLARE( Tr2StateMachine ); @@ -27,11 +28,28 @@ class Tr2ControllerExpression static const uint32_t OWNER_BUFFER_INDEX = 1; static const uint32_t STATE_MACHINE_BUFFER_INDEX = 2; static const uint32_t EXTRA_BUFFER_INDEX = 3; + static const uint32_t REFERENCE_BUFFER_INDEX = 4; private: + struct Reference + { + std::string path; + std::string name; + std::unique_ptr binding; + bool pending; + }; + std::string CreateParser( const char* expression, const CcpParser::FunctionView& extraFunctions ); + std::string BindReferences( const char* expression, std::string& rewritten ); + std::string AddReference( const std::string& path ); + void ClearReferences(); + void ResolvePendingReferences() const; CcpParser::Program m_program; + mutable std::vector m_references; + mutable bool m_hasPendingReferences; + std::vector m_referenceVariables; + mutable std::vector m_referenceValues; const Tr2StateMachine* m_stateMachine; const ITr2ActionController* m_controller; diff --git a/trinity/Controllers/Tr2TimelineController.cpp b/trinity/Controllers/Tr2TimelineController.cpp index 838f3006b..7a3328928 100644 --- a/trinity/Controllers/Tr2TimelineController.cpp +++ b/trinity/Controllers/Tr2TimelineController.cpp @@ -307,16 +307,21 @@ const std::vector>& Tr2TimelineController::GetBin { if( m_bindingPathRoots.empty() ) { - m_bindingPathRoots.reserve( 1 + m_variables.size() ); + m_bindingPathRoots.reserve( 2 + m_variables.size() ); if( m_owner ) { m_bindingPathRoots.push_back( { "Owner", m_owner } ); } + UpdateSpaceObjectParentRoot( m_bindingPathRoots, m_owner ); for( auto& var : m_variables ) { m_bindingPathRoots.push_back( { var->GetName(), var->GetRawRoot() } ); } } + else + { + UpdateSpaceObjectParentRoot( m_bindingPathRoots, m_owner ); + } return m_bindingPathRoots; } @@ -334,6 +339,8 @@ std::optional Tr2TimelineController::GetFloatVariableByName( const char* void Tr2TimelineController::GetExpressionTermInfo( std::vector& out ) const { + out.push_back( Tr2ExpressionTermInfo::Variable( "Bindings", "Owner", "the object this controller is attached to - follow it with a path to a float attribute, e.g. Owner.translation.x" ) ); + out.push_back( Tr2ExpressionTermInfo::Variable( "Bindings", "SpaceObjectParent", "the space object this controller's owner belongs to, or the owner itself if it is a space object" ) ); for( auto it = begin( m_variables ); it != end( m_variables ); ++it ) { out.push_back( Tr2ExpressionTermInfo::Variable( "Variables", ( *it )->GetName().c_str(), "controller variable" ) );