Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

experiment: Store components in contiguous arrays #1684

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ RelWithDebInfo/
docker/configs

# Third party libraries
thirdparty/magic_enum
thirdparty/mysql/
thirdparty/mysql_linux/
CMakeVariables.txt
Expand Down
3 changes: 0 additions & 3 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -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
21 changes: 19 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -252,7 +269,6 @@ include_directories(
"tests/dGameTests/dComponentsTests"

SYSTEM
"thirdparty/magic_enum/include/magic_enum"
"thirdparty/raknet/Source"
"thirdparty/tinyxml2"
"thirdparty/recastnavigation"
Expand Down Expand Up @@ -303,6 +319,7 @@ file(
# Add our library subdirectories for creation of the library object
add_subdirectory(dCommon)
add_subdirectory(dDatabase)
add_subdirectory(dECS)
add_subdirectory(dChatFilter)
add_subdirectory(dNet)
add_subdirectory(dScripts) # Add for dGame to use
Expand All @@ -313,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)
Expand Down
1 change: 1 addition & 0 deletions dCommon/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -70,5 +70,6 @@ else ()
endif ()

target_link_libraries(dCommon
PUBLIC magic_enum::magic_enum
PRIVATE ZLIB::ZLIB bcrypt tinyxml2
INTERFACE dDatabase)
2 changes: 1 addition & 1 deletion dCommon/dEnums/MessageType/Game.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#pragma once
#include <cstdint>

#include "magic_enum.hpp"
#include <magic_enum/magic_enum.hpp>

namespace MessageType {
enum class Game : uint16_t {
Expand Down
2 changes: 1 addition & 1 deletion dCommon/dEnums/MessageType/World.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#pragma once
#include <cstdint>

#include "magic_enum.hpp"
#include <magic_enum/magic_enum.hpp>

namespace MessageType {
enum class World : uint32_t {
Expand Down
2 changes: 1 addition & 1 deletion dCommon/dEnums/StringifiedEnum.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
#define __STRINGIFIEDENUM_H__

#include <string>
#include "magic_enum.hpp"
#include <magic_enum/magic_enum.hpp>

namespace StringifiedEnum {
template<typename T>
Expand Down
2 changes: 1 addition & 1 deletion dCommon/dEnums/eInventoryType.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

#include <cstdint>

#include "magic_enum.hpp"
#include <magic_enum/magic_enum.hpp>

static const uint8_t NUMBER_OF_INVENTORIES = 17;
/**
Expand Down
8 changes: 1 addition & 7 deletions dDatabase/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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 $<TARGET_FILE:MariaDB::ConnCpp> ${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)
13 changes: 13 additions & 0 deletions dECS/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
set(gcc_like_cxx "$<COMPILE_LANG_AND_ID:CXX,ARMClang,AppleClang,Clang,GNU,LCC>")
set(msvc_cxx "$<COMPILE_LANG_AND_ID:CXX,MSVC>")

add_library(dECS STATIC
"Core.h"
"Core.cpp"
)
target_include_directories(dECS PUBLIC .)
target_link_libraries(dECS PRIVATE dCommon magic_enum::magic_enum)
target_compile_options(dECS PRIVATE
"$<${gcc_like_cxx}:$<BUILD_INTERFACE:-Wall;-Wextra>>"
"$<${msvc_cxx}:$<BUILD_INTERFACE:/W3>>"
)
70 changes: 70 additions & 0 deletions dECS/Core.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#include <atomic>
#include <magic_enum/magic_enum_containers.hpp>
#include <eReplicaComponentType.h>
#include "Core.h"

namespace dECS {
struct WorldData {
using CompSignature = magic_enum::containers::bitset<eReplicaComponentType>;
using CompMap = std::unordered_map<LWOOBJID, CompSignature>;
using CompStorage = std::unordered_map<eReplicaComponentType, std::unique_ptr<IStorage>>;

std::atomic<LWOOBJID> nextId = 1;
CompMap map;
CompStorage data;
};

World::World() : m_World{ std::make_shared<WorldData>() } {};

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()) {
// Check that the entity has this component
if (!w->map[m_Id].test(kind)) return nullptr;

// Get the location where it's stored
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<void*>(std::as_const(*this).GetComponent(kind));
}
}
124 changes: 124 additions & 0 deletions dECS/Core.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
#pragma once
#include <cstdint>
#include <memory>
#include <unordered_map>
#include <vector>

class Component;
enum class eReplicaComponentType : uint32_t;
using LWOOBJID = int64_t;

namespace dECS {
// template <typename C>
// concept IsComponent = std::derived_from<C, Component>;

struct WorldData;
class World;
class Entity;
struct IStorage;

template <typename C>
class Storage;

using WorldPtr = std::shared_ptr<WorldData>;
using WeakWorldPtr = std::weak_ptr<WorldData>;

class World {
public:
World();

[[nodiscard]]
Entity MakeEntity();

private:
WorldPtr m_World;
};

class Entity {
public:
friend Entity World::MakeEntity();

using StorageConstructor = std::function<std::unique_ptr<IStorage>()>;

[[nodiscard]]
constexpr LWOOBJID GetObjectID() const noexcept {
return m_Id;
}

[[maybe_unused]]
void* AddComponent(eReplicaComponentType, StorageConstructor);

template <typename C>
[[maybe_unused]]
C* AddComponent() {
return static_cast<C*>(AddComponent(C::ComponentType, std::make_unique<Storage<C>>));
}

[[nodiscard]]
const void* GetComponent(eReplicaComponentType) const;

[[nodiscard]]
void* GetComponent(eReplicaComponentType);

template <typename C>
[[nodiscard]]
const C* GetComponent() const {
return static_cast<const C*>(GetComponent(C::ComponentType));
}

template <typename C>
[[nodiscard]]
C* GetComponent() {
return static_cast<C*>(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<LWOOBJID, size_t>;

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 <typename C>
class Storage : public IStorage {
public:
[[nodiscard]]
void* at(const size_t index) override {
return static_cast<void*>(&m_Vec.at(index));
}

[[nodiscard]]
const void* at(const size_t index) const override {
return static_cast<const void*>(&m_Vec.at(index));
}

[[nodiscard]]
void* emplace_back() override {
return static_cast<void*>(&m_Vec.emplace_back());
}

private:
std::vector<C> m_Vec;
};
}
2 changes: 1 addition & 1 deletion dGame/dBehaviors/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
target_include_directories(dBehaviors PUBLIC "."
"${PROJECT_SOURCE_DIR}/dGame/dGameMessages" # via BehaviorContext.h
PRIVATE
Expand Down
2 changes: 1 addition & 1 deletion dGame/dComponents/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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 INTERFACE dBehaviors)
4 changes: 4 additions & 0 deletions dGame/dComponents/Component.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

#include "tinyxml2.h"

namespace RakNet {
class BitStream;
};

class Entity;

/**
Expand Down
2 changes: 1 addition & 1 deletion dGame/dGameMessages/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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 dDatabase
INTERFACE dGameBase # TradingManager
)
target_include_directories(dGameMessages PUBLIC "."
Expand Down
1 change: 1 addition & 0 deletions dGame/dInventory/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
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
Expand Down
2 changes: 1 addition & 1 deletion dNet/AuthPackets.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
#define _VARIADIC_MAX 10
#include "dCommonVars.h"
#include "dNetCommon.h"
#include "magic_enum.hpp"
#include <magic_enum/magic_enum.hpp>

enum class ServerType : uint32_t;
enum class eLoginResponse : uint8_t;
Expand Down
2 changes: 1 addition & 1 deletion dNet/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Loading
Loading