From 66a0638ec402b4cb5b9cd6415255acebbf4b6afe Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Sat, 5 Oct 2024 17:41:15 +0200 Subject: [PATCH] Avoid allocations during global initialization 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. --- Include/RmlUi/Core/Core.h | 3 - Include/RmlUi/Core/ElementInstancer.h | 5 + Include/RmlUi/Core/Factory.h | 2 +- Include/RmlUi/Core/ObserverPtr.h | 28 +- Include/RmlUi/Core/StyleSheetSpecification.h | 3 +- Include/RmlUi/Core/XMLParser.h | 8 +- Source/Core/CMakeLists.txt | 3 + Source/Core/ComputeProperty.cpp | 23 +- Source/Core/ComputeProperty.h | 5 +- Source/Core/ControlledLifetimeResource.h | 86 ++++++ Source/Core/Core.cpp | 161 +++++------ Source/Core/Element.cpp | 19 +- Source/Core/ElementInstancer.cpp | 35 ++- Source/Core/ElementMeta.cpp | 54 ++++ Source/Core/ElementMeta.h | 67 +++++ Source/Core/ElementStyle.cpp | 10 +- Source/Core/EventSpecification.cpp | 112 ++++---- Source/Core/EventSpecification.h | 1 + Source/Core/Factory.cpp | 264 ++++++++---------- Source/Core/Layout/LayoutPools.cpp | 32 ++- Source/Core/Layout/LayoutPools.h | 3 + Source/Core/ObserverPtr.cpp | 47 +++- Source/Core/PluginRegistry.cpp | 81 +++--- Source/Core/PluginRegistry.h | 2 +- Source/Core/PropertyParserAnimation.cpp | 170 +++++------ Source/Core/PropertyParserAnimation.h | 10 + Source/Core/PropertyParserColour.cpp | 60 ++-- Source/Core/PropertyParserColour.h | 7 +- Source/Core/PropertyParserDecorator.cpp | 27 +- Source/Core/PropertyParserDecorator.h | 6 +- Source/Core/PropertyParserNumber.cpp | 52 ++-- Source/Core/PropertyParserNumber.h | 6 + Source/Core/StyleSheetContainer.cpp | 2 +- Source/Core/StyleSheetParser.cpp | 39 +-- Source/Core/StyleSheetSpecification.cpp | 36 ++- Source/Core/SystemInterface.cpp | 10 +- Source/Core/XMLParser.cpp | 38 +-- Tests/Source/Common/TestsShell.cpp | 4 +- Tests/Source/UnitTests/Core.cpp | 15 + Tests/Source/UnitTests/ElementDocument.cpp | 36 +-- .../UnitTests/ElementFormControlSelect.cpp | 2 + 41 files changed, 982 insertions(+), 592 deletions(-) create mode 100644 Source/Core/ControlledLifetimeResource.h create mode 100644 Source/Core/ElementMeta.cpp create mode 100644 Source/Core/ElementMeta.h diff --git a/Include/RmlUi/Core/Core.h b/Include/RmlUi/Core/Core.h index 1cd1990b0..fddac4ed5 100644 --- a/Include/RmlUi/Core/Core.h +++ b/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 diff --git a/Include/RmlUi/Core/ElementInstancer.h b/Include/RmlUi/Core/ElementInstancer.h index 36c91b6a3..c221c66de 100644 --- a/Include/RmlUi/Core/ElementInstancer.h +++ b/Include/RmlUi/Core/ElementInstancer.h @@ -117,5 +117,10 @@ class ElementInstancerGeneric : public ElementInstancer { } }; +namespace Detail { + void InitializeElementInstancerPools(); + void ShutdownElementInstancerPools(); +} // namespace Detail + } // namespace Rml #endif diff --git a/Include/RmlUi/Core/Factory.h b/Include/RmlUi/Core/Factory.h index c222fdba2..29d21ab3b 100644 --- a/Include/RmlUi/Core/Factory.h +++ b/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(); diff --git a/Include/RmlUi/Core/ObserverPtr.h b/Include/RmlUi/Core/ObserverPtr.h index 4e7a3aff9..19386abf7 100644 --- a/Include/RmlUi/Core/ObserverPtr.h +++ b/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 class EnableObserverPtr; @@ -117,7 +121,7 @@ class RMLUICORE_API ObserverPtr { if (block) { block->num_observers -= 1; - DeallocateObserverPtrBlockIfEmpty(block); + Detail::DeallocateObserverPtrBlockIfEmpty(block); block = nullptr; } } @@ -125,13 +129,13 @@ class RMLUICORE_API ObserverPtr { private: friend class Rml::EnableObserverPtr; - 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 @@ -151,7 +155,7 @@ class RMLUICORE_API EnableObserverPtr { if (block) { block->pointed_to_object = nullptr; - DeallocateObserverPtrBlockIfEmpty(block); + Detail::DeallocateObserverPtrBlockIfEmpty(block); } } @@ -180,13 +184,13 @@ class RMLUICORE_API EnableObserverPtr { { if (!block) { - block = AllocateObserverPtrBlock(); + block = Detail::AllocateObserverPtrBlock(); block->num_observers = 0; block->pointed_to_object = static_cast(static_cast(this)); } } - ObserverPtrBlock* block = nullptr; + Detail::ObserverPtrBlock* block = nullptr; }; } // namespace Rml diff --git a/Include/RmlUi/Core/StyleSheetSpecification.h b/Include/RmlUi/Core/StyleSheetSpecification.h index 196b237d8..bfef9ebdc 100644 --- a/Include/RmlUi/Core/StyleSheetSpecification.h +++ b/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(); diff --git a/Include/RmlUi/Core/XMLParser.h b/Include/RmlUi/Core/XMLParser.h index ad788b29d..ffb62256f 100644 --- a/Include/RmlUi/Core/XMLParser.h +++ b/Include/RmlUi/Core/XMLParser.h @@ -104,15 +104,9 @@ class RMLUICORE_API XMLParser : public BaseXMLParser { void HandleData(const String& data, XMLDataType type) override; private: - // The header of the document being parsed. UniquePtr header; - - // The active node handler. XMLNodeHandler* active_handler; - - // The parser stack. - using ParserStack = Stack; - ParserStack stack; + Stack stack; }; } // namespace Rml diff --git a/Source/Core/CMakeLists.txt b/Source/Core/CMakeLists.txt index 99bebaa98..8c743d6b5 100644 --- a/Source/Core/CMakeLists.txt +++ b/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 diff --git a/Source/Core/ComputeProperty.cpp b/Source/Core/ComputeProperty.cpp index cfcfc15c4..07e73279b 100644 --- a/Source/Core/ComputeProperty.cpp +++ b/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 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(); diff --git a/Source/Core/ComputeProperty.h b/Source/Core/ComputeProperty.h index a681513b8..cd7748b19 100644 --- a/Source/Core/ComputeProperty.h +++ b/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 diff --git a/Source/Core/ControlledLifetimeResource.h b/Source/Core/ControlledLifetimeResource.h new file mode 100644 index 000000000..645df6a36 --- /dev/null +++ b/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 +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 diff --git a/Source/Core/Core.cpp b/Source/Core/Core.cpp index 08722f407..fc4f5fe63 100644 --- a/Source/Core/Core.cpp +++ b/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 default_system_interface; -static UniquePtr default_file_interface; -static UniquePtr default_font_interface; -static UniquePtr default_text_input_handler; +struct CoreData { + // Default interfaces should be created and destroyed on Initialise and Shutdown, respectively. + UniquePtr default_system_interface; + UniquePtr default_file_interface; + UniquePtr default_font_interface; + UniquePtr default_text_input_handler; -static UniquePtr>> render_managers; + SmallUnorderedMap> render_managers; + UnorderedMap contexts; +}; -static bool initialised = false; +static ControlledLifetimeResource core_data; -using ContextMap = UnorderedMap; -static ContextMap contexts; +static bool initialised = false; -// The ObserverPtrBlock pool -extern Pool* 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(); - system_interface = default_system_interface.get(); + core_data->default_system_interface = MakeUnique(); + system_interface = core_data->default_system_interface.get(); } if (!file_interface) { #ifndef RMLUI_NO_FILE_INTERFACE_DEFAULT - default_file_interface = MakeUnique(); - file_interface = default_file_interface.get(); + core_data->default_file_interface = MakeUnique(); + 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(); - font_interface = default_font_interface.get(); + core_data->default_font_interface = MakeUnique(); + 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(); - text_input_handler = default_text_input_handler.get(); + core_data->default_text_input_handler = MakeUnique(); + text_input_handler = core_data->default_text_input_handler.get(); } EventSpecificationInterface::Initialize(); - render_managers = MakeUnique>>(); + Detail::InitializeObserverPtrPool(); + if (render_interface) - (*render_managers)[render_interface] = MakeUnique(render_interface); + core_data->render_managers[render_interface] = MakeUnique(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(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 diff --git a/Source/Core/Element.cpp b/Source/Core/Element.cpp index 75d75a657..6dea91e32 100644 --- a/Source/Core/Element.cpp +++ b/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 attribute_event_listeners; - EventDispatcher event_dispatcher; - ElementStyle style; - ElementBackgroundBorder background_border; - ElementEffects effects; - ElementScroll scroll; - Style::ComputedValues computed_values; -}; - -static Pool 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) diff --git a/Source/Core/ElementInstancer.cpp b/Source/Core/ElementInstancer.cpp index 46fda3961..6ad7d8b61 100644 --- a/Source/Core/ElementInstancer.cpp +++ b/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 pool_element(200, true); -static Pool pool_text_default(200, true); +struct ElementInstancerPools { + Pool pool_element{200, true}; + Pool pool_text_default{200, true}; + + bool IsEmpty() const { return pool_element.GetNumAllocatedObjects() == 0 && pool_text_default.GetNumAllocatedObjects() == 0; } +}; +static ControlledLifetimeResource 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(ptr)); } void ElementInstancerText::ReleaseElement(Element* element) { - pool_text_default.DestroyAndDeallocate(rmlui_static_cast(element)); + element_instancer_pools->pool_text_default.DestroyAndDeallocate(rmlui_static_cast(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 diff --git a/Source/Core/ElementMeta.cpp b/Source/Core/ElementMeta.cpp new file mode 100644 index 000000000..05f20a09b --- /dev/null +++ b/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::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 diff --git a/Source/Core/ElementMeta.h b/Source/Core/ElementMeta.h new file mode 100644 index 000000000..859cc18ed --- /dev/null +++ b/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 attribute_event_listeners; + EventDispatcher event_dispatcher; + ElementStyle style; + ElementBackgroundBorder background_border; + ElementEffects effects; + ElementScroll scroll; + Style::ComputedValues computed_values; +}; + +struct ElementMetaPool { + Pool pool{50, true}; + + static ControlledLifetimeResource element_meta_pool; + static void Initialize(); + static void Shutdown(); +}; + +} // namespace Rml +#endif diff --git a/Source/Core/ElementStyle.cpp b/Source/Core/ElementStyle.cpp index 644d17e96..db029f502 100644 --- a/Source/Core/ElementStyle.cpp +++ b/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)) diff --git a/Source/Core/EventSpecification.cpp b/Source/Core/EventSpecification.cpp index fc0f1aca3..1650bc924 100644 --- a/Source/Core/EventSpecification.cpp +++ b/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 specifications = {{EventId::Invalid, "invalid", false, false, DefaultActionPhase::None}}; - -// Reverse lookup map from event type to id. -static UnorderedMap 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 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 type_lookup; +}; + +static ControlledLifetimeResource 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(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()); diff --git a/Source/Core/EventSpecification.h b/Source/Core/EventSpecification.h index a45b4477a..9c5690521 100644 --- a/Source/Core/EventSpecification.h +++ b/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. diff --git a/Source/Core/Factory.cpp b/Source/Core/Factory.cpp index 1c18f787f..a3ff7f0bd 100644 --- a/Source/Core/Factory.cpp +++ b/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; -static ElementInstancerMap element_instancers; - -// Decorator instancers. -using DecoratorInstancerMap = UnorderedMap; -static DecoratorInstancerMap decorator_instancers; - -// Filter instancers. -using FilterInstancerMap = UnorderedMap; -static FilterInstancerMap filter_instancers; - -// Font effect instancers. -using FontEffectInstancerMap = UnorderedMap; -static FontEffectInstancerMap font_effect_instancers; - -// Data view instancers. -using DataViewInstancerMap = UnorderedMap; -static DataViewInstancerMap data_view_instancers; - -// Data controller instancers. -using DataControllerInstancerMap = UnorderedMap; -static DataControllerInstancerMap data_controller_instancers; - -// Structural data view instancers. -using StructuralDataViewInstancerMap = SmallUnorderedMap; -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 context_default; @@ -192,28 +153,46 @@ struct DefaultInstancers { DataControllerInstancerDefault data_controller_value; }; -static UniquePtr default_instancers; +struct FactoryData { + DefaultInstancers default_instancers; + UnorderedMap element_instancers; + UnorderedMap decorator_instancers; + UnorderedMap filter_instancers; + UnorderedMap font_effect_instancers; + UnorderedMap data_view_instancers; + UnorderedMap data_controller_instancers; + SmallUnorderedMap structural_data_view_instancers; + StringList structural_data_view_attribute_names; +}; + +static ControlledLifetimeResource 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(); + factory_data.Initialize(); + + DefaultInstancers& default_instancers = factory_data->default_instancers; // Default context instancer if (!context_instancer) { - default_instancers->context_default = MakeUnique(); - context_instancer = default_instancers->context_default.get(); + default_instancers.context_default = MakeUnique(); + context_instancer = default_instancers.context_default.get(); } // Default event instancer if (!event_instancer) { - default_instancers->event_default = MakeUnique(); - event_instancer = default_instancers->event_default.get(); + default_instancers.event_default = MakeUnique(); + 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()); @@ -309,32 +288,17 @@ bool Factory::Initialise() XMLParser::RegisterNodeHandler("tabset", MakeShared()); XMLParser::RegisterNodeHandler("textarea", MakeShared()); XMLParser::RegisterNodeHandler("select", MakeShared()); - - 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 diff --git a/Source/Core/Layout/LayoutPools.cpp b/Source/Core/Layout/LayoutPools.cpp index 82308d8f9..d3438515c 100644 --- a/Source/Core/Layout/LayoutPools.cpp +++ b/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> layout_chunk_pool_big(50, true); -static Pool> layout_chunk_pool_medium(50, true); -static Pool> layout_chunk_pool_small(50, true); +struct LayoutPoolsData { + Pool> layout_chunk_pool_big{50, true}; + Pool> layout_chunk_pool_medium{50, true}; + Pool> layout_chunk_pool_small{50, true}; +}; + +static ControlledLifetimeResource 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*)chunk); + layout_pools_data->layout_chunk_pool_small.DestroyAndDeallocate((LayoutChunk*)chunk); else if (size <= ChunkSizeMedium) - layout_chunk_pool_medium.DestroyAndDeallocate((LayoutChunk*)chunk); + layout_pools_data->layout_chunk_pool_medium.DestroyAndDeallocate((LayoutChunk*)chunk); else if (size <= ChunkSizeBig) - layout_chunk_pool_big.DestroyAndDeallocate((LayoutChunk*)chunk); + layout_pools_data->layout_chunk_pool_big.DestroyAndDeallocate((LayoutChunk*)chunk); else { RMLUI_ERROR; diff --git a/Source/Core/Layout/LayoutPools.h b/Source/Core/Layout/LayoutPools.h index e6b586282..bd05ccdf6 100644 --- a/Source/Core/Layout/LayoutPools.h +++ b/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); diff --git a/Source/Core/ObserverPtr.cpp b/Source/Core/ObserverPtr.cpp index 68b99be21..ce859023e 100644 --- a/Source/Core/ObserverPtr.cpp +++ b/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* observerPtrBlockPool = nullptr; +struct ObserverPtrData { + Pool block_pool{128, true}; +}; +static ControlledLifetimeResource observer_ptr_data; -static Pool& 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(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 diff --git a/Source/Core/PluginRegistry.cpp b/Source/Core/PluginRegistry.cpp index 343466839..4b77e2c3f 100644 --- a/Source/Core/PluginRegistry.cpp +++ b/Source/Core/PluginRegistry.cpp @@ -28,99 +28,116 @@ #include "PluginRegistry.h" #include "../../Include/RmlUi/Core/Plugin.h" +#include "ControlledLifetimeResource.h" #include namespace Rml { -typedef Vector PluginList; -static PluginList basic_plugins; -static PluginList document_plugins; -static PluginList element_plugins; +struct PluginVectors { + Vector basic; + Vector document; + Vector element; +}; -PluginRegistry::PluginRegistry() {} +static ControlledLifetimeResource 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& 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 diff --git a/Source/Core/PluginRegistry.h b/Source/Core/PluginRegistry.h index 484ac6859..33b544f1f 100644 --- a/Source/Core/PluginRegistry.h +++ b/Source/Core/PluginRegistry.h @@ -70,7 +70,7 @@ class PluginRegistry { static void NotifyElementDestroy(Element* element); private: - PluginRegistry(); + PluginRegistry() = delete; }; } // namespace Rml diff --git a/Source/Core/PropertyParserAnimation.cpp b/Source/Core/PropertyParserAnimation.cpp index d9faaed35..6121f075b 100644 --- a/Source/Core/PropertyParserAnimation.cpp +++ b/Source/Core/PropertyParserAnimation.cpp @@ -51,61 +51,97 @@ struct Keyword { } }; -static const UnorderedMap 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 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 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 or 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 or 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 diff --git a/Source/Core/PropertyParserAnimation.h b/Source/Core/PropertyParserAnimation.h index 24117bfb4..681469026 100644 --- a/Source/Core/PropertyParserAnimation.h +++ b/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 @@ class PropertyParserAnimation : public PropertyParser { /// @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 parser_data; }; } // namespace Rml diff --git a/Source/Core/PropertyParserColour.cpp b/Source/Core/PropertyParserColour.cpp index 03b842d88..a4e6f1077 100644 --- a/Source/Core/PropertyParserColour.cpp +++ b/Source/Core/PropertyParserColour.cpp @@ -27,6 +27,7 @@ */ #include "PropertyParserColour.h" +#include "ControlledLifetimeResource.h" #include #include #include @@ -61,28 +62,41 @@ static void HSLAToRGBA(Array& 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 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 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; diff --git a/Source/Core/PropertyParserColour.h b/Source/Core/PropertyParserColour.h index 4450d7dfa..12319f6e1 100644 --- a/Source/Core/PropertyParserColour.h +++ b/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 @@ class PropertyParserColour : public PropertyParser { /// Parse a colour directly. static bool ParseColour(Colourb& colour, const String& value); + static void Initialize(); + static void Shutdown(); + private: - using ColourMap = UnorderedMap; - static const ColourMap html_colours; + static ControlledLifetimeResource parser_data; }; } // namespace Rml diff --git a/Source/Core/PropertyParserDecorator.cpp b/Source/Core/PropertyParserDecorator.cpp index 950cdddc7..de2494a18 100644 --- a/Source/Core/PropertyParserDecorator.cpp +++ b/Source/Core/PropertyParserDecorator.cpp @@ -35,12 +35,25 @@ namespace Rml { -const SmallUnorderedMap PropertyParserDecorator::area_keywords = { - {"border-box", BoxArea::Border}, - {"padding-box", BoxArea::Padding}, - {"content-box", BoxArea::Content}, +struct PropertyParserDecoratorData { + const SmallUnorderedMap area_keywords = { + {"border-box", BoxArea::Border}, + {"padding-box", BoxArea::Padding}, + {"content-box", BoxArea::Content}, + }; }; +ControlledLifetimeResource 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; diff --git a/Source/Core/PropertyParserDecorator.h b/Source/Core/PropertyParserDecorator.h index 892546e65..236889601 100644 --- a/Source/Core/PropertyParserDecorator.h +++ b/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 @@ class PropertyParserDecorator : public PropertyParser { static String ConvertAreaToString(BoxArea area); + static void Initialize(); + static void Shutdown(); + private: - static const SmallUnorderedMap area_keywords; + static ControlledLifetimeResource parser_data; }; } // namespace Rml diff --git a/Source/Core/PropertyParserNumber.cpp b/Source/Core/PropertyParserNumber.cpp index 26b781633..7fd64db35 100644 --- a/Source/Core/PropertyParserNumber.cpp +++ b/Source/Core/PropertyParserNumber.cpp @@ -31,25 +31,39 @@ namespace Rml { -static const UnorderedMap 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 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 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; diff --git a/Source/Core/PropertyParserNumber.h b/Source/Core/PropertyParserNumber.h index 45da9abed..cb0276161 100644 --- a/Source/Core/PropertyParserNumber.h +++ b/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 @@ class PropertyParserNumber : public PropertyParser { /// @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 parser_data; + // Stores a bit mask of allowed units. Units units; diff --git a/Source/Core/StyleSheetContainer.cpp b/Source/Core/StyleSheetContainer.cpp index 4e6e6a2fd..c6e1c0c2c 100644 --- a/Source/Core/StyleSheetContainer.cpp +++ b/Source/Core/StyleSheetContainer.cpp @@ -59,7 +59,7 @@ bool StyleSheetContainer::UpdateCompiledStyleSheet(const Context* context) Vector 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++) { diff --git a/Source/Core/StyleSheetParser.cpp b/Source/Core/StyleSheetParser.cpp index 603aed5a5..6ac1f0464 100644 --- a/Source/Core/StyleSheetParser.cpp +++ b/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 @@ -158,8 +159,6 @@ class SpritesheetPropertyParser final : public AbstractPropertyParser { } }; -static UniquePtr 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 @@ class MediaQueryPropertyParser final : public AbstractPropertyParser { } }; -static UniquePtr 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 style_sheet_property_parsers; StyleSheetParser::StyleSheetParser() { @@ -221,14 +226,12 @@ StyleSheetParser::~StyleSheetParser() {} void StyleSheetParser::Initialise() { - spritesheet_property_parser = MakeUnique(); - media_query_property_parser = MakeUnique(); + 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; } diff --git a/Source/Core/StyleSheetSpecification.cpp b/Source/Core/StyleSheetSpecification.cpp index be53a44c3..434fc3fe5 100644 --- a/Source/Core/StyleSheetSpecification.cpp +++ b/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"); diff --git a/Source/Core/SystemInterface.cpp b/Source/Core/SystemInterface.cpp index 7159b4e90..1e64d6d75 100644 --- a/Source/Core/SystemInterface.cpp +++ b/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) diff --git a/Source/Core/XMLParser.cpp b/Source/Core/XMLParser.cpp index a4d9b4268..7c4af7920 100644 --- a/Source/Core/XMLParser.cpp +++ b/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>; -static NodeHandlers node_handlers; -static SharedPtr default_node_handler; +struct XmlParserData { + UnorderedMap> node_handlers; + SharedPtr default_node_handler; +}; + +static ControlledLifetimeResource xml_parser_data; XMLParser::XMLParser(Element* root) { @@ -64,24 +68,27 @@ XMLParser::~XMLParser() {} XMLNodeHandler* XMLParser::RegisterNodeHandler(const String& _tag, SharedPtr 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) diff --git a/Tests/Source/Common/TestsShell.cpp b/Tests/Source/Common/TestsShell.cpp index 72c75fcc7..fa10c4500 100644 --- a/Tests/Source/Common/TestsShell.cpp +++ b/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 diff --git a/Tests/Source/UnitTests/Core.cpp b/Tests/Source/UnitTests/Core.cpp index 78ab8b59f..f0bf90616 100644 --- a/Tests/Source/UnitTests/Core.cpp +++ b/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 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(); +} diff --git a/Tests/Source/UnitTests/ElementDocument.cpp b/Tests/Source/UnitTests/ElementDocument.cpp index 6400393a1..0162dd5d3 100644 --- a/Tests/Source/UnitTests/ElementDocument.cpp +++ b/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(); } diff --git a/Tests/Source/UnitTests/ElementFormControlSelect.cpp b/Tests/Source/UnitTests/ElementFormControlSelect.cpp index b7f62f6ab..f2f177259 100644 --- a/Tests/Source/UnitTests/ElementFormControlSelect.cpp +++ b/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(); }