Веб-сайт самохостера Lotigara

summaryrefslogtreecommitdiff
path: root/source/game/StarWorldStorage.hpp
diff options
context:
space:
mode:
authorKae <80987908+Novaenia@users.noreply.github.com>2023-06-20 14:33:09 +1000
committerKae <80987908+Novaenia@users.noreply.github.com>2023-06-20 14:33:09 +1000
commit6352e8e3196f78388b6c771073f9e03eaa612673 (patch)
treee23772f79a7fbc41bc9108951e9e136857484bf4 /source/game/StarWorldStorage.hpp
parent6741a057e5639280d85d0f88ba26f000baa58f61 (diff)
everything everywhere
all at once
Diffstat (limited to 'source/game/StarWorldStorage.hpp')
-rw-r--r--source/game/StarWorldStorage.hpp308
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