Browse Source

WIP: Rework event dispatcher loop, introduce observer pointer (for now based on std::shared_ptr)

Michael Ragazzon 6 years ago
parent
commit
f745fac0ee

+ 2 - 0
CMake/FileList.cmake

@@ -138,6 +138,7 @@ set(Core_PUB_HDR_FILES
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/MathTypes.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Matrix4.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Matrix4.inl
+    ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/ObserverPtr.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Platform.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Plugin.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Profiling.h
@@ -246,6 +247,7 @@ set(Core_SRC_FILES
     ${PROJECT_SOURCE_DIR}/Source/Core/LayoutLineBox.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Log.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Math.cpp
+    ${PROJECT_SOURCE_DIR}/Source/Core/ObserverPtr.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Plugin.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/PluginRegistry.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/precompiled.cpp

+ 1 - 0
Include/RmlUi/Core/Core.h

@@ -59,6 +59,7 @@
 #include "ID.h"
 #include "Input.h"
 #include "Log.h"
+#include "ObserverPtr.h"
 #include "Plugin.h"
 #include "PropertiesIteratorView.h"
 #include "Property.h"

+ 2 - 1
Include/RmlUi/Core/Element.h

@@ -34,6 +34,7 @@
 #include "Box.h"
 #include "ComputedValues.h"
 #include "Event.h"
+#include "ObserverPtr.h"
 #include "Property.h"
 #include "Types.h"
 #include "Transform.h"
@@ -70,7 +71,7 @@ struct ElementMeta;
 	@author Peter Curry
  */
 
-class RMLUICORE_API Element : public ScriptInterface
+class RMLUICORE_API Element : public ScriptInterface, public EnableObserverPtr<Element>
 {
 public:
 	/// Constructs a new RmlUi element. This should not be called directly; use the Factory

+ 2 - 1
Include/RmlUi/Core/EventListener.h

@@ -31,6 +31,7 @@
 
 #include "Header.h"
 #include "Event.h"
+#include "ObserverPtr.h"
 
 namespace Rml {
 namespace Core {
@@ -44,7 +45,7 @@ class Element;
 	@author Lloyd Weehuizen
  */
 
-class RMLUICORE_API EventListener
+class RMLUICORE_API EventListener : public EnableObserverPtr<EventListener>
 {
 public:
 	virtual ~EventListener() {}

+ 90 - 0
Include/RmlUi/Core/ObserverPtr.h

@@ -0,0 +1,90 @@
+/*
+ * 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.
+ *
+ */
+
+#ifndef RMLUIOBSERVERPTR_H
+#define RMLUIOBSERVERPTR_H
+
+#include "Types.h"
+
+namespace Rml {
+namespace Core {
+
+/**
+	Observer pointer.
+
+	Holds a weak reference to something owned by someone else. Will return false if the pointed to object was destroyed.
+
+	Note: Not thread safe.
+ */
+
+template<typename T>
+class ObserverPtr {
+public:
+	ObserverPtr(std::weak_ptr<uintptr_t> ptr) : ptr(ptr) {}
+
+	explicit operator bool() const { return !ptr.expired(); }
+
+	T* get() {
+		// TODO: Locking it here here does nothing really, but required because we are basing this on a std::shared_ptr for now.
+		auto lock = ptr.lock();
+		if (!lock)
+			return nullptr;
+		return reinterpret_cast<T*>(*lock);
+	}
+
+	T* operator->() { return get(); }
+
+private:
+	WeakPtr<uintptr_t> ptr;
+};
+
+
+template<typename T>
+class EnableObserverPtr {
+public:
+	ObserverPtr<T> GetObserverPtr() const { return ObserverPtr<T>(self_reference); }
+
+protected:
+	EnableObserverPtr() : self_reference(std::make_shared<uintptr_t>(reinterpret_cast<uintptr_t>(static_cast<T*>(this))))
+	{}
+
+private:
+	SharedPtr<uintptr_t> self_reference;
+};
+
+
+struct ObserverPtrBlock {
+	int num_observer_pointers = 0;
+	uintptr_t pointed_to_object = 0;
+};
+
+
+}
+}
+
+#endif

+ 2 - 0
Include/RmlUi/Core/Types.h

@@ -129,6 +129,8 @@ template<typename T>
 using UniqueReleaserPtr = std::unique_ptr<T, Releaser<T>>;
 template<typename T>
 using SharedPtr = std::shared_ptr<T>;
+template<typename T>
+using WeakPtr = std::weak_ptr<T>;
 
 using ElementPtr = UniqueReleaserPtr<Element>;
 using ContextPtr = UniqueReleaserPtr<Context>;

+ 2 - 2
Samples/basic/demo/data/demo.rml

@@ -171,7 +171,7 @@ textarea {
 </panel>
 <tab>Decorators</tab>
 <panel id="decorators">
-	<div>	
+	<div>
 		<button>Image</button>
 		<button class="ninepatch">Ninepatch</button>
 	</div>
@@ -223,7 +223,7 @@ textarea {
 	<button id="high_scores" onkeydown="hello" autofocus>High Scores</button><br />
 	<button id="options">Options</button><br />
 	<button id="help">Help</button><br />
-	<div><button id="exit" onclick="exit">Exit</button></div>
+	<div onclick="parent"><button id="exit" onclick="exit">Exit<span onclick="destroyed">?</span></button></div>
 	<img src="../../../assets/high_scores_defender.tga"/>
 	<img src="icon-game" style="vertical-align: 10px;"/>
 	<img src="../../../assets/high_scores_defender.tga" style="image-color: #fc5;" coords="0 0 64 64"/>

+ 14 - 3
Samples/basic/demo/src/main.cpp

@@ -143,15 +143,26 @@ public:
 	{
 		using namespace Rml::Core;
 
-		if (value == "exit")
+		Rml::Core::Log::Message(Rml::Core::Log::LT_WARNING, "%s", value.c_str());
+		if (value == "destroyed")
 		{
-			element->GetParentNode()->SetInnerRML("<button onclick='confirm_exit'>Are you sure?</button>");
-			event.StopImmediatePropagation();
+			element->GetParentNode()->GetParentNode()->SetInnerRML("");// "<button onclick='confirm_exit'>Are you sure?</button>");
+			//if (auto child = element->GetChild(0))
+			//	child->Focus();
+			//event.StopImmediatePropagation();
 		}
 		else if (value == "confirm_exit")
 		{
 			Shell::RequestExit();
 		}
+		else if (value == "destroyed")
+		{
+
+		}
+		else if (value == "parent")
+		{
+
+		}
 	}
 
 	void OnDetach(Rml::Core::Element* element) override { delete this; }

+ 5 - 4
Source/Core/Element.cpp

@@ -105,7 +105,8 @@ static Pool< ElementMeta > element_meta_chunk_pool(200, true);
 
 /// Constructs a new RmlUi element.
 Element::Element(const String& tag) : tag(tag), relative_offset_base(0, 0), relative_offset_position(0, 0), absolute_offset(0, 0), scroll_offset(0, 0), content_offset(0, 0), content_box(0, 0), 
-transform_state(), dirty_transform(false), dirty_perspective(false), dirty_animation(false), dirty_transition(false)
+transform_state(), dirty_transform(false), dirty_perspective(false), dirty_animation(false), dirty_transition(false),
+EnableObserverPtr<Element>()
 {
 	RMLUI_ASSERT(tag == StringUtilities::ToLower(tag));
 	parent = nullptr;
@@ -1207,21 +1208,21 @@ void Element::RemoveEventListener(EventId id, EventListener* listener, bool in_c
 bool Element::DispatchEvent(const String& type, const Dictionary& parameters)
 {
 	const EventSpecification& specification = EventSpecificationInterface::GetOrInsert(type);
-	return event_dispatcher->DispatchEvent(this, specification.id, type, parameters, specification.interruptible, specification.bubbles, specification.default_action_phase);
+	return event_dispatcher->TrueDispatchEvent(this, specification.id, type, parameters, specification.interruptible, specification.bubbles, specification.default_action_phase);
 }
 
 // Dispatches the specified event
 bool Element::DispatchEvent(const String& type, const Dictionary& parameters, bool interruptible, bool bubbles)
 {
 	const EventSpecification& specification = EventSpecificationInterface::GetOrInsert(type);
-	return event_dispatcher->DispatchEvent(this, specification.id, type, parameters, interruptible, bubbles, specification.default_action_phase);
+	return event_dispatcher->TrueDispatchEvent(this, specification.id, type, parameters, interruptible, bubbles, specification.default_action_phase);
 }
 
 // Dispatches the specified event
 bool Element::DispatchEvent(EventId id, const Dictionary& parameters)
 {
 	const EventSpecification& specification = EventSpecificationInterface::Get(id);
-	return event_dispatcher->DispatchEvent(this, specification.id, specification.type, parameters, specification.interruptible, specification.bubbles, specification.default_action_phase);
+	return event_dispatcher->TrueDispatchEvent(this, specification.id, specification.type, parameters, specification.interruptible, specification.bubbles, specification.default_action_phase);
 }
 
 // Scrolls the parent element's contents so that this element is visible.

+ 1 - 1
Source/Core/ElementUtilities.cpp

@@ -193,7 +193,7 @@ bool ElementUtilities::GetClippingRegion(Vector2i& clip_origin, Vector2i& clip_d
 			// Ignore nodes that don't clip.
 			if (clipping_element->GetClientWidth() < clipping_element->GetScrollWidth()
 				|| clipping_element->GetClientHeight() < clipping_element->GetScrollHeight())
-			{				
+			{
 				Vector2f element_origin_f = clipping_element->GetAbsoluteOffset(Box::CONTENT);
 				Vector2f element_dimensions_f = clipping_element->GetBox().GetSize(Box::CONTENT);
 				

+ 124 - 1
Source/Core/EventDispatcher.cpp

@@ -140,7 +140,7 @@ bool EventDispatcher::DispatchEvent(Element* target_element, EventId id, const S
 	{
 		event->SetPhase(EventPhase::Target);
 		event->SetCurrentElement(target_element);
-		TriggerEvents(*event, default_action_phase);
+		target_element->GetEventDispatcher()->TriggerEvents(*event, default_action_phase);
 	}
 
 	// Bubble phase - target to root (normal event bindings)
@@ -228,5 +228,128 @@ void EventDispatcher::TriggerEvents(Event& event, DefaultActionPhase default_act
 	}
 }
 
+
+void EventDispatcher::AddEvents(std::vector<ListenerDesc>& add_listeners, std::vector<ObserverPtr<Element>>& default_action_elements, const EventId event_id, const EventPhase phase, DefaultActionPhase default_action_phase)
+{
+	// Find the range of entries with matching id and phase, given that listeners are sorted by (id,phase).
+	// In the case of target phase we will match any listener phase.
+	Listeners::iterator begin, end;
+	if (phase == EventPhase::Capture)
+		std::tie(begin, end) = std::equal_range(listeners.begin(), listeners.end(), EventListenerEntry(event_id, nullptr, true), CompareIdPhase());
+	else if (phase == EventPhase::Target)
+		std::tie(begin, end) = std::equal_range(listeners.begin(), listeners.end(), EventListenerEntry(event_id, nullptr, false), CompareId());
+	else if (phase == EventPhase::Bubble)
+		std::tie(begin, end) = std::equal_range(listeners.begin(), listeners.end(), EventListenerEntry(event_id, nullptr, false), CompareIdPhase());
+
+	// Copy the range in case the original list of listeners get modified during ProcessEvent.
+	const Listeners listeners_range(begin, end);
+
+	for (auto it = begin; it != end; ++it)
+	{
+		add_listeners.push_back({ element->GetObserverPtr(), it->listener->GetObserverPtr(), phase });
+	}
+
+	const bool do_default_action = ((unsigned int)phase & (unsigned int)default_action_phase);
+
+	// Do the default action unless we have been cancelled.
+	if (do_default_action)
+	{
+		default_action_elements.push_back(element->GetObserverPtr());
+	}
+}
+
+bool EventDispatcher::TrueDispatchEvent(Element* target_element, EventId id, const String& type, const Dictionary& parameters, bool interruptible, bool bubbles, DefaultActionPhase default_action_phase)
+{
+	std::vector<ListenerDesc> listeners;
+	std::vector<ObserverPtr<Element>> default_action_elements;
+
+
+
+	// Build the element traversal from the tree
+	typedef std::vector<Element*> Elements;
+	Elements elements;
+
+	Element* walk_element = target_element->GetParentNode();
+	while (walk_element)
+	{
+		elements.push_back(walk_element);
+		walk_element = walk_element->GetParentNode();
+	}
+
+	// Capture phase - root to target (only triggers event listeners that are registered with capture phase)
+	// Note: We walk elements in REVERSE as they're placed in the list from the elements parent to the root
+	for (int i = (int)elements.size() - 1; i >= 0; i--)
+	{
+		EventDispatcher* dispatcher = elements[i]->GetEventDispatcher();
+		dispatcher->AddEvents(listeners, default_action_elements, id, EventPhase::Capture, default_action_phase);
+	}
+
+	target_element->GetEventDispatcher()->AddEvents(listeners, default_action_elements, id, EventPhase::Target, default_action_phase);
+
+	// Bubble phase - target to root (normal event bindings)
+	if (bubbles)
+	{
+		for (Element* el : elements)
+		{
+			EventDispatcher* dispatcher = el->GetEventDispatcher();
+			dispatcher->AddEvents(listeners, default_action_elements, id, EventPhase::Bubble, default_action_phase);
+		}
+	}
+
+	if (listeners.empty() && default_action_elements.empty())
+		return true;
+
+	// Instance event
+	EventPtr event = Factory::InstanceEvent(target_element, id, type, parameters, interruptible);
+	if (!event)
+		return false;
+
+	Element* previous_element = nullptr;
+
+	// Process event in each listener
+	for (auto& listener_desc : listeners)
+	{
+		auto element = listener_desc.element.get();
+		auto listener = listener_desc.listener.get();
+
+		if (previous_element != element)
+		{
+			event->SetCurrentElement(element);
+			if (!event->IsPropagating())
+				break;
+			previous_element = element;
+		}
+
+		if (element && listener)
+		{
+			event->SetPhase(listener_desc.phase);
+			listener->ProcessEvent(*event);
+		}
+
+		if (!event->IsImmediatePropagating())
+			break;
+	}
+
+	// Process default actions
+	for (auto& element_observer : default_action_elements)
+	{
+		if (!event->IsPropagating())
+			break;
+
+		auto element = element_observer.get();
+
+		if (element)
+		{
+			event->SetPhase(element == target_element ? EventPhase::Target : EventPhase::Bubble);
+			event->SetCurrentElement(element);
+			element->ProcessDefaultAction(*event);
+		}
+	}
+
+	bool propagating = event->IsPropagating();
+
+	return propagating;
+}
+
 }
 }

+ 18 - 3
Source/Core/EventDispatcher.h

@@ -44,6 +44,13 @@ struct EventListenerEntry {
 	EventListener* listener;
 };
 
+
+struct ListenerDesc {
+	ObserverPtr<Element> element;
+	ObserverPtr<EventListener> listener;
+	EventPhase phase;
+};
+
 /**
 	The Event Dispatcher manages a list of event listeners and triggers the events via EventHandlers
 	whenever requested.
@@ -76,18 +83,24 @@ public:
 	/// Detaches all events from this dispatcher and all child dispatchers.
 	void DetachAllEvents();
 
-	/// Dispatches the specified event with element as the target
-	/// @param[in] target_element The target element of the event
+	/// Dispatches the specified event, targeting the element associated with this dispatcher.
 	/// @param[in] name The name of the event
 	/// @param[in] parameters The event parameters
 	/// @param[in] interruptible Can the event propagation be stopped
 	/// @return True if the event was not consumed (ie, was prevented from propagating by an element), false if it was.
-	bool DispatchEvent(Element* element, EventId id, const String& type, const Dictionary& parameters, bool interruptible, bool bubbles, DefaultActionPhase default_action_phase);
+	static bool DispatchEvent(Element* target_element, EventId id, const String& type, const Dictionary& parameters, bool interruptible, bool bubbles, DefaultActionPhase default_action_phase);
 
 	/// Returns event types with number of listeners for debugging.
 	/// @return Summary of attached listeners.
 	String ToString() const;
 
+
+	static bool TrueDispatchEvent(Element* target_element, EventId id, const String& type, const Dictionary& parameters, bool interruptible, bool bubbles, DefaultActionPhase default_action_phase);
+
+
+	void EventDispatcher::AddEvents(std::vector<ListenerDesc>& add_listeners, std::vector<ObserverPtr<Element>>& default_action_elements, const EventId event_id, const EventPhase phase, DefaultActionPhase default_action_phase);
+
+
 private:
 	Element* element;
 
@@ -99,6 +112,8 @@ private:
 	void TriggerEvents(Event& event, DefaultActionPhase default_action_phase);
 };
 
+
+
 }
 }
 

+ 37 - 0
Source/Core/ObserverPtr.cpp

@@ -0,0 +1,37 @@
+/*
+ * 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 "precompiled.h"
+#include "../../Include/RmlUi/Core/ObserverPtr.h"
+
+namespace Rml {
+namespace Core {
+
+
+}
+}