diff options
46 files changed, 1075 insertions, 56 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/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..160d242 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,28 @@ +{ +    "version": "0.2.0", +    "configurations": [ +        { +            "name": "Debug with Ninja", +            "type": "cppdbg", +            "request": "launch", +            "program": "${workspaceFolder}/mwe/ecs-homemade/build/ecs-homemade.exe", +            "args": [], +            "stopAtEntry": true, +            "cwd": "${workspaceFolder}", +            "environment": [], +            "externalConsole": false, +            "MIMode": "gdb", +            "preLaunchTask": "build", +            "setupCommands": [ +                { +                    "description": "Enable pretty-printing for gdb", +                    "text": "-enable-pretty-printing", +                    "ignoreFailures": true +                } +            ], +            "logging": { +                "engineLogging": true +            } +        } +    ] +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 0fd5d23..19afa6f 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,28 +1,38 @@  { +    "version": "2.0.0",      "tasks": [          { -            "type": "cppbuild", -            "label": "C/C++: g++ build active file", -            "command": "/usr/bin/g++", +            "label": "configure", +            "type": "shell", +            "command": "cmake",              "args": [ -                "-fdiagnostics-color=always", -                "-g", -                "${file}", -                "-o", -                "${fileDirname}/${fileBasenameNoExtension}" +                "-G", +                "Ninja", +                "-B", +                "${workspaceFolder}/mwe/ecs-homemade/build",  // Create build folder here +                "${workspaceFolder}/mwe/ecs-homemade"  // Path to your source directory              ], -            "options": { -                "cwd": "${fileDirname}" +            "group": { +                "kind": "build", +                "isDefault": false              }, -            "problemMatcher": [ -                "$gcc" +            "problemMatcher": [] +        }, +        { +            "label": "build", +            "type": "shell", +            "command": "cmake", +            "args": [ +                "--build", +                "${workspaceFolder}/mwe/ecs-homemade/build"  // Build directory              ], +            "dependsOn": "configure",  // Ensure the configure task runs first              "group": {                  "kind": "build",                  "isDefault": true              }, -            "detail": "Task generated by Debugger." +            "problemMatcher": ["$gcc"], +            "detail": "Generated task for building the project."          } -    ], -    "version": "2.0.0" +    ]  } diff --git a/contributing.md b/contributing.md index 9f87ec5..364e835 100644 --- a/contributing.md +++ b/contributing.md @@ -17,6 +17,12 @@  # 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 @@ -51,6 +57,8 @@    ```  - 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/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..d368516 --- /dev/null +++ b/mwe/ecs-homemade/inc/ComponentManager.h @@ -0,0 +1,55 @@ +#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 + +	template <typename T, typename... Args> +	void AddComponent(std::uint32_t id, +					  Args &&... args); //Add a component of a specific type +	template <typename T> +	void DeleteComponentsById( +		std::uint32_t id); //Deletes all components of a specific type and id +	template <typename T> +	void DeleteComponents(); //Deletes all components of a specific type +	void DeleteAllComponentsOfId( +		std::uint32_t id); //Deletes all components of a specific id +	void DeleteAllComponents(); //Deletes all components + +	template <typename T> +	std::vector<std::reference_wrapper<T>> GetComponentsByID(std::uint32_t id) +		const; //Get a vector<> of all components at specific type and id +	template <typename T> +	std::vector<std::reference_wrapper<T>> GetComponentsByType() +		const; //Get a vector<> of all components of a specific type + +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..d6da8e8 --- /dev/null +++ b/mwe/ecs-homemade/inc/ComponentManager.hpp @@ -0,0 +1,127 @@ + +template <typename T, typename... Args> +void ComponentManager::AddComponent(std::uint32_t id, Args &&... args) { +	std::type_index type = typeid( +		T); //Determine the type of T (this is used as the key of the unordered_map<>) + +	if (mComponents.find(type) +		== mComponents +			   .end()) { //Check if this component type is already in the unordered_map<> +		mComponents[type] = std::vector<std::vector<std::unique_ptr< +			Component>>>(); //If not, create a new (empty) vector<> of vector<unique_ptr<Component>> +	} + +	if (id +		>= mComponents[type] +			   .size()) { //Resize the vector<> if the id is greater than the current size +		mComponents[type].resize( +			id +			+ 1); //Initialize new slots to nullptr (resize does automatically init to nullptr) +	} + +	mComponents[type][id].push_back(std::make_unique<T>(std::forward<Args>( +		args)...)); //Create a new component of type T using perfect forwarding and store its unique_ptr in the vector<> +} + +template <typename T> +void ComponentManager::DeleteComponentsById(std::uint32_t id) { +	std::type_index type = typeid( +		T); //Determine the type of T (this is used as the key of the unordered_map<>) + +	if (mComponents.find(type) +		!= mComponents.end()) { //Find the type (in the unordered_map<>) +		std::vector<std::vector<std::unique_ptr<Component>>> & componentArray +			= mComponents[type]; //Get the correct vector<> + +		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 whole vector<> of this specific type and id +		} +	} +} + +template <typename T> +void ComponentManager::DeleteComponents() { +	std::type_index type = typeid( +		T); //Determine the type of T (this is used as the key of the unordered_map<>) + +	if (mComponents.find(type) +		!= mComponents.end()) { //Find the type (in the unordered_map<>) +		mComponents[type] +			.clear(); //Clear the whole vector<> of this specific type +	} +} + +template <typename T> +std::vector<std::reference_wrapper<T>> +ComponentManager::GetComponentsByID(std::uint32_t id) const { +	std::type_index type = typeid( +		T); //Determine the type of T (this is used as the key of the unordered_map<>) + +	std::vector<std::reference_wrapper<T>> +		componentVector; //Create an empty vector<> + +	if (mComponents.find(type) +		!= mComponents.end()) { //Find the type (in the unordered_map<>) + +		const std::vector<std::vector<std::unique_ptr<Component>>> & +			componentArray +			= mComponents.at(type); //Get the correct vector<> + +		if (id +			< componentArray +				  .size()) { //Make sure that the id (that we are looking for) is within the boundaries of the vector<> +			for (const std::unique_ptr<Component> & componentPtr : +				 componentArray[id]) { //Loop trough the whole vector<> +				T * castedComponent = static_cast<T *>( +					componentPtr.get()); //Cast the unique_ptr to a raw pointer + +				if (castedComponent) { //Ensure that the cast was successful +					componentVector.push_back( +						*castedComponent); //Add the dereferenced raw pointer to the vector<> +				} +			} +		} +	} + +	return componentVector; //Return the vector<> +} + +template <typename T> +std::vector<std::reference_wrapper<T>> +ComponentManager::GetComponentsByType() const { +	std::type_index type = typeid( +		T); //Determine the type of T (this is used as the key of the unordered_map<>) + +	std::vector<std::reference_wrapper<T>> +		componentVector; //Create an empty vector<> +	//std::uint32_t id = 0;	//Set the id to 0 (the id will also be stored in the returned vector<>) + +	if (mComponents.find(type) +		!= mComponents.end()) { //Find the type (in the unordered_map<>) + +		const std::vector<std::vector<std::unique_ptr<Component>>> & +			componentArray +			= mComponents.at(type); //Get the correct vector<> + +		for (const std::vector<std::unique_ptr<Component>> & component : +			 componentArray) { //Loop through the whole vector<> +			for (const std::unique_ptr<Component> & componentPtr : +				 component) { //Loop trough the whole vector<> +				T * castedComponent = static_cast<T *>( +					componentPtr.get()); //Cast the unique_ptr to a raw pointer + +				if (castedComponent) { //Ensure that the cast was successful +					componentVector.emplace_back(std::ref( +						*castedComponent)); //Pair the dereferenced raw pointer and the id and add it to the vector<> +				} +			} + +			//++id;	//Increase the id (the id will also be stored in the returned vector<>) +		} +	} + +	return componentVector; //Return the vector<> +} diff --git a/mwe/ecs-homemade/inc/Components.h b/mwe/ecs-homemade/inc/Components.h new file mode 100644 index 0000000..98c5fe7 --- /dev/null +++ b/mwe/ecs-homemade/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-homemade/inc/GameObjectMax.h b/mwe/ecs-homemade/inc/GameObjectMax.h new file mode 100644 index 0000000..3029053 --- /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> +	void 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..91d51ea --- /dev/null +++ b/mwe/ecs-homemade/inc/GameObjectMax.hpp @@ -0,0 +1,7 @@ +#include "ComponentManager.h" + +template <typename T, typename... Args> +void GameObject::AddComponent(Args &&... args) { +	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..c8347b3 --- /dev/null +++ b/mwe/ecs-homemade/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-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..330e154 --- /dev/null +++ b/mwe/ecs-homemade/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]->AddComponent<Sprite>("C:/Test"); +		gameObject[i]->AddComponent<Rigidbody>(0, 0, i); +		gameObject[i]->AddComponent<Colider>(i); +	} + +	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; +	} + +	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/src/CMakeLists.txt b/src/CMakeLists.txt index e63c1f9..9c508a8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -3,9 +3,7 @@ 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) -add_compile_definitions(DEBUG)  add_subdirectory(../lib/soloud soloud)  add_subdirectory(../lib/googletest googletest) diff --git a/src/crepe/CMakeLists.txt b/src/crepe/CMakeLists.txt index c7f18cd..a02e991 100644 --- a/src/crepe/CMakeLists.txt +++ b/src/crepe/CMakeLists.txt @@ -8,6 +8,14 @@ target_sources(crepe PUBLIC  	ParticleEmitter.cpp  	ParticleSystem.cpp  	SDLApp.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 @@ -18,6 +26,16 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES  	ParticleSystem.hpp  	Particle.hpp  	SDLApp.hpp +	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..084cd33 --- /dev/null +++ b/src/crepe/ComponentManager.hpp @@ -0,0 +1,152 @@ +#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..42e3666 --- /dev/null +++ b/src/crepe/Script.cpp @@ -0,0 +1,7 @@ +#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..ba4073a --- /dev/null +++ b/src/crepe/Script.h @@ -0,0 +1,16 @@ +#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. +}; + +} + diff --git a/src/crepe/ScriptSystem.cpp b/src/crepe/ScriptSystem.cpp new file mode 100644 index 0000000..e301c71 --- /dev/null +++ b/src/crepe/ScriptSystem.cpp @@ -0,0 +1,22 @@ +#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..e1ed290 --- /dev/null +++ b/src/crepe/ScriptSystem.h @@ -0,0 +1,18 @@ +#pragma once + +#include "System.h" + +namespace crepe { + +class ScriptSystem : public System { +public: +	static ScriptSystem & get_instance(); +	virtual void update(); + +private: +	ScriptSystem(); +	~ScriptSystem(); +}; + +} + 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..3fe3d66 --- /dev/null +++ b/src/crepe/System.h @@ -0,0 +1,23 @@ +#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; +}; + +} + diff --git a/src/crepe/api/BehaviorScript.cpp b/src/crepe/api/BehaviorScript.cpp new file mode 100644 index 0000000..2dd933e --- /dev/null +++ b/src/crepe/api/BehaviorScript.cpp @@ -0,0 +1,10 @@ +#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..e9542c1 --- /dev/null +++ b/src/crepe/api/BehaviorScript.h @@ -0,0 +1,17 @@ +#pragma once + +#include "../Script.h" +#include "../Component.h" + +namespace crepe::api { + +class BehaviorScript : public Script, public Component { +	// only allow ComponentManager to instantiate scripts +	friend class ComponentManager; + +protected: +	BehaviorScript(); +}; + +} + diff --git a/src/crepe/api/CMakeLists.txt b/src/crepe/api/CMakeLists.txt index 9548594..86623de 100644 --- a/src/crepe/api/CMakeLists.txt +++ b/src/crepe/api/CMakeLists.txt @@ -1,9 +1,10 @@  target_sources(crepe PUBLIC  	# AudioSource.cpp +	BehaviorScript.cpp  )  target_sources(crepe PUBLIC FILE_SET HEADERS FILES -	AudioSource.h -	Component.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/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 bfe7291..2b0fbe1 100644 --- a/src/crepe/util/log.h +++ b/src/crepe/util/log.h @@ -7,7 +7,7 @@  // 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__) @@ -24,10 +24,10 @@  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 index 17e995b..f66e08a 100644 --- a/src/example/CMakeLists.txt +++ b/src/example/CMakeLists.txt @@ -1,5 +1,20 @@ -add_executable(audio_internal EXCLUDE_FROM_ALL audio_internal.cpp) -target_link_libraries(audio_internal PUBLIC crepe) +# 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)  add_executable(particel EXCLUDE_FROM_ALL particel.cpp)  target_link_libraries(particel PUBLIC crepe) 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..28605c7 --- /dev/null +++ b/src/example/script.cpp @@ -0,0 +1,33 @@ +/** \file + *  + * Standalone example for usage of the script component and system + */ + +#include <crepe/util/log.h> +#include <crepe/ScriptSystem.h> +#include <crepe/ComponentManager.h> +#include <crepe/GameObject.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; +} + |