diff options
author | Tiger Wang <ziwei.tiger@outlook.com> | 2020-09-08 10:46:16 +0200 |
---|---|---|
committer | Tiger Wang <ziwei.tiger@outlook.com> | 2020-12-21 01:11:34 +0100 |
commit | 742e27ad2f037205285e475be487ec9ed874ca91 (patch) | |
tree | 3218d3c2791e823c88a294ba7fdb10035c8fbb9b /src/Bindings | |
parent | Enable LOS checks for Hostile Mobs. (diff) | |
download | cuberite-742e27ad2f037205285e475be487ec9ed874ca91.tar cuberite-742e27ad2f037205285e475be487ec9ed874ca91.tar.gz cuberite-742e27ad2f037205285e475be487ec9ed874ca91.tar.bz2 cuberite-742e27ad2f037205285e475be487ec9ed874ca91.tar.lz cuberite-742e27ad2f037205285e475be487ec9ed874ca91.tar.xz cuberite-742e27ad2f037205285e475be487ec9ed874ca91.tar.zst cuberite-742e27ad2f037205285e475be487ec9ed874ca91.zip |
Diffstat (limited to 'src/Bindings')
-rw-r--r-- | src/Bindings/BlockState.cpp | 213 | ||||
-rw-r--r-- | src/Bindings/BlockState.h | 93 | ||||
-rw-r--r-- | src/Bindings/BlockTypePalette.cpp | 380 | ||||
-rw-r--r-- | src/Bindings/BlockTypePalette.h | 138 | ||||
-rw-r--r-- | src/Bindings/BlockTypeRegistry.cpp | 240 | ||||
-rw-r--r-- | src/Bindings/BlockTypeRegistry.h | 216 |
6 files changed, 1280 insertions, 0 deletions
diff --git a/src/Bindings/BlockState.cpp b/src/Bindings/BlockState.cpp new file mode 100644 index 000000000..8ee87c50f --- /dev/null +++ b/src/Bindings/BlockState.cpp @@ -0,0 +1,213 @@ +#include "Globals.h" +#include "BlockState.h" + + + + + +BlockState::BlockState(): + mChecksum(initializeChecksum()) +{ + // Nothing needed yet +} + + + + + +BlockState::BlockState(const AString & aKey, const AString & aValue): + mState({{aKey, aValue}}), + mChecksum(initializeChecksum()) +{ +} + + + + + +BlockState::BlockState(std::initializer_list<std::pair<const AString, AString>> aKeysAndValues): + mState(aKeysAndValues), + mChecksum(initializeChecksum()) +{ +} + + + + + +BlockState::BlockState(const std::map<AString, AString> & aKeysAndValues): + mState(aKeysAndValues), + mChecksum(initializeChecksum()) +{ +} + + + + + +BlockState::BlockState(std::map<AString, AString> && aKeysAndValues): + mState(std::move(aKeysAndValues)), + mChecksum(initializeChecksum()) +{ +} + + + + + +BlockState::BlockState(const BlockState & aCopyFrom, std::initializer_list<std::pair<const AString, AString>> aAdditionalKeysAndValues): + mState(aCopyFrom.mState) +{ + for (const auto & kav: aAdditionalKeysAndValues) + { + mState[kav.first] = kav.second; + } + mChecksum = initializeChecksum(); +} + + + + + +BlockState::BlockState(const BlockState & aCopyFrom, const std::map<AString, AString> & aAdditionalKeysAndValues): + mState(aCopyFrom.mState) +{ + for (const auto & kav: aAdditionalKeysAndValues) + { + mState[kav.first] = kav.second; + } + mChecksum = initializeChecksum(); +} + + + + + +bool BlockState::operator <(const BlockState & aOther) const +{ + // Fast-return this using checksum + if (mChecksum != aOther.mChecksum) + { + return (mChecksum < aOther.mChecksum); + } + + // Can fast-return this due to how comparison works + if (mState.size() != aOther.mState.size()) + { + return (mState.size() < aOther.mState.size()); + } + + auto itA = mState.begin(); + auto itOther = aOther.mState.begin(); + + // don't need to check itOther, size checks above ensure size(A) == size(O) + while (itA != mState.end()) + { + { + const auto cmp = itA->first.compare(itOther->first); + if (cmp != 0) + { + return (cmp < 0); + } + } + { + const auto cmp = itA->second.compare(itOther->second); + if (cmp != 0) + { + return (cmp < 0); + } + } + + ++itA; + ++itOther; + } + + return false; +} + + + + + +bool BlockState::operator ==(const BlockState & aOther) const +{ + // Fast-fail if the checksums differ or differrent counts: + if ((mChecksum != aOther.mChecksum) || (mState.size() != aOther.mState.size())) + { + return false; + } + + // Slow-check everything if the checksums match: + return std::equal(mState.begin(), mState.end(), aOther.mState.begin()); +} + + + + + +const AString & BlockState::value(const AString & aKey) const +{ + auto itr = mState.find(aKey); + if (itr == mState.end()) + { + static AString empty; + return empty; + } + return itr->second; +} + + + + + +UInt32 BlockState::initializeChecksum() +{ + removeEmptyKeys(); + + // Calculate the checksum as a XOR of all mState keys' and values' checksums + // This way we don't depend on the std::map's ordering + UInt32 res = 0; + for (const auto & kv: mState) + { + auto partial = partialChecksum(kv.first) ^ partialChecksum(kv.second); + res = res ^ partial; + } + return res; +} + + + + + +void BlockState::removeEmptyKeys() +{ + for (auto itr = mState.begin(); itr != mState.end();) + { + if (itr->second.empty()) + { + itr = mState.erase(itr); + } + else + { + ++itr; + } + } +} + + + + + +UInt32 BlockState::partialChecksum(const AString & aString) +{ + UInt32 shift = 0; + UInt32 res = 0; + for (auto ch: aString) + { + UInt32 v = static_cast<UInt8>(ch); + v = v << shift; + shift = (shift + 1) % 24; + res = res ^ v; + } + return res; +} diff --git a/src/Bindings/BlockState.h b/src/Bindings/BlockState.h new file mode 100644 index 000000000..ab451236b --- /dev/null +++ b/src/Bindings/BlockState.h @@ -0,0 +1,93 @@ +#pragma once + +#include <initializer_list> + + + + + +/** Represents the state of a single block (previously known as "block meta"). +The state consists of a map of string -> string, plus a mechanism for fast equality checks between two BlockState instances. +Once a BlockState instance is created, it is then immutable - there's no way of changing it, only by creating a (modified) copy. +A BlockState instance can be created from hard-coded data or from dynamic data: + BlockState bs({{"key1", "value1"}, {key2", "value2"}}); // Hard-coded + - or - + std::map<AString, AString> map({{"key1", "value1"}, {key2", "value2"}}); + map["key3"] = "value3"; + BlockState bs(map); // From dynamic data +*/ +class BlockState +{ +public: + + /** Creates a new instance with an empty map. */ + BlockState(); + + /** Creates a new instance consisting of a single key-value pair. + If the value is empty, it is not stored wihin the map. */ + BlockState(const AString & aKey, const AString & aValue); + + /** Creates a new instance initialized with several (hard-coded) key-value pairs. + Any key with an empty value is not stored within the map. */ + BlockState(std::initializer_list<std::pair<const AString, AString>> aKeysAndValues); + + /** Creates a new instance initialized with several (dynamic) key-value pairs. + Makes a copy of aKeysAndValues for this object. + Any key with an empty value is not stored within the map. */ + BlockState(const std::map<AString, AString> & aKeysAndValues); + + /** Creates a new instance initialized with several (dynamic) key-value pairs. + Any key with an empty value is not stored within the map. */ + BlockState(std::map<AString, AString> && aKeysAndValues); + + /** Creates a copy of the specified BlockState with the (hard-coded) additional keys and values added to it. + Any key in aAdditionalKeysAndValues that is already present in aCopyFrom is overwritten with the aAdditionalKeysAndValues' one. + Any key with an empty value is not stored in the map. + (it's possible to erase a key from aCopyFrom by setting it to empty string in aAdditionalKeysAndValues). */ + BlockState(const BlockState & aCopyFrom, std::initializer_list<std::pair<const AString, AString>> aAdditionalKeysAndValues); + + /** Creates a copy of the specified BlockState with the (dynamic) additional keys and values added to it. + Any key in aAdditionalKeysAndValues that is already present in aCopyFrom is overwritten with the aAdditionalKeysAndValues' one. + Any key with an empty value is not stored in the map. + (it's possible to erase a key from aCopyFrom by setting it to empty string in aAdditionalKeysAndValues). */ + BlockState(const BlockState & aCopyFrom, const std::map<AString, AString> & aAdditionalKeysAndValues); + + /** Less-than comparison. */ + bool operator <(const BlockState & aOther) const; + + /** Fast equality check. */ + bool operator ==(const BlockState & aOther) const; + + /** Fast inequality check. */ + bool operator !=(const BlockState & aOther) const + { + return !(operator ==(aOther)); + } + + /** Returns the value at the specified key. + If the key is not present, returns an empty string. */ + const AString & value(const AString & aKey) const; + + +protected: + + /** The state, represented as a string->string map. */ + std::map<AString, AString> mState; + + /** The checksum used for the fast equality check. + This is calculated upon creation. */ + UInt32 mChecksum; + + + /** Normalizes mState and calculates the checksum from it. + Removes all the empty values from mState. + Used only from constructors. */ + UInt32 initializeChecksum(); + + /** Removes all the keys from mState that have an empty value. */ + void removeEmptyKeys(); + + /** Calculates the partial checksum of a single string. + Used from within initializeChecksum(). */ + UInt32 partialChecksum(const AString & aString); +}; diff --git a/src/Bindings/BlockTypePalette.cpp b/src/Bindings/BlockTypePalette.cpp new file mode 100644 index 000000000..7759505cf --- /dev/null +++ b/src/Bindings/BlockTypePalette.cpp @@ -0,0 +1,380 @@ +#include "Globals.h" +#include "BlockTypePalette.h" +#include "json/value.h" +#include "JsonUtils.h" + + + + + +/** Returns the index into aString >= aStartIdx at which the next separator occurs. +Separator is one of \t, \n or \r. +Returns AString::npos if no such separator. */ +static size_t findNextSeparator(const AString & aString, size_t aStartIdx = 0) +{ + for (size_t i = aStartIdx, len = aString.length(); i < len; ++i) + { + switch (aString[i]) + { + case '\t': + case '\n': + case '\r': + { + return i; + } + } + } + return AString::npos; +} + + + + + +BlockTypePalette::BlockTypePalette(): + mMaxIndex(0) +{ +} + + + + + +UInt32 BlockTypePalette::index(const AString & aBlockTypeName, const BlockState & aBlockState) +{ + auto idx = maybeIndex(aBlockTypeName, aBlockState); + if (idx.second) + { + return idx.first; + } + + // Not found, append: + auto index = mMaxIndex++; + mBlockToNumber[aBlockTypeName][aBlockState] = index; + mNumberToBlock[index] = {aBlockTypeName, aBlockState}; + return index; +} + + + + + +std::pair<UInt32, bool> BlockTypePalette::maybeIndex(const AString & aBlockTypeName, const BlockState & aBlockState) const +{ + auto itr1 = mBlockToNumber.find(aBlockTypeName); + if (itr1 == mBlockToNumber.end()) + { + return {0, false}; + } + auto itr2 = itr1->second.find(aBlockState); + if (itr2 == itr1->second.end()) + { + return {0, false}; + } + return {itr2->second, true}; +} + + + + + +UInt32 BlockTypePalette::count() const +{ + return static_cast<UInt32>(mNumberToBlock.size()); +} + + + + + +const std::pair<AString, BlockState> & BlockTypePalette::entry(UInt32 aIndex) const +{ + auto itr = mNumberToBlock.find(aIndex); + if (itr == mNumberToBlock.end()) + { + throw NoSuchIndexException(aIndex); + } + return itr->second; +} + + + + + +std::map<UInt32, UInt32> BlockTypePalette::createTransformMapAddMissing(const BlockTypePalette & aFrom) +{ + std::map<UInt32, UInt32> res; + for (const auto & fromEntry: aFrom.mNumberToBlock) + { + auto fromIndex = fromEntry.first; + const auto & blockTypeName = fromEntry.second.first; + const auto & blockState = fromEntry.second.second; + res[fromIndex] = index(blockTypeName, blockState); + } + return res; +} + + + + + +std::map<UInt32, UInt32> BlockTypePalette::createTransformMapWithFallback(const BlockTypePalette & aFrom, UInt32 aFallbackIndex) const +{ + std::map<UInt32, UInt32> res; + for (const auto & fromEntry: aFrom.mNumberToBlock) + { + auto fromIndex = fromEntry.first; + const auto & blockTypeName = fromEntry.second.first; + const auto & blockState = fromEntry.second.second; + auto thisIndex = maybeIndex(blockTypeName, blockState); + if (thisIndex.second) + { + // The entry was found in this + res[fromIndex] = thisIndex.first; + } + else + { + // The entry was NOT found in this, replace with fallback: + res[fromIndex] = aFallbackIndex; + } + } + return res; +} + + + + + +void BlockTypePalette::loadFromString(const AString & aString) +{ + static const AString hdrTsvRegular = "BlockTypePalette"; + static const AString hdrTsvUpgrade = "UpgradeBlockTypePalette"; + + // Detect format by checking the header line (none -> JSON): + if (aString.substr(0, hdrTsvRegular.length()) == hdrTsvRegular) + { + return loadFromTsv(aString, false); + } + else if (aString.substr(0, hdrTsvUpgrade.length()) == hdrTsvUpgrade) + { + return loadFromTsv(aString, true); + } + return loadFromJsonString(aString); +} + + + + + +void BlockTypePalette::loadFromJsonString(const AString & aJsonPalette) +{ + // Parse the string into JSON object: + Json::Value root; + std::string errs; + if (!JsonUtils::ParseString(aJsonPalette, root, &errs)) + { + throw LoadFailedException(errs); + } + + // Sanity-check the JSON's structure: + if (!root.isObject()) + { + throw LoadFailedException("Incorrect palette format, expected an object at root."); + } + + // Load the palette: + for (auto itr = root.begin(), end = root.end(); itr != end; ++itr) + { + const auto & blockTypeName = itr.name(); + const auto & states = (*itr)["states"]; + if (states == Json::Value()) + { + throw LoadFailedException(Printf("Missing \"states\" for block type \"%s\"", blockTypeName)); + } + for (const auto & state: states) + { + auto id = static_cast<UInt32>(std::stoul(state["id"].asString())); + std::map<AString, AString> props; + if (state.isMember("properties")) + { + const auto & properties = state["properties"]; + if (!properties.isObject()) + { + throw LoadFailedException(Printf("Member \"properties\" is not a JSON object (block type \"%s\", id %u).", blockTypeName, id)); + } + for (const auto & key: properties.getMemberNames()) + { + props[key] = properties[key].asString(); + } + } + addMapping(id, blockTypeName, props); + } + } +} + + + + + +void BlockTypePalette::loadFromTsv(const AString & aTsvPalette, bool aIsUpgrade) +{ + static const AString hdrTsvRegular = "BlockTypePalette"; + static const AString hdrTsvUpgrade = "UpgradeBlockTypePalette"; + + // Check the file signature: + auto idx = findNextSeparator(aTsvPalette); + if ((idx == AString::npos) || (aTsvPalette[idx] == '\t')) + { + throw LoadFailedException("Invalid signature"); + } + auto signature = aTsvPalette.substr(0, idx); + bool isUpgrade = (signature == hdrTsvUpgrade); + if (!isUpgrade && (signature != hdrTsvRegular)) + { + throw LoadFailedException("Unknown signature"); + } + if (aTsvPalette[idx] == '\r') // CR of the CRLF pair, skip the LF: + { + idx += 1; + } + + // Parse the header: + bool hasHadVersion = false; + AString commonPrefix; + int line = 2; + auto len = aTsvPalette.length(); + while (true) + { + auto keyStart = idx + 1; + auto keyEnd = findNextSeparator(aTsvPalette, idx + 1); + if (keyEnd == AString::npos) + { + throw LoadFailedException(Printf("Invalid header key format on line %u", line)); + } + if (keyEnd == idx + 1) // Empty line, end of headers + { + if (aTsvPalette[keyEnd] == '\r') // CR of the CRLF pair, skip the LF: + { + ++keyEnd; + } + idx = keyEnd; + ++line; + break; + } + auto valueEnd = findNextSeparator(aTsvPalette, keyEnd + 1); + if ((valueEnd == AString::npos) || (aTsvPalette[valueEnd] == '\t')) + { + throw LoadFailedException(Printf("Invalid header value format on line %u", line)); + } + auto key = aTsvPalette.substr(keyStart, keyEnd - keyStart); + if (key == "FileVersion") + { + unsigned version = 0; + auto value = aTsvPalette.substr(keyEnd + 1, valueEnd - keyEnd - 1); + if (!StringToInteger(value, version)) + { + throw LoadFailedException("Invalid FileVersion value"); + } + else if (version != 1) + { + throw LoadFailedException(Printf("Unknown FileVersion: %u. Only version 1 is supported.", version)); + } + hasHadVersion = true; + } + else if (key == "CommonPrefix") + { + commonPrefix = aTsvPalette.substr(keyEnd + 1, valueEnd - keyEnd - 1); + } + idx = valueEnd; + if (aTsvPalette[idx] == '\r') // CR of the CRLF pair, skip the LF: + { + ++idx; + } + ++line; + } + if (!hasHadVersion) + { + throw LoadFailedException("No FileVersion value"); + } + + // Parse the data: + while (idx + 1 < len) + { + auto lineStart = idx + 1; + auto idEnd = findNextSeparator(aTsvPalette, lineStart); + if ((idEnd == AString::npos) || (aTsvPalette[idEnd] != '\t')) + { + throw LoadFailedException(Printf("Incomplete data on line %u (id)", line)); + } + UInt32 id; + if (!StringToInteger(aTsvPalette.substr(lineStart, idEnd - lineStart), id)) + { + throw LoadFailedException(Printf("Failed to parse id on line %u", line)); + } + size_t metaEnd = idEnd; + if (isUpgrade) + { + metaEnd = findNextSeparator(aTsvPalette, idEnd + 1); + if ((metaEnd == AString::npos) || (aTsvPalette[metaEnd] != '\t')) + { + throw LoadFailedException(Printf("Incomplete data on line %u (meta)", line)); + } + UInt32 meta = 0; + if (!StringToInteger(aTsvPalette.substr(idEnd + 1, metaEnd - idEnd - 1), meta)) + { + throw LoadFailedException(Printf("Failed to parse meta on line %u", line)); + } + if (meta > 15) + { + throw LoadFailedException(Printf("Invalid meta value on line %u: %u", line, meta)); + } + id = (id * 16) | meta; + } + auto blockTypeEnd = findNextSeparator(aTsvPalette, metaEnd + 1); + if (blockTypeEnd == AString::npos) + { + throw LoadFailedException(Printf("Incomplete data on line %u (blockTypeName)", line)); + } + auto blockTypeName = aTsvPalette.substr(metaEnd + 1, blockTypeEnd - metaEnd - 1); + auto blockStateEnd = blockTypeEnd; + AStringMap blockState; + while (aTsvPalette[blockStateEnd] == '\t') + { + auto keyEnd = findNextSeparator(aTsvPalette, blockStateEnd + 1); + if ((keyEnd == AString::npos) || (aTsvPalette[keyEnd] != '\t')) + { + throw LoadFailedException(Printf("Incomplete data on line %u (blockState key)", line)); + } + auto valueEnd = findNextSeparator(aTsvPalette, keyEnd + 1); + if (valueEnd == AString::npos) + { + throw LoadFailedException(Printf("Incomplete data on line %u (blockState value)", line)); + } + auto key = aTsvPalette.substr(blockStateEnd + 1, keyEnd - blockStateEnd - 1); + auto value = aTsvPalette.substr(keyEnd + 1, valueEnd - keyEnd - 1); + blockState[key] = value; + blockStateEnd = valueEnd; + } + addMapping(id, commonPrefix + blockTypeName, std::move(blockState)); + ++line; + if (aTsvPalette[blockStateEnd] == '\r') // CR of the CRLF pair, skip the LF: + { + ++blockStateEnd; + } + idx = blockStateEnd; + } +} + + + + + +void BlockTypePalette::addMapping(UInt32 aID, const AString & aBlockTypeName, const BlockState & aBlockState) +{ + mNumberToBlock[aID] = {aBlockTypeName, aBlockState}; + mBlockToNumber[aBlockTypeName][aBlockState] = aID; + if (aID > mMaxIndex) + { + mMaxIndex = aID; + } +} diff --git a/src/Bindings/BlockTypePalette.h b/src/Bindings/BlockTypePalette.h new file mode 100644 index 000000000..2aade422b --- /dev/null +++ b/src/Bindings/BlockTypePalette.h @@ -0,0 +1,138 @@ +#pragma once + +#include <utility> +#include "BlockState.h" + + + + + +/** Holds a palette that maps between block type + state and numbers. +Used primarily by PalettedBlockArea to map from stringular block representation to numeric, +and by protocols to map from stringular block representation to protocol-numeric. +The object itself provides no thread safety, users of this class need to handle locking, if required. +Note that the palette itself doesn't support erasing; +to erase, create a new instance and re-add only the wanted items. + +Internally, the object uses two synced maps, one for each translation direction. + +The palette can be loaded from a string (file). The loader supports either the blocks.json file exported by +the vanilla server itself (https://wiki.vg/Data_Generators), or a processed text file generated by +our tool $/Tools/BlockTypePaletteGenerator/, or a hand-written text file describing the upgrade from +1.12 BlockType + BlockMeta to 1.13 string representations. +The text file is a TSV (tab-separated values), which basically means the data is generally structured as +<value1><tab><value2><tab><value3><tab>...<valueN><eol>, where eol is the platform's CR / CRLF / LF lineend. +The file starts with a single value on the first line, "BlockTypePalette" or "UpgradeBlockTypePalette", which +is used to detect the file format. The following lines are "headers", simple <key><tab><value><eol> entries +that contain the metadata about the file. "FileVersion" is a compulsory key, "CommonPrefix" is supported, others +are ignored. +The headers are followed by an empty line (that signalizes the end of headers) and then the actual data. +For regular BlockTypePalette TSV file of version 1, the data is in the format: +<index><tab><blockTypeName><tab><state1Name><tab><state1Value><tab><state2Name> ... <eol> +For the UpgradeBlockTypePalette TSV file of version 1, the data is in the format: +<blockType><tab><blockMeta><tab><blockTypeName><tab><state1Name><tab><state1Value><tab><state2Name> ... <eol> +If a CommonPrefix header is present, its value is pre-pended to each blockTypeName loaded (thus allowing +the file to be overall smaller). */ +class BlockTypePalette +{ +public: + + /** Exception that is thrown if requiesting an index not present in the palette. */ + class NoSuchIndexException: + public std::runtime_error + { + using Super = std::runtime_error; + + public: + NoSuchIndexException(UInt32 aIndex): + Super(Printf("No such palette index: %u", aIndex)) + { + } + }; + + + /** Exception that is thrown when loading the palette fails hard (bad format). */ + class LoadFailedException: + public std::runtime_error + { + using Super = std::runtime_error; + + public: + LoadFailedException(const AString & aReason): + Super(aReason) + { + } + }; + + + + /** Create a new empty instance. */ + BlockTypePalette(); + + /** Returns the index of the specified block type name and state. + If the combination is not found, it is added to the palette and the new index is returned. */ + UInt32 index(const AString & aBlockTypeName, const BlockState & aBlockState); + + /** Returns the <index, true> of the specified block type name and state, if it exists. + If the combination is not found, returns <undefined, false>. */ + std::pair<UInt32, bool> maybeIndex(const AString & aBlockTypeName, const BlockState & aBlockState) const; + + /** Returns the total number of entries in the palette. */ + UInt32 count() const; + + /** Returns the blockspec represented by the specified palette index. + If the index is not valid, throws a NoSuchIndexException. */ + const std::pair<AString, BlockState> & entry(UInt32 aIndex) const; + + /** Returns an index-transform map from aFrom to this (this.entry(idx) == aFrom.entry(res[idx])). + Entries from aFrom that are not present in this are added. + Used when pasting two areas, to transform the src palette to dst palette. */ + std::map<UInt32, UInt32> createTransformMapAddMissing(const BlockTypePalette & aFrom); + + /** Returns an index-transform map from aFrom to this (this.entry(idx) == aFrom.entry(res[idx])). + Entries from aFrom that are not present in this are assigned the fallback index. + Used for protocol block type mapping. */ + std::map<UInt32, UInt32> createTransformMapWithFallback(const BlockTypePalette & aFrom, UInt32 aFallbackIndex) const; + + /** Loads the palette from the string representation. + Throws a LoadFailedException if the loading fails hard (bad string format); + but still a part of the data may already be loaded at that point. + If the string specifies duplicate entries (either to already existing entries, or to itself), + the duplicates replace the current values silently (this allows us to chain multiple files as "overrides". + Auto-detects the string format (json / tsv, normal / upgrade palette) and calls the appropriate load function. */ + void loadFromString(const AString & aString); + + +protected: + + /** The mapping from numeric to stringular representation. + mNumberToBlock[index] = {"blockTypeName", blockState}. */ + std::map<UInt32, std::pair<AString, BlockState>> mNumberToBlock; + + /** The mapping from stringular to numeric representation. + mStringToNumber["blockTypeName"][blockState] = index. */ + std::unordered_map<AString, std::map<BlockState, UInt32>> mBlockToNumber; + + /** The maximum index ever used in the maps. + Used when adding new entries through the index() call. */ + UInt32 mMaxIndex; + + + /** Loads the palette from the JSON representation, https://wiki.vg/Data_Generators + Throws a LoadFailedException if the loading fails hard (bad string format); + but still a part of the data may already be loaded at that point. + See also: loadFromString(). */ + void loadFromJsonString(const AString & aJsonPalette); + + /** Loads the palette from the regular or upgrade TSV representation. + aIsUpgrade specifies whether the format is an upgrade TSV (true) or a regular one (false) + Throws a LoadFailedException if the loading fails hard (bad string format); + but still a part of the data may already be loaded at that point. + See also: loadFromString(). */ + void loadFromTsv(const AString & aTsvPalette, bool aIsUpgrade); + + /** Adds a mapping between the numeric and stringular representation into both maps, + updates the mMaxIndex, if appropriate. + Silently overwrites any previous mapping for the ID, if present, but keeps the old string->id mapping. */ + void addMapping(UInt32 aID, const AString & aBlockTypeName, const BlockState & aBlockState); +}; diff --git a/src/Bindings/BlockTypeRegistry.cpp b/src/Bindings/BlockTypeRegistry.cpp new file mode 100644 index 000000000..491e03593 --- /dev/null +++ b/src/Bindings/BlockTypeRegistry.cpp @@ -0,0 +1,240 @@ + +#include "Globals.h" +#include "BlockTypeRegistry.h" + + + + +//////////////////////////////////////////////////////////////////////////////// +// BlockInfo: + +BlockInfo::BlockInfo( + const AString & aPluginName, + const AString & aBlockTypeName, + std::shared_ptr<cBlockHandler> aHandler, + const std::map<AString, AString> & aHints, + const std::map<AString, BlockInfo::HintCallback> & aHintCallbacks +): + m_PluginName(aPluginName), + m_BlockTypeName(aBlockTypeName), + m_Handler(std::move(aHandler)), + m_Hints(aHints), + m_HintCallbacks(aHintCallbacks) +{ +} + + + + + +AString BlockInfo::hintValue( + const AString & aHintName, + const BlockState & aBlockState +) +{ + // Search the hint callbacks first: + auto itrC = m_HintCallbacks.find(aHintName); + if (itrC != m_HintCallbacks.end()) + { + // Hint callback found, use it: + return itrC->second(m_BlockTypeName, aBlockState); + } + + // Search the static hints: + auto itr = m_Hints.find(aHintName); + if (itr != m_Hints.end()) + { + // Hint found, use it: + return itr->second; + } + + // Nothing found, return empty string: + return AString(); +} + + + + + +void BlockInfo::setHint(const AString & aHintKey, const AString & aHintValue) +{ + m_Hints[aHintKey] = aHintValue; + + // Warn if the hint is already provided by a callback (aHintValue will be ignored when evaluating the hint): + auto itrC = m_HintCallbacks.find(aHintKey); + if (itrC != m_HintCallbacks.end()) + { + LOGINFO("Setting a static hint %s for block type %s, but there's already a callback for that hint. The static hint will be ignored.", + aHintKey.c_str(), m_BlockTypeName.c_str() + ); + } +} + + + + + +void BlockInfo::removeHint(const AString & aHintKey) +{ + m_Hints.erase(aHintKey); +} + + + + + +//////////////////////////////////////////////////////////////////////////////// +// BlockTypeRegistry: + +void BlockTypeRegistry::registerBlockType( + const AString & aPluginName, + const AString & aBlockTypeName, + std::shared_ptr<cBlockHandler> aHandler, + const std::map<AString, AString> & aHints, + const std::map<AString, BlockInfo::HintCallback> & aHintCallbacks +) +{ + auto blockInfo = std::make_shared<BlockInfo>( + aPluginName, aBlockTypeName, std::move(aHandler), aHints, aHintCallbacks + ); + + // Check previous registrations: + cCSLock lock(m_CSRegistry); + auto itr = m_Registry.find(aBlockTypeName); + if (itr != m_Registry.end()) + { + if (itr->second->pluginName() != aPluginName) + { + throw AlreadyRegisteredException(itr->second, blockInfo); + } + } + + // Store the registration: + m_Registry[aBlockTypeName] = blockInfo; +} + + + + + +std::shared_ptr<BlockInfo> BlockTypeRegistry::blockInfo(const AString & aBlockTypeName) +{ + cCSLock lock(m_CSRegistry); + auto itr = m_Registry.find(aBlockTypeName); + if (itr == m_Registry.end()) + { + return nullptr; + } + return itr->second; +} + + + + + +void BlockTypeRegistry::removeAllByPlugin(const AString & aPluginName) +{ + cCSLock lock(m_CSRegistry); + for (auto itr = m_Registry.begin(); itr != m_Registry.end();) + { + if (itr->second->pluginName() == aPluginName) + { + itr = m_Registry.erase(itr); + } + else + { + ++itr; + } + } +} + + + + + +void BlockTypeRegistry::setBlockTypeHint( + const AString & aBlockTypeName, + const AString & aHintKey, + const AString & aHintValue +) +{ + cCSLock lock(m_CSRegistry); + auto blockInfo = m_Registry.find(aBlockTypeName); + if (blockInfo == m_Registry.end()) + { + throw NotRegisteredException(aBlockTypeName, aHintKey, aHintValue); + } + blockInfo->second->setHint(aHintKey, aHintValue); +} + + + + + +void BlockTypeRegistry::removeBlockTypeHint( + const AString & aBlockTypeName, + const AString & aHintKey +) +{ + cCSLock lock(m_CSRegistry); + auto blockInfo = m_Registry.find(aBlockTypeName); + if (blockInfo == m_Registry.end()) + { + return; + } + blockInfo->second->removeHint(aHintKey); +} + + + + + +//////////////////////////////////////////////////////////////////////////////// +// BlockTypeRegistry::AlreadyRegisteredException: + +BlockTypeRegistry::AlreadyRegisteredException::AlreadyRegisteredException( + const std::shared_ptr<BlockInfo> & aPreviousRegistration, + const std::shared_ptr<BlockInfo> & aNewRegistration +) : + Super(message(aPreviousRegistration, aNewRegistration)), + m_PreviousRegistration(aPreviousRegistration), + m_NewRegistration(aNewRegistration) +{ +} + + + + + +AString BlockTypeRegistry::AlreadyRegisteredException::message( + const std::shared_ptr<BlockInfo> & aPreviousRegistration, + const std::shared_ptr<BlockInfo> & aNewRegistration +) +{ + return Printf("Attempting to register BlockTypeName %s from plugin %s, while it is already registered in plugin %s", + aNewRegistration->blockTypeName().c_str(), + aNewRegistration->pluginName().c_str(), + aPreviousRegistration->pluginName().c_str() + ); +} + + + + + +//////////////////////////////////////////////////////////////////////////////// +// BlockTypeRegistry::NotRegisteredException: + +BlockTypeRegistry::NotRegisteredException::NotRegisteredException( + const AString & aBlockTypeName, + const AString & aHintKey, + const AString & aHintValue +): + Super(Printf( + "Attempting to set a hint of nonexistent BlockTypeName.\n\tBlockTypeName = %s\n\tHintKey = %s\n\tHintValue = %s", + aBlockTypeName.c_str(), + aHintKey.c_str(), + aHintValue.c_str() + )) +{ +} diff --git a/src/Bindings/BlockTypeRegistry.h b/src/Bindings/BlockTypeRegistry.h new file mode 100644 index 000000000..3a85ee510 --- /dev/null +++ b/src/Bindings/BlockTypeRegistry.h @@ -0,0 +1,216 @@ +#pragma once + + + + + +#include <map> +#include <functional> + + + + + +// fwd: +class cBlockHandler; +class BlockState; + + + + + +/** Complete information about a single block type. +The BlockTypeRegistry uses this structure to store the registered information. */ +class BlockInfo +{ +public: + + /** Callback is used to query block hints dynamically, based on the current BlockState. + Useful for example for redstone lamps that can be turned on or off. */ + using HintCallback = std::function<AString(const AString & aTypeName, const BlockState & aBlockState)>; + + + /** Creates a new instance with the specified BlockTypeName and handler / hints / callbacks. + aPluginName specifies the name of the plugin to associate with the block type (to allow unload / reload). */ + BlockInfo( + const AString & aPluginName, + const AString & aBlockTypeName, + std::shared_ptr<cBlockHandler> aHandler, + const std::map<AString, AString> & aHints = std::map<AString, AString>(), + const std::map<AString, HintCallback> & aHintCallbacks = std::map<AString, HintCallback>() + ); + + + /** Retrieves the value associated with the specified hint for this specific BlockTypeName and BlockState. + Queries hint callbacks first, then static hints if a callback doesn't exist. + Returns an empty string if hint not found at all. */ + AString hintValue( + const AString & aHintName, + const BlockState & aBlockState + ); + + // Simple getters: + const AString & pluginName() const { return m_PluginName; } + const AString & blockTypeName() const { return m_BlockTypeName; } + std::shared_ptr<cBlockHandler> handler() const { return m_Handler; } + + /** Sets (creates or updates) a static hint. + Hints provided by callbacks are unaffected by this - callbacks are "higher priority", they overwrite anything set here. + Logs an info message if the hint is already provided by a hint callback. */ + void setHint(const AString & aHintKey, const AString & aHintValue); + + /** Removes a hint. + Silently ignored if the hint hasn't been previously set. */ + void removeHint(const AString & aHintKey); + + +private: + + /** The name of the plugin that registered the block. */ + AString m_PluginName; + + /** The name of the block type, such as "minecraft:redstone_lamp" */ + AString m_BlockTypeName; + + /** The callbacks to call for various interaction. */ + std::shared_ptr<cBlockHandler> m_Handler; + + /** Optional static hints for any subsystem to use, such as "IsSnowable" -> "1". + Hint callbacks are of higher priority than m_Hints - if a hint is provided by a m_HintCallback, its value in m_Hints is ignored. */ + std::map<AString, AString> m_Hints; + + /** The callbacks for dynamic evaluation of hints, such as "LightValue" -> function(BlockTypeName, BlockState). + Hint callbacks are of higher priority than m_Hints - if a hint is provided by a m_HintCallback, its value in m_Hints is ignored. */ + std::map<AString, HintCallback> m_HintCallbacks; +}; + + + + + +/** Stores information on all known block types. +Can dynamically add and remove block types. +Block types are identified using BlockTypeName. +Supports unregistering and re-registering the same type by the same plugin. +Stores the name of the plugin that registered the type, for better plugin error messages ("already registered in X") +and so that we can unload and reload plugins. */ +class BlockTypeRegistry +{ +public: + // fwd: + class AlreadyRegisteredException; + class NotRegisteredException; + + + /** Creates an empty new instance of the block type registry */ + BlockTypeRegistry() = default; + + /** Registers the specified block type. + If the block type already exists and the plugin is the same, updates the registration. + If the block type already exists and the plugin is different, throws an AlreadyRegisteredException. */ + void registerBlockType( + const AString & aPluginName, + const AString & aBlockTypeName, + std::shared_ptr<cBlockHandler> aHandler, + const std::map<AString, AString> & aHints = std::map<AString, AString>(), + const std::map<AString, BlockInfo::HintCallback> & aHintCallbacks = std::map<AString, BlockInfo::HintCallback>() + ); + + /** Returns the registration information for the specified BlockTypeName. + Returns nullptr if BlockTypeName not found. */ + std::shared_ptr<BlockInfo> blockInfo(const AString & aBlockTypeName); + + /** Removes all registrations done by the specified plugin. */ + void removeAllByPlugin(const AString & aPluginName); + + /** Sets (adds or overwrites) a single Hint value for a BlockType. + Throws NotRegisteredException if the BlockTypeName is not registered. */ + void setBlockTypeHint( + const AString & aBlockTypeName, + const AString & aHintKey, + const AString & aHintValue + ); + + /** Removes a previously registered single Hint value for a BlockType. + Throws NotRegisteredException if the BlockTypeName is not registered. + Silently ignored if the Hint hasn't been previously set. */ + void removeBlockTypeHint( + const AString & aBlockTypeName, + const AString & aHintKey + ); + + +private: + + /** The actual block type registry. + Maps the BlockTypeName to the BlockInfo instance. */ + std::map<AString, std::shared_ptr<BlockInfo>> m_Registry; + + /** The CS that protects m_Registry against multithreaded access. */ + cCriticalSection m_CSRegistry; +}; + + + + + +/** The exception thrown from BlockTypeRegistry::registerBlockType() if the same block type is being registered from a different plugin. */ +class BlockTypeRegistry::AlreadyRegisteredException: public std::runtime_error +{ + using Super = std::runtime_error; + +public: + + /** Creates a new instance of the exception that provides info on both the original registration and the newly attempted + registration that caused the failure. */ + AlreadyRegisteredException( + const std::shared_ptr<BlockInfo> & aPreviousRegistration, + const std::shared_ptr<BlockInfo> & aNewRegistration + ); + + // Simple getters: + std::shared_ptr<BlockInfo> previousRegistration() const { return m_PreviousRegistration; } + std::shared_ptr<BlockInfo> newRegistration() const { return m_NewRegistration; } + + +private: + + std::shared_ptr<BlockInfo> m_PreviousRegistration; + std::shared_ptr<BlockInfo> m_NewRegistration; + + + /** Returns the general exception message formatted by the two registrations. + The output is used when logging. */ + static AString message( + const std::shared_ptr<BlockInfo> & aPreviousRegistration, + const std::shared_ptr<BlockInfo> & aNewRegistration + ); +}; + + + + + +/** The exception thrown from BlockTypeRegistry::setBlockTypeHint() if the block type has not been registered before. */ +class BlockTypeRegistry::NotRegisteredException: public std::runtime_error +{ + using Super = std::runtime_error; + +public: + + /** Creates a new instance of the exception that provides info on both the original registration and the newly attempted + registration that caused the failure. */ + NotRegisteredException( + const AString & aBlockTypeName, + const AString & aHintKey, + const AString & aHintValue + ); + + // Simple getters: + const AString & blockTypeName() const { return m_BlockTypeName; } + + +private: + + const AString m_BlockTypeName; +}; |