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 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/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/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..3a0104a815 100644 --- a/src/openrct2/entity/EntityRegistry.cpp +++ b/src/openrct2/entity/EntityRegistry.cpp @@ -46,28 +46,36 @@ 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 std::array, SPATIAL_INDEX_SIZE> gEntitySpatialIndex; +static constexpr uint32_t kInvalidSpatialIndex = 0xFFFFFFFFu; +static constexpr uint32_t kSpatialIndexDirtyMask = 1u << 31; + +static std::array, kSpatialIndexSize> 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; + 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; } +static constexpr uint32_t GetSpatialIndex(EntityBase* entity) +{ + return entity->SpatialIndex & ~kSpatialIndexDirtyMask; +} + constexpr bool EntityTypeIsMiscEntity(const EntityType type) { switch (type) @@ -121,7 +129,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 +212,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 +320,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 +414,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 +438,43 @@ 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; + for (auto& entityList : gEntityLists) + { + for (auto& entityId : entityList) + { + auto* entity = GetEntity(entityId); + 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 }); + } + } + } +} + +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) @@ -452,13 +491,9 @@ void EntityBase::MoveTo(const CoordsXYZ& newLocation) loc.x = kLocationNull; } - EntitySpatialMove(this, loc); - if (loc.x == kLocationNull) { - x = loc.x; - y = loc.y; - z = loc.z; + SetLocation(loc); } else { 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 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() 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);