Skip to content

Commit

Permalink
Avoid allocations during global initialization
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
mikke89 committed Oct 5, 2024
1 parent 14bf5fc commit cc85823
Show file tree
Hide file tree
Showing 42 changed files with 998 additions and 592 deletions.
3 changes: 0 additions & 3 deletions Include/RmlUi/Core/Core.h
Original file line number Diff line number Diff line change
Expand Up @@ -180,9 +180,6 @@ RMLUICORE_API void ReleaseCompiledGeometry(RenderInterface* render_interface = n
/// @note Invalidates all existing FontFaceHandles returned from the font engine.
RMLUICORE_API void ReleaseFontResources();

/// Forces all memory pools used by RmlUi to be released.
RMLUICORE_API void ReleaseMemoryPools();

} // namespace Rml

#endif
5 changes: 5 additions & 0 deletions Include/RmlUi/Core/ElementInstancer.h
Original file line number Diff line number Diff line change
Expand Up @@ -117,5 +117,10 @@ class ElementInstancerGeneric : public ElementInstancer {
}
};

namespace Detail {
void InitializeElementInstancerPools();
void ShutdownElementInstancerPools();
} // namespace Detail

} // namespace Rml
#endif
2 changes: 1 addition & 1 deletion Include/RmlUi/Core/Factory.h
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
28 changes: 16 additions & 12 deletions Include/RmlUi/Core/ObserverPtr.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,16 @@

namespace Rml {

struct RMLUICORE_API ObserverPtrBlock {
int num_observers;
void* pointed_to_object;
};
RMLUICORE_API ObserverPtrBlock* AllocateObserverPtrBlock();
RMLUICORE_API void DeallocateObserverPtrBlockIfEmpty(ObserverPtrBlock* block);
namespace Detail {
struct RMLUICORE_API ObserverPtrBlock {
int num_observers;
void* pointed_to_object;
};
RMLUICORE_API ObserverPtrBlock* AllocateObserverPtrBlock();
RMLUICORE_API void DeallocateObserverPtrBlockIfEmpty(ObserverPtrBlock* block);
void InitializeObserverPtrPool();
void ShutdownObserverPtrPool();
} // namespace Detail

template <typename T>
class EnableObserverPtr;
Expand Down Expand Up @@ -117,21 +121,21 @@ class RMLUICORE_API ObserverPtr {
if (block)
{
block->num_observers -= 1;
DeallocateObserverPtrBlockIfEmpty(block);
Detail::DeallocateObserverPtrBlockIfEmpty(block);
block = nullptr;
}
}

private:
friend class Rml::EnableObserverPtr<T>;

explicit ObserverPtr(ObserverPtrBlock* block) noexcept : block(block)
explicit ObserverPtr(Detail::ObserverPtrBlock* block) noexcept : block(block)
{
if (block)
block->num_observers += 1;
}

ObserverPtrBlock* block;
Detail::ObserverPtrBlock* block;
};

template <typename T>
Expand All @@ -151,7 +155,7 @@ class RMLUICORE_API EnableObserverPtr {
if (block)
{
block->pointed_to_object = nullptr;
DeallocateObserverPtrBlockIfEmpty(block);
Detail::DeallocateObserverPtrBlockIfEmpty(block);
}
}

Expand Down Expand Up @@ -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<void*>(static_cast<T*>(this));
}
}

ObserverPtrBlock* block = nullptr;
Detail::ObserverPtrBlock* block = nullptr;
};

} // namespace Rml
Expand Down
3 changes: 1 addition & 2 deletions Include/RmlUi/Core/StyleSheetSpecification.h
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
8 changes: 1 addition & 7 deletions Include/RmlUi/Core/XMLParser.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<DocumentHeader> header;

// The active node handler.
XMLNodeHandler* active_handler;

// The parser stack.
using ParserStack = Stack<ParseFrame>;
ParserStack stack;
Stack<ParseFrame> stack;
};

} // namespace Rml
Expand Down
16 changes: 16 additions & 0 deletions Samples/basic/load_document/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,29 @@
#include <RmlUi_Backend.h>
#include <Shell.h>

void* operator new(size_t n)
{
// Overload global new and delete for memory inspection
void* ptr = std::malloc(n);
return ptr;
}

void operator delete(void* ptr) noexcept
{
std::free(ptr);
}

#if defined RMLUI_PLATFORM_WIN32
#include <RmlUi_Include_Windows.h>
int APIENTRY WinMain(HINSTANCE /*instance_handle*/, HINSTANCE /*previous_instance_handle*/, char* /*command_line*/, int /*command_show*/)
#else
int main(int /*argc*/, char** /*argv*/)
#endif
{
std::vector<std::pair<std::string, Rml::DataViewInstancer*>> test;

static Rml::UnorderedMap<Rml::String, Rml::Element*> elements;

const int window_width = 1024;
const int window_height = 768;

Expand Down
3 changes: 3 additions & 0 deletions Source/Core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ add_library(rmlui_core
ContextInstancer.cpp
ContextInstancerDefault.cpp
ContextInstancerDefault.h
ControlledLifetimeResource.h
ConvolutionFilter.cpp
Core.cpp
DataController.cpp
Expand Down Expand Up @@ -65,6 +66,8 @@ add_library(rmlui_core
ElementHandle.cpp
ElementHandle.h
ElementInstancer.cpp
ElementMeta.cpp
ElementMeta.h
ElementScroll.cpp
ElementStyle.cpp
ElementStyle.h
Expand Down
23 changes: 21 additions & 2 deletions Source/Core/ComputeProperty.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,29 @@
#include "../../Include/RmlUi/Core/ComputedValues.h"
#include "../../Include/RmlUi/Core/Property.h"
#include "../../Include/RmlUi/Core/StringUtilities.h"
#include "ControlledLifetimeResource.h"

namespace Rml {

const Style::ComputedValues DefaultComputedValues{nullptr};
struct ComputedPropertyData {
const Style::ComputedValues computed{nullptr};
};
static ControlledLifetimeResource<ComputedPropertyData> computed_property_data;

const Style::ComputedValues& DefaultComputedValues()
{
return computed_property_data->computed;
}

void InitializeComputeProperty()
{
computed_property_data.Initialize();
}

void ShutdownComputeProperty()
{
computed_property_data.Shutdown();
}

static constexpr float PixelsPerInch = 96.0f;

Expand Down Expand Up @@ -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();
Expand Down
5 changes: 4 additions & 1 deletion Source/Core/ComputeProperty.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,10 @@ uint16_t ComputeBorderWidth(float computed_length);

String GetFontFaceDescription(const String& font_family, Style::FontStyle style, Style::FontWeight weight);

extern const Style::ComputedValues DefaultComputedValues;
const Style::ComputedValues& DefaultComputedValues();

void InitializeComputeProperty();
void ShutdownComputeProperty();

} // namespace Rml
#endif
86 changes: 86 additions & 0 deletions Source/Core/ControlledLifetimeResource.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* This source file is part of RmlUi, the HTML/CSS Interface Middleware
*
* For the latest information, see http://github.com/mikke89/RmlUi
*
* Copyright (c) 2019-2024 The RmlUi Team, and contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/

#ifndef RMLUI_CORE_CONTROLLEDLIFETIMERESOURCE_H
#define RMLUI_CORE_CONTROLLEDLIFETIMERESOURCE_H

#include "../../Include/RmlUi/Core/Debug.h"
#include "../../Include/RmlUi/Core/Traits.h"

namespace Rml {

template <typename T>
class ControlledLifetimeResource : NonCopyMoveable {
public:
ControlledLifetimeResource() = default;
~ControlledLifetimeResource() noexcept { RMLUI_ASSERTMSG(!pointer || intentionally_leaked, "Resource was not properly shut down."); }

explicit operator bool() const noexcept { return pointer != nullptr; }

void Initialize()
{
RMLUI_ASSERTMSG(!pointer, "Resource already initialized.");
pointer = new T();
}

void InitializeIfEmpty()
{
if (!pointer)
Initialize();
else
SetIntentionallyLeaked(false);
}

void Leak() { SetIntentionallyLeaked(true); }

void Shutdown()
{
RMLUI_ASSERTMSG(pointer, "Shutting down resource that was not initialized, or has been shut down already.");
RMLUI_ASSERTMSG(!intentionally_leaked, "Shutting down resource that was marked as leaked.");
delete pointer;
pointer = nullptr;
}

T* operator->()
{
RMLUI_ASSERTMSG(pointer, "Resource used before it was initialized, or after it was shut down.");
return pointer;
}

private:
#ifdef RMLUI_DEBUG
void SetIntentionallyLeaked(bool leaked) { intentionally_leaked = leaked; }
bool intentionally_leaked = false;
#else
void SetIntentionallyLeaked(bool /*leaked*/) {}
#endif

T* pointer = nullptr;
};

} // namespace Rml
#endif
Loading

0 comments on commit cc85823

Please sign in to comment.