diff --git a/include/eepp/core/memorymanager.hpp b/include/eepp/core/memorymanager.hpp index 4d716f39e..cd137d257 100644 --- a/include/eepp/core/memorymanager.hpp +++ b/include/eepp/core/memorymanager.hpp @@ -12,10 +12,12 @@ namespace EE { class EE_API AllocatedPointer { public: - AllocatedPointer( void* data, const std::string& File, int Line, size_t memory, - bool track = false, bool globalAllocation = false ); + /** file must have static storage duration. All memory-manager entry points pass __FILE__ or a + * string literal, so retaining the pointer avoids allocating bookkeeping strings. */ + AllocatedPointer( void* data, const char* file, int line, size_t memory, bool track = false, + bool globalAllocation = false ); - std::string mFile; + const char* mFile; int mLine; size_t mMemory; void* mData; diff --git a/include/eepp/core/small_function.hpp b/include/eepp/core/small_function.hpp new file mode 100644 index 000000000..24f011995 --- /dev/null +++ b/include/eepp/core/small_function.hpp @@ -0,0 +1,174 @@ +#ifndef EE_CORE_SMALL_FUNCTION_HPP +#define EE_CORE_SMALL_FUNCTION_HPP + +#include +#include +#include +#include +#include + +namespace EE { + +/** Copyable type-erased void callable with inline storage and a heap fallback. + * + * This intentionally implements only the signature used by the scene scheduler. Ordinary lambdas + * stay inside the object; unusually large or over-aligned callables retain std::function-like + * fallback behavior. + */ +template class SmallFunction { + public: + SmallFunction() noexcept = default; + SmallFunction( std::nullptr_t ) noexcept {} + + SmallFunction( const SmallFunction& other ) { + if ( other.mVTable ) + other.mVTable->copy( *this, other ); + } + + SmallFunction( SmallFunction&& other ) { + if ( other.mVTable ) + other.mVTable->move( *this, other ); + } + + template , + std::enable_if_t && + std::is_invocable_r_v, + int> = 0> + SmallFunction( Callable&& callable ) { + emplace( std::forward( callable ) ); + } + + ~SmallFunction() { reset(); } + + SmallFunction& operator=( const SmallFunction& other ) { + if ( this != &other ) { + reset(); + if ( other.mVTable ) + other.mVTable->copy( *this, other ); + } + return *this; + } + + SmallFunction& operator=( SmallFunction&& other ) { + if ( this != &other ) { + reset(); + if ( other.mVTable ) + other.mVTable->move( *this, other ); + } + return *this; + } + + template , + std::enable_if_t && + std::is_invocable_r_v, + int> = 0> + SmallFunction& operator=( Callable&& callable ) { + reset(); + emplace( std::forward( callable ) ); + return *this; + } + + SmallFunction& operator=( std::nullptr_t ) noexcept { + reset(); + return *this; + } + + explicit operator bool() const noexcept { return nullptr != mVTable; } + + void operator()() const { + if ( !mVTable ) + throw std::bad_function_call(); + mVTable->invoke( const_cast( *this ) ); + } + + void reset() noexcept { + if ( mVTable ) { + mVTable->destroy( *this ); + mVTable = nullptr; + } + } + + static constexpr std::size_t inlineSize() noexcept { return InlineSize; } + + private: + static constexpr std::size_t StorageSize = + InlineSize < sizeof( void* ) ? sizeof( void* ) : InlineSize; + using Storage = std::aligned_storage_t; + + struct VTable { + void ( *invoke )( SmallFunction& ); + void ( *copy )( SmallFunction&, const SmallFunction& ); + void ( *move )( SmallFunction&, SmallFunction& ); + void ( *destroy )( SmallFunction& ) noexcept; + }; + + template + static constexpr bool FitsInline = + sizeof( Callable ) <= StorageSize && alignof( Callable ) <= alignof( Storage ); + + template static Callable*& heapPointer( SmallFunction& function ) { + return *reinterpret_cast( &function.mStorage ); + } + + template static Callable* inlinePointer( SmallFunction& function ) { + return reinterpret_cast( &function.mStorage ); + } + + template void emplace( Value&& value ) { + static_assert( std::is_copy_constructible_v, + "SmallFunction requires a copy-constructible callable" ); + if constexpr ( FitsInline ) { + new ( &mStorage ) Callable( std::forward( value ) ); + mVTable = &inlineVTable(); + } else { + heapPointer( *this ) = new Callable( std::forward( value ) ); + mVTable = &heapVTable(); + } + } + + template static const VTable& inlineVTable() { + static const VTable table{ + []( SmallFunction& function ) { ( *inlinePointer( function ) )(); }, + []( SmallFunction& destination, const SmallFunction& source ) { + new ( &destination.mStorage ) + Callable( *inlinePointer( const_cast( source ) ) ); + destination.mVTable = &inlineVTable(); + }, + []( SmallFunction& destination, SmallFunction& source ) { + new ( &destination.mStorage ) + Callable( std::move( *inlinePointer( source ) ) ); + inlinePointer( source )->~Callable(); + destination.mVTable = &inlineVTable(); + source.mVTable = nullptr; + }, + []( SmallFunction& function ) noexcept { + inlinePointer( function )->~Callable(); + } }; + return table; + } + + template static const VTable& heapVTable() { + static const VTable table{ + []( SmallFunction& function ) { ( *heapPointer( function ) )(); }, + []( SmallFunction& destination, const SmallFunction& source ) { + heapPointer( destination ) = + new Callable( *heapPointer( const_cast( source ) ) ); + destination.mVTable = &heapVTable(); + }, + []( SmallFunction& destination, SmallFunction& source ) { + heapPointer( destination ) = heapPointer( source ); + heapPointer( source ) = nullptr; + destination.mVTable = &heapVTable(); + source.mVTable = nullptr; + }, + []( SmallFunction& function ) noexcept { delete heapPointer( function ); } }; + return table; + } + + Storage mStorage; + const VTable* mVTable{ nullptr }; +}; + +} // namespace EE + +#endif diff --git a/include/eepp/math/ease.hpp b/include/eepp/math/ease.hpp index 2371aeca7..36d52eda4 100644 --- a/include/eepp/math/ease.hpp +++ b/include/eepp/math/ease.hpp @@ -1,8 +1,8 @@ #ifndef EE_MATH_EASE #define EE_MATH_EASE -#include #include +#include namespace EE { namespace Math { @@ -118,7 +118,7 @@ class Ease { return ""; } - static Interpolation fromName( const std::string& name, + static Interpolation fromName( std::string_view name, const Interpolation& defaultInterpolation = Ease::Linear ) { if ( "linear" == name ) return Ease::Linear; @@ -184,13 +184,22 @@ class Ease { return Ease::ElasticInOut; if ( "cubicbezier" == name || "cubic-bezier" == name ) return Ease::CubizBezier; - // Handle the case that the name is a function - if ( name.find_first_of( '(' ) != std::string::npos ) { - System::FunctionString func( System::FunctionString::parse( name ) ); - return fromName( func.getName(), defaultInterpolation ); - } + // Function parameters do not participate in interpolation-name lookup. + const auto functionStart = name.find_first_of( '(' ); + if ( functionStart != std::string_view::npos ) + return fromName( name.substr( 0, functionStart ), defaultInterpolation ); return defaultInterpolation; } + + static Interpolation fromName( const std::string& name, + const Interpolation& defaultInterpolation = Ease::Linear ) { + return fromName( std::string_view{ name }, defaultInterpolation ); + } + + static Interpolation fromName( const char* name, + const Interpolation& defaultInterpolation = Ease::Linear ) { + return fromName( std::string_view{ name }, defaultInterpolation ); + } }; }} // namespace EE::Math diff --git a/include/eepp/scene/action.hpp b/include/eepp/scene/action.hpp index 357e18c95..6d4b53b57 100644 --- a/include/eepp/scene/action.hpp +++ b/include/eepp/scene/action.hpp @@ -1,6 +1,7 @@ #ifndef EE_SCENEACTION_HPP #define EE_SCENEACTION_HPP +#include #include #include using namespace EE::System; @@ -21,6 +22,18 @@ class EE_API Action { virtual ~Action(); + /** Allocates actions from a shared size-class pool. Existing Action factories and ownership + * semantics are unchanged; completed slots are recycled by operator delete. */ + static void* operator new( std::size_t size ); + + static void operator delete( void* ptr ) noexcept; + + static void operator delete( void* ptr, std::size_t size ) noexcept; + + static void* operator new( std::size_t, void* place ) noexcept { return place; } + + static void operator delete( void*, void* ) noexcept {} + /** Starts the action. */ virtual void start() = 0; @@ -91,7 +104,12 @@ class EE_API Action { protected: friend class Node; - typedef UnorderedMap> ActionCallbackMap; + struct ActionCallbackEntry { + ActionType type; + Uint32 id; + ActionCallback callback; + }; + using ActionCallbackMap = SmallVector; Node* mNode{ nullptr }; Action::UniqueID mId{ 0 }; diff --git a/include/eepp/scene/actionmanager.hpp b/include/eepp/scene/actionmanager.hpp index 72db7f21e..ade3a0d54 100644 --- a/include/eepp/scene/actionmanager.hpp +++ b/include/eepp/scene/actionmanager.hpp @@ -43,6 +43,9 @@ class EE_API ActionManager { std::vector getActionsByTagFromTarget( Node* target, const Action::UniqueID& tag ); + void getActionsByTagFromTarget( Node* target, const Action::UniqueID& tag, + SmallVector& actions ); + void update( const Time& time ); std::size_t count() const; diff --git a/include/eepp/scene/actions/runnable.hpp b/include/eepp/scene/actions/runnable.hpp index d17e3c4b6..f5d3f6376 100644 --- a/include/eepp/scene/actions/runnable.hpp +++ b/include/eepp/scene/actions/runnable.hpp @@ -1,15 +1,15 @@ #ifndef EE_SCENE_ACTION_CALLBACK_HPP #define EE_SCENE_ACTION_CALLBACK_HPP +#include #include #include -#include namespace EE { namespace Scene { namespace Actions { class EE_API Runnable : public Delay { public: - typedef std::function RunnableFunc; + using RunnableFunc = SmallFunction<48>; static Runnable* New( RunnableFunc callback, const Time& time = Seconds( 0 ), bool loop = false ); diff --git a/include/eepp/scene/actions/sequence.hpp b/include/eepp/scene/actions/sequence.hpp index dcb17bc6f..2bd80e1b7 100644 --- a/include/eepp/scene/actions/sequence.hpp +++ b/include/eepp/scene/actions/sequence.hpp @@ -42,11 +42,14 @@ class EE_API Sequence : public Action { virtual ~Sequence(); protected: - std::vector mSequence; + using ActionContainer = SmallVector; + ActionContainer mSequence; Time mDuration; Uint32 mCurPos; Sequence( const std::vector sequence ); + + Sequence( ActionContainer sequence ); }; }}} // namespace EE::Scene::Actions diff --git a/include/eepp/scene/actions/spawn.hpp b/include/eepp/scene/actions/spawn.hpp index 8f3bb992a..70073d954 100644 --- a/include/eepp/scene/actions/spawn.hpp +++ b/include/eepp/scene/actions/spawn.hpp @@ -42,10 +42,13 @@ class EE_API Spawn : public Action { virtual ~Spawn(); protected: - std::vector mSpawn; + using ActionContainer = SmallVector; + ActionContainer mSpawn; bool mAllDone; Spawn( const std::vector spawn ); + + Spawn( ActionContainer spawn ); }; }}} // namespace EE::Scene::Actions diff --git a/include/eepp/scene/node.hpp b/include/eepp/scene/node.hpp index 03488f2fb..64d85fb3b 100644 --- a/include/eepp/scene/node.hpp +++ b/include/eepp/scene/node.hpp @@ -1346,6 +1346,9 @@ class EE_API Node : public Transformable { */ std::vector getActionsByTag( const Action::UniqueID& tag ); + /** Appends matching actions to inline-capable storage without allocating in the common case. */ + void getActionsByTag( const Action::UniqueID& tag, SmallVector& actions ); + /** * @brief Removes all actions from this node. * diff --git a/include/eepp/system/functionstring.hpp b/include/eepp/system/functionstring.hpp index b789a758f..0d865c637 100644 --- a/include/eepp/system/functionstring.hpp +++ b/include/eepp/system/functionstring.hpp @@ -6,6 +6,7 @@ #include #include +#include namespace EE { namespace System { @@ -14,6 +15,82 @@ class EE_API FunctionString { using Parameters = SmallVector; using TypeStringVector = SmallVector; + class View { + public: + View() = default; + + std::string_view getName() const { return mName; } + + bool isEmpty() const { return mName.empty(); } + + template bool forEachParameter( Callback&& callback ) const { + return FunctionString::forEachParameter( mParameters, + std::forward( callback ) ); + } + + private: + friend class FunctionString; + + View( std::string_view name, std::string_view parameters ) : + mName( name ), mParameters( parameters ) {} + + std::string_view mName; + std::string_view mParameters; + }; + + static View parseView( std::string_view function ); + + /** Iterates a comma-separated parameter list without allocating. Nested parentheses and + * quoted strings protect commas. Quoted parameters are reported without their outer quotes; + * escaped quotes remain escaped because the returned views reference the original input. + * The callback receives (parameter, parameterWasString) and returns whether to continue. */ + template + static bool forEachParameter( std::string_view parameters, Callback&& callback ) { + std::size_t start = 0; + int parenthesisDepth = 0; + char quote = 0; + bool escaped = false; + + auto emit = [&]( std::size_t end ) { + std::string_view parameter = + String::trim( parameters.substr( start, end - start ), " \t\n\r\f\v" ); + bool wasString = parameter.size() >= 2 && + ( parameter.front() == '\'' || parameter.front() == '"' ) && + parameter.back() == parameter.front(); + if ( wasString ) + parameter = parameter.substr( 1, parameter.size() - 2 ); + if ( parameter.empty() && !wasString ) + return true; + return callback( parameter, wasString ); + }; + + for ( std::size_t i = 0; i < parameters.size(); ++i ) { + const char character = parameters[i]; + if ( quote ) { + if ( escaped ) { + escaped = false; + } else if ( character == '\\' ) { + escaped = true; + } else if ( character == quote ) { + quote = 0; + } + continue; + } + if ( character == '\'' || character == '"' ) { + quote = character; + } else if ( character == '(' ) { + ++parenthesisDepth; + } else if ( character == ')' && parenthesisDepth > 0 ) { + --parenthesisDepth; + } else if ( character == ',' && parenthesisDepth == 0 ) { + if ( !emit( i ) ) + return false; + start = i + 1; + } + } + return emit( parameters.size() ); + } + static FunctionString parse( const std::string& function ); static FunctionString parse( std::string_view function ); @@ -41,7 +118,7 @@ class EE_API FunctionString { Parameters parameters; TypeStringVector typeStringData; - template static FunctionString parse( StringType function ); + static FunctionString parseOwned( std::string_view function ); }; }} // namespace EE::System diff --git a/include/eepp/ui/css/animationdefinition.hpp b/include/eepp/ui/css/animationdefinition.hpp index 7f91b296b..68aa745d3 100644 --- a/include/eepp/ui/css/animationdefinition.hpp +++ b/include/eepp/ui/css/animationdefinition.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include using namespace EE::Math; @@ -56,8 +57,12 @@ class EE_API AnimationDefinition { const Ease::Interpolation& getTimingFunction() const; + /** Kept for source compatibility. Internal animation code uses the inline view below so this + * legacy vector is only materialized when an external caller requests it. */ const std::vector& getTimingFunctionParameters() const; + const TimingFunction::Parameters& getTimingFunctionParametersInline() const; + const AnimationFillMode& getFillMode() const; void setName( const std::string& value ); @@ -72,6 +77,8 @@ class EE_API AnimationDefinition { void setTimingFunctionParameters( const std::vector& timingFunctionParameters ); + void setTimingFunctionParameters( const TimingFunction::Parameters& timingFunctionParameters ); + void setDirection( const AnimationDirection& value ); void setFillMode( const AnimationFillMode& value ); @@ -87,7 +94,9 @@ class EE_API AnimationDefinition { Time mDuration = Time::Zero; Int32 mIterations = 1; /* -1 == "infinite" */ Ease::Interpolation mTimingFunction = Ease::Interpolation::Linear; - std::vector mTimingFunctionParameters{}; + TimingFunction::Parameters mTimingFunctionParameters{}; + mutable std::vector mTimingFunctionParametersVector; + mutable bool mTimingFunctionParametersVectorValid{ false }; AnimationDirection mDirection = Normal; AnimationFillMode mFillMode = None; bool mPaused = false; @@ -98,7 +107,7 @@ inline bool operator==( const AnimationDefinition& a, const AnimationDefinition& a.getDelay() == b.getDelay() && a.getDirection() == b.getDirection() && a.isPaused() == b.isPaused() && a.getIterations() == b.getIterations() && a.getName() == b.getName() && - a.getTimingFunctionParameters() == b.getTimingFunctionParameters(); + a.getTimingFunctionParametersInline() == b.getTimingFunctionParametersInline(); } inline bool operator!=( const AnimationDefinition& a, const AnimationDefinition& b ) { diff --git a/include/eepp/ui/css/elementdefinition.hpp b/include/eepp/ui/css/elementdefinition.hpp index 4546cbeb4..bc7267cae 100644 --- a/include/eepp/ui/css/elementdefinition.hpp +++ b/include/eepp/ui/css/elementdefinition.hpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace EE { namespace UI { namespace CSS { @@ -22,6 +23,8 @@ class EE_API ElementDefinition : NonCopyable { const std::vector& getTransitionProperties() const; + const ComputedTransitions& getTransitions() const; + const std::vector& getAnimationProperties() const; const StyleSheetVariables& getVariables() const; @@ -38,6 +41,7 @@ class EE_API ElementDefinition : NonCopyable { StyleSheetVariables mVariables; PropertyIdSet mPropertyIds; std::vector mTransitionProperties; + ComputedTransitions mTransitions; std::vector mAnimationProperties; bool mStructurallyVolatile; diff --git a/include/eepp/ui/css/stylesheetlength.hpp b/include/eepp/ui/css/stylesheetlength.hpp index b82db33ce..c10fbf283 100644 --- a/include/eepp/ui/css/stylesheetlength.hpp +++ b/include/eepp/ui/css/stylesheetlength.hpp @@ -58,12 +58,21 @@ class EE_API StyleSheetLength { static StyleSheetLength fromString( const std::string& str, const Float& defaultValue = 0, bool pxAsDp = false ); + static StyleSheetLength fromString( std::string_view str, const Float& defaultValue = 0, + bool pxAsDp = false ); + static StyleSheetLength fromString( const char* str, const Float& defaultValue = 0, + bool pxAsDp = false ) { + return fromString( std::string_view{ str }, defaultValue, pxAsDp ); + } StyleSheetLength(); StyleSheetLength( const Float& val, const Unit& unit ); StyleSheetLength( const std::string& val, const Float& defaultValue = 0 ); + StyleSheetLength( std::string_view val, const Float& defaultValue = 0 ); + StyleSheetLength( const char* val, const Float& defaultValue = 0 ) : + StyleSheetLength( std::string_view{ val }, defaultValue ) {} StyleSheetLength( const StyleSheetLength& val ); @@ -98,7 +107,7 @@ class EE_API StyleSheetLength { protected: static bool isFunctionString( std::string_view str ); - static bool parseFunction( const std::string& str, Unit& outUnit, Arguments& outArgs ); + static bool parseFunction( std::string_view str, Unit& outUnit, Arguments& outArgs ); Float resolveFunction( const Float& parentSize, const Sizef& viewSize, const Float& displayDpi, const Float& elFontSize, const Float& globalFontSize, diff --git a/include/eepp/ui/css/stylesheetpropertyanimation.hpp b/include/eepp/ui/css/stylesheetpropertyanimation.hpp index 20873f942..af7dca09e 100644 --- a/include/eepp/ui/css/stylesheetpropertyanimation.hpp +++ b/include/eepp/ui/css/stylesheetpropertyanimation.hpp @@ -26,7 +26,7 @@ class EE_API StyleSheetPropertyAnimation : public Action { const PropertyDefinition* property, const std::string& startValue, const std::string& endValue, const Ease::Interpolation& timingFunction, - const std::vector timingFunctionParameters, + const TimingFunction::Parameters& timingFunctionParameters, const Uint32& propertyIndex, const bool& isDone ); static StyleSheetPropertyAnimation* fromAnimationKeyframes( @@ -48,6 +48,13 @@ class EE_API StyleSheetPropertyAnimation : public Action { const std::vector& timingFunctionParameters, const AnimationOrigin& animationOrigin ); + static StyleSheetPropertyAnimation* + New( const PropertyDefinition* property, const std::string& startValue, + const std::string& endValue, const Uint32& propertyIndex, const Time& duration, + const Time& delay, const Ease::Interpolation& timingFunction, + const TimingFunction::Parameters& timingFunctionParameters, + const AnimationOrigin& animationOrigin ); + void start() override; void stop() override; @@ -87,8 +94,8 @@ class EE_API StyleSheetPropertyAnimation : public Action { protected: AnimationDefinition mAnimation; const PropertyDefinition* mPropertyDef; - std::vector mStates; - std::vector mAnimationStepsTime; + SmallVector mStates; + SmallVector mAnimationStepsTime; Time mRealElapsed; Time mElapsed; Int32 mPendingIterations; @@ -103,6 +110,13 @@ class EE_API StyleSheetPropertyAnimation : public Action { std::vector animationStepsTime, const Uint32& propertyIndex, const AnimationOrigin& animationOrigin ); + StyleSheetPropertyAnimation( const AnimationDefinition& animation, + const PropertyDefinition* propertyDef, + SmallVector states, + SmallVector animationStepsTime, + const Uint32& propertyIndex, + const AnimationOrigin& animationOrigin ); + void onStart() override; void onUpdate( const Time& time ) override; diff --git a/include/eepp/ui/css/timingfunction.hpp b/include/eepp/ui/css/timingfunction.hpp index 62cf4419c..b34387e73 100644 --- a/include/eepp/ui/css/timingfunction.hpp +++ b/include/eepp/ui/css/timingfunction.hpp @@ -1,20 +1,28 @@ #ifndef EE_UI_CSS_TIMINGFUNCTION_HPP #define EE_UI_CSS_TIMINGFUNCTION_HPP +#include #include #include -using namespace EE; using namespace EE::Math; namespace EE { namespace UI { namespace CSS { class EE_API TimingFunction { public: + using Parameters = SmallVector; + static TimingFunction parse( std::string timingFunction ); + static TimingFunction parse( std::string_view timingFunction ); + + static TimingFunction parse( const char* timingFunction ) { + return parse( std::string_view{ timingFunction } ); + } + Ease::Interpolation interpolation{ Ease::None }; - std::vector parameters; + Parameters parameters; }; }}} // namespace EE::UI::CSS diff --git a/include/eepp/ui/css/transitiondefinition.hpp b/include/eepp/ui/css/transitiondefinition.hpp index cc922e9d1..1ebe0a1a1 100644 --- a/include/eepp/ui/css/transitiondefinition.hpp +++ b/include/eepp/ui/css/transitiondefinition.hpp @@ -1,9 +1,11 @@ #ifndef EE_UI_CSS_TRANSITIONDEFINITION_HPP #define EE_UI_CSS_TRANSITIONDEFINITION_HPP +#include #include #include #include +#include #include using namespace EE::Math; @@ -37,6 +39,33 @@ class EE_API TransitionDefinition { typedef UnorderedMap TransitionsMap; +/** Compact transition data used by computed element definitions. It owns no strings and keeps + * the complete CSS timing-function payload inline. */ +struct ComputedTransitionDefinition { + String::HashType propertyNameHash{ 0 }; + Ease::Interpolation timingFunction{ Ease::Linear }; + TimingFunction::Parameters timingFunctionParameters; + Time delay{ Time::Zero }; + Time duration{ Time::Zero }; +}; + +class EE_API ComputedTransitions { + public: + static ComputedTransitions + parse( const std::vector& styleSheetProperties ); + + const ComputedTransitionDefinition* get( String::HashType propertyNameHash ) const; + + bool empty() const { return mTransitions.empty(); } + + std::size_t size() const { return mTransitions.size(); } + + private: + SmallVector mTransitions; + + void set( const ComputedTransitionDefinition& transition ); +}; + }}} // namespace EE::UI::CSS #endif diff --git a/include/eepp/ui/uinodedrawable.hpp b/include/eepp/ui/uinodedrawable.hpp index c589eedb7..01cdbf867 100644 --- a/include/eepp/ui/uinodedrawable.hpp +++ b/include/eepp/ui/uinodedrawable.hpp @@ -96,7 +96,7 @@ class EE_API UINodeDrawable : public Drawable { Sizef calcDrawableSize( const std::string& drawableSizeEq ); - Vector2f calcPosition( std::string positionXEq, std::string positionYEq ); + Vector2f calcPosition( const std::string& positionXEq, const std::string& positionYEq ); const std::string& getPositionX() const; diff --git a/include/eepp/ui/uistyle.hpp b/include/eepp/ui/uistyle.hpp index 943028070..58d3bd14c 100644 --- a/include/eepp/ui/uistyle.hpp +++ b/include/eepp/ui/uistyle.hpp @@ -100,7 +100,6 @@ class EE_API UIStyle : public UIState { std::shared_ptr mElementStyle; std::shared_ptr mGlobalDefinition; std::shared_ptr mDefinition; - CSS::TransitionsMap mTransitions; CSS::AnimationsMap mAnimations; UnorderedSet mRelatedWidgets; UnorderedSet mSubscribedWidgets; diff --git a/src/eepp/core/memorymanager.cpp b/src/eepp/core/memorymanager.cpp index 9bb4d67a3..75b3b39f4 100644 --- a/src/eepp/core/memorymanager.cpp +++ b/src/eepp/core/memorymanager.cpp @@ -18,7 +18,7 @@ using namespace EE::Window; namespace EE { -AllocatedPointer::AllocatedPointer( void* data, const std::string& file, int line, size_t memory, +AllocatedPointer::AllocatedPointer( void* data, const char* file, int line, size_t memory, bool track, bool globalAllocation ) { mData = data; mFile = file; @@ -75,7 +75,7 @@ void* MemoryManager::addPointerInPlace( void* place, const AllocatedPointer& aAl AllocatedPointerMapIt it = state.pointers.find( place ); if ( it != state.pointers.end() ) { - removePointer( place, aAllocatedPointer.mFile.c_str(), aAllocatedPointer.mLine ); + removePointer( place, aAllocatedPointer.mFile, aAllocatedPointer.mLine ); } return addPointer( aAllocatedPointer ); @@ -110,7 +110,7 @@ void* MemoryManager::addPointer( const AllocatedPointer& aAllocatedPointer ) { if ( aAllocatedPointer.mTrack ) eePRINTL( "Allocating pointer %p at '%s' %d", aAllocatedPointer.mData, - aAllocatedPointer.mFile.c_str(), aAllocatedPointer.mLine ); + aAllocatedPointer.mFile, aAllocatedPointer.mLine ); return aAllocatedPointer.mData; } @@ -123,7 +123,7 @@ void* MemoryManager::reallocPointer( void* data, const AllocatedPointer& aAlloca AllocatedPointerMapIt it = state.pointers.find( data ); if ( it != state.pointers.end() && it->second.mTrack ) - eePRINTL( "Realloc pointer %p at '%s' %d", data, aAllocatedPointer.mFile.c_str(), + eePRINTL( "Realloc pointer %p at '%s' %d", data, aAllocatedPointer.mFile, aAllocatedPointer.mLine ); if ( it == state.pointers.end() ) @@ -131,10 +131,10 @@ void* MemoryManager::reallocPointer( void* data, const AllocatedPointer& aAlloca if ( aAllocatedPointer.mTrack ) eePRINTL( "Reallocating pointer %p at '%s' %d", aAllocatedPointer.mData, - aAllocatedPointer.mFile.c_str(), aAllocatedPointer.mLine ); + aAllocatedPointer.mFile, aAllocatedPointer.mLine ); if ( it->first != aAllocatedPointer.mData ) { - removePointer( data, aAllocatedPointer.mFile.c_str(), aAllocatedPointer.mLine ); + removePointer( data, aAllocatedPointer.mFile, aAllocatedPointer.mLine ); addPointer( aAllocatedPointer ); } else { state.totalMemoryUsage -= it->second.mMemory; @@ -310,7 +310,7 @@ void MemoryManager::showResults() { char address[2 + sizeof( void* ) * 2 + 1]; snprintf( address, sizeof( address ), "%p", ap.mData ); - leaks.add_row( { address, ap.mFile, std::to_string( ap.mLine ), + leaks.add_row( { address, std::string{ ap.mFile }, std::to_string( ap.mLine ), FileSystem::sizeToString( static_cast( ap.mMemory ) ) } ); } @@ -320,7 +320,7 @@ void MemoryManager::showResults() { auto allocationLocation = []( const AllocatedPointer& allocation ) { return allocation.mData == nullptr ? std::string( "-" ) - : allocation.mFile + ":" + std::to_string( allocation.mLine ); + : std::string{ allocation.mFile } + ":" + std::to_string( allocation.mLine ); }; tabulate::Table summary; diff --git a/src/eepp/scene/action.cpp b/src/eepp/scene/action.cpp index f875d9e9c..4cbd4c10d 100644 --- a/src/eepp/scene/action.cpp +++ b/src/eepp/scene/action.cpp @@ -1,8 +1,138 @@ +#include +#include #include #include +#include +#include namespace EE { namespace Scene { +namespace { + +class ActionPool { + public: + static ActionPool& instance(); + + void* allocate( std::size_t size ) { + const std::size_t classIndex = findClass( size ); + if ( classIndex == NumClasses ) + return allocateLarge( size ); + + std::lock_guard lock( mMutex ); + SizeClass& sizeClass = mClasses[classIndex]; + if ( nullptr == sizeClass.freeList ) + allocatePage( classIndex ); + + BlockHeader* block = sizeClass.freeList; + sizeClass.freeList = block->next; + block->classIndex = static_cast( classIndex ); + return block + 1; + } + + void deallocate( void* ptr ) noexcept { + if ( nullptr == ptr ) + return; + + BlockHeader* block = static_cast( ptr ) - 1; + if ( block->classIndex == LargeAllocation ) { + std::free( block ); + return; + } + + std::lock_guard lock( mMutex ); + SizeClass& sizeClass = mClasses[block->classIndex]; + block->next = sizeClass.freeList; + sizeClass.freeList = block; + } + + private: + static constexpr std::array ClassSizes = { 64, 96, 128, 192, 256, + 384, 512, 768, 1024 }; + static constexpr std::size_t NumClasses = ClassSizes.size(); + static constexpr Uint16 LargeAllocation = static_cast( -1 ); + static constexpr std::size_t BlocksPerPage = 64; + + struct alignas( std::max_align_t ) BlockHeader { + BlockHeader* next; + Uint16 classIndex; + }; + + struct Page { + Page* next; + }; + + struct SizeClass { + BlockHeader* freeList{ nullptr }; + Page* pages{ nullptr }; + }; + + std::array mClasses; + std::mutex mMutex; + + static constexpr std::size_t alignUp( std::size_t size ) { + return ( size + alignof( std::max_align_t ) - 1 ) & ~( alignof( std::max_align_t ) - 1 ); + } + + static std::size_t findClass( std::size_t size ) { + for ( std::size_t i = 0; i < NumClasses; ++i ) { + if ( size <= ClassSizes[i] ) + return i; + } + return NumClasses; + } + + void allocatePage( std::size_t classIndex ) { + SizeClass& sizeClass = mClasses[classIndex]; + const std::size_t stride = alignUp( sizeof( BlockHeader ) + ClassSizes[classIndex] ); + const std::size_t bytes = alignUp( sizeof( Page ) ) + stride * BlocksPerPage; + Page* page = static_cast( std::malloc( bytes ) ); + if ( nullptr == page ) + throw std::bad_alloc(); + + page->next = sizeClass.pages; + sizeClass.pages = page; + char* storage = reinterpret_cast( page ) + alignUp( sizeof( Page ) ); + for ( std::size_t i = 0; i < BlocksPerPage; ++i ) { + BlockHeader* block = reinterpret_cast( storage + stride * i ); + block->classIndex = static_cast( classIndex ); + block->next = sizeClass.freeList; + sizeClass.freeList = block; + } + } + + static void* allocateLarge( std::size_t size ) { + BlockHeader* block = + static_cast( std::malloc( sizeof( BlockHeader ) + size ) ); + if ( nullptr == block ) + throw std::bad_alloc(); + block->classIndex = LargeAllocation; + block->next = nullptr; + return block + 1; + } +}; + +ActionPool& ActionPool::instance() { + // Actions can be owned by globals initialized before the first action allocation. Keeping the + // pool alive until process exit avoids a static-destruction-order race with those owners. + alignas( ActionPool ) static unsigned char storage[sizeof( ActionPool )]; + static ActionPool* pool = new ( storage ) ActionPool; + return *pool; +} + +} // namespace + +void* Action::operator new( std::size_t size ) { + return ActionPool::instance().allocate( size ); +} + +void Action::operator delete( void* ptr ) noexcept { + ActionPool::instance().deallocate( ptr ); +} + +void Action::operator delete( void* ptr, std::size_t ) noexcept { + ActionPool::instance().deallocate( ptr ); +} + Action::~Action() { sendEvent( ActionType::OnDelete ); } @@ -53,7 +183,7 @@ Action* Action::reverse() const { Uint32 Action::addEventListener( const ActionType& actionType, const ActionCallback& callback ) { mNumCallBacks++; - mCallbacks[actionType][mNumCallBacks] = callback; + mCallbacks.emplace_back( ActionCallbackEntry{ actionType, mNumCallBacks, callback } ); return mNumCallBacks; } @@ -65,22 +195,23 @@ Action* Action::on( const Action::ActionType& actionType, const Action::ActionCa void Action::removeEventListener( const Uint32& callbackId ) { for ( auto it = mCallbacks.begin(); it != mCallbacks.end(); ++it ) { - UnorderedMap& event = it->second; - - if ( event.erase( callbackId ) ) + if ( it->id == callbackId ) { + mCallbacks.erase( it ); break; + } } } void Action::sendEvent( const ActionType& actionType ) { - if ( 0 != mCallbacks.count( actionType ) ) { - auto event = mCallbacks[actionType]; - - if ( !event.empty() ) { - for ( auto it = event.begin(); it != event.end(); ++it ) - it->second( this, actionType ); - } + // Callbacks are allowed to mutate listener registration. Preserve the previous snapshot + // semantics while keeping the common callback count in inline storage. + SmallVector callbacks; + for ( const auto& callback : mCallbacks ) { + if ( callback.type == actionType ) + callbacks.emplace_back( callback.callback ); } + for ( auto& callback : callbacks ) + callback( this, actionType ); } void Action::onStart() {} diff --git a/src/eepp/scene/actionmanager.cpp b/src/eepp/scene/actionmanager.cpp index 190c739cd..8c89ef5c6 100644 --- a/src/eepp/scene/actionmanager.cpp +++ b/src/eepp/scene/actionmanager.cpp @@ -81,12 +81,21 @@ std::vector ActionManager::getActionsByTagFromTarget( Node* target, return actions; } +void ActionManager::getActionsByTagFromTarget( Node* target, const Action::UniqueID& tag, + SmallVector& actions ) { + Lock l( mMutex ); + for ( Action* action : mActions ) { + if ( action->getTarget() == target && action->getTag() == tag ) + actions.emplace_back( action ); + } +} + bool ActionManager::removeActionByTag( const Action::UniqueID& tag ) { return removeAction( getActionByTag( tag ) ); } bool ActionManager::removeActionsByTagFromTarget( Node* target, const Action::UniqueID& tag ) { - std::vector removeList; + ActionList removeList; { Lock l( mMutex ); @@ -226,7 +235,7 @@ bool ActionManager::removeActions( const std::vector& actions ) { } bool ActionManager::removeAllActionsFromTarget( Node* target ) { - std::vector removeList; + ActionList removeList; { Lock l( mMutex ); diff --git a/src/eepp/scene/actions/runnable.cpp b/src/eepp/scene/actions/runnable.cpp index 22da9e534..55e9f8d66 100644 --- a/src/eepp/scene/actions/runnable.cpp +++ b/src/eepp/scene/actions/runnable.cpp @@ -4,7 +4,7 @@ namespace EE { namespace Scene { namespace Actions { Runnable* Runnable::New( RunnableFunc callback, const Time& time, bool loop ) { - return eeNew( Runnable, ( callback, time, loop ) ); + return eeNew( Runnable, ( std::move( callback ), time, loop ) ); } Runnable::Runnable( Runnable::RunnableFunc callback, const Time& time, bool loop ) : diff --git a/src/eepp/scene/actions/sequence.cpp b/src/eepp/scene/actions/sequence.cpp index d8ee28302..d2bbbd339 100644 --- a/src/eepp/scene/actions/sequence.cpp +++ b/src/eepp/scene/actions/sequence.cpp @@ -7,41 +7,45 @@ Sequence* Sequence::New( const std::vector sequence ) { } Sequence* Sequence::New( Action* action, Action* action2 ) { - return New( {action, action2} ); + return eeNew( Sequence, ( ActionContainer{ action, action2 } ) ); } Sequence* Sequence::New( Action* action, Action* action2, Action* action3 ) { - return New( {action, action2, action3} ); + return eeNew( Sequence, ( ActionContainer{ action, action2, action3 } ) ); } Sequence* Sequence::New( Action* action, Action* action2, Action* action3, Action* action4 ) { - return New( {action, action2, action3, action4} ); + return eeNew( Sequence, ( ActionContainer{ action, action2, action3, action4 } ) ); } Sequence* Sequence::New( Action* action, Action* action2, Action* action3, Action* action4, Action* action5 ) { - return New( {action, action2, action3, action4, action5} ); + return eeNew( Sequence, ( ActionContainer{ action, action2, action3, action4, action5 } ) ); } Sequence* Sequence::New( Action* action, Action* action2, Action* action3, Action* action4, Action* action5, Action* action6 ) { - return New( {action, action2, action3, action4, action5, action6} ); + return eeNew( Sequence, + ( ActionContainer{ action, action2, action3, action4, action5, action6 } ) ); } Sequence* Sequence::New( Action* action, Action* action2, Action* action3, Action* action4, Action* action5, Action* action6, Action* action7 ) { - return New( {action, action2, action3, action4, action5, action6, action7} ); + return eeNew( Sequence, ( ActionContainer{ action, action2, action3, action4, action5, action6, + action7 } ) ); } Sequence* Sequence::New( Action* action, Action* action2, Action* action3, Action* action4, Action* action5, Action* action6, Action* action7, Action* action8 ) { - return New( {action, action2, action3, action4, action5, action6, action7, action8} ); + return eeNew( Sequence, ( ActionContainer{ action, action2, action3, action4, action5, action6, + action7, action8 } ) ); } Sequence* Sequence::New( Action* action, Action* action2, Action* action3, Action* action4, Action* action5, Action* action6, Action* action7, Action* action8, Action* action9 ) { - return New( {action, action2, action3, action4, action5, action6, action7, action8, action9} ); + return eeNew( Sequence, ( ActionContainer{ action, action2, action3, action4, action5, action6, + action7, action8, action9 } ) ); } void Sequence::start() { @@ -102,17 +106,17 @@ Time Sequence::getTotalTime() { } Action* Sequence::clone() const { - return Sequence::New( mSequence ); + return eeNew( Sequence, ( mSequence ) ); } Action* Sequence::reverse() const { - std::vector reversed; + ActionContainer reversed; for ( auto it = mSequence.rbegin(); it != mSequence.rend(); ++it ) { reversed.push_back( *it ); } - return Sequence::New( reversed ); + return eeNew( Sequence, ( std::move( reversed ) ) ); } Sequence::~Sequence() { @@ -122,6 +126,9 @@ Sequence::~Sequence() { } } -Sequence::Sequence( const std::vector sequence ) : mSequence( sequence ), mCurPos( 0 ) {} +Sequence::Sequence( const std::vector sequence ) : + mSequence( sequence.begin(), sequence.end() ), mCurPos( 0 ) {} + +Sequence::Sequence( ActionContainer sequence ) : mSequence( std::move( sequence ) ), mCurPos( 0 ) {} }}} // namespace EE::Scene::Actions diff --git a/src/eepp/scene/actions/spawn.cpp b/src/eepp/scene/actions/spawn.cpp index 8eb4c7df8..42ebc8cd7 100644 --- a/src/eepp/scene/actions/spawn.cpp +++ b/src/eepp/scene/actions/spawn.cpp @@ -7,41 +7,45 @@ Spawn* Spawn::New( const std::vector spawn ) { } Spawn* Spawn::New( Action* action, Action* action2 ) { - return New( {action, action2} ); + return eeNew( Spawn, ( ActionContainer{ action, action2 } ) ); } Spawn* Spawn::New( Action* action, Action* action2, Action* action3 ) { - return New( {action, action2, action3} ); + return eeNew( Spawn, ( ActionContainer{ action, action2, action3 } ) ); } Spawn* Spawn::New( Action* action, Action* action2, Action* action3, Action* action4 ) { - return New( {action, action2, action3, action4} ); + return eeNew( Spawn, ( ActionContainer{ action, action2, action3, action4 } ) ); } Spawn* Spawn::New( Action* action, Action* action2, Action* action3, Action* action4, Action* action5 ) { - return New( {action, action2, action3, action4, action5} ); + return eeNew( Spawn, ( ActionContainer{ action, action2, action3, action4, action5 } ) ); } Spawn* Spawn::New( Action* action, Action* action2, Action* action3, Action* action4, Action* action5, Action* action6 ) { - return New( {action, action2, action3, action4, action5, action6} ); + return eeNew( Spawn, + ( ActionContainer{ action, action2, action3, action4, action5, action6 } ) ); } Spawn* Spawn::New( Action* action, Action* action2, Action* action3, Action* action4, Action* action5, Action* action6, Action* action7 ) { - return New( {action, action2, action3, action4, action5, action6, action7} ); + return eeNew( Spawn, ( ActionContainer{ action, action2, action3, action4, action5, action6, + action7 } ) ); } Spawn* Spawn::New( Action* action, Action* action2, Action* action3, Action* action4, Action* action5, Action* action6, Action* action7, Action* action8 ) { - return New( {action, action2, action3, action4, action5, action6, action7, action8} ); + return eeNew( Spawn, ( ActionContainer{ action, action2, action3, action4, action5, action6, + action7, action8 } ) ); } Spawn* Spawn::New( Action* action, Action* action2, Action* action3, Action* action4, Action* action5, Action* action6, Action* action7, Action* action8, Action* action9 ) { - return New( {action, action2, action3, action4, action5, action6, action7, action8, action9} ); + return eeNew( Spawn, ( ActionContainer{ action, action2, action3, action4, action5, action6, + action7, action8, action9 } ) ); } void Spawn::start() { @@ -102,17 +106,17 @@ Time Spawn::getTotalTime() { } Action* Spawn::clone() const { - return Spawn::New( mSpawn ); + return eeNew( Spawn, ( mSpawn ) ); } Action* Spawn::reverse() const { - std::vector reversed; + ActionContainer reversed; for ( auto it = mSpawn.rbegin(); it != mSpawn.rend(); ++it ) { reversed.push_back( *it ); } - return Spawn::New( reversed ); + return eeNew( Spawn, ( std::move( reversed ) ) ); } Spawn::~Spawn() { @@ -122,6 +126,9 @@ Spawn::~Spawn() { } } -Spawn::Spawn( const std::vector spawn ) : mSpawn( spawn ), mAllDone( false ) {} +Spawn::Spawn( const std::vector spawn ) : + mSpawn( spawn.begin(), spawn.end() ), mAllDone( false ) {} + +Spawn::Spawn( ActionContainer spawn ) : mSpawn( std::move( spawn ) ), mAllDone( false ) {} }}} // namespace EE::Scene::Actions diff --git a/src/eepp/scene/node.cpp b/src/eepp/scene/node.cpp index 7e471cf9b..86773024d 100644 --- a/src/eepp/scene/node.cpp +++ b/src/eepp/scene/node.cpp @@ -1526,6 +1526,10 @@ std::vector Node::getActionsByTag( const Action::UniqueID& tag ) { return getActionManager()->getActionsByTagFromTarget( this, tag ); } +void Node::getActionsByTag( const Action::UniqueID& tag, SmallVector& actions ) { + getActionManager()->getActionsByTagFromTarget( this, tag, actions ); +} + void Node::clearActions() { getActionManager()->removeAllActionsFromTarget( this ); } diff --git a/src/eepp/system/color.cpp b/src/eepp/system/color.cpp index aa23cd405..c891319cd 100644 --- a/src/eepp/system/color.cpp +++ b/src/eepp/system/color.cpp @@ -106,10 +106,7 @@ static bool tryParseModernRgbRgba( std::string_view paramsStr, Color& outColor ) } static bool parseHslHue( Float& out, std::string_view tok ) { - while ( !tok.empty() && ( tok.front() == ' ' || tok.front() == '\t' ) ) - tok.remove_prefix( 1 ); - while ( !tok.empty() && ( tok.back() == ' ' || tok.back() == '\t' ) ) - tok.remove_suffix( 1 ); + tok = String::trim( tok, " \t" ); if ( tok.empty() ) return false; if ( tok.size() == 4 && ( std::strncmp( tok.data(), "none", 4 ) == 0 || @@ -133,10 +130,7 @@ static bool parseHslHue( Float& out, std::string_view tok ) { } static bool parseHslPercentOrNumber( Float& out, std::string_view tok ) { - while ( !tok.empty() && ( tok.front() == ' ' || tok.front() == '\t' ) ) - tok.remove_prefix( 1 ); - while ( !tok.empty() && ( tok.back() == ' ' || tok.back() == '\t' ) ) - tok.remove_suffix( 1 ); + tok = String::trim( tok, " \t" ); if ( tok.empty() ) return false; if ( tok.size() == 4 && ( std::strncmp( tok.data(), "none", 4 ) == 0 || diff --git a/src/eepp/system/functionstring.cpp b/src/eepp/system/functionstring.cpp index 42c2d962a..b95924a7e 100644 --- a/src/eepp/system/functionstring.cpp +++ b/src/eepp/system/functionstring.cpp @@ -3,128 +3,56 @@ #include #include -#include namespace EE { namespace System { -template FunctionString FunctionString::parse( StringType function ) { - using CharType = typename StringType::value_type; - - size_t funcSep = function.find( '(' ); - if ( funcSep == StringType::npos ) - return FunctionString( "", {}, {} ); - - auto funcName = String::trim( function.substr( 0, funcSep ) ); +FunctionString FunctionString::parseOwned( std::string_view function ) { + View view = parseView( function ); + if ( view.isEmpty() ) + return {}; Parameters funcParameters; TypeStringVector typeStringData; - - auto parametersString = function.substr( funcSep + 1 ); - size_t paramClose = parametersString.find_last_of( ')' ); - if ( paramClose == StringType::npos ) - return FunctionString( "", {}, {} ); - - parametersString = parametersString.substr( 0, paramClose ); - - bool stateParsingString = false; - std::basic_string buffer; - CharType prevChar = 0; - CharType quoteChar = 0; - - bool currentParamIsString = false; - int parenDepth = 0; - - auto pushBufferToParams = [&]() { - if constexpr ( std::is_same_v ) { - if ( !currentParamIsString ) - String::trimInPlace( buffer ); - funcParameters.push_back( buffer ); - } else { - std::string utf8Buffer = String( buffer ).toUtf8(); - if ( !currentParamIsString ) - String::trimInPlace( utf8Buffer ); - funcParameters.push_back( utf8Buffer ); + view.forEachParameter( [&]( std::string_view parameter, bool wasString ) { + std::string owned; + owned.reserve( parameter.size() ); + const char quote = wasString ? function[parameter.data() - function.data() - 1] : 0; + for ( std::size_t i = 0; i < parameter.size(); ++i ) { + if ( wasString && parameter[i] == '\\' && i + 1 < parameter.size() && + parameter[i + 1] == quote ) + continue; + owned += parameter[i]; } - typeStringData.push_back( currentParamIsString ); - buffer.clear(); - currentParamIsString = false; - }; + funcParameters.emplace_back( std::move( owned ) ); + typeStringData.emplace_back( wasString ); + return true; + } ); + return FunctionString( std::string{ view.getName() }, std::move( funcParameters ), + std::move( typeStringData ) ); +} - for ( size_t i = 0; i < parametersString.length(); ++i ) { - - CharType c = parametersString[i]; - - if ( !stateParsingString ) { - if ( c == '(' ) { - parenDepth++; - buffer += c; - } else if ( c == ')' ) { - if ( parenDepth == 0 ) - break; - if ( parenDepth > 0 ) - parenDepth--; - buffer += c; - } else if ( c == ',' ) { - if ( parenDepth == 0 ) { - // Add param if we either accumulated characters or actively parsed an empty - // string parameter. - if ( !buffer.empty() || currentParamIsString ) { - pushBufferToParams(); - } - } else { - buffer += c; - } - } else if ( c == '"' || c == '\'' ) { - stateParsingString = true; - quoteChar = c; - if ( parenDepth == 0 && buffer.empty() ) { - currentParamIsString = true; - } else { - buffer += c; - } - } else if ( c == ' ' ) { - if ( buffer.empty() || currentParamIsString ) - continue; - else - buffer += c; - } else { - buffer += c; - } - } else { - if ( c == '\\' && i + 1 < parametersString.length() && - parametersString[i + 1] == quoteChar ) { - } else if ( prevChar != '\\' && c == quoteChar ) { - stateParsingString = false; - if ( !currentParamIsString ) { - buffer += c; - } - } else { - buffer += c; - } - prevChar = c; - } - } - - // Add param if we either accumulated characters or actively parsed an empty string parameter. - if ( !buffer.empty() || currentParamIsString ) - pushBufferToParams(); - - if constexpr ( std::is_same_v ) { - return FunctionString( std::string{ funcName }, funcParameters, typeStringData ); - } else { - return FunctionString( String( funcName ).toUtf8(), funcParameters, typeStringData ); - } +FunctionString::View FunctionString::parseView( std::string_view function ) { + const std::size_t open = function.find( '(' ); + if ( open == std::string_view::npos ) + return {}; + const std::size_t close = function.rfind( ')' ); + if ( close == std::string_view::npos || close < open ) + return {}; + std::string_view name = String::trim( function.substr( 0, open ), " \t\n\r\f\v" ); + if ( name.empty() ) + return {}; + return View{ name, function.substr( open + 1, close - open - 1 ) }; } FunctionString FunctionString::parse( const std::string& function ) { - return parse( std::string_view{ function } ); + return parseOwned( function ); } FunctionString FunctionString::parse( std::string_view function ) { - return parse( function ); + return parseOwned( function ); } FunctionString FunctionString::parse( String::View function ) { - return parse( function ); + return parseOwned( String( function ).toUtf8() ); } FunctionString::FunctionString( const std::string& name, const Parameters& parameters, diff --git a/src/eepp/ui/css/animationdefinition.cpp b/src/eepp/ui/css/animationdefinition.cpp index a0acc68a1..5320566ae 100644 --- a/src/eepp/ui/css/animationdefinition.cpp +++ b/src/eepp/ui/css/animationdefinition.cpp @@ -119,7 +119,8 @@ UnorderedMap AnimationDefinition::parseAnimati case PropertyId::AnimationTimingFunction: { TimingFunction tf( TimingFunction::parse( val ) ); timingFunctions.emplace_back( tf.interpolation ); - timingFunctionParameters.emplace_back( tf.parameters ); + timingFunctionParameters.emplace_back( tf.parameters.begin(), + tf.parameters.end() ); break; } default: @@ -256,12 +257,29 @@ const String::HashType& AnimationDefinition::getId() const { } const std::vector& AnimationDefinition::getTimingFunctionParameters() const { + if ( !mTimingFunctionParametersVectorValid ) { + mTimingFunctionParametersVector.assign( mTimingFunctionParameters.begin(), + mTimingFunctionParameters.end() ); + mTimingFunctionParametersVectorValid = true; + } + return mTimingFunctionParametersVector; +} + +const TimingFunction::Parameters& AnimationDefinition::getTimingFunctionParametersInline() const { return mTimingFunctionParameters; } void AnimationDefinition::setTimingFunctionParameters( const std::vector& timingFunctionParameters ) { + mTimingFunctionParameters.assign( timingFunctionParameters.begin(), + timingFunctionParameters.end() ); + mTimingFunctionParametersVectorValid = false; +} + +void AnimationDefinition::setTimingFunctionParameters( + const TimingFunction::Parameters& timingFunctionParameters ) { mTimingFunctionParameters = timingFunctionParameters; + mTimingFunctionParametersVectorValid = false; } const AnimationDefinition::AnimationFillMode& AnimationDefinition::getFillMode() const { diff --git a/src/eepp/ui/css/elementdefinition.cpp b/src/eepp/ui/css/elementdefinition.cpp index 6629569d2..22793d118 100644 --- a/src/eepp/ui/css/elementdefinition.cpp +++ b/src/eepp/ui/css/elementdefinition.cpp @@ -34,6 +34,10 @@ const std::vector& ElementDefinition::getTransitionPr return mTransitionProperties; } +const ComputedTransitions& ElementDefinition::getTransitions() const { + return mTransitions; +} + const std::vector& ElementDefinition::getAnimationProperties() const { return mAnimationProperties; } @@ -81,6 +85,7 @@ void ElementDefinition::refresh() { } resolveVariables(); + mTransitions = ComputedTransitions::parse( mTransitionProperties ); for ( auto& property : mProperties ) { // Only registered properties participate in the dense ID set. Unknown diff --git a/src/eepp/ui/css/stylesheetlength.cpp b/src/eepp/ui/css/stylesheetlength.cpp index 8413130c0..7b8a013cb 100644 --- a/src/eepp/ui/css/stylesheetlength.cpp +++ b/src/eepp/ui/css/stylesheetlength.cpp @@ -232,6 +232,9 @@ StyleSheetLength::StyleSheetLength( const Float& val, const StyleSheetLength::Un mUnit( unit ), mValue( val ) {} StyleSheetLength::StyleSheetLength( const std::string& val, const Float& defaultValue ) : + StyleSheetLength( fromString( std::string_view{ val }, defaultValue ) ) {} + +StyleSheetLength::StyleSheetLength( std::string_view val, const Float& defaultValue ) : StyleSheetLength( fromString( val, defaultValue ) ) {} StyleSheetLength::StyleSheetLength( const StyleSheetLength& val ) { @@ -382,9 +385,14 @@ StyleSheetLength& StyleSheetLength::operator=( const StyleSheetLength& val ) { StyleSheetLength StyleSheetLength::fromString( const std::string& str, const Float& defaultValue, bool pxAsDp ) { + return fromString( std::string_view{ str }, defaultValue, pxAsDp ); +} + +StyleSheetLength StyleSheetLength::fromString( std::string_view str, const Float& defaultValue, + bool pxAsDp ) { StyleSheetLength length; length.setValue( defaultValue, Unit::Px ); - const std::string_view value = String::trim( std::string_view( str ) ); + const std::string_view value = String::trim( str ); if ( isFunctionString( value ) ) { Unit funcUnit = Unit::Px; @@ -470,9 +478,8 @@ bool StyleSheetLength::isFunctionString( std::string_view str ) { String::istartsWith( str, "max(" ) || String::istartsWith( str, "calc(" ); } -bool StyleSheetLength::parseFunction( const std::string& str, Unit& outUnit, Arguments& outArgs ) { - std::string_view sv( str ); - std::string_view trimmed = String::trim( sv ); +bool StyleSheetLength::parseFunction( std::string_view str, Unit& outUnit, Arguments& outArgs ) { + std::string_view trimmed = String::trim( str ); if ( String::istartsWith( trimmed, "clamp(" ) ) { outUnit = Unit::Clamp; diff --git a/src/eepp/ui/css/stylesheetpropertyanimation.cpp b/src/eepp/ui/css/stylesheetpropertyanimation.cpp index 1aa05e381..a92a055bd 100644 --- a/src/eepp/ui/css/stylesheetpropertyanimation.cpp +++ b/src/eepp/ui/css/stylesheetpropertyanimation.cpp @@ -11,7 +11,7 @@ using namespace EE::Math::easing; namespace EE { namespace UI { namespace CSS { inline Float easingFn( const Ease::Interpolation& timingFunction, - const std::vector& timingFunctionParameters, const double& t, + const TimingFunction::Parameters& timingFunctionParameters, const double& t, const double& b, const double& c, const double& d ) { if ( timingFunction != Ease::Interpolation::CubizBezier ) return easingCb[timingFunction]( t, b, c, d ); @@ -24,13 +24,12 @@ inline Float easingFn( const Ease::Interpolation& timingFunction, return t; } -void StyleSheetPropertyAnimation::tweenProperty( UIWidget* widget, const Float& normalizedProgress, - const PropertyDefinition* property, - const std::string& startValue, - const std::string& endValue, - const Ease::Interpolation& timingFunction, - const std::vector timingFunctionParameters, - const Uint32& propertyIndex, const bool& isDone ) { +void StyleSheetPropertyAnimation::tweenProperty( + UIWidget* widget, const Float& normalizedProgress, const PropertyDefinition* property, + const std::string& startValue, const std::string& endValue, + const Ease::Interpolation& timingFunction, + const TimingFunction::Parameters& timingFunctionParameters, const Uint32& propertyIndex, + const bool& isDone ) { switch ( property->getType() ) { case PropertyType::NumberFloat: case PropertyType::NumberInt: { @@ -220,13 +219,27 @@ StyleSheetPropertyAnimation* StyleSheetPropertyAnimation::New( const Uint32& propertyIndex, const Time& duration, const Time& delay, const Ease::Interpolation& timingFunction, const std::vector& timingFunctionParameters, const AnimationOrigin& animationOrigin ) { + TimingFunction::Parameters parameters; + parameters.assign( timingFunctionParameters.begin(), timingFunctionParameters.end() ); + return New( property, startValue, endValue, propertyIndex, duration, delay, timingFunction, + parameters, animationOrigin ); +} + +StyleSheetPropertyAnimation* +StyleSheetPropertyAnimation::New( const PropertyDefinition* property, const std::string& startValue, + const std::string& endValue, const Uint32& propertyIndex, + const Time& duration, const Time& delay, + const Ease::Interpolation& timingFunction, + const TimingFunction::Parameters& timingFunctionParameters, + const AnimationOrigin& animationOrigin ) { AnimationDefinition animation; animation.setDelay( delay ); animation.setDuration( duration ); animation.setTimingFunction( timingFunction ); animation.setTimingFunctionParameters( timingFunctionParameters ); - return New( animation, property, { startValue, endValue }, { 0, 1 }, propertyIndex, - animationOrigin ); + return eeNew( StyleSheetPropertyAnimation, + ( animation, property, SmallVector{ startValue, endValue }, + SmallVector{ 0, 1 }, propertyIndex, animationOrigin ) ); } StyleSheetPropertyAnimation::StyleSheetPropertyAnimation( const AnimationDefinition& animation, @@ -237,8 +250,26 @@ StyleSheetPropertyAnimation::StyleSheetPropertyAnimation( const AnimationDefinit const AnimationOrigin& animationOrigin ) : mAnimation( animation ), mPropertyDef( propertyDef ), - mStates( states ), - mAnimationStepsTime( animationStepsTime ), + mStates( std::make_move_iterator( states.begin() ), std::make_move_iterator( states.end() ) ), + mAnimationStepsTime( std::make_move_iterator( animationStepsTime.begin() ), + std::make_move_iterator( animationStepsTime.end() ) ), + mPendingIterations( animation.getIterations() ), + mPropertyIndex( propertyIndex ), + mAnimationOrigin( animationOrigin ), + mPaused( mAnimation.isPaused() ) { + mId = ID; +} + +StyleSheetPropertyAnimation::StyleSheetPropertyAnimation( const AnimationDefinition& animation, + const PropertyDefinition* propertyDef, + SmallVector states, + SmallVector animationStepsTime, + const Uint32& propertyIndex, + const AnimationOrigin& animationOrigin ) : + mAnimation( animation ), + mPropertyDef( propertyDef ), + mStates( std::move( states ) ), + mAnimationStepsTime( std::move( animationStepsTime ) ), mPendingIterations( animation.getIterations() ), mPropertyIndex( propertyIndex ), mAnimationOrigin( animationOrigin ), @@ -321,15 +352,17 @@ Time StyleSheetPropertyAnimation::getTotalTime() { } Action* StyleSheetPropertyAnimation::clone() const { - return New( mAnimation, mPropertyDef, mStates, mAnimationStepsTime, mPropertyIndex, - mAnimationOrigin ); + return eeNew( StyleSheetPropertyAnimation, + ( mAnimation, mPropertyDef, mStates, mAnimationStepsTime, mPropertyIndex, + mAnimationOrigin ) ); } Action* StyleSheetPropertyAnimation::reverse() const { - std::vector vcopy( mStates ); + SmallVector vcopy( mStates ); std::reverse( vcopy.begin(), vcopy.end() ); - return New( mAnimation, mPropertyDef, vcopy, mAnimationStepsTime, mPropertyIndex, - mAnimationOrigin ); + return eeNew( StyleSheetPropertyAnimation, + ( mAnimation, mPropertyDef, std::move( vcopy ), mAnimationStepsTime, + mPropertyIndex, mAnimationOrigin ) ); } const Uint32& StyleSheetPropertyAnimation::getPropertyIndex() const { @@ -371,7 +404,8 @@ void StyleSheetPropertyAnimation::onUpdate( const Time& ) { Float relativeProgress = curTime / relTime; tweenProperty( widget, relativeProgress, mPropertyDef, mStates[curPos - 1], mStates[curPos], mAnimation.getTimingFunction(), - mAnimation.getTimingFunctionParameters(), mPropertyIndex, isDone() ); + mAnimation.getTimingFunctionParametersInline(), mPropertyIndex, + isDone() ); } } } @@ -480,16 +514,16 @@ void StyleSheetPropertyAnimation::prepareDirection() { } void StyleSheetPropertyAnimation::reverseAnimation() { - std::vector reverseCopy( mStates ); + SmallVector reverseCopy( mStates ); std::reverse( reverseCopy.begin(), reverseCopy.end() ); - mStates = reverseCopy; + mStates = std::move( reverseCopy ); - std::vector reverseTimes( mAnimationStepsTime ); + SmallVector reverseTimes( mAnimationStepsTime ); std::reverse( reverseTimes.begin(), reverseTimes.end() ); for ( size_t i = 0; i < reverseTimes.size(); i++ ) { reverseTimes[i] = 1.f - reverseTimes[i]; } - mAnimationStepsTime = reverseTimes; + mAnimationStepsTime = std::move( reverseTimes ); } }}} // namespace EE::UI::CSS diff --git a/src/eepp/ui/css/timingfunction.cpp b/src/eepp/ui/css/timingfunction.cpp index 2d74c1694..a8a98c7b6 100644 --- a/src/eepp/ui/css/timingfunction.cpp +++ b/src/eepp/ui/css/timingfunction.cpp @@ -1,29 +1,45 @@ -#include +#include #include -using namespace EE::System; - namespace EE { namespace UI { namespace CSS { TimingFunction TimingFunction::parse( std::string timingFunction ) { - String::trimInPlace( timingFunction ); - String::toLowerInPlace( timingFunction ); - if ( timingFunction.find_first_of( '(' ) == std::string::npos ) { - return { Ease::fromName( timingFunction ), {} }; - } else { - TimingFunction tf; - FunctionString func( FunctionString::parse( timingFunction ) ); - if ( !func.isEmpty() ) { - tf.interpolation = Ease::fromName( timingFunction, Ease::Interpolation::None ); - const auto& params = func.getParameters(); - for ( const auto& param : params ) { - double d; - if ( String::fromString( d, param ) ) - tf.parameters.emplace_back( d ); - } - } - return tf; + return parse( std::string_view{ timingFunction } ); +} + +TimingFunction TimingFunction::parse( std::string_view timingFunction ) { + timingFunction = String::trim( timingFunction, " \t\n\r\f\v" ); + SmallVector normalized; + for ( char character : timingFunction ) + normalized.emplace_back( character >= 'A' && character <= 'Z' ? character + 32 + : character ); + timingFunction = std::string_view{ normalized.data(), normalized.size() }; + + TimingFunction result; + const auto open = timingFunction.find( '(' ); + if ( open == std::string_view::npos ) { + result.interpolation = Ease::fromName( timingFunction, Ease::Interpolation::None ); + return result; } + const auto close = timingFunction.rfind( ')' ); + if ( close == std::string_view::npos || close <= open ) + return result; + result.interpolation = + Ease::fromName( timingFunction.substr( 0, open ), Ease::Interpolation::None ); + + std::string_view parameters = timingFunction.substr( open + 1, close - open - 1 ); + while ( !parameters.empty() && result.parameters.size() < 4 ) { + const auto comma = parameters.find( ',' ); + std::string_view parameter = String::trim( parameters.substr( 0, comma ), " \t\n\r\f\v" ); + + double value = 0; + if ( String::fromString( value, parameter ) ) + result.parameters.emplace_back( value ); + if ( comma == std::string_view::npos ) + break; + parameters.remove_prefix( comma + 1 ); + } + return result; } }}} // namespace EE::UI::CSS diff --git a/src/eepp/ui/css/transitiondefinition.cpp b/src/eepp/ui/css/transitiondefinition.cpp index ff572b66b..64f3670f4 100644 --- a/src/eepp/ui/css/transitiondefinition.cpp +++ b/src/eepp/ui/css/transitiondefinition.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -6,122 +7,247 @@ namespace EE { namespace UI { namespace CSS { +namespace { + +template void forEachCommaItem( std::string_view value, Callback&& callback ) { + System::FunctionString::forEachParameter( value, [&]( std::string_view item, bool ) { + callback( item ); + return true; + } ); +} + +SmallVector splitTransitionTokens( std::string_view value ) { + SmallVector tokens; + std::size_t start = 0; + int parenthesisDepth = 0; + for ( std::size_t i = 0; i <= value.size(); ++i ) { + if ( i < value.size() ) { + if ( value[i] == '(' ) + ++parenthesisDepth; + else if ( value[i] == ')' && parenthesisDepth > 0 ) + --parenthesisDepth; + } + if ( i == value.size() || + ( std::isspace( static_cast( value[i] ) ) && parenthesisDepth == 0 ) ) { + if ( i > start ) + tokens.emplace_back( value.substr( start, i - start ) ); + while ( i + 1 < value.size() && + std::isspace( static_cast( value[i + 1] ) ) ) + ++i; + start = i + 1; + } + } + return tokens; +} + +Time parseTime( std::string_view value ) { + value = String::trim( value, " \t\n\r\f\v" ); + auto lower = []( char character ) { + return character >= 'A' && character <= 'Z' ? character + ( 'a' - 'A' ) : character; + }; + bool milliseconds = value.size() >= 2 && lower( value[value.size() - 2] ) == 'm' && + lower( value.back() ) == 's'; + bool seconds = !milliseconds && !value.empty() && lower( value.back() ) == 's'; + bool minutes = !milliseconds && !seconds && !value.empty() && lower( value.back() ) == 'm'; + std::size_t numberLength = value.size() - ( milliseconds ? 2 : ( seconds || minutes ? 1 : 0 ) ); + double number = 0; + const char* numberStart = value.data(); + if ( numberLength > 0 && *numberStart == '+' ) { + ++numberStart; + --numberLength; + } + if ( !String::fromString( number, std::string_view{ numberStart, numberLength } ) ) + return Time::Zero; + if ( milliseconds ) + return Milliseconds( number ); + if ( minutes ) + return Minutes( number ); + return Seconds( number ); +} + +String::HashType lowerHash( std::string_view value ) { + return String::hashToLower( value.data(), static_cast( value.size() ) ); +} + +std::string lowerString( std::string_view value ) { + std::string result{ value }; + String::toLowerInPlace( result ); + return result; +} + +} // namespace + UnorderedMap TransitionDefinition::parseTransitionProperties( const std::vector& styleSheetProperties ) { - std::vector properties; - std::vector