diff --git a/sourceCode/ECS.hpp b/sourceCode/ECS.hpp new file mode 100644 index 0000000..7ea2f02 --- /dev/null +++ b/sourceCode/ECS.hpp @@ -0,0 +1,147 @@ +/* + * SPDX-FileCopyrightText: Dora "cat" + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at http://mozilla.org/MPL/2.0/. + */ +#ifndef GUARD_TOURMALINE_ECS_H +#define GUARD_TOURMALINE_ECS_H +#include +#include +#include +#include +#include +#include + +#include "Systems/Logging.hpp" +#include "Types.hpp" + +namespace Tourmaline::ECS { +using Entity = Tourmaline::Type::UUID; +class World; + +struct BaseComponent { +public: + virtual ~BaseComponent() = default; + const Entity &GetOwner(); + +private: + const Entity *owner; + friend World; +}; + +// Concepts +template +concept Component = std::derived_from; + +class World { +public: + // Entity + Entity CreateEntity(); + bool EntityExists(const Entity &entity) noexcept; + [[nodiscard( + "It is not guranteed that an entity can always be destroyed, please make " + "sure by checking the returned bool")]] + bool DestroyEntity(Entity entity); + + // Components + template + T &AddComponent(const Entity &entity, Args &&...constructionArguments) { + // Insert to entity list + auto entityIter = GetEntityIterator( + entity, + std::format( + "Cannot add component \"{}\"! Entity \"{}\" does not exist!", + typeid(T).name(), entity.asString()), + "AddComponent", Systems::Logging::LogLevel::Error); + + auto [componentIter, success] = entityIter->second.try_emplace( + typeid(T), T(std::forward(constructionArguments)...)); + Systems::Logging::Log( + std::format("Cannot add component! Component \"{}\" already exists " + "in entity \"{}\" ", + typeid(T).name(), entity.asString()), + "AddComponent", Systems::Logging::LogLevel::Error, !success); + + T &component = std::any_cast(componentIter->second); + component.owner = &entity; + return component; + } + + template + [[nodiscard("Discarding an expensive operation's result!")]] + T &GetComponent(const Entity &entity) { + auto iter = GetEntityIterator( + entity, + std::format("Can't get entity \"{}\"'s component \"{}\", since " + "entity does not exist!", + entity.asString(), typeid(T).name()), + "GetComponent", Systems::Logging::LogLevel::Error); + + auto component = iter->second.find(typeid(T)); + Systems::Logging::Log( + std::format( + "Entity \"{}\" does not have component \"{}\", cannot get it!", + entity.asString(), typeid(T).name()), + "GetComponent", Systems::Logging::LogLevel::Error, + component == iter->second.end()); + + return std::any_cast(component->second); + } + + template + [[nodiscard("Discarding an expensive operation's result!")]] + bool HasComponent(const Entity &entity) { + auto iter = GetEntityIterator( + entity, + std::format("Can't find if entity \"{}\" has component \"{}\", since " + "entity does not exist!", + entity.asString(), typeid(T).name())); + + return iter != entityComponentList.end() && + (iter->second.find(typeid(T)) != iter->second.end()); + } + + template + [[nodiscard( + "It is not guranteed that a component can always be removed, please make " + "sure by checking the returned bool")]] + bool RemoveComponent(const Entity &entity) { + auto entityIter = GetEntityIterator( + entity, + std::format("Cannot remove component {} from entity {}, since entity " + "does not exist!", + typeid(T).name(), entity.asString()), + "RemoveComponent", Systems::Logging::LogLevel::Warning); + if (entityIter == entityComponentList.end()) { + return false; + } + + auto componentIter = entityIter->second.find(typeid(T)); + if (componentIter == entityIter->second.end()) { + Systems::Logging::Log( + std::format("Cannot remove component {} from entity {}, since entity " + "does not have that component", + typeid(T).name(), entity.asString()), + "RemoveComponent", Systems::Logging::LogLevel::Warning); + return false; + } + + entityIter->second.erase(componentIter); + return true; + } + +private: + std::unordered_map> + entityComponentList{}; + + decltype(entityComponentList)::iterator + GetEntityIterator(const Entity &entity, const std::string &errorMessage = "", + const std::string &position = "", + Tourmaline::Systems::Logging::LogLevel severity = + Systems::Logging::LogLevel::Warning); +}; +} // namespace Tourmaline::ECS +#endif diff --git a/sourceCode/ECS/Component.cpp b/sourceCode/ECS/Component.cpp new file mode 100644 index 0000000..6c8ca52 --- /dev/null +++ b/sourceCode/ECS/Component.cpp @@ -0,0 +1,14 @@ +/* + * SPDX-FileCopyrightText: Dora "cat" + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include "../ECS.hpp" + +using namespace Tourmaline::ECS; + +const Entity &BaseComponent::GetOwner() { return *this->owner; } diff --git a/sourceCode/ECS/World.cpp b/sourceCode/ECS/World.cpp new file mode 100644 index 0000000..02a6e21 --- /dev/null +++ b/sourceCode/ECS/World.cpp @@ -0,0 +1,55 @@ +/* + * SPDX-FileCopyrightText: Dora "cat" + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include "../ECS.hpp" +#include "../Systems/Random.hpp" + +using namespace Tourmaline::ECS; + +Entity World::CreateEntity() { + auto [iterator, success] = + entityComponentList.try_emplace(Systems::Random::GenerateUUID()); + + Systems::Logging::Log("Failed to create an entity! Possibly by incredible " + "luck generated already existing UUID?", + "CreateEntity", Systems::Logging::LogLevel::Critical, + !success); + return iterator->first; +} + +bool World::EntityExists(const Entity &entity) noexcept { + return entityComponentList.find(entity) != entityComponentList.end(); +} + +bool World::DestroyEntity(Entity entity) { + auto entityIter = GetEntityIterator( + entity, + std::format("Cannot delete entity \"{}\", it does not exist!", + entity.asString()), + "DestroyEntity"); + + if (entityIter == entityComponentList.end()) { + return false; + } + + entityComponentList.erase(entityIter); + return true; +} + +// Very repetitive code +decltype(World::entityComponentList)::iterator +World::GetEntityIterator(const Entity &entity, const std::string &errorMessage, + const std::string &position, + Tourmaline::Systems::Logging::LogLevel severity) { + auto iter = entityComponentList.find(entity); + + Systems::Logging::Log(errorMessage, "GetEntityIterator/" + position, severity, + iter == entityComponentList.end()); + return iter; +}