Browse Source

Merge branch 'fast_styles'

Michael Ragazzon 3 years ago
parent
commit
2242e34bb7

+ 1 - 1
CMake/FileList.cmake

@@ -111,7 +111,6 @@ set(Core_HDR_FILES
     ${PROJECT_SOURCE_DIR}/Source/Core/TextureResource.h
     ${PROJECT_SOURCE_DIR}/Source/Core/TextureResource.h
     ${PROJECT_SOURCE_DIR}/Source/Core/TransformState.h
     ${PROJECT_SOURCE_DIR}/Source/Core/TransformState.h
     ${PROJECT_SOURCE_DIR}/Source/Core/TransformUtilities.h
     ${PROJECT_SOURCE_DIR}/Source/Core/TransformUtilities.h
-    ${PROJECT_SOURCE_DIR}/Source/Core/Utilities.h
     ${PROJECT_SOURCE_DIR}/Source/Core/WidgetScroll.h
     ${PROJECT_SOURCE_DIR}/Source/Core/WidgetScroll.h
     ${PROJECT_SOURCE_DIR}/Source/Core/XMLNodeHandlerBody.h
     ${PROJECT_SOURCE_DIR}/Source/Core/XMLNodeHandlerBody.h
     ${PROJECT_SOURCE_DIR}/Source/Core/XMLNodeHandlerDefault.h
     ${PROJECT_SOURCE_DIR}/Source/Core/XMLNodeHandlerDefault.h
@@ -221,6 +220,7 @@ set(Core_PUB_HDR_FILES
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/TypeConverter.inl
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/TypeConverter.inl
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Types.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Types.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/URL.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/URL.h
+    ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Utilities.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Variant.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Variant.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Variant.inl
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Variant.inl
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Vector2.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Vector2.h

+ 6 - 14
Include/RmlUi/Core/StyleSheet.h

@@ -29,10 +29,10 @@
 #ifndef RMLUI_CORE_STYLESHEET_H
 #ifndef RMLUI_CORE_STYLESHEET_H
 #define RMLUI_CORE_STYLESHEET_H
 #define RMLUI_CORE_STYLESHEET_H
 
 
-#include "Traits.h"
 #include "PropertyDictionary.h"
 #include "PropertyDictionary.h"
 #include "Spritesheet.h"
 #include "Spritesheet.h"
 #include "StyleSheetTypes.h"
 #include "StyleSheetTypes.h"
+#include "Traits.h"
 
 
 namespace Rml {
 namespace Rml {
 
 
@@ -40,14 +40,11 @@ class Element;
 class ElementDefinition;
 class ElementDefinition;
 class StyleSheetNode;
 class StyleSheetNode;
 class Decorator;
 class Decorator;
-class FontEffect;
 class SpritesheetList;
 class SpritesheetList;
-class Stream;
 class StyleSheetContainer;
 class StyleSheetContainer;
 class StyleSheetParser;
 class StyleSheetParser;
 struct PropertySource;
 struct PropertySource;
 struct Sprite;
 struct Sprite;
-struct Spritesheet;
 
 
 /**
 /**
 	StyleSheet maintains a single stylesheet definition. A stylesheet can be combined with another stylesheet to create
 	StyleSheet maintains a single stylesheet definition. A stylesheet can be combined with another stylesheet to create
@@ -61,9 +58,6 @@ class RMLUICORE_API StyleSheet final : public NonCopyMoveable
 public:
 public:
 	~StyleSheet();
 	~StyleSheet();
 
 
-	using NodeList = Vector< const StyleSheetNode* >;
-	using NodeIndex = UnorderedMap< size_t, NodeList >;
-
 	/// Combines this style sheet with another one, producing a new sheet.
 	/// Combines this style sheet with another one, producing a new sheet.
 	UniquePtr<StyleSheet> CombineStyleSheet(const StyleSheet& sheet) const;
 	UniquePtr<StyleSheet> CombineStyleSheet(const StyleSheet& sheet) const;
 	/// Merges another style sheet into this.
 	/// Merges another style sheet into this.
@@ -81,10 +75,7 @@ public:
 	const Sprite* GetSprite(const String& name) const;
 	const Sprite* GetSprite(const String& name) const;
 
 
 	/// Returns the compiled element definition for a given element and its hierarchy.
 	/// Returns the compiled element definition for a given element and its hierarchy.
-	SharedPtr<ElementDefinition> GetElementDefinition(const Element* element) const;
-
-	/// Retrieve the hash key used to look-up applicable nodes in the node index.
-	static size_t NodeHash(const String& tag, const String& id);
+	SharedPtr<const ElementDefinition> GetElementDefinition(const Element* element) const;
 
 
 	/// Returns a list of instanced decorators from the declarations. The instances are cached for faster future retrieval.
 	/// Returns a list of instanced decorators from the declarations. The instances are cached for faster future retrieval.
 	const Vector<SharedPtr<const Decorator>>& InstanceDecorators(const DecoratorDeclarationList& declaration_list, const PropertySource* decorator_source) const;
 	const Vector<SharedPtr<const Decorator>>& InstanceDecorators(const DecoratorDeclarationList& declaration_list, const PropertySource* decorator_source) const;
@@ -112,14 +103,14 @@ private:
 	SpritesheetList spritesheet_list;
 	SpritesheetList spritesheet_list;
 
 
 	// Map of all styled nodes, that is, they have one or more properties.
 	// Map of all styled nodes, that is, they have one or more properties.
-	NodeIndex styled_node_index;
+	StyleSheetIndex styled_node_index;
 
 
 	// Index of node sets to element definitions.
 	// Index of node sets to element definitions.
-	using ElementDefinitionCache = UnorderedMap< size_t, SharedPtr<ElementDefinition> >;
+	using ElementDefinitionCache = UnorderedMap<StyleSheetIndex::NodeList, SharedPtr<const ElementDefinition>>;
 	mutable ElementDefinitionCache node_cache;
 	mutable ElementDefinitionCache node_cache;
 
 
 	// Cached decorator instances.
 	// Cached decorator instances.
-	using DecoratorCache = UnorderedMap< String, Vector<SharedPtr<const Decorator>> >;
+	using DecoratorCache = UnorderedMap<String, Vector<SharedPtr<const Decorator>>>;
 	mutable DecoratorCache decorator_cache;
 	mutable DecoratorCache decorator_cache;
 
 
 	friend Rml::StyleSheetParser;
 	friend Rml::StyleSheetParser;
@@ -127,4 +118,5 @@ private:
 };
 };
 
 
 } // namespace Rml
 } // namespace Rml
+
 #endif
 #endif

+ 34 - 4
Include/RmlUi/Core/StyleSheetTypes.h

@@ -15,7 +15,7 @@
  *
  *
  * The above copyright notice and this permission notice shall be included in
  * The above copyright notice and this permission notice shall be included in
  * all copies or substantial portions of the Software.
  * all copies or substantial portions of the Software.
- * 
+ *
  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -29,18 +29,20 @@
 #ifndef RMLUI_CORE_STYLESHEETTYPES_H
 #ifndef RMLUI_CORE_STYLESHEETTYPES_H
 #define RMLUI_CORE_STYLESHEETTYPES_H
 #define RMLUI_CORE_STYLESHEETTYPES_H
 
 
-#include "Types.h"
 #include "PropertyDictionary.h"
 #include "PropertyDictionary.h"
+#include "Types.h"
+#include "Utilities.h"
 
 
 namespace Rml {
 namespace Rml {
 
 
 class Decorator;
 class Decorator;
 class DecoratorInstancer;
 class DecoratorInstancer;
 class StyleSheet;
 class StyleSheet;
+class StyleSheetNode;
 
 
 struct KeyframeBlock {
 struct KeyframeBlock {
 	KeyframeBlock(float normalized_time) : normalized_time(normalized_time) {}
 	KeyframeBlock(float normalized_time) : normalized_time(normalized_time) {}
-	float normalized_time;  // [0, 1]
+	float normalized_time; // [0, 1]
 	PropertyDictionary properties;
 	PropertyDictionary properties;
 };
 };
 struct Keyframes {
 struct Keyframes {
@@ -68,12 +70,40 @@ struct DecoratorDeclarationList {
 
 
 struct MediaBlock {
 struct MediaBlock {
 	MediaBlock() {}
 	MediaBlock() {}
-	MediaBlock(PropertyDictionary _properties, SharedPtr<StyleSheet> _stylesheet) : properties(std::move(_properties)), stylesheet(std::move(_stylesheet)) {}
+	MediaBlock(PropertyDictionary _properties, SharedPtr<StyleSheet> _stylesheet) :
+		properties(std::move(_properties)), stylesheet(std::move(_stylesheet))
+	{}
 
 
 	PropertyDictionary properties; // Media query properties
 	PropertyDictionary properties; // Media query properties
 	SharedPtr<StyleSheet> stylesheet;
 	SharedPtr<StyleSheet> stylesheet;
 };
 };
 using MediaBlockList = Vector<MediaBlock>;
 using MediaBlockList = Vector<MediaBlock>;
 
 
+/**
+   StyleSheetIndex contains a cached index of all styled nodes for quick lookup when finding applicable style nodes for the current state of a given
+   element.
+ */
+struct StyleSheetIndex {
+	using NodeList = Vector<const StyleSheetNode*>;
+	using NodeIndex = UnorderedMap<std::size_t, NodeList>;
+
+	// The following objects are given in prioritized order. Any nodes in the first object will not be contained in the next one and so on.
+	NodeIndex ids, classes, tags;
+	NodeList other;
+};
 } // namespace Rml
 } // namespace Rml
+
+namespace std {
+// Hash specialization for the node list, so it can be used as key in UnorderedMap.
+template <>
+struct hash<::Rml::StyleSheetIndex::NodeList> {
+	std::size_t operator()(const ::Rml::StyleSheetIndex::NodeList& nodes) const
+	{
+		std::size_t seed = 0;
+		for (const ::Rml::StyleSheetNode* node : nodes)
+			::Rml::Utilities::HashCombine(seed, node);
+		return seed;
+	}
+};
+} // namespace std
 #endif
 #endif

+ 2 - 0
Source/Core/Utilities.h → Include/RmlUi/Core/Utilities.h

@@ -29,6 +29,8 @@
 #ifndef RMLUI_CORE_UTILITIES_H
 #ifndef RMLUI_CORE_UTILITIES_H
 #define RMLUI_CORE_UTILITIES_H
 #define RMLUI_CORE_UTILITIES_H
 
 
+#include "Types.h"
+
 namespace Rml {
 namespace Rml {
 
 
 namespace Utilities {
 namespace Utilities {

+ 4 - 3
Source/Core/Element.cpp

@@ -40,6 +40,7 @@
 #include "../../Include/RmlUi/Core/PropertyIdSet.h"
 #include "../../Include/RmlUi/Core/PropertyIdSet.h"
 #include "../../Include/RmlUi/Core/PropertiesIteratorView.h"
 #include "../../Include/RmlUi/Core/PropertiesIteratorView.h"
 #include "../../Include/RmlUi/Core/PropertyDefinition.h"
 #include "../../Include/RmlUi/Core/PropertyDefinition.h"
+#include "../../Include/RmlUi/Core/StyleSheet.h"
 #include "../../Include/RmlUi/Core/StyleSheetSpecification.h"
 #include "../../Include/RmlUi/Core/StyleSheetSpecification.h"
 #include "../../Include/RmlUi/Core/TransformPrimitive.h"
 #include "../../Include/RmlUi/Core/TransformPrimitive.h"
 #include "Clock.h"
 #include "Clock.h"
@@ -1048,7 +1049,7 @@ Element* Element::Closest(const String& selectors) const
 	{
 	{
 		for (const StyleSheetNode* node : leaf_nodes)
 		for (const StyleSheetNode* node : leaf_nodes)
 		{
 		{
-			if (node->IsApplicable(parent, false))
+			if (node->IsApplicable(parent))
 			{
 			{
 				return parent;
 				return parent;
 			}
 			}
@@ -1512,7 +1513,7 @@ static Element* QuerySelectorMatchRecursive(const StyleSheetNodeListRaw& nodes,
 
 
 		for (const StyleSheetNode* node : nodes)
 		for (const StyleSheetNode* node : nodes)
 		{
 		{
-			if (node->IsApplicable(child, false))
+			if (node->IsApplicable(child))
 				return child;
 				return child;
 		}
 		}
 
 
@@ -1534,7 +1535,7 @@ static void QuerySelectorAllMatchRecursive(ElementList& matching_elements, const
 
 
 		for (const StyleSheetNode* node : nodes)
 		for (const StyleSheetNode* node : nodes)
 		{
 		{
-			if (node->IsApplicable(child, false))
+			if (node->IsApplicable(child))
 			{
 			{
 				matching_elements.push_back(child);
 				matching_elements.push_back(child);
 				break;
 				break;

+ 6 - 1
Source/Core/ElementStyle.cpp

@@ -177,7 +177,7 @@ void ElementStyle::UpdateDefinition()
 
 
 		definition_dirty = false;
 		definition_dirty = false;
 
 
-		SharedPtr<ElementDefinition> new_definition;
+		SharedPtr<const ElementDefinition> new_definition;
 		
 		
 		if (const StyleSheet* style_sheet = element->GetStyleSheet())
 		if (const StyleSheet* style_sheet = element->GetStyleSheet())
 		{
 		{
@@ -319,6 +319,11 @@ String ElementStyle::GetClassNames() const
 	return class_names;
 	return class_names;
 }
 }
 
 
+const StringList& ElementStyle::GetClassNameList() const
+{
+	return classes;
+}
+
 // Sets a local property override on the element to a pre-parsed value.
 // Sets a local property override on the element to a pre-parsed value.
 bool ElementStyle::SetProperty(PropertyId id, const Property& property)
 bool ElementStyle::SetProperty(PropertyId id, const Property& property)
 {
 {

+ 3 - 1
Source/Core/ElementStyle.h

@@ -87,6 +87,8 @@ public:
 	/// Return the active class list.
 	/// Return the active class list.
 	/// @return A string containing all the classes on the element, separated by spaces.
 	/// @return A string containing all the classes on the element, separated by spaces.
 	String GetClassNames() const;
 	String GetClassNames() const;
+	/// Return the active class list.
+	const StringList& GetClassNameList() const;
 
 
 	/// Sets a local property override on the element to a pre-parsed value.
 	/// Sets a local property override on the element to a pre-parsed value.
 	/// @param[in] name The name of the new property.
 	/// @param[in] name The name of the new property.
@@ -165,7 +167,7 @@ private:
 	// Any properties that have been overridden in this element.
 	// Any properties that have been overridden in this element.
 	PropertyDictionary inline_properties;
 	PropertyDictionary inline_properties;
 	// The definition of this element, provides applicable properties from the stylesheet.
 	// The definition of this element, provides applicable properties from the stylesheet.
-	SharedPtr<ElementDefinition> definition;
+	SharedPtr<const ElementDefinition> definition;
 	// Set if a new element definition should be fetched from the style.
 	// Set if a new element definition should be fetched from the style.
 	bool definition_dirty;
 	bool definition_dirty;
 
 

+ 1 - 1
Source/Core/PropertyParserFontEffect.cpp

@@ -32,7 +32,7 @@
 #include "../../Include/RmlUi/Core/FontEffectInstancer.h"
 #include "../../Include/RmlUi/Core/FontEffectInstancer.h"
 #include "../../Include/RmlUi/Core/PropertySpecification.h"
 #include "../../Include/RmlUi/Core/PropertySpecification.h"
 #include "../../Include/RmlUi/Core/Profiling.h"
 #include "../../Include/RmlUi/Core/Profiling.h"
-#include "Utilities.h"
+#include "../../Include/RmlUi/Core/Utilities.h"
 #include <algorithm>
 #include <algorithm>
 
 
 namespace Rml {
 namespace Rml {

+ 48 - 68
Source/Core/StyleSheet.cpp

@@ -27,24 +27,18 @@
  */
  */
 
 
 #include "../../Include/RmlUi/Core/StyleSheet.h"
 #include "../../Include/RmlUi/Core/StyleSheet.h"
-#include "ElementDefinition.h"
-#include "StyleSheetNode.h"
-#include "Utilities.h"
 #include "../../Include/RmlUi/Core/DecoratorInstancer.h"
 #include "../../Include/RmlUi/Core/DecoratorInstancer.h"
 #include "../../Include/RmlUi/Core/Element.h"
 #include "../../Include/RmlUi/Core/Element.h"
 #include "../../Include/RmlUi/Core/Profiling.h"
 #include "../../Include/RmlUi/Core/Profiling.h"
 #include "../../Include/RmlUi/Core/PropertyDefinition.h"
 #include "../../Include/RmlUi/Core/PropertyDefinition.h"
 #include "../../Include/RmlUi/Core/StyleSheetSpecification.h"
 #include "../../Include/RmlUi/Core/StyleSheetSpecification.h"
+#include "ElementDefinition.h"
+#include "ElementStyle.h"
+#include "StyleSheetNode.h"
 #include <algorithm>
 #include <algorithm>
 
 
 namespace Rml {
 namespace Rml {
 
 
-// Sorts style nodes based on specificity.
-inline static bool StyleSheetNodeSort(const StyleSheetNode* lhs, const StyleSheetNode* rhs)
-{
-	return lhs->GetSpecificity() < rhs->GetSpecificity();
-}
-
 StyleSheet::StyleSheet()
 StyleSheet::StyleSheet()
 {
 {
 	root = MakeUnique<StyleSheetNode>();
 	root = MakeUnique<StyleSheetNode>();
@@ -105,7 +99,7 @@ void StyleSheet::MergeStyleSheet(const StyleSheet& other_sheet)
 void StyleSheet::BuildNodeIndex()
 void StyleSheet::BuildNodeIndex()
 {
 {
 	RMLUI_ZoneScoped;
 	RMLUI_ZoneScoped;
-	styled_node_index.clear();
+	styled_node_index = {};
 	root->BuildIndex(styled_node_index);
 	root->BuildIndex(styled_node_index);
 	root->SetStructurallyVolatileRecursive(false);
 	root->SetStructurallyVolatileRecursive(false);
 }
 }
@@ -170,89 +164,75 @@ const Sprite* StyleSheet::GetSprite(const String& name) const
 	return spritesheet_list.GetSprite(name);
 	return spritesheet_list.GetSprite(name);
 }
 }
 
 
-size_t StyleSheet::NodeHash(const String& tag, const String& id)
-{
-	size_t seed = 0;
-	if (!tag.empty())
-		seed = Hash<String>()(tag);
-	if(!id.empty())
-		Utilities::HashCombine(seed, id);
-	return seed;
-}
-
 // Returns the compiled element definition for a given element hierarchy.
 // Returns the compiled element definition for a given element hierarchy.
-SharedPtr<ElementDefinition> StyleSheet::GetElementDefinition(const Element* element) const
+SharedPtr<const ElementDefinition> StyleSheet::GetElementDefinition(const Element* element) const
 {
 {
 	RMLUI_ASSERT_NONRECURSIVE;
 	RMLUI_ASSERT_NONRECURSIVE;
 
 
-	// See if there are any styles defined for this element.
 	// Using static to avoid allocations. Make sure we don't call this function recursively.
 	// Using static to avoid allocations. Make sure we don't call this function recursively.
 	static Vector< const StyleSheetNode* > applicable_nodes;
 	static Vector< const StyleSheetNode* > applicable_nodes;
 	applicable_nodes.clear();
 	applicable_nodes.clear();
 
 
-	const String& tag = element->GetTagName();
-	const String& id = element->GetId();
-
-	// The styled_node_index is hashed with the tag and id of the RCSS rule. However, we must also check
-	// the rules which don't have them defined, because they apply regardless of tag and id.
-	Array<size_t, 4> node_hash;
-	int num_hashes = 2;
-
-	node_hash[0] = 0;
-	node_hash[1] = NodeHash(tag, String());
-
-	// If we don't have an id, we can safely skip nodes that define an id. Otherwise, we also check the id nodes.
-	if (!id.empty())
-	{
-		num_hashes = 4;
-		node_hash[2] = NodeHash(String(), id);
-		node_hash[3] = NodeHash(tag, id);
-	}
-
-	// The hashes are keys into a set of applicable nodes (given tag and id).
-	for (int i = 0; i < num_hashes; i++)
-	{
-		auto it_nodes = styled_node_index.find(node_hash[i]);
-		if (it_nodes != styled_node_index.end())
+	auto AddApplicableNodes = [element](const StyleSheetIndex::NodeIndex& node_index, const String& key) {
+		auto it_nodes = node_index.find(Hash<String>()(key));
+		if (it_nodes != node_index.end())
 		{
 		{
-			const NodeList& nodes = it_nodes->second;
+			const StyleSheetIndex::NodeList& nodes = it_nodes->second;
 
 
-			// Now see if we satisfy all of the requirements not yet tested: classes, pseudo classes, structural selectors, 
-			// and the full requirements of parent nodes. What this involves is traversing the style nodes backwards, 
-			// trying to match nodes in the element's hierarchy to nodes in the style hierarchy.
 			for (const StyleSheetNode* node : nodes)
 			for (const StyleSheetNode* node : nodes)
 			{
 			{
-				if (node->IsApplicable(element, true))
-				{
+				// We found a node that has at least one requirement matching the element. Now see if we satisfy the remaining requirements of the
+				// node, including all ancestor nodes. What this involves is traversing the style nodes backwards, trying to match nodes in the
+				// element's hierarchy to nodes in the style hierarchy.
+				if (node->IsApplicable(element))
 					applicable_nodes.push_back(node);
 					applicable_nodes.push_back(node);
-				}
 			}
 			}
 		}
 		}
-	}
+	};
 
 
-	std::sort(applicable_nodes.begin(), applicable_nodes.end(), StyleSheetNodeSort);
+	// See if there are any styles defined for this element.
+	const String& tag = element->GetTagName();
+	const String& id = element->GetId();
+	const StringList& class_names = element->GetStyle()->GetClassNameList();
+
+	// First, look up the indexed requirements. 
+	if (!id.empty())
+		AddApplicableNodes(styled_node_index.ids, id);
+
+	for (const String& name : class_names)
+		AddApplicableNodes(styled_node_index.classes, name);
+
+	AddApplicableNodes(styled_node_index.tags, tag);
+
+	// Also check all remaining nodes that don't contain any indexed requirements.
+	for (const StyleSheetNode* node : styled_node_index.other)
+	{
+		if (node->IsApplicable(element))
+			applicable_nodes.push_back(node);
+	}
 
 
 	// If this element definition won't actually store any information, don't bother with it.
 	// If this element definition won't actually store any information, don't bother with it.
 	if (applicable_nodes.empty())
 	if (applicable_nodes.empty())
 		return nullptr;
 		return nullptr;
 
 
-	// Check if this puppy has already been cached in the node index.
-	size_t seed = 0;
-	for (const StyleSheetNode* node : applicable_nodes)
-		Utilities::HashCombine(seed, node);
+	// Sort the applicable nodes by specificity first, then by pointer value in case we have duplicate specificities.
+	std::sort(applicable_nodes.begin(), applicable_nodes.end(), [](const StyleSheetNode* a, const StyleSheetNode* b) {
+		const int a_specificity = a->GetSpecificity();
+		const int b_specificity = b->GetSpecificity();
+		if (a_specificity == b_specificity)
+			return a < b;
+		return a_specificity < b_specificity;
+	});
 
 
-	auto cache_iterator = node_cache.find(seed);
-	if (cache_iterator != node_cache.end())
+	// Check if this puppy has already been cached in the node index.
+	SharedPtr<const ElementDefinition>& definition = node_cache[applicable_nodes];
+	if (!definition)
 	{
 	{
-		SharedPtr<ElementDefinition>& definition = (*cache_iterator).second;
-		return definition;
+		// Otherwise, create a new definition and add it to our cache.
+		definition = MakeShared<const ElementDefinition>(applicable_nodes);
 	}
 	}
 
 
-	// Create the new definition and add it to our cache.
-	auto new_definition = MakeShared<ElementDefinition>(applicable_nodes);
-	node_cache[seed] = new_definition;
-
-	return new_definition;
+	return definition;
 }
 }
 
 
 } // namespace Rml
 } // namespace Rml

+ 1 - 1
Source/Core/StyleSheetContainer.cpp

@@ -31,9 +31,9 @@
 #include "../../Include/RmlUi/Core/PropertyDictionary.h"
 #include "../../Include/RmlUi/Core/PropertyDictionary.h"
 #include "../../Include/RmlUi/Core/Profiling.h"
 #include "../../Include/RmlUi/Core/Profiling.h"
 #include "../../Include/RmlUi/Core/StyleSheet.h"
 #include "../../Include/RmlUi/Core/StyleSheet.h"
+#include "../../Include/RmlUi/Core/Utilities.h"
 #include "ComputeProperty.h"
 #include "ComputeProperty.h"
 #include "StyleSheetParser.h"
 #include "StyleSheetParser.h"
-#include "Utilities.h"
 
 
 namespace Rml {
 namespace Rml {
 
 

+ 42 - 17
Source/Core/StyleSheetNode.cpp

@@ -29,6 +29,7 @@
 #include "StyleSheetNode.h"
 #include "StyleSheetNode.h"
 #include "../../Include/RmlUi/Core/Element.h"
 #include "../../Include/RmlUi/Core/Element.h"
 #include "../../Include/RmlUi/Core/Profiling.h"
 #include "../../Include/RmlUi/Core/Profiling.h"
+#include "../../Include/RmlUi/Core/StyleSheet.h"
 #include "StyleSheetFactory.h"
 #include "StyleSheetFactory.h"
 #include "StyleSheetNodeSelector.h"
 #include "StyleSheetNodeSelector.h"
 #include <algorithm>
 #include <algorithm>
@@ -121,23 +122,42 @@ UniquePtr<StyleSheetNode> StyleSheetNode::DeepCopy(StyleSheetNode* in_parent) co
 }
 }
 
 
 // Builds up a style sheet's index recursively.
 // Builds up a style sheet's index recursively.
-void StyleSheetNode::BuildIndex(StyleSheet::NodeIndex& styled_node_index) const
+void StyleSheetNode::BuildIndex(StyleSheetIndex& styled_node_index) const
 {
 {
 	// If this has properties defined, then we insert it into the styled node index.
 	// If this has properties defined, then we insert it into the styled node index.
-	if(properties.GetNumProperties() > 0)
+	if (properties.GetNumProperties() > 0)
 	{
 	{
-		// The keys of the node index is a hashed combination of tag and id. These are used for fast lookup of applicable nodes.
-		size_t node_hash = StyleSheet::NodeHash(tag, id);
-		StyleSheet::NodeList& nodes = styled_node_index[node_hash];
-		auto it = std::find(nodes.begin(), nodes.end(), this);
-		if(it == nodes.end())
-			nodes.push_back(this);
+		auto IndexInsertNode = [](StyleSheetIndex::NodeIndex& node_index, const String& key, const StyleSheetNode* node) {
+			StyleSheetIndex::NodeList& nodes = node_index[Hash<String>()(key)];
+			auto it = std::find(nodes.begin(), nodes.end(), node);
+			if (it == nodes.end())
+				nodes.push_back(node);
+		};
+
+		// Add this node to the appropriate index for looking up applicable nodes later. Prioritize the most unique requirement first and the most
+		// general requirement last. This way we are able to rule out as many nodes as possible as quickly as possible.
+		if (!id.empty())
+		{
+			IndexInsertNode(styled_node_index.ids, id, this);
+		}
+		else if (!class_names.empty())
+		{
+			// @performance Right now we just use the first class for simplicity. Later we may want to devise a better strategy to try to add the
+			// class with the most unique name. For example by adding the class from this node's list that has the fewest existing matches.
+			IndexInsertNode(styled_node_index.classes, class_names.front(), this);
+		}
+		else if (!tag.empty())
+		{
+			IndexInsertNode(styled_node_index.tags, tag, this);
+		}
+		else
+		{
+			styled_node_index.other.push_back(this);
+		}
 	}
 	}
 
 
 	for (auto& child : children)
 	for (auto& child : children)
-	{
 		child->BuildIndex(styled_node_index);
 		child->BuildIndex(styled_node_index);
-	}
 }
 }
 
 
 bool StyleSheetNode::SetStructurallyVolatileRecursive(bool ancestor_is_structural_pseudo_class)
 bool StyleSheetNode::SetStructurallyVolatileRecursive(bool ancestor_is_structural_pseudo_class)
@@ -241,24 +261,29 @@ inline bool StyleSheetNode::MatchStructuralSelector(const Element* element) cons
 }
 }
 
 
 // Returns true if this node is applicable to the given element, given its IDs, classes and heritage.
 // Returns true if this node is applicable to the given element, given its IDs, classes and heritage.
-bool StyleSheetNode::IsApplicable(const Element* const in_element, bool skip_id_tag) const
+bool StyleSheetNode::IsApplicable(const Element* const in_element) const
 {
 {
 	// Determine whether the element matches the current node and its entire lineage. The entire hierarchy of
 	// Determine whether the element matches the current node and its entire lineage. The entire hierarchy of
 	// the element's document will be considered during the match as necessary.
 	// the element's document will be considered during the match as necessary.
 
 
-	if (skip_id_tag)
+	for (const String& name : pseudo_class_names)
 	{
 	{
-		// Id and tag have already been checked, only check class and pseudo class.
-		if (!MatchClassPseudoClass(in_element))
+		if (!in_element->IsPseudoClassSet(name))
 			return false;
 			return false;
 	}
 	}
-	else
+
+	if (!tag.empty() && tag != in_element->GetTagName())
+		return false;
+
+	for (const String& name : class_names)
 	{
 	{
-		// Id and tag have not already been matched, match everything.
-		if (!Match(in_element))
+		if (!in_element->IsClassSet(name))
 			return false;
 			return false;
 	}
 	}
 
 
+	if (!id.empty() && id != in_element->GetId())
+		return false;
+
 	const Element* element = in_element;
 	const Element* element = in_element;
 
 
 	// Walk up through all our parent nodes, each one of them must be matched by some ancestor element.
 	// Walk up through all our parent nodes, each one of them must be matched by some ancestor element.

+ 4 - 3
Source/Core/StyleSheetNode.h

@@ -30,12 +30,13 @@
 #define RMLUI_CORE_STYLESHEETNODE_H
 #define RMLUI_CORE_STYLESHEETNODE_H
 
 
 #include "../../Include/RmlUi/Core/PropertyDictionary.h"
 #include "../../Include/RmlUi/Core/PropertyDictionary.h"
-#include "../../Include/RmlUi/Core/StyleSheet.h"
 #include "../../Include/RmlUi/Core/Types.h"
 #include "../../Include/RmlUi/Core/Types.h"
 #include <tuple>
 #include <tuple>
 
 
 namespace Rml {
 namespace Rml {
 
 
+struct StyleSheetIndex;
+class StyleSheetNode;
 class StyleSheetNodeSelector;
 class StyleSheetNodeSelector;
 
 
 struct StructuralSelector {
 struct StructuralSelector {
@@ -76,7 +77,7 @@ public:
 	/// Recursively set structural volatility.
 	/// Recursively set structural volatility.
 	bool SetStructurallyVolatileRecursive(bool ancestor_is_structurally_volatile);
 	bool SetStructurallyVolatileRecursive(bool ancestor_is_structurally_volatile);
 	/// Builds up a style sheet's index recursively.
 	/// Builds up a style sheet's index recursively.
-	void BuildIndex(StyleSheet::NodeIndex& styled_node_index) const;
+	void BuildIndex(StyleSheetIndex& styled_node_index) const;
 
 
 	/// Imports properties from a single rule definition into the node's properties and sets the
 	/// Imports properties from a single rule definition into the node's properties and sets the
 	/// appropriate specificity on them. Any existing attributes sharing a key with a new attribute
 	/// appropriate specificity on them. Any existing attributes sharing a key with a new attribute
@@ -88,7 +89,7 @@ public:
 	const PropertyDictionary& GetProperties() const;
 	const PropertyDictionary& GetProperties() const;
 
 
 	/// Returns true if this node is applicable to the given element, given its IDs, classes and heritage.
 	/// Returns true if this node is applicable to the given element, given its IDs, classes and heritage.
-	bool IsApplicable(const Element* element, bool skip_id_tag) const;
+	bool IsApplicable(const Element* element) const;
 
 
 	/// Returns the specificity of this node.
 	/// Returns the specificity of this node.
 	int GetSpecificity() const;
 	int GetSpecificity() const;

+ 250 - 0
Tests/Source/Benchmarks/ElementStyle.cpp

@@ -0,0 +1,250 @@
+/*
+ * This source file is part of RmlUi, the HTML/CSS Interface Middleware
+ *
+ * For the latest information, see http://github.com/mikke89/RmlUi
+ *
+ * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
+ * Copyright (c) 2019 The RmlUi Team, and contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#include "../Common/TestsShell.h"
+#include <RmlUi/Core/Context.h>
+#include <RmlUi/Core/Element.h>
+#include <RmlUi/Core/ElementDocument.h>
+#include <RmlUi/Core/Types.h>
+#include <doctest.h>
+#include <nanobench.h>
+
+using namespace ankerl;
+using namespace Rml;
+
+static constexpr const char* document_rml_template = R"(
+<rml>
+<head>
+	
+	<title>Benchmark Sample</title>
+	<link type="text/template" href="/assets/window.rml"/>
+	<style>
+		body
+		{
+			font-family: LatoLatin;
+			font-weight: normal;
+			font-style: normal;
+			font-size: 15dp;
+			color: white;
+		}
+		body.window
+		{
+			max-width: 2000px;
+			max-height: 2000px;
+			left: 100px;
+			top: 50px;
+			width: 1300px;
+			height: 600px;
+		}
+		#performance 
+		{
+			width: 800px;
+			height: 300px;
+		}
+
+/* Insert generated style rules below */
+%s
+
+	</style>
+</head>
+
+<body template="window">
+<div id="performance"/>
+</body>
+</rml>
+)";
+
+static int GetNumDescendentElements(Element* element)
+{
+	const int num_children = element->GetNumChildren(true);
+	int result = num_children;
+	for (int i = 0; i < num_children; i++)
+	{
+		result += GetNumDescendentElements(element->GetChild(i));
+	}
+	return result;
+}
+
+static constexpr int num_rule_iterations = 10;
+
+static String GenerateRCSS(bool with_tag, bool with_id, bool with_class, bool with_pseudo_class, bool with_child_div, String& out_rule_name)
+{
+	static_assert('a' < 'z' && 'a' + 25 == 'z', "Assumes ASCII characters");
+
+	auto GenerateRule = [=](const String& name) {
+		String rule;
+		if (!with_tag && !with_id && !with_class && !with_pseudo_class)
+			rule += '*';
+		else
+		{
+			if (with_tag)
+			{
+				if (with_id || with_class || with_pseudo_class)
+					rule += "div";
+				else
+					rule += name;
+			}
+
+			if (with_id)
+				rule += '#' + name;
+			if (with_class)
+				rule += '.' + name;
+			if (with_pseudo_class)
+				rule += ':' + name;
+		}
+		if (with_child_div)
+			rule += " div";
+		return rule;
+	};
+
+	out_rule_name = GenerateRule("a");
+
+	String result;
+
+	for (int i = 0; i < num_rule_iterations; i++)
+	{
+		for (char c = 'a'; c <= 'z'; c++)
+		{
+			const String name(i, c);
+			result += GenerateRule(name);
+
+			// Set a property that does not require a layout change
+			result += CreateString(64, " { scrollbar-margin: %dpx; }\n", int(c - 'a') + 1);
+		}
+	}
+
+	return result;
+}
+
+static String GenerateRml(const int num_rows)
+{
+	static nanobench::Rng rng;
+
+	Rml::String rml;
+	rml.reserve(1000 * num_rows);
+
+	for (int i = 0; i < num_rows; i++)
+	{
+		int index = rng() % 1000;
+		int route = rng() % 50;
+		int max = (rng() % 40) + 10;
+		int value = rng() % max;
+		String class_name_a = char('a' + char(rng() % 26)) + ToString(rng() % num_rule_iterations);
+		String class_name_b = char('a' + char(rng() % 26)) + ToString(rng() % num_rule_iterations);
+		Rml::String rml_row = Rml::CreateString(1000, R"(
+			<div class="row">
+				<div class="col col1"><button class="expand" index="%d">+</button>&nbsp;<a>Route %d</a></div>
+				<div class="col col23"><input type="range" class="assign_range" min="0" max="%d" value="%d"/></div>
+				<div class="col col4 %s">Assigned</div>
+				<select>
+					<option>Red</option><option>Blue</option><option selected>Green</option><option style="background-color: yellow;">Yellow</option>
+				</select>
+				<div class="inrow unmark_collapse %s">
+					<div class="col col123 assign_text">Assign to route</div>
+					<div class="col col4">
+						<input type="submit" class="vehicle_depot_assign_confirm" quantity="0">Confirm</input>
+					</div>
+				</div>
+			</div>)",
+			index, route, max, value, class_name_a.c_str(), class_name_b.c_str());
+		rml += rml_row;
+	}
+
+	return rml;
+}
+
+TEST_CASE("elementstyle")
+{
+	Context* context = TestsShell::GetContext();
+	REQUIRE(context);
+
+	constexpr int num_rows = 50;
+	const String rml = GenerateRml(num_rows);
+
+	// Benchmark the lookup of applicable style rules for elements.
+	//
+	// We do this by toggling a pseudo class on an element with a lot of descendent elements. This dirties the style definition for this and all
+	// descendent elements, requiring a lookup for applicable nodes on each of them. We repeat this benchmark with different combinations of unique
+	// "dummy" style rules added to the style sheet.
+
+	nanobench::Bench bench;
+	bench.title("ElementStyle (rule name)");
+	bench.timeUnit(std::chrono::microseconds(1), "us");
+	bench.relative(true);
+
+	for (int i = 0; i <= 0b11111 + 1; i++)
+	{
+		const bool reference = (i == 0);
+		const int flags = i - 1;
+
+		const bool with_tag = flags & (1 << 0);
+		const bool with_id = flags & (1 << 1);
+		const bool with_class = flags & (1 << 2);
+		const bool with_pseudo_class = flags & (1 << 3);
+		const bool with_child_div = flags & (1 << 4);
+
+		String name = "Reference (no style rules)";
+		const String styles = reference ? "" : GenerateRCSS(with_tag, with_id, with_class, with_pseudo_class, with_child_div, name);
+
+		const String compiled_document_rml = Rml::CreateString(1000 + styles.size(), document_rml_template, styles.c_str());
+
+		ElementDocument* document = context->LoadDocumentFromMemory(compiled_document_rml);
+		document->Show();
+
+		Element* el = document->GetElementById("performance");
+		el->SetInnerRML(rml);
+		context->Update();
+		context->Render();
+
+		if (reference)
+		{
+			String msg = Rml::CreateString(128, "\nElement update after pseudo class change with %d descendant elements and %d unique RCSS rules.",
+				GetNumDescendentElements(el), num_rule_iterations * 26);
+			MESSAGE(msg);
+
+			bench.run("Reference (load document)", [&] {
+				ElementDocument* new_document = context->LoadDocumentFromMemory(compiled_document_rml);
+				new_document->Close();
+				context->Update();
+			});
+			bench.run("Reference (update unmodified)", [&] { context->Update(); });
+		}
+
+		bool hover_active = false;
+
+		bench.run(name, [&] {
+			hover_active = !hover_active;
+			// Toggle some arbitrary pseudo class on the element to dirty the definition on this and all descendent elements.
+			el->SetPseudoClass("hover", hover_active);
+			context->Update();
+		});
+
+		document->Close();
+		context->Update();
+	}
+}