From 427b7c104770021ec61615d4677dd342c8c9cfac Mon Sep 17 00:00:00 2001 From: jadebenn Date: Tue, 17 Dec 2024 00:13:14 -0600 Subject: [PATCH] initial implementation --- .gitignore | 1 + .gitmodules | 3 - CMakeLists.txt | 20 ++- dCommon/CMakeLists.txt | 1 + dCommon/dEnums/MessageType/Game.h | 2 +- dCommon/dEnums/MessageType/World.h | 2 +- dCommon/dEnums/StringifiedEnum.h | 2 +- dCommon/dEnums/eInventoryType.h | 2 +- dDatabase/CMakeLists.txt | 8 +- dECS/CMakeLists.txt | 7 +- dECS/Core.cpp | 70 ++++++++++ dECS/Core.h | 124 ++++++++++++++++++ dECS/ECS.cpp | 8 -- dECS/ECS.h | 65 --------- dGame/dBehaviors/CMakeLists.txt | 2 +- dGame/dComponents/CMakeLists.txt | 2 +- dGame/dComponents/Component.h | 4 + dGame/dGameMessages/CMakeLists.txt | 2 +- dGame/dInventory/CMakeLists.txt | 1 + dNet/AuthPackets.h | 2 +- dNet/CMakeLists.txt | 2 +- dWorldServer/CMakeLists.txt | 2 +- .../dEnumsTests/MagicEnumTests.cpp | 2 +- tests/dECSTests/CMakeLists.txt | 2 +- tests/dECSTests/TestECS.cpp | 69 +++++++++- thirdparty/CMakeLists.txt | 3 - thirdparty/magic_enum | 1 - 27 files changed, 300 insertions(+), 109 deletions(-) create mode 100644 dECS/Core.cpp create mode 100644 dECS/Core.h delete mode 100644 dECS/ECS.cpp delete mode 100644 dECS/ECS.h delete mode 160000 thirdparty/magic_enum diff --git a/.gitignore b/.gitignore index 3ad1009e..6d829df4 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ RelWithDebInfo/ docker/configs # Third party libraries +thirdparty/magic_enum thirdparty/mysql/ thirdparty/mysql_linux/ CMakeVariables.txt diff --git a/.gitmodules b/.gitmodules index 4ba6a43b..aeac3b11 100644 --- a/.gitmodules +++ b/.gitmodules @@ -14,6 +14,3 @@ path = thirdparty/mariadb-connector-cpp url = https://github.com/mariadb-corporation/mariadb-connector-cpp.git ignore = dirty -[submodule "thirdparty/magic_enum"] - path = thirdparty/magic_enum - url = https://github.com/Neargye/magic_enum.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 6482dbe9..bd76621b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -110,6 +110,23 @@ set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) find_package(MariaDB) +# Fetch third party dependencies +set(DLU_THIRDPARTY_SOURCE_DIR ${CMAKE_SOURCE_DIR}/thirdparty) + +include(FetchContent) +FetchContent_Declare( + magic_enum + SYSTEM + # SOURCE_DIR ${DLU_THIRDPARTY_SOURCE_DIR}/magic_enum + GIT_REPOSITORY https://github.com/Neargye/magic_enum.git + GIT_TAG v0.9.7 +) +FetchContent_MakeAvailable(magic_enum) + +include(CMakePrintHelpers) +cmake_print_properties(TARGETS magic_enum::magic_enum PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES) + # Create a /resServer directory make_directory(${CMAKE_BINARY_DIR}/resServer) @@ -252,7 +269,6 @@ include_directories( "tests/dGameTests/dComponentsTests" SYSTEM - "thirdparty/magic_enum/include/magic_enum" "thirdparty/raknet/Source" "thirdparty/tinyxml2" "thirdparty/recastnavigation" @@ -314,7 +330,7 @@ add_subdirectory(dPhysics) add_subdirectory(dServer) # Create a list of common libraries shared between all binaries -set(COMMON_LIBRARIES "dCommon" "dDatabase" "dNet" "raknet" "magic_enum") +set(COMMON_LIBRARIES "dCommon" "dDatabase" "dNet" "raknet" "magic_enum::magic_enum") # Add platform specific common libraries if(UNIX) diff --git a/dCommon/CMakeLists.txt b/dCommon/CMakeLists.txt index d020ff72..717960ad 100644 --- a/dCommon/CMakeLists.txt +++ b/dCommon/CMakeLists.txt @@ -70,5 +70,6 @@ else () endif () target_link_libraries(dCommon + PUBLIC magic_enum::magic_enum PRIVATE ZLIB::ZLIB bcrypt tinyxml2 INTERFACE dDatabase) diff --git a/dCommon/dEnums/MessageType/Game.h b/dCommon/dEnums/MessageType/Game.h index 8c5bddae..60359da4 100644 --- a/dCommon/dEnums/MessageType/Game.h +++ b/dCommon/dEnums/MessageType/Game.h @@ -1,7 +1,7 @@ #pragma once #include -#include "magic_enum.hpp" +#include namespace MessageType { enum class Game : uint16_t { diff --git a/dCommon/dEnums/MessageType/World.h b/dCommon/dEnums/MessageType/World.h index acca6246..6e56fd59 100644 --- a/dCommon/dEnums/MessageType/World.h +++ b/dCommon/dEnums/MessageType/World.h @@ -1,7 +1,7 @@ #pragma once #include -#include "magic_enum.hpp" +#include namespace MessageType { enum class World : uint32_t { diff --git a/dCommon/dEnums/StringifiedEnum.h b/dCommon/dEnums/StringifiedEnum.h index 1816d705..deab81d8 100644 --- a/dCommon/dEnums/StringifiedEnum.h +++ b/dCommon/dEnums/StringifiedEnum.h @@ -2,7 +2,7 @@ #define __STRINGIFIEDENUM_H__ #include -#include "magic_enum.hpp" +#include namespace StringifiedEnum { template diff --git a/dCommon/dEnums/eInventoryType.h b/dCommon/dEnums/eInventoryType.h index 1c6688b2..724027c8 100644 --- a/dCommon/dEnums/eInventoryType.h +++ b/dCommon/dEnums/eInventoryType.h @@ -5,7 +5,7 @@ #include -#include "magic_enum.hpp" +#include static const uint8_t NUMBER_OF_INVENTORIES = 17; /** diff --git a/dDatabase/CMakeLists.txt b/dDatabase/CMakeLists.txt index 42bdb983..d69e2e5c 100644 --- a/dDatabase/CMakeLists.txt +++ b/dDatabase/CMakeLists.txt @@ -2,12 +2,6 @@ add_subdirectory(CDClientDatabase) add_subdirectory(GameDatabase) add_library(dDatabase STATIC "MigrationRunner.cpp") - -add_custom_target(conncpp_dylib - ${CMAKE_COMMAND} -E copy $ ${PROJECT_BINARY_DIR}) - -add_dependencies(dDatabase conncpp_dylib) - target_include_directories(dDatabase PUBLIC ".") target_link_libraries(dDatabase - PUBLIC dDatabaseCDClient dDatabaseGame) + PUBLIC magic_enum::magic_enum dDatabaseCDClient dDatabaseGame) diff --git a/dECS/CMakeLists.txt b/dECS/CMakeLists.txt index 81f2190b..7852ced2 100644 --- a/dECS/CMakeLists.txt +++ b/dECS/CMakeLists.txt @@ -1,4 +1,7 @@ -add_library(dECS STATIC "ECS.cpp") +add_library(dECS STATIC +"Core.h" +"Core.cpp" +) target_include_directories(dECS PUBLIC .) -target_link_libraries(dECS PRIVATE dCommon) +target_link_libraries(dECS PRIVATE dCommon magic_enum::magic_enum) target_compile_options(dECS PRIVATE "-Wall") diff --git a/dECS/Core.cpp b/dECS/Core.cpp new file mode 100644 index 00000000..88a9083d --- /dev/null +++ b/dECS/Core.cpp @@ -0,0 +1,70 @@ +#include +#include +#include +#include "Core.h" + +namespace dECS { + struct WorldData { + using CompSignature = magic_enum::containers::bitset; + using CompMap = std::unordered_map; + using CompStorage = std::unordered_map>; + + std::atomic nextId = 1; + CompMap map; + CompStorage data; + }; + + World::World() : m_World{ std::make_shared() } {}; + + Entity World::MakeEntity() { + return Entity{ m_World->nextId.fetch_add(1, std::memory_order::relaxed), + m_World }; + } + + void* Entity::AddComponent(const eReplicaComponentType kind, const StorageConstructor storageConstructor) { + if (auto w = m_World.lock()) { + // add to kind signature + w->map[m_Id].set(kind, true); + + // get or add storage + auto storageIt = w->data.find(kind); + if (storageIt == w->data.cend()) { + bool inserted = false; + std::tie(storageIt, inserted) = w->data.try_emplace(kind, storageConstructor()); + if (!inserted) throw "storage emplacement failure"; + } + auto& storage = *storageIt->second; + + // return reference if already mapped, otherwise add component + auto compIt = storage.rowMap.find(m_Id); + if (compIt == storage.rowMap.cend()) { + const auto curSize = storage.rowMap.size(); + storage.rowMap.emplace(m_Id, curSize); + return storage.emplace_back(); + } + const auto row = compIt->second; + return storage.at(row); + } + return nullptr; + } + + const void* Entity::GetComponent(const eReplicaComponentType kind) const { + if (auto const w = m_World.lock()) { + const auto& compSig = w->map.at(m_Id); + if (!compSig.test(kind)) return nullptr; + + const auto& storage = *w->data.at(kind); + const auto it = storage.rowMap.find(m_Id); + if (it == storage.rowMap.cend()) return nullptr; + + const auto row = it->second; + return storage.at(row); + } + return nullptr; + } + + void* Entity::GetComponent(const eReplicaComponentType kind) { + // Casting away const for this overload is safe, if not at all pretty + return const_cast(std::as_const(*this).GetComponent(kind)); + } +} diff --git a/dECS/Core.h b/dECS/Core.h new file mode 100644 index 00000000..bdfe6af9 --- /dev/null +++ b/dECS/Core.h @@ -0,0 +1,124 @@ +#pragma once +#include +#include +#include +#include + +class Component; +enum class eReplicaComponentType : uint32_t; +using LWOOBJID = int64_t; + +namespace dECS { + // template + // concept IsComponent = std::derived_from; + + struct WorldData; + struct World; + struct Entity; + struct IStorage; + + template + class Storage; + + using WorldPtr = std::shared_ptr; + using WeakWorldPtr = std::weak_ptr; + + class World { + public: + World(); + + [[nodiscard]] + Entity MakeEntity(); + + private: + WorldPtr m_World; + }; + + class Entity { + public: + friend Entity World::MakeEntity(); + + using StorageConstructor = std::function()>; + + [[nodiscard]] + constexpr LWOOBJID Id() const noexcept { + return m_Id; + } + + [[maybe_unused]] + void* AddComponent(eReplicaComponentType, StorageConstructor); + + template + [[maybe_unused]] + C* AddComponent() { + return static_cast(AddComponent(C::ComponentType, std::make_unique>)); + } + + [[nodiscard]] + const void* GetComponent(eReplicaComponentType) const; + + [[nodiscard]] + void* GetComponent(eReplicaComponentType); + + template + [[nodiscard]] + const C* GetComponent() const { + return static_cast(GetComponent(C::ComponentType)); + } + + template + [[nodiscard]] + C* GetComponent() { + return static_cast(GetComponent(C::ComponentType)); + } + + private: + Entity(const LWOOBJID id, const WeakWorldPtr world) + : m_Id{ id } + , m_World { world } + {} + + LWOOBJID m_Id; + + WeakWorldPtr m_World; + }; + + struct IStorage { + using RowMap = std::unordered_map; + + virtual ~IStorage() = default; + + [[nodiscard]] + virtual void* at(size_t) = 0; + + [[nodiscard]] + virtual const void* at(size_t) const = 0; + + [[nodiscard]] + virtual void* emplace_back() = 0; + + RowMap rowMap; + }; + + template + class Storage : public IStorage { + public: + [[nodiscard]] + void* at(const size_t index) override { + return static_cast(&m_Vec.at(index)); + } + + [[nodiscard]] + const void* at(const size_t index) const override { + return static_cast(&m_Vec.at(index)); + } + + [[nodiscard]] + void* emplace_back() override { + return static_cast(&m_Vec.emplace_back()); + } + + private: + std::vector m_Vec; + }; +} diff --git a/dECS/ECS.cpp b/dECS/ECS.cpp deleted file mode 100644 index 28345295..00000000 --- a/dECS/ECS.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#include "ECS.h" -#include "dCommonVars.h" -#include -#include - -namespace Component { - -} diff --git a/dECS/ECS.h b/dECS/ECS.h deleted file mode 100644 index 612084a9..00000000 --- a/dECS/ECS.h +++ /dev/null @@ -1,65 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include "dCommonVars.h" -#include "eReplicaComponentType.h" - -namespace Component { - constexpr size_t MAX_KINDS { 32 }; - - enum class Kind : uint32_t { - NONE = 0, - DESTROYABLE = 7, - PET = 26, - }; - - using Signature = std::bitset; - - // Components - struct Destroyable; - struct Pet { - static constexpr Kind KIND_ID = Kind::PET; - - // The ID under which this pet is stored in the database (if it's tamed) - LWOOBJID m_DatabaseId; - - // The ID of the item from which this pet was creat - LWOOBJID m_ItemId; - - // The name of this pet - std::string name; - }; - - class IStorage { - public: - constexpr IStorage(const Kind kind) : m_Kind{ kind } {} - virtual ~IStorage() = default; - constexpr virtual void Remove(const size_t index) = 0; - - [[nodiscard]] - constexpr Kind GetKind() const { - return m_Kind; - } - - protected: - Kind m_Kind; - }; - - template - class Storage : public IStorage { - public: - constexpr Storage() : IStorage{ C::KIND_ID } {} - - constexpr void Remove(const size_t index) override { - auto& elementToDelete = m_Vec.at(index); - auto& lastElement = m_Vec.back(); - std::swap(elementToDelete, lastElement); - m_Vec.pop_back(); - } - - private: - std::vector m_Vec; - }; -} diff --git a/dGame/dBehaviors/CMakeLists.txt b/dGame/dBehaviors/CMakeLists.txt index 35d8cae6..ca930afa 100644 --- a/dGame/dBehaviors/CMakeLists.txt +++ b/dGame/dBehaviors/CMakeLists.txt @@ -55,7 +55,7 @@ set(DGAME_DBEHAVIORS_SOURCES "AirMovementBehavior.cpp" "VerifyBehavior.cpp") add_library(dBehaviors OBJECT ${DGAME_DBEHAVIORS_SOURCES}) -target_link_libraries(dBehaviors PUBLIC dDatabaseCDClient dPhysics) +target_link_libraries(dBehaviors PUBLIC dDatabaseCDClient dPhysics magic_enum::magic_enum tinyxml2) target_include_directories(dBehaviors PUBLIC "." "${PROJECT_SOURCE_DIR}/dGame/dGameMessages" # via BehaviorContext.h PRIVATE diff --git a/dGame/dComponents/CMakeLists.txt b/dGame/dComponents/CMakeLists.txt index c60e135f..82f019b7 100644 --- a/dGame/dComponents/CMakeLists.txt +++ b/dGame/dComponents/CMakeLists.txt @@ -79,4 +79,4 @@ target_include_directories(dComponents PUBLIC "." ) target_precompile_headers(dComponents REUSE_FROM dGameBase) -target_link_libraries(dComponents INTERFACE dBehaviors) +target_link_libraries(dComponents PUBLIC magic_enum::magic_enum tinyxml2 INTERFACE dBehaviors) diff --git a/dGame/dComponents/Component.h b/dGame/dComponents/Component.h index 160565fb..3e9de3a8 100644 --- a/dGame/dComponents/Component.h +++ b/dGame/dComponents/Component.h @@ -2,6 +2,10 @@ #include "tinyxml2.h" +namespace RakNet { + class BitStream; +}; + class Entity; /** diff --git a/dGame/dGameMessages/CMakeLists.txt b/dGame/dGameMessages/CMakeLists.txt index 0f28dea4..9da8a185 100644 --- a/dGame/dGameMessages/CMakeLists.txt +++ b/dGame/dGameMessages/CMakeLists.txt @@ -6,7 +6,7 @@ set(DGAME_DGAMEMESSAGES_SOURCES add_library(dGameMessages OBJECT ${DGAME_DGAMEMESSAGES_SOURCES}) target_link_libraries(dGameMessages - PUBLIC dDatabase + PUBLIC magic_enum::magic_enum tinyxml2 dDatabase INTERFACE dGameBase # TradingManager ) target_include_directories(dGameMessages PUBLIC "." diff --git a/dGame/dInventory/CMakeLists.txt b/dGame/dInventory/CMakeLists.txt index b45b27bf..530777c6 100644 --- a/dGame/dInventory/CMakeLists.txt +++ b/dGame/dInventory/CMakeLists.txt @@ -24,6 +24,7 @@ target_include_directories(dInventory PUBLIC "." "${PROJECT_SOURCE_DIR}/dGame/dMission" # via MissionComponent.h "${PROJECT_SOURCE_DIR}/dZoneManager" # via Item.cpp ) +target_link_libraries(dInventory PUBLIC magic_enum::magic_enum tinyxml2) target_precompile_headers(dInventory REUSE_FROM dGameBase) # Workaround for compiler bug where the optimized code could result in a memcpy of 0 bytes, even though that isnt possible. # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=97185 diff --git a/dNet/AuthPackets.h b/dNet/AuthPackets.h index ee1e4586..9c7e2bf2 100644 --- a/dNet/AuthPackets.h +++ b/dNet/AuthPackets.h @@ -4,7 +4,7 @@ #define _VARIADIC_MAX 10 #include "dCommonVars.h" #include "dNetCommon.h" -#include "magic_enum.hpp" +#include enum class ServerType : uint32_t; enum class eLoginResponse : uint8_t; diff --git a/dNet/CMakeLists.txt b/dNet/CMakeLists.txt index 15cdda42..8c15d938 100644 --- a/dNet/CMakeLists.txt +++ b/dNet/CMakeLists.txt @@ -8,7 +8,7 @@ set(DNET_SOURCES "AuthPackets.cpp" "ZoneInstanceManager.cpp") add_library(dNet STATIC ${DNET_SOURCES}) -target_link_libraries(dNet PRIVATE bcrypt MD5) +target_link_libraries(dNet PUBLIC magic_enum::magic_enum PRIVATE bcrypt MD5) target_include_directories(dNet PRIVATE "${PROJECT_SOURCE_DIR}/dCommon" "${PROJECT_SOURCE_DIR}/dCommon/dEnums" diff --git a/dWorldServer/CMakeLists.txt b/dWorldServer/CMakeLists.txt index 62a3767a..815a2c08 100644 --- a/dWorldServer/CMakeLists.txt +++ b/dWorldServer/CMakeLists.txt @@ -13,7 +13,7 @@ target_include_directories(WorldServer PRIVATE "${PROJECT_SOURCE_DIR}/dServer" # BinaryPathFinder.h ) -target_link_libraries(WorldServer ${COMMON_LIBRARIES} +target_link_libraries(WorldServer PUBLIC ${COMMON_LIBRARIES} dScripts dGameBase dComponents diff --git a/tests/dCommonTests/dEnumsTests/MagicEnumTests.cpp b/tests/dCommonTests/dEnumsTests/MagicEnumTests.cpp index fcd517f1..a1e1fb3e 100644 --- a/tests/dCommonTests/dEnumsTests/MagicEnumTests.cpp +++ b/tests/dCommonTests/dEnumsTests/MagicEnumTests.cpp @@ -8,7 +8,7 @@ #include "Game.h" #include "MessageType/Game.h" #include "MessageType/World.h" -#include "magic_enum.hpp" +#include #define ENUM_EQ(e, y, z)\ LOG("%s %s", StringifiedEnum::ToString(static_cast(y)).data(), #z);\ diff --git a/tests/dECSTests/CMakeLists.txt b/tests/dECSTests/CMakeLists.txt index c9ca8239..3031f0bf 100644 --- a/tests/dECSTests/CMakeLists.txt +++ b/tests/dECSTests/CMakeLists.txt @@ -3,7 +3,7 @@ add_executable(dECSTests ) # Link needed libraries -target_link_libraries(dECSTests PRIVATE dCommon dECS GTest::gtest_main) +target_link_libraries(dECSTests PRIVATE dCommon dGame dECS GTest::gtest_main) # Discover the tests gtest_discover_tests(dECSTests) diff --git a/tests/dECSTests/TestECS.cpp b/tests/dECSTests/TestECS.cpp index 61c2343e..0d1b5af4 100644 --- a/tests/dECSTests/TestECS.cpp +++ b/tests/dECSTests/TestECS.cpp @@ -1,10 +1,67 @@ +#include #include -#include "ECS.h" +#include "Core.h" +#include +#include -TEST(ECSTest, MakeStorage) { - const auto storage = Component::Storage(); - const Component::IStorage* const storagePtr = &storage; +using namespace dECS; - ASSERT_EQ(storagePtr->GetKind(), Component::Kind::PET); - ASSERT_NE(storagePtr->GetKind(), Component::Kind::DESTROYABLE); +struct TestComponent { + static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::CHOICE_BUILD; + + int value; +}; + +// Test that entity IDs increment correctly +TEST(ECSTest, IncrementEntityIdsSingleThread) { + auto w = World{}; + + auto ea = w.MakeEntity(); + ASSERT_EQ(ea.Id(), 1); + + auto eb = w.MakeEntity(); + ASSERT_EQ(eb.Id(), 2); + + auto ec = w.MakeEntity(); + ASSERT_EQ(ec.Id(), 3); +} + +// Test adding and getting components +TEST(ECSTest, MakeOneEntityAndAddComponents) { + auto w = World{}; + auto e = w.MakeEntity(); + ASSERT_EQ(e.Id(), 1); + + // add component + auto* const testCompPtr = e.AddComponent(); + ASSERT_NE(testCompPtr, nullptr); + ASSERT_EQ(testCompPtr->ComponentType, eReplicaComponentType::CHOICE_BUILD); + ASSERT_EQ(testCompPtr->value, 0); + testCompPtr->value = 15; + + // try getting the same component we just added + auto* const getTestCompPtr = e.GetComponent(); + ASSERT_NE(getTestCompPtr, nullptr); + ASSERT_EQ(testCompPtr, getTestCompPtr); + ASSERT_NE(getTestCompPtr->value, 0); + ASSERT_EQ(getTestCompPtr->value, 15); +} + +// Test world scoping +TEST(ECSTest, WorldScope) { + auto e = std::optional{}; + + { + auto w = World{}; + e.emplace(w.MakeEntity()); + ASSERT_EQ(e->Id(), 1); + + // add component within scope + auto* const cPtr = e->AddComponent(); + ASSERT_NE(cPtr, nullptr); + } + + // Attempting to access this component now that the world has gone + // out of scope should return nullptr + ASSERT_EQ(e->GetComponent(), nullptr); } diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt index 79863a53..a4b92a2d 100644 --- a/thirdparty/CMakeLists.txt +++ b/thirdparty/CMakeLists.txt @@ -34,9 +34,6 @@ target_include_directories(bcrypt PRIVATE "libbcrypt/src") # Source code for sqlite add_subdirectory(SQLite) -# Source code for magic_enum -add_subdirectory(magic_enum) - # Create our third party library objects add_subdirectory(raknet) diff --git a/thirdparty/magic_enum b/thirdparty/magic_enum deleted file mode 160000 index e55b9b54..00000000 --- a/thirdparty/magic_enum +++ /dev/null @@ -1 +0,0 @@ -Subproject commit e55b9b54d5cf61f8e117cafb17846d7d742dd3b4