Compare commits

...

13 Commits

Author SHA1 Message Date
Michele Caini
62bd742673 fixed doc 2017-12-27 17:59:57 +01:00
Michele Caini
42d0a3d734 v2.4.0 2017-12-27 17:57:04 +01:00
Michele Caini
f0f8681455 bug fixing 2017-12-27 17:55:26 +01:00
Michele Caini
c801afddcb added optional data to process::init 2017-12-23 00:30:00 +01:00
Michele Caini
20e0e1333e minor changes 2017-12-23 00:21:05 +01:00
Michele Caini
a6b373fec4 minor changes 2017-12-23 00:18:23 +01:00
Michele Caini
41c77720bb added optional data to scheduler/process 2017-12-22 23:59:07 +01:00
Michele Caini
92e6340120 cleanup 2017-12-22 23:58:49 +01:00
Michele Caini
1221f63cbd updated doc 2017-12-22 09:24:56 +01:00
Michele Caini
0f24418891 added ResourceCache::temp 2017-12-20 13:39:23 +01:00
Michele Caini
f477c0ab87 fixed reserve 2017-12-18 14:57:23 +01:00
Michele Caini
9358691901 added reserve 2017-12-18 14:08:38 +01:00
Michele Caini
cd343ba598 updated appveyor.yml (waiting for a new stable release of googletest) 2017-12-15 23:06:43 +01:00
14 changed files with 199 additions and 62 deletions

View File

@@ -16,7 +16,7 @@ endif()
# Project configuration
#
project(entt VERSION 2.3.0)
project(entt VERSION 2.4.0)
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug)

View File

@@ -840,6 +840,28 @@ whether all the components have to be accessed or not.
function template of a registry during iterations, if possible. However, keep in
mind that it works only with the components of the view itself.
### Give me everything
Views are narrow windows on the entire list of entities. They work by filtering
entities according to their components.<br/>
In some cases there may be the need to iterate all the entities regardless of
their components. The registry offers a specific member function to do that:
```cpp
registry.each([](auto entity) {
// ...
});
```
Each entity ever created is returned, no matter if it's in use or not.<br/>
Usually, filtering entities that aren't currently in use is more expensive than
iterating them all and filtering out those in which one isn't interested.
As a rule of thumb, consider using a view if the goal is to iterate entities
that have a determinate set of components. A view is usually faster than
combining this function with a bunch of custom tests.<br/>
In all the other cases, this is the way to go.
## Side notes
* Entity identifiers are numbers and nothing more. They are not classes and they

View File

@@ -14,7 +14,7 @@ configuration:
before_build:
- cd %BUILD_DIR%
- cmake .. -G"Visual Studio 15 2017"
- cmake .. -DCMAKE_CXX_FLAGS=/D_SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING -G"Visual Studio 15 2017"
build:
parallel: true

View File

@@ -231,6 +231,33 @@ public:
return entities.size() - available.size();
}
/**
* @brief Increases the capacity of the pool for a given component.
*
* If the new capacity is greater than the current capacity, new storage is
* allocated, otherwise the method does nothing.
*
* @tparam Component Type of component for which to reserve storage.
* @param cap Desired capacity.
*/
template<typename Component>
void reserve(size_type cap) {
ensure<Component>().reserve(cap);
}
/**
* @brief Increases the capacity of a registry in terms of entities.
*
* If the new capacity is greater than the current capacity, new storage is
* allocated, otherwise the method does nothing.
*
* @param cap Desired capacity.
*/
void reserve(size_type cap) {
entities.reserve(cap);
available.reserve(cap);
}
/**
* @brief Returns the number of entities ever created.
* @return Number of entities ever created.

View File

@@ -117,6 +117,19 @@ public:
/*! @brief Default move assignment operator. @return This sparse set. */
SparseSet & operator=(SparseSet &&) = default;
/**
* @brief Increases the capacity of a sparse set.
*
* If the new capacity is greater than the current capacity, new storage is
* allocated, otherwise the method does nothing.
*
* @param cap Desired capacity.
*/
void reserve(size_type cap) {
reverse.reserve(cap);
direct.reserve(cap);
}
/**
* @brief Returns the number of elements in a sparse set.
*
@@ -293,7 +306,9 @@ public:
void swap(pos_type lhs, pos_type rhs) noexcept {
assert(lhs < direct.size());
assert(rhs < direct.size());
std::swap(reverse[direct[lhs]], reverse[direct[rhs]]);
const auto src = direct[lhs] & traits_type::entity_mask;
const auto dst = direct[rhs] & traits_type::entity_mask;
std::swap(reverse[src], reverse[dst]);
std::swap(direct[lhs], direct[rhs]);
}
@@ -400,6 +415,19 @@ public:
/*! @brief Default move assignment operator. @return This sparse set. */
SparseSet & operator=(SparseSet &&) = default;
/**
* @brief Increases the capacity of a sparse set.
*
* If the new capacity is greater than the current capacity, new storage is
* allocated, otherwise the method does nothing.
*
* @param cap Desired capacity.
*/
void reserve(size_type cap) {
underlying_type::reserve(cap);
instances.reserve(cap);
}
/**
* @brief Direct access to the array of objects.
*
@@ -541,8 +569,8 @@ public:
return compare(const_cast<const object_type &>(instances[rhs]), const_cast<const object_type &>(instances[lhs]));
});
for(pos_type i = 0; i < copy.size(); ++i) {
auto curr = i;
for(pos_type pos = 0, last = copy.size(); pos < last; ++pos) {
auto curr = pos;
auto next = copy[curr];
while(curr != next) {

View File

@@ -10,28 +10,6 @@
namespace entt {
namespace {
struct BaseProcess {
enum class State: unsigned int {
UNINITIALIZED = 0,
RUNNING,
PAUSED,
SUCCEEDED,
FAILED,
ABORTED,
FINISHED
};
template<State state>
using tag = std::integral_constant<State, state>;
};
}
/**
* @brief Base class for processes.
*
@@ -82,17 +60,30 @@ struct BaseProcess {
* @tparam Delta Type to use to provide elapsed time.
*/
template<typename Derived, typename Delta>
class Process: private BaseProcess {
class Process {
enum class State: unsigned int {
UNINITIALIZED = 0,
RUNNING,
PAUSED,
SUCCEEDED,
FAILED,
ABORTED,
FINISHED
};
template<State state>
using tag = std::integral_constant<State, state>;
template<typename Target = Derived>
auto tick(int, tag<State::UNINITIALIZED>)
-> decltype(std::declval<Target>().init()) {
static_cast<Target *>(this)->init();
auto tick(int, tag<State::UNINITIALIZED>, void *data)
-> decltype(std::declval<Target>().init(data)) {
static_cast<Target *>(this)->init(data);
}
template<typename Target = Derived>
auto tick(int, tag<State::RUNNING>, Delta delta)
-> decltype(std::declval<Target>().update(delta)) {
static_cast<Target *>(this)->update(delta);
auto tick(int, tag<State::RUNNING>, Delta delta, void *data)
-> decltype(std::declval<Target>().update(delta, data)) {
static_cast<Target *>(this)->update(delta, data);
}
template<typename Target = Derived>
@@ -227,15 +218,16 @@ public:
/**
* @brief Updates a process and its internal state if required.
* @param delta Elapsed time.
* @param data Optional data.
*/
void tick(Delta delta) {
void tick(Delta delta, void *data = nullptr) {
switch (current) {
case State::UNINITIALIZED:
tick(0, tag<State::UNINITIALIZED>{});
tick(0, tag<State::UNINITIALIZED>{}, data);
current = State::RUNNING;
// no break on purpose, tasks are executed immediately
case State::RUNNING:
tick(0, tag<State::RUNNING>{}, delta);
tick(0, tag<State::RUNNING>{}, delta, data);
default:
// suppress warnings
break;
@@ -322,9 +314,10 @@ struct ProcessAdaptor: Process<ProcessAdaptor<Func, Delta>, Delta>, private Func
/**
* @brief Updates a process and its internal state if required.
* @param delta Elapsed time.
* @param data Optional data.
*/
void update(Delta delta) {
Func::operator()(delta, [this](){ this->succeed(); }, [this](){ this->fail(); });
void update(Delta delta, void *data) {
Func::operator()(delta, data, [this](){ this->succeed(); }, [this](){ this->fail(); });
}
};

View File

@@ -47,7 +47,7 @@ class Scheduler final {
struct ProcessHandler final {
using instance_type = std::unique_ptr<void, void(*)(void *)>;
using update_type = bool(*)(ProcessHandler &, Delta);
using update_type = bool(*)(ProcessHandler &, Delta, void *);
using abort_type = void(*)(ProcessHandler &, bool);
using next_type = std::unique_ptr<ProcessHandler>;
@@ -81,16 +81,16 @@ class Scheduler final {
};
template<typename Proc>
static bool update(ProcessHandler &handler, Delta delta) {
static bool update(ProcessHandler &handler, Delta delta, void *data) {
auto *process = static_cast<Proc *>(handler.instance.get());
process->tick(delta);
process->tick(delta, data);
auto dead = process->dead();
if(dead) {
if(handler.next && !process->rejected()) {
handler = std::move(*handler.next);
dead = handler.update(handler, delta);
dead = handler.update(handler, delta, data);
} else {
handler.instance.reset();
}
@@ -269,13 +269,14 @@ public:
* with its child.
*
* @param delta Elapsed time.
* @param data Optional data.
*/
void update(Delta delta) {
void update(Delta delta, void *data = nullptr) {
bool clean = false;
for(auto i = handlers.size(); i > 0; --i) {
auto &handler = handlers[i-1];
const bool dead = handler.update(handler, delta);
for(auto pos = handlers.size(); pos > 0; --pos) {
auto &handler = handlers[pos-1];
const bool dead = handler.update(handler, delta, data);
clean = clean || dead;
}

View File

@@ -74,7 +74,7 @@ public:
}
/**
* @brief Loads the resource that corresponds to the given identifier.
* @brief Loads the resource that corresponds to a given identifier.
*
* In case an identifier isn't already present in the cache, it loads its
* resource and stores it aside for future uses. Arguments are forwarded
@@ -130,7 +130,24 @@ public:
}
/**
* @brief Creates a handle for the given resource identifier.
* @brief Creates a temporary handle for a resource.
*
* Arguments are forwarded directly to the loader in order to construct
* properly the requested resource. The handle isn't stored aside and the
* cache isn't in charge of the lifetime of the resource itself.
*
* @tparam Loader Type of loader to use to load the resource.
* @tparam Args Types of arguments to use to load the resource.
* @param args Arguments to use to load the resource.
* @return A handle for the given resource.
*/
template<typename Loader, typename... Args>
ResourceHandle<Resource> temp(Args&&... args) const {
return { Loader{}.get(std::forward<Args>(args)...) };
}
/**
* @brief Creates a handle for a given resource identifier.
*
* A resource handle can be in a either valid or invalid state. In other
* terms, a resource handle is properly initialized with a resource if the
@@ -148,7 +165,7 @@ public:
}
/**
* @brief Checks if a cache contains the given identifier.
* @brief Checks if a cache contains a given identifier.
* @param id Unique resource identifier.
* @return True if the cache contains the resource, false otherwise.
*/
@@ -157,7 +174,7 @@ public:
}
/**
* @brief Discards the resource that corresponds to the given identifier.
* @brief Discards the resource that corresponds to a given identifier.
*
* Handles are not invalidated and the memory used by the resource isn't
* freed as long as at least a handle keeps the resource itself alive.

View File

@@ -25,14 +25,14 @@ struct Invoker<Ret(Args...), Collector> {
virtual ~Invoker() = default;
template<typename SFINAE = Ret>
typename std::enable_if<std::is_void<SFINAE>::value, bool>::type
typename std::enable_if_t<std::is_void<SFINAE>::value, bool>
invoke(Collector &, proto_type proto, void *instance, Args... args) {
proto(instance, args...);
return true;
}
template<typename SFINAE = Ret>
typename std::enable_if<!std::is_void<SFINAE>::value, bool>::type
typename std::enable_if_t<!std::is_void<SFINAE>::value, bool>
invoke(Collector &collector, proto_type proto, void *instance, Args... args) {
return collector(proto(instance, args...));
}

View File

@@ -6,6 +6,7 @@ TEST(DefaultRegistry, Functionalities) {
entt::DefaultRegistry registry;
ASSERT_EQ(registry.size(), entt::DefaultRegistry::size_type{0});
ASSERT_NO_THROW(registry.reserve(42));
ASSERT_TRUE(registry.empty());
ASSERT_EQ(registry.capacity(), entt::DefaultRegistry::size_type{0});

View File

@@ -4,6 +4,7 @@
TEST(SparseSetNoType, Functionalities) {
entt::SparseSet<unsigned int> set;
ASSERT_NO_THROW(set.reserve(42));
ASSERT_TRUE(set.empty());
ASSERT_EQ(set.size(), 0u);
ASSERT_EQ(set.begin(), set.end());
@@ -79,6 +80,7 @@ TEST(SparseSetWithType, AggregatesMustWork) {
TEST(SparseSetWithType, Functionalities) {
entt::SparseSet<unsigned int, int> set;
ASSERT_NO_THROW(set.reserve(42));
ASSERT_TRUE(set.empty());
ASSERT_EQ(set.size(), 0u);
ASSERT_EQ(set.begin(), set.end());

View File

@@ -10,12 +10,19 @@ struct FakeProcess: entt::Process<FakeProcess, int> {
void pause() noexcept { process_type::pause(); }
void unpause() noexcept { process_type::unpause(); }
void init() { initInvoked = true; }
void update(delta_type) { updateInvoked = true; }
void init(void *) { initInvoked = true; }
void succeeded() { succeededInvoked = true; }
void failed() { failedInvoked = true; }
void aborted() { abortedInvoked = true; }
void update(delta_type, void *data) {
if(data) {
(*static_cast<int *>(data))++;
}
updateInvoked = true;
}
bool initInvoked{false};
bool updateInvoked{false};
bool succeededInvoked{false};
@@ -95,6 +102,26 @@ TEST(Process, Fail) {
ASSERT_FALSE(process.abortedInvoked);
}
TEST(Process, Data) {
FakeProcess process;
int value = 0;
process.tick(0, &value);
process.succeed();
process.tick(0, &value);
ASSERT_FALSE(process.alive());
ASSERT_TRUE(process.dead());
ASSERT_FALSE(process.paused());
ASSERT_EQ(value, 1);
ASSERT_TRUE(process.initInvoked);
ASSERT_TRUE(process.updateInvoked);
ASSERT_TRUE(process.succeededInvoked);
ASSERT_FALSE(process.failedInvoked);
ASSERT_FALSE(process.abortedInvoked);
}
TEST(Process, AbortNextTick) {
FakeProcess process;
@@ -132,7 +159,7 @@ TEST(Process, AbortImmediately) {
TEST(ProcessAdaptor, Resolved) {
bool updated = false;
auto lambda = [&updated](std::uint64_t, auto resolve, auto) {
auto lambda = [&updated](std::uint64_t, void *, auto resolve, auto) {
ASSERT_FALSE(updated);
updated = true;
resolve();
@@ -148,7 +175,7 @@ TEST(ProcessAdaptor, Resolved) {
TEST(ProcessAdaptor, Rejected) {
bool updated = false;
auto lambda = [&updated](std::uint64_t, auto, auto rejected) {
auto lambda = [&updated](std::uint64_t, void *, auto, auto rejected) {
ASSERT_FALSE(updated);
updated = true;
rejected();
@@ -161,3 +188,19 @@ TEST(ProcessAdaptor, Rejected) {
ASSERT_TRUE(process.rejected());
ASSERT_TRUE(updated);
}
TEST(ProcessAdaptor, Data) {
int value = 0;
auto lambda = [](std::uint64_t, void *data, auto resolve, auto) {
*static_cast<int *>(data) = 42;
resolve();
};
auto process = entt::ProcessAdaptor<decltype(lambda), std::uint64_t>{lambda};
process.tick(0, &value);
ASSERT_TRUE(process.dead());
ASSERT_EQ(value, 42);
}

View File

@@ -8,7 +8,7 @@ struct FooProcess: entt::Process<FooProcess, int> {
: onUpdate{onUpdate}, onAborted{onAborted}
{}
void update(delta_type) { onUpdate(); }
void update(delta_type, void *) { onUpdate(); }
void aborted() { onAborted(); }
std::function<void()> onUpdate;
@@ -16,7 +16,7 @@ struct FooProcess: entt::Process<FooProcess, int> {
};
struct SucceededProcess: entt::Process<SucceededProcess, int> {
void update(delta_type) {
void update(delta_type, void *) {
ASSERT_FALSE(updated);
updated = true;
++invoked;
@@ -30,7 +30,7 @@ struct SucceededProcess: entt::Process<SucceededProcess, int> {
unsigned int SucceededProcess::invoked = 0;
struct FailedProcess: entt::Process<FailedProcess, int> {
void update(delta_type) {
void update(delta_type, void *) {
ASSERT_FALSE(updated);
updated = true;
fail();
@@ -92,11 +92,11 @@ TEST(Scheduler, Functor) {
bool firstFunctor = false;
bool secondFunctor = false;
scheduler.attach([&firstFunctor](auto, auto resolve, auto){
scheduler.attach([&firstFunctor](auto, void *, auto resolve, auto){
ASSERT_FALSE(firstFunctor);
firstFunctor = true;
resolve();
}).then([&secondFunctor](auto, auto, auto reject){
}).then([&secondFunctor](auto, void *, auto, auto reject){
ASSERT_FALSE(secondFunctor);
secondFunctor = true;
reject();

View File

@@ -77,4 +77,7 @@ TEST(ResourceCache, Functionalities) {
ASSERT_EQ(cache.size(), entt::ResourceCache<Resource>::size_type{});
ASSERT_TRUE(cache.empty());
ASSERT_TRUE(cache.temp<Loader>(42));
ASSERT_TRUE(cache.empty());
}