summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/BlockState.cpp167
-rw-r--r--src/BlockState.h90
-rw-r--r--src/CMakeLists.txt2
-rw-r--r--tests/BlockTypeRegistry/BlockStateTest.cpp145
-rw-r--r--tests/BlockTypeRegistry/CMakeLists.txt13
5 files changed, 417 insertions, 0 deletions
diff --git a/src/BlockState.cpp b/src/BlockState.cpp
new file mode 100644
index 000000000..4e2607bd3
--- /dev/null
+++ b/src/BlockState.cpp
@@ -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;
+}
diff --git a/src/BlockState.h b/src/BlockState.h
new file mode 100644
index 000000000..3b0575f0e
--- /dev/null
+++ b/src/BlockState.h
@@ -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);
+};
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index d23fe388d..dd92a969c 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -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
diff --git a/tests/BlockTypeRegistry/BlockStateTest.cpp b/tests/BlockTypeRegistry/BlockStateTest.cpp
new file mode 100644
index 000000000..d3ce92455
--- /dev/null
+++ b/tests/BlockTypeRegistry/BlockStateTest.cpp
@@ -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;
+}
+
+
+
+
diff --git a/tests/BlockTypeRegistry/CMakeLists.txt b/tests/BlockTypeRegistry/CMakeLists.txt
index 25b18c373..c95c503dd 100644
--- a/tests/BlockTypeRegistry/CMakeLists.txt
+++ b/tests/BlockTypeRegistry/CMakeLists.txt
@@ -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
)