Fix WorldStorage accessing null entityMap if a broken world throws an exception on load

happened to me when trying to repair a broken world file
This commit is contained in:
Kae 2024-08-08 11:59:38 +10:00
parent dc37c9bdb8
commit b2afd65144

View File

@ -371,15 +371,17 @@ void WorldStorage::unloadAll(bool force) {
// or being entirely outside of the world geometry. This limits the number // or being entirely outside of the world geometry. This limits the number
// of tries to completely uninit and store all entities before giving up // of tries to completely uninit and store all entities before giving up
// and just letting some entities not be stored. // and just letting some entities not be stored.
unsigned forceUnloadTries = storageConfig.getUInt("forceUnloadTries"); if (m_entityMap) {
for (unsigned i = 0; i < forceUnloadTries; ++i) { unsigned forceUnloadTries = storageConfig.getUInt("forceUnloadTries");
for (auto sector : sectors) for (unsigned i = 0; i < forceUnloadTries; ++i) {
unloadSectorToLevel(sector, SectorLoadLevel::Tiles, force); for (auto& sector : sectors)
unloadSectorToLevel(sector, SectorLoadLevel::Tiles, force);
if (!force || m_entityMap->size() == 0) if (!force || m_entityMap->size() == 0)
break; break;
}
} }
for (auto sector : sectors) for (auto& sector : sectors)
unloadSectorToLevel(sector, SectorLoadLevel::None, force); unloadSectorToLevel(sector, SectorLoadLevel::None, force);
} catch (std::exception const& e) { } catch (std::exception const& e) {
@ -721,71 +723,71 @@ bool WorldStorage::unloadSectorToLevel(Sector const& sector, SectorLoadLevel tar
if (!m_tileArray->sectorValid(sector) || targetLoadLevel == SectorLoadLevel::Loaded) if (!m_tileArray->sectorValid(sector) || targetLoadLevel == SectorLoadLevel::Loaded)
return true; return true;
auto entityFactory = Root::singleton().entityFactory();
auto& metadata = m_sectorMetadata[sector]; auto& metadata = m_sectorMetadata[sector];
List<EntityPtr> entitiesToStore;
List<EntityPtr> entitiesToRemove;
bool entitiesOverlap = false; bool entitiesOverlap = false;
if (m_entityMap) {
auto entityFactory = Root::singleton().entityFactory();
List<EntityPtr> entitiesToStore;
List<EntityPtr> entitiesToRemove;
for (auto& entity : m_entityMap->entityQuery(RectF(m_tileArray->sectorRegion(sector)))) { for (auto& entity : m_entityMap->entityQuery(RectF(m_tileArray->sectorRegion(sector)))) {
// Only store / remove entities who belong to this sector. If an entity // Only store / remove entities who belong to this sector. If an entity
// overlaps with this sector but does not belong to it, we may not want to // overlaps with this sector but does not belong to it, we may not want to
// completely unload it. // completely unload it.
auto position = entity->position(); auto position = entity->position();
if (!belongsInSector(sector, position)) { if (!belongsInSector(sector, position)) {
if (auto entitySector = sectorForPosition(Vec2I(position))) { if (auto entitySector = sectorForPosition(Vec2I(position))) {
if (auto p = m_sectorMetadata.ptr(*entitySector)) if (auto p = m_sectorMetadata.ptr(*entitySector))
entitiesOverlap |= p->timeToLive > 0.0f; entitiesOverlap |= p->timeToLive > 0.0f;
}
continue;
} }
continue;
bool keepAlive = m_generatorFacade->entityKeepAlive(this, entity);
if (keepAlive && !force)
return false;
if (m_generatorFacade->entityPersistent(this, entity))
entitiesToStore.append(std::move(entity));
else
entitiesToRemove.append(std::move(entity));
} }
bool keepAlive = m_generatorFacade->entityKeepAlive(this, entity); for (auto const& entity : entitiesToRemove) {
if (keepAlive && !force)
return false;
if (m_generatorFacade->entityPersistent(this, entity))
entitiesToStore.append(std::move(entity));
else
entitiesToRemove.append(std::move(entity));
}
for (auto const& entity : entitiesToRemove) {
m_entityMap->removeEntity(entity->entityId());
m_generatorFacade->destructEntity(this, entity);
}
if (metadata.loadLevel == SectorLoadLevel::Entities || !entitiesToStore.empty()) {
EntitySectorStore sectorStore;
// If our current load level indicates that we might have entities that are
// not loaded, we need to load and merge with them, otherwise we should be
// overwriting them.
if (metadata.loadLevel < SectorLoadLevel::Entities) {
if (auto res = m_db.find(entitySectorKey(sector)))
sectorStore = readEntitySector(*res);
}
UniqueIndexStore storedUniques;
for (auto const& entity : entitiesToStore) {
m_entityMap->removeEntity(entity->entityId()); m_entityMap->removeEntity(entity->entityId());
m_generatorFacade->destructEntity(this, entity); m_generatorFacade->destructEntity(this, entity);
auto position = entity->position();
if (auto uniqueId = entity->uniqueId())
storedUniques.add(*uniqueId, {sector, position});
sectorStore.append(entityFactory->storeVersionedEntity(entity));
} }
m_db.insert(entitySectorKey(sector), writeEntitySector(sectorStore));
if (metadata.loadLevel < SectorLoadLevel::Entities)
mergeSectorUniques(sector, storedUniques);
else
updateSectorUniques(sector, storedUniques);
if (metadata.loadLevel == SectorLoadLevel::Entities) { if (metadata.loadLevel == SectorLoadLevel::Entities || !entitiesToStore.empty()) {
metadata.loadLevel = SectorLoadLevel::Tiles; EntitySectorStore sectorStore;
m_generatorFacade->sectorLoadLevelChanged(this, sector, SectorLoadLevel::Tiles);
// If our current load level indicates that we might have entities that are
// not loaded, we need to load and merge with them, otherwise we should be
// overwriting them.
if (metadata.loadLevel < SectorLoadLevel::Entities) {
if (auto res = m_db.find(entitySectorKey(sector)))
sectorStore = readEntitySector(*res);
}
UniqueIndexStore storedUniques;
for (auto const& entity : entitiesToStore) {
m_entityMap->removeEntity(entity->entityId());
m_generatorFacade->destructEntity(this, entity);
auto position = entity->position();
if (auto uniqueId = entity->uniqueId())
storedUniques.add(*uniqueId, {sector, position});
sectorStore.append(entityFactory->storeVersionedEntity(entity));
}
m_db.insert(entitySectorKey(sector), writeEntitySector(sectorStore));
if (metadata.loadLevel < SectorLoadLevel::Entities)
mergeSectorUniques(sector, storedUniques);
else
updateSectorUniques(sector, storedUniques);
if (metadata.loadLevel == SectorLoadLevel::Entities) {
metadata.loadLevel = SectorLoadLevel::Tiles;
m_generatorFacade->sectorLoadLevelChanged(this, sector, SectorLoadLevel::Tiles);
}
} }
} }