From 2666a9937e1dffd3707842bfc8b64ec3b19a78e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=B6eh=20Matt?= <5415177+ZehMatt@users.noreply.github.com> Date: Fri, 27 Sep 2024 11:34:05 +0300 Subject: [PATCH 1/8] Refactor the spatial indexing into a separate phase --- src/openrct2/Context.cpp | 3 ++ src/openrct2/entity/EntityBase.h | 1 + src/openrct2/entity/EntityRegistry.cpp | 58 +++++++++++++++++++------- src/openrct2/entity/EntityRegistry.h | 1 + 4 files changed, 48 insertions(+), 15 deletions(-) diff --git a/src/openrct2/Context.cpp b/src/openrct2/Context.cpp index 9eff422afe..b1a46bb472 100644 --- a/src/openrct2/Context.cpp +++ b/src/openrct2/Context.cpp @@ -1322,6 +1322,7 @@ namespace OpenRCT2 if (ShouldDraw()) { + UpdateEntitiesSpatialIndex(); Draw(); } } @@ -1358,6 +1359,8 @@ namespace OpenRCT2 const float alpha = std::min(_ticksAccumulator / kGameUpdateTimeMS, 1.0f); tweener.Tween(alpha); + UpdateEntitiesSpatialIndex(); + Draw(); } } diff --git a/src/openrct2/entity/EntityBase.h b/src/openrct2/entity/EntityBase.h index 9c43108192..bee06c1021 100644 --- a/src/openrct2/entity/EntityBase.h +++ b/src/openrct2/entity/EntityBase.h @@ -44,6 +44,7 @@ struct EntityBase EntitySpriteData SpriteData; // Used as direction or rotation depending on the entity. uint8_t Orientation; + uint32_t SpatialIndex; /** * Moves a sprite to a new location, invalidates the current position if valid diff --git a/src/openrct2/entity/EntityRegistry.cpp b/src/openrct2/entity/EntityRegistry.cpp index 18ffc5b881..38303df7b8 100644 --- a/src/openrct2/entity/EntityRegistry.cpp +++ b/src/openrct2/entity/EntityRegistry.cpp @@ -48,12 +48,14 @@ static bool _entityFlashingList[MAX_ENTITIES]; constexpr const uint32_t SPATIAL_INDEX_SIZE = (kMaximumMapSizeTechnical * kMaximumMapSizeTechnical) + 1; constexpr uint32_t SPATIAL_INDEX_LOCATION_NULL = SPATIAL_INDEX_SIZE - 1; +static constexpr uint32_t kInvalidSpatialIndex = 0xFFFFFFFFu; +static constexpr uint32_t kSpatialIndexDirtyMask = 1u << 31; static std::array, SPATIAL_INDEX_SIZE> gEntitySpatialIndex; static void FreeEntity(EntityBase& entity); -static constexpr size_t GetSpatialIndexOffset(const CoordsXY& loc) +static constexpr uint32_t ComputeSpatialIndex(const CoordsXY& loc) { if (loc.IsNull()) return SPATIAL_INDEX_LOCATION_NULL; @@ -68,6 +70,11 @@ static constexpr size_t GetSpatialIndexOffset(const CoordsXY& loc) return tileX * kMaximumMapSizeTechnical + tileY; } +static constexpr uint32_t GetSpatialIndex(EntityBase* entity) +{ + return entity->SpatialIndex & ~kSpatialIndexDirtyMask; +} + constexpr bool EntityTypeIsMiscEntity(const EntityType type) { switch (type) @@ -121,7 +128,7 @@ EntityBase* GetEntity(EntityId entityIndex) const std::vector& GetEntityTileList(const CoordsXY& spritePos) { - return gEntitySpatialIndex[GetSpatialIndexOffset(spritePos)]; + return gEntitySpatialIndex[ComputeSpatialIndex(spritePos)]; } static void ResetEntityLists() @@ -204,10 +211,10 @@ void ResetEntitySpatialIndices() } for (EntityId::UnderlyingType i = 0; i < MAX_ENTITIES; i++) { - auto* spr = GetEntity(EntityId::FromUnderlying(i)); - if (spr != nullptr && spr->Type != EntityType::Null) + auto* entity = GetEntity(EntityId::FromUnderlying(i)); + if (entity != nullptr && entity->Type != EntityType::Null) { - EntitySpatialInsert(spr, { spr->x, spr->y }); + EntitySpatialInsert(entity, { entity->x, entity->y }); } } } @@ -312,6 +319,7 @@ static void PrepareNewEntity(EntityBase* base, const EntityType type) base->SpriteData.HeightMin = 0x14; base->SpriteData.HeightMax = 0x8; base->SpriteData.SpriteRect = {}; + base->SpatialIndex = kInvalidSpatialIndex; EntitySpatialInsert(base, { kLocationNull, 0 }); } @@ -405,15 +413,19 @@ void UpdateMoneyEffect() // Performs a search to ensure that insert keeps next_in_quadrant in sprite_index order static void EntitySpatialInsert(EntityBase* entity, const CoordsXY& newLoc) { - size_t newIndex = GetSpatialIndexOffset(newLoc); + const auto newIndex = ComputeSpatialIndex(newLoc); + auto& spatialVector = gEntitySpatialIndex[newIndex]; auto index = std::lower_bound(std::begin(spatialVector), std::end(spatialVector), entity->Id); spatialVector.insert(index, entity->Id); + + entity->SpatialIndex = newIndex; } static void EntitySpatialRemove(EntityBase* entity) { - size_t currentIndex = GetSpatialIndexOffset({ entity->x, entity->y }); + const auto currentIndex = GetSpatialIndex(entity); + auto& spatialVector = gEntitySpatialIndex[currentIndex]; auto index = BinaryFind(std::begin(spatialVector), std::end(spatialVector), entity->Id); if (index != std::end(spatialVector)) @@ -425,17 +437,28 @@ static void EntitySpatialRemove(EntityBase* entity) LOG_WARNING("Bad sprite spatial index. Rebuilding the spatial index..."); ResetEntitySpatialIndices(); } + + entity->SpatialIndex = kInvalidSpatialIndex; } -static void EntitySpatialMove(EntityBase* entity, const CoordsXY& newLoc) +void UpdateEntitiesSpatialIndex() { - size_t newIndex = GetSpatialIndexOffset(newLoc); - size_t currentIndex = GetSpatialIndexOffset({ entity->x, entity->y }); - if (newIndex == currentIndex) - return; + // TODO: This is not optimal, we should only iterate active entities. + for (EntityId::UnderlyingType i = 0; i < MAX_ENTITIES; i++) + { + auto* entity = GetEntity(EntityId::FromUnderlying(i)); + if (entity == nullptr || entity->Type == EntityType::Null) + continue; - EntitySpatialRemove(entity); - EntitySpatialInsert(entity, newLoc); + if (entity->SpatialIndex & kSpatialIndexDirtyMask) + { + if (entity->SpatialIndex != kInvalidSpatialIndex) + { + EntitySpatialRemove(entity); + } + EntitySpatialInsert(entity, { entity->x, entity->y }); + } + } } void EntityBase::MoveTo(const CoordsXYZ& newLocation) @@ -452,7 +475,12 @@ void EntityBase::MoveTo(const CoordsXYZ& newLocation) loc.x = kLocationNull; } - EntitySpatialMove(this, loc); + // EntitySpatialMove(this, loc); + const auto newSpatialIndex = ComputeSpatialIndex(loc); + if (newSpatialIndex != GetSpatialIndex(this)) + { + SpatialIndex |= kSpatialIndexDirtyMask; + } if (loc.x == kLocationNull) { diff --git a/src/openrct2/entity/EntityRegistry.h b/src/openrct2/entity/EntityRegistry.h index 68affac7e7..7fea52cd94 100644 --- a/src/openrct2/entity/EntityRegistry.h +++ b/src/openrct2/entity/EntityRegistry.h @@ -66,6 +66,7 @@ void UpdateMoneyEffect(); void EntitySetCoordinates(const CoordsXYZ& entityPos, EntityBase* entity); void EntityRemove(EntityBase* entity); uint16_t RemoveFloatingEntities(); +void UpdateEntitiesSpatialIndex(); #pragma pack(push, 1) struct EntitiesChecksum From dfb053e2864ae7f26053b3fda8d7e92647849909 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=B6eh=20Matt?= <5415177+ZehMatt@users.noreply.github.com> Date: Fri, 27 Sep 2024 11:40:18 +0300 Subject: [PATCH 2/8] Only iterate over active entities --- src/openrct2/entity/EntityRegistry.cpp | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/openrct2/entity/EntityRegistry.cpp b/src/openrct2/entity/EntityRegistry.cpp index 38303df7b8..37db189881 100644 --- a/src/openrct2/entity/EntityRegistry.cpp +++ b/src/openrct2/entity/EntityRegistry.cpp @@ -443,20 +443,22 @@ static void EntitySpatialRemove(EntityBase* entity) void UpdateEntitiesSpatialIndex() { - // TODO: This is not optimal, we should only iterate active entities. - for (EntityId::UnderlyingType i = 0; i < MAX_ENTITIES; i++) + for (auto& entityList : gEntityLists) { - auto* entity = GetEntity(EntityId::FromUnderlying(i)); - if (entity == nullptr || entity->Type == EntityType::Null) - continue; - - if (entity->SpatialIndex & kSpatialIndexDirtyMask) + for (auto& entityId : entityList) { - if (entity->SpatialIndex != kInvalidSpatialIndex) + auto* entity = GetEntity(entityId); + if (entity == nullptr || entity->Type == EntityType::Null) + continue; + + if (entity->SpatialIndex & kSpatialIndexDirtyMask) { - EntitySpatialRemove(entity); + if (entity->SpatialIndex != kInvalidSpatialIndex) + { + EntitySpatialRemove(entity); + } + EntitySpatialInsert(entity, { entity->x, entity->y }); } - EntitySpatialInsert(entity, { entity->x, entity->y }); } } } From eda0a75fb859ae061c31d3580db5812421e69574 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=B6eh=20Matt?= <5415177+ZehMatt@users.noreply.github.com> Date: Fri, 27 Sep 2024 11:43:02 +0300 Subject: [PATCH 3/8] Code style changes --- src/openrct2/entity/EntityRegistry.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/openrct2/entity/EntityRegistry.cpp b/src/openrct2/entity/EntityRegistry.cpp index 37db189881..10cf957f78 100644 --- a/src/openrct2/entity/EntityRegistry.cpp +++ b/src/openrct2/entity/EntityRegistry.cpp @@ -46,26 +46,27 @@ static std::vector _freeIdList; static bool _entityFlashingList[MAX_ENTITIES]; -constexpr const uint32_t SPATIAL_INDEX_SIZE = (kMaximumMapSizeTechnical * kMaximumMapSizeTechnical) + 1; -constexpr uint32_t SPATIAL_INDEX_LOCATION_NULL = SPATIAL_INDEX_SIZE - 1; +static constexpr const uint32_t kSpatialIndexSize = (kMaximumMapSizeTechnical * kMaximumMapSizeTechnical) + 1; +static constexpr uint32_t kSpatialIndexNullBucket = kSpatialIndexSize - 1; + static constexpr uint32_t kInvalidSpatialIndex = 0xFFFFFFFFu; static constexpr uint32_t kSpatialIndexDirtyMask = 1u << 31; -static std::array, SPATIAL_INDEX_SIZE> gEntitySpatialIndex; +static std::array, kSpatialIndexSize> gEntitySpatialIndex; static void FreeEntity(EntityBase& entity); static constexpr uint32_t ComputeSpatialIndex(const CoordsXY& loc) { if (loc.IsNull()) - return SPATIAL_INDEX_LOCATION_NULL; + return kSpatialIndexNullBucket; // NOTE: The input coordinate is rotated and can have negative components. const auto tileX = std::abs(loc.x) / kCoordsXYStep; const auto tileY = std::abs(loc.y) / kCoordsXYStep; if (tileX >= kMaximumMapSizeTechnical || tileY >= kMaximumMapSizeTechnical) - return SPATIAL_INDEX_LOCATION_NULL; + return kSpatialIndexNullBucket; return tileX * kMaximumMapSizeTechnical + tileY; } From 551a6b527503c2c2e73bc23b7af26c43f73a576c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=B6eh=20Matt?= <5415177+ZehMatt@users.noreply.github.com> Date: Fri, 27 Sep 2024 12:18:29 +0300 Subject: [PATCH 4/8] Move the call to UpdateEntitiesSpatialIndex to more appropriate places --- src/openrct2/Context.cpp | 3 --- src/openrct2/GameState.cpp | 2 ++ src/openrct2/entity/EntityTweener.cpp | 12 +++++------- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/openrct2/Context.cpp b/src/openrct2/Context.cpp index b1a46bb472..9eff422afe 100644 --- a/src/openrct2/Context.cpp +++ b/src/openrct2/Context.cpp @@ -1322,7 +1322,6 @@ namespace OpenRCT2 if (ShouldDraw()) { - UpdateEntitiesSpatialIndex(); Draw(); } } @@ -1359,8 +1358,6 @@ namespace OpenRCT2 const float alpha = std::min(_ticksAccumulator / kGameUpdateTimeMS, 1.0f); tweener.Tween(alpha); - UpdateEntitiesSpatialIndex(); - Draw(); } } diff --git a/src/openrct2/GameState.cpp b/src/openrct2/GameState.cpp index 9fe9a21a36..7196278d68 100644 --- a/src/openrct2/GameState.cpp +++ b/src/openrct2/GameState.cpp @@ -332,6 +332,8 @@ namespace OpenRCT2 // Update windows // WindowDispatchUpdateAll(); + UpdateEntitiesSpatialIndex(); + // Start autosave timer after update if (gLastAutoSaveUpdate == kAutosavePause) { diff --git a/src/openrct2/entity/EntityTweener.cpp b/src/openrct2/entity/EntityTweener.cpp index bfd40100a6..52bc453237 100644 --- a/src/openrct2/entity/EntityTweener.cpp +++ b/src/openrct2/entity/EntityTweener.cpp @@ -97,14 +97,12 @@ void EntityTweener::Tween(float alpha) if (posA == posB) continue; - ent->Invalidate(); - EntitySetCoordinates( - { static_cast(std::round(posB.x * alpha + posA.x * inv)), - static_cast(std::round(posB.y * alpha + posA.y * inv)), - static_cast(std::round(posB.z * alpha + posA.z * inv)) }, - ent); - ent->Invalidate(); + ent->MoveTo({ static_cast(std::round(posB.x * alpha + posA.x * inv)), + static_cast(std::round(posB.y * alpha + posA.y * inv)), + static_cast(std::round(posB.z * alpha + posA.z * inv)) }); } + + UpdateEntitiesSpatialIndex(); } void EntityTweener::Restore() From 92ac65720aac09443bd84ff3bb2b1a88e90a455c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=B6eh=20Matt?= <5415177+ZehMatt@users.noreply.github.com> Date: Fri, 27 Sep 2024 12:19:37 +0300 Subject: [PATCH 5/8] Remove comment --- src/openrct2/entity/EntityRegistry.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/openrct2/entity/EntityRegistry.cpp b/src/openrct2/entity/EntityRegistry.cpp index 10cf957f78..10ae01abb9 100644 --- a/src/openrct2/entity/EntityRegistry.cpp +++ b/src/openrct2/entity/EntityRegistry.cpp @@ -478,7 +478,6 @@ void EntityBase::MoveTo(const CoordsXYZ& newLocation) loc.x = kLocationNull; } - // EntitySpatialMove(this, loc); const auto newSpatialIndex = ComputeSpatialIndex(loc); if (newSpatialIndex != GetSpatialIndex(this)) { From 6a90e91237cbf85a3d923e6eef78f5a8429fae6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=B6eh=20Matt?= <5415177+ZehMatt@users.noreply.github.com> Date: Sat, 12 Oct 2024 04:18:41 +0300 Subject: [PATCH 6/8] Bump up network version --- src/openrct2/network/NetworkBase.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/openrct2/network/NetworkBase.cpp b/src/openrct2/network/NetworkBase.cpp index 8cc33fafcb..a1c1edf24d 100644 --- a/src/openrct2/network/NetworkBase.cpp +++ b/src/openrct2/network/NetworkBase.cpp @@ -49,7 +49,7 @@ using namespace OpenRCT2; // It is used for making sure only compatible builds get connected, even within // single OpenRCT2 version. -constexpr uint8_t kNetworkStreamVersion = 6; +constexpr uint8_t kNetworkStreamVersion = 7; const std::string kNetworkStreamID = std::string(OPENRCT2_VERSION) + "-" + std::to_string(kNetworkStreamVersion); From 698a6491fc567c7504850f75bf54fca1dff31c0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=B6eh=20Matt?= <5415177+ZehMatt@users.noreply.github.com> Date: Sat, 12 Oct 2024 04:57:19 +0300 Subject: [PATCH 7/8] Ensure the spatial index invalidates when moving the entity position --- src/openrct2/entity/EntityBase.cpp | 12 ------------ src/openrct2/entity/EntityRegistry.cpp | 23 ++++++++++++++--------- 2 files changed, 14 insertions(+), 21 deletions(-) diff --git a/src/openrct2/entity/EntityBase.cpp b/src/openrct2/entity/EntityBase.cpp index 7b61eb001c..862211a732 100644 --- a/src/openrct2/entity/EntityBase.cpp +++ b/src/openrct2/entity/EntityBase.cpp @@ -20,18 +20,6 @@ template<> bool EntityBase::Is() const return true; } -CoordsXYZ EntityBase::GetLocation() const -{ - return { x, y, z }; -} - -void EntityBase::SetLocation(const CoordsXYZ& newLocation) -{ - x = newLocation.x; - y = newLocation.y; - z = newLocation.z; -} - void EntityBase::Invalidate() { if (x == kLocationNull) diff --git a/src/openrct2/entity/EntityRegistry.cpp b/src/openrct2/entity/EntityRegistry.cpp index 10ae01abb9..3a0104a815 100644 --- a/src/openrct2/entity/EntityRegistry.cpp +++ b/src/openrct2/entity/EntityRegistry.cpp @@ -464,6 +464,19 @@ void UpdateEntitiesSpatialIndex() } } +CoordsXYZ EntityBase::GetLocation() const +{ + return { x, y, z }; +} + +void EntityBase::SetLocation(const CoordsXYZ& newLocation) +{ + x = newLocation.x; + y = newLocation.y; + z = newLocation.z; + SpatialIndex |= kSpatialIndexDirtyMask; +} + void EntityBase::MoveTo(const CoordsXYZ& newLocation) { if (x != kLocationNull) @@ -478,17 +491,9 @@ void EntityBase::MoveTo(const CoordsXYZ& newLocation) loc.x = kLocationNull; } - const auto newSpatialIndex = ComputeSpatialIndex(loc); - if (newSpatialIndex != GetSpatialIndex(this)) - { - SpatialIndex |= kSpatialIndexDirtyMask; - } - if (loc.x == kLocationNull) { - x = loc.x; - y = loc.y; - z = loc.z; + SetLocation(loc); } else { From 5bbed4adc29d632eea396d0c1ac3277e413f5c3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=B6eh=20Matt?= <5415177+ZehMatt@users.noreply.github.com> Date: Sat, 12 Oct 2024 05:00:23 +0300 Subject: [PATCH 8/8] Update replay --- CMakeLists.txt | 4 ++-- openrct2.proj | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 729764dfc3..3e83b17257 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -80,9 +80,9 @@ set(OPENMSX_VERSION "1.6") set(OPENMSX_URL "https://github.com/OpenRCT2/OpenMusic/releases/download/v${OPENMSX_VERSION}/openmusic.zip") set(OPENMSX_SHA1 "ba170fa6d777b309c15420f4b6eb3fa25082a9d1") -set(REPLAYS_VERSION "0.0.81") +set(REPLAYS_VERSION "0.0.83") set(REPLAYS_URL "https://github.com/OpenRCT2/replays/releases/download/v${REPLAYS_VERSION}/replays.zip") -set(REPLAYS_SHA1 "F698526D1D2DABEF80F350A6363223D67DBC96B1") +set(REPLAYS_SHA1 "FFC98C36AFEC68DC6A48E863413D4E2364A202B3") option(FORCE32 "Force 32-bit build. It will add `-m32` to compiler flags.") option(WITH_TESTS "Build tests") diff --git a/openrct2.proj b/openrct2.proj index 46c0fbb70d..6d9bf56efd 100644 --- a/openrct2.proj +++ b/openrct2.proj @@ -51,8 +51,8 @@ b1b1f1b241d2cbff63a1889c4dc5a09bdf769bfb https://github.com/OpenRCT2/OpenMusic/releases/download/v1.6/openmusic.zip ba170fa6d777b309c15420f4b6eb3fa25082a9d1 - https://github.com/OpenRCT2/replays/releases/download/v0.0.81/replays.zip - F698526D1D2DABEF80F350A6363223D67DBC96B1 + https://github.com/OpenRCT2/replays/releases/download/v0.0.83/replays.zip + FFC98C36AFEC68DC6A48E863413D4E2364A202B3