Browse Source

Add subscription logic for EventManager class

Patrick-Christopher Mattulat 9 months ago
parent
commit
c453d08194

+ 12 - 3
include/ls-std/event/reworked/EventListener.hpp

@@ -3,7 +3,7 @@
 * Company:         Lynar Studios
 * E-Mail:          webmaster@lynarstudios.com
 * Created:         2024-05-16
-* Changed:         2024-05-16
+* Changed:         2024-05-17
 *
 * */
 
@@ -16,14 +16,23 @@
 
 namespace ls::std::event::reworked
 {
-  class EventListener : public ls::std::core::Class
+  class EventListener : private ::std::enable_shared_from_this<EventListener>, public ls::std::core::Class
   {
     public:
 
       explicit EventListener();
       ~EventListener() noexcept override;
 
-      void subscribe(const ls::std::event::reworked::Event &_event, const ls::std::event::reworked::type::EventAction &_action);
+      [[nodiscard]] ls::std::event::reworked::type::listener_id getId() const;
+      void setId(ls::std::event::reworked::type::listener_id _id);
+      [[maybe_unused]] bool subscribe(const ls::std::event::reworked::Event &_event, const ls::std::event::reworked::type::event_action &_action);
+
+    private:
+
+      ls::std::event::reworked::type::listener_id id{};
+
+      void _requestListenerId(const ::std::shared_ptr<ls::std::core::Class> &_manager);
+      [[nodiscard]] bool _subscribe(const ls::std::event::reworked::Event &_event, const ls::std::event::reworked::type::event_action &_action);
   };
 }
 

+ 24 - 1
include/ls-std/event/reworked/EventManager.hpp

@@ -3,7 +3,7 @@
 * Company:         Lynar Studios
 * E-Mail:          webmaster@lynarstudios.com
 * Created:         2024-05-16
-* Changed:         2024-05-16
+* Changed:         2024-05-17
 *
 * */
 
@@ -11,7 +11,18 @@
 #define LS_STD_EVENT_MANAGER_REWORKED_HPP
 
 #include "Event.hpp"
+#include "EventListener.hpp"
+#include <list>
 #include <ls-std/core/Class.hpp>
+#include <ls-std/event/reworked/type/EventTypes.hpp>
+#include <memory>
+#include <unordered_map>
+
+namespace ls::std::event::reworked::type
+{
+  using event_listeners = ::std::list<::std::pair<::std::shared_ptr<ls::std::event::reworked::EventListener>, ls::std::event::reworked::type::event_action>>;
+  using event_observability_inventory = ::std::unordered_map<::std::string, ls::std::event::reworked::type::event_listeners>;
+}
 
 namespace ls::std::event::reworked
 {
@@ -22,7 +33,19 @@ namespace ls::std::event::reworked
       explicit EventManager();
       ~EventManager() noexcept override;
 
+      [[nodiscard]] ls::std::event::reworked::type::listener_id getNextProvisionId() const;
+      [[nodiscard]] bool holdsListenerForEvent(ls::std::event::reworked::type::listener_id _id, const ls::std::event::reworked::Event &_event);
       void invoke(const ls::std::event::reworked::Event &_event) const;
+      [[nodiscard]] ls::std::event::reworked::type::listener_id requestListenerId();
+      void subscribeListenerForEvent(::std::shared_ptr<ls::std::event::reworked::EventListener> _listener, const ls::std::event::reworked::Event &_event, ls::std::event::reworked::type::event_action _action);
+
+    private:
+
+      ls::std::event::reworked::type::event_observability_inventory inventory{};
+      ls::std::event::reworked::type::listener_id provisionId = 1;
+
+      static void _notifyListeners(const ls::std::event::reworked::type::event_listeners &_listeners);
+      bool _observesEvent(const ls::std::event::reworked::Event &_event) const;
   };
 }
 

+ 4 - 2
include/ls-std/event/reworked/type/EventTypes.hpp

@@ -3,18 +3,20 @@
 * Company:         Lynar Studios
 * E-Mail:          webmaster@lynarstudios.com
 * Created:         2024-05-16
-* Changed:         2024-05-16
+* Changed:         2024-05-17
 *
 * */
 
 #ifndef LS_STD_EVENT_TYPES_REWORKED_HPP
 #define LS_STD_EVENT_TYPES_REWORKED_HPP
 
+#include <cstdint>
 #include <functional>
 
 namespace ls::std::event::reworked::type
 {
-  using EventAction = ::std::function<void()>;
+  using event_action = ::std::function<void()>;
+  using listener_id = uint32_t;
 }
 
 #endif

+ 47 - 4
source/ls-std/event/reworked/EventListener.cpp

@@ -3,23 +3,66 @@
 * Company:         Lynar Studios
 * E-Mail:          webmaster@lynarstudios.com
 * Created:         2024-05-16
-* Changed:         2024-05-16
+* Changed:         2024-05-17
 *
 * */
 
+#include <ls-std/core/evaluator/NullPointerEvaluator.hpp>
 #include <ls-std/event/reworked/EventListener.hpp>
+#include <ls-std/event/reworked/EventManager.hpp>
 
 using ls::std::core::Class;
+using ls::std::core::NullPointerEvaluator;
 using ls::std::event::reworked::Event;
 using ls::std::event::reworked::EventListener;
-using ls::std::event::reworked::type::EventAction;
+using ls::std::event::reworked::EventManager;
+using ls::std::event::reworked::type::event_action;
+using ls::std::event::reworked::type::listener_id;
+using ::std::dynamic_pointer_cast;
+using ::std::shared_ptr;
 
 EventListener::EventListener() : Class("EventListener")
 {}
 
 EventListener::~EventListener() noexcept = default;
 
-void EventListener::subscribe(const Event &_event, const EventAction &_action)
+listener_id EventListener::getId() const
 {
-  // implement
+  return this->id;
+}
+
+void EventListener::setId(listener_id _id)
+{
+  this->id = _id;
+}
+
+bool EventListener::subscribe(const Event &_event, const event_action &_action)
+{
+  NullPointerEvaluator(_event.getManager(), "no event manager is provided for " + _event.getClassName()).evaluate();
+  this->_requestListenerId(_event.getManager());
+
+  return this->_subscribe(_event, _action);
+}
+
+void EventListener::_requestListenerId(const shared_ptr<Class> &_manager)
+{
+  shared_ptr<EventManager> manager = dynamic_pointer_cast<EventManager>(_manager);
+
+  if (this->id == 0)
+  {
+    this->setId(manager->requestListenerId());
+  }
+}
+
+bool EventListener::_subscribe(const Event &_event, const event_action &_action)
+{
+  bool subscribed{};
+
+  if (shared_ptr<EventManager> manager = dynamic_pointer_cast<EventManager>(_event.getManager()); !manager->holdsListenerForEvent(this->id, _event))
+  {
+    manager->subscribeListenerForEvent(shared_from_this(), _event, _action);
+    subscribed = manager->holdsListenerForEvent(this->id, _event);
+  }
+
+  return subscribed;
 }

+ 80 - 2
source/ls-std/event/reworked/EventManager.cpp

@@ -3,22 +3,100 @@
 * Company:         Lynar Studios
 * E-Mail:          webmaster@lynarstudios.com
 * Created:         2024-05-16
-* Changed:         2024-05-16
+* Changed:         2024-05-17
 *
 * */
 
+#include <ls-std/core/exception/EventNotHandledException.hpp>
+#include <ls-std/event/reworked/EventListener.hpp>
 #include <ls-std/event/reworked/EventManager.hpp>
+#include <utility>
 
 using ls::std::core::Class;
+using ls::std::core::EventNotHandledException;
 using ls::std::event::reworked::Event;
+using ls::std::event::reworked::EventListener;
 using ls::std::event::reworked::EventManager;
+using ls::std::event::reworked::type::event_action;
+using ls::std::event::reworked::type::event_listeners;
+using ls::std::event::reworked::type::listener_id;
+using ::std::make_pair;
+using ::std::move;
+using ::std::shared_ptr;
 
 EventManager::EventManager() : Class("EventManager")
 {}
 
 EventManager::~EventManager() noexcept = default;
 
+listener_id EventManager::getNextProvisionId() const
+{
+  return this->provisionId;
+}
+
+bool EventManager::holdsListenerForEvent(listener_id _id, const Event &_event)
+{
+  bool holdsListener{};
+
+  if (this->_observesEvent(_event))
+  {
+    for (const auto &[listener, eventAction] : this->inventory[_event.getClassName()])
+    {
+      holdsListener = listener->getId() == _id;
+
+      if (holdsListener)
+      {
+        break;
+      }
+    }
+  }
+
+  return holdsListener;
+}
+
 void EventManager::invoke(const Event &_event) const
 {
-  // implement
+  if (!this->_observesEvent(_event))
+  {
+    throw EventNotHandledException("event " + _event.getClassName() + " is not known by event manager");
+  }
+  else
+  {
+    for (const auto &[eventName, listeners] : this->inventory)
+    {
+      EventManager::_notifyListeners(listeners);
+    }
+  }
+}
+
+listener_id EventManager::requestListenerId()
+{
+  listener_id providedId = this->provisionId;
+  ++this->provisionId;
+
+  return providedId;
+}
+
+void EventManager::subscribeListenerForEvent(shared_ptr<EventListener> _listener, const Event &_event, event_action _action)
+{
+  if (!this->_observesEvent(_event))
+  {
+    this->inventory[_event.getClassName()] = {};
+  }
+
+  auto inventoryEntry = make_pair<shared_ptr<EventListener>, event_action>(::move(_listener), ::move(_action));
+  this->inventory[_event.getClassName()].push_back(inventoryEntry);
+}
+
+void EventManager::_notifyListeners(const event_listeners &_listeners)
+{
+  for (const auto &[listener, eventAction] : _listeners)
+  {
+    eventAction();
+  }
+}
+
+bool EventManager::_observesEvent(const Event &_event) const
+{
+  return this->inventory.find(_event.getClassName()) != this->inventory.end();
 }

+ 45 - 1
test/cases/event/reworked/EventListenerTest.cpp

@@ -3,14 +3,22 @@
 * Company:         Lynar Studios
 * E-Mail:          webmaster@lynarstudios.com
 * Created:         2024-05-16
-* Changed:         2024-05-16
+* Changed:         2024-05-17
 *
 * */
 
 #include <gtest/gtest.h>
+#include <ls-std-event-test.hpp>
+#include <ls-std/ls-std-core.hpp>
 #include <ls-std/ls-std-event.hpp>
+#include <memory>
 
+using ls::std::core::NullPointerException;
 using ls::std::event::reworked::EventListener;
+using ::std::make_shared;
+using ::std::string;
+using test::event::Button;
+using test::event::OnClickEvent;
 using testing::Test;
 
 namespace
@@ -27,4 +35,40 @@ namespace
   {
     ASSERT_STREQ("EventListener", EventListener().getClassName().c_str());
   }
+
+  TEST_F(EventListenerTest, getId)
+  {
+    ASSERT_EQ(0, EventListener().getId());
+  }
+
+  TEST_F(EventListenerTest, setId)
+  {
+    auto listener = EventListener();
+
+    ASSERT_EQ(0, listener.getId());
+    listener.setId(17);
+    ASSERT_EQ(17, listener.getId());
+  }
+
+  TEST_F(EventListenerTest, subscribe_with_missing_event_manager)
+  {
+    auto myButton = make_shared<Button>();
+
+    EXPECT_THROW(
+        {
+          try
+          {
+            myButton->subscribe(OnClickEvent(), [myButton]() mutable { myButton->onClickEvent(); });
+          }
+          catch (const NullPointerException &_exception)
+          {
+            string actual = _exception.what();
+            string expected = _exception.getName() + " thrown - no event manager is provided for OnClickEvent";
+
+            EXPECT_STREQ(expected.c_str(), actual.c_str());
+            throw;
+          }
+        },
+        NullPointerException);
+  }
 }

+ 58 - 2
test/cases/event/reworked/EventManagerTest.cpp

@@ -3,16 +3,20 @@
 * Company:         Lynar Studios
 * E-Mail:          webmaster@lynarstudios.com
 * Created:         2024-05-16
-* Changed:         2024-05-16
+* Changed:         2024-05-17
 *
 * */
 
 #include <gtest/gtest.h>
 #include <ls-std-event-test.hpp>
+#include <ls-std/ls-std-core.hpp>
 #include <ls-std/ls-std-event.hpp>
+#include <string>
 
+using ls::std::core::EventNotHandledException;
 using ls::std::event::reworked::EventManager;
 using ::std::make_shared;
+using ::std::string;
 using test::event::Button;
 using test::event::OnClickEvent;
 using testing::Test;
@@ -32,12 +36,64 @@ namespace
     ASSERT_STREQ("EventManager", EventManager().getClassName().c_str());
   }
 
+  TEST_F(EventManagerTest, getNextProvisionId)
+  {
+    auto eventManager = EventManager();
+    ASSERT_EQ(1, eventManager.getNextProvisionId());
+  }
+
+  TEST_F(EventManagerTest, holdsListenerForEvent)
+  {
+    auto eventManager = make_shared<EventManager>();
+    ASSERT_FALSE(eventManager->holdsListenerForEvent(1, OnClickEvent()));
+  }
+
+  TEST_F(EventManagerTest, invoke_event_not_known)
+  {
+    auto eventManager = make_shared<EventManager>();
+
+    EXPECT_THROW(
+        {
+          try
+          {
+            eventManager->invoke(OnClickEvent());
+          }
+          catch (const EventNotHandledException &_exception)
+          {
+            string actual = _exception.what();
+            string expected = _exception.getName() + " thrown - event OnClickEvent is not known by event manager";
+
+            EXPECT_STREQ(expected.c_str(), actual.c_str());
+            throw;
+          }
+        },
+        EventNotHandledException);
+  }
+
+  TEST_F(EventManagerTest, requestListenerId)
+  {
+    auto eventManager = EventManager();
+
+    ASSERT_EQ(1, eventManager.getNextProvisionId());
+    ASSERT_EQ(1, eventManager.requestListenerId());
+    ASSERT_EQ(2, eventManager.getNextProvisionId());
+  }
+
+  TEST_F(EventManagerTest, subscribeListenerForEvent)
+  {
+    auto eventManager = make_shared<EventManager>();
+    auto myButton = make_shared<Button>();
+
+    eventManager->subscribeListenerForEvent(myButton, OnClickEvent(), [myButton]() mutable { myButton->onClickEvent(); });
+    ASSERT_TRUE(eventManager->holdsListenerForEvent(myButton->getId(), OnClickEvent()));
+  }
+
   //TEST_F(EventManagerTest, invoke)
   //{
   //  auto eventManager = make_shared<EventManager>();
   //
   //  auto myButton = make_shared<Button>();
-  //  myButton->subscribe(OnClickEvent().of(eventManager), [myButton] () mutable { myButton->onClickEvent(); });
+  //  myButton->subscribe(OnClickEvent().of(eventManager), [myButton]() mutable { myButton->onClickEvent(); });
   //
   //  ASSERT_FALSE(myButton->isClicked());
   //  eventManager->invoke(OnClickEvent());