Memory safety and Window Events

Adding memory checks and Window Event Listener

+ Memory Checks (debug)
+ Listening to window events
+ Runtime Directory Structure (Vision)
This commit is contained in:
noffie 2025-04-20 17:49:15 +02:00
parent 550fc291de
commit 3679beaa4e
13 changed files with 491 additions and 100 deletions

View file

@ -281,4 +281,35 @@ struct MainApp : public cig::App {
)
}
}
```
## Data structures
### Runtime
```
./
|- Themes
| |- ExampleTheme.json
| \- themes.txt
|- Settings.json
\- App.json
```
#### Themes
```
# themes.txt
1, ExampleTheme.json
---
default: 1
```
```json5
// ExampleTheme.json
{
"Name": "Standard Cigui Theme",
"background-color": [20, 20, 20],
"primary": [120, 20, 50],
"other": "auto" // calculates the secondary and any further color automatically based on "primary"
}
```

View file

@ -1,6 +1,10 @@
#include <cigui/core/App.hpp>
#include <cigui/cigui.hpp>
int main() {
cig::App::GetInstance().Run();
int main(int argc, char** argv) {
MEM_CHECKED_MAIN(return_code) {
auto app = cig::App();
return_code = app.Run();
}
return return_code;
}

View file

@ -1,4 +1,4 @@
#include <iosteam>
#include <iostream>
int main() {
std::cout << "Hello World" << std::endl;

View file

@ -4,7 +4,19 @@
#include <cigui/config.h>
#include <stdint.h>
#include <cigui/App.hpp>
#include <cigui/utils/MemoryDebug.hpp>
#include <cigui/core/App.hpp>
#ifdef _DEBUG
#define MEM_CHECKED_MAIN(rtc) \
int rtc = 0; \
MEMORY_INFO_DUMP(); \
for (bool exit_main_mem_check_dump_tmp_var = true; exit_main_mem_check_dump_tmp_var == true; MEMORY_INFO_DUMP(), exit_main_mem_check_dump_tmp_var = false)
#else
#define MEM_CHECKED_MAIN(rtc) int rtc = 0;
#endif
namespace cig {
constexpr unsigned int VERSION_MAJOR = CIGUI_VERSION_MAJOR;

View file

@ -1,13 +1,27 @@
#pragma once
#include <cigui/config.h>
#include <filesystem>
#include <LLGL/LLGL.h>
#include <cigui/utils/List.hpp>
#include <cigui/utils/Vectors.hpp>
namespace cig
{
struct Vertex
class LocalFilesystem
{
std::filesystem::path executable_path;
std::filesystem::path working_directory;
public:
explicit LocalFilesystem(const char* arg0)
{
executable_path = std::filesystem::current_path() / arg0;
working_directory = std::filesystem::current_path();
}
};
struct alignas(4) Vertex
{
float position[2]; // 2D vector for X and Y coordinates
uint8_t color[4]; // 4D vector for red, green, blue, and alpha components
@ -16,24 +30,50 @@ namespace cig
class App
{
protected:
LLGL::RenderSystemPtr m_RenderSystem;
std::pair<LLGL::Shader*, LLGL::Shader*> m_ShaderPack;
LLGL::SwapChain* m_SwapChain = nullptr;
LLGL::PipelineState* m_Pipeline = nullptr;
LLGL::Buffer* m_VertexBuffer = nullptr;
LLGL::CommandBuffer* m_CmdBuffer = nullptr;
LLGL::Window* m_Window = nullptr;
class WindowListener final : public LLGL::Window::EventListener
{
public:
explicit WindowListener(App& app);
void OnResize(LLGL::Window& sender, const LLGL::Extent2D& clientAreaSize) override;
void OnQuit(LLGL::Window& sender, bool& veto) override;
void OnLostFocus(LLGL::Window& sender) override;
void OnGetFocus(LLGL::Window& sender) override;
private:
App& m_App;
};
std::shared_ptr<WindowListener> m_WindowListener;
[[nodiscard]] bool ShaderLanguageIsSupported(LLGL::ShadingLanguage lang) const;
List<Vertex> m_Vertices;
vec2u m_WindowSize;
void resized(vec2u size);
void update(bool force = false);
void render() const;
[[nodiscard]] bool save() const;
public:
App();
~App();
LLGL::RenderSystemPtr m_RenderSystem;
LLGL::SwapChain* m_SwapChain;
void Initialize(int argc = 0, char** argv = nullptr);
std::pair<LLGL::Shader*, LLGL::Shader*> m_ShaderPack;
LLGL::PipelineState* m_Pipeline;
LLGL::Buffer* m_VertexBuffer;
LLGL::CommandBuffer* m_CmdBuffer;
LLGL::Window* m_Window;
bool ShaderLanguageIsSupported(LLGL::ShadingLanguage lang);
List<Vertex> m_Vertices;
public:
static App& GetInstance();
void Run();
int Run();
};
}

View file

@ -0,0 +1,18 @@
#pragma once
namespace cig::Memory
{
template<typename T>
T* alloc(size_t count = 1);
template<typename T>
T* realloc(T* data, size_t from, size_t to);
template<typename T>
void dealloc(T* data);
template<typename T>
T* dupalloc(const T* data, size_t count = 1);
}
#include <cigui/core/Memory.inl>

View file

@ -0,0 +1,49 @@
#pragma once
#include <cassert>
#include <algorithm>
#include <cstdlib>
#include <cigui/utils/MemoryDebug.hpp>
namespace cig::Memory
{
template<typename T>
T* alloc(const size_t count) {
assert(count > 0 || "Count must be greater than 0");
T* data = static_cast<T*>(calloc(count, sizeof(T)));
MEMORY_ALLOCATOR_DEBUG(data, count);
return data;
}
template<typename T>
T* realloc(T* data, const size_t from, const size_t to)
{
assert((from > 0 && data) || "Data must exist");
assert((to > 0 && to > from) ||"Destination must be greater than Source");
T* newData = alloc<T>(to);
std::copy(data, data + from, newData);
dealloc<T>(data);
MEMORY_REALLOCATOR_DEBUG(data, newData, to);
return newData;
}
template<typename T>
void dealloc(T* data)
{
assert(data || "Data must exist");
MEMORY_DEALLOCATOR_DEBUG(data);
free(data);
}
template<typename T>
T* dupalloc(const T* data, const size_t count)
{
assert(data || "Data must exist");
assert(count > 0 || "Count must be greater than 0");
T* ndata = alloc<T>(count);
std::copy(data, data + count, ndata);
MEMORY_DUPALLOCATOR_DEBUG(data, count);
return ndata;
}
}

View file

@ -8,7 +8,7 @@ namespace cig
template <typename T, size_t growth_scalar = 1, size_t growth_summand = 3>
class List
{
std::unique_ptr<T> m_Data;
T* m_Data = nullptr;
size_t m_Size = 0;
size_t m_Capacity;
@ -17,10 +17,11 @@ namespace cig
public:
explicit List(size_t capacity = 3);
~List();
void own(T* data, size_t size);
void copy(T* data, size_t size);
void copy(const T* data, size_t size);
[[nodiscard]] size_t size() const;

View file

@ -4,6 +4,7 @@
template <typename T, size_t growth_scalar, size_t growth_summand> \
rtt List<T, growth_scalar, growth_summand>
#include <cigui/core/Memory.hpp>
namespace cig
{
@ -11,26 +12,40 @@ namespace cig
{
if (!m_Data)
{
m_Data = std::unique_ptr<T>(static_cast<T*>(calloc(capacity, sizeof(T))));
m_Data = Memory::alloc<T>(capacity);
m_Capacity = capacity;
return;
}
std::unique_ptr<T> newData(static_cast<T*>(calloc(capacity, sizeof(T))));
std::copy(m_Data.get(), m_Data.get() + m_Size, newData.get());
m_Data = std::move(newData);
m_Data = Memory::realloc<T>(m_Data, m_Size, capacity);
m_Capacity = capacity;
}
LIST_FUNC_DEFINE( )::List(const size_t capacity) : m_Capacity(capacity) { reserve(capacity); }
template <typename T, size_t growth_scalar, size_t growth_summand>
List<T, growth_scalar, growth_summand>::~List()
{
if (m_Data)
Memory::dealloc<T>(m_Data);
}
template <typename T, size_t growth_scalar, size_t growth_summand>
List<T, growth_scalar, growth_summand>::List(const size_t capacity)
: m_Capacity(capacity)
{
reserve(capacity);
}
LIST_FUNC_DEFINE(void)::own(T* data, const size_t size) {
if (m_Data)
{
Memory::dealloc<T>(m_Data);
}
m_Data = data;
m_Size = size;
}
LIST_FUNC_DEFINE(void)::copy(T* data, const size_t size) {
m_Data = std::make_unique<T[]>(size);
std::copy(data, data + size, m_Data.get());
LIST_FUNC_DEFINE(void)::copy(const T* data, const size_t size) {
m_Data = Memory::dupalloc<T>(data, size);
m_Size = size;
}
@ -39,15 +54,15 @@ namespace cig
}
LIST_FUNC_DEFINE(T &)::operator[](const size_t index) {
return m_Data.get()[index];
return m_Data[index];
}
LIST_FUNC_DEFINE(const T &)::operator[](const size_t index) const {
return m_Data.get()[index];
return m_Data[index];
}
LIST_FUNC_DEFINE(void)::need(const size_t additional_size) {
if (m_Size + additional_size > m_Capacity)
reserve(m_Capacity + additional_size);
reserve(m_Capacity + additional_size + growth_summand);
}
LIST_FUNC_DEFINE(bool)::empty() const {
@ -61,29 +76,29 @@ namespace cig
LIST_FUNC_DEFINE(void)::push_back(const T& value) {
if (m_Size >= m_Capacity)
reserve(m_Capacity * growth_scalar + growth_summand);
m_Data.get()[m_Size++] = value;
m_Data[m_Size++] = value;
}
LIST_FUNC_DEFINE(template <typename... Args> void)::emplace_back(Args&&... args) {
if (m_Size >= m_Capacity)
reserve(m_Capacity * growth_scalar + growth_summand);
m_Data.get()[m_Size++] = T(std::forward<Args>(args)...);
m_Data[m_Size++] = T(std::forward<Args>(args)...);
}
LIST_FUNC_DEFINE(void)::expand(const List<T>& other) {
need(other.size());
std::copy(other.m_Data.get(), other.m_Data.get() + other.size(), m_Data.get() + m_Size);
std::copy(other.m_Data, other.m_Data + other.size(), m_Data + m_Size);
m_Size += other.size();
}
LIST_FUNC_DEFINE(template <typename Lambda> void)::iterate(Lambda&& lambda) const
{
for (size_t i = 0; i < m_Size; i++)
lambda(m_Data.get()[i]);
lambda(m_Data[i]);
}
LIST_FUNC_DEFINE(const T *)::data() const {
return m_Data.get();
LIST_FUNC_DEFINE(const T*)::data() const {
return m_Data;
}
}

View file

@ -0,0 +1,159 @@
#pragma once
#ifdef _DEBUG
#include <iostream>
#include <unordered_map>
#include <chrono>
namespace cig::Memory::Debug
{
namespace PrettyDurationDump
{
inline std::string DumpDurationPretty(const std::chrono::nanoseconds& duration)
{
using timetype = unsigned long long;
struct
{
bool hours = false;
bool minutes = false;
bool seconds = false;
bool milliseconds = false;
bool microseconds = false;
} m_TimeFlag;
timetype precice_nanoseconds = duration.count();
timetype nanoseconds = precice_nanoseconds % 1000;
timetype precice_microseconds = precice_nanoseconds / 1000;
timetype microseconds = precice_microseconds % 1000;
if (microseconds > 0) m_TimeFlag.microseconds = true;
timetype precice_milliseconds = precice_microseconds / 1000;
timetype milliseconds = precice_milliseconds % 1000;
if (milliseconds > 0) m_TimeFlag.milliseconds = true;
timetype precice_seconds = precice_milliseconds / 1000;
timetype seconds = precice_seconds % 60;
if (seconds > 0) m_TimeFlag.seconds = true;
timetype precice_minutes = precice_seconds / 60;
timetype minutes = precice_minutes % 60;
if (minutes > 0) m_TimeFlag.minutes = true;
timetype precice_hours = precice_minutes / 60;
timetype hours = precice_hours;
if (hours > 0) m_TimeFlag.hours = true;
std::stringstream ss;
if (m_TimeFlag.hours)
ss << hours << "h ";
if (m_TimeFlag.minutes)
ss << minutes << "m ";
if (m_TimeFlag.seconds)
ss << seconds << "s ";
if (m_TimeFlag.milliseconds)
ss << milliseconds << "ms ";
if (m_TimeFlag.microseconds)
ss << microseconds << "us ";
ss << nanoseconds << "ns";
return ss.str();
}
}
struct ptr_info
{
size_t count, bytes;
const char* var;
const char* file;
const char* func;
int line;
};
inline std::unordered_map<void*, ptr_info> allocations;
template<typename T>
void log_allocation(const T* ptr, const size_t size, const char* var = "", const char* file = "", const char* func = "", int line = 0)
{
allocations[(void*)ptr] = {
size,
sizeof(T),
var,
file,
func,
line
};
}
template<typename T>
void log_deallocator(const T* ptr)
{
allocations.erase(allocations.find((void*)ptr));
}
template<typename T>
void log_reallocator(const T* ptr, const T* newptr, const size_t to, const char* var = "", const char* file = "", const char* func = "", int line = 0)
{
log_allocation(newptr, to, var, file, func, line);
}
template<typename T>
void log_dupallocator(const T* ptr, const size_t size, const char* var = "", const char* file = "", const char* func = "", int line = 0)
{
log_allocation(ptr, size, var, file, func, line);
}
inline void log_info_dump(const char* file, const char* func, int line)
{
static std::chrono::high_resolution_clock::time_point s_LastDump;
static bool s_FirstDump = true;
if (s_FirstDump)
{
s_LastDump = std::chrono::high_resolution_clock::now();
s_FirstDump = false;
return;
}
std::cout << "\n------------------------------------ Allocator Debug Info ------------------------------------" << std::endl;
if (file)
std::cout << "\tFile: \t\t" << file << std::endl;
if (func)
std::cout << "\tFunction: \t" << func << std::endl;
if (line)
std::cout << "\tLine: \t\t" << line << std::endl;
// time since last dump
auto now = std::chrono::high_resolution_clock::now();
const auto diff = std::chrono::duration_cast<std::chrono::nanoseconds>(now - s_LastDump);
std::cout << "\tLast dump:\t" << PrettyDurationDump::DumpDurationPretty(diff) << " ago" << std::endl;
s_LastDump = std::chrono::high_resolution_clock::now();
std::cout << "\tAllocations: " << allocations.size() << std::endl;
for (auto& [ptr, info] : allocations)
{
std::cout << "Allocation: " << ptr
<< "\n\tSize: " << info.count
<< "\n\tBytes: " << info.bytes * info.count
<< "\n\tFile: " << info.file
<< "\n\tFunction: " << info.func
<< "\n\tLine: " << info.line
<< "\n\tVariable: " << info.var
<< std::endl;
}
std::cout << "----------------------------------------------------------------------------------------------" << std::endl;
}
}
#define MEMORY_ALLOCATOR_DEBUG(ptr, size) cig::Memory::Debug::log_allocation(ptr, size, #ptr, __FILE__, __FUNCTION__, __LINE__)
#define MEMORY_DEALLOCATOR_DEBUG(ptr) cig::Memory::Debug::log_deallocator(ptr)
#define MEMORY_REALLOCATOR_DEBUG(ptr, newptr, to) cig::Memory::Debug::log_reallocator(ptr, newptr, to, #newptr, __FILE__, __FUNCTION__, __LINE__)
#define MEMORY_DUPALLOCATOR_DEBUG(ptr, size) cig::Memory::Debug::log_dupallocator(ptr, size, #ptr, __FILE__, __FUNCTION__, __LINE__)
#define MEMORY_INFO_DUMP() cig::Memory::Debug::log_info_dump(__FILE__, __FUNCTION__, __LINE__)
#else
#define MEMORY_ALLOCATOR_DEBUG(ptr, size)
#define MEMORY_DEALLOCATOR_DEBUG(ptr)
#define MEMORY_REALLOCATOR_DEBUG(ptr, from, to)
#define MEMORY_DUPALLOCATOR_DEBUG(ptr, size)
#endif

View file

@ -0,0 +1 @@
#pragma once

View file

@ -22,14 +22,6 @@
TYPEDEF_VECTOR(NAME, long long, N, ll) \
TYPEDEF_VECTOR(NAME, unsigned long long, N, ull)
#if defined(__GNUC__) || defined(__clang__)
#define UNNAMED_STRUCT __extension__ struct
#else
#define UNNAMED_STRUCT struct
// Disable warning for anonymous structs and unions
#pragma warning(push)
#pragma warning(disable : 4201)
#endif
#if CIGUI_DLL
#define VECTOR_TEMPLATE_INST(N, T) CIGUI_TEMPLATE_INST Vector##N<T>; \
@ -57,15 +49,15 @@ namespace cig
template <typename T>
union Vector2
{
UNNAMED_STRUCT
struct
{
T x, y;
};
} dims;
UNNAMED_STRUCT
struct
{
T a, b;
};
T width, height;
} sizes;
};
TYPEDEF_VECTORS(Vector2, 2)
@ -73,15 +65,20 @@ namespace cig
template <typename T>
union Vector3
{
UNNAMED_STRUCT
struct
{
T x, y, z;
};
} dims;
UNNAMED_STRUCT
struct
{
T r, g, b;
};
} color;
struct
{
T width, height, depth;
} sizes;
};
TYPEDEF_VECTORS(Vector3, 3)
@ -89,32 +86,28 @@ namespace cig
template <typename T>
union Vector4
{
UNNAMED_STRUCT
struct
{
T x, y, z, w;
};
} dims;
UNNAMED_STRUCT
struct
{
T r, g, b, a;
};
} color;
UNNAMED_STRUCT
struct
{
T left, top, right, bottom;
};
} dirs;
};
TYPEDEF_VECTORS(Vector4, 4)
}
#if !defined(__GNUC__) && !defined(__clang__)
#pragma warning(pop)
#endif
#undef UNNAMED_STRUCT
#undef TYPEDEF_VECTOR
#undef TYPEDEF_VECTORS
#if CIGUI_DLL
#undef VECTOR_TEMPLATE_INST
#endif

View file

@ -3,9 +3,11 @@
#include <iostream>
#include <LLGL/Utils/VertexFormat.h>
#include <cigui/core/Memory.hpp>
namespace cig
{
App::App()
App::App() : m_WindowListener(std::make_shared<WindowListener>(*this))
{
// Rectangle
m_Vertices.emplace_back(Vertex{ { 0.5f, -0.5f }, { 0, 255, 0, 255 } });
@ -28,9 +30,13 @@ namespace cig
}
LLGL::SwapChainDescriptor swapChainDesc;
swapChainDesc.resolution = { 800, 600 }; // Framebuffer resolution of 800x600 pixels
swapChainDesc.fullscreen = false; // No fullscreen, use windowed mode
swapChainDesc.samples = 8; // 8 samples for anti-aliasing
swapChainDesc.resizable = true;
swapChainDesc.debugName = "SwapChain";
swapChainDesc.swapBuffers = 2;
swapChainDesc.resolution.width = 800;
swapChainDesc.resolution.height = 600; // Framebuffer resolution of 800x600 pixels
swapChainDesc.fullscreen = false; // No fullscreen, use windowed mode
swapChainDesc.samples = 8; // 8 samples for anti-aliasing
m_SwapChain = m_RenderSystem->CreateSwapChain(swapChainDesc);
LLGL::VertexFormat vertexFormat;
@ -70,60 +76,122 @@ namespace cig
}
LLGL::GraphicsPipelineDescriptor pipelineDesc;
pipelineDesc.vertexShader = m_ShaderPack.first;
pipelineDesc.fragmentShader = m_ShaderPack.second;
pipelineDesc.rasterizer.multiSampleEnabled = swapChainDesc.samples > 1;
pipelineDesc.vertexShader = m_ShaderPack.first;
pipelineDesc.fragmentShader = m_ShaderPack.second;
pipelineDesc.primitiveTopology = LLGL::PrimitiveTopology::TriangleList;
pipelineDesc.rasterizer.multiSampleEnabled = swapChainDesc.samples > 1;
m_Pipeline = m_RenderSystem->CreatePipelineState(pipelineDesc);
m_CmdBuffer = m_RenderSystem->CreateCommandBuffer(LLGL::CommandBufferFlags::ImmediateSubmit);
}
App::~App() {}
App::~App()
{
}
bool App::ShaderLanguageIsSupported(LLGL::ShadingLanguage lang)
App::WindowListener::WindowListener(App& app) : m_App(app) {}
void App::WindowListener::OnResize(LLGL::Window& sender, const LLGL::Extent2D& clientAreaSize)
{
EventListener::OnResize(sender, clientAreaSize);
m_App.m_SwapChain->ResizeBuffers(clientAreaSize);
m_App.resized({clientAreaSize.width, clientAreaSize.height});
m_App.update(true);
m_App.render();
}
void App::WindowListener::OnQuit(LLGL::Window& sender, bool& veto)
{
EventListener::OnQuit(sender, veto);
veto = !m_App.save() || veto;
std::cout << "App: Quit" << std::endl;
}
void App::WindowListener::OnLostFocus(LLGL::Window& sender)
{
EventListener::OnLostFocus(sender);
std::cout << "App: Lost focus" << std::endl;
}
void App::WindowListener::OnGetFocus(LLGL::Window& sender)
{
EventListener::OnGetFocus(sender);
std::cout << "App: Got focus" << std::endl;
}
bool App::ShaderLanguageIsSupported(LLGL::ShadingLanguage lang) const
{
const auto& supportedLangs = m_RenderSystem->GetRenderingCaps().shadingLanguages;
return std::find(supportedLangs.begin(), supportedLangs.end(), lang) != supportedLangs.end();
}
App& App::GetInstance()
void App::resized(vec2u size)
{
static App instance;
return instance;
m_WindowSize = size;
}
void App::Run()
void App::Initialize(int argc, char** argv)
{
if (!m_RenderSystem)
return;
m_Window = &LLGL::CastTo<LLGL::Window>(m_SwapChain->GetSurface());
m_Window->SetTitle("Hello Triangle");
m_Window->Show();
while (!m_Window->HasQuit()) {
m_Window->ProcessEvents();
}
m_CmdBuffer->Begin();
void App::update(bool force)
{
}
void App::render() const
{
static LLGL::ClearValue clear_value(.1f,.1f,.1f,1.f);
m_CmdBuffer->Begin();
{
m_CmdBuffer->SetVertexBuffer(*m_VertexBuffer);
m_CmdBuffer->BeginRenderPass(*m_SwapChain);
{
m_CmdBuffer->SetViewport(m_SwapChain->GetResolution());
m_CmdBuffer->SetViewport(m_SwapChain->GetResolution());
m_CmdBuffer->Clear(LLGL::ClearFlags::Color, clear_value);
m_CmdBuffer->Clear(LLGL::ClearFlags::Color);
m_CmdBuffer->SetPipelineState(*m_Pipeline);
m_CmdBuffer->Draw(m_Vertices.size(), 0);
m_CmdBuffer->SetPipelineState(*m_Pipeline);
{
m_CmdBuffer->Draw(static_cast<uint32_t>(m_Vertices.size()), 0);
}
}
m_CmdBuffer->EndRenderPass();
m_CmdBuffer->End();
m_SwapChain->Present();
}
m_CmdBuffer->End();
m_SwapChain->Present();
}
bool App::save() const
{
return true;
}
int App::Run()
{
if (!m_RenderSystem)
return -1;
m_Window = &LLGL::CastTo<LLGL::Window>(m_SwapChain->GetSurface());
m_Window->AddEventListener(m_WindowListener);
m_Window->SetTitle("Common Interface GUI");
m_Window->Show();
while (LLGL::Surface::ProcessEvents() && !m_Window->HasQuit()) {
update();
render();
}
return 0;
}
}