diff options
Diffstat (limited to 'src/crepe/manager')
-rw-r--r-- | src/crepe/manager/CMakeLists.txt | 23 | ||||
-rw-r--r-- | src/crepe/manager/ComponentManager.cpp | 63 | ||||
-rw-r--r-- | src/crepe/manager/ComponentManager.h | 161 | ||||
-rw-r--r-- | src/crepe/manager/ComponentManager.hpp | 161 | ||||
-rw-r--r-- | src/crepe/manager/EventManager.cpp | 46 | ||||
-rw-r--r-- | src/crepe/manager/EventManager.h | 161 | ||||
-rw-r--r-- | src/crepe/manager/EventManager.hpp | 36 | ||||
-rw-r--r-- | src/crepe/manager/Manager.cpp | 6 | ||||
-rw-r--r-- | src/crepe/manager/Manager.h | 17 | ||||
-rw-r--r-- | src/crepe/manager/Mediator.h | 35 | ||||
-rw-r--r-- | src/crepe/manager/ResourceManager.cpp | 34 | ||||
-rw-r--r-- | src/crepe/manager/ResourceManager.h | 48 | ||||
-rw-r--r-- | src/crepe/manager/ResourceManager.hpp | 26 | ||||
-rw-r--r-- | src/crepe/manager/SaveManager.cpp | 173 | ||||
-rw-r--r-- | src/crepe/manager/SaveManager.h | 114 | ||||
-rw-r--r-- | src/crepe/manager/SceneManager.cpp | 35 | ||||
-rw-r--r-- | src/crepe/manager/SceneManager.h | 52 | ||||
-rw-r--r-- | src/crepe/manager/SceneManager.hpp | 25 |
18 files changed, 1216 insertions, 0 deletions
diff --git a/src/crepe/manager/CMakeLists.txt b/src/crepe/manager/CMakeLists.txt new file mode 100644 index 0000000..480c8ee --- /dev/null +++ b/src/crepe/manager/CMakeLists.txt @@ -0,0 +1,23 @@ +target_sources(crepe PUBLIC + ComponentManager.cpp + EventManager.cpp + Manager.cpp + SaveManager.cpp + SceneManager.cpp + ResourceManager.cpp +) + +target_sources(crepe PUBLIC FILE_SET HEADERS FILES + ComponentManager.h + ComponentManager.hpp + EventManager.h + EventManager.hpp + Manager.h + Mediator.h + SaveManager.h + SceneManager.h + SceneManager.hpp + ResourceManager.h + ResourceManager.hpp +) + diff --git a/src/crepe/manager/ComponentManager.cpp b/src/crepe/manager/ComponentManager.cpp new file mode 100644 index 0000000..5a96158 --- /dev/null +++ b/src/crepe/manager/ComponentManager.cpp @@ -0,0 +1,63 @@ +#include "../api/GameObject.h" +#include "../util/Log.h" +#include "../types.h" + +#include "ComponentManager.h" + +using namespace crepe; +using namespace std; + +ComponentManager::ComponentManager(Mediator & mediator) : Manager(mediator) { + mediator.component_manager = *this; + dbg_trace(); +} +ComponentManager::~ComponentManager() { dbg_trace(); } + +void ComponentManager::delete_all_components_of_id(game_object_id_t id) { + // Do not delete persistent objects + if (this->persistent[id]) { + return; + } + + // Loop through all the types (in the unordered_map<>) + for (auto & [type, component_array] : this->components) { + // Make sure that the id (that we are looking for) is within the boundaries of the vector<> + if (id < component_array.size()) { + // Clear the components at this specific id + component_array[id].clear(); + } + } +} + +void ComponentManager::delete_all_components() { + // Loop through all the types (in the unordered_map<>) + for (auto & [type, component_array] : this->components) { + // Loop through all the ids (in the vector<>) + for (game_object_id_t id = 0; id < component_array.size(); id++) { + // Do not delete persistent objects + if (!this->persistent[id]) { + // Clear the components at this specific id + component_array[id].clear(); + } + } + } + + this->next_id = 0; +} + +GameObject ComponentManager::new_object(const string & name, const string & tag, + const vec2 & position, double rotation, double scale) { + // Find the first available id (taking persistent objects into account) + while (this->persistent[this->next_id]) { + this->next_id++; + } + + GameObject object{*this, this->next_id, name, tag, position, rotation, scale}; + this->next_id++; + + return object; +} + +void ComponentManager::set_persistent(game_object_id_t id, bool persistent) { + this->persistent[id] = persistent; +} diff --git a/src/crepe/manager/ComponentManager.h b/src/crepe/manager/ComponentManager.h new file mode 100644 index 0000000..ad37586 --- /dev/null +++ b/src/crepe/manager/ComponentManager.h @@ -0,0 +1,161 @@ +#pragma once + +#include <memory> +#include <typeindex> +#include <unordered_map> +#include <vector> + +#include "../Component.h" +#include "../types.h" + +#include "Manager.h" + +namespace crepe { + +class GameObject; + +/** + * \brief Manages all components + * + * This class manages all components. It provides methods to add, delete and get components. + */ +class ComponentManager : public Manager { + // TODO: This relation should be removed! I (loek) believe that the scene manager should + // create/destroy components because the GameObject's are stored in concrete Scene classes, + // which will in turn call GameObject's destructor, which will in turn call + // ComponentManager::delete_components_by_id or something. This is a pretty major change, so + // here is a comment and temporary fix instead :tada: + friend class SceneManager; + +public: + ComponentManager(Mediator & mediator); + ~ComponentManager(); // dbg_trace + + /** + * \brief Create a new game object using the component manager + * + * \param name Metadata::name (required) + * \param tag Metadata::tag (optional, empty by default) + * \param position Transform::position (optional, origin by default) + * \param rotation Transform::rotation (optional, 0 by default) + * \param scale Transform::scale (optional, 1 by default) + * + * \returns GameObject interface + * + * \note This method automatically assigns a new entity ID + */ + GameObject new_object(const std::string & name, const std::string & tag = "", + const vec2 & position = {0, 0}, double rotation = 0, + double scale = 1); + +protected: + /** + * GameObject is used as an interface to add/remove components, and the game programmer is + * supposed to use it instead of interfacing with the component manager directly. + */ + friend class GameObject; + /** + * \brief Add a component to the ComponentManager + * + * This method adds a component to the ComponentManager. The component is created with the + * given arguments and added to the ComponentManager. + * + * \tparam T The type of the component + * \tparam Args The types of the arguments + * \param id The id of the GameObject this component belongs to + * \param args The arguments to create the component + * \return The created component + */ + template <typename T, typename... Args> + T & add_component(game_object_id_t id, Args &&... args); + /** + * \brief Delete all components of a specific type and id + * + * This method deletes all components of a specific type and id. + * + * \tparam T The type of the component + * \param id The id of the GameObject this component belongs to + */ + template <typename T> + void delete_components_by_id(game_object_id_t id); + /** + * \brief Delete all components of a specific type + * + * This method deletes all components of a specific type. + * + * \tparam T The type of the component + */ + template <typename T> + void delete_components(); + /** + * \brief Delete all components of a specific id + * + * This method deletes all components of a specific id. + * + * \param id The id of the GameObject this component belongs to + */ + void delete_all_components_of_id(game_object_id_t id); + /** + * \brief Delete all components + * + * This method deletes all components. + */ + void delete_all_components(); + /** + * \brief Set a GameObject as persistent + * + * This method sets a GameObject as persistent. If a GameObject is persistent, its + * components will not be deleted. + * + * \param id The id of the GameObject to set as persistent + * \param persistent The persistent flag + */ + void set_persistent(game_object_id_t id, bool persistent); + +public: + /** + * \brief Get all components of a specific type and id + * + * This method gets all components of a specific type and id. + * + * \tparam T The type of the component + * \param id The id of the GameObject this component belongs to + * \return A vector of all components of the specific type and id + */ + template <typename T> + RefVector<T> get_components_by_id(game_object_id_t id) const; + /** + * \brief Get all components of a specific type + * + * This method gets all components of a specific type. + * + * \tparam T The type of the component + * \return A vector of all components of the specific type + */ + template <typename T> + RefVector<T> get_components_by_type() const; + +private: + /** + * \brief The components + * + * This unordered_map stores all components. The key is the type of the component and the + * value is a vector of vectors of unique pointers to the components. + * + * Every component type has its own vector of vectors of unique pointers to the components. + * The first vector is for the ids of the GameObjects and the second vector is for the + * components (because a GameObject might have multiple components). + */ + std::unordered_map<std::type_index, std::vector<std::vector<std::unique_ptr<Component>>>> + components; + + //! Persistent flag for each GameObject + std::unordered_map<game_object_id_t, bool> persistent; + + //! ID of next GameObject allocated by \c ComponentManager::new_object + game_object_id_t next_id = 0; +}; + +} // namespace crepe + +#include "ComponentManager.hpp" diff --git a/src/crepe/manager/ComponentManager.hpp b/src/crepe/manager/ComponentManager.hpp new file mode 100644 index 0000000..ffb38ec --- /dev/null +++ b/src/crepe/manager/ComponentManager.hpp @@ -0,0 +1,161 @@ +#pragma once + +#include <type_traits> + +#include "ComponentManager.h" +#include "types.h" + +namespace crepe { + +template <class T, typename... Args> +T & ComponentManager::add_component(game_object_id_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 (this->components.find(type) == this->components.end()) { + //If not, create a new (empty) vector<> of vector<unique_ptr<Component>> + this->components[type] = vector<vector<unique_ptr<Component>>>(); + } + + // Resize the vector<> if the id is greater than the current size + if (id >= this->components[type].size()) { + // Initialize new slots to nullptr (resize does automatically init to nullptr) + this->components[type].resize(id + 1); + } + + // Create a new component of type T (arguments directly forwarded). The + // constructor must be called by ComponentManager. + T * instance_ptr = new T(id, forward<Args>(args)...); + if (instance_ptr == nullptr) throw std::bad_alloc(); + + T & instance_ref = *instance_ptr; + unique_ptr<T> instance = unique_ptr<T>(instance_ptr); + + // Check if the vector size is not greater than get_instances_max + int max_instances = instance->get_instances_max(); + if (max_instances != -1 && components[type][id].size() >= max_instances) { + throw std::runtime_error( + "Exceeded maximum number of instances for this component type"); + } + + // store its unique_ptr in the vector<> + this->components[type][id].push_back(std::move(instance)); + + return instance_ref; +} + +template <typename T> +void ComponentManager::delete_components_by_id(game_object_id_t id) { + using namespace std; + + // Do not delete persistent objects + if (this->persistent[id]) { + return; + } + + // 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 (this->components.find(type) != this->components.end()) { + // Get the correct vector<> + vector<vector<unique_ptr<Component>>> & component_array = this->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); + + if (this->components.find(type) == this->components.end()) return; + + // Loop through the whole vector<> of this specific type + for (game_object_id_t i = 0; i < this->components[type].size(); ++i) { + // Do not delete persistent objects + if (!this->persistent[i]) { + this->components[type][i].clear(); + } + } +} + +template <typename T> +RefVector<T> ComponentManager::get_components_by_id(game_object_id_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<> + RefVector<T> component_vector; + + if (this->components.find(type) == this->components.end()) return component_vector; + + // Get the correct vector<> + const vector<vector<unique_ptr<Component>>> & component_array = this->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()) return component_vector; + + // 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()); + + if (casted_component == nullptr) continue; + + // Add the dereferenced raw pointer to the vector<> + component_vector.push_back(*casted_component); + } + + return component_vector; +} + +template <typename T> +RefVector<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<> + RefVector<T> component_vector; + + // Find the type (in the unordered_map<>) + if (this->components.find(type) == this->components.end()) return component_vector; + + // Get the correct vector<> + const vector<vector<unique_ptr<Component>>> & component_array = this->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 == nullptr) continue; + + // Add the dereferenced raw pointer to the vector<> + component_vector.emplace_back(ref(*casted_component)); + } + } + + // Return the vector<> + return component_vector; +} + +} // namespace crepe diff --git a/src/crepe/manager/EventManager.cpp b/src/crepe/manager/EventManager.cpp new file mode 100644 index 0000000..20f0dd3 --- /dev/null +++ b/src/crepe/manager/EventManager.cpp @@ -0,0 +1,46 @@ +#include "EventManager.h" + +using namespace crepe; +using namespace std; + +EventManager & EventManager::get_instance() { + static EventManager instance; + return instance; +} + +void EventManager::dispatch_events() { + for (auto & event : this->events_queue) { + this->handle_event(event.type, event.channel, *event.event.get()); + } + this->events_queue.clear(); +} + +void EventManager::handle_event(type_index type, event_channel_t channel, const Event & data) { + auto handlers_it = this->subscribers.find(type); + if (handlers_it == this->subscribers.end()) return; + + vector<CallbackEntry> & handlers = handlers_it->second; + for (auto & handler : handlers) { + bool check_channel = handler.channel != CHANNEL_ALL || channel != CHANNEL_ALL; + if (check_channel && handler.channel != channel) continue; + + bool handled = handler.callback->exec(data); + if (handled) return; + } +} + +void EventManager::clear() { + this->subscribers.clear(); + this->events_queue.clear(); +} + +void EventManager::unsubscribe(subscription_t id) { + for (auto & [event_type, handlers] : this->subscribers) { + for (auto it = handlers.begin(); it != handlers.end(); it++) { + // find listener with subscription id + if ((*it).id != id) continue; + it = handlers.erase(it); + return; + } + } +} diff --git a/src/crepe/manager/EventManager.h b/src/crepe/manager/EventManager.h new file mode 100644 index 0000000..d634f54 --- /dev/null +++ b/src/crepe/manager/EventManager.h @@ -0,0 +1,161 @@ +#pragma once + +#include <memory> +#include <typeindex> +#include <unordered_map> +#include <vector> + +#include "../api/Event.h" +#include "../api/EventHandler.h" + +namespace crepe { + +//! Event listener unique ID +typedef size_t subscription_t; + +/** + * \brief Event channel + * + * Events can be sent to a specific channel, which prevents listeners on other channels from + * being called. The default channel is EventManager::CHANNEL_ALL, which calls all listeners. + */ +typedef size_t event_channel_t; + +/** + * \class EventManager + * \brief Manages event subscriptions, triggers, and queues, enabling decoupled event handling. + * + * The `EventManager` acts as a centralized event system. It allows for registering callbacks + * for specific event types, triggering events synchronously, queueing events for later + * processing, and managing subscriptions via unique identifiers. + */ +class EventManager { +public: + static constexpr const event_channel_t CHANNEL_ALL = -1; + + /** + * \brief Get the singleton instance of the EventManager. + * + * This method returns the unique instance of the EventManager, creating it if it + * doesn't already exist. Ensures only one instance is active in the program. + * + * \return Reference to the singleton instance of the EventManager. + */ + static EventManager & get_instance(); + + /** + * \brief Subscribe to a specific event type. + * + * Registers a callback for a given event type and optional channel. Each callback + * is assigned a unique subscription ID that can be used for later unsubscription. + * + * \tparam EventType The type of the event to subscribe to. + * \param callback The callback function to be invoked when the event is triggered. + * \param channel The channel number to subscribe to (default is CHANNEL_ALL, which listens to all channels). + * \return A unique subscription ID associated with the registered callback. + */ + template <typename EventType> + subscription_t subscribe(const EventHandler<EventType> & callback, + event_channel_t channel = CHANNEL_ALL); + + /** + * \brief Unsubscribe a previously registered callback. + * + * Removes a callback from the subscription list based on its unique subscription ID. + * + * \param event_id The unique subscription ID of the callback to remove. + */ + void unsubscribe(subscription_t event_id); + + /** + * \brief Trigger an event immediately. + * + * Synchronously invokes all registered callbacks for the given event type on the specified channel. + * + * \tparam EventType The type of the event to trigger. + * \param event The event instance to pass to the callbacks. + * \param channel The channel to trigger the event on (default is CHANNEL_ALL, which triggers on all channels). + */ + template <typename EventType> + void trigger_event(const EventType & event = {}, event_channel_t channel = CHANNEL_ALL); + + /** + * \brief Queue an event for later processing. + * + * Adds an event to the event queue to be processed during the next call to `dispatch_events`. + * + * \tparam EventType The type of the event to queue. + * \param event The event instance to queue. + * \param channel The channel to associate with the event (default is CHANNEL_ALL). + */ + template <typename EventType> + void queue_event(const EventType & event = {}, event_channel_t channel = CHANNEL_ALL); + + /** + * \brief Process all queued events. + * + * Iterates through the event queue and triggers callbacks for each queued event. + * Events are removed from the queue once processed. + */ + void dispatch_events(); + + /** + * \brief Clear all subscriptions. + * + * Removes all registered event handlers and clears the subscription list. + */ + void clear(); + +private: + /** + * \brief Default constructor for the EventManager. + * + * Constructor is private to enforce the singleton pattern. + */ + EventManager() = default; + + /** + * \struct QueueEntry + * \brief Represents an entry in the event queue. + */ + struct QueueEntry { + std::unique_ptr<Event> event; ///< The event instance. + event_channel_t channel = CHANNEL_ALL; ///< The channel associated with the event. + std::type_index type; ///< The type of the event. + }; + + /** + * \brief Internal event handler + * + * This function processes a single event, and is used to process events both during + * EventManager::dispatch_events and inside EventManager::trigger_event + * + * \param type \c typeid of concrete Event class + * \param channel Event channel + * \param data Event data + */ + void handle_event(std::type_index type, event_channel_t channel, const Event & data); + + /** + * \struct CallbackEntry + * \brief Represents a registered event handler callback. + */ + struct CallbackEntry { + std::unique_ptr<IEventHandlerWrapper> callback; ///< The callback function wrapper. + event_channel_t channel = CHANNEL_ALL; ///< The channel this callback listens to. + subscription_t id = -1; ///< Unique subscription ID. + }; + + //! The queue of events to be processed during dispatch. + std::vector<QueueEntry> events_queue; + + //! A map of event type to registered callbacks. + std::unordered_map<std::type_index, std::vector<CallbackEntry>> subscribers; + + //! Counter to generate unique subscription IDs. + subscription_t subscription_counter = 0; +}; + +} // namespace crepe + +#include "EventManager.hpp" diff --git a/src/crepe/manager/EventManager.hpp b/src/crepe/manager/EventManager.hpp new file mode 100644 index 0000000..a5f4556 --- /dev/null +++ b/src/crepe/manager/EventManager.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include "EventManager.h" + +namespace crepe { + +template <typename EventType> +subscription_t EventManager::subscribe(const EventHandler<EventType> & callback, + event_channel_t channel) { + subscription_counter++; + std::type_index event_type = typeid(EventType); + std::unique_ptr<EventHandlerWrapper<EventType>> handler + = std::make_unique<EventHandlerWrapper<EventType>>(callback); + std::vector<CallbackEntry> & handlers = this->subscribers[event_type]; + handlers.emplace_back(CallbackEntry{ + .callback = std::move(handler), .channel = channel, .id = subscription_counter}); + return subscription_counter; +} + +template <typename EventType> +void EventManager::queue_event(const EventType & event, event_channel_t channel) { + static_assert(std::is_base_of<Event, EventType>::value, + "EventType must derive from Event"); + this->events_queue.push_back(QueueEntry{ + .event = std::make_unique<EventType>(event), + .channel = channel, + .type = typeid(EventType), + }); +} + +template <typename EventType> +void EventManager::trigger_event(const EventType & event, event_channel_t channel) { + this->handle_event(typeid(EventType), channel, event); +} + +} // namespace crepe diff --git a/src/crepe/manager/Manager.cpp b/src/crepe/manager/Manager.cpp new file mode 100644 index 0000000..fe7c936 --- /dev/null +++ b/src/crepe/manager/Manager.cpp @@ -0,0 +1,6 @@ +#include "Manager.h" + +using namespace crepe; + +Manager::Manager(Mediator & mediator) : mediator(mediator) { } + diff --git a/src/crepe/manager/Manager.h b/src/crepe/manager/Manager.h new file mode 100644 index 0000000..9adfd0b --- /dev/null +++ b/src/crepe/manager/Manager.h @@ -0,0 +1,17 @@ +#pragma once + +#include "Mediator.h" + +namespace crepe { + +class Manager { +public: + Manager(Mediator & mediator); + virtual ~Manager() = default; + +protected: + Mediator & mediator; +}; + +} + diff --git a/src/crepe/manager/Mediator.h b/src/crepe/manager/Mediator.h new file mode 100644 index 0000000..475aed9 --- /dev/null +++ b/src/crepe/manager/Mediator.h @@ -0,0 +1,35 @@ +#pragma once + +#include "../util/OptionalRef.h" + +// TODO: remove these singletons: +#include "SaveManager.h" +#include "EventManager.h" + +namespace crepe { + +class ComponentManager; +class SceneManager; +class ResourceManager; + +/** + * Struct to pass references to classes that would otherwise need to be singletons down to + * other classes within the engine hierarchy. Made to prevent constant changes to subclasses to + * pass specific references through dependency injection. All references on this struct + * *should* be explicitly checked for availability as this struct does not guarantee anything. + * + * \note Dereferencing members of this struct should be deferred. If you are a user of this + * class, keep a reference to this mediator instead of just picking references from it when you + * receive an instance. + * + * \warning This class should never be directly accessible from the API + */ +struct Mediator { + OptionalRef<ComponentManager> component_manager; + OptionalRef<SceneManager> scene_manager; + OptionalRef<SaveManager> save_manager = SaveManager::get_instance(); + OptionalRef<EventManager> event_manager = EventManager::get_instance(); + OptionalRef<ResourceManager> resource_manager; +}; + +} diff --git a/src/crepe/manager/ResourceManager.cpp b/src/crepe/manager/ResourceManager.cpp new file mode 100644 index 0000000..87585ad --- /dev/null +++ b/src/crepe/manager/ResourceManager.cpp @@ -0,0 +1,34 @@ +#include "util/Log.h" + +#include "ResourceManager.h" + +using namespace crepe; +using namespace std; + +ResourceManager::ResourceManager(Mediator & mediator) : Manager(mediator) { + mediator.resource_manager = *this; + dbg_trace(); +} +ResourceManager::~ResourceManager() { dbg_trace(); } + +void ResourceManager::clear() { + std::erase_if(this->resources, [](const pair<const Asset, CacheEntry> & pair) { + const CacheEntry & entry = pair.second; + return entry.persistent == false; + }); +} + +void ResourceManager::clear_all() { + this->resources.clear(); +} + +void ResourceManager::set_persistent(const Asset & asset, bool persistent) { + this->get_entry(asset).persistent = persistent; +} + +ResourceManager::CacheEntry & ResourceManager::get_entry(const Asset & asset) { + if (!this->resources.contains(asset)) + this->resources[asset] = {}; + return this->resources.at(asset); +} + diff --git a/src/crepe/manager/ResourceManager.h b/src/crepe/manager/ResourceManager.h new file mode 100644 index 0000000..e7e6abc --- /dev/null +++ b/src/crepe/manager/ResourceManager.h @@ -0,0 +1,48 @@ +#pragma once + +#include <memory> +#include <unordered_map> + +#include "../Resource.h" +#include "../api/Asset.h" + +#include "Manager.h" + +namespace crepe { + +/** + * \brief The ResourceManager is responsible for storing and managing assets over + * multiple scenes. + * + * The ResourceManager ensures that assets are loaded once and can be accessed + * across different scenes. It caches assets to avoid reloading them every time + * a scene is loaded. Assets are retained in memory until the ResourceManager is + * destroyed, at which point the cached assets are cleared. + */ +class ResourceManager : public Manager { +public: + ResourceManager(Mediator & mediator); + virtual ~ResourceManager(); // dbg_trace + +private: + struct CacheEntry { + std::unique_ptr<Resource> resource = nullptr; + bool persistent = false; + }; + //! A cache that holds all the assets, accessible by their file path, over multiple scenes. + std::unordered_map<const Asset, CacheEntry> resources; + CacheEntry & get_entry(const Asset & asset); + +public: + void set_persistent(const Asset & asset, bool persistent); + + template <typename Resource> + Resource & get(const Asset & asset); + + void clear(); + void clear_all(); +}; + +} // namespace crepe + +#include "ResourceManager.hpp" diff --git a/src/crepe/manager/ResourceManager.hpp b/src/crepe/manager/ResourceManager.hpp new file mode 100644 index 0000000..8270bc5 --- /dev/null +++ b/src/crepe/manager/ResourceManager.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include <format> + +#include "ResourceManager.h" + +namespace crepe { + +template <typename T> +T & ResourceManager::get(const Asset & asset) { + using namespace std; + static_assert(is_base_of<Resource, T>::value, "cache must recieve a derivative class of Resource"); + + CacheEntry & entry = this->get_entry(asset); + if (entry.resource == nullptr) + entry.resource = make_unique<T>(asset); + + T * concrete_resource = dynamic_cast<T *>(entry.resource.get()); + if (concrete_resource == nullptr) + throw runtime_error(format("ResourceManager: mismatch between requested type and actual type of resource ({})", asset.get_path())); + + return *concrete_resource; +} + +} + diff --git a/src/crepe/manager/SaveManager.cpp b/src/crepe/manager/SaveManager.cpp new file mode 100644 index 0000000..121d017 --- /dev/null +++ b/src/crepe/manager/SaveManager.cpp @@ -0,0 +1,173 @@ +#include "../facade/DB.h" +#include "../util/Log.h" +#include "../api/Config.h" +#include "../ValueBroker.h" + +#include "SaveManager.h" + +using namespace std; +using namespace crepe; + +template <> +string SaveManager::serialize(const string & value) const noexcept { + return value; +} +template <typename T> +string SaveManager::serialize(const T & value) const noexcept { + return to_string(value); +} +template string SaveManager::serialize(const uint8_t &) const noexcept; +template string SaveManager::serialize(const int8_t &) const noexcept; +template string SaveManager::serialize(const uint16_t &) const noexcept; +template string SaveManager::serialize(const int16_t &) const noexcept; +template string SaveManager::serialize(const uint32_t &) const noexcept; +template string SaveManager::serialize(const int32_t &) const noexcept; +template string SaveManager::serialize(const uint64_t &) const noexcept; +template string SaveManager::serialize(const int64_t &) const noexcept; +template string SaveManager::serialize(const float &) const noexcept; +template string SaveManager::serialize(const double &) const noexcept; + +template <> +uint64_t SaveManager::deserialize(const string & value) const noexcept { + try { + return stoul(value); + } catch (std::invalid_argument &) { + return 0; + } +} +template <> +int64_t SaveManager::deserialize(const string & value) const noexcept { + try { + return stol(value); + } catch (std::invalid_argument &) { + return 0; + } +} +template <> +float SaveManager::deserialize(const string & value) const noexcept { + try { + return stof(value); + } catch (std::invalid_argument &) { + return 0; + } + return stof(value); +} +template <> +double SaveManager::deserialize(const string & value) const noexcept { + try { + return stod(value); + } catch (std::invalid_argument &) { + return 0; + } +} +template <> +string SaveManager::deserialize(const string & value) const noexcept { + return value; +} + +template <> +uint8_t SaveManager::deserialize(const string & value) const noexcept { + return deserialize<uint64_t>(value); +} +template <> +int8_t SaveManager::deserialize(const string & value) const noexcept { + return deserialize<int64_t>(value); +} +template <> +uint16_t SaveManager::deserialize(const string & value) const noexcept { + return deserialize<uint64_t>(value); +} +template <> +int16_t SaveManager::deserialize(const string & value) const noexcept { + return deserialize<int64_t>(value); +} +template <> +uint32_t SaveManager::deserialize(const string & value) const noexcept { + return deserialize<uint64_t>(value); +} +template <> +int32_t SaveManager::deserialize(const string & value) const noexcept { + return deserialize<int64_t>(value); +} + +SaveManager::SaveManager() { dbg_trace(); } + +SaveManager & SaveManager::get_instance() { + dbg_trace(); + static SaveManager instance; + return instance; +} + +DB & SaveManager::get_db() { + Config & cfg = Config::get_instance(); + // TODO: make this path relative to XDG_DATA_HOME on Linux and whatever the + // default equivalent is on Windows using some third party library + static DB db(cfg.savemgr.location); + return db; +} + +bool SaveManager::has(const string & key) { + DB & db = this->get_db(); + return db.has(key); +} + +template <> +void SaveManager::set(const string & key, const string & value) { + DB & db = this->get_db(); + db.set(key, value); +} +template <typename T> +void SaveManager::set(const string & key, const T & value) { + DB & db = this->get_db(); + db.set(key, std::to_string(value)); +} +template void SaveManager::set(const string &, const uint8_t &); +template void SaveManager::set(const string &, const int8_t &); +template void SaveManager::set(const string &, const uint16_t &); +template void SaveManager::set(const string &, const int16_t &); +template void SaveManager::set(const string &, const uint32_t &); +template void SaveManager::set(const string &, const int32_t &); +template void SaveManager::set(const string &, const uint64_t &); +template void SaveManager::set(const string &, const int64_t &); +template void SaveManager::set(const string &, const float &); +template void SaveManager::set(const string &, const double &); + +template <typename T> +ValueBroker<T> SaveManager::get(const string & key, const T & default_value) { + if (!this->has(key)) this->set<T>(key, default_value); + return this->get<T>(key); +} +template ValueBroker<uint8_t> SaveManager::get(const string &, const uint8_t &); +template ValueBroker<int8_t> SaveManager::get(const string &, const int8_t &); +template ValueBroker<uint16_t> SaveManager::get(const string &, const uint16_t &); +template ValueBroker<int16_t> SaveManager::get(const string &, const int16_t &); +template ValueBroker<uint32_t> SaveManager::get(const string &, const uint32_t &); +template ValueBroker<int32_t> SaveManager::get(const string &, const int32_t &); +template ValueBroker<uint64_t> SaveManager::get(const string &, const uint64_t &); +template ValueBroker<int64_t> SaveManager::get(const string &, const int64_t &); +template ValueBroker<float> SaveManager::get(const string &, const float &); +template ValueBroker<double> SaveManager::get(const string &, const double &); +template ValueBroker<string> SaveManager::get(const string &, const string &); + +template <typename T> +ValueBroker<T> SaveManager::get(const string & key) { + T value; + return { + [this, key](const T & target) { this->set<T>(key, target); }, + [this, key, value]() mutable -> const T & { + value = this->deserialize<T>(this->get_db().get(key)); + return value; + }, + }; +} +template ValueBroker<uint8_t> SaveManager::get(const string &); +template ValueBroker<int8_t> SaveManager::get(const string &); +template ValueBroker<uint16_t> SaveManager::get(const string &); +template ValueBroker<int16_t> SaveManager::get(const string &); +template ValueBroker<uint32_t> SaveManager::get(const string &); +template ValueBroker<int32_t> SaveManager::get(const string &); +template ValueBroker<uint64_t> SaveManager::get(const string &); +template ValueBroker<int64_t> SaveManager::get(const string &); +template ValueBroker<float> SaveManager::get(const string &); +template ValueBroker<double> SaveManager::get(const string &); +template ValueBroker<string> SaveManager::get(const string &); diff --git a/src/crepe/manager/SaveManager.h b/src/crepe/manager/SaveManager.h new file mode 100644 index 0000000..3d8c852 --- /dev/null +++ b/src/crepe/manager/SaveManager.h @@ -0,0 +1,114 @@ +#pragma once + +#include <memory> + +#include "../ValueBroker.h" + +namespace crepe { + +class DB; + +/** + * \brief Save data manager + * + * This class provides access to a simple key-value store that stores + * - integers (8-64 bit, signed or unsigned) + * - real numbers (float or double) + * - string (std::string) + * + * The underlying database is a key-value store. + */ +class SaveManager { +public: + /** + * \brief Get a read/write reference to a value and initialize it if it does not yet exist + * + * \param key The value key + * \param default_value Value to initialize \c key with if it does not already exist in the + * database + * + * \return Read/write reference to the value + */ + template <typename T> + ValueBroker<T> get(const std::string & key, const T & default_value); + + /** + * \brief Get a read/write reference to a value + * + * \param key The value key + * + * \return Read/write reference to the value + * + * \note Attempting to read this value before it is initialized (i.e. set) will result in an + * exception + */ + template <typename T> + ValueBroker<T> get(const std::string & key); + + /** + * \brief Set a value directly + * + * \param key The value key + * \param value The value to store + */ + template <typename T> + void set(const std::string & key, const T & value); + + /** + * \brief Check if the save file has a value for this \c key + * + * \param key The value key + * + * \returns True if the key exists, or false if it does not + */ + bool has(const std::string & key); + +private: + SaveManager(); + virtual ~SaveManager() = default; + +private: + /** + * \brief Serialize an arbitrary value to STL string + * + * \tparam T Type of arbitrary value + * + * \returns String representation of value + */ + template <typename T> + std::string serialize(const T &) const noexcept; + + /** + * \brief Deserialize an STL string back to type \c T + * + * \tparam T Type of value + * \param value Serialized value + * + * \returns Deserialized value + */ + template <typename T> + T deserialize(const std::string & value) const noexcept; + +public: + // singleton + static SaveManager & get_instance(); + SaveManager(const SaveManager &) = delete; + SaveManager(SaveManager &&) = delete; + SaveManager & operator=(const SaveManager &) = delete; + SaveManager & operator=(SaveManager &&) = delete; + +private: + /** + * \brief Create an instance of DB and return its reference + * + * \returns DB instance + * + * This function exists because DB is a facade class, which can't directly be used in the API + * without workarounds + * + * TODO: better solution + */ + static DB & get_db(); +}; + +} // namespace crepe diff --git a/src/crepe/manager/SceneManager.cpp b/src/crepe/manager/SceneManager.cpp new file mode 100644 index 0000000..50a9fbb --- /dev/null +++ b/src/crepe/manager/SceneManager.cpp @@ -0,0 +1,35 @@ +#include <algorithm> +#include <memory> + +#include "ComponentManager.h" +#include "SceneManager.h" + +using namespace crepe; +using namespace std; + +SceneManager::SceneManager(Mediator & mediator) : Manager(mediator) { + mediator.scene_manager = *this; +} + +void SceneManager::set_next_scene(const string & name) { next_scene = name; } + +void SceneManager::load_next_scene() { + // next scene not set + if (this->next_scene.empty()) return; + + auto it = find_if(this->scenes.begin(), this->scenes.end(), + [&next_scene = this->next_scene](unique_ptr<Scene> & scene) { + return scene.get()->get_name() == next_scene; + }); + + // next scene not found + if (it == this->scenes.end()) return; + unique_ptr<Scene> & scene = *it; + + // Delete all components of the current scene + ComponentManager & mgr = this->mediator.component_manager; + mgr.delete_all_components(); + + // Load the new scene + scene->load_scene(); +} diff --git a/src/crepe/manager/SceneManager.h b/src/crepe/manager/SceneManager.h new file mode 100644 index 0000000..e0955c2 --- /dev/null +++ b/src/crepe/manager/SceneManager.h @@ -0,0 +1,52 @@ +#pragma once + +#include <memory> +#include <vector> + +#include "../api/Scene.h" + +#include "Manager.h" + +namespace crepe { + +class ComponentManager; + +/** + * \brief Manages scenes + * + * This class manages scenes. It can add new scenes and load them. It also manages the current scene + * and the next scene. + */ +class SceneManager : public Manager { +public: + SceneManager(Mediator & mediator); + +public: + /** + * \brief Add a new concrete scene to the scene manager + * + * \tparam T Type of concrete scene + */ + template <typename T, typename... Args> + void add_scene(Args &&... args); + /** + * \brief Set the next scene + * + * This scene will be loaded at the end of the frame + * + * \param name Name of the next scene + */ + void set_next_scene(const std::string & name); + //! Load a new scene (if there is one) + void load_next_scene(); + +private: + //! Vector of concrete scenes (added by add_scene()) + std::vector<std::unique_ptr<Scene>> scenes; + //! Next scene to load + std::string next_scene; +}; + +} // namespace crepe + +#include "SceneManager.hpp" diff --git a/src/crepe/manager/SceneManager.hpp b/src/crepe/manager/SceneManager.hpp new file mode 100644 index 0000000..dff4e51 --- /dev/null +++ b/src/crepe/manager/SceneManager.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include "SceneManager.h" + +namespace crepe { + +template <typename T, typename... Args> +void SceneManager::add_scene(Args &&... args) { + using namespace std; + static_assert(is_base_of<Scene, T>::value, "T must be derived from Scene"); + + Scene * scene = new T(std::forward<Args>(args)...); + unique_ptr<Scene> unique_scene(scene); + + unique_scene->mediator = this->mediator; + + this->scenes.emplace_back(std::move(unique_scene)); + + // The first scene added, is the one that will be loaded at the beginning + if (next_scene.empty()) { + next_scene = scene->get_name(); + } +} + +} // namespace crepe |