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:
21
.vscode/c_cpp_properties.json
vendored
Normal file
21
.vscode/c_cpp_properties.json
vendored
Normal 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
|
||||
}
|
||||
96
examples/layer_example.cpp
Normal file
96
examples/layer_example.cpp
Normal 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;
|
||||
}
|
||||
309
include/Layer.h
309
include/Layer.h
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user