Added BlockState implementation for 1.13 support.
This commit is contained in:
parent
fcc836f2a5
commit
02fbf16865
167
src/BlockState.cpp
Normal file
167
src/BlockState.cpp
Normal 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
90
src/BlockState.h
Normal 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);
|
||||
};
|
@ -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
|
||||
|
145
tests/BlockTypeRegistry/BlockStateTest.cpp
Normal file
145
tests/BlockTypeRegistry/BlockStateTest.cpp
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -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
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user