1
0
Fork 0

Added BlockState implementation for 1.13 support.

This commit is contained in:
Mattes D 2019-08-23 16:17:05 +02:00
parent fcc836f2a5
commit 02fbf16865
5 changed files with 417 additions and 0 deletions

167
src/BlockState.cpp Normal file
View File

@ -0,0 +1,167 @@
#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-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;
}

90
src/BlockState.h Normal file
View File

@ -0,0 +1,90 @@
#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);
/** 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);
};

View File

@ -17,6 +17,7 @@ SET (SRCS
BlockArea.cpp
BlockID.cpp
BlockInfo.cpp
BlockState.cpp
BlockTypeRegistry.cpp
BrewingRecipes.cpp
Broadcaster.cpp
@ -84,6 +85,7 @@ SET (HDRS
BlockID.h
BlockInServerPluginInterface.h
BlockInfo.h
BlockState.h
BlockTracer.h
BlockTypeRegistry.h
BrewingRecipes.h

View File

@ -0,0 +1,145 @@
#include "Globals.h"
#include "BlockState.h"
#include "../TestHelpers.h"
/** Tests the class constructors with static (hard-coded) data. */
static void testStaticCreation()
{
LOGD("Testing BlockState creation from static data...");
// Create a few BlockStates using the static-data constructors:
BlockState bs1v1;
BlockState bs2v1("property", "value");
BlockState bs3v1({{"property1", "value1"}, {"property2", "value2"}});
BlockState bs1v2(bs1v1);
BlockState bs2v2(bs2v1);
BlockState bs3v2(bs3v1);
BlockState bs1v3(bs1v2, {{"added property", "value1"}});
BlockState bs2v3(bs2v2, {{"added property", "value2"}});
BlockState bs3v3(bs3v2, {{"added property", "value3"}});
// Test (in-)equality (v1 = v2 != v3):
TEST_EQUAL(bs1v1, bs1v2);
TEST_EQUAL(bs2v1, bs2v2);
TEST_EQUAL(bs3v1, bs3v2);
TEST_NOTEQUAL(bs1v1, bs1v3);
TEST_NOTEQUAL(bs2v1, bs2v3);
TEST_NOTEQUAL(bs3v1, bs3v3);
TEST_NOTEQUAL(bs1v2, bs1v3);
TEST_NOTEQUAL(bs2v2, bs2v3);
TEST_NOTEQUAL(bs3v2, bs3v3);
// Test that the values are actually stored:
TEST_EQUAL(bs1v1.value("property"), "");
TEST_EQUAL(bs2v1.value("property"), "value");
TEST_EQUAL(bs2v1.value("property1"), "");
TEST_EQUAL(bs3v1.value("property1"), "value1");
TEST_EQUAL(bs3v1.value("property2"), "value2");
TEST_EQUAL(bs1v3.value("added property"), "value1");
TEST_EQUAL(bs2v3.value("added property"), "value2");
TEST_EQUAL(bs3v3.value("added property"), "value3");
}
/** Tests the dynamic-data constructors (map param, deep-copy). */
static void testDynamicCreation()
{
LOGD("Testing BlockState creation from dynamic data...");
using Map = std::map<AString, AString>;
// Construct from scratch:
{
BlockState bs1a({{"property", "value"}});
Map map1({{"property", "value"}});
BlockState bs1b(map1);
TEST_EQUAL(bs1a, bs1b); // Creation works
map1.clear();
TEST_EQUAL(bs1a, bs1b); // Created a copy independent of map1
}
// Construct by moving:
{
BlockState bs2a({{"property", "value"}});
Map map2({{"property", "value"}});
BlockState bs2b(std::move(map2));
TEST_EQUAL(bs2a, bs2b); // Creation works
}
// Construct by modifying:
{
BlockState bsSrc("property1", "value1");
BlockState bs3a(bsSrc, {{"property2", "value2"}});
Map map3({{"property2", "value2"}});
BlockState bs3b(bsSrc, map3);
TEST_EQUAL(bs3a, bs3b);
map3.clear();
TEST_EQUAL(bs3a, bs3b);
}
}
/** Tests replacing the properties in the copy-and-modify constructors. */
static void testReplacing()
{
LOGD("Testing replacing / removing properties in BlockState copies...");
// Test replacing:
BlockState bs1("property1", "value1v1");
BlockState bs2(bs1, {{"property1", "value1v2"}});
TEST_EQUAL(bs2.value("property1"), "value1v2"); // Stored the new one
TEST_EQUAL(bs1.value("property1"), "value1v1"); // Didn't replace in the original
// Test removing:
BlockState bs3(bs1, {{"property1", ""}});
BlockState bsEmpty;
TEST_EQUAL(bs3, bsEmpty);
}
int main()
{
LOGD("BlockStateTest started");
try
{
testStaticCreation();
testDynamicCreation();
testReplacing();
}
catch (const TestException & exc)
{
LOGERROR("BlockStateTest has failed explicitly: %s", exc.mMessage.c_str());
return 1;
}
catch (const std::runtime_error & exc)
{
LOGERROR("BlockStateTest has failed, an unhandled exception was thrown: %s", exc.what());
return 1;
}
catch (...)
{
LOGERROR("BlockStateTest has failed, an unknown exception was thrown.");
return 1;
}
LOGD("BlockStateTest finished");
return 0;
}

View File

@ -11,6 +11,16 @@ add_definitions(-DTEST_GLOBALS=1)
# Define individual test executables:
# BlockStateTest: Verify that the BlockState class works as intended:
add_executable(BlockStateTest
BlockStateTest.cpp
../TestHelpers.h
${CMAKE_SOURCE_DIR}/src/BlockState.cpp
${CMAKE_SOURCE_DIR}/src/StringUtils.cpp
${CMAKE_SOURCE_DIR}/src/OSSupport/CriticalSection.cpp
)
target_link_libraries(BlockStateTest fmt::fmt)
# BlockTypeRegistryTest: Verify that the BlockTypeRegistry class works as intended:
add_executable(BlockTypeRegistryTest
BlockTypeRegistryTest.cpp
@ -25,8 +35,10 @@ target_link_libraries(BlockTypeRegistryTest fmt::fmt)
# Define individual tests:
add_test(NAME BlockStateTest COMMAND BlockStateTest)
add_test(NAME BlockTypeRegistryTest COMMAND BlockTypeRegistryTest)
@ -35,6 +47,7 @@ add_test(NAME BlockTypeRegistryTest COMMAND BlockTypeRegistryTest)
# Put all the tests into a solution folder (MSVC):
set_target_properties(
BlockStateTest
BlockTypeRegistryTest
PROPERTIES FOLDER Tests/BlockTypeRegistry
)