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 10 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)
15 changes: 15 additions & 0 deletions dECS/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
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"
"Iter.h"
"Core.cpp"
"System.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));
}
}
180 changes: 180 additions & 0 deletions dECS/Core.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
#pragma once
#include <cstdint>
#include <memory>
#include <string_view>
#include <type_traits>
#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>;

// Data structures
struct WorldData;
class World;

template <typename... Cs>
class System;

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();

template <typename... Cs>
[[nodiscard]]
System<Cs...> MakeSystem() {
return System<Cs...>{};
}

template <typename... Cs, typename S>
[[nodiscard]]
System<Cs...> MakeSystem(S&& name) {
return System<Cs...>{ std::forward<S>(name) };
}

private:
WorldPtr m_World;
};

template <typename... Cs>
class System {
public:
friend System World::MakeSystem<Cs...>();

template <typename... Ts, typename S>
friend System<Ts...> World::MakeSystem(S&&);

/*template <typename Fn>
requires std::is_invocable_r_v<void, Fn(Cs...), ObjId, Cs...>
void ForEach(Fn&& f) {
for (ObjId i = 0; i < mT.size(); ++i) {
auto& c = mT[i];
f(i, std::get<Cs>(c)...);
}
}*/

template <typename Fn>
requires std::is_invocable_r_v<void, Fn(Cs...), Cs...>
void ForEach(Fn&& fn) {
std::tuple<Cs...> comps; // some sort of iterator that returns a tuple each 'step?'
for (size_t i = 0; i < 5; ++i) {
fn(std::get<Cs>(comps)...);
}
}

private:
System() = default;

template <typename S>
explicit System(S&& name)
: m_name{ std::forward<S>(name) }
{}

std::string m_name;
};

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;
};
}
Loading