diff options
43 files changed, 1129 insertions, 911 deletions
diff --git a/asset/texture/ERROR.png b/asset/texture/ERROR.png Binary files differnew file mode 100644 index 0000000..2af3548 --- /dev/null +++ b/asset/texture/ERROR.png diff --git a/mwe/events/include/event.h b/mwe/events/include/event.h index e1b220b..ee1bf52 100644 --- a/mwe/events/include/event.h +++ b/mwe/events/include/event.h @@ -148,7 +148,7 @@ private: }; class ShutDownEvent : public Event { public: - ShutDownEvent() : Event("ShutDownEvent"){}; + ShutDownEvent() : Event("ShutDownEvent") {}; REGISTER_EVENT_TYPE(ShutDownEvent) diff --git a/src/crepe/Component.h b/src/crepe/Component.h index 5279fb3..06ab1ef 100644 --- a/src/crepe/Component.h +++ b/src/crepe/Component.h @@ -27,6 +27,12 @@ protected: //! Only the ComponentManager can create components friend class ComponentManager; + // TODO: this breaks the CollisionSystem but should be uncommented: + // Component(const Component &) = delete; + // Component(Component &&) = delete; + // virtual Component & operator=(const Component &) = delete; + // virtual Component & operator=(Component &&) = delete; + public: virtual ~Component() = default; diff --git a/src/crepe/api/BehaviorScript.hpp b/src/crepe/api/BehaviorScript.hpp index 6bd123d..dd197b3 100644 --- a/src/crepe/api/BehaviorScript.hpp +++ b/src/crepe/api/BehaviorScript.hpp @@ -16,6 +16,7 @@ BehaviorScript & BehaviorScript::set_script(Args &&... args) { Script * s = new T(std::forward<Args>(args)...); s->game_object_id = this->game_object_id; s->component_manager_ref = &this->component_manager; + s->event_manager_ref = &EventManager::get_instance(); this->script = std::unique_ptr<Script>(s); return *this; } diff --git a/src/crepe/api/CMakeLists.txt b/src/crepe/api/CMakeLists.txt index 07b3a82..cc3e5e2 100644 --- a/src/crepe/api/CMakeLists.txt +++ b/src/crepe/api/CMakeLists.txt @@ -20,11 +20,12 @@ target_sources(crepe PUBLIC BoxCollider.cpp CircleCollider.cpp EventManager.cpp - EventHandler.cpp IKeyListener.cpp IMouseListener.cpp LoopManager.cpp LoopTimer.cpp + EventHandler.cpp + Script.cpp ) target_sources(crepe PUBLIC FILE_SET HEADERS FILES @@ -54,6 +55,7 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES EventManager.h EventManager.hpp EventHandler.h + EventHandler.hpp Event.h IKeyListener.h IMouseListener.h diff --git a/src/crepe/api/Event.h b/src/crepe/api/Event.h index bd6a541..9923a05 100644 --- a/src/crepe/api/Event.h +++ b/src/crepe/api/Event.h @@ -1,59 +1,115 @@ +// TODO discussing the location of these events #pragma once -#include "KeyCodes.h" -#include <iostream> + #include <string> -#include <typeindex> -#include "system/CollisionSystem.h" -class Event { -public: -}; +#include "../system/CollisionSystem.h" + +#include "KeyCodes.h" +/** + * \brief Base class for all event types in the system. + */ +class Event {}; + +/** + * \brief Event triggered when a key is pressed. + */ class KeyPressEvent : public Event { public: - int repeat = 0; + //! false if first time press, true if key is repeated + bool repeat = false; + + //! The key that was pressed. Keycode key = Keycode::NONE; }; +/** + * \brief Event triggered when a key is released. + */ class KeyReleaseEvent : public Event { public: + //! The key that was released. Keycode key = Keycode::NONE; }; +/** + * \brief Event triggered when a mouse button is pressed. + */ class MousePressEvent : public Event { public: + //! X-coordinate of the mouse position at the time of the event. int mouse_x = 0; + + //! Y-coordinate of the mouse position at the time of the event. int mouse_y = 0; + + //! The mouse button that was pressed. MouseButton button = MouseButton::NONE; }; +/** + * \brief Event triggered when a mouse button is clicked (press and release). + */ class MouseClickEvent : public Event { public: + //! X-coordinate of the mouse position at the time of the event. int mouse_x = 0; + + //! Y-coordinate of the mouse position at the time of the event. int mouse_y = 0; + + //! The mouse button that was clicked. MouseButton button = MouseButton::NONE; }; + +/** + * \brief Event triggered when a mouse button is released. + */ class MouseReleaseEvent : public Event { public: + //! X-coordinate of the mouse position at the time of the event. int mouse_x = 0; + + //! Y-coordinate of the mouse position at the time of the event. int mouse_y = 0; + + //! The mouse button that was released. MouseButton button = MouseButton::NONE; }; + +/** + * \brief Event triggered when the mouse is moved. + */ class MouseMoveEvent : public Event { public: + //! X-coordinate of the mouse position at the time of the event. int mouse_x = 0; + + //! Y-coordinate of the mouse position at the time of the event. int mouse_y = 0; }; + +/** + * \brief Event triggered during a collision between objects. + */ class CollisionEvent : public Event { public: crepe::CollisionSystem::CollisionInfo info; CollisionEvent(const crepe::CollisionSystem::CollisionInfo& collisionInfo) : info(collisionInfo) {} }; + +/** + * \brief Event triggered when text is submitted, e.g., from a text input. + */ class TextSubmitEvent : public Event { public: + //! The submitted text. std::string text = ""; }; -class ShutDownEvent : public Event { -public: -}; + +/** + * \brief Event triggered to indicate the application is shutting down. + */ +class ShutDownEvent : public Event {}; diff --git a/src/crepe/api/EventHandler.cpp b/src/crepe/api/EventHandler.cpp index 93a116a..4dc232f 100644 --- a/src/crepe/api/EventHandler.cpp +++ b/src/crepe/api/EventHandler.cpp @@ -1,2 +1,5 @@ #include "EventHandler.h" -bool IEventHandlerWrapper::exec(const Event & e) { return call(e); } + +using namespace crepe; + +bool IEventHandlerWrapper::exec(const Event & e) { return this->call(e); } diff --git a/src/crepe/api/EventHandler.h b/src/crepe/api/EventHandler.h index 0ab90de..ef659fd 100644 --- a/src/crepe/api/EventHandler.h +++ b/src/crepe/api/EventHandler.h @@ -1,9 +1,11 @@ #pragma once -#include "Event.h" + #include <functional> -#include <iostream> -#include <typeindex> +#include <string> +#include "Event.h" + +namespace crepe { /** * \brief A type alias for an event handler function. * @@ -11,8 +13,9 @@ * indicating whether the event is handled. * * \tparam EventType The type of event this handler will handle. + * + * Returning \c false from an event handler results in the event being propogated to other listeners for the same event type, while returning \c true stops propogation altogether. */ -// TODO: typedef template <typename EventType> using EventHandler = std::function<bool(const EventType & e)>; @@ -21,7 +24,7 @@ using EventHandler = std::function<bool(const EventType & e)>; * \brief An abstract base class for event handler wrappers. * * This class provides the interface for handling events. Derived classes must implement the - * `call()` method to process events and the `get_type()` method to return the handler's type. + * `call()` method to process events */ class IEventHandlerWrapper { public: @@ -40,15 +43,6 @@ public: */ bool exec(const Event & e); - /** - * \brief Get the type of the event handler. - * - * This method returns the type of the event handler as a string. - * - * \return A string representing the handler's type. - */ - virtual std::string get_type() const = 0; - private: /** * \brief The method responsible for handling the event. @@ -81,8 +75,7 @@ public: * * \param handler The event handler function. */ - explicit EventHandlerWrapper(const EventHandler<EventType> & handler) - : m_handler(handler), m_handler_type(m_handler.target_type().name()) {} + explicit EventHandlerWrapper(const EventHandler<EventType> & handler); private: /** @@ -93,20 +86,11 @@ private: * \param e The event to be handled. * \return A boolean value indicating whether the event is handled. */ - bool call(const Event & e) override { - return m_handler(static_cast<const EventType &>(e)); - } - - /** - * \brief Returns the type of the handler. - * - * This method returns a string representing the type of the event handler. - * - * \return The handler type as a string. - */ - std::string get_type() const override { return m_handler_type; } + bool call(const Event & e) override; //! The event handler function. - EventHandler<EventType> m_handler; - //! The type name of the handler function. - const std::string m_handler_type; + EventHandler<EventType> handler; }; + +} // namespace crepe + +#include "EventHandler.hpp" diff --git a/src/crepe/api/EventHandler.hpp b/src/crepe/api/EventHandler.hpp new file mode 100644 index 0000000..391dcca --- /dev/null +++ b/src/crepe/api/EventHandler.hpp @@ -0,0 +1,18 @@ +#include <typeindex> + +#include "EventHandler.h" + +namespace crepe { + +// Implementation of EventHandlerWrapper constructor +template <typename EventType> +EventHandlerWrapper<EventType>::EventHandlerWrapper(const EventHandler<EventType> & handler) + : handler(handler) {} + +// Implementation of EventHandlerWrapper::call +template <typename EventType> +bool EventHandlerWrapper<EventType>::call(const Event & e) { + return this->handler(static_cast<const EventType &>(e)); +} + +} //namespace crepe diff --git a/src/crepe/api/EventManager.cpp b/src/crepe/api/EventManager.cpp index e881d49..20f0dd3 100644 --- a/src/crepe/api/EventManager.cpp +++ b/src/crepe/api/EventManager.cpp @@ -1,6 +1,7 @@ #include "EventManager.h" using namespace crepe; +using namespace std; EventManager & EventManager::get_instance() { static EventManager instance; @@ -8,74 +9,38 @@ EventManager & EventManager::get_instance() { } void EventManager::dispatch_events() { - for (std::vector<std::tuple<std::unique_ptr<Event>, int, - std::type_index>>::iterator event_it - = this->events_queue.begin(); - event_it != this->events_queue.end();) { - std::unique_ptr<Event> & event = std::get<0>(*event_it); - int channel = std::get<1>(*event_it); - std::type_index event_type = std::get<2>(*event_it); + for (auto & event : this->events_queue) { + this->handle_event(event.type, event.channel, *event.event.get()); + } + this->events_queue.clear(); +} - bool event_handled = false; +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; - if (channel) { - std::unordered_map< - std::type_index, - std::unordered_map< - int, std::vector<std::unique_ptr<IEventHandlerWrapper>>>>:: - iterator handlers_it - = subscribers_by_event_id.find(event_type); - if (handlers_it != subscribers_by_event_id.end()) { - std::unordered_map< - int, std::vector<std::unique_ptr<IEventHandlerWrapper>>> & - handlers_map - = handlers_it->second; - std::unordered_map< - int, std::vector<std::unique_ptr<IEventHandlerWrapper>>>:: - iterator handlers - = handlers_map.find(channel); - if (handlers != handlers_map.end()) { - std::vector<std::unique_ptr<IEventHandlerWrapper>> & - callbacks - = handlers->second; - for (std::vector<std::unique_ptr<IEventHandlerWrapper>>:: - iterator handler_it - = callbacks.begin(); - handler_it != callbacks.end(); ++handler_it) { - if ((*handler_it)->exec(*event)) { - event_it = events_queue.erase(event_it); - event_handled = true; - break; - } - } - } - } - } else { - // Handle event for all channels - std::unordered_map< - std::type_index, - std::vector<std::unique_ptr<IEventHandlerWrapper>>>::iterator - handlers_it - = this->subscribers.find(event_type); - if (handlers_it != this->subscribers.end()) { - std::vector<std::unique_ptr<IEventHandlerWrapper>> & handlers - = handlers_it->second; - for (std::vector<std::unique_ptr<IEventHandlerWrapper>>:: - iterator handler_it - = handlers.begin(); - handler_it != handlers.end(); ++handler_it) { - // remove event from queue since and continue when callback returns true - if ((*handler_it)->exec(*event)) { - event_it = this->events_queue.erase(event_it); - event_handled = true; - break; - } - } - } - } + 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(); +} - if (!event_handled) { - ++event_it; +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/api/EventManager.h b/src/crepe/api/EventManager.h index 38d2e64..348a04d 100644 --- a/src/crepe/api/EventManager.h +++ b/src/crepe/api/EventManager.h @@ -1,8 +1,6 @@ #pragma once -#include <functional> #include <memory> -#include <type_traits> #include <typeindex> #include <unordered_map> #include <vector> @@ -10,113 +8,154 @@ #include "Event.h" #include "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 The EventManager class is responsible for managing the subscription, triggering, - * and queueing of events. It handles events and dispatches them to appropriate subscribers. + * \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: - /** - * \brief Deleted copy constructor to prevent copying of the EventManager instance. - */ - EventManager(const EventManager &) = delete; - - /** - * \brief Deleted copy assignment operator to prevent assignment of the EventManager instance. - */ - const EventManager & operator=(const EventManager &) = delete; + 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 on the first call. - * - * \return Reference to the EventManager instance. - */ + * \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 an event. - * - * This method allows the registration of a callback for a specific event type and channel. - * - * \tparam EventType The type of the event to subscribe to. - * \param callback The callback function to invoke when the event is triggered. - * \param channel The channel number to subscribe to (default is 0). - */ + * \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> - void subscribe(EventHandler<EventType> && callback, int channel = 0); + subscription_t subscribe(const EventHandler<EventType> & callback, + event_channel_t channel = CHANNEL_ALL); /** - * \brief Unsubscribe from an event. - * - * This method removes a previously registered callback from an event. - * - * \tparam EventType The type of the event to unsubscribe from. - * \param callback The callback function to remove from the subscription list. - * \param channel The event ID to unsubscribe from. - */ - template <typename EventType> - void unsubscribe(const EventHandler<EventType> &, int channel); + * \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. - * - * This method invokes the appropriate callback(s) for the specified event. - * - * \tparam EventType The type of the event to trigger. - * \param event The event data to pass to the callback. - * \param channel The channel from which to trigger the event (default is 0). - */ + * \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, int channel); + void trigger_event(const EventType & event, event_channel_t channel = CHANNEL_ALL); /** - * \brief Queue an event for later processing. - * - * This method adds an event to the event queue, which will be processed in the - * dispatch_events function. - * - * \tparam EventType The type of the event to queue. - * \param event The event to queue. - * \param channel The channel number for the event (default is 0). - */ + * \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(EventType && event, int channel); + void queue_event(const EventType & event, event_channel_t channel = CHANNEL_ALL); /** - * \brief Dispatch all queued events. - * - * This method processes all events in the event queue and triggers the corresponding - * callbacks for each event. - */ + * \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. - * - * This constructor is private to enforce the singleton pattern. - */ + * \brief Default constructor for the EventManager. + * + * Constructor is private to enforce the singleton pattern. + */ EventManager() = default; - //! The queue of events to be processed. - std::vector<std::tuple<std::unique_ptr<Event>, int, std::type_index>> - events_queue; - //! Registered event handlers. - std::unordered_map<std::type_index, - std::vector<std::unique_ptr<IEventHandlerWrapper>>> - subscribers; - //! Event handlers indexed by event ID. - std::unordered_map< - std::type_index, - std::unordered_map<int, - std::vector<std::unique_ptr<IEventHandlerWrapper>>>> - subscribers_by_event_id; + /** + * \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/api/EventManager.hpp b/src/crepe/api/EventManager.hpp index b509097..a5f4556 100644 --- a/src/crepe/api/EventManager.hpp +++ b/src/crepe/api/EventManager.hpp @@ -1,140 +1,36 @@ +#pragma once + #include "EventManager.h" + namespace crepe { template <typename EventType> -void EventManager::subscribe(EventHandler<EventType> && callback, int channel) { +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); - - if (channel) { - std::unordered_map<int, - std::vector<std::unique_ptr<IEventHandlerWrapper>>> & - handlers_map - = this->subscribers_by_event_id[event_type]; - std::unordered_map< - int, std::vector<std::unique_ptr<IEventHandlerWrapper>>>::iterator - handlers - = handlers_map.find(channel); - if (handlers != handlers_map.end()) { - handlers->second.emplace_back(std::move(handler)); - } else { - handlers_map[channel].emplace_back(std::move(handler)); - } - } else { - std::vector<std::unique_ptr<IEventHandlerWrapper>> & handlers - = this->subscribers[event_type]; - handlers.emplace_back(std::move(handler)); - } -} - -template <typename EventType> -void EventManager::queue_event(EventType && event, int channel) { - std::type_index event_type = std::type_index(typeid(EventType)); - - std::unique_ptr<EventType> event_ptr - = std::make_unique<EventType>(std::forward<EventType>(event)); - - std::tuple<std::unique_ptr<Event>, int, std::type_index> tuple( - std::move(event_ptr), channel, event_type); - this->events_queue.push_back(std::move(tuple)); + 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::trigger_event(const EventType & event, int channel) { - std::type_index event_type = std::type_index(typeid(EventType)); - - if (channel > 0) { - std::unordered_map<int, - std::vector<std::unique_ptr<IEventHandlerWrapper>>> & - handlers_map - = this->subscribers_by_event_id[event_type]; - std::unordered_map< - int, std::vector<std::unique_ptr<IEventHandlerWrapper>>>::iterator - handlers_it - = handlers_map.find(channel); - - if (handlers_it != handlers_map.end()) { - std::vector<std::unique_ptr<IEventHandlerWrapper>> & handlers - = handlers_it->second; - for (std::vector<std::unique_ptr<IEventHandlerWrapper>>::iterator it - = handlers.begin(); - it != handlers.end();++it) { - // stops when callback returns true - if((*it)->exec(event)){ - break; - } - } - } - } else { - std::vector<std::unique_ptr<IEventHandlerWrapper>> & handlers - = this->subscribers[event_type]; - for (std::vector<std::unique_ptr<IEventHandlerWrapper>>::iterator it - = handlers.begin(); - it != handlers.end();++it) { - // stops when callback returns true - if((*it)->exec(event)){ - break; - } - } - } +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::unsubscribe(const EventHandler<EventType> & callback, - int channel) { - std::type_index event_type(typeid(EventType)); - std::string handler_name = callback.target_type().name(); - - if (channel) { - std::unordered_map< - std::type_index, - std::unordered_map< - int, std::vector<std::unique_ptr<IEventHandlerWrapper>>>>:: - iterator subscriber_list - = this->subscribers_by_event_id.find(event_type); - if (subscriber_list != this->subscribers_by_event_id.end()) { - std::unordered_map< - int, std::vector<std::unique_ptr<IEventHandlerWrapper>>> & - handlers_map - = subscriber_list->second; - std::unordered_map< - int, - std::vector<std::unique_ptr<IEventHandlerWrapper>>>::iterator - handlers - = handlers_map.find(channel); - if (handlers != handlers_map.end()) { - std::vector<std::unique_ptr<IEventHandlerWrapper>> & callbacks - = handlers->second; - for (std::vector< - std::unique_ptr<IEventHandlerWrapper>>::iterator it - = callbacks.begin(); - it != callbacks.end(); ++it) { - if ((*it)->get_type() == handler_name) { - it = callbacks.erase(it); - return; - } - } - } - } - } else { - std::unordered_map<std::type_index, - std::vector<std::unique_ptr<IEventHandlerWrapper>>>:: - iterator handlers_it - = this->subscribers.find(event_type); - if (handlers_it != this->subscribers.end()) { - std::vector<std::unique_ptr<IEventHandlerWrapper>> & handlers - = handlers_it->second; - for (std::vector<std::unique_ptr<IEventHandlerWrapper>>::iterator it - = handlers.begin(); - it != handlers.end(); ++it) { - if ((*it)->get_type() == handler_name) { - it = handlers.erase(it); - return; - } - } - } - } +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/api/IKeyListener.cpp b/src/crepe/api/IKeyListener.cpp index 4fd9855..8642655 100644 --- a/src/crepe/api/IKeyListener.cpp +++ b/src/crepe/api/IKeyListener.cpp @@ -2,50 +2,18 @@ using namespace crepe; -IKeyListener::IKeyListener() { - this->channel = channel; - this->subscribe_events(); -} -IKeyListener::IKeyListener(int channel) { - this->channel = channel; - this->subscribe_events(); -} -IKeyListener::~IKeyListener() { this->unsubscribe_events(); } - -void IKeyListener::subscribe_events() { - key_pressed_handler = [this](const KeyPressEvent & event) { - return this->on_key_pressed(event); - }; - key_released_handler = [this](const KeyReleaseEvent & event) { - return this->on_key_released(event); - }; - EventManager::get_instance().subscribe<KeyPressEvent>( - std::move(this->key_pressed_handler), this->channel); - EventManager::get_instance().subscribe<KeyReleaseEvent>( - std::move(this->key_released_handler), this->channel); +// Constructor with specified channel +IKeyListener::IKeyListener(event_channel_t channel) + : event_manager(EventManager::get_instance()) { + this->press_id = event_manager.subscribe<KeyPressEvent>( + [this](const KeyPressEvent & event) { return this->on_key_pressed(event); }, channel); + this->release_id = event_manager.subscribe<KeyReleaseEvent>( + [this](const KeyReleaseEvent & event) { return this->on_key_released(event); }, + channel); } -void IKeyListener::unsubscribe_events() { - EventManager::get_instance().unsubscribe<KeyPressEvent>( - this->key_pressed_handler, channel); - EventManager::get_instance().unsubscribe<KeyReleaseEvent>( - this->key_released_handler, channel); - std::cout << std::endl; -} -void IKeyListener::activate_keys() { - if (this->active) { - return; - } - this->subscribe_events(); -} -void IKeyListener::deactivate_keys() { - if (!this->active) { - return; - } - this->unsubscribe_events(); -} -void IKeyListener::set_channel(int channel) { - this->unsubscribe_events(); - this->channel = channel; - this->subscribe_events(); +// Destructor, unsubscribe events +IKeyListener::~IKeyListener() { + event_manager.unsubscribe(this->press_id); + event_manager.unsubscribe(this->release_id); } diff --git a/src/crepe/api/IKeyListener.h b/src/crepe/api/IKeyListener.h index 839acbf..328a4c2 100644 --- a/src/crepe/api/IKeyListener.h +++ b/src/crepe/api/IKeyListener.h @@ -1,8 +1,11 @@ #pragma once + #include "Event.h" #include "EventHandler.h" #include "EventManager.h" +namespace crepe { + /** * \class IKeyListener * \brief Interface for keyboard event handling in the application. @@ -13,17 +16,12 @@ public: * \brief Constructs an IKeyListener with a specified channel. * \param channel The channel ID for event handling. */ - IKeyListener(int channel); - - /** - * \brief Default constructor for IKeyListener. - */ - IKeyListener(); - - /** - * \brief Destructor. - */ + IKeyListener(event_channel_t channel = EventManager::CHANNEL_ALL); virtual ~IKeyListener(); + IKeyListener(const IKeyListener &) = delete; + IKeyListener & operator=(const IKeyListener &) = delete; + IKeyListener & operator=(IKeyListener &&) = delete; + IKeyListener(IKeyListener &&) = delete; /** * \brief Pure virtual function to handle key press events. @@ -39,40 +37,13 @@ public: */ virtual bool on_key_released(const KeyReleaseEvent & event) = 0; - /** - * \brief Activates key listening. - */ - void activate_keys(); - - /** - * \brief Deactivates key listening. - */ - void deactivate_keys(); - - /** - * \brief Sets the channel ID for event handling. - * \param channel The channel ID to set. - */ - void set_channel(int channel); - -protected: - /** - * \brief Subscribes to key events. - */ - void subscribe_events(); - - /** - * \brief Unsubscribes from key events. - */ - void unsubscribe_events(); - private: - //! Indicates whether key listening is active. - bool active = true; - //! Channel ID for event handling. - int channel = 0; - //! Key press event handler. - EventHandler<KeyPressEvent> key_pressed_handler; - //!< Key release event handler. - EventHandler<KeyReleaseEvent> key_released_handler; + //! Key press event id + subscription_t press_id = -1; + //! Key release event id + subscription_t release_id = -1; + //! EventManager reference + EventManager & event_manager; }; + +} // namespace crepe diff --git a/src/crepe/api/IMouseListener.cpp b/src/crepe/api/IMouseListener.cpp index 489e55b..989aeb3 100644 --- a/src/crepe/api/IMouseListener.cpp +++ b/src/crepe/api/IMouseListener.cpp @@ -2,63 +2,28 @@ using namespace crepe; -IMouseListener::IMouseListener(int channel) { this->channel = channel; } - -IMouseListener::IMouseListener() { this->subscribe_events(); } - -IMouseListener::~IMouseListener() { this->unsubscribe_events(); } - -void IMouseListener::subscribe_events() { - // Define handler lambdas and subscribe them - mouse_click_handler = [this](const MouseClickEvent & event) { - return this->on_mouse_clicked(event); - }; - mouse_press_handler = [this](const MousePressEvent & event) { - return this->on_mouse_pressed(event); - }; - mouse_release_handler = [this](const MouseReleaseEvent & event) { - return this->on_mouse_released(event); - }; - mouse_move_handler = [this](const MouseMoveEvent & event) { - return this->on_mouse_moved(event); - }; - EventManager::get_instance().subscribe<MouseClickEvent>( - std::move(this->mouse_click_handler), this->channel); - EventManager::get_instance().subscribe<MousePressEvent>( - std::move(this->mouse_press_handler), this->channel); - EventManager::get_instance().subscribe<MouseReleaseEvent>( - std::move(this->mouse_release_handler), this->channel); - EventManager::get_instance().subscribe<MouseMoveEvent>( - std::move(this->mouse_move_handler), this->channel); -} -// TODO: reference voor singleton -void IMouseListener::unsubscribe_events() { - EventManager::get_instance().unsubscribe<MouseClickEvent>( - this->mouse_click_handler, this->channel); - EventManager::get_instance().unsubscribe<MousePressEvent>( - this->mouse_press_handler, this->channel); - EventManager::get_instance().unsubscribe<MouseReleaseEvent>( - this->mouse_release_handler, this->channel); - EventManager::get_instance().unsubscribe<MouseMoveEvent>( - this->mouse_move_handler, this->channel); -} - -void IMouseListener::activate_mouse() { - if (this->active) { - return; - } - this->subscribe_events(); -} - -void IMouseListener::deactivate_mouse() { - if (!this->active) { - return; - } - this->unsubscribe_events(); +IMouseListener::IMouseListener(event_channel_t channel) + : event_manager(EventManager::get_instance()) { + this->click_id = event_manager.subscribe<MouseClickEvent>( + [this](const MouseClickEvent & event) { return this->on_mouse_clicked(event); }, + channel); + + this->press_id = event_manager.subscribe<MousePressEvent>( + [this](const MousePressEvent & event) { return this->on_mouse_pressed(event); }, + channel); + + this->release_id = event_manager.subscribe<MouseReleaseEvent>( + [this](const MouseReleaseEvent & event) { return this->on_mouse_released(event); }, + channel); + + this->move_id = event_manager.subscribe<MouseMoveEvent>( + [this](const MouseMoveEvent & event) { return this->on_mouse_moved(event); }, channel); } -void IMouseListener::set_channel(int channel) { - this->unsubscribe_events(); - this->channel = channel; - this->subscribe_events(); +IMouseListener::~IMouseListener() { + // Unsubscribe event handlers + event_manager.unsubscribe(this->click_id); + event_manager.unsubscribe(this->press_id); + event_manager.unsubscribe(this->release_id); + event_manager.unsubscribe(this->move_id); } diff --git a/src/crepe/api/IMouseListener.h b/src/crepe/api/IMouseListener.h index 7e92956..15e1619 100644 --- a/src/crepe/api/IMouseListener.h +++ b/src/crepe/api/IMouseListener.h @@ -4,6 +4,8 @@ #include "EventHandler.h" #include "EventManager.h" +namespace crepe { + /** * \class IMouseListener * \brief Interface for mouse event handling in the application. @@ -11,34 +13,14 @@ class IMouseListener { public: /** - * \brief Default constructor. - */ - IMouseListener(); - - /** * \brief Constructs an IMouseListener with a specified channel. * \param channel The channel ID for event handling. */ - IMouseListener(int channel); - - /** - * \brief Destructor. - */ + IMouseListener(event_channel_t channel = EventManager::CHANNEL_ALL); virtual ~IMouseListener(); - - /** - * \brief Copy constructor (deleted). - */ - IMouseListener(const IMouseListener &) = delete; - - /** - * \brief Copy assignment operator (deleted). - */ IMouseListener & operator=(const IMouseListener &) = delete; - - /** - * \brief Move constructor (deleted). - */ + IMouseListener(const IMouseListener &) = delete; + IMouseListener & operator=(const IMouseListener &&) = delete; IMouseListener(IMouseListener &&) = delete; /** @@ -74,44 +56,17 @@ public: */ virtual bool on_mouse_moved(const MouseMoveEvent & event) = 0; - /** - * \brief Activates mouse listening. - */ - void activate_mouse(); - - /** - * \brief Deactivates mouse listening. - */ - void deactivate_mouse(); - - /** - * \brief Sets the channel ID for event handling. - * \param channel The channel ID to set. - */ - void set_channel(int channel); - -protected: - /** - * \brief Subscribes to mouse events on the specified channel. - */ - void subscribe_events(); - - /** - * \brief Unsubscribes from mouse events on the specified channel. - */ - void unsubscribe_events(); - private: - //! Indicates whether mouse listening is active. - bool active = true; - //! Channel ID for event handling. - int channel = 0; - //! Mouse click event handler. - EventHandler<MouseClickEvent> mouse_click_handler; - //! Mouse press event handler. - EventHandler<MousePressEvent> mouse_press_handler; - //! Mouse release event handler. - EventHandler<MouseReleaseEvent> mouse_release_handler; - //! Mouse move event handler. - EventHandler<MouseMoveEvent> mouse_move_handler; + //! Mouse click event id + subscription_t click_id = -1; + //! Mouse press event id + subscription_t press_id = -1; + //! Mouse release event id + subscription_t release_id = -1; + //! Mouse move event id + subscription_t move_id = -1; + //! EventManager reference + EventManager & event_manager; }; + +} //namespace crepe diff --git a/src/crepe/api/KeyCodes.h b/src/crepe/api/KeyCodes.h index e5a91fc..9e173e0 100644 --- a/src/crepe/api/KeyCodes.h +++ b/src/crepe/api/KeyCodes.h @@ -1,94 +1,93 @@ #pragma once +//! Enumeration for mouse button inputs, including standard and extended buttons. enum class MouseButton { - NONE = 0, - LEFT_MOUSE = 1, - RIGHT_MOUSE = 2, - MIDDLE_MOUSE = 3, - X1_MOUSE = 4, - X2_MOUSE = 5, - SCROLL_UP = 6, - SCROLL_DOWN = 7, + NONE = 0, //!< No mouse button input. + LEFT_MOUSE = 1, //!< Left mouse button. + RIGHT_MOUSE = 2, //!< Right mouse button. + MIDDLE_MOUSE = 3, //!< Middle mouse button (scroll wheel press). + X1_MOUSE = 4, //!< First extended mouse button. + X2_MOUSE = 5, //!< Second extended mouse button. + SCROLL_UP = 6, //!< Scroll wheel upward movement. + SCROLL_DOWN = 7, //!< Scroll wheel downward movement. }; -enum class Keycode : int { - NONE = 0, - SPACE = 32, - APOSTROPHE = 39, /* ' */ - COMMA = 44, /* , */ - MINUS = 45, /* - */ - PERIOD = 46, /* . */ - SLASH = 47, /* / */ - - D0 = 48, /* 0 */ - D1 = 49, /* 1 */ - D2 = 50, /* 2 */ - D3 = 51, /* 3 */ - D4 = 52, /* 4 */ - D5 = 53, /* 5 */ - D6 = 54, /* 6 */ - D7 = 55, /* 7 */ - D8 = 56, /* 8 */ - D9 = 57, /* 9 */ - - SEMICOLON = 59, /* ; */ - EQUAL = 61, /* = */ - - A = 65, - B = 66, - C = 67, - D = 68, - E = 69, - F = 70, - G = 71, - H = 72, - I = 73, - J = 74, - K = 75, - L = 76, - M = 77, - N = 78, - O = 79, - P = 80, - Q = 81, - R = 82, - S = 83, - T = 84, - U = 85, - V = 86, - W = 87, - X = 88, - Y = 89, - Z = 90, - - LEFT_BRACKET = 91, /* [ */ - BACKSLASH = 92, /* \ */ - RIGHT_BRACKET = 93, /* ] */ - GRAVE_ACCENT = 96, /* ` */ - - WORLD1 = 161, /* non-US #1 */ - WORLD2 = 162, /* non-US #2 */ - - /* Function keys */ - ESCAPE = 256, - ENTER = 257, - TAB = 258, - BACKSPACE = 259, - INSERT = 260, - DELETE = 261, - RIGHT = 262, - LEFT = 263, - DOWN = 264, - UP = 265, - PAGE_UP = 266, - PAGE_DOWN = 267, - HOME = 268, - END = 269, - CAPS_LOCK = 280, - SCROLL_LOCK = 281, - NUM_LOCK = 282, - PRINT_SCREEN = 283, - PAUSE = 284, +//! Enumeration for keyboard key inputs, including printable characters, function keys, and keypad keys. +enum class Keycode { + NONE = 0, //!< No key input. + SPACE = 32, //!< Spacebar. + APOSTROPHE = 39, //!< Apostrophe ('). + COMMA = 44, //!< Comma (,). + MINUS = 45, //!< Minus (-). + PERIOD = 46, //!< Period (.). + SLASH = 47, //!< Slash (/). + D0 = 48, //!< Digit 0. + D1 = 49, //!< Digit 1. + D2 = 50, //!< Digit 2. + D3 = 51, //!< Digit 3. + D4 = 52, //!< Digit 4. + D5 = 53, //!< Digit 5. + D6 = 54, //!< Digit 6. + D7 = 55, //!< Digit 7. + D8 = 56, //!< Digit 8. + D9 = 57, //!< Digit 9. + SEMICOLON = 59, //!< Semicolon (;). + EQUAL = 61, //!< Equal sign (=). + A = 65, //!< Key 'A'. + B = 66, //!< Key 'B'. + C = 67, //!< Key 'C'. + D = 68, //!< Key 'D'. + E = 69, //!< Key 'E'. + F = 70, //!< Key 'F'. + G = 71, //!< Key 'G'. + H = 72, //!< Key 'H'. + I = 73, //!< Key 'I'. + J = 74, //!< Key 'J'. + K = 75, //!< Key 'K'. + L = 76, //!< Key 'L'. + M = 77, //!< Key 'M'. + N = 78, //!< Key 'N'. + O = 79, //!< Key 'O'. + P = 80, //!< Key 'P'. + Q = 81, //!< Key 'Q'. + R = 82, //!< Key 'R'. + S = 83, //!< Key 'S'. + T = 84, //!< Key 'T'. + U = 85, //!< Key 'U'. + V = 86, //!< Key 'V'. + W = 87, //!< Key 'W'. + X = 88, //!< Key 'X'. + Y = 89, //!< Key 'Y'. + Z = 90, //!< Key 'Z'. + LEFT_BRACKET = 91, //!< Left bracket ([). + BACKSLASH = 92, //!< Backslash (\). + RIGHT_BRACKET = 93, //!< Right bracket (]). + GRAVE_ACCENT = 96, //!< Grave accent (`). + WORLD1 = 161, //!< Non-US key #1. + WORLD2 = 162, //!< Non-US key #2. + ESCAPE = 256, //!< Escape key. + ENTER = 257, //!< Enter key. + TAB = 258, //!< Tab key. + BACKSPACE = 259, //!< Backspace key. + INSERT = 260, //!< Insert key. + DELETE = 261, //!< Delete key. + RIGHT = 262, //!< Right arrow key. + LEFT = 263, //!< Left arrow key. + DOWN = 264, //!< Down arrow key. + UP = 265, //!< Up arrow key. + PAGE_UP = 266, //!< Page Up key. + PAGE_DOWN = 267, //!< Page Down key. + HOME = 268, //!< Home key. + END = 269, //!< End key. + CAPS_LOCK = 280, //!< Caps Lock key. + SCROLL_LOCK = 281, //!< Scroll Lock key. + NUM_LOCK = 282, //!< Num Lock key. + PRINT_SCREEN = 283, //!< Print Screen key. + PAUSE = 284, //!< Pause key. + /** + * \name Function keys (F1-F25). + * \{ + */ F1 = 290, F2 = 291, F3 = 292, @@ -114,8 +113,11 @@ enum class Keycode : int { F23 = 312, F24 = 313, F25 = 314, - - /* Keypad */ + /// \} + /** + * \name Keypad digits and operators. + * \{ + */ KP0 = 320, KP1 = 321, KP2 = 322, @@ -133,7 +135,11 @@ enum class Keycode : int { KP_ADD = 334, KP_ENTER = 335, KP_EQUAL = 336, - + /// \} + /** + * \name Modifier keys. + * \{ + */ LEFT_SHIFT = 340, LEFT_CONTROL = 341, LEFT_ALT = 342, @@ -142,5 +148,6 @@ enum class Keycode : int { RIGHT_CONTROL = 345, RIGHT_ALT = 346, RIGHT_SUPER = 347, - MENU = 348 + /// \} + MENU = 348, //!< Menu key. }; diff --git a/src/crepe/api/Script.cpp b/src/crepe/api/Script.cpp new file mode 100644 index 0000000..9ebb074 --- /dev/null +++ b/src/crepe/api/Script.cpp @@ -0,0 +1,11 @@ +#include "Script.h" + +using namespace crepe; + +Script::~Script() { + EventManager & evmgr = *this->event_manager_ref; + for (auto id : this->listeners) { + evmgr.unsubscribe(id); + } +} + diff --git a/src/crepe/api/Script.h b/src/crepe/api/Script.h index 939d142..4d98e4d 100644 --- a/src/crepe/api/Script.h +++ b/src/crepe/api/Script.h @@ -4,6 +4,8 @@ #include "../types.h" +#include "EventManager.h" + namespace crepe { class ScriptSystem; @@ -22,7 +24,11 @@ class ComponentManager; class Script { protected: /** - * \brief Script initialization function + * \name Interface functions + * \{ + */ + /** + * \brief Script initialization function (empty by default) * * This function is called during the ScriptSystem::update() routine *before* * Script::update() if it (a) has not yet been called and (b) the \c BehaviorScript component @@ -30,24 +36,32 @@ protected: */ virtual void init() {} /** - * \brief Script update function + * \brief Script update function (empty by default) * * This function is called during the ScriptSystem::update() routine if the \c BehaviorScript * component holding this script instance is active. */ virtual void update() {} + //! \} + //! ScriptSystem calls \c init() and \c update() friend class crepe::ScriptSystem; protected: /** - * \brief Get single component of type \c T on this game object (utility) + * \name Utility functions + * \{ + */ + + /** + * \brief Get single component of type \c T on this game object * * \tparam T Type of component * * \returns Reference to component * - * \throws nullptr if this game object does not have a component matching type \c T + * \throws std::runtime_error if this game object does not have a component matching type \c + * T */ template <typename T> T & get_component() const; @@ -55,7 +69,7 @@ protected: // cause compile-time errors /** - * \brief Get all components of type \c T on this game object (utility) + * \brief Get all components of type \c T on this game object * * \tparam T Type of component * @@ -63,31 +77,65 @@ protected: */ template <typename T> std::vector<std::reference_wrapper<T>> get_components() const; - + + /** + * \brief Log a message using Log::logf + * + * \tparam Args Log::logf parameters + * \param args Log::logf parameters + */ + template <typename... Args> + void logf(Args &&... args); + + game_object_id_t get_game_object_id() const { return this->game_object_id; }; + /** - * \brief Gets game object id this script is attached to + * \brief Subscribe to an event * - * \returns game object id + * \see EventManager::subscribe */ - game_object_id_t get_game_object_id() const {return this->game_object_id;}; - + template <typename EventType> + void subscribe(const EventHandler<EventType> & callback, event_channel_t channel = EventManager::CHANNEL_ALL); + + //! \} + protected: // NOTE: Script must have a constructor without arguments so the game programmer doesn't need // to manually add `using Script::Script` to their concrete script class. Script() = default; //! Only \c BehaviorScript instantiates Script friend class BehaviorScript; +public: + // std::unique_ptr destroys script + virtual ~Script(); + + Script(const Script &) = delete; + Script(Script &&) = delete; + Script & operator=(const Script &) = delete; + Script & operator=(Script &&) = delete; private: - // These references are set by BehaviorScript immediately after calling the constructor of - // Script. + /** + * \name Late references + * + * These references are set by BehaviorScript immediately after calling the constructor of + * Script. + * + * \{ + */ + //! Game object ID of game object parent BehaviorScript is attached to game_object_id_t game_object_id = -1; + //! Reference to component manager instance ComponentManager * component_manager_ref = nullptr; - // TODO: use OptionalRef instead of pointer + //! Reference to event manager instance + EventManager * event_manager_ref = nullptr; + //! \} private: //! Flag to indicate if \c init() has been called already bool initialized = false; + //! List of subscribed events + std::vector<subscription_t> listeners; }; } // namespace crepe diff --git a/src/crepe/api/Script.hpp b/src/crepe/api/Script.hpp index a064a90..42c8f0b 100644 --- a/src/crepe/api/Script.hpp +++ b/src/crepe/api/Script.hpp @@ -20,9 +20,21 @@ T & Script::get_component() const { template <typename T> std::vector<std::reference_wrapper<T>> Script::get_components() const { - auto & mgr = *this->component_manager_ref; + ComponentManager & mgr = *this->component_manager_ref; return mgr.get_components_by_id<T>(this->game_object_id); } +template <typename... Args> +void Script::logf(Args &&... args) { + Log::logf(std::forward<Args>(args)...); +} + +template <typename EventType> +void Script::subscribe(const EventHandler<EventType> & callback, event_channel_t channel) { + EventManager & mgr = *this->event_manager_ref; + subscription_t listener = mgr.subscribe<EventType>(callback, channel); + this->listeners.push_back(listener); +} + } // namespace crepe diff --git a/src/crepe/api/Texture.cpp b/src/crepe/api/Texture.cpp index de0d0ea..734a5bb 100644 --- a/src/crepe/api/Texture.cpp +++ b/src/crepe/api/Texture.cpp @@ -35,5 +35,5 @@ int Texture::get_width() const { } int Texture::get_height() const { if (this->texture == nullptr) return 0; - return SDLContext::get_instance().get_width(*this); + return SDLContext::get_instance().get_height(*this); } diff --git a/src/crepe/facade/DB.cpp b/src/crepe/facade/DB.cpp index d5d19dc..95cf606 100644 --- a/src/crepe/facade/DB.cpp +++ b/src/crepe/facade/DB.cpp @@ -18,8 +18,8 @@ DB::DB(const string & path) { this->db = {db, [](libdb::DB * db) { db->close(db, 0); }}; // load or create database file - ret = this->db->open(this->db.get(), NULL, path.c_str(), NULL, libdb::DB_BTREE, DB_CREATE, - 0); + const char * file = path.empty() ? NULL : path.c_str(); + ret = this->db->open(this->db.get(), NULL, file, NULL, libdb::DB_BTREE, DB_CREATE, 0); if (ret != 0) throw runtime_error(format("db->open: {}", libdb::db_strerror(ret))); // create cursor diff --git a/src/crepe/facade/DB.h b/src/crepe/facade/DB.h index 629b0eb..115c0f1 100644 --- a/src/crepe/facade/DB.h +++ b/src/crepe/facade/DB.h @@ -22,8 +22,10 @@ class DB { public: /** * \param path The path of the database (created if nonexistant) + * + * \note If \p path is empty, the database is entirely in-memory */ - DB(const std::string & path); + DB(const std::string & path = ""); virtual ~DB() = default; public: diff --git a/src/crepe/facade/SDLContext.cpp b/src/crepe/facade/SDLContext.cpp index 803516e..00523a6 100644 --- a/src/crepe/facade/SDLContext.cpp +++ b/src/crepe/facade/SDLContext.cpp @@ -1,5 +1,6 @@ #include <SDL2/SDL.h> #include <SDL2/SDL_image.h> +#include <SDL2/SDL_keycode.h> #include <SDL2/SDL_rect.h> #include <SDL2/SDL_render.h> #include <SDL2/SDL_surface.h> @@ -7,13 +8,15 @@ #include <cmath> #include <cstddef> #include <functional> -#include <iostream> #include <memory> +#include <stdexcept> #include <string> +#include "../api/Camera.h" #include "../api/Sprite.h" #include "../api/Texture.h" #include "../api/Transform.h" +#include "../api/Vector2.h" #include "../util/Log.h" #include "SDLContext.h" @@ -30,29 +33,22 @@ SDLContext::SDLContext() { dbg_trace(); // FIXME: read window defaults from config manager - if (SDL_Init(SDL_INIT_VIDEO) < 0) { - // FIXME: throw exception - std::cerr << "SDL could not initialize! SDL_Error: " << SDL_GetError() << std::endl; - return; + if (SDL_Init(SDL_INIT_VIDEO) != 0) { + throw runtime_error(format("SDLContext: SDL_Init error: {}", SDL_GetError())); } SDL_Window * tmp_window = SDL_CreateWindow("Crepe Game Engine", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, this->viewport.w, this->viewport.h, 0); if (!tmp_window) { - // FIXME: throw exception - std::cerr << "Window could not be created! SDL_Error: " << SDL_GetError() << std::endl; - return; + throw runtime_error(format("SDLContext: SDL_Window error: {}", SDL_GetError())); } this->game_window = {tmp_window, [](SDL_Window * window) { SDL_DestroyWindow(window); }}; SDL_Renderer * tmp_renderer = SDL_CreateRenderer(this->game_window.get(), -1, SDL_RENDERER_ACCELERATED); if (!tmp_renderer) { - // FIXME: throw exception - std::cerr << "Renderer could not be created! SDL_Error: " << SDL_GetError() - << std::endl; - SDL_DestroyWindow(this->game_window.get()); - return; + throw runtime_error( + format("SDLContext: SDL_CreateRenderer error: {}", SDL_GetError())); } this->game_renderer @@ -60,9 +56,7 @@ SDLContext::SDLContext() { int img_flags = IMG_INIT_PNG; if (!(IMG_Init(img_flags) & img_flags)) { - // FIXME: throw exception - std::cout << "SDL_image could not initialize! SDL_image Error: " << IMG_GetError() - << std::endl; + throw runtime_error("SDLContext: SDL_image could not initialize!"); } } @@ -78,7 +72,6 @@ SDLContext::~SDLContext() { IMG_Quit(); SDL_Quit(); } - void SDLContext::handle_events(bool & running) { //TODO: wouter i need events /* @@ -103,40 +96,63 @@ void SDLContext::handle_events(bool & running) { void SDLContext::clear_screen() { SDL_RenderClear(this->game_renderer.get()); } void SDLContext::present_screen() { SDL_RenderPresent(this->game_renderer.get()); } -void SDLContext::draw(const Sprite & sprite, const Transform & transform, const Camera & cam) { - - SDL_RendererFlip render_flip - = (SDL_RendererFlip) ((SDL_FLIP_HORIZONTAL * sprite.flip.flip_x) - | (SDL_FLIP_VERTICAL * sprite.flip.flip_y)); - - double adjusted_x = (transform.position.x - cam.x -(sprite.sprite_rect.w/2)) * cam.zoom; - double adjusted_y = (transform.position.y - cam.y -(sprite.sprite_rect.h/2)) * cam.zoom; - double adjusted_w = sprite.sprite_rect.w * transform.scale * cam.zoom; - double adjusted_h = sprite.sprite_rect.h * transform.scale * cam.zoom; - - SDL_Rect srcrect = { +SDL_Rect SDLContext::get_src_rect(const Sprite & sprite) const { + return SDL_Rect{ .x = sprite.sprite_rect.x, .y = sprite.sprite_rect.y, .w = sprite.sprite_rect.w, .h = sprite.sprite_rect.h, }; +} +SDL_Rect SDLContext::get_dst_rect(const Sprite & sprite, const Vector2 & pos, + const double & scale, const Camera & cam) const { - SDL_Rect dstrect = { + double adjusted_x = (pos.x - cam.x) * cam.zoom; + double adjusted_y = (pos.y - cam.y) * cam.zoom; + double adjusted_w = sprite.sprite_rect.w * scale * cam.zoom; + double adjusted_h = sprite.sprite_rect.h * scale * cam.zoom; + + return SDL_Rect{ .x = static_cast<int>(adjusted_x), .y = static_cast<int>(adjusted_y), .w = static_cast<int>(adjusted_w), .h = static_cast<int>(adjusted_h), }; +} + +void SDLContext::draw_particle(const Sprite & sprite, const Vector2 & pos, + const double & angle, const double & scale, + const Camera & camera) { + + SDL_RendererFlip render_flip + = (SDL_RendererFlip) ((SDL_FLIP_HORIZONTAL * sprite.flip.flip_x) + | (SDL_FLIP_VERTICAL * sprite.flip.flip_y)); + + SDL_Rect srcrect = this->get_src_rect(sprite); + SDL_Rect dstrect = this->get_dst_rect(sprite, pos, scale, camera); + + SDL_RenderCopyEx(this->game_renderer.get(), sprite.sprite_image->texture.get(), &srcrect, + &dstrect, angle, NULL, render_flip); +} + +void SDLContext::draw(const Sprite & sprite, const Transform & transform, const Camera & cam) { + + SDL_RendererFlip render_flip + = (SDL_RendererFlip) ((SDL_FLIP_HORIZONTAL * sprite.flip.flip_x) + | (SDL_FLIP_VERTICAL * sprite.flip.flip_y)); + + SDL_Rect srcrect = this->get_src_rect(sprite); + SDL_Rect dstrect = this->get_dst_rect(sprite, transform.position, transform.scale, cam); SDL_RenderCopyEx(this->game_renderer.get(), sprite.sprite_image->texture.get(), &srcrect, &dstrect, transform.rotation, NULL, render_flip); } -void SDLContext::camera(const Camera & cam) { +void SDLContext::set_camera(const Camera & cam) { this->viewport.w = static_cast<int>(cam.aspect_width); this->viewport.h = static_cast<int>(cam.aspect_height); - this->viewport.x = static_cast<int>(cam.x) - (SCREEN_WIDTH / 2); - this->viewport.y = static_cast<int>(cam.y) - (SCREEN_HEIGHT / 2); + this->viewport.x = static_cast<int>(cam.x) - (this->viewport.w / 2); + this->viewport.y = static_cast<int>(cam.y) - (this->viewport.h / 2); SDL_SetRenderDrawColor(this->game_renderer.get(), cam.bg_color.r, cam.bg_color.g, cam.bg_color.b, cam.bg_color.a); @@ -148,9 +164,8 @@ std::unique_ptr<SDL_Texture, std::function<void(SDL_Texture *)>> SDLContext::texture_from_path(const std::string & path) { SDL_Surface * tmp = IMG_Load(path.c_str()); - if (tmp == nullptr) { - tmp = IMG_Load("../asset/texture/ERROR.png"); - } + if (tmp == nullptr) + throw runtime_error(format("SDLContext: IMG_Load error: {}", SDL_GetError())); std::unique_ptr<SDL_Surface, std::function<void(SDL_Surface *)>> img_surface; img_surface = {tmp, [](SDL_Surface * surface) { SDL_FreeSurface(surface); }}; @@ -159,7 +174,7 @@ SDLContext::texture_from_path(const std::string & path) { = SDL_CreateTextureFromSurface(this->game_renderer.get(), img_surface.get()); if (tmp_texture == nullptr) { - throw runtime_error(format("Texture cannot be load from {}", path)); + throw runtime_error(format("SDLContext: Texture cannot be load from {}", path)); } std::unique_ptr<SDL_Texture, std::function<void(SDL_Texture *)>> img_texture; diff --git a/src/crepe/facade/SDLContext.h b/src/crepe/facade/SDLContext.h index 652a83e..841ffc9 100644 --- a/src/crepe/facade/SDLContext.h +++ b/src/crepe/facade/SDLContext.h @@ -1,8 +1,10 @@ #pragma once #include <SDL2/SDL_keycode.h> +#include <SDL2/SDL_rect.h> #include <SDL2/SDL_render.h> #include <SDL2/SDL_video.h> +#include <cmath> #include <functional> #include <memory> #include <string> @@ -10,10 +12,7 @@ #include "../api/Sprite.h" #include "../api/Transform.h" #include "api/Camera.h" - -// FIXME: this needs to be removed -const int SCREEN_WIDTH = 640; -const int SCREEN_HEIGHT = 480; +#include "api/Vector2.h" namespace crepe { @@ -121,6 +120,9 @@ private: */ void draw(const Sprite & sprite, const Transform & transform, const Camera & camera); + void draw_particle(const Sprite & sprite, const Vector2 & pos, const double & angle, + const double & scale, const Camera & camera); + //! Clears the screen, preparing for a new frame. void clear_screen(); @@ -128,10 +130,31 @@ private: void present_screen(); /** - * \brief Sets the current camera for rendering. + * \brief sets the background of the camera (will be adjusted in future PR) * \param camera Reference to the Camera object. */ - void camera(const Camera & camera); + void set_camera(const Camera & camera); + +private: + /** + * \brief calculates the sqaure size of the image + * + * \param sprite Reference to the sprite to calculate the rectangle + * \return sdl rectangle to draw a src image + */ + SDL_Rect get_src_rect(const Sprite & sprite) const; + /** + * \brief calculates the sqaure size of the image for an destination + * + * \param sprite Reference to the sprite to calculate the rectangle + * \param pos the pos in pixel positions + * \param scale the multiplier to increase of decrease for the specified sprite + * \param cam Reference to the current camera in the scene to calculate the position based + * on the camera + * \return sdl rectangle to draw a dst image to draw on the screen + */ + SDL_Rect get_dst_rect(const Sprite & sprite, const Vector2 & pos, const double & scale, + const Camera & cam) const; private: //! sdl Window diff --git a/src/crepe/system/CollisionSystem.cpp b/src/crepe/system/CollisionSystem.cpp index 2132b0d..1a30e3f 100644 --- a/src/crepe/system/CollisionSystem.cpp +++ b/src/crepe/system/CollisionSystem.cpp @@ -220,8 +220,8 @@ std::vector<std::pair<CollisionSystem::CollidedInfoStor,CollisionSystem::Collide // Check collision if (check_box_box_collision(boxcolliders[i], boxcolliders[j], transform1, transform2, rigidbody1, rigidbody2)) { collisions_ret.emplace_back(std::make_pair( - CollidedInfoStor{boxcolliders[i], transform1, rigidbody1}, - CollidedInfoStor{boxcolliders[j], transform2, rigidbody2} + CollidedInfoStor{boxcolliders[i], transform1, rigidbody1}, + CollidedInfoStor{boxcolliders[j], transform2, rigidbody2} )); } } diff --git a/src/crepe/system/RenderSystem.cpp b/src/crepe/system/RenderSystem.cpp index 96c5f27..e379771 100644 --- a/src/crepe/system/RenderSystem.cpp +++ b/src/crepe/system/RenderSystem.cpp @@ -1,12 +1,16 @@ #include <algorithm> #include <cassert> +#include <cmath> #include <functional> +#include <iostream> #include <stdexcept> #include <vector> #include "../ComponentManager.h" +#include "../api/ParticleEmitter.h" #include "../api/Sprite.h" #include "../api/Transform.h" +#include "../api/Vector2.h" #include "../facade/SDLContext.h" #include "RenderSystem.h" @@ -25,7 +29,8 @@ void RenderSystem::update_camera() { if (cameras.size() == 0) throw std::runtime_error("No cameras in current scene"); for (Camera & cam : cameras) { - this->context.camera(cam); + if (!cam.active) continue; + this->context.set_camera(cam); this->curr_cam_ref = &cam; } } @@ -38,7 +43,7 @@ bool sorting_comparison(const Sprite & a, const Sprite & b) { } std::vector<std::reference_wrapper<Sprite>> -RenderSystem::sort(std::vector<std::reference_wrapper<Sprite>> & objs) { +RenderSystem::sort(std::vector<std::reference_wrapper<Sprite>> & objs) const { std::vector<std::reference_wrapper<Sprite>> sorted_objs(objs); std::sort(sorted_objs.begin(), sorted_objs.end(), sorting_comparison); @@ -46,20 +51,54 @@ RenderSystem::sort(std::vector<std::reference_wrapper<Sprite>> & objs) { return sorted_objs; } -void RenderSystem::render_sprites() { +void RenderSystem::update() { + this->clear_screen(); + this->update_camera(); + this->render(); + this->present_screen(); +} + +bool RenderSystem::render_particle(const Sprite & sprite, const double & scale) { + + ComponentManager & mgr = this->component_manager; + + vector<reference_wrapper<ParticleEmitter>> emitters + = mgr.get_components_by_id<ParticleEmitter>(sprite.game_object_id); + + bool rendering_particles = false; + + for (const ParticleEmitter & em : emitters) { + if (!(&em.data.sprite == &sprite)) continue; + rendering_particles = true; + if (!em.active) continue; + + for (const Particle & p : em.data.particles) { + if (!p.active) continue; + this->context.draw_particle(sprite, p.position, p.angle, scale, + *this->curr_cam_ref); + } + } + return rendering_particles; +} +void RenderSystem::render_normal(const Sprite & sprite, const Transform & tm) { + this->context.draw(sprite, tm, *this->curr_cam_ref); +} + +void RenderSystem::render() { + ComponentManager & mgr = this->component_manager; vector<reference_wrapper<Sprite>> sprites = mgr.get_components_by_type<Sprite>(); vector<reference_wrapper<Sprite>> sorted_sprites = this->sort(sprites); for (const Sprite & sprite : sorted_sprites) { - auto transforms = mgr.get_components_by_id<Transform>(sprite.game_object_id); - this->context.draw(sprite, transforms[0], *this->curr_cam_ref); - } -} + if (!sprite.active) continue; + const Transform & transform + = mgr.get_components_by_id<Transform>(sprite.game_object_id).front().get(); -void RenderSystem::update() { - this->clear_screen(); - this->update_camera(); - this->render_sprites(); - this->present_screen(); + bool rendered_particles = this->render_particle(sprite, transform.scale); + + if (rendered_particles) continue; + + this->render_normal(sprite, transform); + } } diff --git a/src/crepe/system/RenderSystem.h b/src/crepe/system/RenderSystem.h index 57b9c73..d25a6e3 100644 --- a/src/crepe/system/RenderSystem.h +++ b/src/crepe/system/RenderSystem.h @@ -6,6 +6,7 @@ #include "facade/SDLContext.h" #include "System.h" +#include <cmath> namespace crepe { @@ -38,8 +39,25 @@ private: //! Updates the active camera used for rendering. void update_camera(); - //! Renders all active sprites to the screen. - void render_sprites(); + //! Renders the whole screen + void render(); + + /** + * \brief Renders all the particles on the screen from a given sprite. + * + * \param sprite renders the particles with given texture + * \param tm the Transform component for scale + * \return true if particles have been rendered + */ + bool render_particle(const Sprite & sprite, const double & scale); + + /** + * \brief renders a sprite with a Transform component on the screen + * + * \param sprite the sprite component that holds all the data + * \param tm the Transform component that holds the position,rotation and scale + */ + void render_normal(const Sprite & sprite, const Transform & tm); /** * \brief sort a vector sprite objects with @@ -48,11 +66,10 @@ private: * \return returns a sorted reference vector */ std::vector<std::reference_wrapper<Sprite>> - sort(std::vector<std::reference_wrapper<Sprite>> & objs); + sort(std::vector<std::reference_wrapper<Sprite>> & objs) const; /** * \todo Include color handling for sprites. - * \todo Implement particle emitter rendering with sprites. * \todo Add text rendering using SDL_ttf for text components. * \todo Implement a text component and a button component. * \todo Ensure each sprite is checked for active status before rendering. diff --git a/src/crepe/util/Log.h b/src/crepe/util/Log.h index d55b11e..fc0bb3a 100644 --- a/src/crepe/util/Log.h +++ b/src/crepe/util/Log.h @@ -34,11 +34,11 @@ class Log { public: //! Log message severity enum Level { - TRACE, //< Include (internal) function calls - DEBUG, //< Include dbg_logf output - INFO, //< General-purpose messages - WARNING, //< Non-fatal errors - ERROR, //< Fatal errors + TRACE, //!< Include (internal) function calls + DEBUG, //!< Include dbg_logf output + INFO, //!< General-purpose messages + WARNING, //!< Non-fatal errors + ERROR, //!< Fatal errors }; /** diff --git a/src/crepe/util/Proxy.h b/src/crepe/util/Proxy.h index b34f7c6..789144a 100644 --- a/src/crepe/util/Proxy.h +++ b/src/crepe/util/Proxy.h @@ -16,6 +16,8 @@ template <typename T> class Proxy { public: //! Set operator + Proxy & operator=(Proxy &); + //! Set operator Proxy & operator=(const T &); //! Get operator operator const T &(); diff --git a/src/crepe/util/Proxy.hpp b/src/crepe/util/Proxy.hpp index b9923db..ef2b69f 100644 --- a/src/crepe/util/Proxy.hpp +++ b/src/crepe/util/Proxy.hpp @@ -14,6 +14,12 @@ Proxy<T> & Proxy<T>::operator=(const T & val) { } template <typename T> +Proxy<T> & Proxy<T>::operator=(Proxy & proxy) { + this->broker.set(T(proxy)); + return *this; +} + +template <typename T> Proxy<T>::operator const T &() { return this->broker.get(); } diff --git a/src/example/CMakeLists.txt b/src/example/CMakeLists.txt index 045f4d4..dd308e4 100644 --- a/src/example/CMakeLists.txt +++ b/src/example/CMakeLists.txt @@ -16,15 +16,11 @@ function(add_example target_name) add_dependencies(examples ${target_name}) endfunction() -add_example(audio_internal) -add_example(script) -add_example(log) add_example(rendering) add_example(asset_manager) add_example(savemgr) -add_example(proxy) -add_example(db) add_example(game) add_example(events) +add_example(rendering_particle) add_example(gameloop) diff --git a/src/example/audio_internal.cpp b/src/example/audio_internal.cpp deleted file mode 100644 index 661161a..0000000 --- a/src/example/audio_internal.cpp +++ /dev/null @@ -1,56 +0,0 @@ -/** \file - * - * Standalone example for usage of the internal \c Sound class. - */ - -#include <crepe/api/Config.h> -#include <crepe/facade/Sound.h> -#include <crepe/util/Log.h> - -#include <thread> - -using namespace crepe; -using namespace std; -using namespace std::chrono_literals; -using std::make_unique; - -// Unrelated stuff that is not part of this POC -int _ = []() { - // Show dbg_trace() output - auto & cfg = Config::get_instance(); - cfg.log.level = Log::Level::TRACE; - - return 0; // satisfy compiler -}(); - -int main() { - // Load a background track (Ogg Vorbis) - auto bgm = Sound("../mwe/audio/bgm.ogg"); - // Load three short samples (WAV) - auto sfx1 = Sound("../mwe/audio/sfx1.wav"); - auto sfx2 = Sound("../mwe/audio/sfx2.wav"); - auto sfx3 = Sound("../mwe/audio/sfx3.wav"); - - // Start the background track - bgm.play(); - - // Play each sample sequentially while pausing and resuming the background track - this_thread::sleep_for(500ms); - sfx1.play(); - this_thread::sleep_for(500ms); - sfx2.play(); - bgm.pause(); - this_thread::sleep_for(500ms); - sfx3.play(); - bgm.play(); - this_thread::sleep_for(500ms); - - // Play all samples simultaniously - sfx1.play(); - sfx2.play(); - sfx3.play(); - this_thread::sleep_for(1000ms); - - // Stop all audio and exit - return EXIT_SUCCESS; -} diff --git a/src/example/db.cpp b/src/example/db.cpp deleted file mode 100644 index ee4e8fc..0000000 --- a/src/example/db.cpp +++ /dev/null @@ -1,30 +0,0 @@ -#include <crepe/api/Config.h> -#include <crepe/facade/DB.h> -#include <crepe/util/Log.h> - -using namespace crepe; -using namespace std; - -// run before main -static auto _ = []() { - auto & cfg = Config::get_instance(); - cfg.log.level = Log::Level::TRACE; - return 0; -}(); - -int main() { - dbg_trace(); - - DB db("file.db"); - - const char * test_key = "test-key"; - string test_data = "Hello world!"; - - dbg_logf("DB has key = {}", db.has(test_key)); - - db.set(test_key, test_data); - - dbg_logf("key = \"{}\"", db.get(test_key)); - - return 0; -} diff --git a/src/example/log.cpp b/src/example/log.cpp deleted file mode 100644 index 5baa021..0000000 --- a/src/example/log.cpp +++ /dev/null @@ -1,28 +0,0 @@ -/** \file - * - * Standalone example for usage of the logging functions - */ - -#include <crepe/api/Config.h> -#include <crepe/util/Log.h> - -using namespace crepe; - -// unrelated setup code -int _ = []() { - // make sure all log messages get printed - auto & cfg = Config::get_instance(); - cfg.log.level = Log::Level::TRACE; - - return 0; // satisfy compiler -}(); - -int main() { - dbg_trace(); - dbg_log("debug message"); - Log::logf("info message with variable: {}", 3); - Log::logf(Log::Level::WARNING, "warning"); - Log::logf(Log::Level::ERROR, "error"); - - return 0; -} diff --git a/src/example/proxy.cpp b/src/example/proxy.cpp deleted file mode 100644 index 69451f8..0000000 --- a/src/example/proxy.cpp +++ /dev/null @@ -1,45 +0,0 @@ -/** \file - * - * Standalone example for usage of the proxy type - */ - -#include <crepe/ValueBroker.h> -#include <crepe/api/Config.h> -#include <crepe/util/Log.h> -#include <crepe/util/Proxy.h> - -using namespace std; -using namespace crepe; - -void test_ro_ref(const int & val) {} -void test_rw_ref(int & val) {} -void test_ro_val(int val) {} - -int main() { - auto & cfg = Config::get_instance(); - cfg.log.level = Log::Level::DEBUG; - - int real_value = 0; - - ValueBroker<int> broker{ - [&real_value](const int & target) { - dbg_logf("set {} to {}", real_value, target); - real_value = target; - }, - [&real_value]() -> const int & { - dbg_logf("get {}", real_value); - return real_value; - }, - }; - - Proxy<int> proxy{broker}; - - broker.set(54); - proxy = 84; - - test_ro_ref(proxy); // this is allowed - // test_rw_ref(proxy); // this should throw a compile error - test_ro_val(proxy); - - return 0; -} diff --git a/src/example/rendering_particle.cpp b/src/example/rendering_particle.cpp new file mode 100644 index 0000000..4571afb --- /dev/null +++ b/src/example/rendering_particle.cpp @@ -0,0 +1,71 @@ +#include "api/Camera.h" +#include "system/ParticleSystem.h" +#include <SDL2/SDL_timer.h> +#include <crepe/ComponentManager.h> + +#include <crepe/Component.h> +#include <crepe/api/Color.h> +#include <crepe/api/GameObject.h> +#include <crepe/api/ParticleEmitter.h> +#include <crepe/api/Rigidbody.h> +#include <crepe/api/Sprite.h> +#include <crepe/api/Texture.h> +#include <crepe/api/Transform.h> +#include <crepe/api/Vector2.h> +#include <crepe/system/RenderSystem.h> + +#include <chrono> +#include <iostream> +#include <memory> + +using namespace crepe; +using namespace std; + +int main(int argc, char * argv[]) { + ComponentManager mgr; + GameObject game_object = mgr.new_object("", "", Vector2{100, 100}, 0, 0.1); + RenderSystem sys{mgr}; + ParticleSystem psys{mgr}; + + Color color(255, 255, 255, 255); + + Sprite & test_sprite = game_object.add_component<Sprite>( + make_shared<Texture>("../asset/texture/img.png"), color, FlipSettings{false, false}); + test_sprite.order_in_layer = 5; + + auto & test = game_object.add_component<ParticleEmitter>(ParticleEmitter::Data{ + .position = {0, 0}, + .max_particles = 10, + .emission_rate = 0.1, + .min_speed = 6, + .max_speed = 20, + .min_angle = -20, + .max_angle = 20, + .begin_lifespan = 0, + .end_lifespan = 60, + .force_over_time = Vector2{0, 0}, + .boundary{ + .width = 1000, + .height = 1000, + .offset = Vector2{0, 0}, + .reset_on_exit = false, + }, + .sprite = test_sprite, + }); + game_object.add_component<Camera>(Color::WHITE); + + game_object + .add_component<Sprite>(make_shared<Texture>("../asset/texture/img.png"), color, + FlipSettings{false, false}) + .order_in_layer + = 6; + + auto start = std::chrono::steady_clock::now(); + while (std::chrono::steady_clock::now() - start < std::chrono::seconds(5)) { + psys.update(); + sys.update(); + SDL_Delay(10); + } + + return 0; +} diff --git a/src/example/script.cpp b/src/example/script.cpp deleted file mode 100644 index a23295b..0000000 --- a/src/example/script.cpp +++ /dev/null @@ -1,49 +0,0 @@ -/** \file - * - * Standalone example for usage of the script component and system - */ - -#include <crepe/ComponentManager.h> -#include <crepe/system/ScriptSystem.h> -#include <crepe/util/Log.h> - -#include <crepe/api/BehaviorScript.h> -#include <crepe/api/Config.h> -#include <crepe/api/GameObject.h> -#include <crepe/api/Script.h> -#include <crepe/api/Transform.h> - -using namespace crepe; -using namespace std; - -// Unrelated stuff that is not part of this POC -int _ = []() { - // Show dbg_trace() output - auto & cfg = Config::get_instance(); - cfg.log.level = Log::Level::TRACE; - - return 0; // satisfy compiler -}(); - -// User-defined script: -class MyScript : public Script { - void update() { - // Retrieve component from the same GameObject this script is on - Transform & test = get_component<Transform>(); - dbg_logf("Transform({:.2f}, {:.2f})", test.position.x, test.position.y); - } -}; - -int main() { - ComponentManager component_manager{}; - ScriptSystem system{component_manager}; - - // Create game object with Transform and BehaviorScript components - GameObject obj = component_manager.new_object("name"); - obj.add_component<BehaviorScript>().set_script<MyScript>(); - - // Update all scripts. This should result in MyScript::update being called - system.update(); - - return EXIT_SUCCESS; -} diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index 14c5123..3eee466 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -6,5 +6,8 @@ target_sources(test_main PUBLIC ParticleTest.cpp ECSTest.cpp SceneManagerTest.cpp + EventTest.cpp + ValueBrokerTest.cpp + DBTest.cpp ) diff --git a/src/test/DBTest.cpp b/src/test/DBTest.cpp new file mode 100644 index 0000000..e80814c --- /dev/null +++ b/src/test/DBTest.cpp @@ -0,0 +1,28 @@ +#include <crepe/facade/DB.h> +#include <gtest/gtest.h> + +using namespace std; +using namespace crepe; +using namespace testing; + +class DBTest : public Test { +public: + DB db; +}; + +TEST_F(DBTest, ReadWrite) { + db.set("foo", "bar"); + EXPECT_EQ(db.get("foo"), "bar"); +} + +TEST_F(DBTest, Nonexistant) { + EXPECT_THROW(db.get("foo"), std::out_of_range); + db.set("foo", "bar"); + EXPECT_NO_THROW(db.get("foo")); +} + +TEST_F(DBTest, Has) { + EXPECT_EQ(db.has("foo"), false); + db.set("foo", "bar"); + EXPECT_EQ(db.has("foo"), true); +} diff --git a/src/test/EventTest.cpp b/src/test/EventTest.cpp new file mode 100644 index 0000000..b0e6c9c --- /dev/null +++ b/src/test/EventTest.cpp @@ -0,0 +1,254 @@ + +#include "api/Event.h" +#include "api/EventManager.h" +#include "api/IKeyListener.h" +#include "api/IMouseListener.h" +#include <gmock/gmock.h> +#include <gtest/gtest.h> +using namespace std; +using namespace std::chrono_literals; +using namespace crepe; + +class EventManagerTest : public ::testing::Test { +protected: + void SetUp() override { + // Clear any existing subscriptions or events before each test + EventManager::get_instance().clear(); + } + + void TearDown() override { + // Ensure cleanup after each test + EventManager::get_instance().clear(); + } +}; +class MockKeyListener : public IKeyListener { +public: + MOCK_METHOD(bool, on_key_pressed, (const KeyPressEvent & event), (override)); + MOCK_METHOD(bool, on_key_released, (const KeyReleaseEvent & event), (override)); +}; + +class MockMouseListener : public IMouseListener { +public: + MOCK_METHOD(bool, on_mouse_clicked, (const MouseClickEvent & event), (override)); + MOCK_METHOD(bool, on_mouse_pressed, (const MousePressEvent & event), (override)); + MOCK_METHOD(bool, on_mouse_released, (const MouseReleaseEvent & event), (override)); + MOCK_METHOD(bool, on_mouse_moved, (const MouseMoveEvent & event), (override)); +}; + +TEST_F(EventManagerTest, EventSubscription) { + EventHandler<KeyPressEvent> key_handler = [](const KeyPressEvent & e) { + std::cout << "Key Event Triggered" << std::endl; + return true; + }; + + // Subscribe to KeyPressEvent + EventManager::get_instance().subscribe<KeyPressEvent>(key_handler, 1); + + // Verify subscription (not directly verifiable; test by triggering event) + + EventManager::get_instance().trigger_event<KeyPressEvent>( + KeyPressEvent{ + .repeat = true, + .key = Keycode::A, + }, + 1); + EventManager::get_instance().trigger_event<KeyPressEvent>( + KeyPressEvent{ + .repeat = true, + .key = Keycode::A, + + }, + EventManager::CHANNEL_ALL); +} +TEST_F(EventManagerTest, EventManagerTest_trigger_all_channels) { + bool triggered = false; + + EventHandler<MouseClickEvent> mouse_handler = [&](const MouseClickEvent & e) { + triggered = true; + EXPECT_EQ(e.mouse_x, 100); + EXPECT_EQ(e.mouse_y, 200); + EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE); + return false; + }; + EventManager::get_instance().subscribe<MouseClickEvent>(mouse_handler, + EventManager::CHANNEL_ALL); + + MouseClickEvent click_event{ + .mouse_x = 100, .mouse_y = 200, .button = MouseButton::LEFT_MOUSE}; + EventManager::get_instance().trigger_event<MouseClickEvent>(click_event, + EventManager::CHANNEL_ALL); + + EXPECT_TRUE(triggered); +} +TEST_F(EventManagerTest, EventManagerTest_trigger_one_channel) { + bool triggered = false; + int test_channel = 1; + EventHandler<MouseClickEvent> mouse_handler = [&](const MouseClickEvent & e) { + triggered = true; + EXPECT_EQ(e.mouse_x, 100); + EXPECT_EQ(e.mouse_y, 200); + EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE); + return false; + }; + EventManager::get_instance().subscribe<MouseClickEvent>(mouse_handler, test_channel); + + MouseClickEvent click_event{ + .mouse_x = 100, .mouse_y = 200, .button = MouseButton::LEFT_MOUSE}; + EventManager::get_instance().trigger_event<MouseClickEvent>(click_event, + EventManager::CHANNEL_ALL); + + EXPECT_FALSE(triggered); + EventManager::get_instance().trigger_event<MouseClickEvent>(click_event, test_channel); +} + +TEST_F(EventManagerTest, EventManagerTest_callback_propagation) { + EventManager & event_manager = EventManager::get_instance(); + + // Flags to track handler calls + bool triggered_true = false; + bool triggered_false = false; + + // Handlers + EventHandler<MouseClickEvent> mouse_handler_true = [&](const MouseClickEvent & e) { + triggered_true = true; + EXPECT_EQ(e.mouse_x, 100); + EXPECT_EQ(e.mouse_y, 200); + EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE); + return true; // Stops propagation + }; + + EventHandler<MouseClickEvent> mouse_handler_false = [&](const MouseClickEvent & e) { + triggered_false = true; + EXPECT_EQ(e.mouse_x, 100); + EXPECT_EQ(e.mouse_y, 200); + EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE); + return false; // Allows propagation + }; + + // Test event + MouseClickEvent click_event{ + .mouse_x = 100, .mouse_y = 200, .button = MouseButton::LEFT_MOUSE}; + event_manager.subscribe<MouseClickEvent>(mouse_handler_true, EventManager::CHANNEL_ALL); + event_manager.subscribe<MouseClickEvent>(mouse_handler_false, EventManager::CHANNEL_ALL); + + // Trigger event + event_manager.trigger_event<MouseClickEvent>(click_event, EventManager::CHANNEL_ALL); + + // Check that only the true handler was triggered + EXPECT_TRUE(triggered_true); + EXPECT_FALSE(triggered_false); + + // Reset and clear + triggered_true = false; + triggered_false = false; + event_manager.clear(); + event_manager.subscribe<MouseClickEvent>(mouse_handler_false, EventManager::CHANNEL_ALL); + event_manager.subscribe<MouseClickEvent>(mouse_handler_true, EventManager::CHANNEL_ALL); + + // Trigger event again + event_manager.trigger_event<MouseClickEvent>(click_event, EventManager::CHANNEL_ALL); + + // Check that both handlers were triggered + EXPECT_TRUE(triggered_true); + EXPECT_TRUE(triggered_false); +} + +TEST_F(EventManagerTest, EventManagerTest_queue_dispatch) { + EventManager & event_manager = EventManager::get_instance(); + bool triggered1 = false; + bool triggered2 = false; + int test_channel = 1; + EventHandler<MouseClickEvent> mouse_handler1 = [&](const MouseClickEvent & e) { + triggered1 = true; + EXPECT_EQ(e.mouse_x, 100); + EXPECT_EQ(e.mouse_y, 200); + EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE); + return false; // Allows propagation + }; + EventHandler<MouseClickEvent> mouse_handler2 = [&](const MouseClickEvent & e) { + triggered2 = true; + EXPECT_EQ(e.mouse_x, 100); + EXPECT_EQ(e.mouse_y, 200); + EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE); + return false; // Allows propagation + }; + event_manager.subscribe<MouseClickEvent>(mouse_handler1); + event_manager.subscribe<MouseClickEvent>(mouse_handler2, test_channel); + + event_manager.queue_event<MouseClickEvent>( + MouseClickEvent{.mouse_x = 100, .mouse_y = 200, .button = MouseButton::LEFT_MOUSE}); + event_manager.queue_event<MouseClickEvent>( + MouseClickEvent{.mouse_x = 100, .mouse_y = 200, .button = MouseButton::LEFT_MOUSE}, + test_channel); + event_manager.dispatch_events(); + EXPECT_TRUE(triggered1); + EXPECT_TRUE(triggered2); +} + +TEST_F(EventManagerTest, EventManagerTest_unsubscribe) { + EventManager & event_manager = EventManager::get_instance(); + + // Flags to track if handlers are triggered + bool triggered1 = false; + bool triggered2 = false; + + // Define EventHandlers + EventHandler<MouseClickEvent> mouse_handler1 = [&](const MouseClickEvent & e) { + triggered1 = true; + EXPECT_EQ(e.mouse_x, 100); + EXPECT_EQ(e.mouse_y, 200); + EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE); + return false; // Allows propagation + }; + + EventHandler<MouseClickEvent> mouse_handler2 = [&](const MouseClickEvent & e) { + triggered2 = true; + EXPECT_EQ(e.mouse_x, 100); + EXPECT_EQ(e.mouse_y, 200); + EXPECT_EQ(e.button, MouseButton::LEFT_MOUSE); + return false; // Allows propagation + }; + // Subscribe handlers + subscription_t handler1_id = event_manager.subscribe<MouseClickEvent>(mouse_handler1); + subscription_t handler2_id = event_manager.subscribe<MouseClickEvent>(mouse_handler2); + + // Queue events + event_manager.queue_event<MouseClickEvent>( + MouseClickEvent{.mouse_x = 100, .mouse_y = 200, .button = MouseButton::LEFT_MOUSE}); + + // Dispatch events - both handlers should be triggered + event_manager.dispatch_events(); + EXPECT_TRUE(triggered1); // Handler 1 should be triggered + EXPECT_TRUE(triggered2); // Handler 2 should be triggered + + // Reset flags + triggered1 = false; + triggered2 = false; + + // Unsubscribe handler1 + event_manager.unsubscribe(handler1_id); + + // Queue the same event again + event_manager.queue_event<MouseClickEvent>( + MouseClickEvent{.mouse_x = 100, .mouse_y = 200, .button = MouseButton::LEFT_MOUSE}); + + // Dispatch events - only handler 2 should be triggered, handler 1 should NOT + event_manager.dispatch_events(); + EXPECT_FALSE(triggered1); // Handler 1 should NOT be triggered + EXPECT_TRUE(triggered2); // Handler 2 should be triggered + + // Reset flags + triggered2 = false; + + // Unsubscribe handler2 + event_manager.unsubscribe(handler2_id); + + // Queue the event again + event_manager.queue_event<MouseClickEvent>( + MouseClickEvent{.mouse_x = 100, .mouse_y = 200, .button = MouseButton::LEFT_MOUSE}); + + // Dispatch events - no handler should be triggered + event_manager.dispatch_events(); + EXPECT_FALSE(triggered1); // Handler 1 should NOT be triggered + EXPECT_FALSE(triggered2); // Handler 2 should NOT be triggered +} diff --git a/src/test/ParticleTest.cpp b/src/test/ParticleTest.cpp index 4e655a9..d9bbba0 100644 --- a/src/test/ParticleTest.cpp +++ b/src/test/ParticleTest.cpp @@ -28,7 +28,7 @@ public: GameObject game_object = mgr.new_object("", "", Vector2{0, 0}, 0, 0); Color color(0, 0, 0, 0); - Sprite test_sprite = game_object.add_component<Sprite>( + Sprite & test_sprite = game_object.add_component<Sprite>( make_shared<Texture>("../asset/texture/img.png"), color, FlipSettings{true, true}); diff --git a/src/test/ValueBrokerTest.cpp b/src/test/ValueBrokerTest.cpp new file mode 100644 index 0000000..e6bb058 --- /dev/null +++ b/src/test/ValueBrokerTest.cpp @@ -0,0 +1,63 @@ +#include <gtest/gtest.h> + +#include <crepe/ValueBroker.h> +#include <crepe/util/Proxy.h> + +using namespace std; +using namespace crepe; +using namespace testing; + +class ValueBrokerTest : public Test { +public: + int read_count = 0; + int write_count = 0; + int value = 0; + + ValueBroker<int> broker{ + [this](const int & target) -> void { + this->write_count++; + this->value = target; + }, + [this]() -> const int & { + this->read_count++; + return this->value; + }, + }; + Proxy<int> proxy{broker}; + + void SetUp() override { + ASSERT_EQ(read_count, 0); + ASSERT_EQ(write_count, 0); + } +}; + +TEST_F(ValueBrokerTest, BrokerWrite) { + broker.set(0); + EXPECT_EQ(read_count, 0); + EXPECT_EQ(write_count, 1); +} + +TEST_F(ValueBrokerTest, BrokerRead) { + broker.get(); + EXPECT_EQ(read_count, 1); + EXPECT_EQ(write_count, 0); +} + +TEST_F(ValueBrokerTest, ProxyWrite) { + proxy = 0; + EXPECT_EQ(read_count, 0); + EXPECT_EQ(write_count, 1); +} + +void dummy(int) {} +TEST_F(ValueBrokerTest, ProxyRead) { + dummy(proxy); + EXPECT_EQ(read_count, 1); + EXPECT_EQ(write_count, 0); +} + +TEST_F(ValueBrokerTest, ProxyReadWrite) { + proxy = proxy; + ASSERT_EQ(read_count, 1); + ASSERT_EQ(write_count, 1); +} |