diff options
author | Kae <80987908+Novaenia@users.noreply.github.com> | 2023-06-20 14:33:09 +1000 |
---|---|---|
committer | Kae <80987908+Novaenia@users.noreply.github.com> | 2023-06-20 14:33:09 +1000 |
commit | 6352e8e3196f78388b6c771073f9e03eaa612673 (patch) | |
tree | e23772f79a7fbc41bc9108951e9e136857484bf4 /source/game/StarWorldStorage.hpp | |
parent | 6741a057e5639280d85d0f88ba26f000baa58f61 (diff) |
everything everywhere
all at once
Diffstat (limited to 'source/game/StarWorldStorage.hpp')
-rw-r--r-- | source/game/StarWorldStorage.hpp | 308 |
1 files changed, 308 insertions, 0 deletions
diff --git a/source/game/StarWorldStorage.hpp b/source/game/StarWorldStorage.hpp new file mode 100644 index 0000000..a0b248c --- /dev/null +++ b/source/game/StarWorldStorage.hpp @@ -0,0 +1,308 @@ +#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 |