Added librarys and test application
This commit is contained in:
8345
include/entityx/3rdparty/catch.hpp
vendored
Normal file
8345
include/entityx/3rdparty/catch.hpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
580
include/entityx/3rdparty/simplesignal.h
vendored
Normal file
580
include/entityx/3rdparty/simplesignal.h
vendored
Normal file
@@ -0,0 +1,580 @@
|
||||
// CC0 Public Domain: http://creativecommons.org/publicdomain/zero/1.0/
|
||||
#ifndef SIMPLE_SIGNAL_H__
|
||||
#define SIMPLE_SIGNAL_H__
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
namespace Simple {
|
||||
|
||||
namespace Lib {
|
||||
|
||||
/// ProtoSignal is the template implementation for callback list.
|
||||
template <typename, typename>
|
||||
class ProtoSignal; // undefined
|
||||
|
||||
/// CollectorInvocation invokes signal handlers differently depending on return
|
||||
/// type.
|
||||
template <typename, typename>
|
||||
struct CollectorInvocation;
|
||||
|
||||
/// CollectorLast returns the result of the last signal handler from a signal
|
||||
/// emission.
|
||||
template <typename Result>
|
||||
struct CollectorLast {
|
||||
typedef Result CollectorResult;
|
||||
explicit CollectorLast() : last_() {}
|
||||
inline bool operator()(Result r) {
|
||||
last_ = r;
|
||||
return true;
|
||||
}
|
||||
CollectorResult result() { return last_; }
|
||||
|
||||
private:
|
||||
Result last_;
|
||||
};
|
||||
|
||||
/// CollectorDefault implements the default signal handler collection behaviour.
|
||||
template <typename Result>
|
||||
struct CollectorDefault : CollectorLast<Result> {};
|
||||
|
||||
/// CollectorDefault specialisation for signals with void return type.
|
||||
template <>
|
||||
struct CollectorDefault<void> {
|
||||
typedef void CollectorResult;
|
||||
void result() {}
|
||||
inline bool operator()(void) { return true; }
|
||||
};
|
||||
|
||||
/// CollectorInvocation specialisation for regular signals.
|
||||
template <class Collector, class R, class... Args>
|
||||
struct CollectorInvocation<Collector, R(Args...)> {
|
||||
inline bool invoke(Collector &collector, const std::function<R(Args...)> &cbf,
|
||||
Args... args) {
|
||||
return collector(cbf(args...));
|
||||
}
|
||||
};
|
||||
|
||||
/// CollectorInvocation specialisation for signals with void return type.
|
||||
template <class Collector, class... Args>
|
||||
struct CollectorInvocation<Collector, void(Args...)> {
|
||||
inline bool invoke(Collector &collector,
|
||||
const std::function<void(Args...)> &cbf, Args... args) {
|
||||
cbf(args...);
|
||||
return collector();
|
||||
}
|
||||
};
|
||||
|
||||
/// ProtoSignal template specialised for the callback signature and collector.
|
||||
template <class Collector, class R, class... Args>
|
||||
class ProtoSignal<R(Args...), Collector> : private CollectorInvocation<
|
||||
Collector, R(Args...)> {
|
||||
protected:
|
||||
typedef std::function<R(Args...)> CbFunction;
|
||||
typedef typename CbFunction::result_type Result;
|
||||
typedef typename Collector::CollectorResult CollectorResult;
|
||||
|
||||
private:
|
||||
/// SignalLink implements a doubly-linked ring with ref-counted nodes
|
||||
/// containing the signal handlers.
|
||||
struct SignalLink {
|
||||
SignalLink *next, *prev;
|
||||
CbFunction function;
|
||||
int ref_count;
|
||||
explicit SignalLink(const CbFunction &cbf)
|
||||
: next(0), prev(0), function(cbf), ref_count(1) {}
|
||||
/*dtor*/ ~SignalLink() { assert(ref_count == 0); }
|
||||
void incref() {
|
||||
ref_count += 1;
|
||||
assert(ref_count > 0);
|
||||
}
|
||||
void decref() {
|
||||
ref_count -= 1;
|
||||
if (!ref_count)
|
||||
delete this;
|
||||
else
|
||||
assert(ref_count > 0);
|
||||
}
|
||||
void unlink() {
|
||||
function = nullptr;
|
||||
if (next) next->prev = prev;
|
||||
if (prev) prev->next = next;
|
||||
decref();
|
||||
// leave intact ->next, ->prev for stale iterators
|
||||
}
|
||||
size_t add_before(const CbFunction &cb) {
|
||||
SignalLink *link = new SignalLink(cb);
|
||||
link->prev = prev; // link to last
|
||||
link->next = this;
|
||||
prev->next = link; // link from last
|
||||
prev = link;
|
||||
static_assert(sizeof(link) == sizeof(size_t), "sizeof size_t");
|
||||
return size_t(link);
|
||||
}
|
||||
bool deactivate(const CbFunction &cbf) {
|
||||
if (cbf == function) {
|
||||
function = 0; // deactivate static head
|
||||
return true;
|
||||
}
|
||||
for (SignalLink *link = this->next ? this->next : this; link != this;
|
||||
link = link->next)
|
||||
if (cbf == link->function) {
|
||||
link->unlink(); // deactivate and unlink sibling
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool remove_sibling(size_t id) {
|
||||
for (SignalLink *link = this->next ? this->next : this; link != this;
|
||||
link = link->next)
|
||||
if (id == size_t(link)) {
|
||||
link->unlink(); // deactivate and unlink sibling
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
SignalLink *callback_ring_; // linked ring of callback nodes
|
||||
/*copy-ctor*/ ProtoSignal(const ProtoSignal &) = delete;
|
||||
ProtoSignal &operator=(const ProtoSignal &) = delete;
|
||||
void ensure_ring() {
|
||||
if (!callback_ring_) {
|
||||
callback_ring_ = new SignalLink(CbFunction()); // ref_count = 1
|
||||
callback_ring_->incref(); // ref_count = 2, head of ring, can be
|
||||
// deactivated but not removed
|
||||
callback_ring_->next = callback_ring_; // ring head initialization
|
||||
callback_ring_->prev = callback_ring_; // ring tail initialization
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
/// ProtoSignal constructor, connects default callback if non-0.
|
||||
ProtoSignal(const CbFunction &method) : callback_ring_(0) {
|
||||
if (method != 0) {
|
||||
ensure_ring();
|
||||
callback_ring_->function = method;
|
||||
}
|
||||
}
|
||||
/// ProtoSignal destructor releases all resources associated with this signal.
|
||||
~ProtoSignal() {
|
||||
if (callback_ring_) {
|
||||
while (callback_ring_->next != callback_ring_)
|
||||
callback_ring_->next->unlink();
|
||||
assert(callback_ring_->ref_count >= 2);
|
||||
callback_ring_->decref();
|
||||
callback_ring_->decref();
|
||||
}
|
||||
}
|
||||
/// Operator to add a new function or lambda as signal handler, returns a
|
||||
/// handler connection ID.
|
||||
size_t connect(const CbFunction &cb) {
|
||||
ensure_ring();
|
||||
return callback_ring_->add_before(cb);
|
||||
}
|
||||
/// Operator to remove a signal handler through it connection ID, returns if a
|
||||
/// handler was removed.
|
||||
bool disconnect(size_t connection) {
|
||||
return callback_ring_ ? callback_ring_->remove_sibling(connection) : false;
|
||||
}
|
||||
/// Emit a signal, i.e. invoke all its callbacks and collect return types with
|
||||
/// the Collector.
|
||||
CollectorResult emit(Args... args) {
|
||||
Collector collector;
|
||||
if (!callback_ring_) return collector.result();
|
||||
SignalLink *link = callback_ring_;
|
||||
link->incref();
|
||||
do {
|
||||
if (link->function != 0) {
|
||||
const bool continue_emission =
|
||||
this->invoke(collector, link->function, args...);
|
||||
if (!continue_emission) break;
|
||||
}
|
||||
SignalLink *old = link;
|
||||
link = old->next;
|
||||
link->incref();
|
||||
old->decref();
|
||||
} while (link != callback_ring_);
|
||||
link->decref();
|
||||
return collector.result();
|
||||
}
|
||||
// Number of connected slots.
|
||||
std::size_t size() {
|
||||
std::size_t size = 0;
|
||||
SignalLink *link = callback_ring_;
|
||||
link->incref();
|
||||
do {
|
||||
if (link->function != 0) {
|
||||
size++;
|
||||
}
|
||||
SignalLink *old = link;
|
||||
link = old->next;
|
||||
link->incref();
|
||||
old->decref();
|
||||
} while (link != callback_ring_);
|
||||
return size;
|
||||
}
|
||||
};
|
||||
|
||||
} // Lib
|
||||
// namespace Simple
|
||||
|
||||
/**
|
||||
* Signal is a template type providing an interface for arbitrary callback
|
||||
* lists.
|
||||
* A signal type needs to be declared with the function signature of its
|
||||
* callbacks,
|
||||
* and optionally a return result collector class type.
|
||||
* Signal callbacks can be added with operator+= to a signal and removed with
|
||||
* operator-=, using
|
||||
* a callback connection ID return by operator+= as argument.
|
||||
* The callbacks of a signal are invoked with the emit() method and arguments
|
||||
* according to the signature.
|
||||
* The result returned by emit() depends on the signal collector class. By
|
||||
* default, the result of
|
||||
* the last callback is returned from emit(). Collectors can be implemented to
|
||||
* accumulate callback
|
||||
* results or to halt a running emissions in correspondance to callback results.
|
||||
* The signal implementation is safe against recursion, so callbacks may be
|
||||
* removed and
|
||||
* added during a signal emission and recursive emit() calls are also safe.
|
||||
* The overhead of an unused signal is intentionally kept very low, around the
|
||||
* size of a single pointer.
|
||||
* Note that the Signal template types is non-copyable.
|
||||
*/
|
||||
template <typename SignalSignature,
|
||||
class Collector = Lib::CollectorDefault<
|
||||
typename std::function<SignalSignature>::result_type>>
|
||||
struct Signal /*final*/ : Lib::ProtoSignal<SignalSignature, Collector> {
|
||||
typedef Lib::ProtoSignal<SignalSignature, Collector> ProtoSignal;
|
||||
typedef typename ProtoSignal::CbFunction CbFunction;
|
||||
/// Signal constructor, supports a default callback as argument.
|
||||
Signal(const CbFunction &method = CbFunction()) : ProtoSignal(method) {}
|
||||
};
|
||||
|
||||
/// This function creates a std::function by binding @a object to the member
|
||||
/// function pointer @a method.
|
||||
template <class Instance, class Class, class R, class... Args>
|
||||
std::function<R(Args...)> slot(Instance &object, R (Class::*method)(Args...)) {
|
||||
return [&object, method](Args... args) { return (object.*method)(args...); };
|
||||
}
|
||||
|
||||
/// This function creates a std::function by binding @a object to the member
|
||||
/// function pointer @a method.
|
||||
template <class Class, class R, class... Args>
|
||||
std::function<R(Args...)> slot(Class *object, R (Class::*method)(Args...)) {
|
||||
return [object, method](Args... args) { return (object->*method)(args...); };
|
||||
}
|
||||
|
||||
/// Keep signal emissions going while all handlers return !0 (true).
|
||||
template <typename Result>
|
||||
struct CollectorUntil0 {
|
||||
typedef Result CollectorResult;
|
||||
explicit CollectorUntil0() : result_() {}
|
||||
const CollectorResult &result() { return result_; }
|
||||
inline bool operator()(Result r) {
|
||||
result_ = r;
|
||||
return result_ ? true : false;
|
||||
}
|
||||
|
||||
private:
|
||||
CollectorResult result_;
|
||||
};
|
||||
|
||||
/// Keep signal emissions going while all handlers return 0 (false).
|
||||
template <typename Result>
|
||||
struct CollectorWhile0 {
|
||||
typedef Result CollectorResult;
|
||||
explicit CollectorWhile0() : result_() {}
|
||||
const CollectorResult &result() { return result_; }
|
||||
inline bool operator()(Result r) {
|
||||
result_ = r;
|
||||
return result_ ? false : true;
|
||||
}
|
||||
|
||||
private:
|
||||
CollectorResult result_;
|
||||
};
|
||||
|
||||
/// CollectorVector returns the result of the all signal handlers from a signal
|
||||
/// emission in a std::vector.
|
||||
template <typename Result>
|
||||
struct CollectorVector {
|
||||
typedef std::vector<Result> CollectorResult;
|
||||
const CollectorResult &result() { return result_; }
|
||||
inline bool operator()(Result r) {
|
||||
result_.push_back(r);
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
CollectorResult result_;
|
||||
};
|
||||
|
||||
} // Simple
|
||||
|
||||
#endif // SIMPLE_SIGNAL_H__
|
||||
|
||||
#ifdef ENABLE_SIMPLE_SIGNAL_TESTS
|
||||
|
||||
#include <string>
|
||||
#include <stdarg.h>
|
||||
#include <time.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#ifdef __MACH__
|
||||
#include <mach/clock.h>
|
||||
#include <mach/mach.h>
|
||||
#endif
|
||||
|
||||
static std::string string_printf(const char *format, ...)
|
||||
__attribute__((__format__(__printf__, 1, 2)));
|
||||
static std::string string_printf(const char *format, ...) {
|
||||
std::string result;
|
||||
char *str = 0;
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
if (vasprintf(&str, format, args) >= 0) result = str;
|
||||
va_end(args);
|
||||
if (str) free(str);
|
||||
return result;
|
||||
}
|
||||
|
||||
static uint64_t timestamp_benchmark() {
|
||||
struct timespec tp = {0, 0};
|
||||
|
||||
#ifdef __MACH__ // OS X does not have clock_gettime, use clock_get_time
|
||||
clock_serv_t cclock;
|
||||
mach_timespec_t mts;
|
||||
host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock);
|
||||
clock_get_time(cclock, &mts);
|
||||
mach_port_deallocate(mach_task_self(), cclock);
|
||||
tp.tv_sec = mts.tv_sec;
|
||||
tp.tv_nsec = mts.tv_nsec;
|
||||
#else
|
||||
clock_gettime(CLOCK_REALTIME, &tp);
|
||||
#endif
|
||||
uint64_t stamp = tp.tv_sec * 1000000000ULL + tp.tv_nsec;
|
||||
return stamp;
|
||||
}
|
||||
|
||||
struct TestCounter {
|
||||
static uint64_t get();
|
||||
static void set(uint64_t);
|
||||
static void add2(void *, uint64_t);
|
||||
};
|
||||
|
||||
namespace { // Anon
|
||||
void (*test_counter_add2)(void *, uint64_t) =
|
||||
TestCounter::add2; // external symbol to prevent easy inlining
|
||||
static uint64_t test_counter_var = 0;
|
||||
} // Anon
|
||||
|
||||
class BasicSignalTests {
|
||||
static std::string accu;
|
||||
struct Foo {
|
||||
char foo_bool(float f, int i, std::string s) {
|
||||
accu += string_printf("Foo: %.2f\n", f + i + s.size());
|
||||
return true;
|
||||
}
|
||||
};
|
||||
static char float_callback(float f, int, std::string) {
|
||||
accu += string_printf("float: %.2f\n", f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
public:
|
||||
static void run() {
|
||||
accu = "";
|
||||
Simple::Signal<char(float, int, std::string)> sig1;
|
||||
size_t id1 = sig1.connect(float_callback);
|
||||
size_t id2 = sig1.connect([](float, int i, std::string) {
|
||||
accu += string_printf("int: %d\n", i);
|
||||
return 0;
|
||||
});
|
||||
size_t id3 = sig1.connect([](float, int, const std::string &s) {
|
||||
accu += string_printf("string: %s\n", s.c_str());
|
||||
return 0;
|
||||
});
|
||||
sig1.emit(.3, 4, "huhu");
|
||||
bool success;
|
||||
success = sig1.disconnect(id1);
|
||||
assert(success == true);
|
||||
success = sig1.disconnect(id1);
|
||||
assert(success == false);
|
||||
success = sig1.disconnect(id2);
|
||||
assert(success == true);
|
||||
success = sig1.disconnect(id3);
|
||||
assert(success == true);
|
||||
success = sig1.disconnect(id3);
|
||||
assert(success == false);
|
||||
success = sig1.disconnect(id2);
|
||||
assert(success == false);
|
||||
Foo foo;
|
||||
sig1.connect(Simple::slot(foo, &Foo::foo_bool));
|
||||
sig1.connect(Simple::slot(&foo, &Foo::foo_bool));
|
||||
sig1.emit(.5, 1, "12");
|
||||
|
||||
Simple::Signal<void(std::string, int)> sig2;
|
||||
sig2.connect([](std::string msg,
|
||||
int) { accu += string_printf("msg: %s", msg.c_str()); });
|
||||
sig2.connect([](std::string,
|
||||
int d) { accu += string_printf(" *%d*\n", d); });
|
||||
sig2.emit("in sig2", 17);
|
||||
|
||||
accu += "DONE";
|
||||
|
||||
const char *expected =
|
||||
"float: 0.30\n"
|
||||
"int: 4\n"
|
||||
"string: huhu\n"
|
||||
"Foo: 3.50\n"
|
||||
"Foo: 3.50\n"
|
||||
"msg: in sig2 *17*\n"
|
||||
"DONE";
|
||||
assert(accu == expected);
|
||||
}
|
||||
};
|
||||
std::string BasicSignalTests::accu;
|
||||
|
||||
class TestCollectorVector {
|
||||
static int handler1() { return 1; }
|
||||
static int handler42() { return 42; }
|
||||
static int handler777() { return 777; }
|
||||
|
||||
public:
|
||||
static void run() {
|
||||
Simple::Signal<int(), Simple::CollectorVector<int>> sig_vector;
|
||||
sig_vector.connect(handler777);
|
||||
sig_vector.connect(handler42);
|
||||
sig_vector.connect(handler1);
|
||||
sig_vector.connect(handler42);
|
||||
sig_vector.connect(handler777);
|
||||
std::vector<int> results = sig_vector.emit();
|
||||
const std::vector<int> reference = {777, 42, 1, 42, 777, };
|
||||
assert(results == reference);
|
||||
}
|
||||
};
|
||||
|
||||
class TestCollectorUntil0 {
|
||||
bool check1, check2;
|
||||
TestCollectorUntil0() : check1(0), check2(0) {}
|
||||
bool handler_true() {
|
||||
check1 = true;
|
||||
return true;
|
||||
}
|
||||
bool handler_false() {
|
||||
check2 = true;
|
||||
return false;
|
||||
}
|
||||
bool handler_abort() { abort(); }
|
||||
|
||||
public:
|
||||
static void run() {
|
||||
TestCollectorUntil0 self;
|
||||
Simple::Signal<bool(), Simple::CollectorUntil0<bool>> sig_until0;
|
||||
sig_until0.connect(Simple::slot(self, &TestCollectorUntil0::handler_true));
|
||||
sig_until0.connect(Simple::slot(self, &TestCollectorUntil0::handler_false));
|
||||
sig_until0.connect(Simple::slot(self, &TestCollectorUntil0::handler_abort));
|
||||
assert(!self.check1 && !self.check2);
|
||||
const bool result = sig_until0.emit();
|
||||
assert(!result && self.check1 && self.check2);
|
||||
}
|
||||
};
|
||||
|
||||
class TestCollectorWhile0 {
|
||||
bool check1, check2;
|
||||
TestCollectorWhile0() : check1(0), check2(0) {}
|
||||
bool handler_0() {
|
||||
check1 = true;
|
||||
return false;
|
||||
}
|
||||
bool handler_1() {
|
||||
check2 = true;
|
||||
return true;
|
||||
}
|
||||
bool handler_abort() { abort(); }
|
||||
|
||||
public:
|
||||
static void run() {
|
||||
TestCollectorWhile0 self;
|
||||
Simple::Signal<bool(), Simple::CollectorWhile0<bool>> sig_while0;
|
||||
sig_while0.connect(Simple::slot(self, &TestCollectorWhile0::handler_0));
|
||||
sig_while0.connect(Simple::slot(self, &TestCollectorWhile0::handler_1));
|
||||
sig_while0.connect(Simple::slot(self, &TestCollectorWhile0::handler_abort));
|
||||
assert(!self.check1 && !self.check2);
|
||||
const bool result = sig_while0.emit();
|
||||
assert(result == true && self.check1 && self.check2);
|
||||
}
|
||||
};
|
||||
|
||||
static void bench_simple_signal() {
|
||||
Simple::Signal<void(void *, uint64_t)> sig_increment;
|
||||
sig_increment.connect(test_counter_add2);
|
||||
const uint64_t start_counter = TestCounter::get();
|
||||
const uint64_t benchstart = timestamp_benchmark();
|
||||
uint64_t i;
|
||||
for (i = 0; i < 999999; i++) {
|
||||
sig_increment.emit(0, 1);
|
||||
}
|
||||
const uint64_t benchdone = timestamp_benchmark();
|
||||
const uint64_t end_counter = TestCounter::get();
|
||||
assert(end_counter - start_counter == i);
|
||||
printf("OK\n Benchmark: Simple::Signal: %fns per emission (size=%zu): ",
|
||||
size_t(benchdone - benchstart) * 1.0 / size_t(i),
|
||||
sizeof(sig_increment));
|
||||
}
|
||||
|
||||
static void bench_callback_loop() {
|
||||
void (*counter_increment)(void *, uint64_t) = test_counter_add2;
|
||||
const uint64_t start_counter = TestCounter::get();
|
||||
const uint64_t benchstart = timestamp_benchmark();
|
||||
uint64_t i;
|
||||
for (i = 0; i < 999999; i++) {
|
||||
counter_increment(0, 1);
|
||||
}
|
||||
const uint64_t benchdone = timestamp_benchmark();
|
||||
const uint64_t end_counter = TestCounter::get();
|
||||
assert(end_counter - start_counter == i);
|
||||
printf("OK\n Benchmark: callback loop: %fns per round: ",
|
||||
size_t(benchdone - benchstart) * 1.0 / size_t(i));
|
||||
}
|
||||
|
||||
uint64_t TestCounter::get() { return test_counter_var; }
|
||||
|
||||
void TestCounter::set(uint64_t v) { test_counter_var = v; }
|
||||
|
||||
void TestCounter::add2(void *, uint64_t v) { test_counter_var += v; }
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
printf("Signal/Basic Tests: ");
|
||||
BasicSignalTests::run();
|
||||
printf("OK\n");
|
||||
|
||||
printf("Signal/CollectorVector: ");
|
||||
TestCollectorVector::run();
|
||||
printf("OK\n");
|
||||
|
||||
printf("Signal/CollectorUntil0: ");
|
||||
TestCollectorUntil0::run();
|
||||
printf("OK\n");
|
||||
|
||||
printf("Signal/CollectorWhile0: ");
|
||||
TestCollectorWhile0::run();
|
||||
printf("OK\n");
|
||||
|
||||
printf("Signal/Benchmark: Simple::Signal: ");
|
||||
bench_simple_signal();
|
||||
printf("OK\n");
|
||||
|
||||
printf("Signal/Benchmark: callback loop: ");
|
||||
bench_callback_loop();
|
||||
printf("OK\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif // DISABLE_TESTS
|
||||
|
||||
// g++ -Wall -O2 -std=gnu++0x -pthread simplesignal.cc -lrt && ./a.out
|
||||
154
include/entityx/Benchmarks_test.cc
Normal file
154
include/entityx/Benchmarks_test.cc
Normal file
@@ -0,0 +1,154 @@
|
||||
#define CATCH_CONFIG_MAIN
|
||||
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include "entityx/3rdparty/catch.hpp"
|
||||
#include "entityx/help/Timer.h"
|
||||
#include "entityx/Entity.h"
|
||||
|
||||
using std::uint64_t;
|
||||
using std::cout;
|
||||
using std::endl;
|
||||
using std::vector;
|
||||
|
||||
using entityx::Receiver;
|
||||
using entityx::Component;
|
||||
using entityx::ComponentHandle;
|
||||
using entityx::Entity;
|
||||
using entityx::EntityCreatedEvent;
|
||||
using entityx::EntityDestroyedEvent;
|
||||
using entityx::EventManager;
|
||||
using entityx::EntityManager;
|
||||
|
||||
struct AutoTimer {
|
||||
~AutoTimer() {
|
||||
cout << timer_.elapsed() << " seconds elapsed" << endl;
|
||||
}
|
||||
|
||||
private:
|
||||
entityx::help::Timer timer_;
|
||||
};
|
||||
|
||||
struct Listener : public Receiver<Listener> {
|
||||
void receive(const EntityCreatedEvent &event) { ++created; }
|
||||
void receive(const EntityDestroyedEvent &event) { ++destroyed; }
|
||||
|
||||
int created = 0;
|
||||
int destroyed = 0;
|
||||
};
|
||||
|
||||
struct Position : public Component<Position> {
|
||||
};
|
||||
|
||||
|
||||
struct Direction : public Component<Direction> {
|
||||
};
|
||||
|
||||
|
||||
struct BenchmarkFixture {
|
||||
BenchmarkFixture() : em(ev) {}
|
||||
|
||||
EventManager ev;
|
||||
EntityManager em;
|
||||
};
|
||||
|
||||
|
||||
TEST_CASE_METHOD(BenchmarkFixture, "TestCreateEntities") {
|
||||
AutoTimer t;
|
||||
|
||||
uint64_t count = 10000000L;
|
||||
cout << "creating " << count << " entities" << endl;
|
||||
|
||||
for (uint64_t i = 0; i < count; i++) {
|
||||
em.create();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE_METHOD(BenchmarkFixture, "TestDestroyEntities") {
|
||||
uint64_t count = 10000000L;
|
||||
vector<Entity> entities;
|
||||
for (uint64_t i = 0; i < count; i++) {
|
||||
entities.push_back(em.create());
|
||||
}
|
||||
|
||||
AutoTimer t;
|
||||
cout << "destroying " << count << " entities" << endl;
|
||||
|
||||
for (auto e : entities) {
|
||||
e.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(BenchmarkFixture, "TestCreateEntitiesWithListener") {
|
||||
Listener listen;
|
||||
ev.subscribe<EntityCreatedEvent>(listen);
|
||||
|
||||
int count = 10000000L;
|
||||
|
||||
AutoTimer t;
|
||||
cout << "creating " << count << " entities while notifying a single EntityCreatedEvent listener" << endl;
|
||||
|
||||
vector<Entity> entities;
|
||||
for (int i = 0; i < count; i++) {
|
||||
entities.push_back(em.create());
|
||||
}
|
||||
|
||||
REQUIRE(entities.size() == count);
|
||||
REQUIRE(listen.created == count);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(BenchmarkFixture, "TestDestroyEntitiesWithListener") {
|
||||
int count = 10000000;
|
||||
vector<Entity> entities;
|
||||
for (int i = 0; i < count; i++) {
|
||||
entities.push_back(em.create());
|
||||
}
|
||||
|
||||
Listener listen;
|
||||
ev.subscribe<EntityDestroyedEvent>(listen);
|
||||
|
||||
AutoTimer t;
|
||||
cout << "destroying " << count << " entities while notifying a single EntityDestroyedEvent listener" << endl;
|
||||
|
||||
for (auto &e : entities) {
|
||||
e.destroy();
|
||||
}
|
||||
|
||||
REQUIRE(entities.size() == count);
|
||||
REQUIRE(listen.destroyed == count);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(BenchmarkFixture, "TestEntityIteration") {
|
||||
int count = 10000000;
|
||||
for (int i = 0; i < count; i++) {
|
||||
auto e = em.create();
|
||||
e.assign<Position>();
|
||||
}
|
||||
|
||||
AutoTimer t;
|
||||
cout << "iterating over " << count << " entities, unpacking one component" << endl;
|
||||
|
||||
ComponentHandle<Position> position;
|
||||
for (auto e : em.entities_with_components(position)) {
|
||||
(void)e;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(BenchmarkFixture, "TestEntityIterationUnpackTwo") {
|
||||
int count = 10000000;
|
||||
for (int i = 0; i < count; i++) {
|
||||
auto e = em.create();
|
||||
e.assign<Position>();
|
||||
e.assign<Direction>();
|
||||
}
|
||||
|
||||
AutoTimer t;
|
||||
cout << "iterating over " << count << " entities, unpacking two components" << endl;
|
||||
|
||||
ComponentHandle<Position> position;
|
||||
ComponentHandle<Direction> direction;
|
||||
for (auto e : em.entities_with_components(position, direction)) {
|
||||
(void)e;
|
||||
}
|
||||
}
|
||||
61
include/entityx/Entity.cc
Normal file
61
include/entityx/Entity.cc
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Alec Thomas <alec@swapoff.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* This software is licensed as described in the file COPYING, which
|
||||
* you should have received as part of this distribution.
|
||||
*
|
||||
* Author: Alec Thomas <alec@swapoff.org>
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include "entityx/Entity.h"
|
||||
|
||||
namespace entityx {
|
||||
|
||||
const Entity::Id Entity::INVALID;
|
||||
BaseComponent::Family BaseComponent::family_counter_ = 0;
|
||||
|
||||
void Entity::invalidate() {
|
||||
id_ = INVALID;
|
||||
manager_ = nullptr;
|
||||
}
|
||||
|
||||
void Entity::destroy() {
|
||||
assert(valid());
|
||||
manager_->destroy(id_);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
std::bitset<entityx::MAX_COMPONENTS> Entity::component_mask() const {
|
||||
return manager_->component_mask(id_);
|
||||
}
|
||||
|
||||
EntityManager::EntityManager(EventManager &event_manager) : event_manager_(event_manager) {
|
||||
}
|
||||
|
||||
EntityManager::~EntityManager() {
|
||||
reset();
|
||||
}
|
||||
|
||||
void EntityManager::reset() {
|
||||
for (Entity entity : entities_for_debugging()) entity.destroy();
|
||||
for (BasePool *pool : component_pools_) {
|
||||
if (pool) delete pool;
|
||||
}
|
||||
for (BaseComponentHelper *helper : component_helpers_) {
|
||||
if (helper) delete helper;
|
||||
}
|
||||
component_pools_.clear();
|
||||
component_helpers_.clear();
|
||||
entity_component_mask_.clear();
|
||||
entity_version_.clear();
|
||||
free_list_.clear();
|
||||
index_counter_ = 0;
|
||||
}
|
||||
|
||||
EntityCreatedEvent::~EntityCreatedEvent() {}
|
||||
EntityDestroyedEvent::~EntityDestroyedEvent() {}
|
||||
|
||||
|
||||
} // namespace entityx
|
||||
1104
include/entityx/Entity.h
Normal file
1104
include/entityx/Entity.h
Normal file
File diff suppressed because it is too large
Load Diff
680
include/entityx/Entity_test.cc
Normal file
680
include/entityx/Entity_test.cc
Normal file
@@ -0,0 +1,680 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Alec Thomas <alec@swapoff.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* This software is licensed as described in the file COPYING, which
|
||||
* you should have received as part of this distribution.
|
||||
*
|
||||
* Author: Alec Thomas <alec@swapoff.org>
|
||||
*/
|
||||
|
||||
#define CATCH_CONFIG_MAIN
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <map>
|
||||
#include "entityx/3rdparty/catch.hpp"
|
||||
#include "entityx/entityx.h"
|
||||
|
||||
// using namespace std;
|
||||
using namespace entityx;
|
||||
|
||||
using std::ostream;
|
||||
using std::vector;
|
||||
using std::set;
|
||||
using std::map;
|
||||
using std::pair;
|
||||
using std::string;
|
||||
|
||||
template <typename T>
|
||||
int size(const T &t) {
|
||||
int n = 0;
|
||||
for (auto i : t) {
|
||||
++n;
|
||||
(void)i; // Unused on purpose, suppress warning
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
struct Position {
|
||||
Position(float x = 0.0f, float y = 0.0f) : x(x), y(y) {}
|
||||
|
||||
bool operator==(const Position &other) const {
|
||||
return x == other.x && y == other.y;
|
||||
}
|
||||
|
||||
float x, y;
|
||||
};
|
||||
|
||||
ostream &operator<<(ostream &out, const Position &position) {
|
||||
out << "Position(" << position.x << ", " << position.y << ")";
|
||||
return out;
|
||||
}
|
||||
|
||||
struct Direction {
|
||||
Direction(float x = 0.0f, float y = 0.0f) : x(x), y(y) {}
|
||||
|
||||
bool operator==(const Direction &other) const {
|
||||
return x == other.x && y == other.y;
|
||||
}
|
||||
|
||||
float x, y;
|
||||
};
|
||||
|
||||
ostream &operator<<(ostream &out, const Direction &direction) {
|
||||
out << "Direction(" << direction.x << ", " << direction.y << ")";
|
||||
return out;
|
||||
}
|
||||
|
||||
struct Tag : Component<Tag> {
|
||||
explicit Tag(string tag) : tag(tag) {}
|
||||
|
||||
bool operator==(const Tag &other) const { return tag == other.tag; }
|
||||
|
||||
string tag;
|
||||
};
|
||||
|
||||
ostream &operator<<(ostream &out, const Tag &tag) {
|
||||
out << "Tag(" << tag.tag << ")";
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
struct EntityManagerFixture {
|
||||
EntityManagerFixture() : em(ev) {}
|
||||
|
||||
EventManager ev;
|
||||
EntityManager em;
|
||||
};
|
||||
|
||||
|
||||
TEST_CASE_METHOD(EntityManagerFixture, "TestCreateEntity") {
|
||||
REQUIRE(em.size() == 0UL);
|
||||
|
||||
Entity e2;
|
||||
REQUIRE(!(e2.valid()));
|
||||
|
||||
Entity e = em.create();
|
||||
REQUIRE(e.valid());
|
||||
REQUIRE(em.size() == 1UL);
|
||||
|
||||
e2 = e;
|
||||
REQUIRE(e2.valid());
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(EntityManagerFixture, "TestEntityAsBoolean") {
|
||||
REQUIRE(em.size() == 0UL);
|
||||
Entity e = em.create();
|
||||
REQUIRE(e.valid());
|
||||
REQUIRE(em.size() == 1UL);
|
||||
REQUIRE(!(!e));
|
||||
|
||||
e.destroy();
|
||||
|
||||
REQUIRE(em.size() == 0UL);
|
||||
|
||||
REQUIRE(!e);
|
||||
|
||||
Entity e2; // Not initialized
|
||||
REQUIRE(!e2);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(EntityManagerFixture, "TestEntityReuse") {
|
||||
Entity e1 = em.create();
|
||||
Entity e2 = e1;
|
||||
auto id = e1.id();
|
||||
REQUIRE(e1.valid());
|
||||
REQUIRE(e2.valid());
|
||||
e1.destroy();
|
||||
REQUIRE(!e1.valid());
|
||||
REQUIRE(!e2.valid());
|
||||
Entity e3 = em.create();
|
||||
// It is assumed that the allocation will reuse the same entity id, though
|
||||
// the version will change.
|
||||
auto new_id = e3.id();
|
||||
REQUIRE(new_id != id);
|
||||
REQUIRE((new_id.id() & 0xffffffffUL) == (id.id() & 0xffffffffUL));
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(EntityManagerFixture, "TestComponentConstruction") {
|
||||
auto e = em.create();
|
||||
auto p = e.assign<Position>(1, 2);
|
||||
auto cp = e.component<Position>();
|
||||
REQUIRE(p == cp);
|
||||
REQUIRE(1.0 == Approx(cp->x));
|
||||
REQUIRE(2.0 == Approx(cp->y));
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(EntityManagerFixture, "TestDestroyEntity") {
|
||||
Entity e = em.create();
|
||||
Entity f = em.create();
|
||||
e.assign<Position>();
|
||||
f.assign<Position>();
|
||||
e.assign<Direction>();
|
||||
f.assign<Direction>();
|
||||
|
||||
REQUIRE(e.valid());
|
||||
REQUIRE(f.valid());
|
||||
REQUIRE(static_cast<bool>(e.component<Position>()));
|
||||
REQUIRE(static_cast<bool>(e.component<Direction>()));
|
||||
REQUIRE(static_cast<bool>(f.component<Position>()));
|
||||
REQUIRE(static_cast<bool>(f.component<Direction>()));
|
||||
|
||||
e.destroy();
|
||||
|
||||
REQUIRE(!(e.valid()));
|
||||
REQUIRE(f.valid());
|
||||
REQUIRE(static_cast<bool>(f.component<Position>()));
|
||||
REQUIRE(static_cast<bool>(f.component<Direction>()));
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(EntityManagerFixture, "TestGetEntitiesWithComponent") {
|
||||
Entity e = em.create();
|
||||
Entity f = em.create();
|
||||
Entity g = em.create();
|
||||
e.assign<Position>();
|
||||
e.assign<Direction>();
|
||||
f.assign<Position>();
|
||||
g.assign<Position>();
|
||||
REQUIRE(3 == size(em.entities_with_components<Position>()));
|
||||
REQUIRE(1 == size(em.entities_with_components<Direction>()));
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(EntityManagerFixture, "TestGetEntitiesWithIntersectionOfComponents") {
|
||||
vector<Entity> entities;
|
||||
for (int i = 0; i < 150; ++i) {
|
||||
Entity e = em.create();
|
||||
entities.push_back(e);
|
||||
if (i % 2 == 0) e.assign<Position>();
|
||||
if (i % 3 == 0) e.assign<Direction>();
|
||||
}
|
||||
REQUIRE(50 == size(em.entities_with_components<Direction>()));
|
||||
REQUIRE(75 == size(em.entities_with_components<Position>()));
|
||||
REQUIRE(25 == size(em.entities_with_components<Direction, Position>()));
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(EntityManagerFixture, "TestGetEntitiesWithComponentAndUnpacking") {
|
||||
vector<Entity::Id> entities;
|
||||
Entity e = em.create();
|
||||
Entity f = em.create();
|
||||
Entity g = em.create();
|
||||
std::vector<std::pair<ComponentHandle<Position>, ComponentHandle<Direction>>> position_directions;
|
||||
position_directions.push_back(std::make_pair(
|
||||
e.assign<Position>(1.0f, 2.0f), e.assign<Direction>(3.0f, 4.0f)));
|
||||
position_directions.push_back(std::make_pair(
|
||||
f.assign<Position>(7.0f, 8.0f), f.assign<Direction>(9.0f, 10.0f)));
|
||||
auto thetag = f.assign<Tag>("tag");
|
||||
g.assign<Position>(5.0f, 6.0f);
|
||||
int i = 0;
|
||||
|
||||
ComponentHandle<Position> position;
|
||||
REQUIRE(3 == size(em.entities_with_components(position)));
|
||||
|
||||
ComponentHandle<Direction> direction;
|
||||
for (auto unused_entity : em.entities_with_components(position, direction)) {
|
||||
(void)unused_entity;
|
||||
REQUIRE(position);
|
||||
REQUIRE(direction);
|
||||
auto pd = position_directions.at(i);
|
||||
REQUIRE(position == pd.first);
|
||||
REQUIRE(direction == pd.second);
|
||||
++i;
|
||||
}
|
||||
REQUIRE(2 == i);
|
||||
ComponentHandle<Tag> tag;
|
||||
i = 0;
|
||||
for (auto unused_entity :
|
||||
em.entities_with_components(position, direction, tag)) {
|
||||
(void)unused_entity;
|
||||
REQUIRE(static_cast<bool>(position));
|
||||
REQUIRE(static_cast<bool>(direction));
|
||||
REQUIRE(static_cast<bool>(tag));
|
||||
auto pd = position_directions.at(1);
|
||||
REQUIRE(position == pd.first);
|
||||
REQUIRE(direction == pd.second);
|
||||
REQUIRE(tag == thetag);
|
||||
i++;
|
||||
}
|
||||
REQUIRE(1 == i);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(EntityManagerFixture, "TestIterateAllEntitiesSkipsDestroyed") {
|
||||
Entity a = em.create();
|
||||
Entity b = em.create();
|
||||
Entity c = em.create();
|
||||
|
||||
b.destroy();
|
||||
|
||||
auto it = em.entities_for_debugging().begin();
|
||||
REQUIRE(a.id() == (*it).id());
|
||||
++it;
|
||||
REQUIRE(c.id() == (*it).id());
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(EntityManagerFixture, "TestUnpack") {
|
||||
Entity e = em.create();
|
||||
auto p = e.assign<Position>(1.0, 2.0);
|
||||
auto d = e.assign<Direction>(3.0, 4.0);
|
||||
auto t = e.assign<Tag>("tag");
|
||||
|
||||
ComponentHandle<Position> up;
|
||||
ComponentHandle<Direction> ud;
|
||||
ComponentHandle<Tag> ut;
|
||||
e.unpack(up);
|
||||
REQUIRE(p == up);
|
||||
e.unpack(up, ud);
|
||||
REQUIRE(p == up);
|
||||
REQUIRE(d == ud);
|
||||
e.unpack(up, ud, ut);
|
||||
REQUIRE(p == up);
|
||||
REQUIRE(d == ud);
|
||||
REQUIRE(t == ut);
|
||||
}
|
||||
|
||||
// gcc 4.7.2 does not allow this struct to be declared locally inside the
|
||||
// TEST_CASE_METHOD.EntityManagerFixture, " //" TEST_CASE_METHOD(EntityManagerFixture, "TestUnpackNullMissing") {
|
||||
// Entity e = em.create();
|
||||
// auto p = e.assign<Position>();
|
||||
|
||||
// std::shared_ptr<Position> up(reinterpret_cast<Position*>(0Xdeadbeef),
|
||||
// NullDeleter());
|
||||
// std::shared_ptr<Direction> ud(reinterpret_cast<Direction*>(0Xdeadbeef),
|
||||
// NullDeleter());
|
||||
// e.unpack<Position, Direction>(up, ud);
|
||||
// REQUIRE(p == up);
|
||||
// REQUIRE(std::shared_ptr<Direction>() == ud);
|
||||
// }
|
||||
|
||||
TEST_CASE_METHOD(EntityManagerFixture, "TestComponentIdsDiffer") {
|
||||
REQUIRE(EntityManager::component_family<Position>() != EntityManager::component_family<Direction>());
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(EntityManagerFixture, "TestEntityCreatedEvent") {
|
||||
struct EntityCreatedEventReceiver
|
||||
: public Receiver<EntityCreatedEventReceiver> {
|
||||
void receive(const EntityCreatedEvent &event) {
|
||||
created.push_back(event.entity);
|
||||
}
|
||||
|
||||
vector<Entity> created;
|
||||
};
|
||||
|
||||
EntityCreatedEventReceiver receiver;
|
||||
ev.subscribe<EntityCreatedEvent>(receiver);
|
||||
|
||||
REQUIRE(0UL == receiver.created.size());
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
em.create();
|
||||
}
|
||||
REQUIRE(10UL == receiver.created.size());
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(EntityManagerFixture, "TestEntityDestroyedEvent") {
|
||||
struct EntityDestroyedEventReceiver
|
||||
: public Receiver<EntityDestroyedEventReceiver> {
|
||||
void receive(const EntityDestroyedEvent &event) {
|
||||
destroyed.push_back(event.entity);
|
||||
}
|
||||
|
||||
vector<Entity> destroyed;
|
||||
};
|
||||
|
||||
EntityDestroyedEventReceiver receiver;
|
||||
ev.subscribe<EntityDestroyedEvent>(receiver);
|
||||
|
||||
REQUIRE(0UL == receiver.destroyed.size());
|
||||
vector<Entity> entities;
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
entities.push_back(em.create());
|
||||
}
|
||||
REQUIRE(0UL == receiver.destroyed.size());
|
||||
for (auto e : entities) {
|
||||
e.destroy();
|
||||
}
|
||||
REQUIRE(10UL == receiver.destroyed.size());
|
||||
REQUIRE(entities == receiver.destroyed);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(EntityManagerFixture, "TestComponentAddedEvent") {
|
||||
struct ComponentAddedEventReceiver
|
||||
: public Receiver<ComponentAddedEventReceiver> {
|
||||
|
||||
ComponentAddedEventReceiver()
|
||||
: position_events(0), direction_events(0) {}
|
||||
|
||||
void receive(const ComponentAddedEvent<Position> &event) {
|
||||
auto p = event.component;
|
||||
float n = static_cast<float>(position_events);
|
||||
REQUIRE(p->x == n);
|
||||
REQUIRE(p->y == n);
|
||||
position_events++;
|
||||
}
|
||||
|
||||
void receive(const ComponentAddedEvent<Direction> &event) {
|
||||
auto p = event.component;
|
||||
float n = static_cast<float>(direction_events);
|
||||
REQUIRE(p->x == -n);
|
||||
REQUIRE(p->y == -n);
|
||||
direction_events++;
|
||||
}
|
||||
|
||||
int position_events;
|
||||
int direction_events;
|
||||
};
|
||||
|
||||
ComponentAddedEventReceiver receiver;
|
||||
ev.subscribe<ComponentAddedEvent<Position>>(receiver);
|
||||
ev.subscribe<ComponentAddedEvent<Direction>>(receiver);
|
||||
|
||||
REQUIRE(ComponentAddedEvent<Position>::family() !=
|
||||
ComponentAddedEvent<Direction>::family());
|
||||
|
||||
REQUIRE(0 == receiver.position_events);
|
||||
REQUIRE(0 == receiver.direction_events);
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
Entity e = em.create();
|
||||
e.assign<Position>(static_cast<float>(i), static_cast<float>(i));
|
||||
e.assign<Direction>(static_cast<float>(-i), static_cast<float>(-i));
|
||||
}
|
||||
REQUIRE(10 == receiver.position_events);
|
||||
REQUIRE(10 == receiver.direction_events);
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE_METHOD(EntityManagerFixture, "TestComponentRemovedEvent") {
|
||||
struct ComponentRemovedReceiver : public Receiver<ComponentRemovedReceiver> {
|
||||
void receive(const ComponentRemovedEvent<Direction> &event) {
|
||||
removed = event.component;
|
||||
}
|
||||
|
||||
ComponentHandle<Direction> removed;
|
||||
};
|
||||
|
||||
ComponentRemovedReceiver receiver;
|
||||
ev.subscribe<ComponentRemovedEvent<Direction>>(receiver);
|
||||
|
||||
REQUIRE(!(receiver.removed));
|
||||
Entity e = em.create();
|
||||
ComponentHandle<Direction> p = e.assign<Direction>(1.0, 2.0);
|
||||
e.remove<Direction>();
|
||||
REQUIRE(receiver.removed == p);
|
||||
REQUIRE(!(e.component<Direction>()));
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(EntityManagerFixture, "TestComponentRemovedEventOnEntityDestroyed") {
|
||||
struct ComponentRemovedReceiver : public Receiver<ComponentRemovedReceiver> {
|
||||
void receive(const ComponentRemovedEvent<Direction> &event) {
|
||||
removed = true;
|
||||
}
|
||||
|
||||
bool removed = false;
|
||||
};
|
||||
|
||||
ComponentRemovedReceiver receiver;
|
||||
ev.subscribe<ComponentRemovedEvent<Direction>>(receiver);
|
||||
|
||||
REQUIRE(!(receiver.removed));
|
||||
|
||||
Entity e = em.create();
|
||||
e.assign<Direction>(1.0, 2.0);
|
||||
e.destroy();
|
||||
|
||||
REQUIRE(receiver.removed);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(EntityManagerFixture, "TestEntityAssignment") {
|
||||
Entity a, b;
|
||||
a = em.create();
|
||||
REQUIRE(a != b);
|
||||
b = a;
|
||||
REQUIRE(a == b);
|
||||
a.invalidate();
|
||||
REQUIRE(a != b);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(EntityManagerFixture, "TestEntityDestroyAll") {
|
||||
Entity a = em.create(), b = em.create();
|
||||
em.reset();
|
||||
REQUIRE(!(a.valid()));
|
||||
REQUIRE(!(b.valid()));
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(EntityManagerFixture, "TestEntityDestroyHole") {
|
||||
std::vector<Entity> entities;
|
||||
|
||||
auto count = [this]()->int {
|
||||
auto e = em.entities_with_components<Position>();
|
||||
return std::count_if(e.begin(), e.end(),
|
||||
[](const Entity &) { return true; });
|
||||
};
|
||||
|
||||
for (int i = 0; i < 5000; i++) {
|
||||
auto e = em.create();
|
||||
e.assign<Position>();
|
||||
entities.push_back(e);
|
||||
}
|
||||
|
||||
REQUIRE(count() == 5000);
|
||||
|
||||
entities[2500].destroy();
|
||||
|
||||
REQUIRE(count() == 4999);
|
||||
}
|
||||
|
||||
// TODO(alec): Disable this on OSX - it doesn't seem to be possible to catch it?!?
|
||||
// TEST_CASE_METHOD(EntityManagerFixture, "DeleteComponentThrowsBadAlloc") {
|
||||
// Position *position = new Position();
|
||||
// REQUIRE_THROWS_AS(delete position, std::bad_alloc);
|
||||
// }
|
||||
|
||||
|
||||
TEST_CASE_METHOD(EntityManagerFixture, "TestComponentHandleInvalidatedWhenEntityDestroyed") {
|
||||
Entity a = em.create();
|
||||
ComponentHandle<Position> position = a.assign<Position>(1, 2);
|
||||
REQUIRE(position);
|
||||
REQUIRE(position->x == 1);
|
||||
REQUIRE(position->y == 2);
|
||||
a.destroy();
|
||||
REQUIRE(!position);
|
||||
}
|
||||
|
||||
struct CopyVerifier : Component<CopyVerifier> {
|
||||
CopyVerifier() : copied(false) {}
|
||||
CopyVerifier(const CopyVerifier &other) {
|
||||
copied = other.copied + 1;
|
||||
}
|
||||
|
||||
int copied;
|
||||
};
|
||||
|
||||
TEST_CASE_METHOD(EntityManagerFixture, "TestComponentAssignmentFromCopy") {
|
||||
Entity a = em.create();
|
||||
CopyVerifier original;
|
||||
ComponentHandle<CopyVerifier> copy = a.assign_from_copy(original);
|
||||
REQUIRE(copy);
|
||||
REQUIRE(copy->copied == 1);
|
||||
a.destroy();
|
||||
REQUIRE(!copy);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(EntityManagerFixture, "TestEntityCreateFromCopy") {
|
||||
Entity a = em.create();
|
||||
a.assign<CopyVerifier>();
|
||||
ComponentHandle<CopyVerifier> original = a.component<CopyVerifier>();
|
||||
ComponentHandle<Position> aPosition = a.assign<Position>(1, 2);
|
||||
Entity b = em.create_from_copy(a);
|
||||
ComponentHandle<CopyVerifier> copy = b.component<CopyVerifier>();
|
||||
ComponentHandle<Position> bPosition = b.component<Position>();
|
||||
REQUIRE(original);
|
||||
REQUIRE(original->copied == false);
|
||||
REQUIRE(copy);
|
||||
REQUIRE(copy->copied == 1);
|
||||
REQUIRE(aPosition->x == bPosition->x);
|
||||
REQUIRE(aPosition->y == bPosition->y);
|
||||
REQUIRE(aPosition.get() != bPosition.get());
|
||||
REQUIRE(a.component_mask() == b.component_mask());
|
||||
REQUIRE(a != b);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(EntityManagerFixture, "TestComponentHandleInvalidatedWhenComponentDestroyed") {
|
||||
Entity a = em.create();
|
||||
ComponentHandle<Position> position = a.assign<Position>(1, 2);
|
||||
REQUIRE(position);
|
||||
REQUIRE(position->x == 1);
|
||||
REQUIRE(position->y == 2);
|
||||
a.remove<Position>();
|
||||
REQUIRE(!position);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(EntityManagerFixture, "TestDeleteEntityWithNoComponents") {
|
||||
Entity a = em.create();
|
||||
a.assign<Position>(1, 2);
|
||||
Entity b = em.create();
|
||||
b.destroy();
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(EntityManagerFixture, "TestEntityInStdSet") {
|
||||
Entity a = em.create();
|
||||
Entity b = em.create();
|
||||
Entity c = em.create();
|
||||
set<Entity> entitySet;
|
||||
REQUIRE(entitySet.insert(a).second);
|
||||
REQUIRE(entitySet.insert(b).second);
|
||||
REQUIRE(entitySet.insert(c).second);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(EntityManagerFixture, "TestEntityInStdMap") {
|
||||
Entity a = em.create();
|
||||
Entity b = em.create();
|
||||
Entity c = em.create();
|
||||
map<Entity, int> entityMap;
|
||||
REQUIRE(entityMap.insert(pair<Entity, int>(a, 1)).second);
|
||||
REQUIRE(entityMap.insert(pair<Entity, int>(b, 2)).second);
|
||||
REQUIRE(entityMap.insert(pair<Entity, int>(c, 3)).second);
|
||||
REQUIRE(entityMap[a] == 1);
|
||||
REQUIRE(entityMap[b] == 2);
|
||||
REQUIRE(entityMap[c] == 3);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(EntityManagerFixture, "TestEntityComponentsFromTuple") {
|
||||
Entity e = em.create();
|
||||
e.assign<Position>(1, 2);
|
||||
e.assign<Direction>(3, 4);
|
||||
|
||||
std::tuple<ComponentHandle<Position>, ComponentHandle<Direction>> components = e.components<Position, Direction>();
|
||||
|
||||
REQUIRE(std::get<0>(components)->x == 1);
|
||||
REQUIRE(std::get<0>(components)->y == 2);
|
||||
REQUIRE(std::get<1>(components)->x == 3);
|
||||
REQUIRE(std::get<1>(components)->y == 4);
|
||||
}
|
||||
|
||||
TEST_CASE("TestComponentDestructorCalledWhenManagerDestroyed") {
|
||||
struct Freed {
|
||||
explicit Freed(bool &yes) : yes(yes) {}
|
||||
~Freed() { yes = true; }
|
||||
|
||||
bool &yes;
|
||||
};
|
||||
|
||||
struct Test : Component<Test> {
|
||||
explicit Test(bool &yes) : freed(yes) {}
|
||||
|
||||
Freed freed;
|
||||
};
|
||||
|
||||
bool freed = false;
|
||||
{
|
||||
EntityX e;
|
||||
auto test = e.entities.create();
|
||||
test.assign<Test>(freed);
|
||||
}
|
||||
REQUIRE(freed == true);
|
||||
}
|
||||
|
||||
TEST_CASE("TestComponentDestructorCalledWhenEntityDestroyed") {
|
||||
struct Freed {
|
||||
explicit Freed(bool &yes) : yes(yes) {}
|
||||
~Freed() { yes = true; }
|
||||
|
||||
bool &yes;
|
||||
};
|
||||
|
||||
struct Test : Component<Test> {
|
||||
explicit Test(bool &yes) : freed(yes) {}
|
||||
|
||||
Freed freed;
|
||||
};
|
||||
|
||||
bool freed = false;
|
||||
EntityX e;
|
||||
auto test = e.entities.create();
|
||||
test.assign<Test>(freed);
|
||||
REQUIRE(freed == false);
|
||||
test.destroy();
|
||||
REQUIRE(freed == true);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(EntityManagerFixture, "TestComponentsRemovedFromReusedEntities") {
|
||||
Entity a = em.create();
|
||||
Entity::Id aid = a.id();
|
||||
a.assign<Position>(1, 2);
|
||||
a.destroy();
|
||||
|
||||
Entity b = em.create();
|
||||
Entity::Id bid = b.id();
|
||||
|
||||
REQUIRE(aid.index() == bid.index());
|
||||
REQUIRE(!b.has_component<Position>());
|
||||
b.assign<Position>(3, 4);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(EntityManagerFixture, "TestConstComponentsNotInstantiatedTwice") {
|
||||
Entity a = em.create();
|
||||
a.assign<Position>(1, 2);
|
||||
|
||||
const Entity b = a;
|
||||
|
||||
REQUIRE(a.component<Position>().valid());
|
||||
REQUIRE(b.component<const Position>().valid());
|
||||
REQUIRE(b.component<const Position>()->x == 1);
|
||||
REQUIRE(b.component<const Position>()->y == 2);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(EntityManagerFixture, "TestEntityManagerEach") {
|
||||
Entity a = em.create();
|
||||
a.assign<Position>(1, 2);
|
||||
int count = 0;
|
||||
em.each<Position>([&count](Entity entity, Position &position) {
|
||||
count++;
|
||||
REQUIRE(position.x == 1);
|
||||
REQUIRE(position.y == 2);
|
||||
});
|
||||
REQUIRE(count == 1);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(EntityManagerFixture, "TestViewEach") {
|
||||
Entity a = em.create();
|
||||
a.assign<Position>(1, 2);
|
||||
int count = 0;
|
||||
em.entities_with_components<Position>().each([&count](Entity entity, Position &position) {
|
||||
count++;
|
||||
REQUIRE(position.x == 1);
|
||||
REQUIRE(position.y == 2);
|
||||
});
|
||||
REQUIRE(count == 1);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(EntityManagerFixture, "TestComponentDereference") {
|
||||
Entity a = em.create();
|
||||
a.assign<Position>(10, 5);
|
||||
auto& positionRef = *a.component<Position>();
|
||||
REQUIRE(positionRef.x == 10);
|
||||
REQUIRE(positionRef.y == 5);
|
||||
positionRef.y = 20;
|
||||
REQUIRE(a.component<Position>()->y == 20);
|
||||
}
|
||||
26
include/entityx/Event.cc
Normal file
26
include/entityx/Event.cc
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Alec Thomas <alec@swapoff.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* This software is licensed as described in the file COPYING, which
|
||||
* you should have received as part of this distribution.
|
||||
*
|
||||
* Author: Alec Thomas <alec@swapoff.org>
|
||||
*/
|
||||
|
||||
#include "entityx/Event.h"
|
||||
|
||||
namespace entityx {
|
||||
|
||||
BaseEvent::Family BaseEvent::family_counter_ = 0;
|
||||
|
||||
BaseEvent::~BaseEvent() {
|
||||
}
|
||||
|
||||
EventManager::EventManager() {
|
||||
}
|
||||
|
||||
EventManager::~EventManager() {
|
||||
}
|
||||
|
||||
} // namespace entityx
|
||||
216
include/entityx/Event.h
Normal file
216
include/entityx/Event.h
Normal file
@@ -0,0 +1,216 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Alec Thomas <alec@swapoff.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* This software is licensed as described in the file COPYING, which
|
||||
* you should have received as part of this distribution.
|
||||
*
|
||||
* Author: Alec Thomas <alec@swapoff.org>
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
#include <list>
|
||||
#include <unordered_map>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include "entityx/config.h"
|
||||
#include "entityx/3rdparty/simplesignal.h"
|
||||
#include "entityx/help/NonCopyable.h"
|
||||
|
||||
|
||||
namespace entityx {
|
||||
|
||||
|
||||
/// Used internally by the EventManager.
|
||||
class BaseEvent {
|
||||
public:
|
||||
typedef std::size_t Family;
|
||||
|
||||
virtual ~BaseEvent();
|
||||
|
||||
protected:
|
||||
static Family family_counter_;
|
||||
};
|
||||
|
||||
|
||||
typedef Simple::Signal<void (const void*)> EventSignal;
|
||||
typedef std::shared_ptr<EventSignal> EventSignalPtr;
|
||||
typedef std::weak_ptr<EventSignal> EventSignalWeakPtr;
|
||||
|
||||
|
||||
/**
|
||||
* Event types should subclass from this.
|
||||
*
|
||||
* struct Explosion : public Event<Explosion> {
|
||||
* Explosion(int damage) : damage(damage) {}
|
||||
* int damage;
|
||||
* };
|
||||
*/
|
||||
template <typename Derived>
|
||||
class Event : public BaseEvent {
|
||||
public:
|
||||
/// Used internally for registration.
|
||||
static Family family() {
|
||||
static Family family = family_counter_++;
|
||||
return family;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class BaseReceiver {
|
||||
public:
|
||||
virtual ~BaseReceiver() {
|
||||
for (auto connection : connections_) {
|
||||
auto &ptr = connection.second.first;
|
||||
if (!ptr.expired()) {
|
||||
ptr.lock()->disconnect(connection.second.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return number of signals connected to this receiver.
|
||||
std::size_t connected_signals() const {
|
||||
std::size_t size = 0;
|
||||
for (auto connection : connections_) {
|
||||
if (!connection.second.first.expired()) {
|
||||
size++;
|
||||
}
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
private:
|
||||
friend class EventManager;
|
||||
std::unordered_map<BaseEvent::Family, std::pair<EventSignalWeakPtr, std::size_t>> connections_;
|
||||
};
|
||||
|
||||
|
||||
template <typename Derived>
|
||||
class Receiver : public BaseReceiver {
|
||||
public:
|
||||
virtual ~Receiver() {}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handles event subscription and delivery.
|
||||
*
|
||||
* Subscriptions are automatically removed when receivers are destroyed..
|
||||
*/
|
||||
class EventManager : entityx::help::NonCopyable {
|
||||
public:
|
||||
EventManager();
|
||||
virtual ~EventManager();
|
||||
|
||||
/**
|
||||
* Subscribe an object to receive events of type E.
|
||||
*
|
||||
* Receivers must be subclasses of Receiver and must implement a receive() method accepting the given event type.
|
||||
*
|
||||
* eg.
|
||||
*
|
||||
* struct ExplosionReceiver : public Receiver<ExplosionReceiver> {
|
||||
* void receive(const Explosion &explosion) {
|
||||
* }
|
||||
* };
|
||||
*
|
||||
* ExplosionReceiver receiver;
|
||||
* em.subscribe<Explosion>(receiver);
|
||||
*/
|
||||
template <typename E, typename Receiver>
|
||||
void subscribe(Receiver &receiver) {
|
||||
void (Receiver::*receive)(const E &) = &Receiver::receive;
|
||||
auto sig = signal_for(Event<E>::family());
|
||||
auto wrapper = EventCallbackWrapper<E>(std::bind(receive, &receiver, std::placeholders::_1));
|
||||
auto connection = sig->connect(wrapper);
|
||||
BaseReceiver &base = receiver;
|
||||
base.connections_.insert(std::make_pair(Event<E>::family(), std::make_pair(EventSignalWeakPtr(sig), connection)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribe an object in order to not receive events of type E anymore.
|
||||
*
|
||||
* Receivers must have subscribed for event E before unsubscribing from event E.
|
||||
*
|
||||
*/
|
||||
template <typename E, typename Receiver>
|
||||
void unsubscribe(Receiver &receiver) {
|
||||
BaseReceiver &base = receiver;
|
||||
// Assert that it has been subscribed before
|
||||
assert(base.connections_.find(Event<E>::family()) != base.connections_.end());
|
||||
auto pair = base.connections_[Event<E>::family()];
|
||||
auto connection = pair.second;
|
||||
auto &ptr = pair.first;
|
||||
if (!ptr.expired()) {
|
||||
ptr.lock()->disconnect(connection);
|
||||
}
|
||||
base.connections_.erase(Event<E>::family());
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
void emit(const E &event) {
|
||||
auto sig = signal_for(Event<E>::family());
|
||||
sig->emit(&event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit an already constructed event.
|
||||
*/
|
||||
template <typename E>
|
||||
void emit(std::unique_ptr<E> event) {
|
||||
auto sig = signal_for(Event<E>::family());
|
||||
sig->emit(event.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit an event to receivers.
|
||||
*
|
||||
* This method constructs a new event object of type E with the provided arguments, then delivers it to all receivers.
|
||||
*
|
||||
* eg.
|
||||
*
|
||||
* std::shared_ptr<EventManager> em = new EventManager();
|
||||
* em->emit<Explosion>(10);
|
||||
*
|
||||
*/
|
||||
template <typename E, typename ... Args>
|
||||
void emit(Args && ... args) {
|
||||
// Using 'E event(std::forward...)' causes VS to fail with an internal error. Hack around it.
|
||||
E event = E(std::forward<Args>(args) ...);
|
||||
auto sig = signal_for(std::size_t(Event<E>::family()));
|
||||
sig->emit(&event);
|
||||
}
|
||||
|
||||
std::size_t connected_receivers() const {
|
||||
std::size_t size = 0;
|
||||
for (EventSignalPtr handler : handlers_) {
|
||||
if (handler) size += handler->size();
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
private:
|
||||
EventSignalPtr &signal_for(std::size_t id) {
|
||||
if (id >= handlers_.size())
|
||||
handlers_.resize(id + 1);
|
||||
if (!handlers_[id])
|
||||
handlers_[id] = std::make_shared<EventSignal>();
|
||||
return handlers_[id];
|
||||
}
|
||||
|
||||
// Functor used as an event signal callback that casts to E.
|
||||
template <typename E>
|
||||
struct EventCallbackWrapper {
|
||||
explicit EventCallbackWrapper(std::function<void(const E &)> callback) : callback(callback) {}
|
||||
void operator()(const void *event) { callback(*(static_cast<const E*>(event))); }
|
||||
std::function<void(const E &)> callback;
|
||||
};
|
||||
|
||||
std::vector<EventSignalPtr> handlers_;
|
||||
};
|
||||
|
||||
} // namespace entityx
|
||||
115
include/entityx/Event_test.cc
Normal file
115
include/entityx/Event_test.cc
Normal file
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Alec Thomas <alec@swapoff.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* This software is licensed as described in the file COPYING, which
|
||||
* you should have received as part of this distribution.
|
||||
*
|
||||
* Author: Alec Thomas <alec@swapoff.org>
|
||||
*/
|
||||
|
||||
#define CATCH_CONFIG_MAIN
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "entityx/3rdparty/catch.hpp"
|
||||
#include "entityx/Event.h"
|
||||
|
||||
|
||||
using entityx::EventManager;
|
||||
using entityx::Event;
|
||||
using entityx::Receiver;
|
||||
|
||||
|
||||
struct Explosion {
|
||||
explicit Explosion(int damage) : damage(damage) {}
|
||||
int damage;
|
||||
};
|
||||
|
||||
|
||||
struct Collision {
|
||||
explicit Collision(int damage) : damage(damage) {}
|
||||
int damage;
|
||||
};
|
||||
|
||||
struct ExplosionSystem : public Receiver<ExplosionSystem> {
|
||||
void receive(const Explosion &explosion) {
|
||||
damage_received += explosion.damage;
|
||||
received_count++;
|
||||
}
|
||||
|
||||
void receive(const Collision &collision) {
|
||||
damage_received += collision.damage;
|
||||
received_count++;
|
||||
}
|
||||
|
||||
int received_count = 0;
|
||||
int damage_received = 0;
|
||||
};
|
||||
|
||||
TEST_CASE("TestEmitReceive") {
|
||||
EventManager em;
|
||||
ExplosionSystem explosion_system;
|
||||
em.subscribe<Explosion>(explosion_system);
|
||||
em.subscribe<Collision>(explosion_system);
|
||||
REQUIRE(0 == explosion_system.damage_received);
|
||||
em.emit<Explosion>(10);
|
||||
REQUIRE(1 == explosion_system.received_count);
|
||||
REQUIRE(10 == explosion_system.damage_received);
|
||||
em.emit<Collision>(10);
|
||||
REQUIRE(20 == explosion_system.damage_received);
|
||||
REQUIRE(2 == explosion_system.received_count);
|
||||
}
|
||||
|
||||
TEST_CASE("TestUntypedEmitReceive") {
|
||||
EventManager em;
|
||||
ExplosionSystem explosion_system;
|
||||
em.subscribe<Explosion>(explosion_system);
|
||||
REQUIRE(0 == explosion_system.damage_received);
|
||||
Explosion explosion(10);
|
||||
em.emit(explosion);
|
||||
REQUIRE(1 == explosion_system.received_count);
|
||||
REQUIRE(10 == explosion_system.damage_received);
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("TestReceiverExpired") {
|
||||
EventManager em;
|
||||
{
|
||||
ExplosionSystem explosion_system;
|
||||
em.subscribe<Explosion>(explosion_system);
|
||||
em.emit<Explosion>(10);
|
||||
REQUIRE(10 == explosion_system.damage_received);
|
||||
REQUIRE(1 == explosion_system.connected_signals());
|
||||
REQUIRE(1 == em.connected_receivers());
|
||||
}
|
||||
REQUIRE(0 == em.connected_receivers());
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("TestSenderExpired") {
|
||||
ExplosionSystem explosion_system;
|
||||
{
|
||||
EventManager em;
|
||||
em.subscribe<Explosion>(explosion_system);
|
||||
em.emit<Explosion>(10);
|
||||
REQUIRE(10 == explosion_system.damage_received);
|
||||
REQUIRE(1 == explosion_system.connected_signals());
|
||||
REQUIRE(1 == em.connected_receivers());
|
||||
}
|
||||
REQUIRE(0 == explosion_system.connected_signals());
|
||||
}
|
||||
|
||||
TEST_CASE("TestUnsubscription") {
|
||||
ExplosionSystem explosion_system;
|
||||
{
|
||||
EventManager em;
|
||||
em.subscribe<Explosion>(explosion_system);
|
||||
REQUIRE(explosion_system.damage_received == 0);
|
||||
em.emit<Explosion>(1);
|
||||
REQUIRE(explosion_system.damage_received == 1);
|
||||
em.unsubscribe<Explosion>(explosion_system);
|
||||
em.emit<Explosion>(1);
|
||||
REQUIRE(explosion_system.damage_received == 1);
|
||||
}
|
||||
}
|
||||
34
include/entityx/System.cc
Normal file
34
include/entityx/System.cc
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Alec Thomas <alec@swapoff.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* This software is licensed as described in the file COPYING, which
|
||||
* you should have received as part of this distribution.
|
||||
*
|
||||
* Author: Alec Thomas <alec@swapoff.org>
|
||||
*/
|
||||
|
||||
#include "entityx/System.h"
|
||||
|
||||
namespace entityx {
|
||||
|
||||
BaseSystem::Family BaseSystem::family_counter_;
|
||||
|
||||
BaseSystem::~BaseSystem() {
|
||||
}
|
||||
|
||||
void SystemManager::update_all(TimeDelta dt) {
|
||||
assert(initialized_ && "SystemManager::configure() not called");
|
||||
for (auto &pair : systems_) {
|
||||
pair.second->update(entity_manager_, event_manager_, dt);
|
||||
}
|
||||
}
|
||||
|
||||
void SystemManager::configure() {
|
||||
for (auto &pair : systems_) {
|
||||
pair.second->configure(entity_manager_, event_manager_);
|
||||
}
|
||||
initialized_ = true;
|
||||
}
|
||||
|
||||
} // namespace entityx
|
||||
179
include/entityx/System.h
Normal file
179
include/entityx/System.h
Normal file
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Alec Thomas <alec@swapoff.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* This software is licensed as described in the file COPYING, which
|
||||
* you should have received as part of this distribution.
|
||||
*
|
||||
* Author: Alec Thomas <alec@swapoff.org>
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
#include <cstdint>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <cassert>
|
||||
#include "entityx/config.h"
|
||||
#include "entityx/Entity.h"
|
||||
#include "entityx/Event.h"
|
||||
#include "entityx/help/NonCopyable.h"
|
||||
|
||||
|
||||
namespace entityx {
|
||||
|
||||
|
||||
class SystemManager;
|
||||
|
||||
|
||||
/**
|
||||
* Base System class. Generally should not be directly used, instead see System<Derived>.
|
||||
*/
|
||||
class BaseSystem : entityx::help::NonCopyable {
|
||||
public:
|
||||
typedef size_t Family;
|
||||
|
||||
virtual ~BaseSystem();
|
||||
|
||||
/**
|
||||
* Called once all Systems have been added to the SystemManager.
|
||||
*
|
||||
* Typically used to set up event handlers.
|
||||
*/
|
||||
virtual void configure(EntityManager &entities, EventManager &events) {
|
||||
configure(events);
|
||||
}
|
||||
|
||||
/**
|
||||
* Legacy configure(). Called by default implementation of configure(EntityManager&, EventManager&).
|
||||
*/
|
||||
virtual void configure(EventManager &events) {}
|
||||
|
||||
/**
|
||||
* Apply System behavior.
|
||||
*
|
||||
* Called every game step.
|
||||
*/
|
||||
virtual void update(EntityManager &entities, EventManager &events, TimeDelta dt) = 0;
|
||||
|
||||
static Family family_counter_;
|
||||
|
||||
protected:
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Use this class when implementing Systems.
|
||||
*
|
||||
* struct MovementSystem : public System<MovementSystem> {
|
||||
* void update(EntityManager &entities, EventManager &events, TimeDelta dt) {
|
||||
* // Do stuff to/with entities...
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
template <typename Derived>
|
||||
class System : public BaseSystem {
|
||||
public:
|
||||
virtual ~System() {}
|
||||
|
||||
private:
|
||||
friend class SystemManager;
|
||||
|
||||
static Family family() {
|
||||
static Family family = family_counter_++;
|
||||
return family;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class SystemManager : entityx::help::NonCopyable {
|
||||
public:
|
||||
SystemManager(EntityManager &entity_manager,
|
||||
EventManager &event_manager) :
|
||||
entity_manager_(entity_manager),
|
||||
event_manager_(event_manager) {}
|
||||
|
||||
/**
|
||||
* Add a System to the SystemManager.
|
||||
*
|
||||
* Must be called before Systems can be used.
|
||||
*
|
||||
* eg.
|
||||
* std::shared_ptr<MovementSystem> movement = entityx::make_shared<MovementSystem>();
|
||||
* system.add(movement);
|
||||
*/
|
||||
template <typename S>
|
||||
void add(std::shared_ptr<S> system) {
|
||||
systems_.insert(std::make_pair(S::family(), system));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a System to the SystemManager.
|
||||
*
|
||||
* Must be called before Systems can be used.
|
||||
*
|
||||
* eg.
|
||||
* auto movement = system.add<MovementSystem>();
|
||||
*/
|
||||
template <typename S, typename ... Args>
|
||||
std::shared_ptr<S> add(Args && ... args) {
|
||||
std::shared_ptr<S> s(new S(std::forward<Args>(args) ...));
|
||||
add(s);
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the registered System instance, if any.
|
||||
*
|
||||
* std::shared_ptr<CollisionSystem> collisions = systems.system<CollisionSystem>();
|
||||
*
|
||||
* @return System instance or empty shared_std::shared_ptr<S>.
|
||||
*/
|
||||
template <typename S>
|
||||
std::shared_ptr<S> system() {
|
||||
auto it = systems_.find(S::family());
|
||||
assert(it != systems_.end());
|
||||
return it == systems_.end()
|
||||
? std::shared_ptr<S>()
|
||||
: std::shared_ptr<S>(std::static_pointer_cast<S>(it->second));
|
||||
}
|
||||
|
||||
/**
|
||||
* Call the System::update() method for a registered system.
|
||||
*/
|
||||
template <typename S>
|
||||
void update(TimeDelta dt) {
|
||||
assert(initialized_ && "SystemManager::configure() not called");
|
||||
std::shared_ptr<S> s = system<S>();
|
||||
s->update(entity_manager_, event_manager_, dt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call System::update() on all registered systems.
|
||||
*
|
||||
* The order which the registered systems are updated is arbitrary but consistent,
|
||||
* meaning the order which they will be updated cannot be specified, but that order
|
||||
* will stay the same as long no systems are added or removed.
|
||||
*
|
||||
* If the order in which systems update is important, use SystemManager::update()
|
||||
* to manually specify the update order. EntityX does not yet support a way of
|
||||
* specifying priority for update_all().
|
||||
*/
|
||||
void update_all(TimeDelta dt);
|
||||
|
||||
/**
|
||||
* Configure the system. Call after adding all Systems.
|
||||
*
|
||||
* This is typically used to set up event handlers.
|
||||
*/
|
||||
void configure();
|
||||
|
||||
private:
|
||||
bool initialized_ = false;
|
||||
EntityManager &entity_manager_;
|
||||
EventManager &event_manager_;
|
||||
std::unordered_map<BaseSystem::Family, std::shared_ptr<BaseSystem>> systems_;
|
||||
};
|
||||
|
||||
} // namespace entityx
|
||||
133
include/entityx/System_test.cc
Normal file
133
include/entityx/System_test.cc
Normal file
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Alec Thomas <alec@swapoff.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* This software is licensed as described in the file COPYING, which
|
||||
* you should have received as part of this distribution.
|
||||
*
|
||||
* Author: Alec Thomas <alec@swapoff.org>
|
||||
*/
|
||||
|
||||
#define CATCH_CONFIG_MAIN
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "entityx/3rdparty/catch.hpp"
|
||||
#include "entityx/System.h"
|
||||
#include "entityx/quick.h"
|
||||
|
||||
// using namespace std;
|
||||
using namespace entityx;
|
||||
using std::string;
|
||||
|
||||
struct Position : Component<Position> {
|
||||
explicit Position(float x = 0.0f, float y = 0.0f) : x(x), y(y) {}
|
||||
|
||||
float x, y;
|
||||
};
|
||||
|
||||
struct Direction : Component<Direction> {
|
||||
explicit Direction(float x = 0.0f, float y = 0.0f) : x(x), y(y) {}
|
||||
|
||||
float x, y;
|
||||
};
|
||||
|
||||
struct Counter : Component<Counter> {
|
||||
explicit Counter(int counter = 0) : counter(counter) {}
|
||||
|
||||
int counter;
|
||||
};
|
||||
|
||||
class MovementSystem : public System<MovementSystem> {
|
||||
public:
|
||||
explicit MovementSystem(string label = "") : label(label) {}
|
||||
|
||||
void update(EntityManager &es, EventManager &events, TimeDelta) override {
|
||||
auto entities = es.entities_with_components<Position, Direction>();
|
||||
ComponentHandle<Position> position;
|
||||
ComponentHandle<Direction> direction;
|
||||
for (auto entity : entities) {
|
||||
entity.unpack<Position, Direction>(position, direction);
|
||||
position->x += direction->x;
|
||||
position->y += direction->y;
|
||||
}
|
||||
}
|
||||
|
||||
string label;
|
||||
};
|
||||
|
||||
class CounterSystem : public System<CounterSystem> {
|
||||
public:
|
||||
void update(EntityManager &es, EventManager &events, TimeDelta) override {
|
||||
auto entities = es.entities_with_components<Counter>();
|
||||
Counter::Handle counter;
|
||||
for (auto entity : entities) {
|
||||
entity.unpack<Counter>(counter);
|
||||
counter->counter++;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class EntitiesFixture : public EntityX {
|
||||
public:
|
||||
std::vector<Entity> created_entities;
|
||||
|
||||
EntitiesFixture() {
|
||||
for (int i = 0; i < 150; ++i) {
|
||||
Entity e = entities.create();
|
||||
created_entities.push_back(e);
|
||||
if (i % 2 == 0) e.assign<Position>(1, 2);
|
||||
if (i % 3 == 0) e.assign<Direction>(1, 1);
|
||||
|
||||
e.assign<Counter>(0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TEST_CASE_METHOD(EntitiesFixture, "TestConstructSystemWithArgs") {
|
||||
systems.add<MovementSystem>("movement");
|
||||
systems.configure();
|
||||
|
||||
REQUIRE("movement" == systems.system<MovementSystem>()->label);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(EntitiesFixture, "TestApplySystem") {
|
||||
systems.add<MovementSystem>();
|
||||
systems.configure();
|
||||
|
||||
systems.update<MovementSystem>(0.0);
|
||||
ComponentHandle<Position> position;
|
||||
ComponentHandle<Direction> direction;
|
||||
for (auto entity : created_entities) {
|
||||
entity.unpack<Position, Direction>(position, direction);
|
||||
if (position && direction) {
|
||||
REQUIRE(2.0 == Approx(position->x));
|
||||
REQUIRE(3.0 == Approx(position->y));
|
||||
} else if (position) {
|
||||
REQUIRE(1.0 == Approx(position->x));
|
||||
REQUIRE(2.0 == Approx(position->y));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(EntitiesFixture, "TestApplyAllSystems") {
|
||||
systems.add<MovementSystem>();
|
||||
systems.add<CounterSystem>();
|
||||
systems.configure();
|
||||
|
||||
systems.update_all(0.0);
|
||||
Position::Handle position;
|
||||
Direction::Handle direction;
|
||||
Counter::Handle counter;
|
||||
for (auto entity : created_entities) {
|
||||
entity.unpack<Position, Direction, Counter>(position, direction, counter);
|
||||
if (position && direction) {
|
||||
REQUIRE(2.0 == Approx(position->x));
|
||||
REQUIRE(3.0 == Approx(position->y));
|
||||
} else if (position) {
|
||||
REQUIRE(1.0 == Approx(position->x));
|
||||
REQUIRE(2.0 == Approx(position->y));
|
||||
}
|
||||
REQUIRE(1 == counter->counter);
|
||||
}
|
||||
}
|
||||
11
include/entityx/config.h
Normal file
11
include/entityx/config.h
Normal file
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
|
||||
namespace entityx {
|
||||
|
||||
static const size_t MAX_COMPONENTS = 64;
|
||||
typedef double TimeDelta;
|
||||
|
||||
} // namespace entityx
|
||||
11
include/entityx/config.h.in
Normal file
11
include/entityx/config.h.in
Normal file
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
|
||||
namespace entityx {
|
||||
|
||||
static const size_t MAX_COMPONENTS = @ENTITYX_MAX_COMPONENTS@;
|
||||
typedef @ENTITYX_DT_TYPE@ TimeDelta;
|
||||
|
||||
} // namespace entityx
|
||||
54
include/entityx/deps/Dependencies.h
Normal file
54
include/entityx/deps/Dependencies.h
Normal file
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Alec Thomas <alec@swapoff.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* This software is licensed as described in the file COPYING, which
|
||||
* you should have received as part of this distribution.
|
||||
*
|
||||
* Author: Alec Thomas <alec@swapoff.org>
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "entityx/System.h"
|
||||
#include "entityx/Event.h"
|
||||
#include "entityx/Entity.h"
|
||||
|
||||
namespace entityx {
|
||||
namespace deps {
|
||||
|
||||
/**
|
||||
* An entityx::System for declaring component dependencies.
|
||||
*
|
||||
* eg. To declare that a `Physics` component must always be paired with `Position`
|
||||
* and `Direction` components:
|
||||
*
|
||||
* system_manager->add<Dependency<Physics, Position, Direction>>();
|
||||
*/
|
||||
template <typename C, typename ... Deps>
|
||||
class Dependency : public System<Dependency<C, Deps...>>, public Receiver<Dependency<C, Deps...>> {
|
||||
public:
|
||||
void receive(const ComponentAddedEvent<C> &event) {
|
||||
assign<Deps...>(event.entity);
|
||||
}
|
||||
|
||||
void configure(EventManager &events) override {
|
||||
events.subscribe<ComponentAddedEvent<C>>(*this);
|
||||
}
|
||||
|
||||
void update(EntityManager &entities, EventManager &events, TimeDelta dt) override {}
|
||||
|
||||
private:
|
||||
template <typename D>
|
||||
void assign(Entity entity) {
|
||||
if (!entity.component<D>()) entity.assign<D>();
|
||||
}
|
||||
|
||||
template <typename D, typename D1, typename ... Ds>
|
||||
void assign(Entity entity) {
|
||||
assign<D>(entity);
|
||||
assign<D1, Ds...>(entity);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace deps
|
||||
} // namespace entityx
|
||||
64
include/entityx/deps/Dependencies_test.cc
Normal file
64
include/entityx/deps/Dependencies_test.cc
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Alec Thomas <alec@swapoff.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* This software is licensed as described in the file COPYING, which
|
||||
* you should have received as part of this distribution.
|
||||
*
|
||||
* Author: Alec Thomas <alec@swapoff.org>
|
||||
*/
|
||||
|
||||
#define CATCH_CONFIG_MAIN
|
||||
|
||||
#include "entityx/3rdparty/catch.hpp"
|
||||
#include "entityx/deps/Dependencies.h"
|
||||
#include "entityx/quick.h"
|
||||
|
||||
|
||||
namespace deps = entityx::deps;
|
||||
|
||||
|
||||
struct A : public entityx::Component<A> {};
|
||||
struct B : public entityx::Component<B> {
|
||||
explicit B(bool b = false) : b(b) {}
|
||||
|
||||
bool b;
|
||||
};
|
||||
struct C : public entityx::Component<C> {};
|
||||
|
||||
TEST_CASE_METHOD(entityx::EntityX, "TestSingleDependency") {
|
||||
systems.add<deps::Dependency<A, B>>();
|
||||
systems.configure();
|
||||
|
||||
entityx::Entity e = entities.create();
|
||||
REQUIRE(!static_cast<bool>(e.component<A>()));
|
||||
REQUIRE(!static_cast<bool>(e.component<B>()));
|
||||
e.assign<A>();
|
||||
REQUIRE(static_cast<bool>(e.component<A>()));
|
||||
REQUIRE(static_cast<bool>(e.component<B>()));
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(entityx::EntityX, "TestMultipleDependencies") {
|
||||
systems.add<deps::Dependency<A, B, C>>();
|
||||
systems.configure();
|
||||
|
||||
entityx::Entity e = entities.create();
|
||||
REQUIRE(!static_cast<bool>(e.component<A>()));
|
||||
REQUIRE(!static_cast<bool>(e.component<B>()));
|
||||
REQUIRE(!static_cast<bool>(e.component<C>()));
|
||||
e.assign<A>();
|
||||
REQUIRE(static_cast<bool>(e.component<A>()));
|
||||
REQUIRE(static_cast<bool>(e.component<B>()));
|
||||
REQUIRE(static_cast<bool>(e.component<C>()));
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(entityx::EntityX, "TestDependencyDoesNotRecreateComponent") {
|
||||
systems.add<deps::Dependency<A, B>>();
|
||||
systems.configure();
|
||||
|
||||
entityx::Entity e = entities.create();
|
||||
e.assign<B>(true);
|
||||
REQUIRE(e.component<B>()->b);
|
||||
e.assign<A>();
|
||||
REQUIRE(e.component<B>()->b);
|
||||
}
|
||||
7
include/entityx/entityx.h
Normal file
7
include/entityx/entityx.h
Normal file
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "entityx/config.h"
|
||||
#include "entityx/Event.h"
|
||||
#include "entityx/Entity.h"
|
||||
#include "entityx/System.h"
|
||||
#include "entityx/quick.h"
|
||||
20
include/entityx/help/NonCopyable.h
Normal file
20
include/entityx/help/NonCopyable.h
Normal file
@@ -0,0 +1,20 @@
|
||||
// Inspired heavily by boost::noncopyable
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace entityx {
|
||||
namespace help {
|
||||
|
||||
class NonCopyable {
|
||||
protected:
|
||||
NonCopyable() = default;
|
||||
~NonCopyable() = default;
|
||||
|
||||
|
||||
NonCopyable(const NonCopyable&) = delete;
|
||||
NonCopyable& operator = (const NonCopyable &) = delete;
|
||||
};
|
||||
|
||||
|
||||
} // namespace help
|
||||
} // namespace entityx
|
||||
21
include/entityx/help/Pool.cc
Normal file
21
include/entityx/help/Pool.cc
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2014 Alec Thomas <alec@swapoff.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* This software is licensed as described in the file COPYING, which
|
||||
* you should have received as part of this distribution.
|
||||
*
|
||||
* Author: Alec Thomas <alec@swapoff.org>
|
||||
*/
|
||||
|
||||
#include "entityx/help/Pool.h"
|
||||
|
||||
namespace entityx {
|
||||
|
||||
BasePool::~BasePool() {
|
||||
for (char *ptr : blocks_) {
|
||||
delete[] ptr;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace entityx
|
||||
95
include/entityx/help/Pool.h
Normal file
95
include/entityx/help/Pool.h
Normal file
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2014 Alec Thomas <alec@swapoff.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* This software is licensed as described in the file COPYING, which
|
||||
* you should have received as part of this distribution.
|
||||
*
|
||||
* Author: Alec Thomas <alec@swapoff.org>
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cassert>
|
||||
#include <vector>
|
||||
|
||||
namespace entityx {
|
||||
|
||||
/**
|
||||
* Provides a resizable, semi-contiguous pool of memory for constructing
|
||||
* objects in. Pointers into the pool will be invalided only when the pool is
|
||||
* destroyed.
|
||||
*
|
||||
* The semi-contiguous nature aims to provide cache-friendly iteration.
|
||||
*
|
||||
* Lookups are O(1).
|
||||
* Appends are amortized O(1).
|
||||
*/
|
||||
class BasePool {
|
||||
public:
|
||||
explicit BasePool(std::size_t element_size, std::size_t chunk_size = 8192)
|
||||
: element_size_(element_size), chunk_size_(chunk_size), capacity_(0) {}
|
||||
virtual ~BasePool();
|
||||
|
||||
std::size_t size() const { return size_; }
|
||||
std::size_t capacity() const { return capacity_; }
|
||||
std::size_t chunks() const { return blocks_.size(); }
|
||||
|
||||
/// Ensure at least n elements will fit in the pool.
|
||||
inline void expand(std::size_t n) {
|
||||
if (n >= size_) {
|
||||
if (n >= capacity_) reserve(n);
|
||||
size_ = n;
|
||||
}
|
||||
}
|
||||
|
||||
inline void reserve(std::size_t n) {
|
||||
while (capacity_ < n) {
|
||||
char *chunk = new char[element_size_ * chunk_size_];
|
||||
blocks_.push_back(chunk);
|
||||
capacity_ += chunk_size_;
|
||||
}
|
||||
}
|
||||
|
||||
inline void *get(std::size_t n) {
|
||||
assert(n < size_);
|
||||
return blocks_[n / chunk_size_] + (n % chunk_size_) * element_size_;
|
||||
}
|
||||
|
||||
inline const void *get(std::size_t n) const {
|
||||
assert(n < size_);
|
||||
return blocks_[n / chunk_size_] + (n % chunk_size_) * element_size_;
|
||||
}
|
||||
|
||||
virtual void destroy(std::size_t n) = 0;
|
||||
|
||||
protected:
|
||||
std::vector<char *> blocks_;
|
||||
std::size_t element_size_;
|
||||
std::size_t chunk_size_;
|
||||
std::size_t size_ = 0;
|
||||
std::size_t capacity_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Implementation of BasePool that provides type-"safe" deconstruction of
|
||||
* elements in the pool.
|
||||
*/
|
||||
template <typename T, std::size_t ChunkSize = 8192>
|
||||
class Pool : public BasePool {
|
||||
public:
|
||||
Pool() : BasePool(sizeof(T), ChunkSize) {}
|
||||
virtual ~Pool() {
|
||||
// Component destructors *must* be called by owner.
|
||||
}
|
||||
|
||||
virtual void destroy(std::size_t n) override {
|
||||
assert(n < size_);
|
||||
T *ptr = static_cast<T*>(get(n));
|
||||
ptr->~T();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace entityx
|
||||
80
include/entityx/help/Pool_test.cc
Normal file
80
include/entityx/help/Pool_test.cc
Normal file
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2014 Alec Thomas <alec@swapoff.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* This software is licensed as described in the file COPYING, which
|
||||
* you should have received as part of this distribution.
|
||||
*
|
||||
* Author: Alec Thomas <alec@swapoff.org>
|
||||
*/
|
||||
|
||||
#define CATCH_CONFIG_MAIN
|
||||
|
||||
#include <vector>
|
||||
#include "entityx/3rdparty/catch.hpp"
|
||||
#include "entityx/help/Pool.h"
|
||||
|
||||
struct Position {
|
||||
explicit Position(int *ptr = nullptr) : ptr(ptr) {
|
||||
if (ptr) (*ptr)++;
|
||||
}
|
||||
~Position() {
|
||||
if (ptr) (*ptr)++;
|
||||
}
|
||||
|
||||
float x, y;
|
||||
int *ptr;
|
||||
};
|
||||
|
||||
|
||||
TEST_CASE("TestPoolReserve") {
|
||||
entityx::Pool<Position, 8> pool;
|
||||
REQUIRE(0 == pool.capacity());
|
||||
REQUIRE(0 == pool.chunks());
|
||||
pool.reserve(8);
|
||||
REQUIRE(0 == pool.size());
|
||||
REQUIRE(8 == pool.capacity());
|
||||
REQUIRE(1 == pool.chunks());
|
||||
pool.reserve(16);
|
||||
REQUIRE(0 == pool.size());
|
||||
REQUIRE(16 == pool.capacity());
|
||||
REQUIRE(2 == pool.chunks());
|
||||
}
|
||||
|
||||
TEST_CASE("TestPoolPointers") {
|
||||
entityx::Pool<Position, 8> pool;
|
||||
std::vector<char*> ptrs;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
pool.expand(i * 8 + 8);
|
||||
// NOTE: This is an attempt to ensure non-contiguous allocations from
|
||||
// arena allocators.
|
||||
ptrs.push_back(new char[8 * sizeof(Position)]);
|
||||
}
|
||||
char *p0 = static_cast<char*>(pool.get(0));
|
||||
char *p7 = static_cast<char*>(pool.get(7));
|
||||
char *p8 = static_cast<char*>(pool.get(8));
|
||||
char *p16 = static_cast<char*>(pool.get(16));
|
||||
char *p24 = static_cast<char*>(pool.get(24));
|
||||
|
||||
void *expected_p7 = p0 + 7 * sizeof(Position);
|
||||
REQUIRE(expected_p7 == static_cast<void*>(p7));
|
||||
void *extrapolated_p8 = p0 + 8 * sizeof(Position);
|
||||
REQUIRE(extrapolated_p8 != static_cast<void*>(p8));
|
||||
void *extrapolated_p16 = p8 + 8 * sizeof(Position);
|
||||
REQUIRE(extrapolated_p16 != static_cast<void*>(p16));
|
||||
void *extrapolated_p24 = p16 + 8 * sizeof(Position);
|
||||
REQUIRE(extrapolated_p24 != static_cast<void*>(p24));
|
||||
}
|
||||
|
||||
TEST_CASE("TestDeconstruct") {
|
||||
entityx::Pool<Position, 8> pool;
|
||||
pool.expand(8);
|
||||
|
||||
void *p0 = pool.get(0);
|
||||
|
||||
int counter = 0;
|
||||
new(p0) Position(&counter);
|
||||
REQUIRE(1 == counter);
|
||||
pool.destroy(0);
|
||||
REQUIRE(2 == counter);
|
||||
}
|
||||
32
include/entityx/help/Timer.cc
Normal file
32
include/entityx/help/Timer.cc
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Antony Woods <antony@teamwoods.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* This software is licensed as described in the file COPYING, which
|
||||
* you should have received as part of this distribution.
|
||||
*
|
||||
* Author: Antony Woods <antony@teamwoods.org>
|
||||
*/
|
||||
|
||||
#include "entityx/help/Timer.h"
|
||||
|
||||
namespace entityx {
|
||||
namespace help {
|
||||
|
||||
Timer::Timer() {
|
||||
_start = std::chrono::system_clock::now();
|
||||
}
|
||||
|
||||
Timer::~Timer() {
|
||||
}
|
||||
|
||||
void Timer::restart() {
|
||||
_start = std::chrono::system_clock::now();
|
||||
}
|
||||
|
||||
double Timer::elapsed() {
|
||||
return std::chrono::duration<double>(std::chrono::system_clock::now() - _start).count();
|
||||
}
|
||||
|
||||
} // namespace help
|
||||
} // namespace entityx
|
||||
29
include/entityx/help/Timer.h
Normal file
29
include/entityx/help/Timer.h
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Antony Woods <antony@teamwoods.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* This software is licensed as described in the file COPYING, which
|
||||
* you should have received as part of this distribution.
|
||||
*
|
||||
* Author: Antony Woods <antony@teamwoods.org>
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
|
||||
namespace entityx {
|
||||
namespace help {
|
||||
|
||||
class Timer {
|
||||
public:
|
||||
Timer();
|
||||
~Timer();
|
||||
|
||||
void restart();
|
||||
double elapsed();
|
||||
private:
|
||||
std::chrono::time_point<std::chrono::system_clock> _start;
|
||||
};
|
||||
|
||||
} // namespace help
|
||||
} // namespace entityx
|
||||
33
include/entityx/quick.h
Normal file
33
include/entityx/quick.h
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Alec Thomas <alec@swapoff.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* This software is licensed as described in the file COPYING, which
|
||||
* you should have received as part of this distribution.
|
||||
*
|
||||
* Author: Alec Thomas <alec@swapoff.org>
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "entityx/Entity.h"
|
||||
#include "entityx/Event.h"
|
||||
#include "entityx/System.h"
|
||||
#include "entityx/config.h"
|
||||
|
||||
namespace entityx {
|
||||
|
||||
/**
|
||||
* A convenience class for instantiating an EventManager, EntityManager and
|
||||
* SystemManager.
|
||||
*/
|
||||
class EntityX {
|
||||
public:
|
||||
EntityX() : entities(events), systems(entities, events) {}
|
||||
|
||||
EventManager events;
|
||||
EntityManager entities;
|
||||
SystemManager systems;
|
||||
};
|
||||
|
||||
} // namespace entityx
|
||||
55
include/entityx/tags/TagsComponent.h
Normal file
55
include/entityx/tags/TagsComponent.h
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Alec Thomas <alec@swapoff.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* This software is licensed as described in the file COPYING, which
|
||||
* you should have received as part of this distribution.
|
||||
*
|
||||
* Author: Alec Thomas <alec@swapoff.org>
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <unordered_set>
|
||||
#include <string>
|
||||
#include "entityx/Entity.h"
|
||||
|
||||
namespace entityx {
|
||||
namespace tags {
|
||||
|
||||
/**
|
||||
* Allow entities to be tagged with strings.
|
||||
*
|
||||
* entity.assign<TagsComponent>("tag1", "tag2");
|
||||
*
|
||||
* ComponentPtr<TagsComponent> tags;
|
||||
* for (Entity entity : entity_manager.entities_with_components(tags))
|
||||
*/
|
||||
class TagsComponent : public Component<TagsComponent> {
|
||||
public:
|
||||
/**
|
||||
* Construct a new TagsComponent with the given tags.
|
||||
*
|
||||
* eg. TagsComponent tags("a", "b", "c");
|
||||
*/
|
||||
template <typename ... Args>
|
||||
TagsComponent(const std::string &tag, const Args & ... tags) {
|
||||
set_tags(tag, tags ...);
|
||||
}
|
||||
|
||||
std::unordered_set<std::string> tags;
|
||||
|
||||
private:
|
||||
template <typename ... Args>
|
||||
void set_tags(const std::string &tag1, const std::string &tag2, const Args & ... tags) {
|
||||
this->tags.insert(tag1);
|
||||
set_tags(tag2, tags ...);
|
||||
}
|
||||
|
||||
void set_tags(const std::string &tag) {
|
||||
tags.insert(tag);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace tags
|
||||
} // namespace entityx
|
||||
42
include/entityx/tags/TagsComponent_test.cc
Normal file
42
include/entityx/tags/TagsComponent_test.cc
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Alec Thomas <alec@swapoff.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* This software is licensed as described in the file COPYING, which
|
||||
* you should have received as part of this distribution.
|
||||
*
|
||||
* Author: Alec Thomas <alec@swapoff.org>
|
||||
*/
|
||||
|
||||
#define CATCH_CONFIG_MAIN
|
||||
|
||||
#include <string>
|
||||
#include "entityx/3rdparty/catch.hpp"
|
||||
#include "entityx/tags/TagsComponent.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace entityx;
|
||||
using namespace entityx::tags;
|
||||
|
||||
|
||||
struct Position : public Component<Position> {};
|
||||
|
||||
|
||||
template <typename T>
|
||||
int size(const T &t) {
|
||||
int n = 0;
|
||||
for (auto i : t) {
|
||||
++n;
|
||||
(void)i; // Unused on purpose, suppress warning
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("TestVariadicConstruction", "TagsComponentTest") {
|
||||
auto tags = TagsComponent("player", "indestructible");
|
||||
unordered_set<string> expected;
|
||||
expected.insert("player");
|
||||
expected.insert("indestructible");
|
||||
REQUIRE(expected == tags.tags);
|
||||
}
|
||||
Reference in New Issue
Block a user