event system.

🏗️ service locator pattern.
main
bog 2023-10-25 16:37:47 +02:00
parent 89956ddf6a
commit 85b5b4e820
15 changed files with 322 additions and 13 deletions

View File

@ -19,12 +19,19 @@ configure_file(
executable(
'mornelune',
sources: [
# Core
'src/main.cpp',
'src/Game.cpp',
'src/Services.cpp',
'src/Service.cpp',
# Logs
'src/logs/Logs.cpp',
'src/logs/FileLogs.cpp',
# Events
'src/events/Events.cpp',
'src/events/Event.cpp',
],
dependencies: [
],

View File

@ -1,9 +1,11 @@
#include "Game.hpp"
#include "logs/FileLogs.hpp"
#include "src/events/Events.hpp"
#include "src/logs/Logs.hpp"
namespace ml
{
/*explicit*/ Game::Game(std::shared_ptr<Logs> logs)
: m_logs { logs }
/*explicit*/ Game::Game()
{
}
@ -13,11 +15,11 @@ namespace ml
void Game::start()
{
m_logs->log(LEVEL_DEBUG, "game started");
ML_SYS(FileLogs)->log(LEVEL_INFO, "game started");
}
void Game::stop()
{
m_logs->log(LEVEL_DEBUG, "game stopped");
ML_SYS(FileLogs)->log(LEVEL_INFO, "game stopped");
}
}

View File

@ -2,24 +2,23 @@
#define ml_GAME_HPP
#include "commons.hpp"
#include "logs/Logs.hpp"
#include "Services.hpp"
namespace ml
{
/**
* Manage services and game state.
* Manage game state.
**/
class Game
{
public:
explicit Game(std::shared_ptr<Logs> logs);
explicit Game();
virtual ~Game();
void start();
void stop();
private:
std::shared_ptr<Logs> m_logs;
};
}

12
src/Service.cpp Normal file
View File

@ -0,0 +1,12 @@
#include "Service.hpp"
namespace ml
{
/*explicit*/ Service::Service()
{
}
/*virtual*/ Service::~Service()
{
}
}

19
src/Service.hpp Normal file
View File

@ -0,0 +1,19 @@
#ifndef ml_SERVICE_HPP
#define ml_SERVICE_HPP
namespace ml
{
class Service
{
public:
explicit Service();
virtual ~Service();
virtual void init() {};
virtual void free() {};
private:
};
}
#endif

24
src/Services.cpp Normal file
View File

@ -0,0 +1,24 @@
#include "Services.hpp"
namespace ml
{
/*static*/ std::shared_ptr<Services> Services::instance = nullptr;
/*static*/ std::shared_ptr<Services> Services::get()
{
if (Services::instance == nullptr)
{
Services::instance = std::shared_ptr<Services>(new Services);
}
return Services::instance;
}
/*explicit*/ Services::Services()
{
}
/*virtual*/ Services::~Services()
{
}
}

63
src/Services.hpp Normal file
View File

@ -0,0 +1,63 @@
#ifndef ml_SERVICES_HPP
#define ml_SERVICES_HPP
#include "commons.hpp"
#include "Service.hpp"
namespace ml
{
/**
* Manage game services.
**/
class Services
{
public:
static std::shared_ptr<Services> get();
virtual ~Services();
template<typename T>
size_t id();
template<typename T, typename... Args>
void add(Args... args);
template<typename T>
std::shared_ptr<T> get();
private:
static std::shared_ptr<Services> instance;
size_t m_id = 0;
std::unordered_map<size_t,
std::shared_ptr<Service>> m_services;
explicit Services();
};
template<typename T>
size_t Services::id()
{
static size_t i = m_id++;
return i;
}
template<typename T, typename... Args>
void Services::add(Args... args)
{
auto service = std::make_shared<T>(args...);
size_t i = id<T>();
m_services[i] = service;
}
template<typename T>
std::shared_ptr<T> Services::get()
{
size_t i = id<T>();
return std::static_pointer_cast<T>(m_services[i]);
}
}
#endif

View File

@ -1,12 +1,19 @@
#ifndef ml_COMMONS_HPP
#define ml_COMMONS_HPP
#include <functional>
#include <unordered_map>
#include <vector>
#include <stdexcept>
#include <string>
#include <cstring>
#include <filesystem>
#include <iostream>
#include <fstream>
#include <optional>
#define ML ml::Services::get()
#define ML_SYS(SYSTEM) ML->get<SYSTEM>()
#define ML_GEN_ENUM(X) X
#define ML_GEN_STRING(X) #X

13
src/events/Event.cpp Normal file
View File

@ -0,0 +1,13 @@
#include "Event.hpp"
namespace ml
{
/*explicit*/ Event::Event(EventType type)
: m_type { type }
{
}
/*virtual*/ Event::~Event()
{
}
}

29
src/events/Event.hpp Normal file
View File

@ -0,0 +1,29 @@
#ifndef ml_EVENT_HPP
#define ml_EVENT_HPP
#include "../commons.hpp"
#define EVENT_TYPES(G) G(EVENT_QUIT)
namespace ml
{
ML_ENUM(EventType, EVENT_TYPES);
/**
* A generic event type.
* @see Events
**/
class Event
{
public:
explicit Event(EventType type);
virtual ~Event();
EventType type() const { return m_type; }
private:
EventType m_type;
};
}
#endif

8
src/events/Events.cpp Normal file
View File

@ -0,0 +1,8 @@
#include "Events.hpp"
namespace ml
{
/*virtual*/ Events::~Events()
{
}
}

119
src/events/Events.hpp Normal file
View File

@ -0,0 +1,119 @@
#ifndef ml_EVENTS_HPP
#define ml_EVENTS_HPP
#include "../commons.hpp"
#include "../Service.hpp"
#include "../Services.hpp"
#include "../logs/FileLogs.hpp"
#include "Event.hpp"
namespace ml
{
using listener_t = std::function<void(Event const&)>;
/**
* Controls event listeners lifetime.
* @see ListenerEntry
**/
struct EventToken {};
struct ListenerEntry {
listener_t listener;
std::optional<std::weak_ptr<EventToken>> token;
};
/**
* Publish events to subscribed listeners.
* @see Event
* @see Service
**/
class Events: public Service
{
public:
explicit Events() = default;
virtual ~Events();
template <typename EventTy>
size_t get();
template <typename EventTy>
void subscribe(listener_t listener,
std::shared_ptr<EventToken> token=nullptr);
template <typename EventTy, typename ObjectTy>
void subscribe(ObjectTy* instance,
void (ObjectTy::*method)(Event const&),
std::shared_ptr<EventToken> token=nullptr);
template <typename EventTy>
void publish(EventTy const& event);
private:
std::unordered_map<size_t,
std::vector<ListenerEntry>> m_listeners;
size_t m_id = 0;
};
template <typename EventTy>
size_t Events::get()
{
static size_t i = m_id++;
return i;
}
template <typename EventTy>
void Events::subscribe(listener_t listener,
std::shared_ptr<EventToken> token)
{
size_t index = get<EventTy>();
ListenerEntry entry;
entry.listener = listener;
if (token)
{
entry.token = token;
}
if (auto itr = m_listeners.find(index);
itr == std::end(m_listeners))
{
m_listeners.insert({index, {entry}});
}
else
{
itr->second.push_back(entry);
}
}
template <typename EventTy, typename ObjectTy>
void Events::subscribe(ObjectTy* instance,
void (ObjectTy::*method)(Event const&),
std::shared_ptr<EventToken> token)
{
auto listener = std::bind(method, instance, std::placeholders::_1);
subscribe<EventTy>(listener, token);
}
template <typename EventTy>
void Events::publish(EventTy const& event)
{
size_t index = get<EventTy>();
std::string name = (EventTypeStr[event.type()]
+ std::strlen("EVENT_"));
ML_SYS(FileLogs)->log(LEVEL_DEBUG, "event: " + name);
for (auto& listener: m_listeners.at(index))
{
if (listener.token == std::nullopt
|| listener.token->lock())
{
listener.listener(event);
}
}
}
}
#endif

View File

@ -24,6 +24,7 @@ namespace ml
switch (level)
{
case LEVEL_DEBUG: color = "34"; break;
case LEVEL_INFO: color = "32"; break;
default:
throw log_error {std::string("cannot find color for log level '")

View File

@ -2,8 +2,9 @@
#define ml_LOGS_HPP
#include "../commons.hpp"
#include "../Service.hpp"
#define LOG_LEVELS(G) G(LEVEL_DEBUG)
#define LOG_LEVELS(G) G(LEVEL_DEBUG), G(LEVEL_INFO)
namespace ml
{
@ -13,7 +14,7 @@ namespace ml
/**
* Logs game activities into an output stream.
**/
class Logs
class Logs: public Service
{
public:
explicit Logs(std::ostream& out);

View File

@ -1,16 +1,21 @@
#include <filesystem>
#include <iostream>
#include "conf.hpp"
#include "commons.hpp"
#include "logs/FileLogs.hpp"
#include "events/Events.hpp"
#include "Services.hpp"
#include "Game.hpp"
int main(int, char**)
{
auto logs = std::make_shared<ml::FileLogs>
ML->add<ml::FileLogs>
(std::filesystem::temp_directory_path() / "mornelune.log");
ML->add<ml::Events>();
ml::Game game {logs};
ml::Game game;
game.start();
game.stop();