1
0

Moved ProtocolBlockTypePalette functionality into BlockTypePalette.

This commit is contained in:
Mattes D 2019-12-02 16:45:55 +01:00
parent 2de6b7537d
commit 7453a9fbe1
11 changed files with 268 additions and 404 deletions

View File

@ -1,5 +1,8 @@
#include "Globals.h" #include "Globals.h"
#include "BlockTypePalette.h" #include "BlockTypePalette.h"
#include "json/value.h"
#include "json/reader.h"
@ -113,3 +116,82 @@ std::map<UInt32, UInt32> BlockTypePalette::createTransformMapWithFallback(const
} }
return res; return res;
} }
void BlockTypePalette::loadFromString(const AString & aString)
{
// TODO: Detect format (Json vs Lua)
return loadFromJsonString(aString);
}
void BlockTypePalette::loadFromJsonString(const AString & aJsonPalette)
{
// Parse the string into JSON object:
Json::Value root;
Json::CharReaderBuilder builder;
std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
std::string errs;
if (!reader->parse(aJsonPalette.data(), aJsonPalette.data() + aJsonPalette.size(), &root, &errs))
{
throw LoadFailedException(errs);
}
// Check the JSON's metadata + version:
if (!root.isObject() ||
!root.isMember("Metadata") ||
!root["Metadata"].isMember("ProtocolBlockTypePaletteVersion") ||
!root.isMember("Palette") ||
!root["Palette"].isArray())
{
throw LoadFailedException("Incorrect palette format, wrong or missing metadata.");
}
auto version = root["Metadata"]["ProtocolBlockTypePaletteVersion"].asUInt();
if (version != 1)
{
throw(Printf("Palette format version %d not supported.", version));
}
// Load the palette:
auto len = root["Palette"].size();
for (decltype(len) i = 0; i < len; ++i)
{
const auto & record = root["Palette"][i];
if (!record.isObject())
{
throw LoadFailedException(Printf("Palette record #%u is not a JSON object.", i));
}
auto blockTypeName = record["name"].asString();
auto id = static_cast<UInt32>(std::stoul(record["id"].asString()));
std::map<AString, AString> state;
if (record.isMember("props"))
{
const auto & props = record["props"];
if (!props.isObject())
{
throw LoadFailedException(Printf("Palette record #%u: \"props\" value is not a JSON object.", i));
}
for (const auto & key: props.getMemberNames())
{
state[key] = props[key].asString();
}
}
BlockState blockState(state);
// Insert / update in the maps:
mNumberToBlock[id] = {blockTypeName, blockState};
mBlockToNumber[blockTypeName][blockState] = id;
if (id > mMaxIndex)
{
mMaxIndex = id;
}
} // for i - Palette[]
}

View File

@ -34,6 +34,20 @@ public:
}; };
/** 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. */ /** Create a new empty instance. */
BlockTypePalette(); BlockTypePalette();
@ -63,6 +77,13 @@ public:
Used for protocol block type mapping. */ Used for protocol block type mapping. */
std::map<UInt32, UInt32> createTransformMapWithFallback(const BlockTypePalette & aFrom, UInt32 aFallbackIndex) const; 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).
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".
Currently handles only JSON representation, expected to handle also Lua representation in the future. */
void loadFromString(const AString & aString);
protected: protected:
@ -77,4 +98,11 @@ protected:
/** The maximum index ever used in the maps. /** The maximum index ever used in the maps.
Used when adding new entries through the index() call. */ Used when adding new entries through the index() call. */
UInt32 mMaxIndex; UInt32 mMaxIndex;
/** Loads the palette from the JSON representation.
Throws a LoadFailedException if the loading fails hard (bad string format).
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". */
void loadFromJsonString(const AString & aJsonPalette);
}; };

View File

@ -13,7 +13,6 @@ SET (SRCS
Protocol_1_12.cpp Protocol_1_12.cpp
Protocol_1_13.cpp Protocol_1_13.cpp
ProtocolRecognizer.cpp ProtocolRecognizer.cpp
ProtocolBlockTypePalette.cpp
) )
SET (HDRS SET (HDRS
@ -30,7 +29,6 @@ SET (HDRS
Protocol_1_12.h Protocol_1_12.h
Protocol_1_13.h Protocol_1_13.h
ProtocolRecognizer.h ProtocolRecognizer.h
ProtocolBlockTypePalette.h
) )
if (NOT MSVC) if (NOT MSVC)

View File

@ -1,144 +0,0 @@
#include "Globals.h"
#include "ProtocolBlockTypePalette.h"
#include <cstdint>
#include <sstream>
#include "json/value.h"
#include "json/reader.h"
ProtocolBlockTypePalette::ProtocolBlockTypePalette()
{
// empty
}
bool ProtocolBlockTypePalette::loadFromString(const AString & aMapping)
{
std::stringstream stream;
stream << aMapping;
return loadFromStream(stream);
}
bool ProtocolBlockTypePalette::loadFromStream(std::istream & aInputStream)
{
Json::Value root;
try
{
aInputStream >> root;
}
#if defined _DEBUG
catch (const std::exception & e)
{
LOGD(e.what());
return false;
}
#else
catch (const std::exception &)
{
return false;
}
#endif
if (!root.isObject() ||
!root.isMember("Metadata") ||
!root["Metadata"].isMember("ProtocolBlockTypePaletteVersion") ||
!root.isMember("Palette") ||
!root["Palette"].isArray())
{
LOGD("Incorrect palette format.");
return false;
}
if (root["Metadata"]["ProtocolBlockTypePaletteVersion"].asUInt() != 1)
{
LOGD("Palette format version not supported.");
return false;
}
auto len = root["Palette"].size();
for (decltype(len) i = 0; i < len; ++i)
{
const auto & record = root["Palette"][i];
if (!record.isObject())
{
LOGD("Record #%u must be a JSON object.", i);
return false;
}
auto blocktype = record["name"].asString();
auto id = std::stoul(record["id"].asString());
std::map<AString, AString> state;
if (id >= NOT_FOUND)
{
LOGD("`id` must be less than ProtocolBlockTypePalette::NOT_FOUND, but is %lu", id);
return false;
}
if (record.isMember("props"))
{
const auto & props = record["props"];
if (!props.isObject())
{
LOGD("`props` key must be a JSON object.");
return false;
}
for (const auto & key: props.getMemberNames())
{
state[key] = props[key].asString();
}
}
// Block type map entry already exists?
if (mIndex.count(blocktype) == 0)
{
mIndex.insert({blocktype, std::map<BlockState, UInt32>()});
}
const auto & result = mIndex[blocktype].insert({BlockState(state), id});
if (result.second == false)
{
LOGINFO("Duplicate block state encountered (Current ID: %lu, other: %lu)", result.first->second, id);
}
}
return true;
}
UInt32 ProtocolBlockTypePalette::index(const AString & aBlockTypeName, const BlockState & aBlockState) const
{
auto a = mIndex.find(aBlockTypeName);
if (a != mIndex.end())
{
auto b = a->second.find(aBlockState);
if (b != a->second.end())
{
return b->second;
}
}
return NOT_FOUND;
}
void ProtocolBlockTypePalette::clear()
{
return mIndex.clear();
}

View File

@ -1,40 +0,0 @@
#pragma once
#include <unordered_map>
#include "../BlockState.h"
/** Parses and holds a collection of block types and their possible states
together with their corresponding Id within the Minecraft network protocol. */
class ProtocolBlockTypePalette
{
public:
static const UInt32 NOT_FOUND = UINT32_MAX;
/** Create a new empty instance. */
ProtocolBlockTypePalette();
/** Loads the palette from a string.
See loadFromStream() for further details. */
bool loadFromString(const AString & aMapping);
/** Loads the palette from an input stream.
Returns `true` on success, `false` otherwise. Sucessive calls to this method
will _add_ data to the palette. If duplicate keys are encountered, they will
be ignored and an info message logged. */
bool loadFromStream(std::istream & aInputStream);
/** Returns the defined index corresponding of the given aBlockTypeName and
aBlockState.
Returns ProtocolBlockTypePalette::NOT_FOUND if the tuple is not found. */
UInt32 index(const AString & aBlockTypeName, const BlockState & aBlockState) const;
/** Clears the palette. */
void clear();
protected:
/** The palette index. Each item in the map represents a single block state
palette entry. The value is the block state ID. */
std::unordered_map<AString, std::map<BlockState, UInt32>> mIndex;
};

View File

@ -123,10 +123,158 @@ static void testTransformWithFallback()
/** Tests that loading a simple JSON palette succeeds. */
static void testLoadSimpleSuccess(void)
{
LOG("Testing loading a simple JSON palette");
BlockTypePalette palette;
auto example = "{\"Metadata\":{\"ProtocolBlockTypePaletteVersion\":1}, \"Palette\":[{\
\"props\": {\
\"foo\": \"bar\"\
}, \
\"name\": \"b\", \
\"id\": \"0\"\
}]}";
palette.loadFromString(example);
TEST_EQUAL(palette.maybeIndex("b", BlockState({{"foo", "bar"}})), (std::make_pair<UInt32, bool>(0, true)));
TEST_EQUAL(palette.maybeIndex("b", BlockState({{"foo", "baz"}})), (std::make_pair<UInt32, bool>(0, false)));
TEST_EQUAL(palette.maybeIndex("a", BlockState({{"foo", "bar"}})), (std::make_pair<UInt32, bool>(0, false)));
}
static void testLoadErrors(void)
{
LOG("Testing palette load error reporting.");
BlockTypePalette palette;
TEST_THROWS(palette.loadFromString(""), BlockTypePalette::LoadFailedException);
TEST_THROWS(palette.loadFromString("[]"), BlockTypePalette::LoadFailedException);
TEST_THROWS(palette.loadFromString("a = {}"), BlockTypePalette::LoadFailedException);
TEST_THROWS(palette.loadFromString("{x = 1}"), BlockTypePalette::LoadFailedException); // Lua style
TEST_THROWS(palette.loadFromString("$#^%&"), BlockTypePalette::LoadFailedException);
}
static void testLoadComplex1(void)
{
LOG("Testing loading a complex palette (1)");
BlockTypePalette palette;
auto str = "{\"Metadata\":{\"ProtocolBlockTypePaletteVersion\":1}, \"Palette\":[{\
\"props\": {\
\"foo\": \"bar\", \
\"moo\": \"baz\"\
}, \
\"id\": \"0\", \
\"name\": \"b\"\
}, {\
\"props\": {\
\"foo\": \"baz\", \
\"moo\": \"bar\"\
}, \
\"id\": \"1\", \
\"name\": \"b\"\
}, {\
\"props\": {\
\"foo\": \"baz\", \
\"moo\": \"bar\"\
}, \
\"id\": \"1001\", \
\"name\": \"b\"\
}]}";
// Note: The palette has a duplicate entry with differrent IDs, the latter ID wins
palette.loadFromString(str);
TEST_EQUAL(palette.maybeIndex("b", {{"foo", "bar"}}).second, false);
TEST_EQUAL(palette.maybeIndex("b", {{"foo", "bar"}, {"moo", "baz"}}), (std::make_pair<UInt32, bool>(0, true)));
TEST_EQUAL(palette.maybeIndex("b", {{"foo", "baz"}, {"moo", "bar"}}), (std::make_pair<UInt32, bool>(1001, true)));
TEST_EQUAL(palette.maybeIndex("c", {{"foo", "baz"}, {"moo", "bar"}}).second, false);
}
static void testLoadComplex2(void)
{
LOG("Testing loading a complex palette (2)");
BlockTypePalette palette;
auto str = "{\"Metadata\":{\"ProtocolBlockTypePaletteVersion\":1}, \"Palette\":[{\
\"id\": \"0\", \
\"name\": \"a\"\
}, {\
\"id\": \"1\", \
\"name\": \"b\"\
}]}";
palette.loadFromString(str);
TEST_EQUAL(palette.maybeIndex("a", BlockState()), (std::make_pair<UInt32, bool>(0, true)));
TEST_EQUAL(palette.maybeIndex("b", BlockState()), (std::make_pair<UInt32, bool>(1, true)));
}
static void testLoadFromFile1(void)
{
LOG("Testing loading a palette from file \"test.btp.json\"");
BlockTypePalette palette;
palette.loadFromString(cFile::ReadWholeFile("test.btp.json"));
TEST_EQUAL(palette.maybeIndex("minecraft:air", BlockState()), (std::make_pair<UInt32, bool>(0, true)));
TEST_EQUAL(palette.maybeIndex("minecraft:stone", BlockState()), (std::make_pair<UInt32, bool>(1, true)));
TEST_EQUAL(
palette.maybeIndex(
"minecraft:dark_oak_leaves",
BlockState({{"persistent", "false"}, {"distance", "6"}})
),
(std::make_pair<UInt32, bool>(225, true))
);
TEST_EQUAL(
palette.maybeIndex(
"minecraft:dark_oak_leaves",
BlockState({{"persistent", "false"}})
).second,
false
);
}
static void testLoadFromFile2(void)
{
LOG("Testing loading a palette from file \"base.btp.json\" (version 1.13)");
BlockTypePalette palette;
palette.loadFromString(cFile::ReadWholeFile("base.btp.json"));
TEST_EQUAL(palette.maybeIndex("minecraft:air", BlockState()), (std::make_pair<UInt32, bool>(0, true)));
TEST_EQUAL(palette.maybeIndex("minecraft:stone", BlockState()), (std::make_pair<UInt32, bool>(1, true)));
TEST_EQUAL(palette.maybeIndex("minecraft:dirt", BlockState()), (std::make_pair<UInt32, bool>(10, true)));
}
IMPLEMENT_TEST_MAIN("BlockTypePalette", IMPLEMENT_TEST_MAIN("BlockTypePalette",
testBasic(); testBasic();
testTransformAddMissing(); testTransformAddMissing();
testTransformWithFallback(); testTransformWithFallback();
testLoadSimpleSuccess();
testLoadErrors();
testLoadComplex1();
testLoadComplex2();
testLoadFromFile1();
testLoadFromFile2();
) )

View File

@ -28,8 +28,9 @@ add_executable(BlockTypePaletteTest
${CMAKE_SOURCE_DIR}/src/BlockTypePalette.cpp ${CMAKE_SOURCE_DIR}/src/BlockTypePalette.cpp
${CMAKE_SOURCE_DIR}/src/StringUtils.cpp ${CMAKE_SOURCE_DIR}/src/StringUtils.cpp
${CMAKE_SOURCE_DIR}/src/OSSupport/CriticalSection.cpp ${CMAKE_SOURCE_DIR}/src/OSSupport/CriticalSection.cpp
${CMAKE_SOURCE_DIR}/src/OSSupport/File.cpp
) )
target_link_libraries(BlockTypePaletteTest fmt::fmt) target_link_libraries(BlockTypePaletteTest fmt::fmt jsoncpp_lib_static)
# BlockTypeRegistryTest: Verify that the BlockTypeRegistry class works as intended: # BlockTypeRegistryTest: Verify that the BlockTypeRegistry class works as intended:
add_executable(BlockTypeRegistryTest add_executable(BlockTypeRegistryTest
@ -53,7 +54,14 @@ add_executable(PalettedBlockAreaTest
${CMAKE_SOURCE_DIR}/src/StringUtils.cpp ${CMAKE_SOURCE_DIR}/src/StringUtils.cpp
${CMAKE_SOURCE_DIR}/src/OSSupport/CriticalSection.cpp ${CMAKE_SOURCE_DIR}/src/OSSupport/CriticalSection.cpp
) )
target_link_libraries(PalettedBlockAreaTest fmt::fmt) target_link_libraries(PalettedBlockAreaTest fmt::fmt jsoncpp_lib_static)
# Extra files for BlockTypePalette test:
file (COPY
test.btp.json
../../Server/Protocol/1.13/base.btp.json
DESTINATION ./
)

View File

@ -19,4 +19,3 @@ add_subdirectory(Network)
add_subdirectory(OSSupport) add_subdirectory(OSSupport)
add_subdirectory(SchematicFileSerializer) add_subdirectory(SchematicFileSerializer)
add_subdirectory(UUID) add_subdirectory(UUID)
add_subdirectory(ProtocolBlockTypePalette)

View File

@ -1,47 +0,0 @@
cmake_minimum_required(VERSION 3.0.2)
enable_testing()
add_definitions(-DTEST_GLOBALS=1)
include_directories(SYSTEM "../../lib/jsoncpp/include")
include_directories(${CMAKE_SOURCE_DIR}/src/)
add_definitions(-DTEST_GLOBALS=1)
set (SHARED_SRCS
${CMAKE_SOURCE_DIR}/src/Protocol/ProtocolBlockTypePalette.cpp
${CMAKE_SOURCE_DIR}/src/BlockState.cpp
${CMAKE_SOURCE_DIR}/src/StringUtils.cpp
)
set (SHARED_HDRS
../TestHelpers.h
${CMAKE_SOURCE_DIR}/src/Protocol/ProtocolBlockTypePalette.h
${CMAKE_SOURCE_DIR}/src/BlockState.h
${CMAKE_SOURCE_DIR}/src/StringUtils.h
)
set (SRCS
ProtocolBlockTypePaletteTest.cpp
)
file (COPY
test.btp.json
../../Server/Protocol/1.13/base.btp.json
DESTINATION ./)
source_group("Shared" FILES ${SHARED_SRCS} ${SHARED_HDRS})
source_group("Sources" FILES ${SRCS})
add_executable(ProtocolBlockTypePaletteTest-exe ${SRCS} ${SHARED_SRCS} ${SHARED_HDRS})
target_link_libraries(ProtocolBlockTypePaletteTest-exe fmt::fmt jsoncpp_lib_static)
add_test(NAME ProtocolBlockTypePaletteTest-test COMMAND ProtocolBlockTypePaletteTest-exe)
# Put the projects into solution folders (MSVC):
set_target_properties(
ProtocolBlockTypePaletteTest-exe
PROPERTIES FOLDER Tests
)

View File

@ -1,168 +0,0 @@
// ProtocolBlockTypePaletteTest.cpp
#include <string>
#include <fstream>
#include <streambuf>
#include "Globals.h"
#include "Protocol/ProtocolBlockTypePalette.h"
#include "../TestHelpers.h"
static void TestSuccess(void)
{
LOG("Test TestSuccess");
ProtocolBlockTypePalette palette;
auto example = "{\"Metadata\":{\"ProtocolBlockTypePaletteVersion\":1}, \"Palette\":[{\
\"props\": {\
\"foo\": \"bar\"\
}, \
\"name\": \"b\", \
\"id\": \"0\"\
}]}";
palette.clear();
TEST_TRUE(palette.loadFromString(example));
TEST_EQUAL(palette.index("b", BlockState({{"foo", "bar"}})), 0);
TEST_EQUAL(palette.index("b", BlockState({{"foo", "baz"}})), ProtocolBlockTypePalette::NOT_FOUND);
TEST_EQUAL(palette.index("a", BlockState({{"foo", "bar"}})), ProtocolBlockTypePalette::NOT_FOUND);
}
static void TestErrors(void)
{
LOG("Test TestErrors");
ProtocolBlockTypePalette palette;
TEST_FALSE(palette.loadFromString(""));
palette.clear();
TEST_FALSE(palette.loadFromString("[]"));
palette.clear();
TEST_FALSE(palette.loadFromString("a = {}"));
palette.clear();
TEST_FALSE(palette.loadFromString("{x = 1}")); // Lua style
palette.clear();
TEST_FALSE(palette.loadFromString("$#^%&"));
}
static void TestComplex1(void)
{
LOG("Test TestComplex1");
ProtocolBlockTypePalette palette;
auto str = "{\"Metadata\":{\"ProtocolBlockTypePaletteVersion\":1}, \"Palette\":[{\
\"props\": {\
\"foo\": \"bar\", \
\"moo\": \"baz\"\
}, \
\"id\": \"0\", \
\"name\": \"b\"\
}, {\
\"props\": {\
\"foo\": \"baz\", \
\"moo\": \"bar\"\
}, \
\"id\": \"1\", \
\"name\": \"b\"\
}, {\
\"props\": {\
\"foo\": \"baz\", \
\"moo\": \"bar\"\
}, \
\"id\": \"1001\", \
\"name\": \"b\"\
}]}";
TEST_TRUE(palette.loadFromString(str)); // This should print info message about duplicate ID
TEST_EQUAL(palette.index("b", BlockState({{"foo","bar"}})), ProtocolBlockTypePalette::NOT_FOUND);
TEST_EQUAL(palette.index("b", BlockState({{"foo","bar"}, {"moo","baz"}})), 0);
TEST_EQUAL(palette.index("b", BlockState({{"foo","baz"}, {"moo","bar"}})), 1);
TEST_EQUAL(palette.index("c", BlockState({{"foo","baz"}, {"moo","bar"}})), ProtocolBlockTypePalette::NOT_FOUND);
}
static void TestComplex2(void)
{
LOG("Test TestComplex2");
ProtocolBlockTypePalette palette;
auto str = "{\"Metadata\":{\"ProtocolBlockTypePaletteVersion\":1}, \"Palette\":[{\
\"id\": \"0\", \
\"name\": \"a\"\
}, {\
\"id\": \"1\", \
\"name\": \"b\"\
}]}";
TEST_TRUE(palette.loadFromString(str));
TEST_EQUAL(palette.index("a", BlockState()), 0);
TEST_EQUAL(palette.index("b", BlockState({})), 1);
}
static void TestFile(void)
{
LOG("Test TestFile");
std::ifstream f("base.btp.json");
ProtocolBlockTypePalette palette;
TEST_TRUE(palette.loadFromStream(f));
// This is a bit problematic - the only permanently fixed block Id is air...
TEST_EQUAL(palette.index("minecraft:air", BlockState()), 0);
TEST_NOTEQUAL(palette.index("minecraft:stone", BlockState()), ProtocolBlockTypePalette::NOT_FOUND);
TEST_NOTEQUAL(palette.index("minecraft:dirt", BlockState()), ProtocolBlockTypePalette::NOT_FOUND);
}
static void TestFile2(void)
{
LOG("Test TestFile2");
std::ifstream f("test.btp.json");
ProtocolBlockTypePalette palette;
TEST_TRUE(palette.loadFromStream(f));
TEST_EQUAL(palette.index("minecraft:air", BlockState({})), 0);
TEST_EQUAL(palette.index("minecraft:stone", BlockState()), 1);
TEST_EQUAL(palette.index("minecraft:stone", BlockState()), 1);
TEST_EQUAL(palette.index(
"minecraft:dark_oak_leaves",
BlockState({{"persistent", "false"}, {"distance", "6"}})
), 225);
TEST_EQUAL(palette.index(
"minecraft:dark_oak_leaves",
BlockState({{"persistent", "false"}})
), ProtocolBlockTypePalette::NOT_FOUND);
}
IMPLEMENT_TEST_MAIN("ProtocolBlockTypePaletteTest",
TestSuccess();
TestErrors();
TestComplex1();
TestComplex2();
TestFile();
TestFile2();
)