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/StarVersioningDatabase.cpp | |
parent | 6741a057e5639280d85d0f88ba26f000baa58f61 (diff) |
everything everywhere
all at once
Diffstat (limited to 'source/game/StarVersioningDatabase.cpp')
-rw-r--r-- | source/game/StarVersioningDatabase.cpp | 219 |
1 files changed, 219 insertions, 0 deletions
diff --git a/source/game/StarVersioningDatabase.cpp b/source/game/StarVersioningDatabase.cpp new file mode 100644 index 0000000..aa47db7 --- /dev/null +++ b/source/game/StarVersioningDatabase.cpp @@ -0,0 +1,219 @@ +#include "StarVersioningDatabase.hpp" +#include "StarDataStreamExtra.hpp" +#include "StarFormat.hpp" +#include "StarLexicalCast.hpp" +#include "StarFile.hpp" +#include "StarLogging.hpp" +#include "StarWorldLuaBindings.hpp" +#include "StarRootLuaBindings.hpp" +#include "StarUtilityLuaBindings.hpp" +#include "StarAssets.hpp" +#include "StarStoredFunctions.hpp" +#include "StarNpcDatabase.hpp" +#include "StarRoot.hpp" +#include "StarCelestialDatabase.hpp" +#include "StarJsonExtra.hpp" + +namespace Star { + +char const* const VersionedJson::Magic = "SBVJ01"; +size_t const VersionedJson::MagicStringSize = 6; + +VersionedJson VersionedJson::readFile(String const& filename) { + DataStreamIODevice ds(File::open(filename, IOMode::Read)); + + if (ds.readBytes(MagicStringSize) != ByteArray(Magic, MagicStringSize)) + throw IOException(strf("Wrong magic bytes at start of versioned json file, expected '%s'", Magic)); + + return ds.read<VersionedJson>(); +} + +void VersionedJson::writeFile(VersionedJson const& versionedJson, String const& filename) { + DataStreamBuffer ds; + ds.writeData(Magic, MagicStringSize); + ds.write(versionedJson); + File::overwriteFileWithRename(ds.takeData(), filename); +} + +Json VersionedJson::toJson() const { + return JsonObject{ + {"id", identifier}, + {"version", version}, + {"content", content} + }; +} + +VersionedJson VersionedJson::fromJson(Json const& source) { + // Old versions of VersionedJson used '__' to distinguish between actual + // content and versioned content, but this is no longer necessary or + // relevant. + auto id = source.optString("id").orMaybe(source.optString("__id")); + auto version = source.optUInt("version").orMaybe(source.optUInt("__version")); + auto content = source.opt("content").orMaybe(source.opt("__content")); + return {*id, (VersionNumber)*version, *content}; +} + +bool VersionedJson::empty() const { + return content.isNull(); +} + +void VersionedJson::expectIdentifier(String const& expectedIdentifier) const { + if (identifier != expectedIdentifier) + throw VersionedJsonException::format("VersionedJson identifier mismatch, expected '%s' but got '%s'", expectedIdentifier, identifier); +} + +DataStream& operator>>(DataStream& ds, VersionedJson& versionedJson) { + ds.read(versionedJson.identifier); + // This is a holdover from when the verison number was optional in + // VersionedJson. We should convert versioned json binary files and the + // celestial chunk database and world storage to a new format eventually + versionedJson.version = ds.read<Maybe<VersionNumber>>().value(); + ds.read(versionedJson.content); + + return ds; +} + +DataStream& operator<<(DataStream& ds, VersionedJson const& versionedJson) { + ds.write(versionedJson.identifier); + ds.write(Maybe<VersionNumber>(versionedJson.version)); + ds.write(versionedJson.content); + + return ds; +} + +VersioningDatabase::VersioningDatabase() { + auto assets = Root::singleton().assets(); + + for (auto const& pair : assets->json("/versioning.config").iterateObject()) + m_currentVersions[pair.first] = pair.second.toUInt(); + + for (auto const& scriptFile : assets->scan("/versioning/", ".lua")) { + try { + auto scriptParts = File::baseName(scriptFile).splitAny("_."); + if (scriptParts.size() != 4) + throw VersioningDatabaseException::format("Script file '%s' filename not of the form <identifier>_<fromversion>_<toversion>.lua", scriptFile); + + String identifier = scriptParts.at(0); + VersionNumber fromVersion = lexicalCast<VersionNumber>(scriptParts.at(1)); + VersionNumber toVersion = lexicalCast<VersionNumber>(scriptParts.at(2)); + + m_versionUpdateScripts[identifier.toLower()].append({scriptFile, fromVersion, toVersion}); + } catch (StarException const&) { + throw VersioningDatabaseException::format("Error parsing version information from versioning script '%s'", scriptFile); + } + } + + // Sort each set of update scripts first by fromVersion, and then in + // *reverse* order of toVersion. This way, the first matching script for a + // given fromVersion should take the json to the *furthest* toVersion. + for (auto& pair : m_versionUpdateScripts) { + pair.second.sort([](VersionUpdateScript const& lhs, VersionUpdateScript const& rhs) { + if (lhs.fromVersion != rhs.fromVersion) + return lhs.fromVersion < rhs.fromVersion; + else + return lhs.toVersion < rhs.toVersion; + }); + } +} + +VersionedJson VersioningDatabase::makeCurrentVersionedJson(String const& identifier, Json const& content) const { + RecursiveMutexLocker locker(m_mutex); + return VersionedJson{identifier, m_currentVersions.get(identifier), content}; +} + +bool VersioningDatabase::versionedJsonCurrent(VersionedJson const& versionedJson) const { + RecursiveMutexLocker locker(m_mutex); + return versionedJson.version == m_currentVersions.get(versionedJson.identifier); +} + +VersionedJson VersioningDatabase::updateVersionedJson(VersionedJson const& versionedJson) const { + RecursiveMutexLocker locker(m_mutex); + + auto& root = Root::singleton(); + CelestialMasterDatabase celestialDatabase; + + VersionedJson result = versionedJson; + Maybe<VersionNumber> targetVersion = m_currentVersions.maybe(versionedJson.identifier); + if (!targetVersion) + throw VersioningDatabaseException::format("Versioned JSON has an unregistered identifier '%s'", versionedJson.identifier); + + LuaCallbacks celestialCallbacks; + celestialCallbacks.registerCallback("parameters", [&celestialDatabase](Json const& coord) { + return celestialDatabase.parameters(CelestialCoordinate(coord))->diskStore(); + }); + + try { + for (auto const& updateScript : m_versionUpdateScripts.value(versionedJson.identifier.toLower())) { + if (result.version >= *targetVersion) + break; + + if (updateScript.fromVersion == result.version) { + auto scriptContext = m_luaRoot.createContext(); + scriptContext.load(*root.assets()->bytes(updateScript.script), updateScript.script); + scriptContext.setCallbacks("root", LuaBindings::makeRootCallbacks()); + scriptContext.setCallbacks("sb", LuaBindings::makeUtilityCallbacks()); + scriptContext.setCallbacks("celestial", celestialCallbacks); + scriptContext.setCallbacks("versioning", makeVersioningCallbacks()); + + result.content = scriptContext.invokePath<Json>("update", result.content); + if (!result.content) { + throw VersioningDatabaseException::format( + "Could not bring versionedJson with identifier '%s' and version %s forward to current version of %s, conversion script from %s to %s returned null (un-upgradeable)", + versionedJson.identifier, result.version, targetVersion, updateScript.fromVersion, updateScript.toVersion); + } + Logger::debug("Brought versionedJson '%s' from version %s to %s", + versionedJson.identifier, result.version, updateScript.toVersion); + result.version = updateScript.toVersion; + } + } + } catch (std::exception const& e) { + throw VersioningDatabaseException(strf("Could not bring versionedJson with identifier '%s' and version %s forward to current version of %s", + versionedJson.identifier, result.version, targetVersion), e); + } + + if (result.version > *targetVersion) { + throw VersioningDatabaseException::format( + "VersionedJson with identifier '%s' and version %s is newer than current version of %s, cannot load", + versionedJson.identifier, result.version, targetVersion); + } + + if (result.version != *targetVersion) { + throw VersioningDatabaseException::format( + "Could not bring VersionedJson with identifier '%s' and version %s forward to current version of %s, best version was %s", + versionedJson.identifier, result.version, targetVersion, result.version); + } + + return result; +} + +Json VersioningDatabase::loadVersionedJson(VersionedJson const& versionedJson, String const& expectedIdentifier) const { + versionedJson.expectIdentifier(expectedIdentifier); + if (versionedJsonCurrent(versionedJson)) + return versionedJson.content; + return updateVersionedJson(versionedJson).content; +} + +LuaCallbacks VersioningDatabase::makeVersioningCallbacks() const { + LuaCallbacks versioningCallbacks; + + versioningCallbacks.registerCallback("loadVersionedJson", [this](String const& storagePath) { + try { + auto& root = Root::singleton(); + String filePath = File::fullPath(root.toStoragePath(storagePath)); + String basePath = File::fullPath(root.toStoragePath(".")); + if (!filePath.beginsWith(basePath)) + throw VersioningDatabaseException::format( + "Cannot load external VersionedJson outside of the Root storage path"); + auto loadedJson = VersionedJson::readFile(filePath); + return updateVersionedJson(loadedJson).content; + } catch (IOException const& e) { + Logger::debug( + "Unable to load versioned JSON file %s in versioning script: %s", storagePath, outputException(e, false)); + return Json(); + } + }); + + return versioningCallbacks; +} + +} |