1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2026-01-21 05:53:02 +01:00

Merge pull request #22836 from ZehMatt/spatial-index

Refactor spatial indexing to be its own phase
This commit is contained in:
Matt
2024-10-12 16:33:37 +03:00
committed by GitHub
9 changed files with 73 additions and 48 deletions

View File

@@ -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")

View File

@@ -51,8 +51,8 @@
<OpenSFXSha1>b1b1f1b241d2cbff63a1889c4dc5a09bdf769bfb</OpenSFXSha1>
<OpenMSXUrl>https://github.com/OpenRCT2/OpenMusic/releases/download/v1.6/openmusic.zip</OpenMSXUrl>
<OpenMSXSha1>ba170fa6d777b309c15420f4b6eb3fa25082a9d1</OpenMSXSha1>
<ReplaysUrl>https://github.com/OpenRCT2/replays/releases/download/v0.0.81/replays.zip</ReplaysUrl>
<ReplaysSha1>F698526D1D2DABEF80F350A6363223D67DBC96B1</ReplaysSha1>
<ReplaysUrl>https://github.com/OpenRCT2/replays/releases/download/v0.0.83/replays.zip</ReplaysUrl>
<ReplaysSha1>FFC98C36AFEC68DC6A48E863413D4E2364A202B3</ReplaysSha1>
</PropertyGroup>
<ItemGroup>

View File

@@ -332,6 +332,8 @@ namespace OpenRCT2
// Update windows
// WindowDispatchUpdateAll();
UpdateEntitiesSpatialIndex();
// Start autosave timer after update
if (gLastAutoSaveUpdate == kAutosavePause)
{

View File

@@ -20,18 +20,6 @@ template<> bool EntityBase::Is<EntityBase>() 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)

View File

@@ -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

View File

@@ -46,28 +46,36 @@ static std::vector<EntityId> _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<std::vector<EntityId>, SPATIAL_INDEX_SIZE> gEntitySpatialIndex;
static constexpr uint32_t kInvalidSpatialIndex = 0xFFFFFFFFu;
static constexpr uint32_t kSpatialIndexDirtyMask = 1u << 31;
static std::array<std::vector<EntityId>, 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<EntityId>& 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
{

View File

@@ -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

View File

@@ -97,14 +97,12 @@ void EntityTweener::Tween(float alpha)
if (posA == posB)
continue;
ent->Invalidate();
EntitySetCoordinates(
{ static_cast<int32_t>(std::round(posB.x * alpha + posA.x * inv)),
static_cast<int32_t>(std::round(posB.y * alpha + posA.y * inv)),
static_cast<int32_t>(std::round(posB.z * alpha + posA.z * inv)) },
ent);
ent->Invalidate();
ent->MoveTo({ static_cast<int32_t>(std::round(posB.x * alpha + posA.x * inv)),
static_cast<int32_t>(std::round(posB.y * alpha + posA.y * inv)),
static_cast<int32_t>(std::round(posB.z * alpha + posA.z * inv)) });
}
UpdateEntitiesSpatialIndex();
}
void EntityTweener::Restore()

View File

@@ -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);