Adds minimal layer and event framework

Introduces a basic application layer and event framework.

This commit implements a simple event system and layer management to facilitate a more modular application architecture. The example demonstrates how to create custom events and layers that can interact with the application.
This commit is contained in:
2025-12-04 09:12:42 -06:00
parent 53797fcc4b
commit 9444aea507
3 changed files with 426 additions and 0 deletions

21
.vscode/c_cpp_properties.json vendored Normal file
View File

@@ -0,0 +1,21 @@
{
"configurations": [
{
"name": "Win32",
"includePath": [
"${default}",
"${workspaceFolder}/**"
],
"defines": [
"_DEBUG",
"UNICODE",
"_UNICODE"
],
"windowsSdkVersion": "10.0.22621.0",
"compilerPath": "C:/msys64/mingw64/bin/g++.exe",
"cStandard": "c23",
"cppStandard": "c++20"
}
],
"version": 4
}

View File

@@ -0,0 +1,96 @@
#define LAYER_IMPLEMENTATION
#include "Layer.h"
#include <iostream>
// ------------------------ Custom Events ------------------------
struct ShutdownEvent : public layer::Event
{
const char *GetName() const override { return "ShutdownEvent"; }
};
// A simple input-like event just for demo
struct PingEvent : public layer::Event
{
explicit PingEvent(int count) : counter(count) {}
const char *GetName() const override { return "PingEvent"; }
int counter;
};
// ------------------------ Custom Layer -------------------------
class DemoLayer : public layer::Layer
{
public:
explicit DemoLayer(layer::Application &app)
: layer::Layer("DemoLayer"), m_App(app)
{
}
void OnAttach() override
{
std::cout << "[DemoLayer] Attached\n";
}
void OnUpdate(layer::Seconds dt) override
{
elapsed += dt;
if (elapsed > 1.0f && !sentPing)
{
sentPing = true;
PingEvent e(42);
std::cout << "[DemoLayer] Sending PingEvent\n";
m_App.OnEvent(e);
}
if (elapsed > 2.0f && !sentShutdown)
{
sentShutdown = true;
ShutdownEvent e;
std::cout << "[DemoLayer] Sending ShutdownEvent\n";
m_App.OnEvent(e);
}
}
void OnEvent(layer::Event &e) override
{
layer::EventDispatcher dispatcher(e);
dispatcher.Dispatch<PingEvent>([this](PingEvent &pe)
{
std::cout << "[DemoLayer] Received PingEvent, counter=" << pe.counter << "\n";
return true; // mark handled
});
dispatcher.Dispatch<ShutdownEvent>([this](ShutdownEvent &)
{
std::cout << "[DemoLayer] Received ShutdownEvent, closing app.\n";
m_App.Close();
return true; });
}
private:
layer::Application &m_App;
float elapsed = 0.0f;
bool sentPing = false;
bool sentShutdown = false;
};
// --------------------------- main ------------------------------
int main()
{
layer::Application app;
DemoLayer demo(app);
app.PushLayer(&demo);
app.Run();
std::cout << "Application exited.\n";
return 0;
}

View File

@@ -0,0 +1,309 @@
#pragma once
/*
Layer.h - minimal application layer + event framework
-----------------------------------------------------
Usage:
// In ONE .cpp file:
#define LAYER_IMPLEMENTATION
#include "Layer.h"
// In all other .cpp files:
#include "Layer.h"
You define your own Event subclasses and use EventDispatcher
to route them in Application / Layer code.
*/
#include <string>
#include <vector>
#include <chrono>
namespace layer
{
using Seconds = float;
// =====================================================
// Event base + dispatcher
// =====================================================
class Event
{
public:
bool handled = false;
virtual ~Event() = default;
// Optional but useful for debugging
virtual const char *GetName() const = 0;
virtual std::string ToString() const { return GetName(); }
};
class EventDispatcher
{
public:
explicit EventDispatcher(Event &e);
// Dispatch to a specific event type using dynamic_cast.
// T must derive from Event.
template <typename T, typename F>
bool Dispatch(F &&func)
{
T *specific = dynamic_cast<T *>(&m_Event);
if (!specific)
return false;
m_Event.handled = func(*specific);
return true;
}
private:
Event &m_Event;
};
// =====================================================
// Layer base
// =====================================================
class Layer
{
public:
explicit Layer(const std::string &name = "Layer");
virtual ~Layer();
virtual void OnAttach() {}
virtual void OnDetach() {}
virtual void OnUpdate(Seconds dt) {}
virtual void OnEvent(Event &e) {}
const std::string &GetName() const { return m_DebugName; }
protected:
std::string m_DebugName;
};
// =====================================================
// LayerStack (non-owning: you manage Layer lifetime)
// =====================================================
class LayerStack
{
public:
LayerStack();
~LayerStack();
// NOTE: Lifetime is managed by the caller.
// Pointers must remain valid while in the stack.
void PushLayer(Layer *layer);
void PushOverlay(Layer *overlay);
void PopLayer(Layer *layer);
void PopOverlay(Layer *overlay);
using Container = std::vector<Layer *>;
Container::iterator begin() { return m_Layers.begin(); }
Container::iterator end() { return m_Layers.end(); }
Container::reverse_iterator rbegin() { return m_Layers.rbegin(); }
Container::reverse_iterator rend() { return m_Layers.rend(); }
const Container &GetLayers() const { return m_Layers; }
private:
Container m_Layers;
std::size_t m_InsertIndex; // layers [0..insert) then overlays [insert..)
};
// =====================================================
// Application
// =====================================================
class Application
{
public:
Application();
virtual ~Application();
// Simple fixed loop:
// while (IsRunning()) { update dt; call OnUpdate + layers' OnUpdate }
void Run();
void Close();
bool IsRunning() const { return m_Running; }
LayerStack &GetLayerStack() { return m_LayerStack; }
const LayerStack &GetLayerStack() const { return m_LayerStack; }
void PushLayer(Layer *layer);
void PushOverlay(Layer *overlay);
void PopLayer(Layer *layer);
void PopOverlay(Layer *overlay);
// Default behaviour: fan out to layers (topmost first).
virtual void OnEvent(Event &e);
// Optional application-wide update hook, before layer updates.
virtual void OnUpdate(Seconds dt) { (void)dt; }
private:
LayerStack m_LayerStack;
bool m_Running;
};
} // namespace layer
#if defined(LAYER_IMPLEMENTATION) && !defined(LAYER_IMPLEMENTATION_DONE)
#define LAYER_IMPLEMENTATION_DONE
namespace layer
{
// ----------------- EventDispatcher -------------------
inline EventDispatcher::EventDispatcher(Event &e)
: m_Event(e)
{
}
// ---------------------- Layer ------------------------
inline Layer::Layer(const std::string &name)
: m_DebugName(name)
{
}
inline Layer::~Layer() = default;
// -------------------- LayerStack ---------------------
inline LayerStack::LayerStack()
: m_InsertIndex(0)
{
}
inline LayerStack::~LayerStack() = default;
inline void LayerStack::PushLayer(Layer *layer)
{
if (!layer)
return;
m_Layers.emplace(m_Layers.begin() + static_cast<std::ptrdiff_t>(m_InsertIndex), layer);
++m_InsertIndex;
layer->OnAttach();
}
inline void LayerStack::PushOverlay(Layer *overlay)
{
if (!overlay)
return;
m_Layers.emplace_back(overlay);
overlay->OnAttach();
}
inline void LayerStack::PopLayer(Layer *layer)
{
if (!layer)
return;
for (std::size_t i = 0; i < m_Layers.size(); ++i)
{
if (m_Layers[i] == layer)
{
m_Layers[i]->OnDetach();
m_Layers.erase(m_Layers.begin() + static_cast<std::ptrdiff_t>(i));
if (i < m_InsertIndex && m_InsertIndex > 0)
--m_InsertIndex;
break;
}
}
}
inline void LayerStack::PopOverlay(Layer *overlay)
{
if (!overlay)
return;
for (std::size_t i = 0; i < m_Layers.size(); ++i)
{
if (m_Layers[i] == overlay)
{
m_Layers[i]->OnDetach();
m_Layers.erase(m_Layers.begin() + static_cast<std::ptrdiff_t>(i));
break;
}
}
}
// -------------------- Application --------------------
inline Application::Application()
: m_Running(true)
{
}
inline Application::~Application() = default;
inline void Application::Run()
{
using clock = std::chrono::high_resolution_clock;
auto last = clock::now();
while (m_Running)
{
auto now = clock::now();
Seconds dt = std::chrono::duration<Seconds>(now - last).count();
last = now;
// Global app hook
OnUpdate(dt);
// Per-layer updates
for (Layer *layer : m_LayerStack.GetLayers())
{
if (layer)
layer->OnUpdate(dt);
}
}
}
inline void Application::Close()
{
m_Running = false;
}
inline void Application::PushLayer(Layer *layer)
{
m_LayerStack.PushLayer(layer);
}
inline void Application::PushOverlay(Layer *overlay)
{
m_LayerStack.PushOverlay(overlay);
}
inline void Application::PopLayer(Layer *layer)
{
m_LayerStack.PopLayer(layer);
}
inline void Application::PopOverlay(Layer *overlay)
{
m_LayerStack.PopOverlay(overlay);
}
inline void Application::OnEvent(Event &e)
{
// Fan out to layers, topmost first
auto &layers = m_LayerStack.GetLayers();
for (auto it = layers.rbegin(); it != layers.rend(); ++it)
{
Layer *layer = *it;
if (!layer)
continue;
layer->OnEvent(e);
if (e.handled)
break;
}
}
} // namespace layer
#endif // LAYER_IMPLEMENTATION / DONE