Browse Source

Only update dirty data variables

Michael Ragazzon 5 years ago
parent
commit
04f6898094

+ 5 - 6
Include/RmlUi/Core/DataController.h

@@ -46,13 +46,12 @@ class RMLUICORE_API DataController {};
 
 class DataControllerAttribute : public DataController {
 public:
-	DataControllerAttribute(const DataModel& model, Element* parent, const String& in_attribute_name, const String& in_value_name);
+	DataControllerAttribute(DataModel& model, Element* parent, const String& in_attribute_name, const String& in_value_name);
 
-	inline operator bool() const {
-		return !attribute_name.empty();
+	explicit operator bool() const {
+		return !attribute_name.empty() && !variable_address.empty();
 	}
-	bool Update(Element* element, const DataModel& model);
-
+	bool Update(Element* element, DataModel& model);
 
 	bool OnAttributeChange( const ElementAttributes& changed_attributes)
 	{
@@ -81,7 +80,7 @@ public:
 		RMLUI_ASSERT(inserted);
 	}
 
-	bool Update(const DataModel& model)
+	bool Update(DataModel& model)
 	{
 		bool result = false;
 		for (auto& controller : attribute_controllers)

+ 17 - 5
Include/RmlUi/Core/DataModel.h

@@ -59,9 +59,6 @@ public:
 		return Bind(name, ptr, type_register->Get<T>(), VariableType::Array);
 	}
 
-	Variant GetValue(const String& address_str) const;
-	bool SetValue(const String& address_str, const Variant& variant) const;
-
 	template<typename T>
 	bool GetValue(const Address& address, T& out_value) const {
 		Variant variant;
@@ -72,6 +69,9 @@ public:
 	Variable GetVariable(const String& address_str) const;
 	Variable GetVariable(const Address& address) const;
 
+	void DirtyVariable(const String& variable_name);
+	bool UpdateVariable(const String& variable_name);
+
 	Address ResolveAddress(const String& address_str, Element* parent) const;
 
 	void AddView(UniquePtr<DataView> view) { views.Add(std::move(view)); }
@@ -80,7 +80,7 @@ public:
 	bool InsertAlias(Element* element, const String& alias_name, Address replace_with_address) const;
 	bool EraseAliases(Element* element) const;
 
-	bool UpdateViews() { return views.Update(*this); }
+	bool UpdateViews();
 
 	void OnElementRemove(Element* element);
 
@@ -97,6 +97,8 @@ private:
 	mutable ScopedAliases aliases;
 
 	DataViews views;
+
+	SmallUnorderedSet< String > dirty_variables;
 };
 
 
@@ -114,9 +116,19 @@ public:
 		model->UpdateViews();
 	}
 
+	bool UpdateVariable(const String& variable_name)
+	{
+		RMLUI_ASSERT(model);
+		return model->UpdateVariable(variable_name);
+	}
+	void DirtyVariable(const String& variable_name)
+	{
+		RMLUI_ASSERT(model);
+		model->DirtyVariable(variable_name);
+	}
 	DataModel* GetModel() { return model; }
 
-	operator bool() { return model != nullptr; }
+	explicit operator bool() { return model != nullptr; }
 
 private:
 	DataModel* model;

+ 32 - 6
Include/RmlUi/Core/DataView.h

@@ -47,6 +47,7 @@ class RMLUICORE_API DataView : NonCopyMoveable {
 public:
 	virtual ~DataView();
 	virtual bool Update(const DataModel& model) = 0;
+	virtual StringList GetVariableNameList() const = 0;
 
 	bool IsValid() const { return (bool)attached_element; }
 	explicit operator bool() const { return IsValid(); }
@@ -64,12 +65,21 @@ private:
 	int element_depth;
 };
 
-class DataViewText : public DataView {
+class DataViewText final : public DataView {
 public:
 	DataViewText(const DataModel& model, ElementText* in_element, const String& in_text, size_t index_begin_search = 0);
 
 	bool Update(const DataModel& model) override;
 
+	StringList GetVariableNameList() const override {
+		StringList list;
+		for (auto& entry : data_entries)
+		{
+			if (!entry.variable_address.empty())
+				list.push_back(entry.variable_address.front().name);
+		}
+		return list;
+	}
 private:
 	String BuildText() const;
 
@@ -85,47 +95,60 @@ private:
 
 
 
-class DataViewAttribute : public DataView {
+class DataViewAttribute final : public DataView {
 public:
 	DataViewAttribute(const DataModel& model, Element* element, Element* parent, const String& binding_name, const String& attribute_name);
 
 	bool Update(const DataModel& model) override;
 
+	StringList GetVariableNameList() const override {
+		return variable_address.empty() ? StringList() : StringList{ variable_address.front().name };
+	}
 private:
 	Address variable_address;
 	String attribute_name;
 };
 
 
-class DataViewStyle : public DataView {
+class DataViewStyle final : public DataView {
 public:
 	DataViewStyle(const DataModel& model, Element* element, Element* parent, const String& binding_name, const String& property_name);
 
 	bool Update(const DataModel& model) override;
 
+	StringList GetVariableNameList() const override {
+		return variable_address.empty() ? StringList() : StringList{ variable_address.front().name };
+	}
 private:
 	Address variable_address;
 	String property_name;
 };
 
 
-class DataViewIf : public DataView {
+class DataViewIf final : public DataView {
 public:
 	DataViewIf(const DataModel& model, Element* element, Element* parent, const String& binding_name);
 
 	bool Update(const DataModel& model) override;
 
+	StringList GetVariableNameList() const override {
+		return variable_address.empty() ? StringList() : StringList{ variable_address.front().name };
+	}
 private:
 	Address variable_address;
 };
 
 
-class DataViewFor : public DataView {
+class DataViewFor final : public DataView {
 public:
 	DataViewFor(const DataModel& model, Element* element, const String& binding_name, const String& rml_contents);
 
 	bool Update(const DataModel& model) override;
 
+	StringList GetVariableNameList() const override {
+		return variable_address.empty() ? StringList() : StringList{ variable_address.front().name };
+	}
+
 private:
 	Address variable_address;
 	String alias_name;
@@ -145,7 +168,7 @@ public:
 
 	void OnElementRemove(Element* element);
 
-	bool Update(const DataModel& model);
+	bool Update(const DataModel& model, const SmallUnorderedSet< String >& dirty_variables);
 
 private:
 	using DataViewList = std::vector<UniquePtr<DataView>>;
@@ -154,6 +177,9 @@ private:
 	
 	DataViewList views_to_add;
 	DataViewList views_to_remove;
+
+	using NameViewMap = std::unordered_multimap<String, DataView*>;
+	NameViewMap name_view_map;
 };
 
 }

+ 7 - 2
Samples/basic/databinding/src/main.cpp

@@ -175,16 +175,21 @@ std::unique_ptr<DemoWindow> demo_window;
 void GameLoop()
 {
 	my_model_handle.UpdateControllers();
-	my_data.good_rating = (my_data.rating > 50);
-	if(my_data.rating >= 0) 
+
+	if(my_model_handle.UpdateVariable("rating"))
 	{
+		my_data.good_rating = (my_data.rating > 50);
+		my_model_handle.DirtyVariable("good_rating");
+
 		size_t new_size = my_data.rating / 10 + 1;
 		if(new_size != my_data.indices.size())
 		{
 			my_data.indices.resize(new_size);
 			std::iota(my_data.indices.begin(), my_data.indices.end(), int(new_size));
+			my_model_handle.DirtyVariable("indices");
 		}
 	}
+
 	my_model_handle.UpdateViews();
 
 	demo_window->Update();

+ 5 - 2
Source/Core/DataController.cpp

@@ -34,7 +34,7 @@ namespace Rml {
 namespace Core {
 
 
-DataControllerAttribute::DataControllerAttribute(const DataModel& model, Element* parent, const String& in_attribute_name, const String& in_value_name) : attribute_name(in_attribute_name)
+DataControllerAttribute::DataControllerAttribute(DataModel& model, Element* parent, const String& in_attribute_name, const String& in_value_name) : attribute_name(in_attribute_name)
 {
     variable_address = model.ResolveAddress(in_value_name, parent);
     if (!model.GetVariable(variable_address))
@@ -44,7 +44,7 @@ DataControllerAttribute::DataControllerAttribute(const DataModel& model, Element
 	}
 }
 
-bool DataControllerAttribute::Update(Element* element, const DataModel& model) 
+bool DataControllerAttribute::Update(Element* element, DataModel& model) 
 {
 	bool result = false;
 	if (dirty)
@@ -52,7 +52,10 @@ bool DataControllerAttribute::Update(Element* element, const DataModel& model)
 		if(Variant* value = element->GetAttribute(attribute_name))
         {
             if (Variable variable = model.GetVariable(variable_address))
+            {
                 result = variable.Set(*value);
+                model.DirtyVariable(variable_address.front().name);
+            }
         }
 		dirty = false;
 	}

+ 36 - 50
Source/Core/DataModel.cpp

@@ -72,49 +72,6 @@ static Address ParseAddress(const String& address_str)
 	return address;
 };
 
-
-Variant DataModel::GetValue(const Rml::Core::String& address_str) const
-{
-	Variable variable = GetVariable(address_str);
-
-	Variant result;
-	if (!variable)
-		return result;
-
-	if (variable.Type() != VariableType::Scalar)
-	{
-		Log::Message(Log::LT_WARNING, "Error retrieving data variable '%s': Only the values of scalar variables can be parsed.", address_str.c_str());
-		return result;
-	}
-	if (!variable.Get(result))
-		Log::Message(Log::LT_WARNING, "Could not parse data value '%s'", address_str.c_str());
-
-	return result;
-}
-
-
-bool DataModel::SetValue(const String& address_str, const Variant& variant) const
-{
-	Variable variable = GetVariable(address_str);
-
-	if (!variable)
-		return false;
-
-	if (variable.Type() != VariableType::Scalar)
-	{
-		Log::Message(Log::LT_WARNING, "Could not assign data value '%s', variable is not a scalar type.", address_str.c_str());
-		return false;
-	}
-
-	if (!variable.Set(variant))
-	{
-		Log::Message(Log::LT_WARNING, "Could not assign data value '%s'", address_str.c_str());
-		return false;
-	}
-
-	return true;
-}
-
 bool DataModel::Bind(String name, void* ptr, VariableDefinition* variable, VariableType type)
 {
 	RMLUI_ASSERT(ptr);
@@ -181,6 +138,26 @@ Variable DataModel::GetVariable(const Address& address) const
 	return variable;
 }
 
+void DataModel::DirtyVariable(const String& variable_name)
+{
+	RMLUI_ASSERTMSG(variables.count(variable_name) == 1, "Variable name not found among added variables.");
+	dirty_variables.insert(variable_name);
+}
+
+bool DataModel::UpdateVariable(const String& variable_name)
+{
+	auto it = dirty_variables.find(variable_name);
+	if (it == dirty_variables.end())
+		return false;
+
+	SmallUnorderedSet< String > dirty_variable{ *it };
+	dirty_variables.erase(it);
+	bool result = views.Update(*this, dirty_variable);
+
+	return result;
+}
+
+
 Address DataModel::ResolveAddress(const String& address_str, Element* parent) const
 {
 	Address address = ParseAddress(address_str);
@@ -252,6 +229,13 @@ bool DataModel::EraseAliases(Element* element) const
 	return aliases.erase(element) == 1;
 }
 
+bool DataModel::UpdateViews() 
+{
+	bool result = views.Update(*this, dirty_variables);
+	dirty_variables.clear();
+	return result;
+}
+
 void DataModel::OnElementRemove(Element* element)
 {
 	EraseAliases(element);
@@ -318,23 +302,25 @@ static struct TestDataVariables {
 
 			std::vector<String> results;
 
-			for (auto& address : test_addresses)
+			for (auto& str_address : test_addresses)
 			{
-				auto the_address = ParseAddress(address);
+				Address address = ParseAddress(str_address);
 
-				Variant variant = model.GetValue(address);
-				results.push_back(variant.Get<String>());
+				String result;
+				if(model.GetValue<String>(address, result))
+					results.push_back(result);
 			}
 
 			RMLUI_ASSERT(results == expected_results);
 
-			bool success = model.SetValue("data.more_fun[1].magic[1]", Variant(String("199")));
+			bool success = model.GetVariable("data.more_fun[1].magic[1]").Set(Variant(String("199")));
 			RMLUI_ASSERT(success && data.more_fun[1].magic[1] == 199);
 
 			data.fun.magic = { 99, 190, 55, 2000, 50, 60, 70, 80, 90 };
 
-			String result = model.GetValue("data.fun.magic[8]").Get<String>();
-			RMLUI_ASSERT(result == "90");
+			Variant test_get_result;
+			bool test_get_success = model.GetVariable("data.fun.magic[8]").Get(test_get_result);
+			RMLUI_ASSERT(test_get_success && test_get_result.Get<String>() == "90");
 		}
 	}
 } test_data_variables;

+ 53 - 14
Source/Core/DataView.cpp

@@ -355,49 +355,88 @@ void DataViews::Add(UniquePtr<DataView> view) {
 
 void DataViews::OnElementRemove(Element* element) 
 {
-	for (auto&& view : views)
+	for (auto it = views.begin(); it != views.end();)
 	{
+		auto& view = *it;
 		if (view && view->GetElement() == element)
+		{
 			views_to_remove.push_back(std::move(view));
+			it = views.erase(it);
+		}
+		else
+			++it;
 	}
 }
 
-bool DataViews::Update(const DataModel & model)
+bool DataViews::Update(const DataModel & model, const SmallUnorderedSet< String >& dirty_variables)
 {
 	bool result = false;
 
+	std::vector<DataView*> dirty_views;
+
 	if(!views_to_add.empty())
 	{
 		views.reserve(views.size() + views_to_add.size());
 		for (auto&& view : views_to_add)
+		{
+			dirty_views.push_back(view.get());
+			for(const String& variable_name : view->GetVariableNameList())
+				name_view_map.emplace(variable_name, view.get());
+
 			views.push_back(std::move(view));
+		}
 		views_to_add.clear();
-
-		// Sort by the element's depth in the document tree so that any structural changes due to a changed variable are reflected in the element's children.
-		// Eg. the 'data-for' view will remove children if any of its data variable array size is reduced.
-		std::sort(views.begin(), views.end(), [](auto&& left, auto&& right) { return left->GetElementDepth() < right->GetElementDepth(); });
 	}
 
-	if(!views_to_remove.empty())
+	for(const String& variable_name : dirty_variables)
 	{
-		views_to_remove.clear();
-		views.erase(
-			std::remove_if(views.begin(), views.end(), [](auto&& view) { return !view; }),
-			views.end()
-		);
+		auto pair = name_view_map.equal_range(variable_name);
+		for (auto it = pair.first; it != pair.second; ++it)
+			dirty_views.push_back(it->second);
 	}
+
+	// Remove duplicate entries
+	std::sort(dirty_views.begin(), dirty_views.end());
+	auto it_remove = std::unique(dirty_views.begin(), dirty_views.end());
+	dirty_views.erase(it_remove, dirty_views.end());
+
+	// Sort by the element's depth in the document tree so that any structural changes due to a changed variable are reflected in the element's children.
+	// Eg. the 'data-for' view will remove children if any of its data variable array size is reduced.
+	std::sort(dirty_views.begin(), dirty_views.end(), [](auto&& left, auto&& right) { return left->GetElementDepth() < right->GetElementDepth(); });
+
 	
-	for (auto& view : views)
+	// Todo: Newly created views won't actually be updated until next Update loop.
+	for (DataView* view : dirty_views)
 	{
+		RMLUI_ASSERT(view);
 		if (!view)
 			continue;
 
 		if (view->IsValid())
 			result |= view->Update(model);
 		else
-			Log::Message(Log::LT_WARNING, "Invalid view");
+			Log::Message(Log::LT_DEBUG, "Invalid view");
 	}
 
+	// Destroy views marked for destruction
+	// @performance: Horrible...
+	if (!views_to_remove.empty())
+	{
+		for (const auto& view : views_to_remove)
+		{
+			for (auto it = name_view_map.begin(); it != name_view_map.end(); )
+			{
+				if (it->second == view.get())
+					it = name_view_map.erase(it);
+				else
+					++it;
+			}
+		}
+
+		views_to_remove.clear();
+	}
+
+
 	return result;
 }