Added librarys and test application

This commit is contained in:
Gulum
2017-10-22 14:26:55 +02:00
parent 7e0c0d8a6c
commit ac45f1f944
206 changed files with 68009 additions and 13 deletions

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
View 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

View 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
View 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

File diff suppressed because it is too large Load Diff

View 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
View 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
View 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

View 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
View 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
View 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

View 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
View 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

View 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

View 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

View 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);
}

View 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"

View 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

View 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

View 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

View 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);
}

View 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

View 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
View 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

View 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

View 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);
}