Created
October 14, 2015 22:40
-
-
Save alecthomas/24d0f33fbfc9ec1e5736 to your computer and use it in GitHub Desktop.
Experimental (not working) EntityX transactional support
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <condition_variable> | |
#include <thread> | |
#include <mutex> | |
#include <iostream> | |
#include <algorithm> | |
#include <type_traits> | |
#include <vector> | |
#include <cstdint> | |
#include <iterator> | |
#include <functional> | |
#include <cassert> | |
#include <unordered_map> | |
#include <set> | |
#include <atomic> | |
using std::cout; | |
using std::endl; | |
template <typename T> | |
std::ostream &operator << (std::ostream &out, const std::vector<T> &v) { | |
out << "["; | |
for (auto &i : v) out << i << " "; | |
out << "]"; | |
return out; | |
} | |
namespace details { | |
template <typename T, typename... Ts> | |
struct get_component_index; | |
template <typename T, typename... Ts> | |
struct get_component_index<T, T, Ts...> : std::integral_constant<std::size_t, 0> {}; | |
template <typename T, typename Tail, typename... Ts> | |
struct get_component_index<T, Tail, Ts...> : | |
std::integral_constant<std::size_t, 1 + get_component_index<T, Ts...>::value> {}; | |
template <typename T, typename... Ts> | |
struct get_component_size; | |
template <typename T, typename... Ts> | |
struct get_component_size<T, T, Ts...> : std::integral_constant<std::size_t, sizeof(T)> {}; | |
template <typename T, typename Tail, typename... Ts> | |
struct get_component_size<T, Tail, Ts...> : | |
std::integral_constant<std::size_t, get_component_size<T, Ts...>::value> {}; | |
template <typename T, typename... Ts> | |
struct is_valid_component; | |
template <typename T, typename... Ts> | |
struct is_valid_component<T, T, Ts...> : std::true_type {}; | |
template <typename T, typename Tail, typename... Ts> | |
struct is_valid_component<T, Tail, Ts...> : is_valid_component<T, Ts...>::type {}; | |
template <typename T> | |
struct is_valid_component<T> { | |
// condition is always false, but should be dependant of T | |
static_assert(sizeof(T) == 0, "type is not a component"); | |
}; | |
template <typename T, typename... Ts> | |
struct get_component_offset; | |
template <typename T, typename... Ts> | |
struct get_component_offset<T, T, Ts...> : std::integral_constant<std::size_t, 0> {}; | |
template <typename T, typename Tail, typename... Ts> | |
struct get_component_offset<T, Tail, Ts...> : | |
std::integral_constant<std::size_t, sizeof(Tail) + get_component_offset<T, Ts...>::value> {}; | |
template <typename... Ts> | |
struct get_component_size_sum; | |
template <typename T> | |
struct get_component_size_sum<T> : std::integral_constant<std::size_t, sizeof(T)> {}; | |
template <typename T, typename... Ts> | |
struct get_component_size_sum<T, Ts...> : | |
std::integral_constant<std::size_t, sizeof(T) + get_component_size_sum<Ts...>::value> {}; | |
// Apply a template method to each type in a variadic template: | |
// | |
// template <typename T> struct print { | |
// void apply(int) { | |
// cout << get<T>() << endl; | |
// } | |
// } | |
// each<print, 0, int, float>(); | |
template <template <typename> class F, int N, typename T> | |
void each() { | |
F<T>::apply(N); | |
} | |
template <template <typename> class F, int N, typename T0, typename T1, typename ... Ts> | |
void each() { | |
each<F, N, T0>(); | |
each<F, N + 1, T1, Ts...>(); | |
} | |
} // namespace details | |
template <typename C> | |
class Component { | |
public: | |
std::uint32_t id() const { return it_->first; } | |
C *operator -> () { return &it_->second; } | |
operator C* () { return &it_->second; } | |
const C *operator -> () const { return &it_->second; } | |
operator const C* () const { return &it_->second; } | |
bool operator !() const { return it_ == end_; } | |
private: | |
template <typename ... Components> | |
friend class EntityManager; | |
template <typename ... Components> | |
friend class Storage; | |
explicit Component(const typename std::unordered_map<std::uint32_t, C>::iterator &it, | |
const typename std::unordered_map<std::uint32_t, C>::iterator &end) : it_(it), end_(end) {} | |
typename std::unordered_map<std::uint32_t, C>::iterator it_, end_; | |
}; | |
template <typename ... Components> | |
class Storage { | |
public: | |
explicit Storage(std::atomic<std::uint32_t> &id) : id_(id) { | |
init<0, Components...>(); | |
} | |
~Storage() { destroy<0, Components...>(); } | |
std::uint32_t create() { | |
return id_++; | |
} | |
template <typename C, typename ... Args> | |
Component<C> assign(std::uint32_t id, Args && ... args) { | |
auto &t = get<C>(); | |
auto pair = t.emplace(id, C(std::forward<Args>(args)...)); | |
return Component<C>(pair.first, t.end()); | |
} | |
template <typename C> | |
Component<C> get(std::uint32_t index) { | |
auto &t = get<C>(); | |
return Component<C>(t.find(index), t.end()); | |
} | |
template <typename C> | |
void remove(std::uint32_t index) { | |
get<C>().erase(index); | |
} | |
void destroy(std::uint32_t index) { | |
destroy<Components...>(index); | |
} | |
private: | |
template <typename C> | |
using Map = std::unordered_map<std::uint32_t, C>; | |
template <typename ... Cs> | |
friend class Transaction; | |
template <typename C> | |
std::unordered_map<std::uint32_t, C> &get() { | |
return *reinterpret_cast<Map<C>*>(components_[details::get_component_index<C, Components...>::value]); | |
} | |
template <int N, typename C> | |
void init() { | |
components_[N] = new Map<C>(); | |
} | |
template <int N, typename C0, typename C1, typename ... Cn> | |
void init() { | |
init<N, C0>(); | |
init<N+1, C1, Cn...>(); | |
} | |
template <int N, typename C> | |
void destroy() { | |
std::unordered_map<std::uint32_t, C> *m = components_[N]; | |
delete m; | |
} | |
template <int N, typename C0, typename C1, typename ... Cn> | |
void destroy() { | |
init<N, C0>(); | |
init<N+1, C1, Cn...>(); | |
} | |
template <typename C> | |
void merge(Storage &storage) { | |
auto &dest = get<C>(); | |
for (auto it : storage.get<C>()) | |
dest[it.first] = it.second; | |
} | |
template <typename C0, typename C1, typename ... Cn> | |
void merge(Storage &storage) { | |
merge<C0>(storage); | |
merge<C1, Cn...>(storage); | |
} | |
void *components_[sizeof...(Components)]; | |
std::atomic<std::uint32_t> &id_; | |
}; | |
template <typename ... Components> | |
class EntityManager { | |
public: | |
EntityManager() : id_(0), storage_(id_) {} | |
std::uint32_t create() { | |
return storage_.create(); | |
} | |
template <typename C, typename ... Args> | |
Component<C> assign(std::uint32_t id, Args && ... args) { | |
return storage_.template assign<C, Args...>(id, std::forward<Args>(args)...); | |
} | |
private: | |
template <typename ... Cs> | |
friend class TransactionManager; | |
std::atomic<std::uint32_t> id_; | |
Storage<Components...> storage_; | |
}; | |
template <typename ... Components> | |
class Transaction { | |
public: | |
std::uint32_t create() { | |
return change_->tx.create(); | |
} | |
template <typename C> | |
Component<C> get(std::uint32_t id) { | |
Component<C> c = change_->tx.template get<C>(id); | |
if (!c) { | |
c = parent_.template get<C>(id); | |
} | |
return c; | |
} | |
template <typename C, typename ... Args> | |
Component<C> assign(std::uint32_t id, Args && ... args) { | |
return change_->tx.template assign<C, Args...>(id, std::forward<Args>(args)...); | |
} | |
void destroy(std::uint32_t id) { | |
change_->deletes.push_back(id); | |
} | |
private: | |
template <typename ... Cs> | |
friend class TransactionManager; | |
explicit Transaction(Storage<Components...> &parent, typename Storage<Components...>::Change *change) : parent_(parent), change_(change) {} | |
Storage<Components...> &parent_; | |
typename Storage<Components...>::Change change_; | |
}; | |
template <typename ... Components> | |
class TransactionManager { | |
public: | |
explicit TransactionManager(EntityManager<Components...> &em) : em_(em) {} | |
Transaction<Components...> begin() { | |
auto state = new typename Transaction<Components...>::State{ | |
Storage<Components...>(em_.id_), | |
}; | |
transactions_.push_back(state); | |
return Transaction<Components...>(em_.storage_, state); | |
} | |
void commit() { | |
for (auto &state : transactions_) | |
em_.storage_.merge(*state); | |
for (auto state : transactions_) | |
for (auto id : storage.) | |
} | |
private: | |
EntityManager<Components...> &em_; | |
std::vector<Storage<Components...>::State*> transactions_; | |
}; | |
struct Direction { | |
Direction() = default; | |
Direction(float x, float y) : x(x), y(y) {} | |
float x, y; | |
}; | |
std::ostream &operator << (std::ostream &out, const Direction &p) { | |
return out << "(" << p.x << ", " << p.y << ")"; | |
} | |
struct Position { | |
Position() = default; | |
Position(float x, float y): x(x), y(y) {} | |
float x, y; | |
}; | |
std::ostream &operator << (std::ostream &out, const Position &p) { | |
return out << "(" << p.x << ", " << p.y << ")"; | |
} | |
template <typename ... Components> | |
TransactionManager<Components...> | |
int main() { | |
EntityManager<Position, Direction> entities; | |
{ | |
TransactionManager<Position, Direction> transactions(entities); | |
// Thread 0 | |
auto tx = transactions.begin(); | |
std::uint32_t id = tx.create(); | |
cout << *tx.assign<Position>(id, 1, 2.5) << endl; | |
cout << *tx.assign<Direction>(id, 3, 4) << endl; | |
tx.commit(); | |
transactions.commit(); | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment