--- /dev/null
+.config
+temp
+/libmspgame.a
+/libmspgame.so
+/mspgame.pc
--- /dev/null
+package "mspgame"
+{
+ version "0.1";
+
+ require "mspcore";
+ require "mspmath";
+
+ build_info
+ {
+ standard CXX "c++20";
+ };
+
+ library "mspgame"
+ {
+ source "source/game";
+ install true;
+ install_map
+ {
+ map "source" "include/msp";
+ };
+ };
+};
--- /dev/null
+#ifndef MSP_GAME_BASICSYSTEM_
+#define MSP_GAME_BASICSYSTEM_
+
+#include <msp/time/timedelta.h>
+#include "pool.h"
+#include "stage.h"
+#include "system.h"
+
+namespace Msp::Game {
+
+template<typename T>
+concept HasPreTick = requires(T x) { x.pre_tick(); };
+
+template<typename T>
+concept HasTick = requires(T x) { x.tick(Time::TimeDelta()); };
+
+template<typename T>
+concept HasPostTick = requires(T x) { x.post_tick(); };
+
+template<typename T>
+class BasicSystem: public System
+{
+public:
+ BasicSystem(Stage &s): System(s) { }
+
+ void pre_tick() override;
+ void tick(Time::TimeDelta) override;
+ void post_tick() override;
+};
+
+template<typename T>
+void BasicSystem<T>::pre_tick()
+{
+ if constexpr(HasPreTick<T>)
+ stage.iterate_objects<T>([](T &obj){ obj.pre_tick(); });
+}
+
+template<typename T>
+void BasicSystem<T>::tick(Time::TimeDelta dt)
+{
+ if constexpr(HasTick<T>)
+ stage.iterate_objects<T>([dt](T &obj){ obj.tick(dt); });
+}
+
+template<typename T>
+void BasicSystem<T>::post_tick()
+{
+ if constexpr(HasPostTick<T>)
+ stage.iterate_objects<T>([](T &obj){ obj.post_tick(); });
+}
+
+} // namespace Msp::Game
+
+#endif
--- /dev/null
+#include "component.h"
+
+namespace Msp::Game {
+
+Component::Component(Handle<Entity> e):
+ entity(e)
+{ }
+
+} // namespace Msp::Game
--- /dev/null
+#ifndef MSP_GAME_COMPONENT_H_
+#define MSP_GAME_COMPONENT_H_
+
+#include <msp/time/timedelta.h>
+#include "handle.h"
+
+namespace Msp::Game {
+
+class Entity;
+
+class Component
+{
+protected:
+ Handle<Entity> entity;
+
+ Component(Handle<Entity>);
+public:
+ virtual ~Component() = default;
+
+ Handle<Entity> get_entity() const { return entity; }
+
+ virtual void pre_tick() { }
+ virtual void tick(Time::TimeDelta) { }
+ virtual void post_tick() { }
+};
+
+} // namespace Msp::Game
+
+#endif
--- /dev/null
+#include "director.h"
+#include <stdexcept>
+#include <msp/time/utils.h>
+#include "stage.h"
+
+using namespace std;
+
+namespace Msp::Game {
+
+Stage &Director::create_stage()
+{
+ stages.emplace_back(std::make_unique<Stage>());
+ return *stages.back();
+}
+
+void Director::tick()
+{
+ Time::TimeStamp now = Time::now();
+ Time::TimeDelta dt = (last_tick ? now-last_tick : Time::zero);
+ last_tick = now;
+
+ backlog = min(backlog+dt, stepsize*max_backlog_steps);
+ for(unsigned i=0; (i<max_steps_per_frame && backlog>=stepsize); ++i, backlog-=stepsize)
+ for(const auto &s: stages)
+ s->tick(stepsize);
+}
+
+} // namespace Msp::Game
--- /dev/null
+#ifndef MSP_GAME_DIRECTOR_H_
+#define MSP_GAME_DIRECTOR_H_
+
+#include <memory>
+#include <vector>
+#include <msp/time/timedelta.h>
+#include <msp/time/timestamp.h>
+
+namespace Msp::Game {
+
+class Stage;
+
+class Director
+{
+private:
+ std::vector<std::unique_ptr<Stage>> stages;
+ Time::TimeStamp last_tick;
+ Time::TimeDelta stepsize = Time::sec/60;
+ Time::TimeDelta backlog;
+ unsigned max_steps_per_frame = 5;
+ unsigned max_backlog_steps = 600;
+
+public:
+ Stage &create_stage();
+
+ void tick();
+};
+
+} // namespace Msp::Game
+
+#endif
--- /dev/null
+#include "entity.h"
+#include "component.h"
+#include "stage.h"
+
+using namespace std;
+
+namespace Msp::Game {
+
+Entity::Entity(Handle<Entity> p):
+ parent(p)
+{ }
+
+void Entity::add_component(Handle<Component> comp)
+{
+ if(comp->get_entity().get()!=this)
+ throw hierarchy_error();
+
+ components.push_back(comp);
+}
+
+void Entity::remove_component(Handle<Component> comp)
+{
+ erase(components, comp);
+}
+
+void Entity::add_child(Handle<Entity> child)
+{
+ if(child->get_parent().get()!=this)
+ throw hierarchy_error();
+
+ children.push_back(child);
+}
+
+void Entity::remove_child(Handle<Entity> child)
+{
+ erase(children, child);
+}
+
+} // namespace Msp::Game
--- /dev/null
+#ifndef MSP_GAME_ENTITY_H_
+#define MSP_GAME_ENTITY_H_
+
+#include "handle.h"
+
+namespace Msp::Game {
+
+class Component;
+
+class hierarchy_error: public std::logic_error
+{
+public:
+ hierarchy_error(): std::logic_error("hierarchy error") { }
+};
+
+class Entity
+{
+private:
+ Handle<Entity> parent;
+ std::vector<Handle<Component>> components;
+ std::vector<Handle<Entity>> children;
+
+public:
+ Entity(Handle<Entity>);
+ virtual ~Entity();
+
+ void add_component(Handle<Component>);
+ void remove_component(Handle<Component>);
+
+ void add_child(Handle<Entity>);
+ void remove_child(Handle<Entity>);
+
+ Handle<Entity> get_parent() const { return parent; }
+ Handle<Entity> get_root();
+};
+
+
+inline Handle<Entity> Entity::get_root()
+{
+ Handle<Entity> e = Handle<Entity>::from_object(this);
+ while(Handle<Entity> p = e->get_parent())
+ e = p;
+ return e;
+}
+
+} // namespace Msp::Game
+
+#endif
--- /dev/null
+#include "handle.h"
+#include <msp/debug/demangle.h>
+
+using namespace std;
+
+namespace Msp::Game {
+
+invalid_handle::invalid_handle(const type_info &ti):
+ logic_error(Debug::demangle(ti.name()))
+{ }
+
+} // namespace Msp::Game
--- /dev/null
+#ifndef MSP_GAME_HANDLE_H_
+#define MSP_GAME_HANDLE_H_
+
+#include <stdexcept>
+#include "pool.h"
+
+namespace Msp::Game {
+
+class invalid_handle: public std::logic_error
+{
+public:
+ invalid_handle(const std::type_info &);
+};
+
+template<typename T>
+class Handle
+{
+ template<typename U>
+ friend class Handle;
+
+protected:
+ T *ptr = nullptr;
+
+public:
+ Handle() = default;
+
+ static Handle from_object(T *o) { Handle h; h.ptr = o; return h; }
+
+ template<typename U>
+ requires std::is_base_of_v<T, U>
+ Handle(const Handle<U> &other): ptr(other.ptr) { }
+
+ T *get() const { return ptr; }
+ T &operator*() const { return *ptr; }
+ T *operator->() const { return ptr; }
+ explicit operator bool() const { return ptr; }
+
+ bool operator==(const Handle &other) const = default;
+};
+
+} // namespace Msp::Game
+
+#endif
--- /dev/null
+#ifndef MSP_GAME_OWNED_H_
+#define MSP_GAME_OWNED_H_
+
+#include <stdexcept>
+#include "handle.h"
+
+namespace Msp::Game {
+
+class Component;
+class Entity;
+class Root;
+class Stage;
+
+template<typename T>
+class Owned: public Handle<T>
+{
+public:
+ Owned() = default;
+
+ template<typename... Args>
+ Owned(Handle<Entity>, Args &&...);
+
+ template<typename... Args>
+ Owned(Entity &parent, Args &&... args): Owned(Handle<Entity>::from_object(&parent), std::forward<Args>(args)...) { }
+
+ Owned(Owned &&other): Handle<T>(other) { other.ptr = nullptr; }
+ Owned &operator=(Owned &&other);
+ ~Owned() { destroy(); }
+
+private:
+ template<typename O>
+ static Stage &get_stage(O &);
+
+ void destroy();
+};
+
+
+template<typename T>
+template<typename... Args>
+Owned<T>::Owned(Handle<Entity> parent, Args &&... args)
+{
+ if(!parent)
+ throw std::invalid_argument("Owned::Owned");
+
+ using DependentEntity = std::conditional_t<sizeof(T), Entity, Entity>;
+ Handle<DependentEntity> dparent = parent;
+
+ Pool<T> &pool = get_stage(*dparent).get_pools().template get_pool<T>();
+ this->ptr = pool.create(parent, std::forward<Args>(args)...);
+ if constexpr(std::is_base_of_v<Component, T>)
+ dparent->add_component(*this);
+ else
+ dparent->add_child(*this);
+}
+
+template<typename T>
+Owned<T> &Owned<T>::operator=(Owned &&other)
+{
+ destroy();
+
+ this->ptr = other.ptr;
+ other.ptr = nullptr;
+
+ return *this;
+}
+
+template<typename T>
+template<typename O>
+Stage &Owned<T>::get_stage(O &obj)
+{
+ using DependentRoot = std::conditional_t<sizeof(T), Root, Root>;
+ if constexpr(std::is_base_of_v<Component, O>)
+ return get_stage(*obj.get_entity());
+ else if constexpr(std::is_base_of_v<Entity, O>)
+ return dynamic_cast<DependentRoot &>(*obj.get_root()).get_stage();
+ else
+ return obj;
+}
+
+template<typename T>
+void Owned<T>::destroy()
+{
+ T *obj = this->get();
+ if(!obj)
+ return;
+
+ Pool<T> &pool = get_stage(*obj).get_pools().template get_pool<T>();
+
+ if constexpr(std::is_base_of_v<Component, T>)
+ obj->get_entity()->remove_component(*this);
+ else if(auto parent = obj->get_parent().get())
+ parent->remove_child(*this);
+
+ pool.destroy(this->ptr);
+}
+
+} // namespace Msp::Game
+
+#endif
--- /dev/null
+#include "pool.h"
+#include <stdexcept>
+#include <msp/io/print.h>
+
+using namespace std;
+
+namespace Msp::Game {
+
+unsigned PoolPool::get_next_id()
+{
+ static unsigned next_id = 0;
+ return next_id++;
+}
+
+
+PoolBase::PoolBase(uint32_t s, DeleteFunc d):
+ object_size(s),
+ deleter(d)
+{ }
+
+PoolBase::PoolBase(PoolBase &&other):
+ object_size(other.object_size),
+ blocks(other.blocks),
+ free_list(other.free_list),
+ object_count(other.object_count),
+ capacity(other.capacity),
+ deleter(other.deleter)
+{
+ other.blocks = nullptr;
+ other.free_list = nullptr;
+ other.object_count = 0;
+ other.capacity = 0;
+}
+
+PoolBase &PoolBase::operator=(PoolBase &&other)
+{
+ destroy_all();
+
+ object_size = other.object_size;
+ blocks = other.blocks;
+ free_list = other.free_list;
+ object_count = other.object_count;
+ capacity = other.capacity;
+ deleter = other.deleter;
+
+ other.blocks = nullptr;
+ other.free_list = nullptr;
+ other.object_count = 0;
+ other.capacity = 0;
+
+ return *this;
+}
+
+PoolBase::~PoolBase()
+{
+ destroy_all();
+}
+
+void PoolBase::destroy_all()
+{
+ if(object_count>0)
+ IO::print(IO::cerr, "Warning: pool is being destroyed but has %d live objects", object_count);
+
+ unsigned block_count = capacity/BLOCK_SIZE;
+ for(unsigned i=0; i<block_count; ++i)
+ delete[] blocks[i];
+ delete[] reinterpret_cast<char *>(blocks);
+}
+
+void *PoolBase::prepare_allocate()
+{
+ if(object_count>=capacity)
+ add_block();
+
+ uint32_t full_index = free_list[object_count];
+ unsigned block_index = full_index/BLOCK_SIZE;
+ unsigned object_index = full_index%BLOCK_SIZE;
+ return blocks[block_index]+object_index*object_size;
+}
+
+void PoolBase::commit_allocate(void *ptr)
+{
+ uint32_t full_index = free_list[object_count];
+ unsigned block_index = full_index/BLOCK_SIZE;
+ unsigned object_index = full_index%BLOCK_SIZE;
+ void *expected = blocks[block_index]+object_index*object_size;
+ if(ptr!=expected)
+ throw logic_error("PoolBase::commit_allocate does not match prepare_allocate");
+
+ FlagType *flags = reinterpret_cast<FlagType *>(blocks[block_index]+BLOCK_SIZE*object_size);
+ FlagType bit = 1<<(object_index%FLAG_BITS);
+ if(flags[object_index/FLAG_BITS]&bit)
+ throw logic_error("PoolBase::commit_allocate to a not-free index");
+
+ flags[object_index/FLAG_BITS] |= bit;
+ ++object_count;
+}
+
+void PoolBase::add_block()
+{
+ unsigned block_count = capacity/BLOCK_SIZE;
+ char *new_mem = new alignas(char *) char[(block_count+1)*(sizeof(char *)+BLOCK_SIZE*sizeof(uint32_t))];
+ char **new_blocks = reinterpret_cast<char **>(new_mem);
+ uint32_t *new_free = reinterpret_cast<uint32_t *>(new_mem+(block_count+1)*sizeof(char *));
+
+ copy(blocks, blocks+block_count, new_blocks);
+ copy(free_list, free_list+capacity, new_free);
+
+ char *block = new alignas(BLOCK_ALIGNMENT) char[BLOCK_SIZE*object_size+BLOCK_SIZE/8];
+ new_blocks[block_count] = block;
+ FlagType *flags = reinterpret_cast<FlagType *>(block+BLOCK_SIZE*object_size);
+ for(unsigned i=0; i<BLOCK_SIZE; ++i)
+ new_free[capacity+i] = capacity+i;
+ for(unsigned i=0; i<BLOCK_SIZE/FLAG_BITS; ++i)
+ flags[i] = 0;
+
+ delete[] reinterpret_cast<char *>(blocks);
+ blocks = new_blocks;
+ free_list = new_free;
+ capacity += BLOCK_SIZE;
+}
+
+void PoolBase::destroy(void *obj)
+{
+ unsigned block_index = 0;
+ unsigned object_index = BLOCK_SIZE;
+ intptr_t addr = reinterpret_cast<intptr_t>(obj);
+ unsigned block_count = capacity/BLOCK_SIZE;
+ while(block_index<block_count)
+ {
+ intptr_t mem = reinterpret_cast<intptr_t>(blocks[block_index]);
+ intptr_t mem_end = mem+BLOCK_SIZE*object_size;
+ if(addr>=mem && addr<mem_end)
+ {
+ object_index = (addr-mem)/object_size;
+ break;
+ }
+ ++block_index;
+ }
+
+ if(object_index>=BLOCK_SIZE)
+ throw invalid_argument("PoolBase::destroy");
+
+ FlagType *flags = reinterpret_cast<FlagType *>(blocks[block_index]+BLOCK_SIZE*object_size);
+ FlagType bit = 1<<(object_index%FLAG_BITS);
+ if(!(flags[object_index/FLAG_BITS]&bit))
+ throw invalid_argument("PoolBase::destroy");
+
+ deleter(obj);
+ flags[object_index/FLAG_BITS] &= ~bit;
+ --object_count;
+ free_list[object_count] = block_index*BLOCK_SIZE+object_index;
+}
+
+} // namespace Msp::Game
--- /dev/null
+#ifndef MSP_GAME_STORAGE_H_
+#define MSP_GAME_STORAGE_H_
+
+#include <cstdint>
+#include <memory>
+#include <vector>
+#include <msp/core/noncopyable.h>
+
+namespace Msp::Game {
+
+class PoolBase;
+
+template<typename T>
+class Pool;
+
+class PoolPool: public NonCopyable
+{
+private:
+ std::vector<std::unique_ptr<PoolBase>> pools;
+
+public:
+ PoolPool() = default;
+ PoolPool(PoolPool &&) = default;
+ PoolPool &operator=(PoolPool &&) = default;
+
+private:
+ static unsigned get_next_id();
+
+public:
+ template<typename T>
+ static unsigned get_type_id() { static unsigned id = get_next_id(); return id; }
+
+ template<typename T>
+ Pool<T> &get_pool();
+};
+
+
+class PoolBase: public NonCopyable
+{
+private:
+ using DeleteFunc = void(void *);
+ using FlagType = uint32_t;
+
+ static constexpr std::size_t BLOCK_SIZE = 512;
+ static constexpr std::size_t BLOCK_ALIGNMENT = 64;
+ static constexpr std::size_t FLAG_BITS = sizeof(FlagType)*8;
+
+ std::size_t object_size = 0;
+ char **blocks = nullptr;
+ std::uint32_t *free_list = nullptr;
+ std::uint32_t object_count = 0;
+ std::uint32_t capacity = 0;
+ DeleteFunc *deleter = nullptr;
+
+protected:
+ PoolBase(std::uint32_t, DeleteFunc);
+public:
+ PoolBase(PoolBase &&);
+ PoolBase &operator=(PoolBase &&);
+ ~PoolBase();
+
+protected:
+ void destroy_all();
+
+ template<typename F>
+ void iterate_objects(const F &);
+
+ void *prepare_allocate();
+ void commit_allocate(void *);
+
+private:
+ void add_block();
+
+public:
+ void destroy(void *);
+};
+
+
+template<typename T>
+class Pool: public PoolBase
+{
+public:
+ Pool(): PoolBase(sizeof(T), delete_object) { }
+
+ template<typename... Args>
+ T *create(Args &&...);
+
+ template<typename F>
+ void iterate_objects(const F &func)
+ { PoolBase::iterate_objects([&func](void *ptr){ func(*static_cast<T *>(ptr)); }); }
+
+private:
+ static void delete_object(void *ptr) { std::destroy_at(static_cast<T *>(ptr)); }
+};
+
+
+template<typename T>
+inline Pool<T> &PoolPool::get_pool()
+{
+ unsigned id = get_type_id<T>();
+ if(pools.size()<=id)
+ pools.resize(id+1);
+
+ std::unique_ptr<PoolBase> &ptr = pools[id];
+ if(!ptr)
+ ptr = std::make_unique<Pool<T>>();
+
+ return *static_cast<Pool<T> *>(ptr.get());
+}
+
+
+template<typename F>
+inline void PoolBase::iterate_objects(const F &func)
+{
+ unsigned block_count = capacity/BLOCK_SIZE;
+ for(unsigned i=0; i<block_count; ++i)
+ {
+ char *ptr = blocks[i];
+ const FlagType *flags = reinterpret_cast<FlagType *>(ptr+BLOCK_SIZE*object_size);
+ for(unsigned j=0; j<BLOCK_SIZE; j+=FLAG_BITS)
+ {
+ char *end = ptr+FLAG_BITS*object_size;
+ if(FlagType f = *flags)
+ {
+ if(!static_cast<FlagType>(~f))
+ {
+ for(; ptr!=end; ptr+=object_size)
+ func(static_cast<void *>(ptr));
+ }
+ else
+ {
+ for(; ptr!=end; ptr+=object_size, f>>=1)
+ if(f&1)
+ func(static_cast<void *>(ptr));
+ }
+ }
+ else
+ ptr = end;
+ ++flags;
+ }
+ }
+}
+
+
+template<typename T>
+template<typename... Args>
+inline T *Pool<T>::create(Args &&... args)
+{
+ void *ptr = prepare_allocate();
+ T *obj = std::construct_at(static_cast<T *>(ptr), std::forward<Args>(args)...);
+ commit_allocate(ptr);
+ return obj;
+}
+
+} // namespace Msp::Game
+
+#endif
--- /dev/null
+#ifndef MSP_GAME_ROOT_H_
+#define MSP_GAME_ROOT_H_
+
+#include "entity.h"
+
+namespace Msp::Game {
+
+class Stage;
+
+class Root: public Entity
+{
+private:
+ Stage &stage;
+
+public:
+ Root(Stage &s): Entity(Handle<Entity>()), stage(s) { }
+
+ Stage &get_stage() const { return stage; }
+};
+
+} // namespace Msp::Game
+
+#endif
--- /dev/null
+#include "stage.h"
+#include "system.h"
+
+namespace Msp::Game {
+
+Stage::Stage():
+ root(*this)
+{ }
+
+// Hide ~unique_ptr<System> from the header
+Stage::~Stage()
+{ }
+
+void Stage::tick(Time::TimeDelta dt)
+{
+ for(const auto &s: systems)
+ s->pre_tick();
+ for(const auto &s: systems)
+ s->tick(dt);
+ for(const auto &s: systems)
+ s->post_tick();
+}
+
+} // namespace Msp::Game
--- /dev/null
+#ifndef MSP_GAME_STAGE_H_
+#define MSP_GAME_STAGE_H_
+
+#include <memory>
+#include <msp/time/timedelta.h>
+#include "handle.h"
+#include "root.h"
+
+namespace Msp::Game {
+
+class System;
+
+class Stage
+{
+private:
+ PoolPool pools;
+ Root root;
+ std::vector<std::unique_ptr<System>> systems;
+
+public:
+ Stage();
+ ~Stage();
+
+ PoolPool &get_pools() { return pools; }
+ Handle<Root> get_root() { return Handle<Root>::from_object(&root); }
+
+ template<typename T, typename F>
+ void iterate_objects(const F &);
+
+ template<typename T, typename... Args>
+ T &add_system(Args &&...);
+
+ const std::vector<std::unique_ptr<System>> &get_systems() const { return systems; }
+
+ void tick(Time::TimeDelta);
+};
+
+template<typename T, typename F>
+void Stage::iterate_objects(const F &func)
+{
+ pools.get_pool<T>().iterate_objects(func);
+}
+
+template<typename T, typename... Args>
+T &Stage::add_system(Args &&... args)
+{
+ systems.emplace_back(std::make_unique<T>(*this, std::forward<Args>(args)...));
+ return static_cast<T &>(*systems.back());
+}
+
+} // namespace Msp::Game
+
+#endif
--- /dev/null
+#ifndef MSP_GAME_SYSTEM_H_
+#define MSP_GAME_SYSTEM_H_
+
+#include <msp/time/timedelta.h>
+
+namespace Msp::Game {
+
+class Stage;
+
+class System
+{
+protected:
+ Stage &stage;
+
+ System(Stage &s): stage(s) { }
+public:
+ virtual ~System() = default;
+
+ virtual void pre_tick() = 0;
+ virtual void tick(Time::TimeDelta) = 0;
+ virtual void post_tick() = 0;
+};
+
+} // namespace Msp::Game
+
+#endif