SpatialMap now uses unordered_multimap to allow for multiple items in a square, but they're also tagged to mark some with collision.
This commit is contained in:
parent
b193bab148
commit
f26189c696
6 changed files with 60 additions and 51 deletions
4
Makefile
4
Makefile
|
@ -37,7 +37,7 @@ tracy_build:
|
||||||
meson compile -j 10 -C builddir
|
meson compile -j 10 -C builddir
|
||||||
|
|
||||||
test: asset_build build
|
test: asset_build build
|
||||||
./builddir/runtests
|
./builddir/runtests "[collision]"
|
||||||
|
|
||||||
run: build test
|
run: build test
|
||||||
ifeq '$(OS)' 'Windows_NT'
|
ifeq '$(OS)' 'Windows_NT'
|
||||||
|
@ -60,7 +60,7 @@ clean:
|
||||||
meson compile --clean -C builddir
|
meson compile --clean -C builddir
|
||||||
|
|
||||||
debug_test: build
|
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:
|
win_installer:
|
||||||
powershell 'start "C:\Program Files (x86)\solicus\InstallForge\bin\ifbuilderenvx86.exe" scripts\win_installer.ifp'
|
powershell 'start "C:\Program Files (x86)\solicus\InstallForge\bin\ifbuilderenvx86.exe" scripts\win_installer.ifp'
|
||||||
|
|
|
@ -14,6 +14,8 @@ namespace DinkyECS
|
||||||
{
|
{
|
||||||
using Entity = unsigned long;
|
using Entity = unsigned long;
|
||||||
|
|
||||||
|
const Entity NONE = 0;
|
||||||
|
|
||||||
using EntityMap = std::unordered_map<Entity, size_t>;
|
using EntityMap = std::unordered_map<Entity, size_t>;
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
|
@ -30,7 +32,7 @@ namespace DinkyECS
|
||||||
typedef std::queue<Event> EventQueue;
|
typedef std::queue<Event> EventQueue;
|
||||||
|
|
||||||
struct World {
|
struct World {
|
||||||
unsigned long entity_count = 0;
|
unsigned long entity_count = NONE+1;
|
||||||
std::unordered_map<std::type_index, EntityMap> $components;
|
std::unordered_map<std::type_index, EntityMap> $components;
|
||||||
std::unordered_map<std::type_index, std::any> $facts;
|
std::unordered_map<std::type_index, std::any> $facts;
|
||||||
std::unordered_map<std::type_index, EventQueue> $events;
|
std::unordered_map<std::type_index, EventQueue> $events;
|
||||||
|
|
|
@ -6,46 +6,49 @@ using namespace fmt;
|
||||||
using DinkyECS::Entity;
|
using DinkyECS::Entity;
|
||||||
|
|
||||||
void SpatialMap::insert(Point pos, Entity ent, bool has_collision) {
|
void SpatialMap::insert(Point pos, Entity ent, bool has_collision) {
|
||||||
if(has_collision) {
|
dbc::check(!occupied(pos), "attempt to insert an entity with collision in space with collision");
|
||||||
dbc::check(!yes_collision.contains(pos), "YES_collision already has entity");
|
|
||||||
yes_collision.insert_or_assign(pos, ent);
|
$collision.emplace(pos, CollisionData{ent, has_collision});
|
||||||
} else {
|
|
||||||
dbc::check(!no_collision.contains(pos), "no_collision already has entity");
|
|
||||||
no_collision.insert_or_assign(pos, ent);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SpatialMap::remove(Point pos) {
|
CollisionData SpatialMap::remove(Point pos, Entity ent) {
|
||||||
if(yes_collision.contains(pos)) {
|
auto [begin, end] = $collision.equal_range(pos);
|
||||||
yes_collision.erase(pos);
|
for(auto it = begin; it != end; ++it) {
|
||||||
return true;
|
if(it->second.entity == ent) {
|
||||||
} else {
|
// does the it->second go invalid after erase?
|
||||||
dbc::check(no_collision.contains(pos), "remove of entity that's not in no_collision");
|
auto copy = it->second;
|
||||||
no_collision.erase(pos);
|
$collision.erase(it);
|
||||||
return false;
|
return copy;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dbc::sentinel("failed to find entity to remove");
|
||||||
}
|
}
|
||||||
|
|
||||||
void SpatialMap::move(Point from, Point to, Entity ent) {
|
void SpatialMap::move(Point from, Point to, Entity ent) {
|
||||||
dbc::check(!occupied(to), "attempt to move to point with an existing entity");
|
auto data = remove(from, ent);
|
||||||
bool has_collision = remove(from);
|
insert(to, ent, data.collision);
|
||||||
insert(to, ent, has_collision);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SpatialMap::occupied(Point at) const {
|
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 {
|
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 {
|
Entity SpatialMap::get(Point at) const {
|
||||||
if(yes_collision.contains(at)) {
|
dbc::check($collision.contains(at), "attempt to get entity when none there");
|
||||||
return yes_collision.at(at);
|
auto [begin, end] = $collision.equal_range(at);
|
||||||
} else {
|
return begin->second.entity;
|
||||||
return no_collision.at(at);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -63,7 +66,7 @@ inline void find_neighbor(const PointEntityMap &table, EntityList &result, Point
|
||||||
|
|
||||||
auto it = table.find(cell);
|
auto it = table.find(cell);
|
||||||
if (it != table.end()) {
|
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
|
// 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
|
// 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($collision, result, cell, 0, 1); // north
|
||||||
find_neighbor(yes_collision, result, cell, 0, -1); // south
|
find_neighbor($collision, result, cell, 0, -1); // south
|
||||||
find_neighbor(yes_collision, result, cell, 1, 0); // east
|
find_neighbor($collision, result, cell, 1, 0); // east
|
||||||
find_neighbor(yes_collision, result, cell, -1, 0); // west
|
find_neighbor($collision, result, cell, -1, 0); // west
|
||||||
|
|
||||||
if(diag) {
|
if(diag) {
|
||||||
find_neighbor(yes_collision, result, cell, 1, -1); // south east
|
find_neighbor($collision, result, cell, 1, -1); // south east
|
||||||
find_neighbor(yes_collision, result, cell, -1, -1); // south west
|
find_neighbor($collision, result, cell, -1, -1); // south west
|
||||||
find_neighbor(yes_collision, result, cell, 1, 1); // north east
|
find_neighbor($collision, result, cell, 1, 1); // north east
|
||||||
find_neighbor(yes_collision, result, cell, -1, 1); // north west
|
find_neighbor($collision, result, cell, -1, 1); // north west
|
||||||
}
|
}
|
||||||
|
|
||||||
return {!result.empty(), result};
|
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);
|
(from.y - sprite.y) * (from.y - sprite.y);
|
||||||
|
|
||||||
if(inside < max_dist) {
|
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 SpatialMap::distance_sorted(Point from, int max_dist) {
|
||||||
SortedEntities sprite_distance;
|
SortedEntities sprite_distance;
|
||||||
|
|
||||||
update_sorted(sprite_distance, yes_collision, from, max_dist);
|
update_sorted(sprite_distance, $collision, from, max_dist);
|
||||||
update_sorted(sprite_distance, no_collision, from, max_dist);
|
|
||||||
|
|
||||||
std::sort(sprite_distance.begin(), sprite_distance.end(), std::greater<>());
|
std::sort(sprite_distance.begin(), sprite_distance.end(), std::greater<>());
|
||||||
|
|
||||||
|
|
|
@ -5,10 +5,14 @@
|
||||||
#include "dinkyecs.hpp"
|
#include "dinkyecs.hpp"
|
||||||
#include "point.hpp"
|
#include "point.hpp"
|
||||||
|
|
||||||
typedef std::vector<DinkyECS::Entity> EntityList;
|
struct CollisionData {
|
||||||
|
DinkyECS::Entity entity = DinkyECS::NONE;
|
||||||
|
bool collision = false;
|
||||||
|
};
|
||||||
|
|
||||||
// Point's has is in point.hpp
|
// Point's has is in point.hpp
|
||||||
using PointEntityMap = std::unordered_map<Point, DinkyECS::Entity>;
|
using EntityList = std::vector<DinkyECS::Entity>;
|
||||||
|
using PointEntityMap = std::unordered_multimap<Point, CollisionData>;
|
||||||
using SortedEntities = std::vector<std::pair<int, DinkyECS::Entity>>;
|
using SortedEntities = std::vector<std::pair<int, DinkyECS::Entity>>;
|
||||||
|
|
||||||
struct FoundEntities {
|
struct FoundEntities {
|
||||||
|
@ -19,18 +23,17 @@ struct FoundEntities {
|
||||||
class SpatialMap {
|
class SpatialMap {
|
||||||
public:
|
public:
|
||||||
SpatialMap() {}
|
SpatialMap() {}
|
||||||
PointEntityMap yes_collision;
|
PointEntityMap $collision;
|
||||||
PointEntityMap no_collision;
|
|
||||||
|
|
||||||
void insert(Point pos, DinkyECS::Entity obj, bool has_collision);
|
void insert(Point pos, DinkyECS::Entity obj, bool has_collision);
|
||||||
void move(Point from, Point to, DinkyECS::Entity ent);
|
void move(Point from, Point to, DinkyECS::Entity ent);
|
||||||
// return value is whether the removed thing has collision
|
// 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 occupied(Point pos) const;
|
||||||
bool something_there(Point at) const;
|
bool something_there(Point at) const;
|
||||||
DinkyECS::Entity get(Point at) const;
|
DinkyECS::Entity get(Point at) const;
|
||||||
FoundEntities neighbors(Point position, bool diag=false) const;
|
FoundEntities neighbors(Point position, bool diag=false) const;
|
||||||
|
|
||||||
SortedEntities distance_sorted(Point from, int max_distance);
|
SortedEntities distance_sorted(Point from, int max_distance);
|
||||||
size_t size() { return yes_collision.size(); }
|
size_t size() { return $collision.size(); }
|
||||||
};
|
};
|
||||||
|
|
|
@ -214,7 +214,7 @@ void System::death(GameLevel &level) {
|
||||||
auto pos = world.get<Position>(ent);
|
auto pos = world.get<Position>(ent);
|
||||||
|
|
||||||
// need to remove _after_ getting the position
|
// 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
|
// distribute_loot is then responsible for putting something there
|
||||||
System::distribute_loot(level, pos);
|
System::distribute_loot(level, pos);
|
||||||
|
@ -327,7 +327,7 @@ void System::collision(GameLevel &level) {
|
||||||
*/
|
*/
|
||||||
void System::remove_from_world(GameLevel &level, Entity entity) {
|
void System::remove_from_world(GameLevel &level, Entity entity) {
|
||||||
auto& item_pos = level.world->get<Position>(entity);
|
auto& item_pos = level.world->get<Position>(entity);
|
||||||
level.collision->remove(item_pos.location);
|
level.collision->remove(item_pos.location, entity);
|
||||||
level.world->remove<Tile>(entity);
|
level.world->remove<Tile>(entity);
|
||||||
// if you don't do this you get the bug that you can pickup
|
// if you don't do this you get the bug that you can pickup
|
||||||
// an item and it'll also be in your inventory
|
// an item and it'll also be in your inventory
|
||||||
|
|
|
@ -37,7 +37,7 @@ TEST_CASE("confirm basic collision operations", "[collision]") {
|
||||||
REQUIRE(nearby[0] == player);
|
REQUIRE(nearby[0] == player);
|
||||||
|
|
||||||
{ // removed
|
{ // removed
|
||||||
collider.remove({11,11});
|
collider.remove({11,11}, player);
|
||||||
auto [found, nearby] = collider.neighbors({10,10}, true);
|
auto [found, nearby] = collider.neighbors({10,10}, true);
|
||||||
REQUIRE(!found);
|
REQUIRE(!found);
|
||||||
REQUIRE(nearby.empty());
|
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;
|
DinkyECS::World world;
|
||||||
SpatialMap collider;
|
SpatialMap collider;
|
||||||
Point player{10,10};
|
Point player{10,10};
|
||||||
|
@ -154,8 +154,10 @@ TEST_CASE("confirm can iterate through all", "[spatialmap-sort]") {
|
||||||
size_t y = Random::uniform<size_t>(0, 251);
|
size_t y = Random::uniform<size_t>(0, 251);
|
||||||
|
|
||||||
Entity ent = world.entity();
|
Entity ent = world.entity();
|
||||||
|
if(!collider.occupied({x, y})) {
|
||||||
collider.insert({x,y}, ent, true);
|
collider.insert({x,y}, ent, true);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auto sprite_distance = collider.distance_sorted(player, 1000);
|
auto sprite_distance = collider.distance_sorted(player, 1000);
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue