mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-10-02 11:10:31 +03:00
Optimize single-rule CSS selector matching:
Add a fast path that directly evaluates selectors containing one rule, avoiding combinator traversal and switch overhead for common class, ID, tag, and pseudo-class selectors. Move complex selector traversal into a dedicated function and cache rule counts in selector loops. Add focused matching coverage and document the Phase 7 benchmark results.
This commit is contained in:
@@ -828,6 +828,19 @@ Use instrumentation from Phase 4 to confirm:
|
|||||||
|
|
||||||
# Phase 7: Add Single-Rule Selector Fast Path
|
# Phase 7: Add Single-Rule Selector Fast Path
|
||||||
|
|
||||||
|
**Status: Implemented**
|
||||||
|
|
||||||
|
## Implementation State
|
||||||
|
|
||||||
|
- `StyleSheetSelector` records whether parsing produced exactly one rule.
|
||||||
|
- Single-rule selectors call `StyleSheetSelectorRule::matches()` directly, while selectors with
|
||||||
|
combinators retain the existing traversal logic in `selectComplex()`.
|
||||||
|
- A focused test covers class, ID, tag+class, and pseudo-class selectors with pseudo matching both
|
||||||
|
enabled and disabled.
|
||||||
|
- Across seven release benchmark runs, `Benchmark.CSSSelectorMatching` had a 93.0 ms median for
|
||||||
|
20.48 million selector calls. Compared with the earlier Phase 5 median of approximately 111.4 ms,
|
||||||
|
this is an indicative 16.6% reduction.
|
||||||
|
|
||||||
## Motivation
|
## Motivation
|
||||||
|
|
||||||
Many selectors are simple:
|
Many selectors are simple:
|
||||||
|
|||||||
@@ -41,14 +41,17 @@ class EE_API StyleSheetSelector {
|
|||||||
std::string mName;
|
std::string mName;
|
||||||
Int64 mSpecificity;
|
Int64 mSpecificity;
|
||||||
std::vector<StyleSheetSelectorRule> mSelectorRules;
|
std::vector<StyleSheetSelectorRule> mSelectorRules;
|
||||||
bool mCacheable;
|
bool mCacheable{ true };
|
||||||
bool mStructurallyVolatile;
|
bool mStructurallyVolatile{ false };
|
||||||
|
bool mIsSingleRule{ false };
|
||||||
|
|
||||||
void addSelectorRule( std::string& buffer,
|
void addSelectorRule( std::string& buffer,
|
||||||
StyleSheetSelectorRule::PatternMatch& curPatternMatch,
|
StyleSheetSelectorRule::PatternMatch& curPatternMatch,
|
||||||
const StyleSheetSelectorRule::PatternMatch& newPatternMatch );
|
const StyleSheetSelectorRule::PatternMatch& newPatternMatch );
|
||||||
|
|
||||||
void parseSelector( std::string selector );
|
void parseSelector( std::string selector );
|
||||||
|
|
||||||
|
bool selectComplex( UIWidget* element, const bool& applyPseudo ) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
}}} // namespace EE::UI::CSS
|
}}} // namespace EE::UI::CSS
|
||||||
|
|||||||
@@ -3,12 +3,12 @@
|
|||||||
|
|
||||||
namespace EE { namespace UI { namespace CSS {
|
namespace EE { namespace UI { namespace CSS {
|
||||||
|
|
||||||
StyleSheetSelector::StyleSheetSelector() : mName( "*" ), mSpecificity( 0 ), mCacheable( true ) {
|
StyleSheetSelector::StyleSheetSelector() : mName( "*" ), mSpecificity( 0 ) {
|
||||||
parseSelector( mName );
|
parseSelector( mName );
|
||||||
}
|
}
|
||||||
|
|
||||||
StyleSheetSelector::StyleSheetSelector( const std::string& selectorName ) :
|
StyleSheetSelector::StyleSheetSelector( const std::string& selectorName ) :
|
||||||
mName( selectorName ), mSpecificity( 0 ), mCacheable( true ), mStructurallyVolatile( false ) {
|
mName( selectorName ), mSpecificity( 0 ) {
|
||||||
parseSelector( mName );
|
parseSelector( mName );
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,6 +136,8 @@ void StyleSheetSelector::parseSelector( std::string selector ) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mIsSingleRule = mSelectorRules.size() == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool StyleSheetSelector::isCacheable() const {
|
bool StyleSheetSelector::isCacheable() const {
|
||||||
@@ -150,9 +152,17 @@ bool StyleSheetSelector::select( UIWidget* element, const bool& applyPseudo ) co
|
|||||||
if ( mSelectorRules.empty() )
|
if ( mSelectorRules.empty() )
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
UIWidget* curElement = element;
|
if ( mIsSingleRule )
|
||||||
|
return mSelectorRules[0].matches( element, applyPseudo );
|
||||||
|
|
||||||
for ( size_t i = 0; i < mSelectorRules.size(); i++ ) {
|
return selectComplex( element, applyPseudo );
|
||||||
|
}
|
||||||
|
|
||||||
|
bool StyleSheetSelector::selectComplex( UIWidget* element, const bool& applyPseudo ) const {
|
||||||
|
UIWidget* curElement = element;
|
||||||
|
const size_t ruleCount = mSelectorRules.size();
|
||||||
|
|
||||||
|
for ( size_t i = 0; i < ruleCount; i++ ) {
|
||||||
const StyleSheetSelectorRule& selectorRule = mSelectorRules[i];
|
const StyleSheetSelectorRule& selectorRule = mSelectorRules[i];
|
||||||
|
|
||||||
switch ( selectorRule.getPatternMatch() ) {
|
switch ( selectorRule.getPatternMatch() ) {
|
||||||
@@ -235,8 +245,9 @@ std::vector<UIWidget*> StyleSheetSelector::getRelatedElements( UIWidget* element
|
|||||||
return elements;
|
return elements;
|
||||||
|
|
||||||
UIWidget* curElement = element;
|
UIWidget* curElement = element;
|
||||||
|
const size_t ruleCount = mSelectorRules.size();
|
||||||
|
|
||||||
for ( size_t i = 0; i < mSelectorRules.size(); i++ ) {
|
for ( size_t i = 0; i < ruleCount; i++ ) {
|
||||||
const StyleSheetSelectorRule& selectorRule = mSelectorRules[i];
|
const StyleSheetSelectorRule& selectorRule = mSelectorRules[i];
|
||||||
|
|
||||||
switch ( selectorRule.getPatternMatch() ) {
|
switch ( selectorRule.getPatternMatch() ) {
|
||||||
|
|||||||
@@ -2914,6 +2914,27 @@ UTEST( UIHTML, HashedSelectorMatchingAndClassMutation ) {
|
|||||||
Engine::destroySingleton();
|
Engine::destroySingleton();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UTEST( UIHTML, SingleRuleSelectorMatching ) {
|
||||||
|
Engine::instance()->createWindow( WindowSettings( 1024, 768, "Single Rule Selector Test",
|
||||||
|
WindowStyle::Default, WindowBackend::Default,
|
||||||
|
32, {}, 1, false, true ),
|
||||||
|
ContextSettings( false, 0, 0, GLv_default, true, false ) );
|
||||||
|
|
||||||
|
UISceneNode* sceneNode = init_test_inline_block();
|
||||||
|
sceneNode->loadLayoutFromString( HTMLFormatter::HTMLtoXML(
|
||||||
|
R"html(<html><body><div id="target" class="foo bar"></div></body></html>)html" ) );
|
||||||
|
auto* target = sceneNode->getRoot()->find( "target" )->asType<UIWidget>();
|
||||||
|
ASSERT_TRUE( target != nullptr );
|
||||||
|
|
||||||
|
EXPECT_TRUE( StyleSheetSelector( ".foo" ).select( target, false ) );
|
||||||
|
EXPECT_TRUE( StyleSheetSelector( "#target" ).select( target, false ) );
|
||||||
|
EXPECT_TRUE( StyleSheetSelector( "div.foo" ).select( target, false ) );
|
||||||
|
EXPECT_TRUE( StyleSheetSelector( ".foo:hover" ).select( target, false ) );
|
||||||
|
EXPECT_FALSE( StyleSheetSelector( ".foo:hover" ).select( target, true ) );
|
||||||
|
|
||||||
|
Engine::destroySingleton();
|
||||||
|
}
|
||||||
|
|
||||||
UTEST( UIHTML, ClassIndexedStyleSheetCandidates ) {
|
UTEST( UIHTML, ClassIndexedStyleSheetCandidates ) {
|
||||||
Engine::instance()->createWindow( WindowSettings( 1024, 768, "Class Index Test",
|
Engine::instance()->createWindow( WindowSettings( 1024, 768, "Class Index Test",
|
||||||
WindowStyle::Default, WindowBackend::Default,
|
WindowStyle::Default, WindowBackend::Default,
|
||||||
|
|||||||
Reference in New Issue
Block a user