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

Commit

Permalink
Incomplete Index Component System (ICS) (#2)
Browse files Browse the repository at this point in the history
* feat(engine): add typeMap utility and test

* refactor: replace static_assert with concepts require

* refactor: rename typeMap to TypeMap

* feat: add component concept, default and test

* feat: add component vector container

* chore: split up cmake build files to libraries

* fix: move compVec template definition into header file

* fix: missing unique_ptr initialisation for compVec

* feat: add remove function to compVec and add isActiveCheck

* refactor: reorder compVec methods

* feat: add index concept

* fix: remove typeMap base class and replace with any type

* feat: add .has member test to typeMap

* feat: add manager with sample addComponent and addIndex

* fix: add component archetype so index only has one template param

* chore: add more tests with runtime assertions
concepts can be checked using Concept<Type> instead of creating a
function

* chore: remove test set_property in CMakeLists

* fix: fix googletet configuration to remove version warning

* chore: remove parent and child component in compVec test
Parent and child component is replaced with a generic test component

* refactor: rename Manager to ICSManager

* fix: CMakeLists Manager was not renamed to ICSManager
  • Loading branch information
joeltio authored Nov 8, 2020
1 parent 8843830 commit 68eb24a
Show file tree
Hide file tree
Showing 14 changed files with 456 additions and 6 deletions.
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>);
}

#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

0 comments on commit 68eb24a

Please sign in to comment.