Browse Source

Add a cache for instanced decorators. Instance decorators during Context::Update. Allow dirtying only element data of decorators instead of a full reload.

Michael Ragazzon 4 years ago
parent
commit
74c5f02849

+ 7 - 2
Include/RmlUi/Core/StyleSheet.h

@@ -45,6 +45,7 @@ class SpritesheetList;
 class Stream;
 class StyleSheetContainer;
 class StyleSheetParser;
+struct PropertySource;
 struct Sprite;
 struct Spritesheet;
 
@@ -85,8 +86,8 @@ public:
 	/// Retrieve the hash key used to look-up applicable nodes in the node index.
 	static size_t NodeHash(const String& tag, const String& id);
 
-	/// Returns the @decorator of the given name, or null if it does not exist.
-	SharedPtr<Decorator> GetDecorator(const String& name) const;
+	/// 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;
 
 private:
 	StyleSheet();
@@ -117,6 +118,10 @@ private:
 	using ElementDefinitionCache = UnorderedMap< size_t, SharedPtr<ElementDefinition> >;
 	mutable ElementDefinitionCache node_cache;
 
+	// Cached decorator instances.
+	using DecoratorCache = UnorderedMap< String, Vector<SharedPtr<const Decorator>> >;
+	mutable DecoratorCache decorator_cache;
+
 	friend Rml::StyleSheetParser;
 	friend Rml::StyleSheetContainer;
 };

+ 11 - 4
Source/Core/Element.cpp

@@ -171,6 +171,8 @@ void Element::Update(float dp_ratio, Vector2f vp_dimensions)
 		UpdateProperties(dp_ratio, vp_dimensions);
 	}
 
+	meta->decoration.InstanceDecorators();
+
 	for (size_t i = 0; i < children.size(); i++)
 		children[i]->Update(dp_ratio, vp_dimensions);
 }
@@ -450,7 +452,7 @@ void Element::SetBox(const Box& box)
 
 		meta->background_border.DirtyBackground();
 		meta->background_border.DirtyBorder();
-		meta->decoration.DirtyDecorators();
+		meta->decoration.DirtyDecoratorsData();
 	}
 }
 
@@ -463,7 +465,7 @@ void Element::AddBox(const Box& box, Vector2f offset)
 
 	meta->background_border.DirtyBackground();
 	meta->background_border.DirtyBorder();
-	meta->decoration.DirtyDecorators();
+	meta->decoration.DirtyDecoratorsData();
 }
 
 // Returns one of the boxes describing the size of the element.
@@ -1825,12 +1827,17 @@ void Element::OnPropertyChange(const PropertyIdSet& changed_properties)
 	}
 	
 	// Dirty the decoration if it's changed.
+	if (changed_properties.Contains(PropertyId::Decorator))
+	{
+		meta->decoration.DirtyDecorators();
+	}
+
+	// Dirty the decoration data when its visual looks may have changed.
 	if (border_radius_changed ||
-		changed_properties.Contains(PropertyId::Decorator) ||
 		changed_properties.Contains(PropertyId::Opacity) ||
 		changed_properties.Contains(PropertyId::ImageColor))
 	{
-		meta->decoration.DirtyDecorators();
+		meta->decoration.DirtyDecoratorsData();
 	}
 
 	// Check for `perspective' and `perspective-origin' changes

+ 44 - 40
Source/Core/ElementDecoration.cpp

@@ -36,17 +36,24 @@
 
 namespace Rml {
 
-ElementDecoration::ElementDecoration(Element* _element)
-{
-	element = _element;
-	decorators_dirty = false;
-}
+ElementDecoration::ElementDecoration(Element* _element) : element(_element)
+{}
 
 ElementDecoration::~ElementDecoration()
 {
 	ReleaseDecorators();
 }
 
+void ElementDecoration::InstanceDecorators()
+{
+	if (decorators_dirty)
+	{
+		decorators_dirty = false;
+		decorators_data_dirty = true;
+		ReloadDecorators();
+	}
+}
+
 // Releases existing decorators and loads all decorators required by the element's definition.
 bool ElementDecoration::ReloadDecorators()
 {
@@ -60,8 +67,8 @@ bool ElementDecoration::ReloadDecorators()
 	if (!property || property->unit != Property::DECORATOR)
 		return false;
 
-	DecoratorsPtr decorators = property->Get<DecoratorsPtr>();
-	if (!decorators)
+	DecoratorsPtr decorators_ptr = property->Get<DecoratorsPtr>();
+	if (!decorators_ptr)
 		return false;
 
 	const StyleSheet* style_sheet = element->GetStyleSheet();
@@ -80,27 +87,17 @@ bool ElementDecoration::ReloadDecorators()
 		}
 	}
 
-	for (const DecoratorDeclaration& decorator : decorators->list)
-	{
-		if (decorator.instancer)
-		{
-			RMLUI_ZoneScopedN("InstanceDecorator");
-			SharedPtr<const Decorator> decorator_instance = decorator.instancer->InstanceDecorator(decorator.type, decorator.properties, DecoratorInstancerInterface(*style_sheet, source));
+	const auto& decorator_list = style_sheet->InstanceDecorators(*decorators_ptr, source);
 
-			if (decorator_instance)
-				LoadDecorator(std::move(decorator_instance));
-			else
-				Log::Message(Log::LT_WARNING, "Decorator '%s' in '%s' could not be instanced, declared at %s:%d", decorator.type.c_str(), decorators->value.c_str(), source ? source->path.c_str() : "", source ? source->line_number : -1);
-		}
-		else
+	for (const SharedPtr<const Decorator>& decorator : decorator_list)
+	{
+		if (decorator)
 		{
-			// If we have no instancer, this means the type is the name of an @decorator rule.
-			SharedPtr<const Decorator> decorator_instance = style_sheet->GetDecorator(decorator.type);
+			DecoratorHandle decorator_handle;
+			decorator_handle.decorator_data = 0;
+			decorator_handle.decorator = decorator;
 
-			if (decorator_instance)
-				LoadDecorator(std::move(decorator_instance));
-			else
-				Log::Message(Log::LT_WARNING, "Decorator name '%s' could not be found in any @decorator rule, declared at %s:%d", decorator.type.c_str(), source ? source->path.c_str() : "", source ? source->line_number : -1);
+			decorators.push_back(std::move(decorator_handle));
 		}
 	}
 
@@ -108,23 +105,29 @@ bool ElementDecoration::ReloadDecorators()
 }
 
 // Loads a single decorator and adds it to the list of loaded decorators for this element.
-int ElementDecoration::LoadDecorator(SharedPtr<const Decorator> decorator)
+void ElementDecoration::ReloadDecoratorsData()
 {
-	DecoratorHandle element_decorator;
-	element_decorator.decorator_data = decorator->GenerateElementData(element);
-	element_decorator.decorator = std::move(decorator);
+	if (decorators_data_dirty)
+	{
+		decorators_data_dirty = false;
+
+		for (DecoratorHandle& decorator : decorators)
+		{
+			if (decorator.decorator_data)
+				decorator.decorator->ReleaseElementData(decorator.decorator_data);
 
-	decorators.push_back(element_decorator);
-	return (int) (decorators.size() - 1);
+			decorator.decorator_data = decorator.decorator->GenerateElementData(element);
+		}
+	}
 }
 
 // Releases all existing decorators and frees their data.
 void ElementDecoration::ReleaseDecorators()
 {
-	for (size_t i = 0; i < decorators.size(); i++)
+	for (DecoratorHandle& decorator : decorators)
 	{
-		if (decorators[i].decorator_data)
-			decorators[i].decorator->ReleaseElementData(decorators[i].decorator_data);
+		if (decorator.decorator_data)
+			decorator.decorator->ReleaseElementData(decorator.decorator_data);
 	}
 
 	decorators.clear();
@@ -133,12 +136,8 @@ void ElementDecoration::ReleaseDecorators()
 
 void ElementDecoration::RenderDecorators()
 {
-	// @performance: Ignore dirty flag if e.g. pseudo classes do not affect the decorators
-	if (decorators_dirty)
-	{
-		decorators_dirty = false;
-		ReloadDecorators();
-	}
+	InstanceDecorators();
+	ReloadDecoratorsData();
 
 	// Render the decorators attached to this element in its current state.
 	// Render from back to front for correct render order.
@@ -154,4 +153,9 @@ void ElementDecoration::DirtyDecorators()
 	decorators_dirty = true;
 }
 
+void ElementDecoration::DirtyDecoratorsData()
+{
+	decorators_data_dirty = true;
+}
+
 } // namespace Rml

+ 11 - 4
Source/Core/ElementDecoration.h

@@ -50,17 +50,22 @@ public:
 	ElementDecoration(Element* element);
 	~ElementDecoration();
 
+	/// Instances decorators if necessary.
+	void InstanceDecorators();
+
 	/// Renders all appropriate decorators.
 	void RenderDecorators();
 
 	/// Mark decorators as dirty and force them to reset themselves.
 	void DirtyDecorators();
+	/// Mark the element data of decorators as dirty.
+	void DirtyDecoratorsData();
 
 private:
-	// Loads a single decorator and adds it to the list of loaded decorators for this element.
-	int LoadDecorator(SharedPtr<const Decorator> decorator);
 	// Releases existing decorators and loads all decorators required by the element's definition.
 	bool ReloadDecorators();
+	// Releases existing element data of decorators, and regenerates it.
+	void ReloadDecoratorsData();
 	// Releases all existing decorators and frees their data.
 	void ReleaseDecorators();
 
@@ -78,8 +83,10 @@ private:
 	// The list of every decorator used by this element in every class.
 	DecoratorHandleList decorators;
 
-	// If set, a full reload is necessary
-	bool decorators_dirty;
+	// If set, a full reload is necessary.
+	bool decorators_dirty = false;
+	// If set, element data of all decorators need to be regenerated.
+	bool decorators_data_dirty = false;
 };
 
 } // namespace Rml

+ 44 - 5
Source/Core/StyleSheet.cpp

@@ -30,6 +30,7 @@
 #include "ElementDefinition.h"
 #include "StyleSheetNode.h"
 #include "Utilities.h"
+#include "../../Include/RmlUi/Core/DecoratorInstancer.h"
 #include "../../Include/RmlUi/Core/Element.h"
 #include "../../Include/RmlUi/Core/Profiling.h"
 #include "../../Include/RmlUi/Core/PropertyDefinition.h"
@@ -118,12 +119,50 @@ const Keyframes * StyleSheet::GetKeyframes(const String & name) const
 	return nullptr;
 }
 
-SharedPtr<Decorator> StyleSheet::GetDecorator(const String& name) const
+const Vector<SharedPtr<const Decorator>>& StyleSheet::InstanceDecorators(const DecoratorDeclarationList& declaration_list, const PropertySource* source) const
 {
-	auto it = decorator_map.find(name);
-	if (it == decorator_map.end())
-		return nullptr;
-	return it->second.decorator;
+	// Generate the cache key. Relative paths of textures may be affected by the source path, and ultimately
+	// which texture should be displayed. Thus, we need to include this path in the cache key.
+	String key;
+	key.reserve(declaration_list.value.size() + 1 + (source ? source->path.size() : 0));
+	key = declaration_list.value;
+	key += ';';
+	if (source)
+		key += source->path;
+
+	auto it_cache = decorator_cache.find(key);
+	if (it_cache != decorator_cache.end())
+		return it_cache->second;
+
+	Vector<SharedPtr<const Decorator>>& decorators = decorator_cache[key];
+
+	for (const DecoratorDeclaration& declaration : declaration_list.list)
+	{
+		if (declaration.instancer)
+		{
+			RMLUI_ZoneScopedN("InstanceDecorator");
+			
+			if (SharedPtr<Decorator> decorator = declaration.instancer->InstanceDecorator(declaration.type, declaration.properties, DecoratorInstancerInterface(*this, source)))
+				decorators.push_back(std::move(decorator));
+			else
+				Log::Message(Log::LT_WARNING, "Decorator '%s' in '%s' could not be instanced, declared at %s:%d", declaration.type.c_str(), declaration_list.value.c_str(), source ? source->path.c_str() : "", source ? source->line_number : -1);
+		}
+		else
+		{
+			// If we have no instancer, this means the type is the name of an @decorator rule.
+			SharedPtr<Decorator> decorator;
+			auto it_map = decorator_map.find(declaration.type);
+			if (it_map != decorator_map.end())
+				decorator = it_map->second.decorator;
+
+			if (decorator)
+				decorators.push_back(std::move(decorator));
+			else
+				Log::Message(Log::LT_WARNING, "Decorator name '%s' could not be found in any @decorator rule, declared at %s:%d", declaration.type.c_str(), source ? source->path.c_str() : "", source ? source->line_number : -1);
+		}
+	}
+
+	return decorators;
 }
 
 const Sprite* StyleSheet::GetSprite(const String& name) const