6352e8e319
all at once
309 lines
12 KiB
C++
309 lines
12 KiB
C++
#ifndef STAR_WORLD_STORAGE_HPP
|
|
#define STAR_WORLD_STORAGE_HPP
|
|
|
|
#include "StarBTreeDatabase.hpp"
|
|
#include "StarVersioningDatabase.hpp"
|
|
#include "StarEntity.hpp"
|
|
#include "StarOrderedSet.hpp"
|
|
#include "StarWorldTiles.hpp"
|
|
#include "StarRpcPromise.hpp"
|
|
#include "StarBiomePlacement.hpp"
|
|
|
|
namespace Star {
|
|
|
|
STAR_EXCEPTION(WorldStorageException, StarException);
|
|
|
|
STAR_CLASS(EntityMap);
|
|
STAR_STRUCT(WorldGeneratorFacade);
|
|
STAR_CLASS(WorldStorage);
|
|
|
|
typedef HashMap<ByteArray, Maybe<ByteArray>> WorldChunks;
|
|
|
|
enum class SectorLoadLevel : uint8_t {
|
|
None = 0,
|
|
Tiles = 1,
|
|
Entities = 2,
|
|
|
|
Loaded = 2
|
|
};
|
|
|
|
enum class SectorGenerationLevel : uint8_t {
|
|
None = 0,
|
|
BaseTiles = 1,
|
|
MicroDungeons = 2,
|
|
CaveLiquid = 3,
|
|
Finalize = 4,
|
|
|
|
Complete = 4,
|
|
|
|
Terraform = 5
|
|
};
|
|
|
|
struct WorldGeneratorFacade {
|
|
typedef ServerTileSectorArray::Sector Sector;
|
|
|
|
WorldGeneratorFacade() {}
|
|
virtual ~WorldGeneratorFacade() {}
|
|
|
|
// Should bring a given sector from generationLevel - 1 to generationLevel.
|
|
virtual void generateSectorLevel(WorldStorage* storage, Sector const& sector, SectorGenerationLevel generationLevel) = 0;
|
|
|
|
virtual void sectorLoadLevelChanged(WorldStorage* storage, Sector const& sector, SectorLoadLevel loadLevel) = 0;
|
|
|
|
// Perform terraforming operations (biome reapplication) on the given sector
|
|
virtual void terraformSector(WorldStorage* storage, Sector const& sector) = 0;
|
|
|
|
// Called after an entity is loaded, but before the entity is added to the
|
|
// EntityMap.
|
|
virtual void initEntity(WorldStorage* storage, EntityId newEntityId, EntityPtr const& entity) = 0;
|
|
|
|
// Called after the entity is removed from the entity map but before it is
|
|
// stored.
|
|
virtual void destructEntity(WorldStorage* storage, EntityPtr const& entity) = 0;
|
|
|
|
// Should return true if this entity should maintain the sector, false
|
|
// otherwise.
|
|
virtual bool entityKeepAlive(WorldStorage* storage, EntityPtr const& entity) const = 0;
|
|
|
|
// Should return true if this entity should be stored along with the world,
|
|
// false otherwise.
|
|
virtual bool entityPersistent(WorldStorage* storage, EntityPtr const& entity) const = 0;
|
|
|
|
// Queues up a microdungeon. Fulfills the rpc promise with the position the
|
|
// microdungeon was placed at
|
|
virtual RpcPromise<Vec2I> enqueuePlacement(List<BiomeItemDistribution> placements, Maybe<DungeonId> id) = 0;
|
|
};
|
|
|
|
// Handles paging entity and tile data in / out of disk backed storage for
|
|
// WorldServer and triggers initial generation. Ties tile sectors to entity
|
|
// sectors, and allows for multiple stage generation of those sectors. Sector
|
|
// generation is done in stages, so that lower generation stages are done in a
|
|
// one sector border around the higher generation stages.
|
|
//
|
|
// WorldStorage is designed so that once constructed, any exceptions triggered
|
|
// during loading, unloading, or generation that would result in an
|
|
// indeterminate world state cause the underlying database to be rolled back
|
|
// and then immediately closed. The underlying database committed only when
|
|
// destructed without error, or a manual call to sync().
|
|
class WorldStorage {
|
|
public:
|
|
typedef ServerTileSectorArray::Sector Sector;
|
|
typedef ServerTileSectorArray::Array TileArray;
|
|
typedef ServerTileSectorArray::ArrayPtr TileArrayPtr;
|
|
|
|
static WorldChunks getWorldChunksUpdate(WorldChunks const& oldChunks, WorldChunks const& newChunks);
|
|
static void applyWorldChunksUpdateToFile(String const& file, WorldChunks const& update);
|
|
static WorldChunks getWorldChunksFromFile(String const& file);
|
|
|
|
// Create a new world of the given size.
|
|
WorldStorage(Vec2U const& worldSize, IODevicePtr const& device, WorldGeneratorFacadePtr const& generatorFacade);
|
|
// Read an existing world.
|
|
WorldStorage(IODevicePtr const& device, WorldGeneratorFacadePtr const& generatorFacade);
|
|
// Read an in-memory world.
|
|
WorldStorage(WorldChunks const& chunks, WorldGeneratorFacadePtr const& generatorFacade);
|
|
~WorldStorage();
|
|
|
|
VersionedJson worldMetadata();
|
|
void setWorldMetadata(VersionedJson const& metadata);
|
|
|
|
ServerTileSectorArrayPtr const& tileArray() const;
|
|
EntityMapPtr const& entityMap() const;
|
|
|
|
Maybe<Sector> sectorForPosition(Vec2I const& position) const;
|
|
List<Sector> sectorsForRegion(RectI const& region) const;
|
|
Maybe<RectI> regionForSector(Sector sector) const;
|
|
|
|
SectorLoadLevel sectorLoadLevel(Sector sector) const;
|
|
// Returns the sector generation level if it is currently loaded, nothing
|
|
// otherwise.
|
|
Maybe<SectorGenerationLevel> sectorGenerationLevel(Sector sector) const;
|
|
// Returns true if the sector is both loaded and fully generated.
|
|
bool sectorActive(Sector) const;
|
|
|
|
// Fully load the given sector and reset its TTL without triggering any
|
|
// generation.
|
|
void loadSector(Sector sector);
|
|
// Fully load, reset the TTL, and if necessary, fully generate the given
|
|
// sector.
|
|
void activateSector(Sector sector);
|
|
// Queue the given sector for activation, if it is not already active. If
|
|
// the sector is loaded at all, also resets the TTL.
|
|
void queueSectorActivation(Sector sector);
|
|
|
|
// Immediately (synchronously) fully generates the sector, then flags it as requiring
|
|
// terraforming (biome reapplication) which will be handled by the normal generation process
|
|
void triggerTerraformSector(Sector sector);
|
|
|
|
// Queues up a microdungeon. Fulfills the rpc promise with the position the
|
|
// microdungeon was placed at
|
|
RpcPromise<Vec2I> enqueuePlacement(List<BiomeItemDistribution> placements, Maybe<DungeonId> id);
|
|
|
|
// Return the remaining time to live for a sector, if loaded. A sector's
|
|
// time to live is reset when loaded or generated, and when the time to live
|
|
// reaches zero, the sector is automatically unloaded.
|
|
Maybe<float> sectorTimeToLive(Sector sector) const;
|
|
// Set the given sector's time to live, if it is loaded at all. Returns
|
|
// false if the sector was not loaded so no action was taken.
|
|
bool setSectorTimeToLive(Sector sector, float newTimeToLive);
|
|
|
|
// Returns the position for a given unique entity if it exists in this world,
|
|
// loaded or not.
|
|
Maybe<Vec2F> findUniqueEntity(String const& uniqueId);
|
|
|
|
// If the given unique entity is not loaded, loads its sector and then if the
|
|
// unique entity is found, returns the entity id, otherwise NullEntityId.
|
|
EntityId loadUniqueEntity(String const& uniqueId);
|
|
|
|
// Does any queued generation work, potentially limiting the total number of
|
|
// increases of SectorGenerationLevel by the sectorGenerationLevelLimit, if
|
|
// given. If sectorOrdering is given, then it will be used to prioritize the
|
|
// queued sectors.
|
|
void generateQueue(Maybe<size_t> sectorGenerationLevelLimit, function<bool(Sector, Sector)> sectorOrdering = {});
|
|
// Ticks down the TTL on sectors and generation queue entries, stores old
|
|
// sectors, expires old generation queue entries, and unloads any zombie
|
|
// entities.
|
|
void tick(float dt);
|
|
|
|
// Unload all sectors that can be unloaded (if force is specified, ALWAYS
|
|
// unloads all sectors)
|
|
void unloadAll(bool force = false);
|
|
|
|
// Sync all active sectors without unloading them, and commits the underlying
|
|
// database.
|
|
void sync();
|
|
|
|
// Syncs all active sectors to disk and stores the full content of the world
|
|
// into memory.
|
|
WorldChunks readChunks();
|
|
|
|
// if this is set, all terrain generation is assumed to be handled by dungeon placement
|
|
// and steps such as microdungeons, biome objects and grass mods will be skipped
|
|
bool floatingDungeonWorld() const;
|
|
void setFloatingDungeonWorld(bool floatingDungeonWorld);
|
|
|
|
private:
|
|
enum class StoreType : uint8_t {
|
|
Metadata = 0,
|
|
TileSector = 1,
|
|
EntitySector = 2,
|
|
UniqueIndex = 3,
|
|
SectorUniques = 4
|
|
};
|
|
|
|
typedef pair<Sector, Vec2F> SectorAndPosition;
|
|
|
|
struct WorldMetadataStore {
|
|
Vec2U worldSize;
|
|
VersionedJson userMetadata;
|
|
};
|
|
|
|
typedef List<VersionedJson> EntitySectorStore;
|
|
// Map of uuid to entity's position and sector they were stored in.
|
|
typedef HashMap<String, SectorAndPosition> UniqueIndexStore;
|
|
// Set of unique ids that are stored in a given sector
|
|
typedef HashSet<String> SectorUniqueStore;
|
|
|
|
struct TileSectorStore {
|
|
TileSectorStore();
|
|
|
|
// Also store generation level along with tiles, simply because tiles are
|
|
// the first things to be loaded and the last to be stored.
|
|
SectorGenerationLevel generationLevel;
|
|
|
|
VersionNumber tileSerializationVersion;
|
|
TileArrayPtr tiles;
|
|
};
|
|
|
|
struct SectorMetadata {
|
|
SectorMetadata();
|
|
|
|
SectorLoadLevel loadLevel;
|
|
SectorGenerationLevel generationLevel;
|
|
float timeToLive;
|
|
};
|
|
|
|
static ByteArray metadataKey();
|
|
static WorldMetadataStore readWorldMetadata(ByteArray const& data);
|
|
static ByteArray writeWorldMetadata(WorldMetadataStore const& metadata);
|
|
|
|
static ByteArray entitySectorKey(Sector const& sector);
|
|
static EntitySectorStore readEntitySector(ByteArray const& data);
|
|
static ByteArray writeEntitySector(EntitySectorStore const& store);
|
|
|
|
static ByteArray tileSectorKey(Sector const& sector);
|
|
static TileSectorStore readTileSector(ByteArray const& data);
|
|
static ByteArray writeTileSector(TileSectorStore const& store);
|
|
|
|
static ByteArray uniqueIndexKey(String const& uniqueId);
|
|
static UniqueIndexStore readUniqueIndexStore(ByteArray const& data);
|
|
static ByteArray writeUniqueIndexStore(UniqueIndexStore const& store);
|
|
|
|
static ByteArray sectorUniqueKey(Sector const& sector);
|
|
static SectorUniqueStore readSectorUniqueStore(ByteArray const& data);
|
|
static ByteArray writeSectorUniqueStore(SectorUniqueStore const& store);
|
|
|
|
static void openDatabase(BTreeDatabase& db, IODevicePtr device);
|
|
|
|
WorldStorage();
|
|
|
|
bool belongsInSector(Sector const& sector, Vec2F const& position) const;
|
|
|
|
// Generate a random TTL value in the configured range
|
|
float randomizedSectorTTL() const;
|
|
|
|
// Generate the given sector to the given generation level. If
|
|
// sectorGenerationLevelLimit is given, stops work as soon as the given
|
|
// number of generation level changes has occurred. Returns whether the
|
|
// given sector was fully generated, and the total number of generation
|
|
// levels increased. If any sector's generation level is brought up at all,
|
|
// it will also reset the TTL for that sector.
|
|
pair<bool, size_t> generateSectorToLevel(Sector const& sector, SectorGenerationLevel targetGenerationLevel, size_t sectorGenerationLevelLimit = NPos);
|
|
|
|
// Bring the sector up to the given load level, and all surrounding sectors
|
|
// as appropriate. If the load level is brought up, also resets the TTL.
|
|
void loadSectorToLevel(Sector const& sector, SectorLoadLevel targetLoadLevel);
|
|
|
|
// Store and unload the given sector to the given level, given the state of
|
|
// the surrounding sectors. If force is true, will always unload to the
|
|
// given level.
|
|
void unloadSectorToLevel(Sector const& sector, SectorLoadLevel targetLoadLevel, bool force = false);
|
|
|
|
// Sync this sector to disk without unloading it.
|
|
void syncSector(Sector const& sector);
|
|
|
|
// Returns the sectors within WorldSectorSize of the given sector. This is
|
|
// *not exactly the same* as the surrounding 9 sectors in a square pattern,
|
|
// because first this does not return invalid sectors, and second, If a world
|
|
// is not evenly divided by the sector size, this may return extra sectors on
|
|
// one side because they are within range.
|
|
List<Sector> adjacentSectors(Sector const& sector) const;
|
|
|
|
// Replace the sector uniques for this sector with the given set
|
|
void updateSectorUniques(Sector const& sector, UniqueIndexStore const& sectorUniques);
|
|
// Merge the stored sector uniques for this sector with the given set
|
|
void mergeSectorUniques(Sector const& sector, UniqueIndexStore const& sectorUniques);
|
|
|
|
Maybe<SectorAndPosition> getUniqueIndexEntry(String const& uniqueId);
|
|
void setUniqueIndexEntry(String const& uniqueId, SectorAndPosition const& sectorAndPosition);
|
|
// Remove the index entry for this unique id, if the index entry found points
|
|
// to the given sector
|
|
void removeUniqueIndexEntry(String const& uniqueId, Sector const& sector);
|
|
|
|
Vec2F m_sectorTimeToLive;
|
|
float m_generationQueueTimeToLive;
|
|
|
|
ServerTileSectorArrayPtr m_tileArray;
|
|
EntityMapPtr m_entityMap;
|
|
WorldGeneratorFacadePtr m_generatorFacade;
|
|
|
|
bool m_floatingDungeonWorld;
|
|
|
|
StableHashMap<Sector, SectorMetadata> m_sectorMetadata;
|
|
OrderedHashMap<Sector, float> m_generationQueue;
|
|
BTreeDatabase m_db;
|
|
};
|
|
|
|
}
|
|
|
|
#endif
|