Skip to content
This repository has been archived by the owner on Jun 1, 2022. It is now read-only.

Incomplete Index Component System (ICS) #2

Merged
merged 21 commits into from
Nov 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
66043ae
feat(engine): add typeMap utility and test
joeltio Oct 31, 2020
31d1125
refactor: replace static_assert with concepts require
joeltio Nov 2, 2020
87e4807
refactor: rename typeMap to TypeMap
joeltio Nov 2, 2020
3886d44
feat: add component concept, default and test
joeltio Nov 2, 2020
47dc134
feat: add component vector container
joeltio Nov 2, 2020
15a69b8
chore: split up cmake build files to libraries
joeltio Nov 2, 2020
3b4c8ed
fix: move compVec template definition into header file
joeltio Nov 2, 2020
b5d454d
fix: missing unique_ptr initialisation for compVec
joeltio Nov 2, 2020
1fa4c8b
feat: add remove function to compVec and add isActiveCheck
joeltio Nov 3, 2020
416b4d8
refactor: reorder compVec methods
joeltio Nov 4, 2020
97a2c2b
feat: add index concept
joeltio Nov 4, 2020
15f00df
fix: remove typeMap base class and replace with any type
joeltio Nov 4, 2020
da9b2c6
feat: add .has member test to typeMap
joeltio Nov 4, 2020
3a9885e
feat: add manager with sample addComponent and addIndex
joeltio Nov 5, 2020
707b899
fix: add component archetype so index only has one template param
joeltio Nov 5, 2020
f068bf7
chore: add more tests with runtime assertions
joeltio Nov 5, 2020
7ec0e23
chore: remove test set_property in CMakeLists
joeltio Nov 7, 2020
3016255
fix: fix googletet configuration to remove version warning
joeltio Nov 8, 2020
59a3e0a
chore: remove parent and child component in compVec test
joeltio Nov 8, 2020
c44326c
refactor: rename Manager to ICSManager
joeltio Nov 8, 2020
3ef67ff
fix: CMakeLists Manager was not renamed to ICSManager
joeltio Nov 8, 2020
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
27 changes: 22 additions & 5 deletions sim/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,17 @@ project(bentobox)
# Source files
set(SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/src")
set(LIB_DIR "${CMAKE_CURRENT_SOURCE_DIR}/lib")
set(SOURCES "${SRC_DIR}/main.cpp")
set(TESTS "${SRC_DIR}/test.cpp")
set(ICS_DIR "${SRC_DIR}/ics")
set(UTIL_DIR "${SRC_DIR}/util")

set(SOURCES
"${SRC_DIR}/main.cpp"
"${SRC_DIR}/ICSManager.h"
)

set(TESTS
"${SRC_DIR}/main.test.cpp"
)

# Executable
set(CMAKE_CXX_STANDARD 20)
Expand All @@ -14,6 +23,13 @@ add_executable(${PROJECT_NAME} ${SOURCES})
add_executable("${PROJECT_NAME}_test" ${TESTS})
target_include_directories(${PROJECT_NAME} PRIVATE ${SRC_DIR})

# Link libraries
foreach (lib IN ITEMS util ics)
add_subdirectory("${SRC_DIR}/${lib}")
target_link_libraries(${PROJECT_NAME} PRIVATE ${lib})
target_link_libraries(${PROJECT_NAME}_test PRIVATE -Wl,--whole-archive ${lib}-test -Wl,--no-whole-archive)
endforeach()

## Dependencies
# GLFW
find_package(glfw3 3 REQUIRED)
Expand All @@ -37,7 +53,8 @@ endif(NOT WIN32)
## Test Dependencies
# Google test
add_subdirectory(
"${LIB_DIR}/googletest/googletest"
"${LIB_DIR}/googletest/googlemock"
EXCLUDE_FROM_ALL)
"${LIB_DIR}/googletest"
EXCLUDE_FROM_ALL
)

target_link_libraries("${PROJECT_NAME}_test" PRIVATE gtest_main)
46 changes: 46 additions & 0 deletions sim/src/ICSManager.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#ifndef BENTOBOX_ICSMANAGER_H
#define BENTOBOX_ICSMANAGER_H

#include <memory>
#include <ics/index.h>
#include <ics/component.h>
#include <ics/compVec.h>
#include <util/typeMap.h>

namespace {
template<ics::Component C>
using CompContainer = ics::CompVec<C>;
}

class ICSManager {
private:
std::unique_ptr<util::TypeMap> indexes;
std::unique_ptr<util::TypeMap> components;
public:
template<ics::Component C>
void addComponent(C c) {
if (components->has<CompContainer<C>>()) {
// The container already exists
// TODO: test that this actually updates the container value
auto container = components->at<CompContainer<C>>();
container.add(c);
} else {
// The container does not exist
auto container = CompContainer<C>();
container.add(c);
components->insert(container);
}
}

template<ics::Component C, ics::Index I>
void addIndex(I i) {
if (indexes->has<I>()) {
// TODO: Make a better error
throw std::logic_error("ICSManager::addIndex: attempting to replace existing index");
} else {
indexes->insert(i);
}
}
};

#endif //BENTOBOX_ICSMANAGER_H
25 changes: 25 additions & 0 deletions sim/src/ics/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
SET(LIB_NAME ics)
SET(SOURCES component.h compVec.h index.h)
SET(TESTS component.test.cpp compVec.test.cpp index.test.cpp)

add_library(
${LIB_NAME}
${SOURCES}
)

add_library(
${LIB_NAME}-test
${SOURCES}
${TESTS}
)

# Cmake could not determine the language used from the files
# https://stackoverflow.com/questions/11801186/cmake-unable-to-determine-linker-language-with-c
set_target_properties(${LIB_NAME} PROPERTIES LINKER_LANGUAGE CXX)
set_target_properties(${LIB_NAME}-test PROPERTIES LINKER_LANGUAGE CXX)

target_include_directories(${LIB_NAME} PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}")
target_include_directories(${LIB_NAME}-test PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}")

# Google test
target_link_libraries("${LIB_NAME}-test" PRIVATE gtest)
76 changes: 76 additions & 0 deletions sim/src/ics/compVec.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#ifndef BENTOBOX_COMPVEC_H
#define BENTOBOX_COMPVEC_H

#include <vector>
#include <queue>
#include <memory>
#include <typeindex>
#include <iostream>

#include "component.h"

namespace ics {
template<Component C>
class CompVec {
public:
typedef typename std::vector<C>::size_type size_type;
private:
std::unique_ptr<std::queue<size_type>> inactiveCompIdx = std::make_unique<std::queue<size_type>>();
std::unique_ptr<std::vector<C>> vecPtr = std::make_unique<std::vector<C>>();
protected:
void isActiveCheck(size_type idx) const;
public:
size_type add(const C& val);
void remove(size_type idx);

C& at(size_type idx) const;
C& operator[](size_type idx);
const C& operator[](size_type idx) const;
};

template<Component C>
void CompVec<C>::isActiveCheck(size_type idx) const {
if (!this->vecPtr->at(idx).isActive) {
// TODO: format the index into the string
throw std::out_of_range("compVec::isActiveCheck: component at index is inactive");
}
}

template<Component C>
typename CompVec<C>::size_type CompVec<C>::add(const C& val) {
if (!this->inactiveCompIdx->empty()) {
// Reuse old component space
CompVec<C>::size_type insertIdx = this->inactiveCompIdx->front();
this->vecPtr->at(insertIdx) = val;
this->inactiveCompIdx->pop();
return insertIdx;
} else {
this->vecPtr->push_back(val);
}
}

template<Component C>
void CompVec<C>::remove(size_type idx) {
// Mark component as deleted
this->vecPtr->at(idx).isActive = false;
this->inactiveCompIdx->push(idx);
}

template<Component C>
C& CompVec<C>::at(CompVec<C>::size_type idx) const {
this->isActiveCheck(idx);
return this->vecPtr->at(idx);
}

template<Component C>
const C& CompVec<C>::operator[](size_type idx) const {
return this->at(idx);
}

template<Component C>
C& CompVec<C>::operator[](size_type idx) {
return this->at(idx);
}
}

#endif //BENTOBOX_COMPVEC_H
62 changes: 62 additions & 0 deletions sim/src/ics/compVec.test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#include <gtest/gtest.h>
#include "component.h"
#include "compVec.h"

#define TEST_SUITE CompVecTest

using namespace ics;

struct TestComp : public DefaultComponent {
int height;
};

TEST(TEST_SUITE, StoreAndRetrieve) {
auto vec = CompVec<TestComp>();
vec.add(TestComp { true, 3 });

TestComp value = vec.at(0);
ASSERT_EQ(value.height, 3);
}

TEST(TEST_SUITE, RetrieveNonExistentComponent) {
auto vec = CompVec<TestComp>();
EXPECT_THROW(
vec.at(0),
std::out_of_range
);
}

TEST(TEST_SUITE, ReuseDeletedComponent) {
auto vec = CompVec<TestComp>();

// Create two elements
auto firstElementIdx = vec.add(TestComp{ true, 3 });
vec.add(TestComp{ true, 3 });

// Remove the first
vec.remove(firstElementIdx);

// The first element's memory location should be reused
auto newElement = TestComp { true, 3 };
auto newInsertIndex = vec.add(newElement);
ASSERT_EQ(firstElementIdx, newInsertIndex);

// The values should be the newly inserted component's values
ics::Component auto firstElement = vec.at(firstElementIdx);
ASSERT_EQ(firstElement, newElement);
ASSERT_EQ(firstElement.height, 3);
}

TEST(TEST_SUITE, RetrieveDeletedComponent) {
auto vec = CompVec<TestComp>();
auto idx = vec.add(TestComp{ true, 3 });
vec.remove(idx);
EXPECT_THROW(
vec.at(idx),
std::out_of_range
);
EXPECT_THROW(
vec[idx],
std::out_of_range
);
}
30 changes: 30 additions & 0 deletions sim/src/ics/component.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#ifndef BENTOBOX_COMPONENT_H
#define BENTOBOX_COMPONENT_H

#include <concepts>

namespace ics {
template<class T>
concept Component = std::semiregular<T>
&& std::same_as<decltype(T::isActive), bool>;

// Needed for Index concept
// See: https://stackoverflow.com/q/37655113/4428725
namespace archetypes {
struct Component {
bool isActive = true;
bool operator==(const Component&) const = default;
};
}

static_assert(Component<archetypes::Component>);

struct DefaultComponent {
bool isActive = true;
bool operator==(const DefaultComponent&) const = default;
};

static_assert(Component<DefaultComponent>);
mrzzy marked this conversation as resolved.
Show resolved Hide resolved
}

#endif //BENTOBOX_COMPONENT_H
40 changes: 40 additions & 0 deletions sim/src/ics/component.test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#include <gtest/gtest.h>
#include "component.h"

#define TEST_SUITE Component

using namespace ics;

TEST(TEST_SUITE, DefaultComponentFulfillsConcept) {
ASSERT_TRUE(Component<DefaultComponent>);
}

struct IncorrectNoIsactiveComponent {
bool operator==(const IncorrectNoIsactiveComponent&) const = default;
};

TEST(TEST_SUITE, RequireIsactive) {
ASSERT_FALSE(Component<IncorrectNoIsactiveComponent>);
}

struct DerivedComp : public DefaultComponent {
int height;
char c;
};

TEST(TEST_SUITE, DerivedComponentsEquivalence) {
auto d1 = DerivedComp {
.height = 3,
.c = 'c',
};

auto d2 = DerivedComp {
.height = 3,
.c = 'c',
};

auto d3 = d1;

ASSERT_EQ(d1, d2);
ASSERT_EQ(d3, d1);
}
14 changes: 14 additions & 0 deletions sim/src/ics/index.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#ifndef BENTOBOX_INDEX_H
#define BENTOBOX_INDEX_H

#include "component.h"

namespace ics {
template<class T>
concept Index = requires(T a, archetypes::Component c) {
{ a.template addComponent(c) } -> std::same_as<void>;
{ a.template removeComponent(c) } -> std::same_as<void>;
};
}

#endif //BENTOBOX_INDEX_H
45 changes: 45 additions & 0 deletions sim/src/ics/index.test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#include <gtest/gtest.h>
#include "component.h"
#include "index.h"

#define TEST_SUITE Index

using namespace ics;

class CorrectIndex {
public:
template<Component C>
void addComponent(C c) {};

template<Component C>
void removeComponent(C c) {};
};

TEST(TEST_SUITE, HasAddAndRemoveComponent) {
ASSERT_TRUE(Index<CorrectIndex>);
}

class IncorrectUnconstrainedIndex {
public:
template<typename C>
void addComponent(C c) {};

template<typename C>
void removeComponent(C c) {};
};

TEST(TEST_SUITE, DISABLED_DisallowsUnconstrainedTypenames) {
ASSERT_FALSE(Index<IncorrectUnconstrainedIndex>);
}

template<Component C>
class IncorrectNoTemplateMethodsIndex {
public:
void addComponent(C c) {};

void removeComponent(C c) {};
};

TEST(TEST_SUITE, DisallowsNonTemplateMethods) {
ASSERT_FALSE(Index<IncorrectNoTemplateMethodsIndex<DefaultComponent>>);
}
Empty file added sim/src/main.test.cpp
Empty file.
1 change: 0 additions & 1 deletion sim/src/test.cpp

This file was deleted.

Loading