Explorar o código

Avoid allocations during global initialization (#689)

Instead, explicitly start lifetime of globals on library initialization. This is achieved by using the new ControlledLifetimeResource class. This can be used as a wrapper around globals to explicitly initialize and shutdown the objects.

This wrapper has been applied to all non-trivial global data in RmlUi. Thus, there should no longer be any memory allocations occurring before `main()` when linking in RmlUi.

We now give a warning if there are objects in user space that refer to any RmlUi resources at the end of `Rml::Shutdown`, as this prevents the library from cleaning up memory pools. Other than the warning, the behavior should be the same as previously.

Breaking change: `Rml::ReleaseMemoryPools` is no longer exposed publicly. This function is automatically called during shutdown and should not be used manually.
Michael R. P. Ragazzon hai 1 ano
pai
achega
41e1291ad9
Modificáronse 41 ficheiros con 982 adicións e 592 borrados
  1. 0 3
      Include/RmlUi/Core/Core.h
  2. 5 0
      Include/RmlUi/Core/ElementInstancer.h
  3. 1 1
      Include/RmlUi/Core/Factory.h
  4. 16 12
      Include/RmlUi/Core/ObserverPtr.h
  5. 1 2
      Include/RmlUi/Core/StyleSheetSpecification.h
  6. 1 7
      Include/RmlUi/Core/XMLParser.h
  7. 3 0
      Source/Core/CMakeLists.txt
  8. 21 2
      Source/Core/ComputeProperty.cpp
  9. 4 1
      Source/Core/ComputeProperty.h
  10. 86 0
      Source/Core/ControlledLifetimeResource.h
  11. 81 80
      Source/Core/Core.cpp
  12. 3 16
      Source/Core/Element.cpp
  13. 27 8
      Source/Core/ElementInstancer.cpp
  14. 54 0
      Source/Core/ElementMeta.cpp
  15. 67 0
      Source/Core/ElementMeta.h
  16. 5 5
      Source/Core/ElementStyle.cpp
  17. 65 47
      Source/Core/EventSpecification.cpp
  18. 1 0
      Source/Core/EventSpecification.h
  19. 114 150
      Source/Core/Factory.cpp
  20. 23 9
      Source/Core/Layout/LayoutPools.cpp
  21. 3 0
      Source/Core/Layout/LayoutPools.h
  22. 32 15
      Source/Core/ObserverPtr.cpp
  23. 49 32
      Source/Core/PluginRegistry.cpp
  24. 1 1
      Source/Core/PluginRegistry.h
  25. 92 78
      Source/Core/PropertyParserAnimation.cpp
  26. 10 0
      Source/Core/PropertyParserAnimation.h
  27. 37 23
      Source/Core/PropertyParserColour.cpp
  28. 5 2
      Source/Core/PropertyParserColour.h
  29. 20 7
      Source/Core/PropertyParserDecorator.cpp
  30. 5 1
      Source/Core/PropertyParserDecorator.h
  31. 33 19
      Source/Core/PropertyParserNumber.cpp
  32. 6 0
      Source/Core/PropertyParserNumber.h
  33. 1 1
      Source/Core/StyleSheetContainer.cpp
  34. 21 18
      Source/Core/StyleSheetParser.cpp
  35. 21 15
      Source/Core/StyleSheetSpecification.cpp
  36. 7 3
      Source/Core/SystemInterface.cpp
  37. 22 16
      Source/Core/XMLParser.cpp
  38. 2 2
      Tests/Source/Common/TestsShell.cpp
  39. 15 0
      Tests/Source/UnitTests/Core.cpp
  40. 20 16
      Tests/Source/UnitTests/ElementDocument.cpp
  41. 2 0
      Tests/Source/UnitTests/ElementFormControlSelect.cpp

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

@@ -180,9 +180,6 @@ RMLUICORE_API void ReleaseCompiledGeometry(RenderInterface* render_interface = n
 /// @note Invalidates all existing FontFaceHandles returned from the font engine.
 RMLUICORE_API void ReleaseFontResources();
 
-/// Forces all memory pools used by RmlUi to be released.
-RMLUICORE_API void ReleaseMemoryPools();
-
 } // namespace Rml
 
 #endif

+ 5 - 0
Include/RmlUi/Core/ElementInstancer.h

@@ -117,5 +117,10 @@ public:
 	}
 };
 
+namespace Detail {
+	void InitializeElementInstancerPools();
+	void ShutdownElementInstancerPools();
+} // namespace Detail
+
 } // namespace Rml
 #endif

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

@@ -71,7 +71,7 @@ enum class EventId : uint16_t;
 class RMLUICORE_API Factory {
 public:
 	/// Initialise the element factory
-	static bool Initialise();
+	static void Initialise();
 	/// Cleanup and shutdown the factory
 	static void Shutdown();
 

+ 16 - 12
Include/RmlUi/Core/ObserverPtr.h

@@ -35,12 +35,16 @@
 
 namespace Rml {
 
-struct RMLUICORE_API ObserverPtrBlock {
-	int num_observers;
-	void* pointed_to_object;
-};
-RMLUICORE_API ObserverPtrBlock* AllocateObserverPtrBlock();
-RMLUICORE_API void DeallocateObserverPtrBlockIfEmpty(ObserverPtrBlock* block);
+namespace Detail {
+	struct RMLUICORE_API ObserverPtrBlock {
+		int num_observers;
+		void* pointed_to_object;
+	};
+	RMLUICORE_API ObserverPtrBlock* AllocateObserverPtrBlock();
+	RMLUICORE_API void DeallocateObserverPtrBlockIfEmpty(ObserverPtrBlock* block);
+	void InitializeObserverPtrPool();
+	void ShutdownObserverPtrPool();
+} // namespace Detail
 
 template <typename T>
 class EnableObserverPtr;
@@ -117,7 +121,7 @@ public:
 		if (block)
 		{
 			block->num_observers -= 1;
-			DeallocateObserverPtrBlockIfEmpty(block);
+			Detail::DeallocateObserverPtrBlockIfEmpty(block);
 			block = nullptr;
 		}
 	}
@@ -125,13 +129,13 @@ public:
 private:
 	friend class Rml::EnableObserverPtr<T>;
 
-	explicit ObserverPtr(ObserverPtrBlock* block) noexcept : block(block)
+	explicit ObserverPtr(Detail::ObserverPtrBlock* block) noexcept : block(block)
 	{
 		if (block)
 			block->num_observers += 1;
 	}
 
-	ObserverPtrBlock* block;
+	Detail::ObserverPtrBlock* block;
 };
 
 template <typename T>
@@ -151,7 +155,7 @@ protected:
 		if (block)
 		{
 			block->pointed_to_object = nullptr;
-			DeallocateObserverPtrBlockIfEmpty(block);
+			Detail::DeallocateObserverPtrBlockIfEmpty(block);
 		}
 	}
 
@@ -180,13 +184,13 @@ private:
 	{
 		if (!block)
 		{
-			block = AllocateObserverPtrBlock();
+			block = Detail::AllocateObserverPtrBlock();
 			block->num_observers = 0;
 			block->pointed_to_object = static_cast<void*>(static_cast<T*>(this));
 		}
 	}
 
-	ObserverPtrBlock* block = nullptr;
+	Detail::ObserverPtrBlock* block = nullptr;
 };
 
 } // namespace Rml

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

@@ -45,8 +45,7 @@ struct DefaultStyleSheetParsers;
 class RMLUICORE_API StyleSheetSpecification {
 public:
 	/// Starts up the specification structure and registers default properties and type parsers.
-	/// @return True if the specification started up successfully, false if not.
-	static bool Initialise();
+	static void Initialise();
 	/// Destroys the specification structure and releases the parsers.
 	static void Shutdown();
 

+ 1 - 7
Include/RmlUi/Core/XMLParser.h

@@ -104,15 +104,9 @@ protected:
 	void HandleData(const String& data, XMLDataType type) override;
 
 private:
-	// The header of the document being parsed.
 	UniquePtr<DocumentHeader> header;
-
-	// The active node handler.
 	XMLNodeHandler* active_handler;
-
-	// The parser stack.
-	using ParserStack = Stack<ParseFrame>;
-	ParserStack stack;
+	Stack<ParseFrame> stack;
 };
 
 } // namespace Rml

+ 3 - 0
Source/Core/CMakeLists.txt

@@ -15,6 +15,7 @@ add_library(rmlui_core
 	ContextInstancer.cpp
 	ContextInstancerDefault.cpp
 	ContextInstancerDefault.h
+	ControlledLifetimeResource.h
 	ConvolutionFilter.cpp
 	Core.cpp
 	DataController.cpp
@@ -65,6 +66,8 @@ add_library(rmlui_core
 	ElementHandle.cpp
 	ElementHandle.h
 	ElementInstancer.cpp
+	ElementMeta.cpp
+	ElementMeta.h
 	ElementScroll.cpp
 	ElementStyle.cpp
 	ElementStyle.h

+ 21 - 2
Source/Core/ComputeProperty.cpp

@@ -30,10 +30,29 @@
 #include "../../Include/RmlUi/Core/ComputedValues.h"
 #include "../../Include/RmlUi/Core/Property.h"
 #include "../../Include/RmlUi/Core/StringUtilities.h"
+#include "ControlledLifetimeResource.h"
 
 namespace Rml {
 
-const Style::ComputedValues DefaultComputedValues{nullptr};
+struct ComputedPropertyData {
+	const Style::ComputedValues computed{nullptr};
+};
+static ControlledLifetimeResource<ComputedPropertyData> computed_property_data;
+
+const Style::ComputedValues& DefaultComputedValues()
+{
+	return computed_property_data->computed;
+}
+
+void InitializeComputeProperty()
+{
+	computed_property_data.Initialize();
+}
+
+void ShutdownComputeProperty()
+{
+	computed_property_data.Shutdown();
+}
 
 static constexpr float PixelsPerInch = 96.0f;
 
@@ -114,7 +133,7 @@ float ComputeFontsize(NumericValue value, const Style::ComputedValues& values, c
 		case Unit::REM:
 			// If the current element is a document, the rem unit is relative to the default size.
 			if (!document_values || &values == document_values)
-				return value.number * DefaultComputedValues.font_size();
+				return value.number * DefaultComputedValues().font_size();
 
 			// Otherwise it is relative to the document font size.
 			return value.number * document_values->font_size();

+ 4 - 1
Source/Core/ComputeProperty.h

@@ -67,7 +67,10 @@ uint16_t ComputeBorderWidth(float computed_length);
 
 String GetFontFaceDescription(const String& font_family, Style::FontStyle style, Style::FontWeight weight);
 
-extern const Style::ComputedValues DefaultComputedValues;
+const Style::ComputedValues& DefaultComputedValues();
+
+void InitializeComputeProperty();
+void ShutdownComputeProperty();
 
 } // namespace Rml
 #endif

+ 86 - 0
Source/Core/ControlledLifetimeResource.h

@@ -0,0 +1,86 @@
+/*
+ * This source file is part of RmlUi, the HTML/CSS Interface Middleware
+ *
+ * For the latest information, see http://github.com/mikke89/RmlUi
+ *
+ * Copyright (c) 2019-2024 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 RMLUI_CORE_CONTROLLEDLIFETIMERESOURCE_H
+#define RMLUI_CORE_CONTROLLEDLIFETIMERESOURCE_H
+
+#include "../../Include/RmlUi/Core/Debug.h"
+#include "../../Include/RmlUi/Core/Traits.h"
+
+namespace Rml {
+
+template <typename T>
+class ControlledLifetimeResource : NonCopyMoveable {
+public:
+	ControlledLifetimeResource() = default;
+	~ControlledLifetimeResource() noexcept { RMLUI_ASSERTMSG(!pointer || intentionally_leaked, "Resource was not properly shut down."); }
+
+	explicit operator bool() const noexcept { return pointer != nullptr; }
+
+	void Initialize()
+	{
+		RMLUI_ASSERTMSG(!pointer, "Resource already initialized.");
+		pointer = new T();
+	}
+
+	void InitializeIfEmpty()
+	{
+		if (!pointer)
+			Initialize();
+		else
+			SetIntentionallyLeaked(false);
+	}
+
+	void Leak() { SetIntentionallyLeaked(true); }
+
+	void Shutdown()
+	{
+		RMLUI_ASSERTMSG(pointer, "Shutting down resource that was not initialized, or has been shut down already.");
+		RMLUI_ASSERTMSG(!intentionally_leaked, "Shutting down resource that was marked as leaked.");
+		delete pointer;
+		pointer = nullptr;
+	}
+
+	T* operator->()
+	{
+		RMLUI_ASSERTMSG(pointer, "Resource used before it was initialized, or after it was shut down.");
+		return pointer;
+	}
+
+private:
+#ifdef RMLUI_DEBUG
+	void SetIntentionallyLeaked(bool leaked) { intentionally_leaked = leaked; }
+	bool intentionally_leaked = false;
+#else
+	void SetIntentionallyLeaked(bool /*leaked*/) {}
+#endif
+
+	T* pointer = nullptr;
+};
+
+} // namespace Rml
+#endif

+ 81 - 80
Source/Core/Core.cpp

@@ -29,6 +29,7 @@
 #include "../../Include/RmlUi/Core/Core.h"
 #include "../../Include/RmlUi/Core/Context.h"
 #include "../../Include/RmlUi/Core/Element.h"
+#include "../../Include/RmlUi/Core/ElementInstancer.h"
 #include "../../Include/RmlUi/Core/Factory.h"
 #include "../../Include/RmlUi/Core/FileInterface.h"
 #include "../../Include/RmlUi/Core/FontEngineInterface.h"
@@ -39,8 +40,12 @@
 #include "../../Include/RmlUi/Core/SystemInterface.h"
 #include "../../Include/RmlUi/Core/TextInputHandler.h"
 #include "../../Include/RmlUi/Core/Types.h"
+#include "ComputeProperty.h"
+#include "ControlledLifetimeResource.h"
+#include "ElementMeta.h"
 #include "EventSpecification.h"
 #include "FileInterfaceDefault.h"
+#include "Layout/LayoutPools.h"
 #include "PluginRegistry.h"
 #include "RenderManagerAccess.h"
 #include "StyleSheetFactory.h"
@@ -64,32 +69,39 @@
 
 namespace Rml {
 
-// RmlUi's renderer interface.
 static RenderInterface* render_interface = nullptr;
-/// RmlUi's system interface.
 static SystemInterface* system_interface = nullptr;
-// RmlUi's file I/O interface.
 static FileInterface* file_interface = nullptr;
-// RmlUi's font engine interface.
 static FontEngineInterface* font_interface = nullptr;
-// RmlUi's text input handler implementation.
 static TextInputHandler* text_input_handler = nullptr;
 
-// Default interfaces should be created and destroyed on Initialise and Shutdown, respectively.
-static UniquePtr<SystemInterface> default_system_interface;
-static UniquePtr<FileInterface> default_file_interface;
-static UniquePtr<FontEngineInterface> default_font_interface;
-static UniquePtr<TextInputHandler> default_text_input_handler;
+struct CoreData {
+	// Default interfaces should be created and destroyed on Initialise and Shutdown, respectively.
+	UniquePtr<SystemInterface> default_system_interface;
+	UniquePtr<FileInterface> default_file_interface;
+	UniquePtr<FontEngineInterface> default_font_interface;
+	UniquePtr<TextInputHandler> default_text_input_handler;
 
-static UniquePtr<SmallUnorderedMap<RenderInterface*, UniquePtr<RenderManager>>> render_managers;
+	SmallUnorderedMap<RenderInterface*, UniquePtr<RenderManager>> render_managers;
+	UnorderedMap<String, ContextPtr> contexts;
+};
 
-static bool initialised = false;
+static ControlledLifetimeResource<CoreData> core_data;
 
-using ContextMap = UnorderedMap<String, ContextPtr>;
-static ContextMap contexts;
+static bool initialised = false;
 
-// The ObserverPtrBlock pool
-extern Pool<ObserverPtrBlock>* observerPtrBlockPool;
+static void InitializeMemoryPools()
+{
+	Detail::InitializeElementInstancerPools();
+	ElementMetaPool::Initialize();
+	LayoutPools::Initialize();
+}
+static void ReleaseMemoryPools()
+{
+	LayoutPools::Shutdown();
+	ElementMetaPool::Shutdown();
+	Detail::ShutdownElementInstancerPools();
+}
 
 #ifndef RMLUI_VERSION
 	#define RMLUI_VERSION "custom"
@@ -99,18 +111,23 @@ bool Initialise()
 {
 	RMLUI_ASSERTMSG(!initialised, "Rml::Initialise() called, but RmlUi is already initialised!");
 
+	InitializeMemoryPools();
+	InitializeComputeProperty();
+
+	core_data.Initialize();
+
 	// Install default interfaces as appropriate.
 	if (!system_interface)
 	{
-		default_system_interface = MakeUnique<SystemInterface>();
-		system_interface = default_system_interface.get();
+		core_data->default_system_interface = MakeUnique<SystemInterface>();
+		system_interface = core_data->default_system_interface.get();
 	}
 
 	if (!file_interface)
 	{
 #ifndef RMLUI_NO_FILE_INTERFACE_DEFAULT
-		default_file_interface = MakeUnique<FileInterfaceDefault>();
-		file_interface = default_file_interface.get();
+		core_data->default_file_interface = MakeUnique<FileInterfaceDefault>();
+		file_interface = core_data->default_file_interface.get();
 #else
 		Log::Message(Log::LT_ERROR, "No file interface set!");
 		return false;
@@ -120,8 +137,8 @@ bool Initialise()
 	if (!font_interface)
 	{
 #ifdef RMLUI_FONT_ENGINE_FREETYPE
-		default_font_interface = MakeUnique<FontEngineInterfaceDefault>();
-		font_interface = default_font_interface.get();
+		core_data->default_font_interface = MakeUnique<FontEngineInterfaceDefault>();
+		font_interface = core_data->default_font_interface.get();
 #else
 		Log::Message(Log::LT_ERROR, "No font engine interface set!");
 		return false;
@@ -130,15 +147,16 @@ bool Initialise()
 
 	if (!text_input_handler)
 	{
-		default_text_input_handler = MakeUnique<TextInputHandler>();
-		text_input_handler = default_text_input_handler.get();
+		core_data->default_text_input_handler = MakeUnique<TextInputHandler>();
+		text_input_handler = core_data->default_text_input_handler.get();
 	}
 
 	EventSpecificationInterface::Initialize();
 
-	render_managers = MakeUnique<SmallUnorderedMap<RenderInterface*, UniquePtr<RenderManager>>>();
+	Detail::InitializeObserverPtrPool();
+
 	if (render_interface)
-		(*render_managers)[render_interface] = MakeUnique<RenderManager>(render_interface);
+		core_data->render_managers[render_interface] = MakeUnique<RenderManager>(render_interface);
 
 	font_interface->Initialize();
 
@@ -171,7 +189,7 @@ void Shutdown()
 	RMLUI_ASSERTMSG(initialised, "Rml::Shutdown() called, but RmlUi is not initialised!");
 
 	// Clear out all contexts, which should also clean up all attached elements.
-	contexts.clear();
+	core_data->contexts.clear();
 
 	// Notify all plugins we're being shutdown.
 	PluginRegistry::NotifyShutdown();
@@ -184,19 +202,23 @@ void Shutdown()
 
 	font_interface->Shutdown();
 
-	render_managers.reset();
+	core_data->render_managers.clear();
+
+	Detail::ShutdownObserverPtrPool();
 
 	initialised = false;
 
+	text_input_handler = nullptr;
 	font_interface = nullptr;
 	render_interface = nullptr;
 	file_interface = nullptr;
 	system_interface = nullptr;
 
-	default_font_interface.reset();
-	default_file_interface.reset();
-	default_system_interface.reset();
+	core_data.Shutdown();
 
+	EventSpecificationInterface::Shutdown();
+
+	ShutdownComputeProperty();
 	ReleaseMemoryPools();
 }
 
@@ -281,7 +303,7 @@ Context* CreateContext(const String& name, const Vector2i dimensions, RenderInte
 	}
 
 	// Each unique render interface gets its own render manager.
-	auto& render_manager = (*render_managers)[render_interface_for_context];
+	auto& render_manager = core_data->render_managers[render_interface_for_context];
 	if (!render_manager)
 		render_manager = MakeUnique<RenderManager>(render_interface_for_context);
 
@@ -295,7 +317,7 @@ Context* CreateContext(const String& name, const Vector2i dimensions, RenderInte
 	new_context->SetDimensions(dimensions);
 
 	Context* new_context_raw = new_context.get();
-	contexts[name] = std::move(new_context);
+	core_data->contexts[name] = std::move(new_context);
 
 	PluginRegistry::NotifyContextCreate(new_context_raw);
 
@@ -304,47 +326,35 @@ Context* CreateContext(const String& name, const Vector2i dimensions, RenderInte
 
 bool RemoveContext(const String& name)
 {
-	auto it = contexts.find(name);
-	if (it != contexts.end())
-	{
-		contexts.erase(it);
-		return true;
-	}
-	return false;
+	return core_data->contexts.erase(name) != 0;
 }
 
 Context* GetContext(const String& name)
 {
-	ContextMap::iterator i = contexts.find(name);
-	if (i == contexts.end())
+	auto it = core_data->contexts.find(name);
+	if (it == core_data->contexts.end())
 		return nullptr;
 
-	return i->second.get();
+	return it->second.get();
 }
 
 Context* GetContext(int index)
 {
-	ContextMap::iterator i = contexts.begin();
-	int count = 0;
-
 	if (index < 0 || index >= GetNumContexts())
 		return nullptr;
 
-	while (count < index)
-	{
-		++i;
-		++count;
-	}
+	auto it = core_data->contexts.begin();
+	std::advance(it, index);
 
-	if (i == contexts.end())
+	if (it == core_data->contexts.end())
 		return nullptr;
 
-	return i->second.get();
+	return it->second.get();
 }
 
 int GetNumContexts()
 {
-	return (int)contexts.size();
+	return (int)core_data->contexts.size();
 }
 
 bool LoadFontFace(const String& file_path, bool fallback_face, Style::FontWeight weight)
@@ -381,9 +391,9 @@ EventId RegisterEventType(const String& type, bool interruptible, bool bubbles,
 StringList GetTextureSourceList()
 {
 	StringList result;
-	if (!render_managers)
+	if (!core_data)
 		return result;
-	for (const auto& render_manager : *render_managers)
+	for (const auto& render_manager : core_data->render_managers)
 	{
 		RenderManagerAccess::GetTextureSourceList(render_manager.second.get(), result);
 	}
@@ -392,9 +402,9 @@ StringList GetTextureSourceList()
 
 void ReleaseTextures(RenderInterface* match_render_interface)
 {
-	if (!render_managers)
+	if (!core_data)
 		return;
-	for (auto& render_manager : *render_managers)
+	for (auto& render_manager : core_data->render_managers)
 	{
 		if (!match_render_interface || render_manager.first == match_render_interface)
 			RenderManagerAccess::ReleaseAllTextures(render_manager.second.get());
@@ -403,10 +413,10 @@ void ReleaseTextures(RenderInterface* match_render_interface)
 
 bool ReleaseTexture(const String& source, RenderInterface* match_render_interface)
 {
-	if (!render_managers)
-		return false;
 	bool result = false;
-	for (auto& render_manager : *render_managers)
+	if (!core_data)
+		return result;
+	for (auto& render_manager : core_data->render_managers)
 	{
 		if (!match_render_interface || render_manager.first == match_render_interface)
 		{
@@ -419,9 +429,9 @@ bool ReleaseTexture(const String& source, RenderInterface* match_render_interfac
 
 void ReleaseCompiledGeometry(RenderInterface* match_render_interface)
 {
-	if (!render_managers)
+	if (!core_data)
 		return;
-	for (auto& render_manager : *render_managers)
+	for (auto& render_manager : core_data->render_managers)
 	{
 		if (!match_render_interface || render_manager.first == match_render_interface)
 			RenderManagerAccess::ReleaseAllCompiledGeometry(render_manager.second.get());
@@ -430,25 +440,16 @@ void ReleaseCompiledGeometry(RenderInterface* match_render_interface)
 
 void ReleaseFontResources()
 {
-	if (font_interface)
-	{
-		for (const auto& name_context : contexts)
-			name_context.second->GetRootElement()->DirtyFontFaceRecursive();
+	if (!font_interface)
+		return;
 
-		font_interface->ReleaseFontResources();
+	for (const auto& name_context : core_data->contexts)
+		name_context.second->GetRootElement()->DirtyFontFaceRecursive();
 
-		for (const auto& name_context : contexts)
-			name_context.second->Update();
-	}
-}
+	font_interface->ReleaseFontResources();
 
-void ReleaseMemoryPools()
-{
-	if (observerPtrBlockPool && observerPtrBlockPool->GetNumAllocatedObjects() <= 0)
-	{
-		delete observerPtrBlockPool;
-		observerPtrBlockPool = nullptr;
-	}
+	for (const auto& name_context : core_data->contexts)
+		name_context.second->Update();
 }
 
 // Functions that need to be accessible within the Core library, but not publicly.
@@ -456,7 +457,7 @@ namespace CoreInternal {
 
 	bool HasRenderManager(RenderInterface* match_render_interface)
 	{
-		return render_managers && render_managers->find(match_render_interface) != render_managers->end();
+		return core_data && core_data->render_managers.find(match_render_interface) != core_data->render_managers.end();
 	}
 
 } // namespace CoreInternal

+ 3 - 16
Source/Core/Element.cpp

@@ -49,6 +49,7 @@
 #include "ElementBackgroundBorder.h"
 #include "ElementDefinition.h"
 #include "ElementEffects.h"
+#include "ElementMeta.h"
 #include "ElementStyle.h"
 #include "EventDispatcher.h"
 #include "EventSpecification.h"
@@ -90,20 +91,6 @@ static float GetScrollOffsetDelta(ScrollAlignment alignment, float begin_offset,
 	return 0.f;
 }
 
-// Meta objects for element collected in a single struct to reduce memory allocations
-struct ElementMeta {
-	ElementMeta(Element* el) : event_dispatcher(el), style(el), background_border(), effects(el), scroll(el), computed_values(el) {}
-	SmallUnorderedMap<EventId, EventListener*> attribute_event_listeners;
-	EventDispatcher event_dispatcher;
-	ElementStyle style;
-	ElementBackgroundBorder background_border;
-	ElementEffects effects;
-	ElementScroll scroll;
-	Style::ComputedValues computed_values;
-};
-
-static Pool<ElementMeta> element_meta_chunk_pool(200, true);
-
 Element::Element(const String& tag) :
 	local_stacking_context(false), local_stacking_context_forced(false), stacking_context_dirty(false), computed_values_are_default_initialized(true),
 	visible(true), offset_fixed(false), absolute_offset_dirty(true), dirty_definition(false), dirty_child_definitions(false), dirty_animation(false),
@@ -125,7 +112,7 @@ Element::Element(const String& tag) :
 
 	z_index = 0;
 
-	meta = element_meta_chunk_pool.AllocateAndConstruct(this);
+	meta = ElementMetaPool::element_meta_pool->pool.AllocateAndConstruct(this);
 	data_model = nullptr;
 }
 
@@ -148,7 +135,7 @@ Element::~Element()
 	children.clear();
 	num_non_dom_children = 0;
 
-	element_meta_chunk_pool.DestroyAndDeallocate(meta);
+	ElementMetaPool::element_meta_pool->pool.DestroyAndDeallocate(meta);
 }
 
 void Element::Update(float dp_ratio, Vector2f vp_dimensions)

+ 27 - 8
Source/Core/ElementInstancer.cpp

@@ -28,6 +28,7 @@
 
 #include "../../Include/RmlUi/Core/ElementInstancer.h"
 #include "../../Include/RmlUi/Core/ElementText.h"
+#include "ControlledLifetimeResource.h"
 #include "Pool.h"
 #include "XMLParseTools.h"
 
@@ -35,28 +36,33 @@ namespace Rml {
 
 ElementInstancer::~ElementInstancer() {}
 
-static Pool<Element> pool_element(200, true);
-static Pool<ElementText> pool_text_default(200, true);
+struct ElementInstancerPools {
+	Pool<Element> pool_element{200, true};
+	Pool<ElementText> pool_text_default{200, true};
+
+	bool IsEmpty() const { return pool_element.GetNumAllocatedObjects() == 0 && pool_text_default.GetNumAllocatedObjects() == 0; }
+};
+static ControlledLifetimeResource<ElementInstancerPools> element_instancer_pools;
 
 ElementPtr ElementInstancerElement::InstanceElement(Element* /*parent*/, const String& tag, const XMLAttributes& /*attributes*/)
 {
-	Element* ptr = pool_element.AllocateAndConstruct(tag);
+	Element* ptr = element_instancer_pools->pool_element.AllocateAndConstruct(tag);
 	return ElementPtr(ptr);
 }
 
 void ElementInstancerElement::ReleaseElement(Element* element)
 {
-	pool_element.DestroyAndDeallocate(element);
+	element_instancer_pools->pool_element.DestroyAndDeallocate(element);
 }
 
 ElementInstancerElement::~ElementInstancerElement()
 {
-	int num_elements = pool_element.GetNumAllocatedObjects();
+	int num_elements = element_instancer_pools->pool_element.GetNumAllocatedObjects();
 	if (num_elements > 0)
 	{
 		Log::Message(Log::LT_WARNING, "--- Found %d leaked element(s) ---", num_elements);
 
-		for (auto it = pool_element.Begin(); it; ++it)
+		for (auto it = element_instancer_pools->pool_element.Begin(); it; ++it)
 			Log::Message(Log::LT_WARNING, "    %s", it->GetAddress().c_str());
 
 		Log::Message(Log::LT_WARNING, "------");
@@ -65,13 +71,26 @@ ElementInstancerElement::~ElementInstancerElement()
 
 ElementPtr ElementInstancerText::InstanceElement(Element* /*parent*/, const String& tag, const XMLAttributes& /*attributes*/)
 {
-	ElementText* ptr = pool_text_default.AllocateAndConstruct(tag);
+	ElementText* ptr = element_instancer_pools->pool_text_default.AllocateAndConstruct(tag);
 	return ElementPtr(static_cast<Element*>(ptr));
 }
 
 void ElementInstancerText::ReleaseElement(Element* element)
 {
-	pool_text_default.DestroyAndDeallocate(rmlui_static_cast<ElementText*>(element));
+	element_instancer_pools->pool_text_default.DestroyAndDeallocate(rmlui_static_cast<ElementText*>(element));
+}
+
+void Detail::InitializeElementInstancerPools()
+{
+	element_instancer_pools.InitializeIfEmpty();
+}
+
+void Detail::ShutdownElementInstancerPools()
+{
+	if (element_instancer_pools->IsEmpty())
+		element_instancer_pools.Shutdown();
+	else
+		element_instancer_pools.Leak();
 }
 
 } // namespace Rml

+ 54 - 0
Source/Core/ElementMeta.cpp

@@ -0,0 +1,54 @@
+/*
+ * 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-2023 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 "ElementMeta.h"
+
+namespace Rml {
+
+ControlledLifetimeResource<ElementMetaPool> ElementMetaPool::element_meta_pool;
+
+void ElementMetaPool::Initialize()
+{
+	element_meta_pool.InitializeIfEmpty();
+}
+
+void ElementMetaPool::Shutdown()
+{
+	const int num_objects = element_meta_pool->pool.GetNumAllocatedObjects();
+	if (num_objects == 0)
+	{
+		element_meta_pool.Shutdown();
+	}
+	else
+	{
+		Log::Message(Log::LT_WARNING, "Element meta pool not empty on shutdown, %d object(s) leaked.", num_objects);
+		element_meta_pool.Leak();
+	}
+}
+
+} // namespace Rml

+ 67 - 0
Source/Core/ElementMeta.h

@@ -0,0 +1,67 @@
+/*
+ * 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-2023 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 RMLUI_CORE_ELEMENTMETA_H
+#define RMLUI_CORE_ELEMENTMETA_H
+
+#include "../../Include/RmlUi/Core/ComputedValues.h"
+#include "../../Include/RmlUi/Core/Element.h"
+#include "../../Include/RmlUi/Core/ElementScroll.h"
+#include "../../Include/RmlUi/Core/Traits.h"
+#include "../../Include/RmlUi/Core/Types.h"
+#include "ControlledLifetimeResource.h"
+#include "ElementBackgroundBorder.h"
+#include "ElementEffects.h"
+#include "ElementStyle.h"
+#include "EventDispatcher.h"
+#include "Pool.h"
+
+namespace Rml {
+
+// Meta objects for element collected in a single struct to reduce memory allocations
+struct ElementMeta {
+	explicit ElementMeta(Element* el) : event_dispatcher(el), style(el), background_border(), effects(el), scroll(el), computed_values(el) {}
+	SmallUnorderedMap<EventId, EventListener*> attribute_event_listeners;
+	EventDispatcher event_dispatcher;
+	ElementStyle style;
+	ElementBackgroundBorder background_border;
+	ElementEffects effects;
+	ElementScroll scroll;
+	Style::ComputedValues computed_values;
+};
+
+struct ElementMetaPool {
+	Pool<ElementMeta> pool{50, true};
+
+	static ControlledLifetimeResource<ElementMetaPool> element_meta_pool;
+	static void Initialize();
+	static void Shutdown();
+};
+
+} // namespace Rml
+#endif

+ 5 - 5
Source/Core/ElementStyle.cpp

@@ -364,7 +364,7 @@ static float ComputeLength(NumericValue value, Element* element)
 		if (ElementDocument* document = element->GetOwnerDocument())
 			doc_font_size = document->GetComputedValues().font_size();
 		else
-			doc_font_size = DefaultComputedValues.font_size();
+			doc_font_size = DefaultComputedValues().font_size();
 		break;
 	case Unit::VW:
 	case Unit::VH:
@@ -419,7 +419,7 @@ float ElementStyle::ResolveRelativeLength(NumericValue value, RelativeTarget rel
 	case RelativeTarget::ParentFontSize:
 	{
 		auto p = element->GetParentNode();
-		base_value = (p ? p->GetComputedValues().font_size() : DefaultComputedValues.font_size());
+		base_value = (p ? p->GetComputedValues().font_size() : DefaultComputedValues().font_size());
 	}
 	break;
 	case RelativeTarget::LineHeight: base_value = element->GetLineHeight(); break;
@@ -530,13 +530,13 @@ PropertyIdSet ElementStyle::ComputeValues(Style::ComputedValues& values, const S
 		// If we skipped this, the old dirty value would be unmodified, instead, now it is set to its default value.
 		// Strictly speaking, we only really need to do this for the dirty, non-inherited values. However, in most
 		// cases it seems simply assigning all non-inherited values is faster than iterating the dirty properties.
-		values.CopyNonInherited(DefaultComputedValues);
+		values.CopyNonInherited(DefaultComputedValues());
 	}
 
 	if (parent_values)
 		values.CopyInherited(*parent_values);
 	else if (!values_are_default_initialized)
-		values.CopyInherited(DefaultComputedValues);
+		values.CopyInherited(DefaultComputedValues());
 
 	bool dirty_em_properties = false;
 
@@ -560,7 +560,7 @@ PropertyIdSet ElementStyle::ComputeValues(Style::ComputedValues& values, const S
 	}
 
 	const float font_size = values.font_size();
-	const float document_font_size = (document_values ? document_values->font_size() : DefaultComputedValues.font_size());
+	const float document_font_size = (document_values ? document_values->font_size() : DefaultComputedValues().font_size());
 
 	// Since vertical-align depends on line-height we compute this before iteration
 	if (dirty_properties.Contains(PropertyId::LineHeight))

+ 65 - 47
Source/Core/EventSpecification.cpp

@@ -28,61 +28,66 @@
 
 #include "EventSpecification.h"
 #include "../../Include/RmlUi/Core/ID.h"
+#include "ControlledLifetimeResource.h"
 
 namespace Rml {
 
-// An EventId is an index into the specifications vector.
-static Vector<EventSpecification> specifications = {{EventId::Invalid, "invalid", false, false, DefaultActionPhase::None}};
-
-// Reverse lookup map from event type to id.
-static UnorderedMap<String, EventId> type_lookup;
+struct EventSpecificationData {
+	// An EventId is an index into the specifications vector, must be listed in the same order as the EventId values.
+	Vector<EventSpecification> specifications = {
+		// clang-format off
+		//      id                 type      interruptible  bubbles     default_action
+		{EventId::Invalid       , "invalid"       , false , false , DefaultActionPhase::None},
+		{EventId::Mousedown     , "mousedown"     , true  , true  , DefaultActionPhase::TargetAndBubble},
+		{EventId::Mousescroll   , "mousescroll"   , true  , true  , DefaultActionPhase::None},
+		{EventId::Mouseover     , "mouseover"     , true  , true  , DefaultActionPhase::Target},
+		{EventId::Mouseout      , "mouseout"      , true  , true  , DefaultActionPhase::Target},
+		{EventId::Focus         , "focus"         , false , false , DefaultActionPhase::Target},
+		{EventId::Blur          , "blur"          , false , false , DefaultActionPhase::Target},
+		{EventId::Keydown       , "keydown"       , true  , true  , DefaultActionPhase::TargetAndBubble},
+		{EventId::Keyup         , "keyup"         , true  , true  , DefaultActionPhase::TargetAndBubble},
+		{EventId::Textinput     , "textinput"     , true  , true  , DefaultActionPhase::TargetAndBubble},
+		{EventId::Mouseup       , "mouseup"       , true  , true  , DefaultActionPhase::TargetAndBubble},
+		{EventId::Click         , "click"         , true  , true  , DefaultActionPhase::TargetAndBubble},
+		{EventId::Dblclick      , "dblclick"      , true  , true  , DefaultActionPhase::TargetAndBubble},
+		{EventId::Load          , "load"          , false , false , DefaultActionPhase::None},
+		{EventId::Unload        , "unload"        , false , false , DefaultActionPhase::None},
+		{EventId::Show          , "show"          , false , false , DefaultActionPhase::None},
+		{EventId::Hide          , "hide"          , false , false , DefaultActionPhase::None},
+		{EventId::Mousemove     , "mousemove"     , true  , true  , DefaultActionPhase::None},
+		{EventId::Dragmove      , "dragmove"      , true  , true  , DefaultActionPhase::None},
+		{EventId::Drag          , "drag"          , false , true  , DefaultActionPhase::Target},
+		{EventId::Dragstart     , "dragstart"     , false , true  , DefaultActionPhase::Target},
+		{EventId::Dragover      , "dragover"      , true  , true  , DefaultActionPhase::None},
+		{EventId::Dragdrop      , "dragdrop"      , true  , true  , DefaultActionPhase::None},
+		{EventId::Dragout       , "dragout"       , true  , true  , DefaultActionPhase::None},
+		{EventId::Dragend       , "dragend"       , true  , true  , DefaultActionPhase::None},
+		{EventId::Handledrag    , "handledrag"    , false , true  , DefaultActionPhase::None},
+		{EventId::Resize        , "resize"        , false , false , DefaultActionPhase::None},
+		{EventId::Scroll        , "scroll"        , false , true  , DefaultActionPhase::None},
+		{EventId::Animationend  , "animationend"  , false , true  , DefaultActionPhase::None},
+		{EventId::Transitionend , "transitionend" , false , true  , DefaultActionPhase::None},
+		{EventId::Change        , "change"        , false , true  , DefaultActionPhase::None},
+		{EventId::Submit        , "submit"        , true  , true  , DefaultActionPhase::None},
+		{EventId::Tabchange     , "tabchange"     , false , true  , DefaultActionPhase::None},
+		// clang-format on
+	};
+
+	// Reverse lookup map from event type to id.
+	UnorderedMap<String, EventId> type_lookup;
+};
+
+static ControlledLifetimeResource<EventSpecificationData> event_specification_data;
 
 namespace EventSpecificationInterface {
 
 	void Initialize()
 	{
-		// Must be specified in the same order as in EventId
-		specifications = {
-			//      id                 type      interruptible  bubbles     default_action
-			// clang-format off
-			{EventId::Invalid       , "invalid"       , false , false , DefaultActionPhase::None},
-			{EventId::Mousedown     , "mousedown"     , true  , true  , DefaultActionPhase::TargetAndBubble},
-			{EventId::Mousescroll   , "mousescroll"   , true  , true  , DefaultActionPhase::None},
-			{EventId::Mouseover     , "mouseover"     , true  , true  , DefaultActionPhase::Target},
-			{EventId::Mouseout      , "mouseout"      , true  , true  , DefaultActionPhase::Target},
-			{EventId::Focus         , "focus"         , false , false , DefaultActionPhase::Target},
-			{EventId::Blur          , "blur"          , false , false , DefaultActionPhase::Target},
-			{EventId::Keydown       , "keydown"       , true  , true  , DefaultActionPhase::TargetAndBubble},
-			{EventId::Keyup         , "keyup"         , true  , true  , DefaultActionPhase::TargetAndBubble},
-			{EventId::Textinput     , "textinput"     , true  , true  , DefaultActionPhase::TargetAndBubble},
-			{EventId::Mouseup       , "mouseup"       , true  , true  , DefaultActionPhase::TargetAndBubble},
-			{EventId::Click         , "click"         , true  , true  , DefaultActionPhase::TargetAndBubble},
-			{EventId::Dblclick      , "dblclick"      , true  , true  , DefaultActionPhase::TargetAndBubble},
-			{EventId::Load          , "load"          , false , false , DefaultActionPhase::None},
-			{EventId::Unload        , "unload"        , false , false , DefaultActionPhase::None},
-			{EventId::Show          , "show"          , false , false , DefaultActionPhase::None},
-			{EventId::Hide          , "hide"          , false , false , DefaultActionPhase::None},
-			{EventId::Mousemove     , "mousemove"     , true  , true  , DefaultActionPhase::None},
-			{EventId::Dragmove      , "dragmove"      , true  , true  , DefaultActionPhase::None},
-			{EventId::Drag          , "drag"          , false , true  , DefaultActionPhase::Target},
-			{EventId::Dragstart     , "dragstart"     , false , true  , DefaultActionPhase::Target},
-			{EventId::Dragover      , "dragover"      , true  , true  , DefaultActionPhase::None},
-			{EventId::Dragdrop      , "dragdrop"      , true  , true  , DefaultActionPhase::None},
-			{EventId::Dragout       , "dragout"       , true  , true  , DefaultActionPhase::None},
-			{EventId::Dragend       , "dragend"       , true  , true  , DefaultActionPhase::None},
-			{EventId::Handledrag    , "handledrag"    , false , true  , DefaultActionPhase::None},
-			{EventId::Resize        , "resize"        , false , false , DefaultActionPhase::None},
-			{EventId::Scroll        , "scroll"        , false , true  , DefaultActionPhase::None},
-			{EventId::Animationend  , "animationend"  , false , true  , DefaultActionPhase::None},
-			{EventId::Transitionend , "transitionend" , false , true  , DefaultActionPhase::None},
-
-			{EventId::Change        , "change"        , false , true  , DefaultActionPhase::None},
-			{EventId::Submit        , "submit"        , true  , true  , DefaultActionPhase::None},
-			{EventId::Tabchange     , "tabchange"     , false , true  , DefaultActionPhase::None},
-			// clang-format on
-		};
-
-		type_lookup.clear();
+		event_specification_data.Initialize();
+
+		auto& specifications = event_specification_data->specifications;
+		auto& type_lookup = event_specification_data->type_lookup;
+
 		type_lookup.reserve(specifications.size());
 		for (auto& specification : specifications)
 			type_lookup.emplace(specification.type, specification.id);
@@ -99,8 +104,14 @@ namespace EventSpecificationInterface {
 #endif
 	}
 
+	void Shutdown()
+	{
+		event_specification_data.Shutdown();
+	}
+
 	static EventSpecification& GetMutable(EventId id)
 	{
+		auto& specifications = event_specification_data->specifications;
 		size_t i = static_cast<size_t>(id);
 		if (i < specifications.size())
 			return specifications[i];
@@ -111,6 +122,9 @@ namespace EventSpecificationInterface {
 	// If not found: Inserts a new entry with given values.
 	static EventSpecification& GetOrInsert(const String& event_type, bool interruptible, bool bubbles, DefaultActionPhase default_action_phase)
 	{
+		auto& specifications = event_specification_data->specifications;
+		auto& type_lookup = event_specification_data->type_lookup;
+
 		auto it = type_lookup.find(event_type);
 
 		if (it != type_lookup.end())
@@ -148,6 +162,8 @@ namespace EventSpecificationInterface {
 
 	EventId GetIdOrInsert(const String& event_type)
 	{
+		auto& type_lookup = event_specification_data->type_lookup;
+
 		auto it = type_lookup.find(event_type);
 		if (it != type_lookup.end())
 			return it->second;
@@ -157,6 +173,8 @@ namespace EventSpecificationInterface {
 
 	EventId InsertOrReplaceCustom(const String& event_type, bool interruptible, bool bubbles, DefaultActionPhase default_action_phase)
 	{
+		auto& specifications = event_specification_data->specifications;
+
 		const size_t size_before = specifications.size();
 		EventSpecification& specification = GetOrInsert(event_type, interruptible, bubbles, default_action_phase);
 		bool got_existing_entry = (size_before == specifications.size());

+ 1 - 0
Source/Core/EventSpecification.h

@@ -46,6 +46,7 @@ struct EventSpecification {
 namespace EventSpecificationInterface {
 
 	void Initialize();
+	void Shutdown();
 
 	// Get event specification for the given id.
 	// Returns the 'invalid' event type if no specification exists for id.

+ 114 - 150
Source/Core/Factory.cpp

@@ -46,6 +46,7 @@
 #include "../../Include/RmlUi/Core/StyleSheetContainer.h"
 #include "../../Include/RmlUi/Core/SystemInterface.h"
 #include "ContextInstancerDefault.h"
+#include "ControlledLifetimeResource.h"
 #include "DataControllerDefault.h"
 #include "DataViewDefault.h"
 #include "DecoratorGradient.h"
@@ -83,46 +84,6 @@
 
 namespace Rml {
 
-// Element instancers.
-using ElementInstancerMap = UnorderedMap<String, ElementInstancer*>;
-static ElementInstancerMap element_instancers;
-
-// Decorator instancers.
-using DecoratorInstancerMap = UnorderedMap<String, DecoratorInstancer*>;
-static DecoratorInstancerMap decorator_instancers;
-
-// Filter instancers.
-using FilterInstancerMap = UnorderedMap<String, FilterInstancer*>;
-static FilterInstancerMap filter_instancers;
-
-// Font effect instancers.
-using FontEffectInstancerMap = UnorderedMap<String, FontEffectInstancer*>;
-static FontEffectInstancerMap font_effect_instancers;
-
-// Data view instancers.
-using DataViewInstancerMap = UnorderedMap<String, DataViewInstancer*>;
-static DataViewInstancerMap data_view_instancers;
-
-// Data controller instancers.
-using DataControllerInstancerMap = UnorderedMap<String, DataControllerInstancer*>;
-static DataControllerInstancerMap data_controller_instancers;
-
-// Structural data view instancers.
-using StructuralDataViewInstancerMap = SmallUnorderedMap<String, DataViewInstancer*>;
-static StructuralDataViewInstancerMap structural_data_view_instancers;
-
-// Structural data view names.
-static StringList structural_data_view_attribute_names;
-
-// The context instancer.
-static ContextInstancer* context_instancer = nullptr;
-
-// The event instancer
-static EventInstancer* event_instancer = nullptr;
-
-// Event listener instancer.
-static EventListenerInstancer* event_listener_instancer = nullptr;
-
 // Default instancers are constructed and destroyed on Initialise and Shutdown, respectively.
 struct DefaultInstancers {
 	UniquePtr<ContextInstancer> context_default;
@@ -192,28 +153,46 @@ struct DefaultInstancers {
 	DataControllerInstancerDefault<DataControllerValue> data_controller_value;
 };
 
-static UniquePtr<DefaultInstancers> default_instancers;
+struct FactoryData {
+	DefaultInstancers default_instancers;
+	UnorderedMap<String, ElementInstancer*> element_instancers;
+	UnorderedMap<String, DecoratorInstancer*> decorator_instancers;
+	UnorderedMap<String, FilterInstancer*> filter_instancers;
+	UnorderedMap<String, FontEffectInstancer*> font_effect_instancers;
+	UnorderedMap<String, DataViewInstancer*> data_view_instancers;
+	UnorderedMap<String, DataControllerInstancer*> data_controller_instancers;
+	SmallUnorderedMap<String, DataViewInstancer*> structural_data_view_instancers;
+	StringList structural_data_view_attribute_names;
+};
+
+static ControlledLifetimeResource<FactoryData> factory_data;
+
+static ContextInstancer* context_instancer = nullptr;
+static EventInstancer* event_instancer = nullptr;
+static EventListenerInstancer* event_listener_instancer = nullptr;
 
 Factory::Factory() {}
 
 Factory::~Factory() {}
 
-bool Factory::Initialise()
+void Factory::Initialise()
 {
-	default_instancers = MakeUnique<DefaultInstancers>();
+	factory_data.Initialize();
+
+	DefaultInstancers& default_instancers = factory_data->default_instancers;
 
 	// Default context instancer
 	if (!context_instancer)
 	{
-		default_instancers->context_default = MakeUnique<ContextInstancerDefault>();
-		context_instancer = default_instancers->context_default.get();
+		default_instancers.context_default = MakeUnique<ContextInstancerDefault>();
+		context_instancer = default_instancers.context_default.get();
 	}
 
 	// Default event instancer
 	if (!event_instancer)
 	{
-		default_instancers->event_default = MakeUnique<EventInstancerDefault>();
-		event_instancer = default_instancers->event_default.get();
+		default_instancers.event_default = MakeUnique<EventInstancerDefault>();
+		event_instancer = default_instancers.event_default.get();
 	}
 
 	// No default event listener instancer
@@ -221,83 +200,83 @@ bool Factory::Initialise()
 		event_listener_instancer = nullptr;
 
 	// Basic element instancers
-	RegisterElementInstancer("*", &default_instancers->element_default);
-	RegisterElementInstancer("img", &default_instancers->element_img);
-	RegisterElementInstancer("#text", &default_instancers->element_text);
-	RegisterElementInstancer("handle", &default_instancers->element_handle);
-	RegisterElementInstancer("body", &default_instancers->element_body);
+	RegisterElementInstancer("*", &default_instancers.element_default);
+	RegisterElementInstancer("img", &default_instancers.element_img);
+	RegisterElementInstancer("#text", &default_instancers.element_text);
+	RegisterElementInstancer("handle", &default_instancers.element_handle);
+	RegisterElementInstancer("body", &default_instancers.element_body);
 
 	// Control element instancers
-	RegisterElementInstancer("form", &default_instancers->form);
-	RegisterElementInstancer("input", &default_instancers->input);
-	RegisterElementInstancer("select", &default_instancers->select);
-	RegisterElementInstancer("label", &default_instancers->element_label);
+	RegisterElementInstancer("form", &default_instancers.form);
+	RegisterElementInstancer("input", &default_instancers.input);
+	RegisterElementInstancer("select", &default_instancers.select);
+	RegisterElementInstancer("label", &default_instancers.element_label);
 
-	RegisterElementInstancer("textarea", &default_instancers->textarea);
-	RegisterElementInstancer("#selection", &default_instancers->selection);
-	RegisterElementInstancer("tabset", &default_instancers->tabset);
+	RegisterElementInstancer("textarea", &default_instancers.textarea);
+	RegisterElementInstancer("#selection", &default_instancers.selection);
+	RegisterElementInstancer("tabset", &default_instancers.tabset);
 
-	RegisterElementInstancer("progress", &default_instancers->progress);
-	RegisterElementInstancer("progressbar", &default_instancers->progress);
+	RegisterElementInstancer("progress", &default_instancers.progress);
+	RegisterElementInstancer("progressbar", &default_instancers.progress);
 
 	// Decorator instancers
-	RegisterDecoratorInstancer("tiled-horizontal", &default_instancers->decorator_tiled_horizontal);
-	RegisterDecoratorInstancer("tiled-vertical", &default_instancers->decorator_tiled_vertical);
-	RegisterDecoratorInstancer("tiled-box", &default_instancers->decorator_tiled_box);
-	RegisterDecoratorInstancer("image", &default_instancers->decorator_image);
-	RegisterDecoratorInstancer("ninepatch", &default_instancers->decorator_ninepatch);
-	RegisterDecoratorInstancer("shader", &default_instancers->decorator_shader);
-
-	RegisterDecoratorInstancer("gradient", &default_instancers->decorator_straight_gradient);
-	RegisterDecoratorInstancer("horizontal-gradient", &default_instancers->decorator_straight_gradient);
-	RegisterDecoratorInstancer("vertical-gradient", &default_instancers->decorator_straight_gradient);
-
-	RegisterDecoratorInstancer("linear-gradient", &default_instancers->decorator_linear_gradient);
-	RegisterDecoratorInstancer("repeating-linear-gradient", &default_instancers->decorator_linear_gradient);
-	RegisterDecoratorInstancer("radial-gradient", &default_instancers->decorator_radial_gradient);
-	RegisterDecoratorInstancer("repeating-radial-gradient", &default_instancers->decorator_radial_gradient);
-	RegisterDecoratorInstancer("conic-gradient", &default_instancers->decorator_conic_gradient);
-	RegisterDecoratorInstancer("repeating-conic-gradient", &default_instancers->decorator_conic_gradient);
+	RegisterDecoratorInstancer("tiled-horizontal", &default_instancers.decorator_tiled_horizontal);
+	RegisterDecoratorInstancer("tiled-vertical", &default_instancers.decorator_tiled_vertical);
+	RegisterDecoratorInstancer("tiled-box", &default_instancers.decorator_tiled_box);
+	RegisterDecoratorInstancer("image", &default_instancers.decorator_image);
+	RegisterDecoratorInstancer("ninepatch", &default_instancers.decorator_ninepatch);
+	RegisterDecoratorInstancer("shader", &default_instancers.decorator_shader);
+
+	RegisterDecoratorInstancer("gradient", &default_instancers.decorator_straight_gradient);
+	RegisterDecoratorInstancer("horizontal-gradient", &default_instancers.decorator_straight_gradient);
+	RegisterDecoratorInstancer("vertical-gradient", &default_instancers.decorator_straight_gradient);
+
+	RegisterDecoratorInstancer("linear-gradient", &default_instancers.decorator_linear_gradient);
+	RegisterDecoratorInstancer("repeating-linear-gradient", &default_instancers.decorator_linear_gradient);
+	RegisterDecoratorInstancer("radial-gradient", &default_instancers.decorator_radial_gradient);
+	RegisterDecoratorInstancer("repeating-radial-gradient", &default_instancers.decorator_radial_gradient);
+	RegisterDecoratorInstancer("conic-gradient", &default_instancers.decorator_conic_gradient);
+	RegisterDecoratorInstancer("repeating-conic-gradient", &default_instancers.decorator_conic_gradient);
 
 	// Filter instancers
-	RegisterFilterInstancer("hue-rotate", &default_instancers->filter_hue_rotate);
-	RegisterFilterInstancer("brightness", &default_instancers->filter_basic_d1);
-	RegisterFilterInstancer("contrast", &default_instancers->filter_basic_d1);
-	RegisterFilterInstancer("grayscale", &default_instancers->filter_basic_d0);
-	RegisterFilterInstancer("invert", &default_instancers->filter_basic_d0);
-	RegisterFilterInstancer("opacity", &default_instancers->filter_basic_d1);
-	RegisterFilterInstancer("saturate", &default_instancers->filter_basic_d1);
-	RegisterFilterInstancer("sepia", &default_instancers->filter_basic_d0);
-
-	RegisterFilterInstancer("blur", &default_instancers->filter_blur);
-	RegisterFilterInstancer("drop-shadow", &default_instancers->filter_drop_shadow);
+	RegisterFilterInstancer("hue-rotate", &default_instancers.filter_hue_rotate);
+	RegisterFilterInstancer("brightness", &default_instancers.filter_basic_d1);
+	RegisterFilterInstancer("contrast", &default_instancers.filter_basic_d1);
+	RegisterFilterInstancer("grayscale", &default_instancers.filter_basic_d0);
+	RegisterFilterInstancer("invert", &default_instancers.filter_basic_d0);
+	RegisterFilterInstancer("opacity", &default_instancers.filter_basic_d1);
+	RegisterFilterInstancer("saturate", &default_instancers.filter_basic_d1);
+	RegisterFilterInstancer("sepia", &default_instancers.filter_basic_d0);
+
+	RegisterFilterInstancer("blur", &default_instancers.filter_blur);
+	RegisterFilterInstancer("drop-shadow", &default_instancers.filter_drop_shadow);
 
 	// Font effect instancers
-	RegisterFontEffectInstancer("blur", &default_instancers->font_effect_blur);
-	RegisterFontEffectInstancer("glow", &default_instancers->font_effect_glow);
-	RegisterFontEffectInstancer("outline", &default_instancers->font_effect_outline);
-	RegisterFontEffectInstancer("shadow", &default_instancers->font_effect_shadow);
+	RegisterFontEffectInstancer("blur", &default_instancers.font_effect_blur);
+	RegisterFontEffectInstancer("glow", &default_instancers.font_effect_glow);
+	RegisterFontEffectInstancer("outline", &default_instancers.font_effect_outline);
+	RegisterFontEffectInstancer("shadow", &default_instancers.font_effect_shadow);
 
 	// Data binding views
 	// clang-format off
-	RegisterDataViewInstancer(&default_instancers->data_view_attribute,      "attr",    false);
-	RegisterDataViewInstancer(&default_instancers->data_view_attribute_if,   "attrif",  false);
-	RegisterDataViewInstancer(&default_instancers->data_view_class,          "class",   false);
-	RegisterDataViewInstancer(&default_instancers->data_view_if,             "if",      false);
-	RegisterDataViewInstancer(&default_instancers->data_view_visible,        "visible", false);
-	RegisterDataViewInstancer(&default_instancers->data_view_rml,            "rml",     false);
-	RegisterDataViewInstancer(&default_instancers->data_view_style,          "style",   false);
-	RegisterDataViewInstancer(&default_instancers->data_view_text,           "text",    false);
-	RegisterDataViewInstancer(&default_instancers->data_view_value,          "value",   false);
-	RegisterDataViewInstancer(&default_instancers->data_view_checked,        "checked", false);
-	RegisterDataViewInstancer(&default_instancers->data_view_alias,          "alias",   false);
-	RegisterDataViewInstancer(&default_instancers->structural_data_view_for, "for",     true );
+	RegisterDataViewInstancer(&default_instancers.data_view_attribute,      "attr",    false);
+	RegisterDataViewInstancer(&default_instancers.data_view_attribute_if,   "attrif",  false);
+	RegisterDataViewInstancer(&default_instancers.data_view_class,          "class",   false);
+	RegisterDataViewInstancer(&default_instancers.data_view_if,             "if",      false);
+	RegisterDataViewInstancer(&default_instancers.data_view_visible,        "visible", false);
+	RegisterDataViewInstancer(&default_instancers.data_view_rml,            "rml",     false);
+	RegisterDataViewInstancer(&default_instancers.data_view_style,          "style",   false);
+	RegisterDataViewInstancer(&default_instancers.data_view_text,           "text",    false);
+	RegisterDataViewInstancer(&default_instancers.data_view_value,          "value",   false);
+	RegisterDataViewInstancer(&default_instancers.data_view_checked,        "checked", false);
+	RegisterDataViewInstancer(&default_instancers.data_view_alias,          "alias",   false);
+	RegisterDataViewInstancer(&default_instancers.structural_data_view_for, "for",     true );
 	// clang-format on
 
 	// Data binding controllers
-	RegisterDataControllerInstancer(&default_instancers->data_controller_value, "checked");
-	RegisterDataControllerInstancer(&default_instancers->data_controller_event, "event");
-	RegisterDataControllerInstancer(&default_instancers->data_controller_value, "value");
+	RegisterDataControllerInstancer(&default_instancers.data_controller_value, "checked");
+	RegisterDataControllerInstancer(&default_instancers.data_controller_event, "event");
+	RegisterDataControllerInstancer(&default_instancers.data_controller_value, "value");
 
 	// XML node handlers
 	XMLParser::RegisterNodeHandler("", MakeShared<XMLNodeHandlerDefault>());
@@ -309,32 +288,17 @@ bool Factory::Initialise()
 	XMLParser::RegisterNodeHandler("tabset", MakeShared<XMLNodeHandlerTabSet>());
 	XMLParser::RegisterNodeHandler("textarea", MakeShared<XMLNodeHandlerTextArea>());
 	XMLParser::RegisterNodeHandler("select", MakeShared<XMLNodeHandlerSelect>());
-
-	return true;
 }
 
 void Factory::Shutdown()
 {
-	element_instancers.clear();
-
-	decorator_instancers.clear();
-
-	font_effect_instancers.clear();
-
-	data_controller_instancers.clear();
-	data_view_instancers.clear();
-	structural_data_view_instancers.clear();
-	structural_data_view_attribute_names.clear();
-
 	context_instancer = nullptr;
-
 	event_listener_instancer = nullptr;
-
 	event_instancer = nullptr;
 
 	XMLParser::ReleaseHandlers();
 
-	default_instancers.reset();
+	factory_data.Shutdown();
 }
 
 void Factory::RegisterContextInstancer(ContextInstancer* instancer)
@@ -352,16 +316,16 @@ ContextPtr Factory::InstanceContext(const String& name, RenderManager* render_ma
 
 void Factory::RegisterElementInstancer(const String& name, ElementInstancer* instancer)
 {
-	element_instancers[StringUtilities::ToLower(name)] = instancer;
+	factory_data->element_instancers[StringUtilities::ToLower(name)] = instancer;
 }
 
 ElementInstancer* Factory::GetElementInstancer(const String& tag)
 {
-	ElementInstancerMap::iterator instancer_iterator = element_instancers.find(tag);
-	if (instancer_iterator == element_instancers.end())
+	auto instancer_iterator = factory_data->element_instancers.find(tag);
+	if (instancer_iterator == factory_data->element_instancers.end())
 	{
-		instancer_iterator = element_instancers.find("*");
-		if (instancer_iterator == element_instancers.end())
+		instancer_iterator = factory_data->element_instancers.find("*");
+		if (instancer_iterator == factory_data->element_instancers.end())
 			return nullptr;
 	}
 
@@ -514,13 +478,13 @@ ElementPtr Factory::InstanceDocumentStream(Context* context, Stream* stream, con
 void Factory::RegisterDecoratorInstancer(const String& name, DecoratorInstancer* instancer)
 {
 	RMLUI_ASSERT(instancer);
-	decorator_instancers[StringUtilities::ToLower(name)] = instancer;
+	factory_data->decorator_instancers[StringUtilities::ToLower(name)] = instancer;
 }
 
 DecoratorInstancer* Factory::GetDecoratorInstancer(const String& name)
 {
-	auto iterator = decorator_instancers.find(name);
-	if (iterator == decorator_instancers.end())
+	auto iterator = factory_data->decorator_instancers.find(name);
+	if (iterator == factory_data->decorator_instancers.end())
 		return nullptr;
 
 	return iterator->second;
@@ -529,13 +493,13 @@ DecoratorInstancer* Factory::GetDecoratorInstancer(const String& name)
 void Factory::RegisterFilterInstancer(const String& name, FilterInstancer* instancer)
 {
 	RMLUI_ASSERT(instancer);
-	filter_instancers[StringUtilities::ToLower(name)] = instancer;
+	factory_data->filter_instancers[StringUtilities::ToLower(name)] = instancer;
 }
 
 FilterInstancer* Factory::GetFilterInstancer(const String& name)
 {
-	auto iterator = filter_instancers.find(name);
-	if (iterator == filter_instancers.end())
+	auto iterator = factory_data->filter_instancers.find(name);
+	if (iterator == factory_data->filter_instancers.end())
 		return nullptr;
 
 	return iterator->second;
@@ -544,13 +508,13 @@ FilterInstancer* Factory::GetFilterInstancer(const String& name)
 void Factory::RegisterFontEffectInstancer(const String& name, FontEffectInstancer* instancer)
 {
 	RMLUI_ASSERT(instancer);
-	font_effect_instancers[StringUtilities::ToLower(name)] = instancer;
+	factory_data->font_effect_instancers[StringUtilities::ToLower(name)] = instancer;
 }
 
 FontEffectInstancer* Factory::GetFontEffectInstancer(const String& name)
 {
-	auto iterator = font_effect_instancers.find(name);
-	if (iterator == font_effect_instancers.end())
+	auto iterator = factory_data->font_effect_instancers.find(name);
+	if (iterator == factory_data->font_effect_instancers.end())
 		return nullptr;
 
 	return iterator->second;
@@ -621,13 +585,13 @@ void Factory::RegisterDataViewInstancer(DataViewInstancer* instancer, const Stri
 	bool inserted = false;
 	if (is_structural_view)
 	{
-		inserted = structural_data_view_instancers.emplace(name, instancer).second;
+		inserted = factory_data->structural_data_view_instancers.emplace(name, instancer).second;
 		if (inserted)
-			structural_data_view_attribute_names.push_back(String("data-") + name);
+			factory_data->structural_data_view_attribute_names.push_back(String("data-") + name);
 	}
 	else
 	{
-		inserted = data_view_instancers.emplace(name, instancer).second;
+		inserted = factory_data->data_view_instancers.emplace(name, instancer).second;
 	}
 
 	if (!inserted)
@@ -636,7 +600,7 @@ void Factory::RegisterDataViewInstancer(DataViewInstancer* instancer, const Stri
 
 void Factory::RegisterDataControllerInstancer(DataControllerInstancer* instancer, const String& name)
 {
-	bool inserted = data_controller_instancers.emplace(name, instancer).second;
+	bool inserted = factory_data->data_controller_instancers.emplace(name, instancer).second;
 	if (!inserted)
 		Log::Message(Log::LT_WARNING, "Could not register data controller instancer '%s'. The given name is already registered.", name.c_str());
 }
@@ -647,14 +611,14 @@ DataViewPtr Factory::InstanceDataView(const String& type_name, Element* element,
 
 	if (is_structural_view)
 	{
-		auto it = structural_data_view_instancers.find(type_name);
-		if (it != structural_data_view_instancers.end())
+		auto it = factory_data->structural_data_view_instancers.find(type_name);
+		if (it != factory_data->structural_data_view_instancers.end())
 			return it->second->InstanceView(element);
 	}
 	else
 	{
-		auto it = data_view_instancers.find(type_name);
-		if (it != data_view_instancers.end())
+		auto it = factory_data->data_view_instancers.find(type_name);
+		if (it != factory_data->data_view_instancers.end())
 			return it->second->InstanceView(element);
 	}
 	return nullptr;
@@ -662,20 +626,20 @@ DataViewPtr Factory::InstanceDataView(const String& type_name, Element* element,
 
 DataControllerPtr Factory::InstanceDataController(const String& type_name, Element* element)
 {
-	auto it = data_controller_instancers.find(type_name);
-	if (it != data_controller_instancers.end())
+	auto it = factory_data->data_controller_instancers.find(type_name);
+	if (it != factory_data->data_controller_instancers.end())
 		return it->second->InstanceController(element);
 	return DataControllerPtr();
 }
 
 bool Factory::IsStructuralDataView(const String& type_name)
 {
-	return structural_data_view_instancers.find(type_name) != structural_data_view_instancers.end();
+	return factory_data->structural_data_view_instancers.find(type_name) != factory_data->structural_data_view_instancers.end();
 }
 
 const StringList& Factory::GetStructuralDataViewAttributeNames()
 {
-	return structural_data_view_attribute_names;
+	return factory_data->structural_data_view_attribute_names;
 }
 
 } // namespace Rml

+ 23 - 9
Source/Core/Layout/LayoutPools.cpp

@@ -28,6 +28,7 @@
 
 #include "LayoutPools.h"
 #include "../../../Include/RmlUi/Core/Element.h"
+#include "../ControlledLifetimeResource.h"
 #include "../Pool.h"
 #include "BlockContainer.h"
 #include "FloatedBoxSpace.h"
@@ -52,9 +53,22 @@ static constexpr size_t ChunkSizeMedium =
 static constexpr size_t ChunkSizeSmall =
 	std::max({sizeof(ReplacedBox), sizeof(InlineLevelBox_Text), sizeof(InlineLevelBox_Atomic), sizeof(LineBox), sizeof(FloatedBoxSpace)});
 
-static Pool<LayoutChunk<ChunkSizeBig>> layout_chunk_pool_big(50, true);
-static Pool<LayoutChunk<ChunkSizeMedium>> layout_chunk_pool_medium(50, true);
-static Pool<LayoutChunk<ChunkSizeSmall>> layout_chunk_pool_small(50, true);
+struct LayoutPoolsData {
+	Pool<LayoutChunk<ChunkSizeBig>> layout_chunk_pool_big{50, true};
+	Pool<LayoutChunk<ChunkSizeMedium>> layout_chunk_pool_medium{50, true};
+	Pool<LayoutChunk<ChunkSizeSmall>> layout_chunk_pool_small{50, true};
+};
+
+static ControlledLifetimeResource<LayoutPoolsData> layout_pools_data;
+
+void LayoutPools::Initialize()
+{
+	layout_pools_data.Initialize();
+}
+void LayoutPools::Shutdown()
+{
+	layout_pools_data.Shutdown();
+}
 
 void* LayoutPools::AllocateLayoutChunk(size_t size)
 {
@@ -62,11 +76,11 @@ void* LayoutPools::AllocateLayoutChunk(size_t size)
 
 	// Note: If any change is made here, make sure a corresponding change is applied to the deallocation procedure below.
 	if (size <= ChunkSizeSmall)
-		return layout_chunk_pool_small.AllocateAndConstruct();
+		return layout_pools_data->layout_chunk_pool_small.AllocateAndConstruct();
 	else if (size <= ChunkSizeMedium)
-		return layout_chunk_pool_medium.AllocateAndConstruct();
+		return layout_pools_data->layout_chunk_pool_medium.AllocateAndConstruct();
 	else if (size <= ChunkSizeBig)
-		return layout_chunk_pool_big.AllocateAndConstruct();
+		return layout_pools_data->layout_chunk_pool_big.AllocateAndConstruct();
 
 	RMLUI_ERROR;
 	return nullptr;
@@ -76,11 +90,11 @@ void LayoutPools::DeallocateLayoutChunk(void* chunk, size_t size)
 {
 	// Note: If any change is made here, make sure a corresponding change is applied to the allocation procedure above.
 	if (size <= ChunkSizeSmall)
-		layout_chunk_pool_small.DestroyAndDeallocate((LayoutChunk<ChunkSizeSmall>*)chunk);
+		layout_pools_data->layout_chunk_pool_small.DestroyAndDeallocate((LayoutChunk<ChunkSizeSmall>*)chunk);
 	else if (size <= ChunkSizeMedium)
-		layout_chunk_pool_medium.DestroyAndDeallocate((LayoutChunk<ChunkSizeMedium>*)chunk);
+		layout_pools_data->layout_chunk_pool_medium.DestroyAndDeallocate((LayoutChunk<ChunkSizeMedium>*)chunk);
 	else if (size <= ChunkSizeBig)
-		layout_chunk_pool_big.DestroyAndDeallocate((LayoutChunk<ChunkSizeBig>*)chunk);
+		layout_pools_data->layout_chunk_pool_big.DestroyAndDeallocate((LayoutChunk<ChunkSizeBig>*)chunk);
 	else
 	{
 		RMLUI_ERROR;

+ 3 - 0
Source/Core/Layout/LayoutPools.h

@@ -35,6 +35,9 @@ namespace Rml {
 
 namespace LayoutPools {
 
+	void Initialize();
+	void Shutdown();
+
 	void* AllocateLayoutChunk(size_t size);
 	void DeallocateLayoutChunk(void* chunk, size_t size);
 

+ 32 - 15
Source/Core/ObserverPtr.cpp

@@ -27,35 +27,52 @@
  */
 
 #include "../../Include/RmlUi/Core/ObserverPtr.h"
+#include "../../Include/RmlUi/Core/Log.h"
+#include "ControlledLifetimeResource.h"
 #include "Pool.h"
 
 namespace Rml {
 
-// The ObserverPtrBlock pool
-Pool<ObserverPtrBlock>* observerPtrBlockPool = nullptr;
+struct ObserverPtrData {
+	Pool<Detail::ObserverPtrBlock> block_pool{128, true};
+};
+static ControlledLifetimeResource<ObserverPtrData> observer_ptr_data;
 
-static Pool<ObserverPtrBlock>& GetPool()
+void Detail::DeallocateObserverPtrBlockIfEmpty(ObserverPtrBlock* block)
 {
-	// Wrap pool in a function to ensure it is initialized before use.
-	// This pool must outlive all other global variables that derive from EnableObserverPtr. This even includes
-	// user variables which we have no control over. For this reason, we intentionally let this leak.
-	if (observerPtrBlockPool == nullptr)
-		observerPtrBlockPool = new Pool<ObserverPtrBlock>(128, true);
-	return *observerPtrBlockPool;
+	RMLUI_ASSERT(block->num_observers >= 0);
+	if (block->num_observers == 0 && block->pointed_to_object == nullptr)
+	{
+		observer_ptr_data->block_pool.DestroyAndDeallocate(block);
+	}
+}
+void Detail::InitializeObserverPtrPool()
+{
+	observer_ptr_data.InitializeIfEmpty();
 }
 
-void DeallocateObserverPtrBlockIfEmpty(ObserverPtrBlock* block)
+void Detail::ShutdownObserverPtrPool()
 {
-	RMLUI_ASSERT(block->num_observers >= 0);
-	if (block->num_observers == 0 && block->pointed_to_object == nullptr)
+	const int num_objects = observer_ptr_data->block_pool.GetNumAllocatedObjects();
+	if (num_objects == 0)
+	{
+		observer_ptr_data.Shutdown();
+	}
+	else
 	{
-		GetPool().DestroyAndDeallocate(block);
+		// This pool must outlive all other global variables that derive from EnableObserverPtr. This even includes user
+		// variables which we have no control over. So if there are any objects still alive, let the pool leak.
+		Log::Message(Log::LT_WARNING,
+			"Observer pointers still alive on shutdown, %d object(s) leaked. "
+			"Please ensure that no RmlUi objects are retained in user space at the end of Rml::Shutdown.",
+			num_objects);
+		observer_ptr_data.Leak();
 	}
 }
 
-ObserverPtrBlock* AllocateObserverPtrBlock()
+Detail::ObserverPtrBlock* Detail::AllocateObserverPtrBlock()
 {
-	return GetPool().AllocateAndConstruct();
+	return observer_ptr_data->block_pool.AllocateAndConstruct();
 }
 
 } // namespace Rml

+ 49 - 32
Source/Core/PluginRegistry.cpp

@@ -28,99 +28,116 @@
 
 #include "PluginRegistry.h"
 #include "../../Include/RmlUi/Core/Plugin.h"
+#include "ControlledLifetimeResource.h"
 #include <algorithm>
 
 namespace Rml {
 
-typedef Vector<Plugin*> PluginList;
-static PluginList basic_plugins;
-static PluginList document_plugins;
-static PluginList element_plugins;
+struct PluginVectors {
+	Vector<Plugin*> basic;
+	Vector<Plugin*> document;
+	Vector<Plugin*> element;
+};
 
-PluginRegistry::PluginRegistry() {}
+static ControlledLifetimeResource<PluginVectors> plugin_vectors;
+
+static void EnsurePluginVectorsInitialized()
+{
+	if (!plugin_vectors)
+	{
+		plugin_vectors.Initialize();
+	}
+}
 
 void PluginRegistry::RegisterPlugin(Plugin* plugin)
 {
+	EnsurePluginVectorsInitialized();
+
 	int event_classes = plugin->GetEventClasses();
 
 	if (event_classes & Plugin::EVT_BASIC)
-		basic_plugins.push_back(plugin);
+		plugin_vectors->basic.push_back(plugin);
 	if (event_classes & Plugin::EVT_DOCUMENT)
-		document_plugins.push_back(plugin);
+		plugin_vectors->document.push_back(plugin);
 	if (event_classes & Plugin::EVT_ELEMENT)
-		element_plugins.push_back(plugin);
+		plugin_vectors->element.push_back(plugin);
 }
 
 void PluginRegistry::UnregisterPlugin(Plugin* plugin)
 {
-	int event_classes = plugin->GetEventClasses();
+	auto erase_value = [](Vector<Plugin*>& container, Plugin* value) {
+		container.erase(std::remove(container.begin(), container.end(), value), container.end());
+	};
 
+	int event_classes = plugin->GetEventClasses();
 	if (event_classes & Plugin::EVT_BASIC)
-		basic_plugins.erase(std::remove(basic_plugins.begin(), basic_plugins.end(), plugin), basic_plugins.end());
+		erase_value(plugin_vectors->basic, plugin);
 	if (event_classes & Plugin::EVT_DOCUMENT)
-		document_plugins.erase(std::remove(document_plugins.begin(), document_plugins.end(), plugin), document_plugins.end());
+		erase_value(plugin_vectors->document, plugin);
 	if (event_classes & Plugin::EVT_ELEMENT)
-		element_plugins.erase(std::remove(element_plugins.begin(), element_plugins.end(), plugin), element_plugins.end());
+		erase_value(plugin_vectors->element, plugin);
 }
 
 void PluginRegistry::NotifyInitialise()
 {
-	for (size_t i = 0; i < basic_plugins.size(); ++i)
-		basic_plugins[i]->OnInitialise();
+	EnsurePluginVectorsInitialized();
+
+	for (Plugin* plugin : plugin_vectors->basic)
+		plugin->OnInitialise();
 }
 
 void PluginRegistry::NotifyShutdown()
 {
-	while (!basic_plugins.empty())
+	while (!plugin_vectors->basic.empty())
 	{
-		Plugin* plugin = basic_plugins.back();
+		Plugin* plugin = plugin_vectors->basic.back();
 		PluginRegistry::UnregisterPlugin(plugin);
 		plugin->OnShutdown();
 	}
-	document_plugins.clear();
-	element_plugins.clear();
+
+	plugin_vectors.Shutdown();
 }
 
 void PluginRegistry::NotifyContextCreate(Context* context)
 {
-	for (size_t i = 0; i < basic_plugins.size(); ++i)
-		basic_plugins[i]->OnContextCreate(context);
+	for (Plugin* plugin : plugin_vectors->basic)
+		plugin->OnContextCreate(context);
 }
 
 void PluginRegistry::NotifyContextDestroy(Context* context)
 {
-	for (size_t i = 0; i < basic_plugins.size(); ++i)
-		basic_plugins[i]->OnContextDestroy(context);
+	for (Plugin* plugin : plugin_vectors->basic)
+		plugin->OnContextDestroy(context);
 }
 
 void PluginRegistry::NotifyDocumentOpen(Context* context, const String& document_path)
 {
-	for (size_t i = 0; i < document_plugins.size(); ++i)
-		document_plugins[i]->OnDocumentOpen(context, document_path);
+	for (Plugin* plugin : plugin_vectors->document)
+		plugin->OnDocumentOpen(context, document_path);
 }
 
 void PluginRegistry::NotifyDocumentLoad(ElementDocument* document)
 {
-	for (size_t i = 0; i < document_plugins.size(); ++i)
-		document_plugins[i]->OnDocumentLoad(document);
+	for (Plugin* plugin : plugin_vectors->document)
+		plugin->OnDocumentLoad(document);
 }
 
 void PluginRegistry::NotifyDocumentUnload(ElementDocument* document)
 {
-	for (size_t i = 0; i < document_plugins.size(); ++i)
-		document_plugins[i]->OnDocumentUnload(document);
+	for (Plugin* plugin : plugin_vectors->document)
+		plugin->OnDocumentUnload(document);
 }
 
 void PluginRegistry::NotifyElementCreate(Element* element)
 {
-	for (size_t i = 0; i < element_plugins.size(); ++i)
-		element_plugins[i]->OnElementCreate(element);
+	for (Plugin* plugin : plugin_vectors->element)
+		plugin->OnElementCreate(element);
 }
 
 void PluginRegistry::NotifyElementDestroy(Element* element)
 {
-	for (size_t i = 0; i < element_plugins.size(); ++i)
-		element_plugins[i]->OnElementDestroy(element);
+	for (Plugin* plugin : plugin_vectors->element)
+		plugin->OnElementDestroy(element);
 }
 
 } // namespace Rml

+ 1 - 1
Source/Core/PluginRegistry.h

@@ -70,7 +70,7 @@ public:
 	static void NotifyElementDestroy(Element* element);
 
 private:
-	PluginRegistry();
+	PluginRegistry() = delete;
 };
 
 } // namespace Rml

+ 92 - 78
Source/Core/PropertyParserAnimation.cpp

@@ -51,61 +51,97 @@ struct Keyword {
 	}
 };
 
-static const UnorderedMap<String, Keyword> keywords = {
-	{"none", {KeywordType::None}},
-	{"all", {KeywordType::All}},
-	{"alternate", {KeywordType::Alternate}},
-	{"infinite", {KeywordType::Infinite}},
-	{"paused", {KeywordType::Paused}},
-
-	{"back-in", {Tween{Tween::Back, Tween::In}}},
-	{"back-out", {Tween{Tween::Back, Tween::Out}}},
-	{"back-in-out", {Tween{Tween::Back, Tween::InOut}}},
-
-	{"bounce-in", {Tween{Tween::Bounce, Tween::In}}},
-	{"bounce-out", {Tween{Tween::Bounce, Tween::Out}}},
-	{"bounce-in-out", {Tween{Tween::Bounce, Tween::InOut}}},
-
-	{"circular-in", {Tween{Tween::Circular, Tween::In}}},
-	{"circular-out", {Tween{Tween::Circular, Tween::Out}}},
-	{"circular-in-out", {Tween{Tween::Circular, Tween::InOut}}},
-
-	{"cubic-in", {Tween{Tween::Cubic, Tween::In}}},
-	{"cubic-out", {Tween{Tween::Cubic, Tween::Out}}},
-	{"cubic-in-out", {Tween{Tween::Cubic, Tween::InOut}}},
-
-	{"elastic-in", {Tween{Tween::Elastic, Tween::In}}},
-	{"elastic-out", {Tween{Tween::Elastic, Tween::Out}}},
-	{"elastic-in-out", {Tween{Tween::Elastic, Tween::InOut}}},
-
-	{"exponential-in", {Tween{Tween::Exponential, Tween::In}}},
-	{"exponential-out", {Tween{Tween::Exponential, Tween::Out}}},
-	{"exponential-in-out", {Tween{Tween::Exponential, Tween::InOut}}},
-
-	{"linear-in", {Tween{Tween::Linear, Tween::In}}},
-	{"linear-out", {Tween{Tween::Linear, Tween::Out}}},
-	{"linear-in-out", {Tween{Tween::Linear, Tween::InOut}}},
-
-	{"quadratic-in", {Tween{Tween::Quadratic, Tween::In}}},
-	{"quadratic-out", {Tween{Tween::Quadratic, Tween::Out}}},
-	{"quadratic-in-out", {Tween{Tween::Quadratic, Tween::InOut}}},
-
-	{"quartic-in", {Tween{Tween::Quartic, Tween::In}}},
-	{"quartic-out", {Tween{Tween::Quartic, Tween::Out}}},
-	{"quartic-in-out", {Tween{Tween::Quartic, Tween::InOut}}},
-
-	{"quintic-in", {Tween{Tween::Quintic, Tween::In}}},
-	{"quintic-out", {Tween{Tween::Quintic, Tween::Out}}},
-	{"quintic-in-out", {Tween{Tween::Quintic, Tween::InOut}}},
-
-	{"sine-in", {Tween{Tween::Sine, Tween::In}}},
-	{"sine-out", {Tween{Tween::Sine, Tween::Out}}},
-	{"sine-in-out", {Tween{Tween::Sine, Tween::InOut}}},
+struct PropertyParserAnimationData {
+	const UnorderedMap<String, Keyword> keywords = {
+		{"none", {KeywordType::None}},
+		{"all", {KeywordType::All}},
+		{"alternate", {KeywordType::Alternate}},
+		{"infinite", {KeywordType::Infinite}},
+		{"paused", {KeywordType::Paused}},
+
+		{"back-in", {Tween{Tween::Back, Tween::In}}},
+		{"back-out", {Tween{Tween::Back, Tween::Out}}},
+		{"back-in-out", {Tween{Tween::Back, Tween::InOut}}},
+
+		{"bounce-in", {Tween{Tween::Bounce, Tween::In}}},
+		{"bounce-out", {Tween{Tween::Bounce, Tween::Out}}},
+		{"bounce-in-out", {Tween{Tween::Bounce, Tween::InOut}}},
+
+		{"circular-in", {Tween{Tween::Circular, Tween::In}}},
+		{"circular-out", {Tween{Tween::Circular, Tween::Out}}},
+		{"circular-in-out", {Tween{Tween::Circular, Tween::InOut}}},
+
+		{"cubic-in", {Tween{Tween::Cubic, Tween::In}}},
+		{"cubic-out", {Tween{Tween::Cubic, Tween::Out}}},
+		{"cubic-in-out", {Tween{Tween::Cubic, Tween::InOut}}},
+
+		{"elastic-in", {Tween{Tween::Elastic, Tween::In}}},
+		{"elastic-out", {Tween{Tween::Elastic, Tween::Out}}},
+		{"elastic-in-out", {Tween{Tween::Elastic, Tween::InOut}}},
+
+		{"exponential-in", {Tween{Tween::Exponential, Tween::In}}},
+		{"exponential-out", {Tween{Tween::Exponential, Tween::Out}}},
+		{"exponential-in-out", {Tween{Tween::Exponential, Tween::InOut}}},
+
+		{"linear-in", {Tween{Tween::Linear, Tween::In}}},
+		{"linear-out", {Tween{Tween::Linear, Tween::Out}}},
+		{"linear-in-out", {Tween{Tween::Linear, Tween::InOut}}},
+
+		{"quadratic-in", {Tween{Tween::Quadratic, Tween::In}}},
+		{"quadratic-out", {Tween{Tween::Quadratic, Tween::Out}}},
+		{"quadratic-in-out", {Tween{Tween::Quadratic, Tween::InOut}}},
+
+		{"quartic-in", {Tween{Tween::Quartic, Tween::In}}},
+		{"quartic-out", {Tween{Tween::Quartic, Tween::Out}}},
+		{"quartic-in-out", {Tween{Tween::Quartic, Tween::InOut}}},
+
+		{"quintic-in", {Tween{Tween::Quintic, Tween::In}}},
+		{"quintic-out", {Tween{Tween::Quintic, Tween::Out}}},
+		{"quintic-in-out", {Tween{Tween::Quintic, Tween::InOut}}},
+
+		{"sine-in", {Tween{Tween::Sine, Tween::In}}},
+		{"sine-out", {Tween{Tween::Sine, Tween::Out}}},
+		{"sine-in-out", {Tween{Tween::Sine, Tween::InOut}}},
+	};
 };
 
+ControlledLifetimeResource<PropertyParserAnimationData> PropertyParserAnimation::parser_data;
+
+void PropertyParserAnimation::Initialize()
+{
+	parser_data.Initialize();
+}
+
+void PropertyParserAnimation::Shutdown()
+{
+	parser_data.Shutdown();
+}
+
 PropertyParserAnimation::PropertyParserAnimation(Type type) : type(type) {}
 
-static bool ParseAnimation(Property& property, const StringList& animation_values)
+bool PropertyParserAnimation::ParseValue(Property& property, const String& value, const ParameterMap& /*parameters*/) const
+{
+	StringList list_of_values;
+	{
+		auto lowercase_value = StringUtilities::ToLower(value);
+		StringUtilities::ExpandString(list_of_values, lowercase_value, ',');
+	}
+
+	bool result = false;
+
+	if (type == ANIMATION_PARSER)
+	{
+		result = ParseAnimation(property, list_of_values);
+	}
+	else if (type == TRANSITION_PARSER)
+	{
+		result = ParseTransition(property, list_of_values);
+	}
+
+	return result;
+}
+
+bool PropertyParserAnimation::ParseAnimation(Property& property, const StringList& animation_values)
 {
 	AnimationList animation_list;
 
@@ -126,8 +162,8 @@ static bool ParseAnimation(Property& property, const StringList& animation_value
 				continue;
 
 			// See if we have a <keyword> or <tween> specifier as defined in keywords
-			auto it = keywords.find(argument);
-			if (it != keywords.end() && it->second.ValidAnimation())
+			auto it = parser_data->keywords.find(argument);
+			if (it != parser_data->keywords.end() && it->second.ValidAnimation())
 			{
 				switch (it->second.type)
 				{
@@ -211,7 +247,7 @@ static bool ParseAnimation(Property& property, const StringList& animation_value
 	return true;
 }
 
-static bool ParseTransition(Property& property, const StringList& transition_values)
+bool PropertyParserAnimation::ParseTransition(Property& property, const StringList& transition_values)
 {
 	TransitionList transition_list{false, false, {}};
 
@@ -233,8 +269,8 @@ static bool ParseTransition(Property& property, const StringList& transition_val
 				continue;
 
 			// See if we have a <keyword> or <tween> specifier as defined in keywords
-			auto it = keywords.find(argument);
-			if (it != keywords.end() && it->second.ValidTransition())
+			auto it = parser_data->keywords.find(argument);
+			if (it != parser_data->keywords.end() && it->second.ValidTransition())
 			{
 				if (it->second.type == KeywordType::None)
 				{
@@ -345,26 +381,4 @@ static bool ParseTransition(Property& property, const StringList& transition_val
 	return true;
 }
 
-bool PropertyParserAnimation::ParseValue(Property& property, const String& value, const ParameterMap& /*parameters*/) const
-{
-	StringList list_of_values;
-	{
-		auto lowercase_value = StringUtilities::ToLower(value);
-		StringUtilities::ExpandString(list_of_values, lowercase_value, ',');
-	}
-
-	bool result = false;
-
-	if (type == ANIMATION_PARSER)
-	{
-		result = ParseAnimation(property, list_of_values);
-	}
-	else if (type == TRANSITION_PARSER)
-	{
-		result = ParseTransition(property, list_of_values);
-	}
-
-	return result;
-}
-
 } // namespace Rml

+ 10 - 0
Source/Core/PropertyParserAnimation.h

@@ -30,6 +30,7 @@
 #define RMLUI_CORE_PROPERTYPARSERANIMATION_H
 
 #include "../../Include/RmlUi/Core/PropertyParser.h"
+#include "ControlledLifetimeResource.h"
 
 namespace Rml {
 
@@ -50,6 +51,15 @@ public:
 	/// @param[in] parameters The parameters defined for this property.
 	/// @return True if the value was validated successfully, false otherwise.
 	bool ParseValue(Property& property, const String& value, const ParameterMap& parameters) const override;
+
+	static void Initialize();
+	static void Shutdown();
+
+private:
+	static bool ParseAnimation(Property& property, const StringList& animation_values);
+	static bool ParseTransition(Property& property, const StringList& transition_values);
+
+	static ControlledLifetimeResource<struct PropertyParserAnimationData> parser_data;
 };
 
 } // namespace Rml

+ 37 - 23
Source/Core/PropertyParserColour.cpp

@@ -27,6 +27,7 @@
  */
 
 #include "PropertyParserColour.h"
+#include "ControlledLifetimeResource.h"
 #include <algorithm>
 #include <cmath>
 #include <string.h>
@@ -61,28 +62,41 @@ static void HSLAToRGBA(Array<float, 4>& vals)
 	}
 }
 
-const PropertyParserColour::ColourMap PropertyParserColour::html_colours = {
-	{"black", Colourb(0, 0, 0)},
-	{"silver", Colourb(192, 192, 192)},
-	{"gray", Colourb(128, 128, 128)},
-	{"grey", Colourb(128, 128, 128)},
-	{"white", Colourb(255, 255, 255)},
-	{"maroon", Colourb(128, 0, 0)},
-	{"red", Colourb(255, 0, 0)},
-	{"orange", Colourb(255, 165, 0)},
-	{"purple", Colourb(128, 0, 128)},
-	{"fuchsia", Colourb(255, 0, 255)},
-	{"green", Colourb(0, 128, 0)},
-	{"lime", Colourb(0, 255, 0)},
-	{"olive", Colourb(128, 128, 0)},
-	{"yellow", Colourb(255, 255, 0)},
-	{"navy", Colourb(0, 0, 128)},
-	{"blue", Colourb(0, 0, 255)},
-	{"teal", Colourb(0, 128, 128)},
-	{"aqua", Colourb(0, 255, 255)},
-	{"transparent", Colourb(0, 0, 0, 0)},
+struct PropertyParserColourData {
+	const UnorderedMap<String, Colourb> html_colours = {
+		{"black", Colourb(0, 0, 0)},
+		{"silver", Colourb(192, 192, 192)},
+		{"gray", Colourb(128, 128, 128)},
+		{"grey", Colourb(128, 128, 128)},
+		{"white", Colourb(255, 255, 255)},
+		{"maroon", Colourb(128, 0, 0)},
+		{"red", Colourb(255, 0, 0)},
+		{"orange", Colourb(255, 165, 0)},
+		{"purple", Colourb(128, 0, 128)},
+		{"fuchsia", Colourb(255, 0, 255)},
+		{"green", Colourb(0, 128, 0)},
+		{"lime", Colourb(0, 255, 0)},
+		{"olive", Colourb(128, 128, 0)},
+		{"yellow", Colourb(255, 255, 0)},
+		{"navy", Colourb(0, 0, 128)},
+		{"blue", Colourb(0, 0, 255)},
+		{"teal", Colourb(0, 128, 128)},
+		{"aqua", Colourb(0, 255, 255)},
+		{"transparent", Colourb(0, 0, 0, 0)},
+	};
 };
 
+ControlledLifetimeResource<PropertyParserColourData> PropertyParserColour::parser_data;
+
+void PropertyParserColour::Initialize()
+{
+	parser_data.Initialize();
+}
+void PropertyParserColour::Shutdown()
+{
+	parser_data.Shutdown();
+}
+
 PropertyParserColour::PropertyParserColour() {}
 
 PropertyParserColour::~PropertyParserColour() {}
@@ -228,11 +242,11 @@ bool PropertyParserColour::ParseColour(Colourb& colour, const String& value)
 	else
 	{
 		// Check for the specification of an HTML colour.
-		ColourMap::const_iterator iterator = html_colours.find(StringUtilities::ToLower(value));
-		if (iterator == html_colours.end())
+		auto it = parser_data->html_colours.find(StringUtilities::ToLower(value));
+		if (it == parser_data->html_colours.end())
 			return false;
 		else
-			colour = (*iterator).second;
+			colour = it->second;
 	}
 
 	return true;

+ 5 - 2
Source/Core/PropertyParserColour.h

@@ -31,6 +31,7 @@
 
 #include "../../Include/RmlUi/Core/PropertyParser.h"
 #include "../../Include/RmlUi/Core/Types.h"
+#include "ControlledLifetimeResource.h"
 
 namespace Rml {
 
@@ -55,9 +56,11 @@ public:
 	/// Parse a colour directly.
 	static bool ParseColour(Colourb& colour, const String& value);
 
+	static void Initialize();
+	static void Shutdown();
+
 private:
-	using ColourMap = UnorderedMap<String, Colourb>;
-	static const ColourMap html_colours;
+	static ControlledLifetimeResource<struct PropertyParserColourData> parser_data;
 };
 
 } // namespace Rml

+ 20 - 7
Source/Core/PropertyParserDecorator.cpp

@@ -35,12 +35,25 @@
 
 namespace Rml {
 
-const SmallUnorderedMap<String, BoxArea> PropertyParserDecorator::area_keywords = {
-	{"border-box", BoxArea::Border},
-	{"padding-box", BoxArea::Padding},
-	{"content-box", BoxArea::Content},
+struct PropertyParserDecoratorData {
+	const SmallUnorderedMap<String, BoxArea> area_keywords = {
+		{"border-box", BoxArea::Border},
+		{"padding-box", BoxArea::Padding},
+		{"content-box", BoxArea::Content},
+	};
 };
 
+ControlledLifetimeResource<PropertyParserDecoratorData> PropertyParserDecorator::parser_data;
+
+void PropertyParserDecorator::Initialize()
+{
+	parser_data.Initialize();
+}
+void PropertyParserDecorator::Shutdown()
+{
+	parser_data.Shutdown();
+}
+
 PropertyParserDecorator::PropertyParserDecorator() {}
 
 PropertyParserDecorator::~PropertyParserDecorator() {}
@@ -94,8 +107,8 @@ bool PropertyParserDecorator::ParseValue(Property& property, const String& decor
 				if (keyword.empty())
 					continue;
 
-				auto it = area_keywords.find(StringUtilities::ToLower(keyword));
-				if (it == area_keywords.end())
+				auto it = parser_data->area_keywords.find(StringUtilities::ToLower(keyword));
+				if (it == parser_data->area_keywords.end())
 					return false; // Bail out if we have an invalid keyword.
 
 				paint_area = it->second;
@@ -150,7 +163,7 @@ bool PropertyParserDecorator::ParseValue(Property& property, const String& decor
 
 String PropertyParserDecorator::ConvertAreaToString(BoxArea area)
 {
-	for (const auto& it : area_keywords)
+	for (const auto& it : parser_data->area_keywords)
 	{
 		if (it.second == area)
 			return it.first;

+ 5 - 1
Source/Core/PropertyParserDecorator.h

@@ -30,6 +30,7 @@
 #define RMLUI_CORE_PROPERTYPARSERDECORATOR_H
 
 #include "../../Include/RmlUi/Core/PropertyParser.h"
+#include "ControlledLifetimeResource.h"
 
 namespace Rml {
 
@@ -47,8 +48,11 @@ public:
 
 	static String ConvertAreaToString(BoxArea area);
 
+	static void Initialize();
+	static void Shutdown();
+
 private:
-	static const SmallUnorderedMap<String, BoxArea> area_keywords;
+	static ControlledLifetimeResource<struct PropertyParserDecoratorData> parser_data;
 };
 
 } // namespace Rml

+ 33 - 19
Source/Core/PropertyParserNumber.cpp

@@ -31,25 +31,39 @@
 
 namespace Rml {
 
-static const UnorderedMap<String, Unit> g_property_unit_string_map = {
-	{"", Unit::NUMBER},
-	{"%", Unit::PERCENT},
-	{"px", Unit::PX},
-	{"dp", Unit::DP},
-	{"x", Unit::X},
-	{"vw", Unit::VW},
-	{"vh", Unit::VH},
-	{"em", Unit::EM},
-	{"rem", Unit::REM},
-	{"in", Unit::INCH},
-	{"cm", Unit::CM},
-	{"mm", Unit::MM},
-	{"pt", Unit::PT},
-	{"pc", Unit::PC},
-	{"deg", Unit::DEG},
-	{"rad", Unit::RAD},
+struct PropertyParserNumberData {
+	const UnorderedMap<String, Unit> unit_string_map = {
+		{"", Unit::NUMBER},
+		{"%", Unit::PERCENT},
+		{"px", Unit::PX},
+		{"dp", Unit::DP},
+		{"x", Unit::X},
+		{"vw", Unit::VW},
+		{"vh", Unit::VH},
+		{"em", Unit::EM},
+		{"rem", Unit::REM},
+		{"in", Unit::INCH},
+		{"cm", Unit::CM},
+		{"mm", Unit::MM},
+		{"pt", Unit::PT},
+		{"pc", Unit::PC},
+		{"deg", Unit::DEG},
+		{"rad", Unit::RAD},
+	};
 };
 
+ControlledLifetimeResource<PropertyParserNumberData> PropertyParserNumber::parser_data;
+
+void PropertyParserNumber::Initialize()
+{
+	parser_data.Initialize();
+}
+
+void PropertyParserNumber::Shutdown()
+{
+	parser_data.Shutdown();
+}
+
 PropertyParserNumber::PropertyParserNumber(Units units, Unit zero_unit) : units(units), zero_unit(zero_unit) {}
 
 PropertyParserNumber::~PropertyParserNumber() {}
@@ -79,8 +93,8 @@ bool PropertyParserNumber::ParseValue(Property& property, const String& value, c
 		return false;
 	}
 
-	const auto it = g_property_unit_string_map.find(str_unit);
-	if (it == g_property_unit_string_map.end())
+	const auto it = parser_data->unit_string_map.find(str_unit);
+	if (it == parser_data->unit_string_map.end())
 	{
 		// Invalid unit name
 		return false;

+ 6 - 0
Source/Core/PropertyParserNumber.h

@@ -30,6 +30,7 @@
 #define RMLUI_CORE_PROPERTYPARSERNUMBER_H
 
 #include "../../Include/RmlUi/Core/PropertyParser.h"
+#include "ControlledLifetimeResource.h"
 
 namespace Rml {
 
@@ -51,7 +52,12 @@ public:
 	/// @return True if the value was validated successfully, false otherwise.
 	bool ParseValue(Property& property, const String& value, const ParameterMap& parameters) const override;
 
+	static void Initialize();
+	static void Shutdown();
+
 private:
+	static ControlledLifetimeResource<struct PropertyParserNumberData> parser_data;
+
 	// Stores a bit mask of allowed units.
 	Units units;
 

+ 1 - 1
Source/Core/StyleSheetContainer.cpp

@@ -59,7 +59,7 @@ bool StyleSheetContainer::UpdateCompiledStyleSheet(const Context* context)
 
 	Vector<int> new_active_media_block_indices;
 
-	const float font_size = DefaultComputedValues.font_size();
+	const float font_size = DefaultComputedValues().font_size();
 
 	for (int media_block_index = 0; media_block_index < (int)media_blocks.size(); media_block_index++)
 	{

+ 21 - 18
Source/Core/StyleSheetParser.cpp

@@ -38,6 +38,7 @@
 #include "../../Include/RmlUi/Core/StyleSheetContainer.h"
 #include "../../Include/RmlUi/Core/StyleSheetSpecification.h"
 #include "ComputeProperty.h"
+#include "ControlledLifetimeResource.h"
 #include "StyleSheetFactory.h"
 #include "StyleSheetNode.h"
 #include <algorithm>
@@ -158,8 +159,6 @@ public:
 	}
 };
 
-static UniquePtr<SpritesheetPropertyParser> spritesheet_property_parser;
-
 /*
  * Media queries need a special parser because they have unique properties that
  * aren't admissible in other property declaration contexts.
@@ -208,7 +207,13 @@ public:
 	}
 };
 
-static UniquePtr<MediaQueryPropertyParser> media_query_property_parser;
+struct StyleSheetParserData {
+	// The following parsers are reasonably heavy to initialize, so we construct them during library initialization.
+	SpritesheetPropertyParser spritesheet;
+	MediaQueryPropertyParser media_query;
+};
+
+static ControlledLifetimeResource<StyleSheetParserData> style_sheet_property_parsers;
 
 StyleSheetParser::StyleSheetParser()
 {
@@ -221,14 +226,12 @@ StyleSheetParser::~StyleSheetParser() {}
 
 void StyleSheetParser::Initialise()
 {
-	spritesheet_property_parser = MakeUnique<SpritesheetPropertyParser>();
-	media_query_property_parser = MakeUnique<MediaQueryPropertyParser>();
+	style_sheet_property_parsers.Initialize();
 }
 
 void StyleSheetParser::Shutdown()
 {
-	spritesheet_property_parser.reset();
-	media_query_property_parser.reset();
+	style_sheet_property_parsers.Shutdown();
 }
 
 static bool IsValidIdentifier(const String& str)
@@ -399,7 +402,7 @@ bool StyleSheetParser::ParseDecoratorBlock(const String& at_name, NamedDecorator
 
 bool StyleSheetParser::ParseMediaFeatureMap(const String& rules, PropertyDictionary& properties, MediaQueryModifier& modifier)
 {
-	media_query_property_parser->SetTargetProperties(&properties);
+	style_sheet_property_parsers->media_query.SetTargetProperties(&properties);
 
 	enum ParseState { Global, Name, Value };
 	ParseState state = Global;
@@ -429,7 +432,8 @@ bool StyleSheetParser::ParseMediaFeatureMap(const String& rules, PropertyDiction
 					// we can only ever see one "not" on the entire global query.
 					if (modifier != MediaQueryModifier::None)
 					{
-						Log::Message(Log::LT_WARNING, "Unexpected '%s' in @media query list at %s:%d.", current_string.c_str(), stream_file_name.c_str(), line_number);
+						Log::Message(Log::LT_WARNING, "Unexpected '%s' in @media query list at %s:%d.", current_string.c_str(),
+							stream_file_name.c_str(), line_number);
 						return false;
 					}
 
@@ -451,8 +455,7 @@ bool StyleSheetParser::ParseMediaFeatureMap(const String& rules, PropertyDiction
 			current_string = StringUtilities::StripWhitespace(StringUtilities::ToLower(std::move(current_string)));
 
 			// allow an empty string to pass through only if we had just parsed a modifier.
-			if (current_string != "and" &&
-				(properties.GetNumProperties() != 0 || !current_string.empty()))
+			if (current_string != "and" && (properties.GetNumProperties() != 0 || !current_string.empty()))
 			{
 				Log::Message(Log::LT_WARNING, "Unexpected '%s' in @media query list at %s:%d. Expected 'and'.", current_string.c_str(),
 					stream_file_name.c_str(), line_number);
@@ -473,7 +476,7 @@ bool StyleSheetParser::ParseMediaFeatureMap(const String& rules, PropertyDiction
 
 			current_string = StringUtilities::StripWhitespace(current_string);
 
-			if (!media_query_property_parser->Parse(name, current_string))
+			if (!style_sheet_property_parsers->media_query.Parse(name, current_string))
 				Log::Message(Log::LT_WARNING, "Syntax error parsing media-query property declaration '%s: %s;' in %s: %d.", name.c_str(),
 					current_string.c_str(), stream_file_name.c_str(), line_number);
 
@@ -629,12 +632,12 @@ bool StyleSheetParser::Parse(MediaBlockList& style_sheets, Stream* _stream, int
 					}
 					else if (at_rule_identifier == "spritesheet")
 					{
-						// The spritesheet parser is reasonably heavy to initialize, so we make it a static global.
-						ReadProperties(*spritesheet_property_parser);
+						auto& spritesheet_property_parser = style_sheet_property_parsers->spritesheet;
+						ReadProperties(spritesheet_property_parser);
 
-						const String& image_source = spritesheet_property_parser->GetImageSource();
-						const SpriteDefinitionList& sprite_definitions = spritesheet_property_parser->GetSpriteDefinitions();
-						const float image_resolution_factor = spritesheet_property_parser->GetImageResolutionFactor();
+						const String& image_source = spritesheet_property_parser.GetImageSource();
+						const SpriteDefinitionList& sprite_definitions = spritesheet_property_parser.GetSpriteDefinitions();
+						const float image_resolution_factor = spritesheet_property_parser.GetImageResolutionFactor();
 
 						if (sprite_definitions.empty())
 						{
@@ -660,7 +663,7 @@ bool StyleSheetParser::Parse(MediaBlockList& style_sheets, Stream* _stream, int
 								display_scale, sprite_definitions);
 						}
 
-						spritesheet_property_parser->Clear();
+						spritesheet_property_parser.Clear();
 						at_rule_name.clear();
 						state = State::Global;
 					}

+ 21 - 15
Source/Core/StyleSheetSpecification.cpp

@@ -97,25 +97,31 @@ ShorthandId StyleSheetSpecification::RegisterShorthand(ShorthandId id, const Str
 	return properties.RegisterShorthand(shorthand_name, property_names, type, id);
 }
 
-bool StyleSheetSpecification::Initialise()
+void StyleSheetSpecification::Initialise()
 {
-	if (instance == nullptr)
-	{
-		new StyleSheetSpecification();
+	RMLUI_ASSERT(!instance);
 
-		instance->RegisterDefaultParsers();
-		instance->RegisterDefaultProperties();
-	}
+	PropertyParserAnimation::Initialize();
+	PropertyParserColour::Initialize();
+	PropertyParserDecorator::Initialize();
+	PropertyParserNumber::Initialize();
 
-	return true;
+	new StyleSheetSpecification();
+
+	instance->RegisterDefaultParsers();
+	instance->RegisterDefaultProperties();
 }
 
 void StyleSheetSpecification::Shutdown()
 {
-	if (instance != nullptr)
-	{
-		delete instance;
-	}
+	RMLUI_ASSERT(instance);
+
+	delete instance;
+
+	PropertyParserAnimation::Shutdown();
+	PropertyParserColour::Shutdown();
+	PropertyParserDecorator::Shutdown();
+	PropertyParserNumber::Shutdown();
 }
 
 bool StyleSheetSpecification::RegisterParser(const String& parser_name, PropertyParser* parser)
@@ -422,10 +428,10 @@ void StyleSheetSpecification::RegisterDefaultProperties()
 	RegisterProperty(PropertyId::Decorator, "decorator", "", false, false).AddParser("decorator");
 	RegisterProperty(PropertyId::MaskImage, "mask-image", "", false, false).AddParser("decorator");
 	RegisterProperty(PropertyId::FontEffect, "font-effect", "", true, false).AddParser("font_effect");
-		
+
 	RegisterProperty(PropertyId::Filter, "filter", "", false, false).AddParser("filter", "filter");
 	RegisterProperty(PropertyId::BackdropFilter, "backdrop-filter", "", false, false).AddParser("filter");
-	
+
 	RegisterProperty(PropertyId::BoxShadow, "box-shadow", "none", false, false).AddParser("box_shadow");
 
 	// Rare properties (not added to computed values)
@@ -435,7 +441,7 @@ void StyleSheetSpecification::RegisterDefaultProperties()
 	RegisterProperty(PropertyId::AlignContent, "align-content", "stretch", false, true).AddParser("keyword", "flex-start, flex-end, center, space-between, space-around, space-evenly, stretch");
 	RegisterProperty(PropertyId::AlignItems, "align-items", "stretch", false, true).AddParser("keyword", "flex-start, flex-end, center, baseline, stretch");
 	RegisterProperty(PropertyId::AlignSelf, "align-self", "auto", false, true).AddParser("keyword", "auto, flex-start, flex-end, center, baseline, stretch");
-	
+
 	RegisterProperty(PropertyId::FlexBasis, "flex-basis", "auto", false, true).AddParser("keyword", "auto").AddParser("length_percent");
 	RegisterProperty(PropertyId::FlexDirection, "flex-direction", "row", false, true).AddParser("keyword", "row, row-reverse, column, column-reverse");
 

+ 7 - 3
Source/Core/SystemInterface.cpp

@@ -35,7 +35,11 @@
 
 namespace Rml {
 
-static String clipboard_text;
+static String& GlobalClipBoardText()
+{
+	static String clipboard_text;
+	return clipboard_text;
+}
 
 SystemInterface::SystemInterface() {}
 
@@ -59,12 +63,12 @@ void SystemInterface::SetMouseCursor(const String& /*cursor_name*/) {}
 void SystemInterface::SetClipboardText(const String& text)
 {
 	// The default implementation will only copy and paste within the application
-	clipboard_text = text;
+	GlobalClipBoardText() = text;
 }
 
 void SystemInterface::GetClipboardText(String& text)
 {
-	text = clipboard_text;
+	text = GlobalClipBoardText();
 }
 
 int SystemInterface::TranslateString(String& translated, const String& input)

+ 22 - 16
Source/Core/XMLParser.cpp

@@ -34,13 +34,17 @@
 #include "../../Include/RmlUi/Core/Types.h"
 #include "../../Include/RmlUi/Core/URL.h"
 #include "../../Include/RmlUi/Core/XMLNodeHandler.h"
+#include "ControlledLifetimeResource.h"
 #include "DocumentHeader.h"
 
 namespace Rml {
 
-using NodeHandlers = UnorderedMap<String, SharedPtr<XMLNodeHandler>>;
-static NodeHandlers node_handlers;
-static SharedPtr<XMLNodeHandler> default_node_handler;
+struct XmlParserData {
+	UnorderedMap<String, SharedPtr<XMLNodeHandler>> node_handlers;
+	SharedPtr<XMLNodeHandler> default_node_handler;
+};
+
+static ControlledLifetimeResource<XmlParserData> xml_parser_data;
 
 XMLParser::XMLParser(Element* root)
 {
@@ -64,24 +68,27 @@ XMLParser::~XMLParser() {}
 
 XMLNodeHandler* XMLParser::RegisterNodeHandler(const String& _tag, SharedPtr<XMLNodeHandler> handler)
 {
+	if (!xml_parser_data)
+		xml_parser_data.Initialize();
+
 	String tag = StringUtilities::ToLower(_tag);
 
 	// Check for a default node registration.
 	if (tag.empty())
 	{
-		default_node_handler = std::move(handler);
-		return default_node_handler.get();
+		xml_parser_data->default_node_handler = std::move(handler);
+		return xml_parser_data->default_node_handler.get();
 	}
 
 	XMLNodeHandler* result = handler.get();
-	node_handlers[tag] = std::move(handler);
+	xml_parser_data->node_handlers[tag] = std::move(handler);
 	return result;
 }
 
 XMLNodeHandler* XMLParser::GetNodeHandler(const String& tag)
 {
-	auto it = node_handlers.find(tag);
-	if (it != node_handlers.end())
+	auto it = xml_parser_data->node_handlers.find(tag);
+	if (it != xml_parser_data->node_handlers.end())
 		return it->second.get();
 
 	return nullptr;
@@ -89,8 +96,7 @@ XMLNodeHandler* XMLParser::GetNodeHandler(const String& tag)
 
 void XMLParser::ReleaseHandlers()
 {
-	default_node_handler.reset();
-	node_handlers.clear();
+	xml_parser_data.Shutdown();
 }
 
 DocumentHeader* XMLParser::GetDocumentHeader()
@@ -100,16 +106,16 @@ DocumentHeader* XMLParser::GetDocumentHeader()
 
 void XMLParser::PushDefaultHandler()
 {
-	active_handler = default_node_handler.get();
+	active_handler = xml_parser_data->default_node_handler.get();
 }
 
 bool XMLParser::PushHandler(const String& tag)
 {
-	NodeHandlers::iterator i = node_handlers.find(StringUtilities::ToLower(tag));
-	if (i == node_handlers.end())
+	auto it = xml_parser_data->node_handlers.find(StringUtilities::ToLower(tag));
+	if (it == xml_parser_data->node_handlers.end())
 		return false;
 
-	active_handler = i->second.get();
+	active_handler = it->second.get();
 	return true;
 }
 
@@ -130,8 +136,8 @@ void XMLParser::HandleElementStart(const String& _name, const XMLAttributes& att
 	const String name = StringUtilities::ToLower(_name);
 
 	// Check for a specific handler that will override the child handler.
-	NodeHandlers::iterator itr = node_handlers.find(name);
-	if (itr != node_handlers.end())
+	auto itr = xml_parser_data->node_handlers.find(name);
+	if (itr != xml_parser_data->node_handlers.end())
 		active_handler = itr->second.get();
 
 	// Store the current active handler, so we can use it through this function (as active handler may change)

+ 2 - 2
Tests/Source/Common/TestsShell.cpp

@@ -164,10 +164,10 @@ void TestsShell::ShutdownShell()
 		(void)num_documents_begin;
 	}
 
-	tests_system_interface.SetNumExpectedWarnings(0);
-
 	Rml::Shutdown();
 
+	tests_system_interface.SetNumExpectedWarnings(0);
+
 #ifdef RMLUI_TESTS_USE_SHELL
 	Backend::Shutdown();
 #else

+ 15 - 0
Tests/Source/UnitTests/Core.cpp

@@ -264,3 +264,18 @@ TEST_CASE("core.initialize")
 
 	Rml::Shutdown();
 }
+
+TEST_CASE("core.observer_ptr")
+{
+	Context* context = TestsShell::GetContext();
+	ElementDocument* document = context->LoadDocument("assets/demo.rml");
+
+	ObserverPtr<Element> observer_ptr = document->GetObserverPtr();
+	document->Close();
+
+	// We expect a warning about the observer pointer being held in user space, preventing its memory pool from shutting down.
+	TestsSystemInterface* system_interface = TestsShell::GetTestsSystemInterface();
+	system_interface->SetNumExpectedWarnings(1);
+
+	TestsShell::ShutdownShell();
+}

+ 20 - 16
Tests/Source/UnitTests/ElementDocument.cpp

@@ -260,28 +260,32 @@ TEST_CASE("Load")
 	Context* context = TestsShell::GetContext();
 	REQUIRE(context);
 
-	MockEventListener mockEventListener;
-	MockEventListenerInstancer mockEventListenerInstancer;
+	{
+		MockEventListener mockEventListener;
+		MockEventListenerInstancer mockEventListenerInstancer;
 
-	tl::sequence sequence;
-	REQUIRE_CALL(mockEventListenerInstancer, InstanceEventListener("something", tl::_))
-		.WITH(_2->GetTagName() == BODY_TAG)
-		.IN_SEQUENCE(sequence)
-		.LR_RETURN(&mockEventListener);
+		tl::sequence sequence;
+		REQUIRE_CALL(mockEventListenerInstancer, InstanceEventListener("something", tl::_))
+			.WITH(_2->GetTagName() == BODY_TAG)
+			.IN_SEQUENCE(sequence)
+			.LR_RETURN(&mockEventListener);
 
-	ALLOW_CALL(mockEventListener, OnAttach(tl::_));
-	ALLOW_CALL(mockEventListener, OnDetach(tl::_));
+		ALLOW_CALL(mockEventListener, OnAttach(tl::_));
+		ALLOW_CALL(mockEventListener, OnDetach(tl::_));
 
-	REQUIRE_CALL(mockEventListener, ProcessEvent(tl::_))
-		.WITH(_1.GetId() == EventId::Load && _1.GetTargetElement()->GetTagName() == BODY_TAG)
-		.IN_SEQUENCE(sequence);
+		REQUIRE_CALL(mockEventListener, ProcessEvent(tl::_))
+			.WITH(_1.GetId() == EventId::Load && _1.GetTargetElement()->GetTagName() == BODY_TAG)
+			.IN_SEQUENCE(sequence);
 
-	Factory::RegisterEventListenerInstancer(&mockEventListenerInstancer);
+		Factory::RegisterEventListenerInstancer(&mockEventListenerInstancer);
 
-	ElementDocument* document = context->LoadDocumentFromMemory(document_focus_rml);
-	REQUIRE(document);
+		ElementDocument* document = context->LoadDocumentFromMemory(document_focus_rml);
+		REQUIRE(document);
+
+		document->Close();
+		context->Update();
+	}
 
-	document->Close();
 	TestsShell::ShutdownShell();
 }
 

+ 2 - 0
Tests/Source/UnitTests/ElementFormControlSelect.cpp

@@ -568,6 +568,8 @@ TEST_CASE("form.select.event.change")
 	CHECK(listener->num_events_processed == 3);
 
 	document->Close();
+	context->Update();
+	listener.reset();
 
 	TestsShell::ShutdownShell();
 }