From 5f419ca58df5aa97310f5513793f157c51f7dedf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Sun, 9 Aug 2026 19:22:00 -0300 Subject: [PATCH] Reduce allocations in CSS parsing and style updates - reuse reentrant property-resolution storage during variable substitution - skip redundant resolution-slot copies using structural source comparison - retain variable cache capacity and resolve variables in place - avoid copying canonical names for definition-backed properties - propagate resolved inherited properties by reference through widget trees - parse animation and transition declarations with allocation-light string views - share declaration tokenization between animation and transition parsers - avoid empty animation-map and action-query allocations - parse border radii without temporary split vectors - accept string views when converting UI lengths - format color hex values directly without string streams - use inline storage for small polygon point collections - preserve animation parsing semantics and fix longhand direction handling - save eeiv configuration when closing through keyboard shortcuts - extend animation, variable-resolution, property-name, and color tests --- include/eepp/math/polygon2.hpp | 3 +- include/eepp/ui/css/animationdefinition.hpp | 6 + include/eepp/ui/css/stylesheetproperty.hpp | 15 +- include/eepp/ui/uinode.hpp | 17 +- include/eepp/ui/uistyle.hpp | 41 +++- include/eepp/ui/uiwidget.hpp | 1 + src/eepp/system/color.cpp | 23 +- src/eepp/ui/border.cpp | 17 +- src/eepp/ui/css/animationdefinition.cpp | 179 ++++++++------ src/eepp/ui/css/declarationparser.hpp | 81 +++++++ src/eepp/ui/css/stylesheetproperty.cpp | 99 ++++---- .../ui/css/stylesheetpropertyanimation.cpp | 2 +- src/eepp/ui/css/transitiondefinition.cpp | 77 +----- src/eepp/ui/uinode.cpp | 3 +- src/eepp/ui/uistyle.cpp | 220 +++++++++--------- src/eepp/ui/uiwidget.cpp | 40 ++-- .../unit_tests/action_transition_tests.cpp | 55 +++++ src/tests/unit_tests/color_parsing_tests.cpp | 7 + .../unit_tests/uicss_inheritance_tests.cpp | 83 +++++++ src/tools/eeiv/eeiv.cpp | 9 +- 20 files changed, 646 insertions(+), 332 deletions(-) create mode 100644 src/eepp/ui/css/declarationparser.hpp diff --git a/include/eepp/math/polygon2.hpp b/include/eepp/math/polygon2.hpp index dfd93b334..920f2663a 100644 --- a/include/eepp/math/polygon2.hpp +++ b/include/eepp/math/polygon2.hpp @@ -1,6 +1,7 @@ #ifndef EE_MATHPOLIGON2_H #define EE_MATHPOLIGON2_H +#include #include #include #include @@ -105,7 +106,7 @@ template class Polygon2 { Uint32 closestPoint( const Vector2& to, T* distance = NULL ); private: - std::vector> Vector; + SmallVector, 4> Vector; Vector2 Position; }; diff --git a/include/eepp/ui/css/animationdefinition.hpp b/include/eepp/ui/css/animationdefinition.hpp index 68aa745d3..18a89cfd1 100644 --- a/include/eepp/ui/css/animationdefinition.hpp +++ b/include/eepp/ui/css/animationdefinition.hpp @@ -100,6 +100,12 @@ class EE_API AnimationDefinition { AnimationDirection mDirection = Normal; AnimationFillMode mFillMode = None; bool mPaused = false; + + static bool isDirectionStringView( std::string_view str ); + static AnimationDirection directionFromStringView( std::string_view str ); + static bool isAnimationFillModeStringView( std::string_view str ); + static AnimationFillMode fillModeFromStringView( std::string_view str ); + void setNameView( std::string_view value ); }; inline bool operator==( const AnimationDefinition& a, const AnimationDefinition& b ) { diff --git a/include/eepp/ui/css/stylesheetproperty.hpp b/include/eepp/ui/css/stylesheetproperty.hpp index cd018ac52..01637a3ed 100644 --- a/include/eepp/ui/css/stylesheetproperty.hpp +++ b/include/eepp/ui/css/stylesheetproperty.hpp @@ -11,6 +11,7 @@ #include #include #include +#include #include using namespace EE::System; @@ -19,6 +20,7 @@ using namespace EE::Graphics; namespace EE { namespace UI { class UINode; +class UIStyle; }} // namespace EE::UI namespace EE { namespace UI { namespace CSS { @@ -29,9 +31,15 @@ class ShorthandDefinition; struct VariableFunctionCache { std::string definition; std::vector variableList; + + void clear() { variableList.clear(); } + + void addVariable( std::string_view variable ) { variableList.emplace_back( variable ); } }; class EE_API StyleSheetProperty { + friend class EE::UI::UIStyle; + public: StyleSheetProperty(); @@ -196,7 +204,7 @@ class EE_API StyleSheetProperty { const String::HashType& getValueHash() const; - const std::vector& getVarCache() const; + std::span getVarCache() const; StyleSheetProperty& setCachedProperty( bool cached ); @@ -205,6 +213,8 @@ class EE_API StyleSheetProperty { void setImportant( bool important ); protected: + bool hasSameResolutionSource( const StyleSheetProperty& property ) const; + std::string mName; String::HashType mNameHash{ 0 }; std::string mValue; @@ -220,6 +230,7 @@ class EE_API StyleSheetProperty { const ShorthandDefinition* mShorthandDefinition{ nullptr }; std::vector mIndexedProperty; std::vector mVarCache; + size_t mVarCacheSize{ 0 }; explicit StyleSheetProperty( bool isVolatile, const PropertyDefinition* definition, const std::string& value, const Int64& specificity = 0, @@ -229,7 +240,7 @@ class EE_API StyleSheetProperty { void checkImportant(); void createIndexed(); void checkVars(); - std::vector checkVars( const std::string& value ); + std::string& mutableValue() { return mValue; } }; typedef UnorderedMap StyleSheetProperties; diff --git a/include/eepp/ui/uinode.hpp b/include/eepp/ui/uinode.hpp index 855c1a061..ee3eef0a8 100644 --- a/include/eepp/ui/uinode.hpp +++ b/include/eepp/ui/uinode.hpp @@ -1151,6 +1151,13 @@ class EE_API UINode : public Node { Float convertLengthAsDp( const CSS::StyleSheetLength& length, const Float& containerLength ) const; + inline Float lengthFromValue( const std::string& value, + const CSS::PropertyRelativeTarget& relativeTarget, + const Float& defaultValue, const Uint32& propertyIndex ) const { + return lengthFromValue( std::string_view{ value }, relativeTarget, defaultValue, + propertyIndex ); + } + /** * @brief Evaluates a CSS length string to a pixel value. * @@ -1163,10 +1170,18 @@ class EE_API UINode : public Node { * @param propertyIndex The property index for multi-value properties (default: 0). * @return The computed length in pixels. */ - Float lengthFromValue( const std::string& value, + Float lengthFromValue( std::string_view value, const CSS::PropertyRelativeTarget& relativeTarget, const Float& defaultValue = 0, const Uint32& propertyIndex = 0 ) const; + inline Float lengthFromValue( const char* value, + const CSS::PropertyRelativeTarget& relativeTarget, + const Float& defaultValue = 0, + const Uint32& propertyIndex = 0 ) const { + return lengthFromValue( std::string_view{ value }, relativeTarget, defaultValue, + propertyIndex ); + } + /** * @brief Evaluates a CSS property to a pixel value. * diff --git a/include/eepp/ui/uistyle.hpp b/include/eepp/ui/uistyle.hpp index 58d3bd14c..c191a01db 100644 --- a/include/eepp/ui/uistyle.hpp +++ b/include/eepp/ui/uistyle.hpp @@ -10,7 +10,7 @@ #include #include #include -#include +#include namespace EE { namespace Graphics { class Font; @@ -96,6 +96,31 @@ class EE_API UIStyle : public UIState { void applyVarValues( CSS::StyleSheetProperty* style ); protected: + class PropertyResolution { + public: + PropertyResolution( const PropertyResolution& ) = delete; + PropertyResolution& operator=( const PropertyResolution& ) = delete; + PropertyResolution( PropertyResolution&& other ) noexcept; + PropertyResolution& operator=( PropertyResolution&& ) = delete; + ~PropertyResolution(); + + const CSS::StyleSheetProperty* get() const { return mProperty; } + + private: + friend class UIStyle; + static constexpr Uint32 NoSlot = static_cast( -1 ); + + PropertyResolution( UIStyle* owner, const CSS::StyleSheetProperty* property, + Uint32 slot = NoSlot ) : + mOwner( owner ), mProperty( property ), mSlot( slot ) {} + + void release(); + + UIStyle* mOwner; + const CSS::StyleSheetProperty* mProperty; + Uint32 mSlot; + }; + UIWidget* mWidget; std::shared_ptr mElementStyle; std::shared_ptr mGlobalDefinition; @@ -105,8 +130,12 @@ class EE_API UIStyle : public UIState { UnorderedSet mSubscribedWidgets; UnorderedSet mStructurallyVolatileChildren; Uint32 mStateDepthCounter{ 0 }; + Uint32 mPropertyResolutionDepth{ 0 }; Uint64 mLoadedVersion{ 0 }; const CSS::StyleSheet* mLoadedStyleSheet{ nullptr }; + /** Lazily allocated for styles that use substitutions. The pointed-to properties remain stable + * during nested resolutions and retain their string/vector capacities between state changes. */ + SmallVector, 1> mPropertyResolutionSlots; bool mChangingState; bool mForceReapplyProperties; bool mDisableAnimations; @@ -118,7 +147,9 @@ class EE_API UIStyle : public UIState { void applyLightDarkValue( std::string& newValue ); - void setVariableFromValue( CSS::StyleSheetProperty* property, const std::string& value ); + const CSS::StyleSheetVariable* getVariableRef( const std::string& variable ); + + void setVariableFromValue( CSS::StyleSheetProperty* property ); void updateState(); @@ -150,9 +181,9 @@ class EE_API UIStyle : public UIState { CSS::StyleSheetProperty* getLocalProperty( CSS::PropertyId propId ); - CSS::StyleSheetProperty* - getResolvedLocalProperty( CSS::PropertyId propId, - std::optional& resolvedProperty ); + PropertyResolution resolveProperty( const CSS::StyleSheetProperty* property ); + + PropertyResolution getResolvedLocalProperty( CSS::PropertyId propId ); void addStructurallyVolatileWidgetFromParent(); diff --git a/include/eepp/ui/uiwidget.hpp b/include/eepp/ui/uiwidget.hpp index 2772de314..49d0db5f8 100644 --- a/include/eepp/ui/uiwidget.hpp +++ b/include/eepp/ui/uiwidget.hpp @@ -1526,6 +1526,7 @@ class EE_API UIWidget : public UINode { void calculateAutoMargin(); void rebuildClassHashes(); + void propagateInheritedPropertyResolved( const CSS::StyleSheetProperty& property ); /** * @brief Default constructor. diff --git a/src/eepp/system/color.cpp b/src/eepp/system/color.cpp index c891319cd..65b4ade44 100644 --- a/src/eepp/system/color.cpp +++ b/src/eepp/system/color.cpp @@ -6,8 +6,6 @@ #include #include #include -#include -#include #include namespace EE { namespace System { @@ -611,14 +609,21 @@ Color Color::div( int divisor, bool divAlpha ) { } std::string Color::toHexString( const bool& prependHashtag ) const { - std::stringstream stream; + static constexpr char hex[] = "0123456789abcdef"; + const std::size_t offset = prependHashtag ? 1 : 0; + std::string result( offset + ( a == 255 ? 6 : 8 ), '0' ); if ( prependHashtag ) - stream << "#"; - stream << std::setfill( '0' ) << std::setw( sizeof( Color ) * 2 ) << std::hex << getValue(); - std::string str = stream.str(); - if ( this->a == 255 ) - return str.substr( 0, prependHashtag ? 7 : 6 ); - return str; + result[0] = '#'; + const auto writeByte = [&]( std::size_t position, Uint8 value ) { + result[offset + position] = hex[value >> 4]; + result[offset + position + 1] = hex[value & 0x0F]; + }; + writeByte( 0, r ); + writeByte( 2, g ); + writeByte( 4, b ); + if ( a != 255 ) + writeByte( 6, a ); + return result; } std::string Color::toRgbaString() const { diff --git a/src/eepp/ui/border.cpp b/src/eepp/ui/border.cpp index 91af3fed6..f950d2c84 100644 --- a/src/eepp/ui/border.cpp +++ b/src/eepp/ui/border.cpp @@ -145,11 +145,20 @@ BorderStyle Borders::toBorderStyle( const std::string& borderStyle ) { } Sizef Borders::radiusFromString( const UINode* node, const std::string& val ) { - auto split = String::split( val, ' ' ); + std::string_view values = String::trim( std::string_view{ val }, " \t\n\r\f\v" ); + const auto firstEnd = values.find_first_of( " \t\n\r\f\v" ); + const std::string_view first = values.substr( 0, firstEnd ); + std::string_view second = first; + if ( firstEnd != std::string_view::npos ) { + values.remove_prefix( firstEnd ); + values = String::trim( values, " \t\n\r\f\v" ); + const auto secondEnd = values.find_first_of( " \t\n\r\f\v" ); + if ( !values.empty() ) + second = values.substr( 0, secondEnd ); + } Sizef size; - size.x = node->lengthFromValue( split[0], CSS::PropertyRelativeTarget::LocalBlockRadiusWidth ); - size.y = node->lengthFromValue( split[split.size() > 1 ? 1 : 0], - CSS::PropertyRelativeTarget::LocalBlockRadiusHeight ); + size.x = node->lengthFromValue( first, CSS::PropertyRelativeTarget::LocalBlockRadiusWidth ); + size.y = node->lengthFromValue( second, CSS::PropertyRelativeTarget::LocalBlockRadiusHeight ); return size; } diff --git a/src/eepp/ui/css/animationdefinition.cpp b/src/eepp/ui/css/animationdefinition.cpp index 5320566ae..e096d1a24 100644 --- a/src/eepp/ui/css/animationdefinition.cpp +++ b/src/eepp/ui/css/animationdefinition.cpp @@ -1,26 +1,47 @@ -#include #include +#include #include #include namespace EE { namespace UI { namespace CSS { -inline bool isTimingFunction( const std::string& str ) { - return Ease::Interpolation::None != Ease::fromName( str, Ease::Interpolation::None ); +namespace { + +bool isTime( std::string_view value ) { + value = String::trim( value, " \t\n\r\f\v" ); + if ( value.empty() ) + return false; + auto lower = []( char character ) { + return character >= 'A' && character <= 'Z' ? character + ( 'a' - 'A' ) : character; + }; + std::size_t suffixSize = 0; + if ( value.size() >= 2 && lower( value[value.size() - 2] ) == 'm' && + lower( value.back() ) == 's' ) + suffixSize = 2; + else if ( lower( value.back() ) == 's' || lower( value.back() ) == 'm' ) + suffixSize = 1; + if ( 0 == suffixSize || value.size() == suffixSize ) + return false; + double number = 0; + std::string_view numberView = value.substr( 0, value.size() - suffixSize ); + if ( !numberView.empty() && numberView.front() == '+' ) + numberView.remove_prefix( 1 ); + return String::fromString( number, numberView ); } +} // namespace + UnorderedMap AnimationDefinition::parseAnimationProperties( const std::vector& stylesheetProperties ) { AnimationsMap animations; - std::vector names; - std::vector