From 947d29aa374b9b5f5c581120ce5a4e7a32d0c981 Mon Sep 17 00:00:00 2001 From: Alec Thomas Date: Mon, 1 Apr 2013 11:51:29 -0400 Subject: Allow shared_ptr implementation to be selected. Fixes #6. --- .gitignore | 1 + .travis.yml | 5 ++- CMakeLists.txt | 80 ++++++++++++++++++++++++++++++++++++++++++---- README.md | 18 ++++++----- entityx/Benchmarks_test.cc | 2 +- entityx/Components.h | 4 +-- entityx/Entity.h | 70 ++++++++++++++++++++-------------------- entityx/Event.h | 16 +++++----- entityx/System.h | 22 ++++++------- entityx/config.h.in | 23 +++++++++++++ scripts/travis.sh | 15 +++++++++ 11 files changed, 183 insertions(+), 73 deletions(-) create mode 100644 entityx/config.h.in create mode 100755 scripts/travis.sh diff --git a/.gitignore b/.gitignore index f08d8e3..4f2a962 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ *.so *.o build/* +entityx/config.h diff --git a/.travis.yml b/.travis.yml index e7ac9b8..1f5acba 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,9 @@ language: cpp compiler: - clang - gcc +env: + - USE_STD_SHARED_PTR=1 + - USE_STD_SHARED_PTR=0 before_install: - sudo apt-add-repository -y ppa:jkeiren/ppa - if test $CC = gcc; then sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test; fi @@ -14,4 +17,4 @@ before_install: - if test $CC = gcc; then sudo update-alternatives --config gcc; fi - if test $CC = gcc; then sudo update-alternatives --config g++; fi -script: cmake -DBUILD_TESTING=1 && make && make test +script: ./scripts/travis.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index 9200f8c..013c96e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,10 +5,82 @@ include_directories(${CMAKE_CURRENT_LIST_DIR}) set(RUN_BENCHMARKS false CACHE BOOL "Run benchmarks") include(${CMAKE_ROOT}/Modules/CheckIncludeFile.cmake) +include(CheckCXXSourceCompiles) + +# Default compiler args +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pedantic -Werror -Wall -Wextra -Wno-unused-parameter -Wno-error=unused-variable -Wno-error=sign-compare -std=c++11") +set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g") +set(CMAKE_CXX_FLAGS_MINSIZEREL "-Os -DNDEBUG") +set(CMAKE_CXX_FLAGS_RELEASE "-O2 -DNDEBUG") +set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O2 -g") + # C++11 feature checks include(CheckCXX11Features.cmake) + # Misc features -CHECK_INCLUDE_FILE("stdint.h" HAVE_STDINT_H) +check_include_file("stdint.h" HAVE_STDINT_H) + +set(USE_CPP11_STDLIB false CACHE BOOL "Use the C++11 stdlib (-stdlib=libc++).") + +if (USE_CPP11_STDLIB) + set(OLD_CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") + check_cxx_source_compiles( + " + #include + + int main() { + std::shared_ptr(); + } + " + HAVE_CXX11_STDLIB + ) + + if (NOT HAVE_CXX11_STDLIB) + message("-- Not using -stdlib=libc++ (test failed to build)") + set(CMAKE_CXX_FLAGS "${OLD_CMAKE_CXX_FLAGS}") + else () + message("-- Using -stdlib=libc++") + endif () +else () + message("-- Using default stdlib (try -DUSE_CPP11_STDLIB=1 to use -stdlib=libc++)") +endif () + +# Check for which shared_ptr implementation to use. +set(USE_STD_SHARED_PTR false CACHE BOOL "Use std::shared_ptr rather than boost::shared_ptr?") + +check_cxx_source_compiles( +" +#include + +int main() { std::shared_ptr(); } +" +HAVE_STD_SHARED_PTR +) + +check_cxx_source_compiles( +" +#include + +int main() { boost::shared_ptr(); } +" +HAVE_BOOST_SHARED_PTR +) + +if (HAVE_STD_SHARED_PTR AND USE_STD_SHARED_PTR) + message("-- Using std::shared_ptr") +else() + if (USE_STD_SHARED_PTR) + message("-- Using boost::shared_ptr (std::shared_ptr could not be used)") + else() + message("-- Using boost::shared_ptr (try -DUSE_STD_SHARED_PTR=1 to use std::shared_ptr)") + endif() +endif() + +configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/entityx/config.h.in + ${CMAKE_CURRENT_SOURCE_DIR}/entityx/config.h +) macro(require FEATURE_NAME MESSAGE_STRING) if (NOT ${${FEATURE_NAME}}) @@ -55,12 +127,6 @@ set(Boost_USE_MULTITHREADED ON) set(Boost_USE_STATIC_RUNTIME OFF) find_package(Boost 1.48.0 REQUIRED COMPONENTS signals) -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pedantic -Werror -Wall -Wextra -Wno-unused-parameter -Wno-error=unused-variable -Wno-error=sign-compare -std=c++11") -set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g") -set(CMAKE_CXX_FLAGS_MINSIZEREL "-Os -DNDEBUG") -set(CMAKE_CXX_FLAGS_RELEASE "-O2 -DNDEBUG") -set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O2 -g") - set(sources entityx/Components.cc entityx/System.cc entityx/Event.cc entityx/Entity.cc entityx/Manager.cc) add_library(entityx STATIC ${sources}) add_library(entityx_shared SHARED ${sources}) diff --git a/README.md b/README.md index 36f93fa..bc7248d 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # EntityX - A fast, type-safe C++ Entity-Component system +[![Build Status](https://travis-ci.org/alecthomas/entityx.png)](https://travis-ci.org/alecthomas/entityx) + Entity-Component (EC) systems are a form of decomposition that completely decouples entity logic and data from the entity "objects" themselves. The [Evolve your Hierarchy](http://cowboyprogramming.com/2007/01/05/evolve-your-heirachy/) article provides a solid overview of EC systems and why you should use them. EntityX is an EC system that uses C++11 features to provide type-safe component management, event delivery, etc. It was built during the creation of a 2D space shooter. @@ -76,7 +78,7 @@ entity.assign(1.0f, 2.0f); You can also assign existing instances of components: ```c++ -boost::shared_ptr position = boost::make_shared(1.0f, 2.0f); +entityx::shared_ptr position = entityx::make_shared(1.0f, 2.0f); entity.assign(position); ``` @@ -86,8 +88,8 @@ To query all entities with a set of components assigned, use ``EntityManager::en ```c++ for (auto entity : entities.entities_with_components()) { - boost::shared_ptr position = entity.component(); - boost::shared_ptr direction = entity.component(); + entityx::shared_ptr position = entity.component(); + entityx::shared_ptr direction = entity.component(); // Do things with entity, position and direction. } @@ -96,7 +98,7 @@ for (auto entity : entities.entities_with_components()) { To retrieve a component associated with an entity use ``Entity::component()``: ```c++ -boost::shared_ptr position = entity.component(); +entityx::shared_ptr position = entity.component(); if (position) { // Do stuff with position } @@ -117,8 +119,8 @@ A basic movement system might be implemented with something like the following: struct MovementSystem : public System { void update(EntityManager &es, EventManager &events, double dt) override { for (auto entity : es.entities_with_components()) { - boost::shared_ptr position = entity.component(); - boost::shared_ptr direction = entity.component(); + entityx::shared_ptr position = entity.component(); + entityx::shared_ptr direction = entity.component(); position->x += direction->x * dt; position->y += direction->y * dt; @@ -154,7 +156,7 @@ Next we implement our collision system, which emits ``Collision`` objects via an class CollisionSystem : public System { public: void update(EntityManager &es, EventManager &events, double dt) override { - boost::shared_ptr left_position, right_position; + entityx::shared_ptr left_position, right_position; for (auto left_entity : es.entities_with_components()) { for (auto right_entity : es.entities_with_components()) { if (collide(left_position, right_position)) { @@ -192,7 +194,7 @@ Several events are emitted by EntityX itself: - `Entity entity` - Entity about to be destroyed. - `ComponentAddedEvent` - emitted when a new component is added to an entity. - `Entity entity` - Entity that component was added to. - - `boost::shared_ptr component` - The component added. + - `entityx::shared_ptr component` - The component added. #### Implementation notes diff --git a/entityx/Benchmarks_test.cc b/entityx/Benchmarks_test.cc index bee7d15..a2ed363 100644 --- a/entityx/Benchmarks_test.cc +++ b/entityx/Benchmarks_test.cc @@ -98,7 +98,7 @@ TEST_F(BenchmarksTest, TestEntityIteration) { for (int i = 0; i < 10; ++i) { for (auto e : em.entities_with_components()) { - boost::shared_ptr position = e.component(); + entityx::shared_ptr position = e.component(); } } } diff --git a/entityx/Components.h b/entityx/Components.h index c963302..9168cc5 100644 --- a/entityx/Components.h +++ b/entityx/Components.h @@ -1,10 +1,10 @@ /** * Copyright (C) 2012 Alec Thomas * 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 */ diff --git a/entityx/Entity.h b/entityx/Entity.h index 65900b9..c2d4af1 100644 --- a/entityx/Entity.h +++ b/entityx/Entity.h @@ -23,7 +23,7 @@ #include #include -#include +#include "entityx/config.h" #include "entityx/Event.h" namespace entityx { @@ -88,17 +88,17 @@ class Entity { EntityManager &manager() { return *manager_; } template - boost::shared_ptr assign(boost::shared_ptr component); + entityx::shared_ptr assign(entityx::shared_ptr component); template - boost::shared_ptr assign(Args && ... args); + entityx::shared_ptr assign(Args && ... args); template - boost::shared_ptr component(); + entityx::shared_ptr component(); template - void unpack(boost::shared_ptr &a); + void unpack(entityx::shared_ptr &a); template - void unpack(boost::shared_ptr &a, boost::shared_ptr &b, Args && ... args); + void unpack(entityx::shared_ptr &a, entityx::shared_ptr &b, Args && ... args); /** * Destroy and invalidate this Entity. @@ -183,11 +183,11 @@ struct EntityDestroyedEvent : public Event { */ template struct ComponentAddedEvent : public Event> { - ComponentAddedEvent(Entity entity, boost::shared_ptr component) : + ComponentAddedEvent(Entity entity, entityx::shared_ptr component) : entity(entity), component(component) {} Entity entity; - boost::shared_ptr component; + entityx::shared_ptr component; }; @@ -291,7 +291,7 @@ class EntityManager : boost::noncopyable { const Iterator end() const { return Iterator(manager_, predicates_, unpackers_, manager_->size()); } template - View &unpack_to(boost::shared_ptr &a) { + View &unpack_to(entityx::shared_ptr &a) { unpackers_.push_back(Unpacker(manager_, a)); // This resulted in a segfault under clang 4.1 on OSX. No idea why. /*unpackers_.push_back([&a, this](Entity::Id id) { @@ -301,7 +301,7 @@ class EntityManager : boost::noncopyable { } template - View &unpack_to(boost::shared_ptr &a, boost::shared_ptr &b, Args && ... args) { + View &unpack_to(entityx::shared_ptr &a, entityx::shared_ptr &b, Args && ... args) { unpack_to(a); return unpack_to(b, args ...); } @@ -310,7 +310,7 @@ class EntityManager : boost::noncopyable { template struct Unpacker { - Unpacker(EntityManager *manager, boost::shared_ptr &c) : manager_(manager), c(c) {} + Unpacker(EntityManager *manager, entityx::shared_ptr &c) : manager_(manager), c(c) {} void operator () (Entity::Id id) { c = manager_->component(id); @@ -318,7 +318,7 @@ class EntityManager : boost::noncopyable { private: EntityManager *manager_; - boost::shared_ptr &c; + entityx::shared_ptr &c; }; View(EntityManager *manager, Predicate predicate) : manager_(manager) { @@ -378,8 +378,8 @@ class EntityManager : boost::noncopyable { * @returns component */ template - boost::shared_ptr assign(Entity::Id entity, boost::shared_ptr component) { - boost::shared_ptr base = boost::static_pointer_cast(component); + entityx::shared_ptr assign(Entity::Id entity, entityx::shared_ptr component) { + entityx::shared_ptr base = entityx::static_pointer_cast(component); accomodate_component(C::family()); entity_components_.at(C::family()).at(entity) = base; entity_component_mask_.at(entity) |= uint64_t(1) << C::family(); @@ -396,8 +396,8 @@ class EntityManager : boost::noncopyable { * @returns Newly created component. */ template - boost::shared_ptr assign(Entity::Id entity, Args && ... args) { - return assign(entity, boost::make_shared(args ...)); + entityx::shared_ptr assign(Entity::Id entity, Args && ... args) { + return assign(entity, entityx::make_shared(args ...)); } /** @@ -406,13 +406,13 @@ class EntityManager : boost::noncopyable { * @returns Component instance, or empty shared_ptr<> if the Entity::Id does not have that Component. */ template - boost::shared_ptr component(Entity::Id id) { + entityx::shared_ptr component(Entity::Id id) { // We don't bother checking the component mask, as we return a nullptr anyway. if (C::family() >= entity_components_.size()) { - return boost::shared_ptr(); + return entityx::shared_ptr(); } - boost::shared_ptr c = entity_components_.at(C::family()).at(id); - return boost::static_pointer_cast(c); + entityx::shared_ptr c = entity_components_.at(C::family()).at(id); + return entityx::static_pointer_cast(c); } /** @@ -428,7 +428,7 @@ class EntityManager : boost::noncopyable { * Find Entities that have all of the specified Components. */ template - View entities_with_components(boost::shared_ptr &c, Components && ... args) { + View entities_with_components(entityx::shared_ptr &c, Components && ... args) { auto mask = component_mask(c, args ...); return View(this, View::ComponentMaskPredicate(entity_component_mask_, mask)) @@ -442,12 +442,12 @@ class EntityManager : boost::noncopyable { * * Useful for fast bulk iterations. * - * boost::shared_ptr p; - * boost::shared_ptr d; + * entityx::shared_ptr p; + * entityx::shared_ptr d; * unpack(e, p, d); */ template - void unpack(Entity::Id id, boost::shared_ptr &a) { + void unpack(Entity::Id id, entityx::shared_ptr &a) { a = component(id); } @@ -458,12 +458,12 @@ class EntityManager : boost::noncopyable { * * Useful for fast bulk iterations. * - * boost::shared_ptr p; - * boost::shared_ptr d; + * entityx::shared_ptr p; + * entityx::shared_ptr d; * unpack(e, p, d); */ template - void unpack(Entity::Id id, boost::shared_ptr &a, boost::shared_ptr &b, Args && ... args) { + void unpack(Entity::Id id, entityx::shared_ptr &a, entityx::shared_ptr &b, Args && ... args) { unpack(id, a); unpack(id, b, args ...); } @@ -482,12 +482,12 @@ class EntityManager : boost::noncopyable { } template - ComponentMask component_mask(const boost::shared_ptr &c) { + ComponentMask component_mask(const entityx::shared_ptr &c) { return component_mask(); } template - ComponentMask component_mask(const boost::shared_ptr &c1, const boost::shared_ptr &c2, Components && ... args) { + ComponentMask component_mask(const entityx::shared_ptr &c1, const entityx::shared_ptr &c2, Components && ... args) { return component_mask(c1) | component_mask(c2, args...); } @@ -513,7 +513,7 @@ class EntityManager : boost::noncopyable { EventManager &event_manager_; // A nested array of: components = entity_components_[family][entity] - std::vector>> entity_components_; + std::vector>> entity_components_; // Bitmask of components associated with each entity. Index into the vector is the Entity::Id. std::vector entity_component_mask_; // List of available Entity::Id IDs. @@ -528,27 +528,27 @@ BaseComponent::Family Component::family() { } template -boost::shared_ptr Entity::assign(boost::shared_ptr component) { +entityx::shared_ptr Entity::assign(entityx::shared_ptr component) { return manager_->assign(id_, component); } template -boost::shared_ptr Entity::assign(Args && ... args) { +entityx::shared_ptr Entity::assign(Args && ... args) { return manager_->assign(id_, args ...); } template -boost::shared_ptr Entity::component() { +entityx::shared_ptr Entity::component() { return manager_->component(id_); } template -void Entity::unpack(boost::shared_ptr &a) { +void Entity::unpack(entityx::shared_ptr &a) { manager_->unpack(id_, a); } template -void Entity::unpack(boost::shared_ptr &a, boost::shared_ptr &b, Args && ... args) { +void Entity::unpack(entityx::shared_ptr &a, entityx::shared_ptr &b, Args && ... args) { manager_->unpack(id_, a, b, args ...); } diff --git a/entityx/Event.h b/entityx/Event.h index a599b90..fcec247 100644 --- a/entityx/Event.h +++ b/entityx/Event.h @@ -1,22 +1,22 @@ /** * Copyright (C) 2012 Alec Thomas * 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 */ #pragma once #include -#include #include #include #include #include #include +#include "entityx/config.h" namespace entityx { @@ -25,7 +25,7 @@ namespace entityx { /// Used internally by the EventManager. class BaseEvent { public: - typedef boost::shared_ptr Ptr; + typedef entityx::shared_ptr Ptr; typedef uint64_t Family; virtual ~BaseEvent() {} @@ -46,7 +46,7 @@ class BaseEvent { template class Event : public BaseEvent { public: - typedef boost::shared_ptr> Ptr; + typedef entityx::shared_ptr> Ptr; /// Used internally for registration. static Family family() { @@ -127,17 +127,17 @@ class EventManager : boost::noncopyable { private: typedef boost::signal EventSignal; - typedef boost::shared_ptr EventSignalPtr; + typedef entityx::shared_ptr EventSignalPtr; EventSignalPtr signal_for(int id) { auto it = handlers_.find(id); if (it == handlers_.end()) { - EventSignalPtr sig(boost::make_shared()); + EventSignalPtr sig(entityx::make_shared()); handlers_.insert(make_pair(id, sig)); return sig; } return it->second; - } + } // Functor used as an event signal callback that casts to E. template diff --git a/entityx/System.h b/entityx/System.h index 962fd29..b0077d7 100644 --- a/entityx/System.h +++ b/entityx/System.h @@ -14,8 +14,8 @@ #include #include #include -#include #include +#include "entityx/config.h" #include "entityx/Entity.h" #include "entityx/Event.h" @@ -82,11 +82,11 @@ class SystemManager : boost::noncopyable { * Must be called before Systems can be used. * * eg. - * boost::shared_ptr movement = boost::make_shared(); + * entityx::shared_ptr movement = entityx::make_shared(); * system.add(movement); */ template - void add(boost::shared_ptr system) { + void add(entityx::shared_ptr system) { systems_.insert(std::make_pair(S::family(), system)); } @@ -99,8 +99,8 @@ class SystemManager : boost::noncopyable { * auto movement = system.add(); */ template - boost::shared_ptr add(Args && ... args) { - boost::shared_ptr s = boost::make_shared(args ...); + entityx::shared_ptr add(Args && ... args) { + entityx::shared_ptr s = entityx::make_shared(args ...); add(s); return s; } @@ -108,17 +108,17 @@ class SystemManager : boost::noncopyable { /** * Retrieve the registered System instance, if any. * - * boost::shared_ptr collisions = systems.system(); + * entityx::shared_ptr collisions = systems.system(); * * @return System instance or empty shared_ptr. */ template - boost::shared_ptr system() { + entityx::shared_ptr system() { auto it = systems_.find(S::family()); assert(it != systems_.end()); return it == systems_.end() - ? boost::shared_ptr() - : boost::static_pointer_cast(it->second); + ? entityx::shared_ptr() + : entityx::static_pointer_cast(it->second); } /** @@ -127,7 +127,7 @@ class SystemManager : boost::noncopyable { template void update(double dt) { assert(initialized_ && "SystemManager::configure() not called"); - boost::shared_ptr s = system(); + entityx::shared_ptr s = system(); s->update(entities_, events_, dt); } @@ -142,7 +142,7 @@ class SystemManager : boost::noncopyable { bool initialized_ = false; EntityManager &entities_; EventManager &events_; - boost::unordered_map> systems_; + boost::unordered_map> systems_; }; } diff --git a/entityx/config.h.in b/entityx/config.h.in new file mode 100644 index 0000000..f5d1689 --- /dev/null +++ b/entityx/config.h.in @@ -0,0 +1,23 @@ +#pragma once + +#cmakedefine HAVE_BOOST_SHARED_PTR 1 +#cmakedefine HAVE_STD_SHARED_PTR 1 +#cmakedefine USE_STD_SHARED_PTR 0 + +// Which shared_ptr implementation should we use? +#if (HAVE_STD_SHARED_PTR && USE_STD_SHARED_PTR) +#include +namespace entityx { +using std::make_shared; +using std::shared_ptr; +using std::static_pointer_cast; +} +#elif HAVE_BOOST_SHARED_PTR +#include +#include +namespace entityx { +using boost::shared_ptr; +using boost::make_shared; +using boost::static_pointer_cast; +} +#endif diff --git a/scripts/travis.sh b/scripts/travis.sh new file mode 100755 index 0000000..9d06fa6 --- /dev/null +++ b/scripts/travis.sh @@ -0,0 +1,15 @@ +#!/bin/bash -e + +CMAKE_ARGS="-DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTING=1" + +if [ "$USE_STD_SHARED_PTR" = "1" ]; then + CMAKE_ARGS="${CMAKE_ARGS} -DUSE_STD_SHARED_PTR=1" + # This fails on OSX + if [ "$CXX" = "clang++" ]; then + CMAKE_ARGS="${CMAKE_ARGS} -DUSE_CPP11_STDLIB=1" + fi +fi + +cmake ${CMAKE_ARGS} +make +make test -- cgit v1.2.3