diff --git a/Makefile b/Makefile index ca4ba17..a75c060 100644 --- a/Makefile +++ b/Makefile @@ -37,7 +37,7 @@ tracy_build: meson compile -j 10 -C builddir test: asset_build build - ./builddir/runtests + ./builddir/runtests "[collision]" run: build test ifeq '$(OS)' 'Windows_NT' @@ -60,7 +60,7 @@ clean: meson compile --clean -C builddir debug_test: build - gdb --nx -x .gdbinit --ex run --args builddir/runtests -e + gdb --nx -x .gdbinit --ex run --args builddir/runtests -e "[collision]" win_installer: powershell 'start "C:\Program Files (x86)\solicus\InstallForge\bin\ifbuilderenvx86.exe" scripts\win_installer.ifp' diff --git a/dinkyecs.hpp b/dinkyecs.hpp index 0b9f340..4e3b4b6 100644 --- a/dinkyecs.hpp +++ b/dinkyecs.hpp @@ -14,6 +14,8 @@ namespace DinkyECS { using Entity = unsigned long; + const Entity NONE = 0; + using EntityMap = std::unordered_map; template @@ -30,7 +32,7 @@ namespace DinkyECS typedef std::queue EventQueue; struct World { - unsigned long entity_count = 0; + unsigned long entity_count = NONE+1; std::unordered_map $components; std::unordered_map $facts; std::unordered_map $events; diff --git a/spatialmap.cpp b/spatialmap.cpp index ab6fff9..224b9f0 100644 --- a/spatialmap.cpp +++ b/spatialmap.cpp @@ -6,46 +6,49 @@ using namespace fmt; using DinkyECS::Entity; void SpatialMap::insert(Point pos, Entity ent, bool has_collision) { - if(has_collision) { - dbc::check(!yes_collision.contains(pos), "YES_collision already has entity"); - yes_collision.insert_or_assign(pos, ent); - } else { - dbc::check(!no_collision.contains(pos), "no_collision already has entity"); - no_collision.insert_or_assign(pos, ent); - } + dbc::check(!occupied(pos), "attempt to insert an entity with collision in space with collision"); + + $collision.emplace(pos, CollisionData{ent, has_collision}); } -bool SpatialMap::remove(Point pos) { - if(yes_collision.contains(pos)) { - yes_collision.erase(pos); - return true; - } else { - dbc::check(no_collision.contains(pos), "remove of entity that's not in no_collision"); - no_collision.erase(pos); - return false; +CollisionData SpatialMap::remove(Point pos, Entity ent) { + auto [begin, end] = $collision.equal_range(pos); + for(auto it = begin; it != end; ++it) { + if(it->second.entity == ent) { + // does the it->second go invalid after erase? + auto copy = it->second; + $collision.erase(it); + return copy; + } } + + dbc::sentinel("failed to find entity to remove"); } void SpatialMap::move(Point from, Point to, Entity ent) { - dbc::check(!occupied(to), "attempt to move to point with an existing entity"); - bool has_collision = remove(from); - insert(to, ent, has_collision); + auto data = remove(from, ent); + insert(to, ent, data.collision); } bool SpatialMap::occupied(Point at) const { - return yes_collision.contains(at); + auto [begin, end] = $collision.equal_range(at); + for(auto it = begin; it != end; ++it) { + if(it->second.collision) { + return true; + } + } + + return false; } bool SpatialMap::something_there(Point at) const { - return yes_collision.contains(at) || no_collision.contains(at); + return $collision.count(at) > 0; } Entity SpatialMap::get(Point at) const { - if(yes_collision.contains(at)) { - return yes_collision.at(at); - } else { - return no_collision.at(at); - } + dbc::check($collision.contains(at), "attempt to get entity when none there"); + auto [begin, end] = $collision.equal_range(at); + return begin->second.entity; } /* @@ -63,7 +66,7 @@ inline void find_neighbor(const PointEntityMap &table, EntityList &result, Point auto it = table.find(cell); if (it != table.end()) { - result.insert(result.end(), it->second); + result.insert(result.end(), it->second.entity); } } @@ -72,16 +75,16 @@ FoundEntities SpatialMap::neighbors(Point cell, bool diag) const { // just unroll the loop since we only check four directions // this also solves the problem that it was detecting that the cell was automatically included as a "neighbor" but it's not - find_neighbor(yes_collision, result, cell, 0, 1); // north - find_neighbor(yes_collision, result, cell, 0, -1); // south - find_neighbor(yes_collision, result, cell, 1, 0); // east - find_neighbor(yes_collision, result, cell, -1, 0); // west + find_neighbor($collision, result, cell, 0, 1); // north + find_neighbor($collision, result, cell, 0, -1); // south + find_neighbor($collision, result, cell, 1, 0); // east + find_neighbor($collision, result, cell, -1, 0); // west if(diag) { - find_neighbor(yes_collision, result, cell, 1, -1); // south east - find_neighbor(yes_collision, result, cell, -1, -1); // south west - find_neighbor(yes_collision, result, cell, 1, 1); // north east - find_neighbor(yes_collision, result, cell, -1, 1); // north west + find_neighbor($collision, result, cell, 1, -1); // south east + find_neighbor($collision, result, cell, -1, -1); // south west + find_neighbor($collision, result, cell, 1, 1); // north east + find_neighbor($collision, result, cell, -1, 1); // north west } return {!result.empty(), result}; @@ -94,7 +97,7 @@ inline void update_sorted(SortedEntities& sprite_distance, PointEntityMap& table (from.y - sprite.y) * (from.y - sprite.y); if(inside < max_dist) { - sprite_distance.push_back({inside, rec.second}); + sprite_distance.push_back({inside, rec.second.entity}); } } } @@ -102,8 +105,7 @@ inline void update_sorted(SortedEntities& sprite_distance, PointEntityMap& table SortedEntities SpatialMap::distance_sorted(Point from, int max_dist) { SortedEntities sprite_distance; - update_sorted(sprite_distance, yes_collision, from, max_dist); - update_sorted(sprite_distance, no_collision, from, max_dist); + update_sorted(sprite_distance, $collision, from, max_dist); std::sort(sprite_distance.begin(), sprite_distance.end(), std::greater<>()); diff --git a/spatialmap.hpp b/spatialmap.hpp index db975cb..9805ba8 100644 --- a/spatialmap.hpp +++ b/spatialmap.hpp @@ -5,10 +5,14 @@ #include "dinkyecs.hpp" #include "point.hpp" -typedef std::vector EntityList; +struct CollisionData { + DinkyECS::Entity entity = DinkyECS::NONE; + bool collision = false; +}; // Point's has is in point.hpp -using PointEntityMap = std::unordered_map; +using EntityList = std::vector; +using PointEntityMap = std::unordered_multimap; using SortedEntities = std::vector>; struct FoundEntities { @@ -19,18 +23,17 @@ struct FoundEntities { class SpatialMap { public: SpatialMap() {} - PointEntityMap yes_collision; - PointEntityMap no_collision; + PointEntityMap $collision; void insert(Point pos, DinkyECS::Entity obj, bool has_collision); void move(Point from, Point to, DinkyECS::Entity ent); // return value is whether the removed thing has collision - bool remove(Point pos); + CollisionData remove(Point pos, DinkyECS::Entity entity); bool occupied(Point pos) const; bool something_there(Point at) const; DinkyECS::Entity get(Point at) const; FoundEntities neighbors(Point position, bool diag=false) const; SortedEntities distance_sorted(Point from, int max_distance); - size_t size() { return yes_collision.size(); } + size_t size() { return $collision.size(); } }; diff --git a/systems.cpp b/systems.cpp index d78ac34..267eebe 100644 --- a/systems.cpp +++ b/systems.cpp @@ -214,7 +214,7 @@ void System::death(GameLevel &level) { auto pos = world.get(ent); // need to remove _after_ getting the position - level.collision->remove(pos.location); + level.collision->remove(pos.location, ent); // distribute_loot is then responsible for putting something there System::distribute_loot(level, pos); @@ -327,7 +327,7 @@ void System::collision(GameLevel &level) { */ void System::remove_from_world(GameLevel &level, Entity entity) { auto& item_pos = level.world->get(entity); - level.collision->remove(item_pos.location); + level.collision->remove(item_pos.location, entity); level.world->remove(entity); // if you don't do this you get the bug that you can pickup // an item and it'll also be in your inventory diff --git a/tests/spatialmap.cpp b/tests/spatialmap.cpp index ee00d9e..5dcff59 100644 --- a/tests/spatialmap.cpp +++ b/tests/spatialmap.cpp @@ -37,7 +37,7 @@ TEST_CASE("confirm basic collision operations", "[collision]") { REQUIRE(nearby[0] == player); { // removed - collider.remove({11,11}); + collider.remove({11,11}, player); auto [found, nearby] = collider.neighbors({10,10}, true); REQUIRE(!found); REQUIRE(nearby.empty()); @@ -141,7 +141,7 @@ TEST_CASE("check all diagonal works", "[collision]") { } } -TEST_CASE("confirm can iterate through all", "[spatialmap-sort]") { +TEST_CASE("confirm can iterate through all", "[collision]") { DinkyECS::World world; SpatialMap collider; Point player{10,10}; @@ -154,7 +154,9 @@ TEST_CASE("confirm can iterate through all", "[spatialmap-sort]") { size_t y = Random::uniform(0, 251); Entity ent = world.entity(); - collider.insert({x,y}, ent, true); + if(!collider.occupied({x, y})) { + collider.insert({x,y}, ent, true); + } } auto sprite_distance = collider.distance_sorted(player, 1000);