Skip to content

Commit

Permalink
refactor(ecs): use EntityHash instead of std::hash
Browse files Browse the repository at this point in the history
  • Loading branch information
RiscadoA committed Oct 2, 2023
1 parent a45a272 commit 663e864
Show file tree
Hide file tree
Showing 14 changed files with 107 additions and 68 deletions.
1 change: 1 addition & 0 deletions core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ set(CUBOS_CORE_SOURCE
"src/cubos/core/al/oal_audio_device.hpp"

"src/cubos/core/ecs/entity/entity.cpp"
"src/cubos/core/ecs/entity/hash.cpp"
"src/cubos/core/ecs/entity/manager.cpp"
"src/cubos/core/ecs/component_manager.cpp"
"src/cubos/core/ecs/commands.cpp"
Expand Down
10 changes: 6 additions & 4 deletions core/include/cubos/core/data/old/serialization_map.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ namespace cubos::core::data::old
/// Class used to map between references and their serialized identifiers.
/// @tparam R Reference type.
/// @tparam I Serialized identifier type.
template <typename R, typename I>
/// @tparam RH Reference hash type.
/// @tparam IH Identifier hash type.
template <typename R, typename I, typename RH = std::hash<R>, typename IH = std::hash<I>>
class SerializationMap final
{
public:
Expand Down Expand Up @@ -121,7 +123,7 @@ namespace cubos::core::data::old

/// @brief Returns the internal map that maps references to IDs
/// @return Map of references and Ids.
inline std::unordered_map<R, I> getMap() const
inline std::unordered_map<R, I, RH> getMap() const
{
return mRefToId;
}
Expand All @@ -130,7 +132,7 @@ namespace cubos::core::data::old
bool mUsingFunctions; ///< True if the map is using functions instead of keeping a map.
std::function<bool(const R&, I&)> mSerialize; ///< Function used to serialize references.
std::function<bool(R&, const I&)> mDeserialize; ///< Function used to deserialize references.
std::unordered_map<R, I> mRefToId; ///< Map of references to serialized IDs.
std::unordered_map<I, R> mIdToRef; ///< Map of serialized IDs to references.
std::unordered_map<R, I, RH> mRefToId; ///< Map of references to serialized IDs.
std::unordered_map<I, R, IH> mIdToRef; ///< Map of serialized IDs to references.
};
} // namespace cubos::core::data::old
7 changes: 4 additions & 3 deletions core/include/cubos/core/ecs/blueprint.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <cubos/core/data/old/binary_serializer.hpp>
#include <cubos/core/data/old/serialization_map.hpp>
#include <cubos/core/ecs/commands.hpp>
#include <cubos/core/ecs/entity/hash.hpp>
#include <cubos/core/memory/buffer_stream.hpp>
#include <cubos/core/memory/type_map.hpp>

Expand Down Expand Up @@ -64,7 +65,7 @@ namespace cubos::core::ecs

/// @brief Returns the internal map that maps entities to their names
/// @return Map of entities and names.
inline std::unordered_map<Entity, std::string> getMap() const
inline std::unordered_map<Entity, std::string, EntityHash> getMap() const
{
return mMap.getMap();
}
Expand Down Expand Up @@ -115,7 +116,7 @@ namespace cubos::core::ecs
auto des = data::old::BinaryDeserializer(this->stream);
des.context().pushSubContext(context);

auto& map = des.context().get<data::old::SerializationMap<Entity, std::string>>();
auto& map = des.context().get<data::old::SerializationMap<Entity, std::string, EntityHash>>();
for (const auto& name : this->names)
{
ComponentType type;
Expand Down Expand Up @@ -165,7 +166,7 @@ namespace cubos::core::ecs
};

/// @brief Stores the entity handles and the associated names.
data::old::SerializationMap<Entity, std::string> mMap;
data::old::SerializationMap<Entity, std::string, EntityHash> mMap;

/// @brief Buffers which store the serialized components of each type in this blueprint.
memory::TypeMap<IBuffer*> mBuffers;
Expand Down
22 changes: 12 additions & 10 deletions core/include/cubos/core/ecs/commands.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <unordered_map>
#include <unordered_set>

#include <cubos/core/ecs/entity/hash.hpp>
#include <cubos/core/ecs/world.hpp>

namespace cubos::core::ecs
Expand Down Expand Up @@ -91,13 +92,14 @@ namespace cubos::core::ecs
private:
friend CommandBuffer;

data::old::SerializationMap<Entity, std::string> mMap; ///< Maps entity names to the instantiated entities.
CommandBuffer& mCommands; ///< Commands object that created this entity.
data::old::SerializationMap<Entity, std::string, EntityHash>
mMap; ///< Maps entity names to the instantiated entities.
CommandBuffer& mCommands; ///< Commands object that created this entity.

/// @brief Constructs.
/// @param map Map of entity names to the instantiated entities.
/// @param commands Commands object that created this entity.
BlueprintBuilder(data::old::SerializationMap<Entity, std::string>&& map, CommandBuffer& commands);
BlueprintBuilder(data::old::SerializationMap<Entity, std::string, EntityHash>&& map, CommandBuffer& commands);
};

/// @brief Used to write ECS commands and execute them at a later time.
Expand Down Expand Up @@ -225,7 +227,7 @@ namespace cubos::core::ecs
void clear() override;
void move(Entity entity, ComponentManager& manager) override;

std::unordered_map<Entity, ComponentType> components; ///< Components in the buffer.
std::unordered_map<Entity, ComponentType, EntityHash> components; ///< Components in the buffer.
};

/// @brief Clears the commands.
Expand All @@ -234,12 +236,12 @@ namespace cubos::core::ecs
std::mutex mMutex; ///< Make this thread-safe.
World& mWorld; ///< World to which the commands will be applied.

std::unordered_map<std::type_index, IBuffer*> mBuffers; ///< Component buffers per component type.
std::unordered_set<Entity> mCreated; ///< Uncommitted created entities.
std::unordered_set<Entity> mDestroyed; ///< Uncommitted destroyed entities.
std::unordered_map<Entity, Entity::Mask> mAdded; ///< Mask of the uncommitted added components.
std::unordered_map<Entity, Entity::Mask> mRemoved; ///< Mask of the uncommitted removed components.
std::unordered_set<Entity> mChanged; ///< Entities whose mask has changed.
std::unordered_map<std::type_index, IBuffer*> mBuffers; ///< Component buffers per component type.
std::unordered_set<Entity, EntityHash> mCreated; ///< Uncommitted created entities.
std::unordered_set<Entity, EntityHash> mDestroyed; ///< Uncommitted destroyed entities.
std::unordered_map<Entity, Entity::Mask, EntityHash> mAdded; ///< Mask of the uncommitted added components.
std::unordered_map<Entity, Entity::Mask, EntityHash> mRemoved; ///< Mask of the uncommitted removed components.
std::unordered_set<Entity, EntityHash> mChanged; ///< Entities whose mask has changed.
};

// Implementation.
Expand Down
17 changes: 1 addition & 16 deletions core/include/cubos/core/ecs/entity/entity.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,13 @@

#include <bitset>
#include <cstdint>
#include <functional>

namespace cubos::core::ecs
{
/// @brief Identifies an entity.
///
/// When serializing/deserializing, if there's a
/// data::old::SerializationMap<Entity, std::string> in the context, it will be used to
/// data::old::SerializationMap<Entity, std::string, EntityHash> in the context, it will be used to
/// (de)serialize strings representing the entities. Otherwise, the identifiers will be
/// (de)serialized as objects with two fields: their index and their generation.
///
Expand Down Expand Up @@ -65,17 +64,3 @@ namespace cubos::core::ecs
uint32_t generation;
};
} // namespace cubos::core::ecs

namespace std
{
// Add hash function for Entity, so that it can be used as a key in an unordered_map.

template <>
struct hash<cubos::core::ecs::Entity>
{
inline std::size_t operator()(const cubos::core::ecs::Entity& k) const
{
return hash<uint32_t>()(k.index);
}
};
} // namespace std
22 changes: 22 additions & 0 deletions core/include/cubos/core/ecs/entity/hash.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/// @file
/// @brief Struct @ref cubos::core::ecs::EntityHash.
/// @ingroup core-ecs

#pragma once

#include <cstddef>

namespace cubos::core::ecs
{
struct Entity;

/// @brief Used to hash @ref Entity objects.
///
/// Can be used to allow @ref Entity objects to be used as keys in an `std::unordered_map`.
///
/// @ingroup core-ecs
struct EntityHash
{
std::size_t operator()(const Entity& entity) const noexcept;
};
} // namespace cubos::core::ecs
1 change: 1 addition & 0 deletions core/include/cubos/core/ecs/entity/manager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <vector>

#include <cubos/core/ecs/entity/entity.hpp>
#include <cubos/core/ecs/entity/hash.hpp>

namespace cubos::core::ecs
{
Expand Down
2 changes: 1 addition & 1 deletion core/src/cubos/core/ecs/blueprint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Entity Blueprint::entity(const std::string& name) const
void Blueprint::merge(const std::string& prefix, const Blueprint& other)
{
// First, merge the maps.
data::old::SerializationMap<Entity, std::string> srcMap;
data::old::SerializationMap<Entity, std::string, EntityHash> srcMap;
for (uint32_t i = 0; i < static_cast<uint32_t>(other.mMap.size()); ++i)
{
// Deserialize from the source buffer using the original entity names.
Expand Down
5 changes: 3 additions & 2 deletions core/src/cubos/core/ecs/commands.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ Entity EntityBuilder::entity() const
return mEntity;
}

BlueprintBuilder::BlueprintBuilder(data::old::SerializationMap<Entity, std::string>&& map, CommandBuffer& commands)
BlueprintBuilder::BlueprintBuilder(data::old::SerializationMap<Entity, std::string, EntityHash>&& map,
CommandBuffer& commands)
: mMap(std::move(map))
, mCommands(commands)
{
Expand Down Expand Up @@ -69,7 +70,7 @@ void CommandBuffer::destroy(Entity entity)

BlueprintBuilder CommandBuffer::spawn(const Blueprint& blueprint)
{
data::old::SerializationMap<Entity, std::string> map;
data::old::SerializationMap<Entity, std::string, EntityHash> map;
for (uint32_t i = 0; i < static_cast<uint32_t>(blueprint.mMap.size()); ++i)
{
map.add(this->create().entity(), blueprint.mMap.getId(Entity(i, 0)));
Expand Down
10 changes: 6 additions & 4 deletions core/src/cubos/core/ecs/entity/entity.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,23 @@
#include <cubos/core/data/old/serialization_map.hpp>
#include <cubos/core/data/old/serializer.hpp>
#include <cubos/core/ecs/entity/entity.hpp>
#include <cubos/core/ecs/entity/hash.hpp>

using cubos::core::ecs::Entity;
using cubos::core::ecs::EntityHash;

template <>
void cubos::core::data::old::serialize<Entity>(Serializer& ser, const Entity& obj, const char* name)
{
if (ser.context().has<SerializationMap<Entity, std::string>>())
if (ser.context().has<SerializationMap<Entity, std::string, EntityHash>>())
{
if (obj.isNull())
{
ser.write("null", name);
return;
}

auto& map = ser.context().get<SerializationMap<Entity, std::string>>();
auto& map = ser.context().get<SerializationMap<Entity, std::string, EntityHash>>();
ser.write(map.getId(obj), name);
}
else
Expand All @@ -31,7 +33,7 @@ void cubos::core::data::old::serialize<Entity>(Serializer& ser, const Entity& ob
template <>
void cubos::core::data::old::deserialize<Entity>(Deserializer& des, Entity& obj)
{
if (des.context().has<SerializationMap<Entity, std::string>>())
if (des.context().has<SerializationMap<Entity, std::string, EntityHash>>())
{
std::string name;
des.read(name);
Expand All @@ -41,7 +43,7 @@ void cubos::core::data::old::deserialize<Entity>(Deserializer& des, Entity& obj)
return;
}

auto& map = des.context().get<SerializationMap<Entity, std::string>>();
auto& map = des.context().get<SerializationMap<Entity, std::string, EntityHash>>();
if (map.hasId(name))
{
obj = map.getRef(name);
Expand Down
20 changes: 20 additions & 0 deletions core/src/cubos/core/ecs/entity/hash.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#include <cubos/core/ecs/entity/entity.hpp>
#include <cubos/core/ecs/entity/hash.hpp>

using cubos::core::ecs::EntityHash;

std::size_t EntityHash::operator()(const Entity& entity) const noexcept
{
// If std::size_t is 64 bits or more, we can fit both the index and generation in it.
// Otherwise, we'll just use the index as the hash, which means we'll always get collisions
// for entities with the same index but different generations. That shouldn't be a problem
// though, since that use case is very unlikely.

if constexpr (sizeof(std::size_t) < sizeof(uint32_t) * 2)
{
return static_cast<std::size_t>(entity.index);
}

return static_cast<std::size_t>(entity.index) |
(static_cast<std::size_t>(entity.generation) << (sizeof(uint32_t) * 8));
}
20 changes: 10 additions & 10 deletions engine/include/cubos/engine/collisions/broad_phase_collisions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,24 @@
#include <unordered_set>
#include <vector>

#include <cubos/core/ecs/entity/hash.hpp>
#include <cubos/core/ecs/entity/manager.hpp>

using cubos::core::ecs::Entity;

namespace cubos::engine
{
/// @brief Resource which stores data used in broad phase collision detection.
/// @ingroup collisions-plugin
struct BroadPhaseCollisions
{
/// @brief Pair of entities that may collide.
using Candidate = std::pair<Entity, Entity>;
using Candidate = std::pair<core::ecs::Entity, core::ecs::Entity>;

/// @brief Hash function to allow Candidates to be used as keys in an unordered_set.
struct CandidateHash
{
std::size_t operator()(const Candidate& candidate) const
{
return std::hash<Entity>()(candidate.first) ^ std::hash<Entity>()(candidate.second);
return core::ecs::EntityHash()(candidate.first) ^ core::ecs::EntityHash()(candidate.second);
}
};

Expand All @@ -50,8 +49,8 @@ namespace cubos::engine
/// @brief Marker used for sweep and prune.
struct SweepMarker
{
Entity entity; ///< Entity referenced by the marker.
bool isMin; ///< Whether the marker is a min or max marker.
core::ecs::Entity entity; ///< Entity referenced by the marker.
bool isMin; ///< Whether the marker is a min or max marker.
};

/// @brief List of ordered sweep markers for each axis. Stores the index of the marker in mMarkers.
Expand All @@ -61,22 +60,23 @@ namespace cubos::engine
///
/// For each each map, the key is an entity and the value is a list of entities that
/// overlap with the key. Symmetrical pairs are not stored.
std::unordered_map<Entity, std::vector<Entity>> sweepOverlapMaps[3];
std::unordered_map<core::ecs::Entity, std::vector<core::ecs::Entity>, core::ecs::EntityHash>
sweepOverlapMaps[3];

/// @brief Set of active entities during sweep for each axis.
std::unordered_set<Entity> activePerAxis[3];
std::unordered_set<core::ecs::Entity, core::ecs::EntityHash> activePerAxis[3];

/// @brief Sets of collision candidates for each collision type. The index of the array is
/// the collision type.
std::unordered_set<Candidate, CandidateHash> candidatesPerType[static_cast<std::size_t>(CollisionType::Count)];

/// @brief Adds an entity to the list of entities tracked by sweep and prune.
/// @param entity Entity to add.
void addEntity(Entity entity);
void addEntity(core::ecs::Entity entity);

/// @brief Removes an entity from the list of entities tracked by sweep and prune.
/// @param entity Entity to remove.
void removeEntity(Entity entity);
void removeEntity(core::ecs::Entity entity);

/// @brief Clears the list of entities tracked by sweep and prune.
void clearEntities();
Expand Down
9 changes: 3 additions & 6 deletions engine/src/cubos/engine/collisions/broad_phase_collisions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,9 @@

#include <cubos/engine/collisions/broad_phase_collisions.hpp>

using cubos::engine::BroadPhaseCollisions;
using cubos::core::ecs::Entity;

using Candidate = BroadPhaseCollisions::Candidate;
using CandidateHash = BroadPhaseCollisions::CandidateHash;
using CollisionType = BroadPhaseCollisions::CollisionType;
using SweepMarker = BroadPhaseCollisions::SweepMarker;
using cubos::engine::BroadPhaseCollisions;

void BroadPhaseCollisions::addEntity(Entity entity)
{
Expand Down Expand Up @@ -41,7 +38,7 @@ void BroadPhaseCollisions::addCandidate(CollisionType type, Candidate candidate)
candidatesPerType[static_cast<std::size_t>(type)].insert(candidate);
}

const std::unordered_set<Candidate, CandidateHash>& BroadPhaseCollisions::candidates(CollisionType type) const
auto BroadPhaseCollisions::candidates(CollisionType type) const -> const std::unordered_set<Candidate, CandidateHash>&
{
return candidatesPerType[static_cast<std::size_t>(type)];
}
Expand Down
Loading

0 comments on commit 663e864

Please sign in to comment.