diff options
author | heavydemon21 <nielsstunnebrink1@gmail.com> | 2024-10-22 11:35:47 +0200 |
---|---|---|
committer | heavydemon21 <nielsstunnebrink1@gmail.com> | 2024-10-22 11:35:47 +0200 |
commit | 176ac90fce318334f1377d94d6e637e1eff84c3c (patch) | |
tree | 104b86fc3537fd82c7b9dd731ee716c51441ca31 | |
parent | edfcb27fe37fdcf08622863f331960325e3899ac (diff) | |
parent | 77555730e3ddb811b9ce8470659663e3f1573de2 (diff) |
Merge branch 'master' into niels/rendering
86 files changed, 1711 insertions, 513 deletions
diff --git a/.clang-format b/.clang-format index 5d233eb..3ae6c28 100644 --- a/.clang-format +++ b/.clang-format @@ -22,6 +22,7 @@ AlignTrailingComments: ReflowComments: false AlignEscapedNewlines: DontAlign BreakBeforeBinaryOperators: All +AlwaysBreakTemplateDeclarations: Yes ... # vim: ft=yaml diff --git a/.clang-tidy b/.clang-tidy index b242e3f..214a5ab 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,18 +1,35 @@ Checks: '-*,readability-identifier-naming' CheckOptions: - - { key: readability-identifier-naming.EnumCase, value: lower_case } - - { key: readability-identifier-naming.GlobalFunctionCase, value: lower_case } - - { key: readability-identifier-naming.ClassCase, value: CamelCase } - - { key: readability-identifier-naming.ClassMethodCase, value: lower_case } - - { key: readability-identifier-naming.ClassMethodPrefix, value: '' } - - { key: readability-identifier-naming.ClassMemberCase, value: lower_case } - - { key: readability-identifier-naming.ClassMemberPrefix, value: '' } - - { key: readability-identifier-naming.GlobalConstantCase, value: UPPER_CASE } - - { key: readability-identifier-naming.GlobalConstantIgnoredRegexp, value: _.* } - - { key: readability-identifier-naming.GlobalVariableCase, value: lower_case } - - { key: readability-identifier-naming.GlobalVariableIgnoredRegexp, value: _.* } - - { key: readability-identifier-naming.MacroDefinitionCase, value: UPPER_CASE } - - { key: readability-identifier-naming.MacroDefinitionIgnoredRegexp, value: _.* } - - { key: readability-identifier-naming.StructCase, value: lower_case } + - key: 'readability-identifier-naming.EnumCase' + value: 'lower_case' + - key: 'readability-identifier-naming.GlobalFunctionCase' + value: 'lower_case' + - key: 'readability-identifier-naming.ClassCase' + value: 'CamelCase' + - key: 'readability-identifier-naming.MethodCase' + value: 'lower_case' + - key: 'readability-identifier-naming.MethodPrefix' + value: '' + - key: 'readability-identifier-naming.MemberCase' + value: 'lower_case' + - key: 'readability-identifier-naming.MemberPrefix' + value: '' + - key: 'readability-identifier-naming.ConstantCase' + value: 'UPPER_CASE' + - key: 'readability-identifier-naming.ConstantIgnoredRegexp' + value: '_.*' + - key: 'readability-identifier-naming.ConstantParameterCase' + value: 'lower_case' + - key: 'readability-identifier-naming.VariableCase' + value: 'lower_case' + - key: 'readability-identifier-naming.VariableIgnoredRegexp' + value: '_.*' + - key: 'readability-identifier-naming.MacroDefinitionCase' + value: 'UPPER_CASE' + - key: 'readability-identifier-naming.MacroDefinitionIgnoredRegexp' + value: '_.*' + - key: 'readability-identifier-naming.StructCase' + value: 'lower_case' # vim: ft=yaml + diff --git a/.editorconfig b/.editorconfig index 1bd7da9..df2cf5e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -5,7 +5,7 @@ indent_style = tab end_of_line = lf insert_final_newline = true -[*.{md,yml}] +[{*.{md,yml},.clang-*}] indent_style = space indent_size = 2 diff --git a/.gitmodules b/.gitmodules index d7a6ebf..ece7fda 100644 --- a/.gitmodules +++ b/.gitmodules @@ -3,7 +3,7 @@ url = https://github.com/google/googletest shallow = true [submodule "lib/soloud/src"] - path = lib/soloud/src + path = lib/soloud url = https://github.com/jarikomppa/soloud shallow = true [submodule "lib/sdl2"] diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 5b380e1..977c0ca 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,5 +1,6 @@ { "recommendations": [ + "llvm-vs-code-extensions.vscode-clangd", "EditorConfig.EditorConfig", "ms-vscode.cmake-tools" ] diff --git a/.vscode/settings.json b/.vscode/settings.json index d265510..af4ae4e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,6 @@ { - "cmake.sourceDirectory": "${workspaceFolder}/src" + "cmake.sourceDirectory": "${workspaceFolder}/src", + "files.associations": { + "*.tcc": "cpp" + } } diff --git a/contributing.md b/contributing.md index 5cbad61..379494d 100644 --- a/contributing.md +++ b/contributing.md @@ -18,220 +18,48 @@ # Code style - ASCII only +- Class names are always singular +- Explanatory comments are placed above the line(s) they are explaining +- Source files should only contain comments that plainly state what the code is + supposed to do +- Explanatory comments in headers may be used to clarify implementation design + decisions - Formatting nitty-gritty is handled by clang-format/clang-tidy (run `make format` in the root folder of this repository to format all sources files) - When using libraries of which the header include order is important, make sure to separate the include statements using a blank line (clang-format may sort include statements, but does not sort across empty lines). -- [Cpp practices](https://lefticus.gitbooks.io/cpp-best-practices/content/) -- [C++ core guidelines](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines) -- [Google c++ style](https://google.github.io/styleguide/cppguide.html) - - -- .h basic code style with doxygen -```cpp -// code-style-example/style.cpp -/*! @file MyClass.h */ - -#ifndef MYPROJECT_MYCLASS_HPP -#define MYPROJECT_MYCLASS_HPP - -/** - * @brief example class - */ -class MyClass { - -public: - /** - * @brief example constructor - * - * @param[integer] t_value example first argument - */ - MyClass(int t_value); - - /** - * @brief constructor example - * - */ - MyClass(); - - /** - * @brief deconstuctor example - * - */ - ~MyClass(); - - /** - * @brief setter example with correct const implementation - * - * @param[const integer] t_value first argument - */ - void set_value(const int t_value); - - /** - * @brief getter example with correct const implementation - * - * @return const integer - */ - const int get_value() const; - - /** - * @brief increment member m_value - * - */ - void increment(); - -private: - /** - * @m_value basic member example - */ - int m_value; -}; - -#endif -``` - -- .cpp basic code style -```cpp -// code-style-example/style.cpp - -#include "style.h" - -MyClass::MyClass(int t_value) : m_value(t_value) {} - -MyClass::MyClass() { m_value = 0; } - -MyClass::~MyClass() {} - -const int MyClass::get_value() const { return m_value; } - -void MyClass::set_value(const int t_value) { m_value = t_value; } - -void MyClass::increment() { m_value += 1; } -``` - -- when to use references - --- If the input parameter in the function cannot accept a nullptr -```cpp -void foo::function(const std::string& name){}; -``` --- Use a reference when you know that the object will exist for the lifetime of the reference and when nullability is not a concern. -```cpp -int x = 10; -int& ref = x; -ref = 20; -``` - --- If a function should return a reference to an object that exists elsewhere (such as a member of a class or an element in a container), you can return a reference. -```cpp -container& get_element(std::vector<container>& vec, size_t index){ - return vec[index]; -} -``` -- When to use pointers - --- Use pointers when dealing with dynamic memory -```cpp -int* ptr = new int(5); -delete ptr; -``` - --- Pointers can be nullptr, allowing them to represent "no object." Use pointers when you need to express optional ownership or reference. -```cpp -void foo::function(int* ptr){ - if(ptr){ - // ptr is not null - } -} -``` - --- When dealing with polymorphism. -```cpp -class Base {}; -class Derived : public Base {}; - -Base* obj = new Derived(); -obj->function(); -``` - - -- Do not use auto - --- Instead of doing this -```cpp -auto s = "Hello"; -auto x = "42"s; -auto x = 42; -auto x = 42.f; -``` --- Do this. -```cpp -std::string s = "Hello"; -std::string x = "42"s; -int x = 42; -float x = 42.f; -``` -- Do not define constants as define but instead directly declare it. --- This is wrong. -```cpp -#define PI 3.14159; -``` - --- Do this. -```cpp -namespace my_project { - class Constants { - public: - static constexpr double PI = 3.14159; +- All engine-related code is implemented under the `crepe` namespace, + user-facing APIs under `crepe::api` (the folder structure should also reflect + this). +- `using namespace` may not be used in header files, only in source files. +- Do not (indirectly) include private dependency headers in API header files, + as these are no longer accessible when the engine is installed +- Getter and setter functions are appropriately prefixed with `get_` and + `set_`. +- Doxygen commands are used with a backslash instead of an at-sign (i.e. + `\brief` instead of `@brief`) +- A singleton's instance is always accessed using a getter function that + instantiates its own class as a static variable within the getter function + scope, instead of storing the instance as a member variable directly: + + ```cpp + class Bad { + static Bad instance; + Bad & get_instance() { return instance; } }; -} -``` -- Use enum class instead of using enum only to prevent bugs -```cpp -void Print_color(int color); - -enum Web_color { red = 0xFF0000, green = 0x00FF00, blue = 0x0000FF }; -enum Product_info { red = 0, purple = 1, blue = 2 }; - -Web_color webby = Web_color::blue; - -// Clearly at least one of these calls is buggy. -Print_color(webby); -Print_color(Product_info::blue); -``` -- Instead use an enum class: -```cpp - -void Print_color(int color); -enum class Web_color { red = 0xFF0000, green = 0x00FF00, blue = 0x0000FF }; -enum class Product_info { red = 0, purple = 1, blue = 2 }; - -Web_color webby = Web_color::blue; -Print_color(webby); // Error: cannot convert Web_color to int. -Print_color(Product_info::red); // Error: cannot convert Product_info to int. - -``` - -- Think about using size_t instead of using int for preventing processor mismatching. This might prevent a bug in the future. -```cpp -std::size_t i = 0; // use this instead of using a int -double x = 0.0; -``` -- comment style guide, you can use TODO:, FIXME:, NOTE:, or HACK: as admonitions when needed. - --- Bad example -```cpp -// Draw loading screen. -draw_load_screen(); -``` --- Better -```cpp -// Compute the first 10,000 decimals of Pi. -// FIXME: Don't crash when computing the 1,337th decimal due to `increment` -// being negative. -``` + class Good { + Good & get_instance() { + static Good instance; + return instance; + } + }; + ``` +- Member variable default values should be directly defined in the class + declaration instead of using the constructor. +- Header files declare either a single class or symbols within a single + namespace. ## CMakeLists specific diff --git a/lib/soloud/src b/lib/soloud -Subproject e82fd32c1f62183922f08c14c814a02b58db187 +Subproject e82fd32c1f62183922f08c14c814a02b58db187 diff --git a/lib/soloud/CMakeLists.txt b/lib/soloud/CMakeLists.txt deleted file mode 100644 index aaccd96..0000000 --- a/lib/soloud/CMakeLists.txt +++ /dev/null @@ -1,97 +0,0 @@ -cmake_minimum_required(VERSION 3.28) - -set(CMAKE_C_STANDARD 11) -set(CMAKE_CXX_STANDARD 20) - -add_compile_definitions(WITH_SDL2) - -add_subdirectory(../sdl2 sdl2) - -project(soloud C CXX) - -add_library(soloud SHARED - src/src/audiosource/ay/chipplayer.cpp - src/src/audiosource/ay/sndbuffer.cpp - src/src/audiosource/ay/sndchip.cpp - src/src/audiosource/ay/sndrender.cpp - src/src/audiosource/ay/soloud_ay.cpp - src/src/audiosource/monotone/soloud_monotone.cpp - src/src/audiosource/noise/soloud_noise.cpp - src/src/audiosource/openmpt/soloud_openmpt.cpp - src/src/audiosource/openmpt/soloud_openmpt_dll.c - src/src/audiosource/sfxr/soloud_sfxr.cpp - src/src/audiosource/speech/darray.cpp - src/src/audiosource/speech/klatt.cpp - src/src/audiosource/speech/resonator.cpp - src/src/audiosource/speech/soloud_speech.cpp - src/src/audiosource/speech/tts.cpp - src/src/audiosource/tedsid/sid.cpp - src/src/audiosource/tedsid/soloud_tedsid.cpp - src/src/audiosource/tedsid/ted.cpp - src/src/audiosource/vic/soloud_vic.cpp - src/src/audiosource/vizsn/soloud_vizsn.cpp - src/src/audiosource/wav/dr_impl.cpp - src/src/audiosource/wav/soloud_wav.cpp - src/src/audiosource/wav/soloud_wavstream.cpp - src/src/audiosource/wav/stb_vorbis.c - - src/src/backend/alsa/soloud_alsa.cpp - src/src/backend/coreaudio/soloud_coreaudio.cpp - src/src/backend/jack/soloud_jack.cpp - src/src/backend/miniaudio/soloud_miniaudio.cpp - src/src/backend/nosound/soloud_nosound.cpp - src/src/backend/null/soloud_null.cpp - src/src/backend/openal/soloud_openal.cpp - src/src/backend/openal/soloud_openal_dll.c - src/src/backend/opensles/soloud_opensles.cpp - src/src/backend/oss/soloud_oss.cpp - src/src/backend/portaudio/soloud_portaudio.cpp - src/src/backend/portaudio/soloud_portaudio_dll.c - src/src/backend/sdl/soloud_sdl1.cpp - src/src/backend/sdl/soloud_sdl1_dll.c - src/src/backend/sdl/soloud_sdl2.cpp - src/src/backend/sdl/soloud_sdl2_dll.c - src/src/backend/sdl2_static/soloud_sdl2_static.cpp - src/src/backend/sdl_static/soloud_sdl_static.cpp - src/src/backend/wasapi/soloud_wasapi.cpp - src/src/backend/winmm/soloud_winmm.cpp - src/src/backend/xaudio2/soloud_xaudio2.cpp - - src/src/core/soloud.cpp - src/src/core/soloud_audiosource.cpp - src/src/core/soloud_bus.cpp - src/src/core/soloud_core_3d.cpp - src/src/core/soloud_core_basicops.cpp - src/src/core/soloud_core_faderops.cpp - src/src/core/soloud_core_filterops.cpp - src/src/core/soloud_core_getters.cpp - src/src/core/soloud_core_setters.cpp - src/src/core/soloud_core_voicegroup.cpp - src/src/core/soloud_core_voiceops.cpp - src/src/core/soloud_fader.cpp - src/src/core/soloud_fft.cpp - src/src/core/soloud_fft_lut.cpp - src/src/core/soloud_file.cpp - src/src/core/soloud_filter.cpp - src/src/core/soloud_misc.cpp - src/src/core/soloud_queue.cpp - src/src/core/soloud_thread.cpp - - # src/src/filter/soloud_bassboostfilter.cpp - # src/src/filter/soloud_biquadresonantfilter.cpp - # src/src/filter/soloud_dcremovalfilter.cpp - # src/src/filter/soloud_duckfilter.cpp - # src/src/filter/soloud_echofilter.cpp - # src/src/filter/soloud_eqfilter.cpp - # src/src/filter/soloud_fftfilter.cpp - # src/src/filter/soloud_flangerfilter.cpp - # src/src/filter/soloud_freeverbfilter.cpp - # src/src/filter/soloud_lofifilter.cpp - # src/src/filter/soloud_robotizefilter.cpp - # src/src/filter/soloud_waveshaperfilter.cpp -) -target_include_directories(soloud PRIVATE src/include) -target_include_directories(soloud SYSTEM INTERFACE src/include) - -target_link_libraries(soloud PRIVATE SDL2) - @@ -6,6 +6,5 @@ doxygen: Doxyfile FORCE FMT += $(shell git ls-files '*.c' '*.cpp' '*.h' '*.hpp') format: FORCE clang-format -i $(FMT) -# clang tidy doesn't work that well :/ -# clang-tidy --fix-errors $(FMT) + $(MAKE) -C src $@ diff --git a/mwe/ecs-homemade/CMakeLists.txt b/mwe/ecs-homemade/CMakeLists.txt new file mode 100644 index 0000000..6267c1a --- /dev/null +++ b/mwe/ecs-homemade/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 3.5) +project(ecs-homemade) + +# Set the C++ standard (optional, but good practice) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED True) + +# Use the debug mode (otherwise breakpoints are not compiled) +set(CMAKE_BUILD_TYPE Debug) + +add_executable(ecs-homemade + src/main.cpp + src/ComponentManager.cpp + src/Components.cpp + src/GameObjectMax.cpp +) +target_include_directories(ecs-homemade PRIVATE "${CMAKE_SOURCE_DIR}/inc") diff --git a/mwe/ecs-homemade/inc/ComponentManager.h b/mwe/ecs-homemade/inc/ComponentManager.h new file mode 100644 index 0000000..0ba358e --- /dev/null +++ b/mwe/ecs-homemade/inc/ComponentManager.h @@ -0,0 +1,58 @@ +#pragma once + +#include "Components.h" +#include <cstdint> +#include <memory> +#include <typeindex> +#include <unordered_map> +#include <utility> +#include <vector> + +class ComponentManager { +public: + static ComponentManager & GetInstance(); //Singleton + + ComponentManager(const ComponentManager &) = delete; //Singleton + ComponentManager(ComponentManager &&) = delete; //Singleton + ComponentManager & operator=(const ComponentManager &) = delete; //Singleton + ComponentManager & operator=(ComponentManager &&) = delete; //Singleton + + //Add a component of a specific type + template <typename T, typename... Args> + T & AddComponent(std::uint32_t id, Args &&... args); + //Deletes all components of a specific type and id + template <typename T> + void DeleteComponentsById(std::uint32_t id); + //Deletes all components of a specific type + template <typename T> + void DeleteComponents(); + //Deletes all components of a specific id + void DeleteAllComponentsOfId(std::uint32_t id); + //Deletes all components + void DeleteAllComponents(); + + //Get a vector<> of all components at specific type and id + template <typename T> + std::vector<std::reference_wrapper<T>> + GetComponentsByID(std::uint32_t id) const; + //Get a vector<> of all components of a specific type + template <typename T> + std::vector<std::reference_wrapper<T>> GetComponentsByType() const; + +private: + static ComponentManager mInstance; //Singleton + + ComponentManager(); //Singleton + + /* + * The std::unordered_map<std::type_index, std::vector<std::vector<std::unique_ptr<Component>>>> below might seem a bit strange, let me explain this structure: + * The std::unordered_map<> has a key and value. The key is a std::type_index and the value is a std::vector. So, a new std::vector will be created for each new std::type_index. + * The first std::vector<> stores another vector<>. This first vector<> is to bind the entity's id to a component. + * The second std::vector<> stores unique_ptrs. Each component can be gathered via an unique_ptr. This second vector<> allows multiple components of the same std::type_index for one entity (id). + */ + std::unordered_map<std::type_index, + std::vector<std::vector<std::unique_ptr<Component>>>> + mComponents; +}; + +#include "ComponentManager.hpp" diff --git a/mwe/ecs-homemade/inc/ComponentManager.hpp b/mwe/ecs-homemade/inc/ComponentManager.hpp new file mode 100644 index 0000000..92db1d4 --- /dev/null +++ b/mwe/ecs-homemade/inc/ComponentManager.hpp @@ -0,0 +1,135 @@ + +template <typename T, typename... Args> +T & ComponentManager::AddComponent(std::uint32_t id, Args &&... args) { + //Determine the type of T (this is used as the key of the unordered_map<>) + std::type_index type = typeid(T); + + //Check if this component type is already in the unordered_map<> + if (mComponents.find(type) == mComponents.end()) { + //If not, create a new (empty) vector<> of vector<unique_ptr<Component>> + mComponents[type] + = std::vector<std::vector<std::unique_ptr<Component>>>(); + } + + //Resize the vector<> if the id is greater than the current size + if (id >= mComponents[type].size()) { + //Initialize new slots to nullptr (resize does automatically init to nullptr) + mComponents[type].resize(id + 1); + } + + //Create a new component of type T using perfect forwarding and store its unique_ptr in the vector<> + mComponents[type][id].push_back( + std::make_unique<T>(std::forward<Args>(args)...)); + + return static_cast<T &>(*mComponents[type][id].back().get()); +} + +template <typename T> +void ComponentManager::DeleteComponentsById(std::uint32_t id) { + //Determine the type of T (this is used as the key of the unordered_map<>) + std::type_index type = typeid(T); + + //Find the type (in the unordered_map<>) + if (mComponents.find(type) != mComponents.end()) { + //Get the correct vector<> + std::vector<std::vector<std::unique_ptr<Component>>> & componentArray + = mComponents[type]; + + //Make sure that the id (that we are looking for) is within the boundaries of the vector<> + if (id < componentArray.size()) { + //Clear the whole vector<> of this specific type and id + componentArray[id].clear(); + } + } +} + +template <typename T> +void ComponentManager::DeleteComponents() { + //Determine the type of T (this is used as the key of the unordered_map<>) + std::type_index type = typeid(T); + + //Find the type (in the unordered_map<>) + if (mComponents.find(type) != mComponents.end()) { + //Clear the whole vector<> of this specific type + mComponents[type].clear(); + } +} + +template <typename T> +std::vector<std::reference_wrapper<T>> +ComponentManager::GetComponentsByID(std::uint32_t id) const { + //Determine the type of T (this is used as the key of the unordered_map<>) + std::type_index type = typeid(T); + + //Create an empty vector<> + std::vector<std::reference_wrapper<T>> componentVector; + + //Find the type (in the unordered_map<>) + if (mComponents.find(type) != mComponents.end()) { + + //Get the correct vector<> + const std::vector<std::vector<std::unique_ptr<Component>>> & + componentArray + = mComponents.at(type); + + //Make sure that the id (that we are looking for) is within the boundaries of the vector<> + if (id < componentArray.size()) { + //Loop trough the whole vector<> + for (const std::unique_ptr<Component> & componentPtr : + componentArray[id]) { + //Cast the unique_ptr to a raw pointer + T * castedComponent = static_cast<T *>(componentPtr.get()); + + //Ensure that the cast was successful + if (castedComponent) { + //Add the dereferenced raw pointer to the vector<> + componentVector.push_back(*castedComponent); + } + } + } + } + + //Return the vector<> + return componentVector; +} + +template <typename T> +std::vector<std::reference_wrapper<T>> +ComponentManager::GetComponentsByType() const { + //Determine the type of T (this is used as the key of the unordered_map<>) + std::type_index type = typeid(T); + + //Create an empty vector<> + std::vector<std::reference_wrapper<T>> componentVector; + //std::uint32_t id = 0; //Set the id to 0 (the id will also be stored in the returned vector<>) + + //Find the type (in the unordered_map<>) + if (mComponents.find(type) != mComponents.end()) { + + //Get the correct vector<> + const std::vector<std::vector<std::unique_ptr<Component>>> & + componentArray + = mComponents.at(type); + + //Loop through the whole vector<> + for (const std::vector<std::unique_ptr<Component>> & component : + componentArray) { + //Loop trough the whole vector<> + for (const std::unique_ptr<Component> & componentPtr : component) { + //Cast the unique_ptr to a raw pointer + T * castedComponent = static_cast<T *>(componentPtr.get()); + + //Ensure that the cast was successful + if (castedComponent) { + //Pair the dereferenced raw pointer and the id and add it to the vector<> + componentVector.emplace_back(std::ref(*castedComponent)); + } + } + + //++id; //Increase the id (the id will also be stored in the returned vector<>) + } + } + + //Return the vector<> + return componentVector; +} diff --git a/mwe/ecs-homemade/inc/Components.h b/mwe/ecs-homemade/inc/Components.h new file mode 100644 index 0000000..ad491e7 --- /dev/null +++ b/mwe/ecs-homemade/inc/Components.h @@ -0,0 +1,66 @@ +#pragma once + +#include <memory> +#include <string> + +class Component { +public: + Component(); + + bool mActive; +}; + +class Sprite : public Component { +public: + Sprite(std::string path); + + std::string mPath; +}; + +class Rigidbody : public Component { +public: + Rigidbody(int mass, int gravityScale, int bodyType); + + int mMass; + int mGravityScale; + int mBodyType; +}; + +class Colider : public Component { +public: + Colider(int size); + + int mSize; +}; + +class IBehaviour { +public: + virtual ~IBehaviour() = default; + virtual void onStart() = 0; + virtual void onUpdate() = 0; +}; + +template <typename T> +class BehaviourWrapper : public IBehaviour { +public: + BehaviourWrapper(); + void onStart() override; + void onUpdate() override; + +private: + T instance; +}; + +class BehaviourScript : public Component { +public: + template <typename T> + void addScript(); + + void onStart(); + void onUpdate(); + +private: + std::unique_ptr<IBehaviour> behaviour; +}; + +#include "Components.hpp" diff --git a/mwe/ecs-homemade/inc/Components.hpp b/mwe/ecs-homemade/inc/Components.hpp new file mode 100644 index 0000000..436a28c --- /dev/null +++ b/mwe/ecs-homemade/inc/Components.hpp @@ -0,0 +1,20 @@ +#include "Components.h" +#include <iostream> + +template <typename T> +BehaviourWrapper<T>::BehaviourWrapper() : instance() {} + +template <typename T> +void BehaviourWrapper<T>::onStart() { + instance.onStart(); +} + +template <typename T> +void BehaviourWrapper<T>::onUpdate() { + instance.onUpdate(); +} + +template <typename T> +void BehaviourScript::addScript() { + behaviour = std::make_unique<BehaviourWrapper<T>>(); +} diff --git a/mwe/ecs-homemade/inc/GameObjectMax.h b/mwe/ecs-homemade/inc/GameObjectMax.h new file mode 100644 index 0000000..5fab44a --- /dev/null +++ b/mwe/ecs-homemade/inc/GameObjectMax.h @@ -0,0 +1,20 @@ +#pragma once + +#include <cstdint> +#include <string> + +class GameObject { +public: + GameObject(std::uint32_t id, std::string name, std::string tag, int layer); + + template <typename T, typename... Args> + T & AddComponent(Args &&... args); + + std::uint32_t mId; + std::string mName; + std::string mTag; + bool mActive; + int mLayer; +}; + +#include "GameObjectMax.hpp" diff --git a/mwe/ecs-homemade/inc/GameObjectMax.hpp b/mwe/ecs-homemade/inc/GameObjectMax.hpp new file mode 100644 index 0000000..2f433bb --- /dev/null +++ b/mwe/ecs-homemade/inc/GameObjectMax.hpp @@ -0,0 +1,7 @@ +#include "ComponentManager.h" + +template <typename T, typename... Args> +T & GameObject::AddComponent(Args &&... args) { + return ComponentManager::GetInstance().AddComponent<T>( + mId, std::forward<Args>(args)...); +} diff --git a/mwe/ecs-homemade/src/ComponentManager.cpp b/mwe/ecs-homemade/src/ComponentManager.cpp new file mode 100644 index 0000000..536c152 --- /dev/null +++ b/mwe/ecs-homemade/src/ComponentManager.cpp @@ -0,0 +1,23 @@ +#include "ComponentManager.h" + +ComponentManager ComponentManager::mInstance; + +ComponentManager & ComponentManager::GetInstance() { return mInstance; } + +ComponentManager::ComponentManager() {} + +void ComponentManager::DeleteAllComponentsOfId(std::uint32_t id) { + for (auto & [type, componentArray] : + mComponents) { //Loop through all the types (in the unordered_map<>) + if (id + < componentArray + .size()) { //Make sure that the id (that we are looking for) is within the boundaries of the vector<> + componentArray[id] + .clear(); //Clear the components at this specific id + } + } +} + +void ComponentManager::DeleteAllComponents() { + mComponents.clear(); //Clear the whole unordered_map<> +} diff --git a/mwe/ecs-homemade/src/Components.cpp b/mwe/ecs-homemade/src/Components.cpp new file mode 100644 index 0000000..de8753e --- /dev/null +++ b/mwe/ecs-homemade/src/Components.cpp @@ -0,0 +1,23 @@ +#include "Components.h" +#include <iostream> + +Component::Component() : mActive(true) {} + +Sprite::Sprite(std::string path) : mPath(path) {} + +Rigidbody::Rigidbody(int mass, int gravityScale, int bodyType) + : mMass(mass), mGravityScale(gravityScale), mBodyType(bodyType) {} + +Colider::Colider(int size) : mSize(size) {} + +void BehaviourScript::onStart() { + if (behaviour) { + behaviour->onStart(); + } +} + +void BehaviourScript::onUpdate() { + if (behaviour) { + behaviour->onUpdate(); + } +} diff --git a/mwe/ecs-homemade/src/GameObjectMax.cpp b/mwe/ecs-homemade/src/GameObjectMax.cpp new file mode 100644 index 0000000..b0c5af7 --- /dev/null +++ b/mwe/ecs-homemade/src/GameObjectMax.cpp @@ -0,0 +1,7 @@ +#include "GameObjectMax.h" + +#include "ComponentManager.h" + +GameObject::GameObject(std::uint32_t id, std::string name, std::string tag, + int layer) + : mId(id), mName(name), mTag(tag), mActive(true), mLayer(layer) {} diff --git a/mwe/ecs-homemade/src/main.cpp b/mwe/ecs-homemade/src/main.cpp new file mode 100644 index 0000000..70c5d2c --- /dev/null +++ b/mwe/ecs-homemade/src/main.cpp @@ -0,0 +1,75 @@ +#include <chrono> +#include <cstdint> +#include <iostream> +#include <vector> + +#include "ComponentManager.h" +#include "Components.h" +#include "GameObjectMax.h" + +class myScript { +public: + void onStart() { std::cout << "In onStart" << std::endl; } + + void onUpdate() { std::cout << "In onUpdate" << std::endl; } +}; + +int main() { + auto startAdding = std::chrono::high_resolution_clock::now(); + + GameObject * gameObject[100000]; + + for (int i = 0; i < 100000; ++i) { + gameObject[i] = new GameObject(i, "Name", "Tag", 0); + + gameObject[i]->AddComponent<Sprite>("C:/Test"); + gameObject[i]->AddComponent<Rigidbody>(0, 0, i); + gameObject[i]->AddComponent<Colider>(i); + gameObject[i]->AddComponent<BehaviourScript>().addScript<myScript>(); + } + + auto stopAdding = std::chrono::high_resolution_clock::now(); + + //This is what systems would do: + + std::vector<std::reference_wrapper<Sprite>> sprites + = ComponentManager::GetInstance().GetComponentsByType<Sprite>(); + for (Sprite & sprite : sprites) { + //std::cout << sprite.get().mPath << std::endl; + } + //std::cout << std::endl; + + std::vector<std::reference_wrapper<Rigidbody>> rigidBodies + = ComponentManager::GetInstance().GetComponentsByType<Rigidbody>(); + for (Rigidbody & rigidbody : rigidBodies) { + //std::cout << rigidbody.get().mMass << " " << rigidbody.get().mGravityScale << " " << rigidbody.get().mBodyType << std::endl; + } + //std::cout << std::endl; + + std::vector<std::reference_wrapper<Colider>> coliders + = ComponentManager::GetInstance().GetComponentsByType<Colider>(); + for (Colider & colider : coliders) { + //std::cout << colider.get().mSize << std::endl; + } + + std::vector<std::reference_wrapper<BehaviourScript>> scripts + = ComponentManager::GetInstance() + .GetComponentsByType<BehaviourScript>(); + for (BehaviourScript & script : scripts) { + //script.onStart(); + //script.onUpdate(); + } + + auto stopLooping = std::chrono::high_resolution_clock::now(); + + for (int i = 0; i < 100000; ++i) { + delete gameObject[i]; + } + + auto Addtime = std::chrono::duration_cast<std::chrono::microseconds>( + stopAdding - startAdding); + auto LoopTime = std::chrono::duration_cast<std::chrono::microseconds>( + stopLooping - stopAdding); + std::cout << "AddTime: " << Addtime.count() << " us" << std::endl; + std::cout << "LoopTime: " << LoopTime.count() << " us" << std::endl; +} diff --git a/mwe/ecs-memory-efficient/CMakeLists.txt b/mwe/ecs-memory-efficient/CMakeLists.txt new file mode 100644 index 0000000..d072907 --- /dev/null +++ b/mwe/ecs-memory-efficient/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 3.5) +project(ecs-memory-efficient) + +# Set the C++ standard (optional, but good practice) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED True) + +# Use the debug mode (otherwise breakpoints are not compiled) +set(CMAKE_BUILD_TYPE Debug) + +add_executable(ecs-memory-efficient + src/main.cpp + src/ComponentManager.cpp + src/Components.cpp + src/GameObjectMax.cpp +) +target_include_directories(ecs-memory-efficient PRIVATE "${CMAKE_SOURCE_DIR}/inc") diff --git a/mwe/ecs-memory-efficient/inc/ComponentManager.h b/mwe/ecs-memory-efficient/inc/ComponentManager.h new file mode 100644 index 0000000..8279a9a --- /dev/null +++ b/mwe/ecs-memory-efficient/inc/ComponentManager.h @@ -0,0 +1,36 @@ +#pragma once + +#include "Components.h" +#include "ContiguousContainer.h" + +class ComponentManager { +public: + static ComponentManager & GetInstance(); //Singleton + + ComponentManager(const ComponentManager &) = delete; //Singleton + ComponentManager(ComponentManager &&) = delete; //Singleton + ComponentManager & operator=(const ComponentManager &) = delete; //Singleton + ComponentManager & operator=(ComponentManager &&) = delete; //Singleton + + template <typename... Args> + void addSpriteComponent(Args &&... args); + template <typename... Args> + void addRigidbodyComponent(Args &&... args); + template <typename... Args> + void addColiderComponent(Args &&... args); + + std::vector<std::reference_wrapper<Sprite>> getAllSpriteReferences(); + std::vector<std::reference_wrapper<Rigidbody>> getAllRigidbodyReferences(); + std::vector<std::reference_wrapper<Colider>> getAllColiderReferences(); + +private: + static ComponentManager mInstance; //Singleton + + ComponentManager(); //Singleton + + ContiguousContainer<Sprite> mSpriteContainer; + ContiguousContainer<Rigidbody> mRigidbodyContainer; + ContiguousContainer<Colider> mColiderContainer; +}; + +#include "ComponentManager.hpp" diff --git a/mwe/ecs-memory-efficient/inc/ComponentManager.hpp b/mwe/ecs-memory-efficient/inc/ComponentManager.hpp new file mode 100644 index 0000000..a914a6b --- /dev/null +++ b/mwe/ecs-memory-efficient/inc/ComponentManager.hpp @@ -0,0 +1,15 @@ + +template <typename... Args> +void ComponentManager::addSpriteComponent(Args &&... args) { + mSpriteContainer.pushBack(std::forward<Args>(args)...); +} + +template <typename... Args> +void ComponentManager::addRigidbodyComponent(Args &&... args) { + mRigidbodyContainer.pushBack(std::forward<Args>(args)...); +} + +template <typename... Args> +void ComponentManager::addColiderComponent(Args &&... args) { + mColiderContainer.pushBack(std::forward<Args>(args)...); +} diff --git a/mwe/ecs-memory-efficient/inc/Components.h b/mwe/ecs-memory-efficient/inc/Components.h new file mode 100644 index 0000000..98c5fe7 --- /dev/null +++ b/mwe/ecs-memory-efficient/inc/Components.h @@ -0,0 +1,33 @@ +#pragma once + +#include <string> + +class Component { +public: + Component(); + + bool mActive; +}; + +class Sprite : public Component { +public: + Sprite(std::string path); + + std::string mPath; +}; + +class Rigidbody : public Component { +public: + Rigidbody(int mass, int gravityScale, int bodyType); + + int mMass; + int mGravityScale; + int mBodyType; +}; + +class Colider : public Component { +public: + Colider(int size); + + int mSize; +}; diff --git a/mwe/ecs-memory-efficient/inc/ContiguousContainer.h b/mwe/ecs-memory-efficient/inc/ContiguousContainer.h new file mode 100644 index 0000000..e3b57ba --- /dev/null +++ b/mwe/ecs-memory-efficient/inc/ContiguousContainer.h @@ -0,0 +1,34 @@ +#pragma once + +#include <cstdlib> // For malloc and free +#include <new> // For placement new +#include <stdexcept> // For std::bad_alloc +#include <utility> // For std::move and std::forward +#include <vector> // For returning references + +template <typename T> +class ContiguousContainer { +public: + ContiguousContainer(); + ~ContiguousContainer(); + + // Use perfect forwarding for pushBack + template <typename... Args> + void pushBack(Args &&... args); + + void popBack(); + T & operator[](size_t index); + size_t getSize() const; + + // Function to return references to all stored objects + std::vector<std::reference_wrapper<T>> getAllReferences(); + +private: + T * mData; + size_t mSize; + size_t mCapacity; + + void resize(size_t new_capacity); // Resize function to allocate more space +}; + +#include "ContiguousContainer.hpp" diff --git a/mwe/ecs-memory-efficient/inc/ContiguousContainer.hpp b/mwe/ecs-memory-efficient/inc/ContiguousContainer.hpp new file mode 100644 index 0000000..408d5aa --- /dev/null +++ b/mwe/ecs-memory-efficient/inc/ContiguousContainer.hpp @@ -0,0 +1,84 @@ +template <typename T> +ContiguousContainer<T>::ContiguousContainer() : mSize(0), mCapacity(10) { + // Allocate memory for 10 objects initially + mData = static_cast<T *>(malloc(mCapacity * sizeof(T))); + if (!mData) { + throw std::bad_alloc(); + } +} + +template <typename T> +ContiguousContainer<T>::~ContiguousContainer() { + // Destroy all constructed objects + for (size_t i = 0; i < mSize; ++i) { + mData[i].~T(); + } + // Free the allocated memory + free(mData); +} + +template <typename T> +template <typename... Args> +void ContiguousContainer<T>::pushBack(Args &&... args) { + if (mSize == mCapacity) { + // Double the capacity if the container is full + resize(mCapacity * 2); + } + // Use placement new with perfect forwarding to construct the object in place + new (mData + mSize) T(std::forward<Args>(args)...); + ++mSize; +} + +template <typename T> +void ContiguousContainer<T>::popBack() { + if (mSize > 0) { + --mSize; + // Explicitly call the destructor + mData[mSize].~T(); + } +} + +template <typename T> +T & ContiguousContainer<T>::operator[](size_t index) { + if (index >= mSize) { + throw std::out_of_range("Index out of range"); + } + return mData[index]; +} + +template <typename T> +size_t ContiguousContainer<T>::getSize() const { + return mSize; +} + +// Function that returns a vector of references to all stored objects +template <typename T> +std::vector<std::reference_wrapper<T>> +ContiguousContainer<T>::getAllReferences() { + std::vector<std::reference_wrapper<T>> references; + references.reserve(mSize); // Reserve space to avoid reallocation + for (size_t i = 0; i < mSize; ++i) { + references.push_back(std::ref(mData[i])); + } + return references; +} + +template <typename T> +void ContiguousContainer<T>::resize(size_t new_capacity) { + // Allocate new memory block with the updated capacity + T * new_data = static_cast<T *>(malloc(new_capacity * sizeof(T))); + if (!new_data) { + throw std::bad_alloc(); + } + + // Move or copy existing objects to the new memory block + for (size_t i = 0; i < mSize; ++i) { + new (new_data + i) T(std::move(mData[i])); // Move the objects + mData[i].~T(); // Call the destructor for the old object + } + + // Free the old memory block + free(mData); + mData = new_data; + mCapacity = new_capacity; +} diff --git a/mwe/ecs-memory-efficient/inc/GameObjectMax.h b/mwe/ecs-memory-efficient/inc/GameObjectMax.h new file mode 100644 index 0000000..760e330 --- /dev/null +++ b/mwe/ecs-memory-efficient/inc/GameObjectMax.h @@ -0,0 +1,24 @@ +#pragma once + +#include <cstdint> +#include <string> + +class GameObject { +public: + GameObject(std::uint32_t id, std::string name, std::string tag, int layer); + + template <typename... Args> + void addSpriteComponent(Args &&... args); + template <typename... Args> + void addRigidbodyComponent(Args &&... args); + template <typename... Args> + void addColiderComponent(Args &&... args); + + std::uint32_t mId; + std::string mName; + std::string mTag; + bool mActive; + int mLayer; +}; + +#include "GameObjectMax.hpp" diff --git a/mwe/ecs-memory-efficient/inc/GameObjectMax.hpp b/mwe/ecs-memory-efficient/inc/GameObjectMax.hpp new file mode 100644 index 0000000..be3ffa2 --- /dev/null +++ b/mwe/ecs-memory-efficient/inc/GameObjectMax.hpp @@ -0,0 +1,19 @@ +#include "ComponentManager.h" + +template <typename... Args> +void GameObject::addSpriteComponent(Args &&... args) { + ComponentManager::GetInstance().addSpriteComponent( + std::forward<Args>(args)...); +} + +template <typename... Args> +void GameObject::addRigidbodyComponent(Args &&... args) { + ComponentManager::GetInstance().addRigidbodyComponent( + std::forward<Args>(args)...); +} + +template <typename... Args> +void GameObject::addColiderComponent(Args &&... args) { + ComponentManager::GetInstance().addColiderComponent( + std::forward<Args>(args)...); +} diff --git a/mwe/ecs-memory-efficient/src/ComponentManager.cpp b/mwe/ecs-memory-efficient/src/ComponentManager.cpp new file mode 100644 index 0000000..8c1fd23 --- /dev/null +++ b/mwe/ecs-memory-efficient/src/ComponentManager.cpp @@ -0,0 +1,22 @@ +#include "ComponentManager.h" + +ComponentManager ComponentManager::mInstance; + +ComponentManager & ComponentManager::GetInstance() { return mInstance; } + +ComponentManager::ComponentManager() {} + +std::vector<std::reference_wrapper<Sprite>> +ComponentManager::getAllSpriteReferences() { + return mSpriteContainer.getAllReferences(); +} + +std::vector<std::reference_wrapper<Rigidbody>> +ComponentManager::getAllRigidbodyReferences() { + return mRigidbodyContainer.getAllReferences(); +} + +std::vector<std::reference_wrapper<Colider>> +ComponentManager::getAllColiderReferences() { + return mColiderContainer.getAllReferences(); +} diff --git a/mwe/ecs-memory-efficient/src/Components.cpp b/mwe/ecs-memory-efficient/src/Components.cpp new file mode 100644 index 0000000..c8347b3 --- /dev/null +++ b/mwe/ecs-memory-efficient/src/Components.cpp @@ -0,0 +1,11 @@ +#include "Components.h" +#include <iostream> + +Component::Component() : mActive(true) {} + +Sprite::Sprite(std::string path) : mPath(path) {} + +Rigidbody::Rigidbody(int mass, int gravityScale, int bodyType) + : mMass(mass), mGravityScale(gravityScale), mBodyType(bodyType) {} + +Colider::Colider(int size) : mSize(size) {} diff --git a/mwe/ecs-memory-efficient/src/GameObjectMax.cpp b/mwe/ecs-memory-efficient/src/GameObjectMax.cpp new file mode 100644 index 0000000..b0c5af7 --- /dev/null +++ b/mwe/ecs-memory-efficient/src/GameObjectMax.cpp @@ -0,0 +1,7 @@ +#include "GameObjectMax.h" + +#include "ComponentManager.h" + +GameObject::GameObject(std::uint32_t id, std::string name, std::string tag, + int layer) + : mId(id), mName(name), mTag(tag), mActive(true), mLayer(layer) {} diff --git a/mwe/ecs-memory-efficient/src/main.cpp b/mwe/ecs-memory-efficient/src/main.cpp new file mode 100644 index 0000000..9c6f2aa --- /dev/null +++ b/mwe/ecs-memory-efficient/src/main.cpp @@ -0,0 +1,59 @@ +#include <chrono> +#include <cstdint> +#include <iostream> +#include <vector> + +#include "ComponentManager.h" +#include "Components.h" +#include "GameObjectMax.h" + +int main() { + auto startAdding = std::chrono::high_resolution_clock::now(); + + GameObject * gameObject[100000]; + + for (int i = 0; i < 100000; ++i) { + gameObject[i] = new GameObject(i, "Name", "Tag", 0); + + gameObject[i]->addSpriteComponent("C:/Test"); + gameObject[i]->addRigidbodyComponent(0, 0, i); + gameObject[i]->addColiderComponent(i); + } + + auto stopAdding = std::chrono::high_resolution_clock::now(); + + //This is what systems would do: + + std::vector<std::reference_wrapper<Sprite>> allSprites + = ComponentManager::GetInstance().getAllSpriteReferences(); + for (Sprite & sprite : allSprites) { + //std::cout << sprite.mPath << std::endl; + } + //std::cout << std::endl; + + std::vector<std::reference_wrapper<Rigidbody>> allRigidbody + = ComponentManager::GetInstance().getAllRigidbodyReferences(); + for (Rigidbody & rigidbody : allRigidbody) { + //std::cout << rigidbody.mMass << " " << rigidbody.mGravityScale << " " << rigidbody.mBodyType << std::endl; + } + //std::cout << std::endl; + + std::vector<std::reference_wrapper<Colider>> allColider + = ComponentManager::GetInstance().getAllColiderReferences(); + for (Colider & colider : allColider) { + //std::cout << colider.mSize << std::endl; + } + + auto stopLooping = std::chrono::high_resolution_clock::now(); + + for (int i = 0; i < 100000; ++i) { + delete gameObject[i]; + } + + auto Addtime = std::chrono::duration_cast<std::chrono::microseconds>( + stopAdding - startAdding); + auto LoopTime = std::chrono::duration_cast<std::chrono::microseconds>( + stopLooping - stopAdding); + std::cout << "AddTime: " << Addtime.count() << " us" << std::endl; + std::cout << "LoopTime: " << LoopTime.count() << " us" << std::endl; +} diff --git a/mwe/gameloop/include/gameObject.h b/mwe/gameloop/include/gameObject.h index 69f4d52..abdc9b0 100644 --- a/mwe/gameloop/include/gameObject.h +++ b/mwe/gameloop/include/gameObject.h @@ -19,6 +19,7 @@ public: void setHeight(float value); void setVelX(float value); void setVelY(float value); + int direction; private: std::string name = ""; diff --git a/mwe/gameloop/include/timer.h b/mwe/gameloop/include/timer.h index 22383b2..8273746 100644 --- a/mwe/gameloop/include/timer.h +++ b/mwe/gameloop/include/timer.h @@ -13,6 +13,8 @@ public: double getFixedDeltaTime() const; void setFPS(int FPS); int getFPS() const; + double getGameScale(); + void setGameScale(double); void enforceFrameRate(); double getLag() const; diff --git a/mwe/gameloop/src/loopManager.cpp b/mwe/gameloop/src/loopManager.cpp index cb532cc..0392853 100644 --- a/mwe/gameloop/src/loopManager.cpp +++ b/mwe/gameloop/src/loopManager.cpp @@ -11,7 +11,14 @@ void LoopManager::processInput() { case SDL_KEYDOWN: if (event.key.keysym.sym == SDLK_ESCAPE) { gameRunning = false; + } else if (event.key.keysym.sym == SDLK_i) { + LoopTimer::getInstance().setGameScale( + LoopTimer::getInstance().getGameScale() + 0.1); + } else if (event.key.keysym.sym == SDLK_k) { + LoopTimer::getInstance().setGameScale( + LoopTimer::getInstance().getGameScale() - 0.1); } + break; } } @@ -41,11 +48,11 @@ void LoopManager::loop() { void LoopManager::setup() { gameRunning = window.initWindow(); LoopTimer::getInstance().start(); - LoopTimer::getInstance().setFPS(50); + LoopTimer::getInstance().setFPS(500); - for (int i = 0; i < 2; i++) { + for (int i = 1; i < 3; i++) { GameObject * square - = new GameObject("square2", i * 40, i * 40, 20, 20, 0, 0); + = new GameObject("square2", i * 60, i * 60, 20, 20, 0, 0); objectList.push_back(square); } } @@ -57,13 +64,26 @@ void LoopManager::render() { } void LoopManager::update() { - fprintf(stderr, "**********normal update********** \n"); + fprintf(stderr, "********** normal update ********** \n"); LoopTimer & timer = LoopTimer::getInstance(); float delta = timer.getDeltaTime(); - for (int i = 0; i < objectList.size(); i++) { - objectList[i]->setX(objectList[i]->getX() + 50 * delta); - objectList[i]->setY(objectList[i]->getY() + 50 * delta); + GameObject * obj = objectList[i]; + + // Move the object based on its direction + if (obj->direction == 1) { + obj->setX(obj->getX() + 50 * delta); + } else { + obj->setX(obj->getX() - 50 * delta); + } + + if (obj->getX() > 500) { + obj->setX(500); + obj->direction = 0; // Switch direction to left + } else if (obj->getX() < 50) { + obj->setX(50); // Clamp the position to the boundary + obj->direction = 1; // Switch direction to right + } } } diff --git a/mwe/gameloop/src/main.cpp b/mwe/gameloop/src/main.cpp index 889a30a..c0f216a 100644 --- a/mwe/gameloop/src/main.cpp +++ b/mwe/gameloop/src/main.cpp @@ -4,9 +4,6 @@ //#include "window.h" #include "loopManager.h" #include "timer.h" -//Screen dimension constants - -//Starts up SDL and creates window int main(int argc, char * args[]) { LoopManager gameLoop; diff --git a/mwe/gameloop/src/timer.cpp b/mwe/gameloop/src/timer.cpp index 61e144d..97baef6 100644 --- a/mwe/gameloop/src/timer.cpp +++ b/mwe/gameloop/src/timer.cpp @@ -1,15 +1,11 @@ #include "timer.h" - -// Constructor (private) LoopTimer::LoopTimer() {} -// Get the singleton instance of the timer LoopTimer & LoopTimer::getInstance() { static LoopTimer instance; return instance; } -// Start the timer (initialize frame time) void LoopTimer::start() { lastFrameTime = SDL_GetTicks64(); elapsedTime = 0; @@ -17,16 +13,14 @@ void LoopTimer::start() { deltaTime = 0; } -// Update the timer, calculate deltaTime void LoopTimer::update() { uint64_t currentFrameTime = SDL_GetTicks64(); - deltaTime - = (currentFrameTime - lastFrameTime) / 1000.0; // Convert to seconds + deltaTime = (currentFrameTime - lastFrameTime) / 1000.0; if (deltaTime > maximumDeltaTime) { deltaTime = maximumDeltaTime; } - + deltaTime *= gameScale; elapsedTime += deltaTime; lastFrameTime = currentFrameTime; } @@ -44,7 +38,8 @@ void LoopTimer::setFPS(int FPS) { } int LoopTimer::getFPS() const { return FPS; } - +void LoopTimer::setGameScale(double value) { gameScale = value; }; +double LoopTimer::getGameScale() { return gameScale; } void LoopTimer::enforceFrameRate() { uint64_t currentFrameTime = SDL_GetTicks64(); double frameDuration = (currentFrameTime - lastFrameTime) / 1000.0; @@ -7,18 +7,46 @@ This repository contains: |`lib/`|third-party libraries as git submodules| |`mwe/`|minimal working examples and proof-of-concepts| |`src/crepe/`|game engine source code| -|`test/`|game engine unit tests| +|`src/test/`|unit tests| +|`src/example/`|standalone examples using game engine| ## Compilation This repository uses CMake (the makefile in the root of this repository is for -running auxiliary tasks only). Make sure you have initialized and updated the -submodules before compiling. +running auxiliary tasks only). See [src/readme.md](src/readme.md) for detailed +building instructions. + +## Installing libraries + +The expected library (source) versions are included in this repository as git +submodules. Follow these steps for manually building one of the required +libraries from source: + +1. Ensure the git submodules are initialized: + ``` + $ git submodule update --init --recursive --depth 1 + ``` +2. `cd` into the library source folder: + ``` + $ cd lib/googletest + or + $ cd lib/sdl2 + or + $ cd lib/soloud/contrib + ``` +3. Configure the build, run the build and install: + ``` + $ cmake -B build -G Ninja + $ ninja -C build + # ninja -C build install + ``` ## Documentation -API documentation is done using Doxygen. To generate the docs, run `make -doxygen`. +API documentation is done using Doxygen. To generate the docs, run +``` +$ make doxygen +``` ## Code style diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a9193fd..09c60bd 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -3,36 +3,37 @@ cmake_minimum_required(VERSION 3.28) set(CMAKE_C_STANDARD 11) set(CMAKE_CXX_STANDARD 20) set(CMAKE_EXPORT_COMPILE_COMMANDS 1) - -# enable debug features set(CMAKE_BUILD_TYPE Debug) -add_compile_definitions(DEBUG) - -add_subdirectory(../lib/soloud soloud) -add_subdirectory(../lib/sdl_image/src SDL2_image) project(crepe C CXX) +find_package(SDL2 REQUIRED) +find_package(SoLoud REQUIRED) +find_package(GTest REQUIRED) + add_library(crepe SHARED) +add_executable(test_main EXCLUDE_FROM_ALL) target_include_directories(crepe PUBLIC SYSTEM INTERFACE . ) -# TODO: libraries should be linked as PRIVATE target_link_libraries(crepe - PUBLIC soloud - PUBLIC SDL2 - PUBLIC SDL2_image + PRIVATE soloud + PRIVATE SDL2 ) add_subdirectory(crepe) +add_subdirectory(test) +add_subdirectory(example) install( TARGETS crepe FILE_SET HEADERS DESTINATION include/crepe ) -add_executable(dummy_rm dummy_rendering.cpp) -#add_executable(dummy_rm dummy_audio.cpp) -target_link_libraries(dummy_rm PUBLIC crepe) +target_link_libraries(test_main + PRIVATE gtest_main + PUBLIC crepe +) + diff --git a/src/crepe/Asset.cpp b/src/crepe/Asset.cpp new file mode 100644 index 0000000..15ddc27 --- /dev/null +++ b/src/crepe/Asset.cpp @@ -0,0 +1,14 @@ +#include <filesystem> + +#include "Asset.h" + +using namespace crepe; + +Asset::Asset(const std::string & src) { + this->src = std::filesystem::canonical(src); + this->file = std::ifstream(this->src, std::ios::in | std::ios::binary); +} + +const std::istream & Asset::read() { return this->file; } + +const char * Asset::canonical() { return this->src.c_str(); } diff --git a/src/crepe/Asset.h b/src/crepe/Asset.h new file mode 100644 index 0000000..0cb5834 --- /dev/null +++ b/src/crepe/Asset.h @@ -0,0 +1,33 @@ +#pragma once + +#include <fstream> +#include <iostream> +#include <string> + +namespace crepe { + +/** + * \brief Asset location helper + * + * This class is used to locate and canonicalize paths to game asset files, and + * should *always* be used when retrieving files from disk. + */ +class Asset { +public: + /** + * \param src Unique identifier to asset + */ + Asset(const std::string & src); + +public: + //! Get an input stream to the contents of this resource + const std::istream & read(); + //! Get the canonical path to this resource + const char * canonical(); + +private: + std::string src; + std::ifstream file; +}; + +} // namespace crepe diff --git a/src/crepe/CMakeLists.txt b/src/crepe/CMakeLists.txt index 05f14d1..addb9dd 100644 --- a/src/crepe/CMakeLists.txt +++ b/src/crepe/CMakeLists.txt @@ -1,7 +1,31 @@ target_sources(crepe PUBLIC + Asset.cpp + Sound.cpp + SoundContext.cpp + ComponentManager.cpp + Component.cpp + GameObject.cpp + Collider.cpp + Rigidbody.cpp + Sprite.cpp + ScriptSystem.cpp + Script.cpp ) target_sources(crepe PUBLIC FILE_SET HEADERS FILES + Asset.h + Sound.h + SoundContext.h + ComponentManager.h + ComponentManager.hpp + Component.h + GameObject.h + GameObject.hpp + Collider.h + Rigidbody.h + Sprite.h + System.h + ScriptSystem.h ) add_subdirectory(api) diff --git a/src/crepe/Collider.cpp b/src/crepe/Collider.cpp new file mode 100644 index 0000000..3f12afd --- /dev/null +++ b/src/crepe/Collider.cpp @@ -0,0 +1,5 @@ +#include "Collider.h" + +using namespace crepe; + +Collider::Collider(int size) : size(size) {} diff --git a/src/crepe/Collider.h b/src/crepe/Collider.h new file mode 100644 index 0000000..120da05 --- /dev/null +++ b/src/crepe/Collider.h @@ -0,0 +1,14 @@ +#pragma once + +#include "Component.h" + +namespace crepe { + +class Collider : public Component { +public: + Collider(int size); + + int size; +}; + +} // namespace crepe diff --git a/src/crepe/Component.cpp b/src/crepe/Component.cpp new file mode 100644 index 0000000..d14159c --- /dev/null +++ b/src/crepe/Component.cpp @@ -0,0 +1,5 @@ +#include "Component.h" + +using namespace crepe; + +Component::Component() : active(true) {} diff --git a/src/crepe/Component.h b/src/crepe/Component.h new file mode 100644 index 0000000..16a4ce5 --- /dev/null +++ b/src/crepe/Component.h @@ -0,0 +1,14 @@ +#pragma once + +namespace crepe { + +class Component { +public: + Component(); + // TODO: shouldn't this constructor be deleted because this class will never + // directly be instantiated? + + bool active; +}; + +} // namespace crepe diff --git a/src/crepe/ComponentManager.cpp b/src/crepe/ComponentManager.cpp new file mode 100644 index 0000000..9a3fec7 --- /dev/null +++ b/src/crepe/ComponentManager.cpp @@ -0,0 +1,24 @@ +#include "ComponentManager.h" + +using namespace crepe; + +ComponentManager & ComponentManager::get_instance() { + static ComponentManager instance; + return instance; +} + +void ComponentManager::delete_all_components_of_id(uint32_t id) { + // Loop through all the types (in the unordered_map<>) + for (auto & [type, componentArray] : components) { + // Make sure that the id (that we are looking for) is within the boundaries of the vector<> + if (id < componentArray.size()) { + // Clear the components at this specific id + componentArray[id].clear(); + } + } +} + +void ComponentManager::delete_all_components() { + // Clear the whole unordered_map<> + components.clear(); +} diff --git a/src/crepe/ComponentManager.h b/src/crepe/ComponentManager.h new file mode 100644 index 0000000..2ab9dc8 --- /dev/null +++ b/src/crepe/ComponentManager.h @@ -0,0 +1,62 @@ +#pragma once + +#include <cstdint> +#include <memory> +#include <typeindex> +#include <unordered_map> +#include <utility> +#include <vector> + +#include "Component.h" + +namespace crepe { + +class ComponentManager { +public: + // Singleton + static ComponentManager & get_instance(); + ComponentManager(const ComponentManager &) = delete; + ComponentManager(ComponentManager &&) = delete; + ComponentManager & operator=(const ComponentManager &) = delete; + ComponentManager & operator=(ComponentManager &&) = delete; + +public: + //! Add a component of a specific type + template <typename T, typename... Args> + void add_component(uint32_t id, Args &&... args); + //! Deletes all components of a specific type and id + template <typename T> + void delete_components_by_id(uint32_t id); + //! Deletes all components of a specific type + template <typename T> + void delete_components(); + //! Deletes all components of a specific id + void delete_all_components_of_id(uint32_t id); + //! Deletes all components + void delete_all_components(); + + //! Get a vector<> of all components at specific type and id + template <typename T> + std::vector<std::reference_wrapper<T>> + get_components_by_id(uint32_t id) const; + //! Get a vector<> of all components of a specific type + template <typename T> + std::vector<std::reference_wrapper<T>> get_components_by_type() const; + +private: + ComponentManager() = default; + + /* + * The std::unordered_map<std::type_index, std::vector<std::vector<std::unique_ptr<Component>>>> below might seem a bit strange, let me explain this structure: + * The std::unordered_map<> has a key and value. The key is a std::type_index and the value is a std::vector. So, a new std::vector will be created for each new std::type_index. + * The first std::vector<> stores another vector<>. This first vector<> is to bind the entity's id to a component. + * The second std::vector<> stores unique_ptrs. Each component can be gathered via an unique_ptr. This second vector<> allows multiple components of the same std::type_index for one entity (id). + */ + std::unordered_map<std::type_index, + std::vector<std::vector<std::unique_ptr<Component>>>> + components; +}; + +} // namespace crepe + +#include "ComponentManager.hpp" diff --git a/src/crepe/ComponentManager.hpp b/src/crepe/ComponentManager.hpp new file mode 100644 index 0000000..2ea0c70 --- /dev/null +++ b/src/crepe/ComponentManager.hpp @@ -0,0 +1,153 @@ +#pragma once + +#include <type_traits> + +#include "ComponentManager.h" + +namespace crepe { + +template <class T, typename... Args> +void ComponentManager::add_component(uint32_t id, Args &&... args) { + using namespace std; + + static_assert(is_base_of<Component, T>::value, + "add_component must recieve a derivative class of Component"); + + // Determine the type of T (this is used as the key of the unordered_map<>) + type_index type = typeid(T); + + // Check if this component type is already in the unordered_map<> + if (components.find(type) == components.end()) { + //If not, create a new (empty) vector<> of vector<unique_ptr<Component>> + components[type] = vector<vector<unique_ptr<Component>>>(); + } + + // Resize the vector<> if the id is greater than the current size + if (id >= components[type].size()) { + // Initialize new slots to nullptr (resize does automatically init to nullptr) + components[type].resize(id + 1); + } + + // Create a new component of type T (arguments directly forwarded). The + // constructor must be called by ComponentManager. + T * instance = new T(forward<Args>(args)...); + // store its unique_ptr in the vector<> + components[type][id].push_back(unique_ptr<T>(instance)); +} + +template <typename T> +void ComponentManager::delete_components_by_id(uint32_t id) { + using namespace std; + + // Determine the type of T (this is used as the key of the unordered_map<>) + type_index type = typeid(T); + + // Find the type (in the unordered_map<>) + if (components.find(type) != components.end()) { + // Get the correct vector<> + vector<vector<unique_ptr<Component>>> & component_array + = components[type]; + + // Make sure that the id (that we are looking for) is within the boundaries of the vector<> + if (id < component_array.size()) { + // Clear the whole vector<> of this specific type and id + component_array[id].clear(); + } + } +} + +template <typename T> +void ComponentManager::delete_components() { + // Determine the type of T (this is used as the key of the unordered_map<>) + std::type_index type = typeid(T); + + // Find the type (in the unordered_map<>) + if (components.find(type) != components.end()) { + // Clear the whole vector<> of this specific type + components[type].clear(); + } +} + +template <typename T> +std::vector<std::reference_wrapper<T>> +ComponentManager::get_components_by_id(uint32_t id) const { + using namespace std; + + // Determine the type of T (this is used as the key of the unordered_map<>) + type_index type = typeid(T); + + // Create an empty vector<> + vector<reference_wrapper<T>> component_vector; + + // Find the type (in the unordered_map<>) + if (components.find(type) != components.end()) { + // Get the correct vector<> + const vector<vector<unique_ptr<Component>>> & component_array + = components.at(type); + + // Make sure that the id (that we are looking for) is within the boundaries of the vector<> + if (id < component_array.size()) { + // Loop trough the whole vector<> + for (const unique_ptr<Component> & component_ptr : + component_array[id]) { + // Cast the unique_ptr to a raw pointer + T * casted_component = static_cast<T *>(component_ptr.get()); + + // Ensure that the cast was successful + if (casted_component) { + // Add the dereferenced raw pointer to the vector<> + component_vector.push_back(*casted_component); + } + } + } + } + + // Return the vector<> + return component_vector; +} + +template <typename T> +std::vector<std::reference_wrapper<T>> +ComponentManager::get_components_by_type() const { + using namespace std; + + // Determine the type of T (this is used as the key of the unordered_map<>) + type_index type = typeid(T); + + // Create an empty vector<> + vector<reference_wrapper<T>> component_vector; + // Set the id to 0 (the id will also be stored in the returned vector<>) + // uint32_t id = 0; + + // Find the type (in the unordered_map<>) + if (components.find(type) != components.end()) { + + // Get the correct vector<> + const vector<vector<unique_ptr<Component>>> & component_array + = components.at(type); + + // Loop through the whole vector<> + for (const vector<unique_ptr<Component>> & component : + component_array) { + // Loop trough the whole vector<> + for (const unique_ptr<Component> & component_ptr : component) { + // Cast the unique_ptr to a raw pointer + T * casted_component = static_cast<T *>(component_ptr.get()); + + // Ensure that the cast was successful + if (casted_component) { + // Pair the dereferenced raw pointer and the id and add it to the vector<> + component_vector.emplace_back(ref(*casted_component)); + } + } + + // Increase the id (the id will also be stored in the returned vector<>) + //++id; + } + } + + // Return the vector<> + return component_vector; +} + +} // namespace crepe diff --git a/src/crepe/GameObject.cpp b/src/crepe/GameObject.cpp new file mode 100644 index 0000000..de3beb6 --- /dev/null +++ b/src/crepe/GameObject.cpp @@ -0,0 +1,7 @@ +#include "GameObject.h" + +using namespace crepe; + +GameObject::GameObject(uint32_t id, std::string name, std::string tag, + int layer) + : id(id), name(name), tag(tag), active(true), layer(layer) {} diff --git a/src/crepe/GameObject.h b/src/crepe/GameObject.h new file mode 100644 index 0000000..3588d9a --- /dev/null +++ b/src/crepe/GameObject.h @@ -0,0 +1,24 @@ +#pragma once + +#include <cstdint> +#include <string> + +namespace crepe { + +class GameObject { +public: + GameObject(uint32_t id, std::string name, std::string tag, int layer); + + template <typename T, typename... Args> + void add_component(Args &&... args); + + uint32_t id; + std::string name; + std::string tag; + bool active; + int layer; +}; + +} // namespace crepe + +#include "GameObject.hpp" diff --git a/src/crepe/GameObject.hpp b/src/crepe/GameObject.hpp new file mode 100644 index 0000000..5966fbf --- /dev/null +++ b/src/crepe/GameObject.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include "GameObject.h" + +#include "ComponentManager.h" + +namespace crepe { + +template <typename T, typename... Args> +void GameObject::add_component(Args &&... args) { + auto & mgr = ComponentManager::get_instance(); + mgr.add_component<T>(id, std::forward<Args>(args)...); +} + +} // namespace crepe diff --git a/src/crepe/Rigidbody.cpp b/src/crepe/Rigidbody.cpp new file mode 100644 index 0000000..495d908 --- /dev/null +++ b/src/crepe/Rigidbody.cpp @@ -0,0 +1,6 @@ +#include "Rigidbody.h" + +using namespace crepe; + +Rigidbody::Rigidbody(int mass, int gravityScale, int bodyType) + : mass(mass), gravity_scale(gravityScale), body_type(bodyType) {} diff --git a/src/crepe/Rigidbody.h b/src/crepe/Rigidbody.h new file mode 100644 index 0000000..63a8877 --- /dev/null +++ b/src/crepe/Rigidbody.h @@ -0,0 +1,16 @@ +#pragma once + +#include "Component.h" + +namespace crepe { + +class Rigidbody : public Component { +public: + Rigidbody(int mass, int gravityScale, int bodyType); + + int mass; + int gravity_scale; + int body_type; +}; + +} // namespace crepe diff --git a/src/crepe/Script.cpp b/src/crepe/Script.cpp new file mode 100644 index 0000000..3b83b7c --- /dev/null +++ b/src/crepe/Script.cpp @@ -0,0 +1,6 @@ +#include "Script.h" + +using namespace crepe; + +void Script::init() {} +void Script::update() {} diff --git a/src/crepe/Script.h b/src/crepe/Script.h new file mode 100644 index 0000000..cdd6814 --- /dev/null +++ b/src/crepe/Script.h @@ -0,0 +1,15 @@ +#pragma once + +namespace crepe { + +class Script { +protected: + virtual void init(); + virtual void update(); + // NOTE: additional *events* (like unity's OnDisable and OnEnable) should be + // implemented as member methods in derivative user script classes and + // registered in init(), otherwise this class will balloon in size with each + // added event. +}; + +} // namespace crepe diff --git a/src/crepe/ScriptSystem.cpp b/src/crepe/ScriptSystem.cpp new file mode 100644 index 0000000..988bb71 --- /dev/null +++ b/src/crepe/ScriptSystem.cpp @@ -0,0 +1,15 @@ +#include "util/log.h" + +#include "ScriptSystem.h" + +using namespace crepe; + +ScriptSystem::ScriptSystem() { dbg_trace(); } +ScriptSystem::~ScriptSystem() { dbg_trace(); } + +ScriptSystem & ScriptSystem::get_instance() { + static ScriptSystem instance; + return instance; +} + +void ScriptSystem::update() { dbg_trace(); } diff --git a/src/crepe/ScriptSystem.h b/src/crepe/ScriptSystem.h new file mode 100644 index 0000000..72e360b --- /dev/null +++ b/src/crepe/ScriptSystem.h @@ -0,0 +1,17 @@ +#pragma once + +#include "System.h" + +namespace crepe { + +class ScriptSystem : public System { +public: + static ScriptSystem & get_instance(); + virtual void update(); + +private: + ScriptSystem(); + ~ScriptSystem(); +}; + +} // namespace crepe diff --git a/src/crepe/Sprite.cpp b/src/crepe/Sprite.cpp new file mode 100644 index 0000000..a5a5e68 --- /dev/null +++ b/src/crepe/Sprite.cpp @@ -0,0 +1,8 @@ +#include <string> + +#include "Sprite.h" + +using namespace crepe; +using namespace std; + +Sprite::Sprite(string path) : path(path) {} diff --git a/src/crepe/Sprite.h b/src/crepe/Sprite.h new file mode 100644 index 0000000..143e702 --- /dev/null +++ b/src/crepe/Sprite.h @@ -0,0 +1,16 @@ +#pragma once + +#include <string> + +#include "Component.h" + +namespace crepe { + +class Sprite : public Component { +public: + Sprite(std::string path); + + std::string path; +}; + +} // namespace crepe diff --git a/src/crepe/System.h b/src/crepe/System.h new file mode 100644 index 0000000..ecbb7f5 --- /dev/null +++ b/src/crepe/System.h @@ -0,0 +1,22 @@ +#pragma once + +namespace crepe { + +class System { +public: + static System & get_instance(); + virtual void update() = 0; + +protected: + System() {}; + virtual ~System() {}; + +private: + // singleton + System(const System &) = delete; + System(System &&) = delete; + System & operator=(const System &) = delete; + System & operator=(System &&) = delete; +}; + +} // namespace crepe diff --git a/src/crepe/api/AudioSource.cpp b/src/crepe/api/AudioSource.cpp index f0d708a..a5b6d6a 100644 --- a/src/crepe/api/AudioSource.cpp +++ b/src/crepe/api/AudioSource.cpp @@ -5,18 +5,18 @@ using namespace crepe::api; -AudioSource::AudioSource(std::unique_ptr<Resource> audio_clip) { - this->_sound = std::make_unique<crepe::Sound>(std::move(audio_clip)); +AudioSource::AudioSource(std::unique_ptr<Asset> audio_clip) { + this->sound = std::make_unique<crepe::Sound>(std::move(audio_clip)); } void AudioSource::play() { return this->play(false); } void AudioSource::play(bool looping) { - this->_sound->set_looping(looping); - this->_sound->play(); + this->sound->set_looping(looping); + this->sound->play(); } void AudioSource::stop() { - this->_sound->pause(); - this->_sound->rewind(); + this->sound->pause(); + this->sound->rewind(); } diff --git a/src/crepe/api/AudioSource.h b/src/crepe/api/AudioSource.h index 4300c48..2d26cda 100644 --- a/src/crepe/api/AudioSource.h +++ b/src/crepe/api/AudioSource.h @@ -2,8 +2,8 @@ #include <memory> +#include "Asset.h" #include "Component.h" -#include "Resource.h" namespace crepe { class Sound; @@ -14,7 +14,7 @@ namespace crepe::api { //! Audio source component class AudioSource : Component { public: - AudioSource(std::unique_ptr<Resource> audio_clip); + AudioSource(std::unique_ptr<Asset> audio_clip); virtual ~AudioSource() = default; public: @@ -26,7 +26,7 @@ public: public: //! Sample file location - std::unique_ptr<Resource> audio_clip; + std::unique_ptr<Asset> audio_clip; //! TODO: ????? bool play_on_awake; //! Repeat the current audio clip during playback @@ -35,7 +35,7 @@ public: float volume; private: - std::unique_ptr<crepe::Sound> _sound; + std::unique_ptr<crepe::Sound> sound; }; } // namespace crepe::api diff --git a/src/crepe/api/BehaviorScript.cpp b/src/crepe/api/BehaviorScript.cpp new file mode 100644 index 0000000..84bfd4c --- /dev/null +++ b/src/crepe/api/BehaviorScript.cpp @@ -0,0 +1,7 @@ +#include "../util/log.h" + +#include "BehaviorScript.h" + +using namespace crepe::api; + +BehaviorScript::BehaviorScript() { dbg_trace(); } diff --git a/src/crepe/api/BehaviorScript.h b/src/crepe/api/BehaviorScript.h new file mode 100644 index 0000000..1d05a75 --- /dev/null +++ b/src/crepe/api/BehaviorScript.h @@ -0,0 +1,16 @@ +#pragma once + +#include "../Component.h" +#include "../Script.h" + +namespace crepe::api { + +class BehaviorScript : public Script, public Component { + // only allow ComponentManager to instantiate scripts + friend class ComponentManager; + +protected: + BehaviorScript(); +}; + +} // namespace crepe::api diff --git a/src/crepe/api/CMakeLists.txt b/src/crepe/api/CMakeLists.txt index dcac0ae..cecc2f1 100644 --- a/src/crepe/api/CMakeLists.txt +++ b/src/crepe/api/CMakeLists.txt @@ -1,23 +1,9 @@ target_sources(crepe PUBLIC - #map_asset.cpp - spritesheet.cpp - resource_manager.cpp - Resource.cpp - game.cpp - Color.cpp + # AudioSource.cpp + BehaviorScript.cpp ) target_sources(crepe PUBLIC FILE_SET HEADERS FILES - Resource.h - baseResource.h - game.h - #map_asset.h - spritesheet.h - resource_manager.h - Component.h - AudioSource.h - Sprite.h - Color.h - Transform.h - Point.h + # AudioSource.h + BehaviorScript.h ) diff --git a/src/crepe/api/Component.h b/src/crepe/api/Component.h deleted file mode 100644 index d5e0499..0000000 --- a/src/crepe/api/Component.h +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -namespace crepe::api { - -class Component { -public: - bool active; -}; - -} // namespace crepe::api diff --git a/src/crepe/api/Resource.cpp b/src/crepe/api/Resource.cpp deleted file mode 100644 index f7f2516..0000000 --- a/src/crepe/api/Resource.cpp +++ /dev/null @@ -1,18 +0,0 @@ -#include <filesystem> -#include <iostream> -#include <iterator> - -#include "Resource.h" -#include "util/log.h" - -using namespace crepe::api; - -Resource::Resource(const std::string & src) { - dbg_trace(); - this->src = std::filesystem::path(src); - this->file = std::ifstream(this->src, std::ios::in | std::ios::binary); -} - -const std::istream & Resource::read() { return this->file; } - -const char * Resource::canonical() { return this->src.c_str(); } diff --git a/src/crepe/api/Resource.h b/src/crepe/api/Resource.h deleted file mode 100644 index f2b2a0e..0000000 --- a/src/crepe/api/Resource.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -#include <fstream> -#include <iostream> -#include <string> - -namespace crepe::api { - -class Resource { -public: - Resource(const std::string & src); - -public: - //! Get an input stream to the contents of this resource - const std::istream & read(); - //! Get the canonical path to this resource - const char * canonical(); - -private: - std::string src; - std::ifstream file; -}; - -} // namespace crepe::api diff --git a/src/crepe/facade/Sound.cpp b/src/crepe/facade/Sound.cpp index e1150ac..64fa281 100644 --- a/src/crepe/facade/Sound.cpp +++ b/src/crepe/facade/Sound.cpp @@ -5,17 +5,17 @@ using namespace crepe; -Sound::Sound(std::unique_ptr<api::Resource> res) { +Sound::Sound(std::unique_ptr<Asset> res) { dbg_trace(); this->load(std::move(res)); } Sound::Sound(const char * src) { dbg_trace(); - this->load(std::make_unique<api::Resource>(src)); + this->load(std::make_unique<Asset>(src)); } -void Sound::load(std::unique_ptr<api::Resource> res) { +void Sound::load(std::unique_ptr<Asset> res) { this->sample.load(res->canonical()); } diff --git a/src/crepe/facade/Sound.h b/src/crepe/facade/Sound.h index 2f1c924..b11f871 100644 --- a/src/crepe/facade/Sound.h +++ b/src/crepe/facade/Sound.h @@ -1,12 +1,11 @@ #pragma once -#include <soloud.h> -#include <soloud_wav.h> +#include <soloud/soloud.h> +#include <soloud/soloud_wav.h> #include <memory> -#include "api/Resource.h" -#include "api/baseResource.h" +#include "Asset.h" namespace crepe { @@ -67,10 +66,10 @@ public: public: Sound(const char * src); - Sound(std::unique_ptr<api::Resource> res); + Sound(std::unique_ptr<Asset> res); private: - void load(std::unique_ptr<api::Resource> res); + void load(std::unique_ptr<Asset> res); private: SoLoud::Wav sample; diff --git a/src/crepe/facade/SoundContext.h b/src/crepe/facade/SoundContext.h index 090966d..d3123d2 100644 --- a/src/crepe/facade/SoundContext.h +++ b/src/crepe/facade/SoundContext.h @@ -1,6 +1,6 @@ #pragma once -#include <soloud.h> +#include <soloud/soloud.h> #include "Sound.h" diff --git a/src/crepe/util/log.cpp b/src/crepe/util/log.cpp index 6829ec3..f91d52c 100644 --- a/src/crepe/util/log.cpp +++ b/src/crepe/util/log.cpp @@ -8,10 +8,10 @@ using namespace crepe::util; static const char * const LOG_PREFIX[] = { - [log_level::debug] = "[DBG] ", - [log_level::info] = "[INFO] ", - [log_level::warning] = "[WARN] ", - [log_level::error] = "[ERR] ", + [log_level::DEBUG] = "[DBG] ", + [log_level::INFO] = "[INFO] ", + [log_level::WARNING] = "[WARN] ", + [log_level::ERROR] = "[ERR] ", }; static void va_logf(enum log_level level, va_list args, const std::string fmt) { @@ -38,7 +38,7 @@ static void va_logf(enum log_level level, va_list args, const std::string fmt) { void crepe::util::logf(const char * fmt, ...) { va_list args; va_start(args, fmt); - va_logf(crepe::util::log_level::debug, args, fmt); + va_logf(crepe::util::log_level::DEBUG, args, fmt); va_end(args); } diff --git a/src/crepe/util/log.h b/src/crepe/util/log.h index 4cab338..2b0fbe1 100644 --- a/src/crepe/util/log.h +++ b/src/crepe/util/log.h @@ -7,24 +7,27 @@ // utility macros #define _crepe_logf_here(fmt, ...) \ - crepe::util::logf(util::log_level::debug, "%s%s (%s:%d)" fmt "\n", \ + crepe::util::logf(util::log_level::DEBUG, "%s%s (%s:%d)" fmt "\n", \ crepe::util::color::FG_WHITE, __PRETTY_FUNCTION__, \ __FILE_NAME__, __LINE__, crepe::util::color::RESET, \ __VA_ARGS__) +// very illegal global function-style macros +// NOLINTBEGIN #define dbg_logf(fmt, ...) _crepe_logf_here(": " fmt, __VA_ARGS__) #define dbg_log(str) _crepe_logf_here(": %s", str) #define dbg_trace() _crepe_logf_here("%s", "") +// NOLINTEND #endif namespace crepe::util { enum log_level { - debug, - info, - warning, - error, + DEBUG, + INFO, + WARNING, + ERROR, }; void logf(const char * fmt, ...); diff --git a/src/example/CMakeLists.txt b/src/example/CMakeLists.txt new file mode 100644 index 0000000..6df4ce7 --- /dev/null +++ b/src/example/CMakeLists.txt @@ -0,0 +1,18 @@ +# add_example(target_name [SOURCES...]) +function(add_example target_name) + # if SOURCES is not specified + if(NOT ARGV1) + # A .cpp file with target_name exists, and should be used + set(sources ${target_name}.cpp) + else() + set(sources ${ARGV}) + endif() + + add_executable(${target_name} EXCLUDE_FROM_ALL ${sources}) + target_link_libraries(${target_name} PUBLIC crepe) +endfunction() + +add_example(audio_internal) +add_example(components_internal) +add_example(script) + diff --git a/src/dummy_audio.cpp b/src/example/audio_internal.cpp index 211a503..1199e2d 100644 --- a/src/dummy_audio.cpp +++ b/src/example/audio_internal.cpp @@ -1,5 +1,10 @@ -#include "crepe/facade/Sound.h" -#include "crepe/util/log.h" +/** \file + * + * Standalone example for usage of the internal \c Sound class. + */ + +#include <crepe/Sound.h> +#include <crepe/util/log.h> #include <chrono> #include <thread> diff --git a/src/example/components_internal.cpp b/src/example/components_internal.cpp new file mode 100644 index 0000000..54ce295 --- /dev/null +++ b/src/example/components_internal.cpp @@ -0,0 +1,60 @@ +/** \file + * + * Standalone example for usage of the internal ECS + */ + +#include <cassert> +#include <chrono> + +#include <crepe/Collider.h> +#include <crepe/Component.h> +#include <crepe/ComponentManager.h> +#include <crepe/GameObject.h> +#include <crepe/Rigidbody.h> +#include <crepe/Sprite.h> +#include <crepe/util/log.h> + +using namespace crepe; +using namespace std; + +#define OBJ_COUNT 100000 + +int main() { + dbg_trace(); + + auto & mgr = ComponentManager::get_instance(); + + auto start_adding = chrono::high_resolution_clock::now(); + + GameObject * game_object[OBJ_COUNT]; + + for (int i = 0; i < OBJ_COUNT; ++i) { + game_object[i] = new GameObject(i, "Name", "Tag", 0); + + game_object[i]->add_component<Sprite>("test"); + game_object[i]->add_component<Rigidbody>(0, 0, i); + game_object[i]->add_component<Collider>(i); + } + + auto stop_adding = chrono::high_resolution_clock::now(); + + auto sprites = mgr.get_components_by_type<Sprite>(); + for (auto sprite : sprites) { + assert(sprite.get().path == "test"); + } + + auto stop_looping = chrono::high_resolution_clock::now(); + + for (int i = 0; i < OBJ_COUNT; ++i) { + delete game_object[i]; + } + + auto add_time = chrono::duration_cast<chrono::microseconds>(stop_adding + - start_adding); + auto loop_time = chrono::duration_cast<chrono::microseconds>(stop_looping + - stop_adding); + printf("add time: %ldus\n", add_time.count()); + printf("loop time: %ldus\n", loop_time.count()); + + return 0; +} diff --git a/src/example/script.cpp b/src/example/script.cpp new file mode 100644 index 0000000..a610b83 --- /dev/null +++ b/src/example/script.cpp @@ -0,0 +1,30 @@ +/** \file + * + * Standalone example for usage of the script component and system + */ + +#include <crepe/ComponentManager.h> +#include <crepe/GameObject.h> +#include <crepe/ScriptSystem.h> +#include <crepe/util/log.h> + +#include <crepe/api/BehaviorScript.h> + +using namespace crepe; +using namespace std; + +class MyScript : public api::BehaviorScript { + void update() { dbg_trace(); } +}; + +int main() { + dbg_trace(); + + auto obj = GameObject(0, "name", "tag", 0); + obj.add_component<MyScript>(); + + auto & sys = ScriptSystem::get_instance(); + sys.update(); // -> MyScript::update + + return 0; +} diff --git a/src/makefile b/src/makefile new file mode 100644 index 0000000..c1ef601 --- /dev/null +++ b/src/makefile @@ -0,0 +1,6 @@ +.PHONY: FORCE + +FMT += $(shell git ls-files '*.c' '*.cpp' '*.h' '*.hpp') +format: FORCE + clang-tidy -p build/compile_commands.json --fix-errors $(FMT) + diff --git a/src/readme.md b/src/readme.md new file mode 100644 index 0000000..a4a71e7 --- /dev/null +++ b/src/readme.md @@ -0,0 +1,33 @@ +# engine source + +This folder contains the crêpe engine source files, unit tests, and some toy +examples. The only target built by default by the CMakeLists.txt in this folder +is the crêpe shared library object. + +Examples (using Ninja): + +``` +$ cmake -B build -G Ninja +$ ninja -C build +``` + +Unit tests can be built by explicitly specifying the target `test_main` when +running the build command: + +``` +$ ninja -C build test_main +``` + +Each source file in the example/ folder corresponds to a CMake target as well: + +``` +$ ninja -C build audio_internal components_internal +``` + +For installing crêpe system-wide after building (install must be run with +elevated privileges): + +``` +# ninja -C build install +``` + diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt new file mode 100644 index 0000000..0d316d6 --- /dev/null +++ b/src/test/CMakeLists.txt @@ -0,0 +1,5 @@ +target_sources(test_main PUBLIC + dummy.cpp + # audio.cpp +) + diff --git a/src/test/audio.cpp b/src/test/audio.cpp new file mode 100644 index 0000000..d6ff689 --- /dev/null +++ b/src/test/audio.cpp @@ -0,0 +1,10 @@ +#include <gtest/gtest.h> + +using namespace std; +using namespace std::chrono_literals; + +// using namespace crepe; + +// TODO: mock internal audio class + +TEST(audio, play) { ASSERT_TRUE(true); } diff --git a/test/dummy.cpp b/src/test/dummy.cpp index a00a9c6..a00a9c6 100644 --- a/test/dummy.cpp +++ b/src/test/dummy.cpp diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt deleted file mode 100644 index f015570..0000000 --- a/test/CMakeLists.txt +++ /dev/null @@ -1,23 +0,0 @@ -cmake_minimum_required(VERSION 3.28) - -set(CMAKE_C_STANDARD 11) -set(CMAKE_CXX_STANDARD 20) -set(CMAKE_EXPORT_COMPILE_COMMANDS 1) - -set(CMAKE_BUILD_TYPE Debug) - -project(test C CXX) - -add_subdirectory(../lib/googletest googletest) -add_subdirectory(../src crepe) - -add_executable(test - dummy.cpp - # audio.cpp -) - -target_link_libraries(test - PRIVATE gtest_main - PUBLIC crepe # TODO: this does not work properly -) - diff --git a/test/audio.cpp b/test/audio.cpp deleted file mode 100644 index 47c5e84..0000000 --- a/test/audio.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include <gtest/gtest.h> -#include <memory> - -#include <crepe/api/AudioSource.h> -#include <crepe/api/Resource.h> - -#include <chrono> -#include <thread> - -using namespace std; -using namespace std::chrono_literals; - -using namespace crepe::api; - -// TODO: mock internal audio class - -TEST(audio, play) { - auto res = std::make_unique<Resource>("../mwe/audio/bgm.ogg"); - auto bgm = AudioSource(std::move(res)); - - bgm.play(); - - this_thread::sleep_for(2s); - - bgm.stop(); - - ASSERT_TRUE(true); -} |