From 42b7f466d2ffeb659c7e4e17c7c38d1b85641135 Mon Sep 17 00:00:00 2001 From: Natalie Martin Date: Sat, 24 Aug 2024 20:47:47 -0700 Subject: [PATCH] Create MessageDispatchService Give entities a way to communicate with each other via the MessageDispatchService. The `MessageInbox` component is used to receive messages. Currently the MessageDispatchService is for entity-to-entity communication. Global events will be handled by another service, although will probably use the same `Message` object with `EventArg` parameters. --- CMakeLists.txt | 11 +- src/event/EventArg.h | 25 ++ src/event/Message.cpp | 72 +++++ src/event/Message.h | 43 +++ src/event/MessageCollection.cpp | 66 +++++ src/event/MessageCollection.h | 32 +++ src/event/MessageDispatchService.cpp | 30 ++ src/event/MessageDispatchService.h | 51 ++++ src/game/components/events/MessageInbox.h | 10 + .../events/hooks/MessageInboxHooks.cpp | 18 ++ .../events/hooks/MessageInboxHooks.h | 10 + src/literals/CrcLookup.h | 263 ++++++++++++++++++ src/literals/HashString.h | 27 ++ test/event/EcsHooks.test.cpp | 30 ++ test/event/Message.test.cpp | 41 +++ test/event/MessageCollection.test.cpp | 69 +++++ test/event/MessageDispatchService.test.cpp | 75 +++++ test/literals/HashString.test.cpp | 22 ++ 18 files changed, 893 insertions(+), 2 deletions(-) create mode 100644 src/event/EventArg.h create mode 100644 src/event/Message.cpp create mode 100644 src/event/Message.h create mode 100644 src/event/MessageCollection.cpp create mode 100644 src/event/MessageCollection.h create mode 100644 src/event/MessageDispatchService.cpp create mode 100644 src/event/MessageDispatchService.h create mode 100644 src/game/components/events/MessageInbox.h create mode 100644 src/game/components/events/hooks/MessageInboxHooks.cpp create mode 100644 src/game/components/events/hooks/MessageInboxHooks.h create mode 100644 src/literals/CrcLookup.h create mode 100644 src/literals/HashString.h create mode 100644 test/event/EcsHooks.test.cpp create mode 100644 test/event/Message.test.cpp create mode 100644 test/event/MessageCollection.test.cpp create mode 100644 test/event/MessageDispatchService.test.cpp create mode 100644 test/literals/HashString.test.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index f0304d7..3a02b53 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,10 +3,17 @@ project(gl-adagio) set(CMAKE_CXX_STANDARD 17) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/bin") -file(GLOB_RECURSE SOURCES "${CMAKE_SOURCE_DIR}/src/*.cpp" "${CMAKE_SOURCE_DIR}/src/math/*.cpp" "${CMAKE_SOURCE_DIR}/src/audio/*.cpp" "${CMAKE_SOURCE_DIR}/src/graphics/*.cpp" "${CMAKE_SOURCE_DIR}/src/state/*.cpp" "${CMAKE_SOURCE_DIR}/src/game/systems/*.cpp" "${CMAKE_SOURCE_DIR}/src/game/states/*.cpp" "${CMAKE_SOURCE_DIR}/src/game/*.cpp") -file(GLOB_RECURSE TESTS "${CMAKE_SOURCE_DIR}/src/math/*.cpp" "${CMAKE_SOURCE_DIR}/test/math/*.cpp" "${CMAKE_SOURCE_DIR}/src/audio/*.cpp" "${CMAKE_SOURCE_DIR}/test/audio/**/*.cpp" "${CMAKE_SOURCE_DIR}/src/game/factories/*.cpp" "${CMAKE_SOURCE_DIR}/src/game/systems/*.cpp" "${CMAKE_SOURCE_DIR}/test/game/**/*.cpp" +file(GLOB_RECURSE SOURCES "${CMAKE_SOURCE_DIR}/src/*.cpp" "${CMAKE_SOURCE_DIR}/src/literals/*.cpp" "${CMAKE_SOURCE_DIR}/src/math/*.cpp" "${CMAKE_SOURCE_DIR}/src/event/*.cpp" "${CMAKE_SOURCE_DIR}/src/audio/*.cpp" "${CMAKE_SOURCE_DIR}/src/graphics/*.cpp" "${CMAKE_SOURCE_DIR}/src/state/*.cpp" "${CMAKE_SOURCE_DIR}/src/game/systems/*.cpp" + "${CMAKE_SOURCE_DIR}/src/game/components/**/*.cpp" + "${CMAKE_SOURCE_DIR}/src/game/states/*.cpp" "${CMAKE_SOURCE_DIR}/src/game/*.cpp") +file(GLOB_RECURSE TESTS + "${CMAKE_SOURCE_DIR}/src/literals/*.cpp" "${CMAKE_SOURCE_DIR}/test/literals/*.cpp" + "${CMAKE_SOURCE_DIR}/src/math/*.cpp" "${CMAKE_SOURCE_DIR}/test/math/*.cpp" "${CMAKE_SOURCE_DIR}/src/audio/*.cpp" "${CMAKE_SOURCE_DIR}/test/audio/**/*.cpp" "${CMAKE_SOURCE_DIR}/src/game/factories/*.cpp" "${CMAKE_SOURCE_DIR}/src/game/systems/*.cpp" + "${CMAKE_SOURCE_DIR}/src/game/components/**/*.cpp" + "${CMAKE_SOURCE_DIR}/test/game/**/*.cpp" "${CMAKE_SOURCE_DIR}/src/graphics/*.cpp" "${CMAKE_SOURCE_DIR}/test/graphics/*.cpp" "${CMAKE_SOURCE_DIR}/src/state/*.cpp" "${CMAKE_SOURCE_DIR}/test/state/**/*.cpp" + "${CMAKE_SOURCE_DIR}/src/event/*.cpp" "${CMAKE_SOURCE_DIR}/test/event/*.cpp" "${CMAKE_SOURCE_DIR}/test/*.cpp") file(COPY "${CMAKE_SOURCE_DIR}/assets" DESTINATION "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") diff --git a/src/event/EventArg.h b/src/event/EventArg.h new file mode 100644 index 0000000..4bf6cbb --- /dev/null +++ b/src/event/EventArg.h @@ -0,0 +1,25 @@ +#ifndef GL_ADAGIO_EVENTVARIANTTYPE_H +#define GL_ADAGIO_EVENTVARIANTTYPE_H + +#include "../literals/HashString.h" +#include + +namespace Adagio { + enum Type { + TYPE_NULL, TYPE_UINT, TYPE_INT, TYPE_FLOAT, TYPE_BOOL, TYPE_HASH + }; + + struct EventArg { + std::uint32_t eventName{0}; + + Type type{TYPE_NULL}; + + union { + std::uint32_t m_Uint{0}; + std::int32_t m_Int; + float m_Float; + bool m_Bool; + }; + }; +} // namespace Adagio +#endif // GL_ADAGIO_EVENTVARIANTTYPE_H diff --git a/src/event/Message.cpp b/src/event/Message.cpp new file mode 100644 index 0000000..30cb894 --- /dev/null +++ b/src/event/Message.cpp @@ -0,0 +1,72 @@ +#include "Message.h" +#include +#include + +const Adagio::EventArg *Adagio::Message::getArg(unsigned char index) const { return &argument[index]; } + +Adagio::Message &Adagio::Message::addBoolArg(std::uint32_t eventName, bool value) { + EventArg &arg = getFirstFreeArgument(); + arg.eventName = eventName; + arg.type = Adagio::TYPE_BOOL; + arg.m_Bool = value; + return *this; +} + +Adagio::Message &Adagio::Message::addUintArg(std::uint32_t eventName, std::uint32_t value) { + EventArg &arg = getFirstFreeArgument(); + arg.eventName = eventName; + arg.type = Adagio::TYPE_UINT; + arg.m_Uint = value; + return *this; +} + +Adagio::Message &Adagio::Message::addIntArg(std::uint32_t eventName, std::int32_t value) { + EventArg &arg = getFirstFreeArgument(); + arg.eventName = eventName; + arg.type = Adagio::TYPE_INT; + arg.m_Int = value; + return *this; +} + +Adagio::Message &Adagio::Message::addFloatArg(std::uint32_t eventName, float value) { + EventArg &arg = getFirstFreeArgument(); + arg.eventName = eventName; + arg.type = Adagio::TYPE_FLOAT; + arg.m_Float = value; + return *this; +} + +unsigned char Adagio::Message::getArgumentCount() const { + for (unsigned char i = 0; i < MAX_EVENT_ARGS; i++) { + if (argument[i].type == Adagio::TYPE_NULL) { + return i; + } + } + return MAX_EVENT_ARGS; +} + +void Adagio::Message::nullifyArguments() { + for (auto &a: argument) { + a.eventName = 0; + a.type = Adagio::TYPE_NULL; + a.m_Uint = 0; + } +} + +Adagio::EventArg &Adagio::Message::getFirstFreeArgument() { + unsigned char count = getArgumentCount(); + if (count >= MAX_EVENT_ARGS) { + throw std::range_error("Too many arguments on event"); + } + return argument[count]; +} + +const Adagio::EventArg *Adagio::Message::getArgByName(std::uint32_t argName) const { + auto i = std::find_if(argument, argument + MAX_EVENT_ARGS, [argName](const Adagio::EventArg &arg) { + return arg.eventName == argName; + }); + if (i != argument + MAX_EVENT_ARGS) { + return i; + } + return nullptr; +} diff --git a/src/event/Message.h b/src/event/Message.h new file mode 100644 index 0000000..8f88866 --- /dev/null +++ b/src/event/Message.h @@ -0,0 +1,43 @@ +#ifndef GL_ADAGIO_MESSAGE_H +#define GL_ADAGIO_MESSAGE_H + +#include "EventArg.h" + +#define MAX_EVENT_ARGS 4 + +namespace Adagio { + struct Message { + struct MessageLinks { + Message *next{nullptr}; + Message *prev{nullptr}; + }; + std::uint32_t name{0}; + std::uint32_t from{0}; + std::uint32_t to{0}; + bool active{false}; + MessageLinks links; + + EventArg argument[MAX_EVENT_ARGS]; + + [[nodiscard]] const EventArg *getArg(unsigned char index) const; + + [[nodiscard]] const EventArg *getArgByName(std::uint32_t name) const; + + Message &addBoolArg(std::uint32_t eventName, bool value); + + Message &addUintArg(std::uint32_t eventName, std::uint32_t value); + + Message &addIntArg(std::uint32_t eventName, std::int32_t value); + + Message &addFloatArg(std::uint32_t eventName, float value); + + [[nodiscard]] unsigned char getArgumentCount() const; + + void nullifyArguments(); + + private: + EventArg &getFirstFreeArgument(); + }; +} // namespace Adagio + +#endif // GL_ADAGIO_MESSAGE_H diff --git a/src/event/MessageCollection.cpp b/src/event/MessageCollection.cpp new file mode 100644 index 0000000..180c296 --- /dev/null +++ b/src/event/MessageCollection.cpp @@ -0,0 +1,66 @@ +#include "MessageCollection.h" + +namespace Adagio { + size_t MessageCollection::size() const { + return length; + } + + void MessageCollection::append(Message *e) { + e->links.next = nullptr; + if (first == nullptr) { + e->links.prev = nullptr; + first = e; + } else { + e->links.prev = last; + last->links.next = e; + } + last = e; + length++; + } + + Message *MessageCollection::operator[](size_t i) const { + return at(i); + } + + void MessageCollection::remove(size_t index) { + Message *event = at(index); + Message *prev = event->links.prev; + Message *next = event->links.next; + if (index == 0) { + first = next; + } + if (index == length - 1) { + last = prev; + } + if (prev && next) { + prev->links.next = next; + next->links.prev = prev; + } + event->nullifyArguments(); + event->active = false; + event->name = 0; + event->from = 0; + event->to = 0; + length--; + } + + Message *MessageCollection::at(size_t i) const { + Message *event = first; + for (size_t j = 0; j < i; j++) { + event = event->links.next; + } + return event; + } + + Message *MessageCollection::shift() { + Message *e = first; + if (first) { + remove(0); + } + return e; + } + + bool MessageCollection::empty() const { + return size() == 0; + } +} // Adagio diff --git a/src/event/MessageCollection.h b/src/event/MessageCollection.h new file mode 100644 index 0000000..7917988 --- /dev/null +++ b/src/event/MessageCollection.h @@ -0,0 +1,32 @@ +#ifndef GL_ADAGIO_MESSAGECOLLECTION_H +#define GL_ADAGIO_MESSAGECOLLECTION_H + +#include "Message.h" + +namespace Adagio { + class MessageCollection { + public: + Message *first{nullptr}; + Message *last{nullptr}; + + void append(Message *e); + + void remove(size_t index); + + Message *shift(); + + [[nodiscard]] bool empty() const; + + [[nodiscard]] size_t size() const; + + [[nodiscard]] Message *at(size_t i) const; + + Message *operator[](size_t i) const; + + private: + size_t length{0}; + }; + +} // Adagio + +#endif //GL_ADAGIO_MESSAGECOLLECTION_H diff --git a/src/event/MessageDispatchService.cpp b/src/event/MessageDispatchService.cpp new file mode 100644 index 0000000..7bd23d7 --- /dev/null +++ b/src/event/MessageDispatchService.cpp @@ -0,0 +1,30 @@ +#include "MessageDispatchService.h" +#include + +namespace Adagio { + Message &MessageDispatchService::dispatch(InboxId to, InboxId from, std::uint32_t eventName) { + auto inbox = inboxMap.find(to); + auto it = std::find_if(eventPool, eventPool + MSGPOOL_MAX, [](Message &e) { + return !e.active; + }); + if (it == eventPool + MSGPOOL_MAX || inbox == inboxMap.end()) { + invalidMessage.name = 0xdeadbeef; + invalidMessage.active = false; + return invalidMessage; + } + it->active = true; + it->from = from; + it->to = to; + it->name = eventName; + inbox->second->append(it); + return *it; + } + + void MessageDispatchService::registerInbox(InboxId id, MessageCollection *inboxDestination) { + inboxMap[id] = inboxDestination; + } + + void MessageDispatchService::unregisterInbox(InboxId id) { + inboxMap.erase(id); + } +} // namespace Adagio diff --git a/src/event/MessageDispatchService.h b/src/event/MessageDispatchService.h new file mode 100644 index 0000000..2d6543a --- /dev/null +++ b/src/event/MessageDispatchService.h @@ -0,0 +1,51 @@ +#ifndef GL_ADAGIO_MESSAGEDISPATCHSERVICE_H +#define GL_ADAGIO_MESSAGEDISPATCHSERVICE_H + +#include +#include +#include "Message.h" +#include "MessageCollection.h" + +#define MSGPOOL_MAX 32 + +namespace Adagio { + + typedef std::uint32_t InboxId; + + class MessageDispatchService { + public: + void registerInbox(InboxId id, MessageCollection *inboxDestination); + + template + void registerInbox(T id, U &inboxObject) { + registerInbox(static_cast(id), &inboxObject.messages); + } + + template + void registerInbox(T id, MessageCollection *inboxDestination) { + registerInbox(static_cast(id), inboxDestination); + } + + void unregisterInbox(InboxId id); + + template + void unregisterInbox(T id) { + unregisterInbox(static_cast(id)); + } + + Message &dispatch(InboxId to, InboxId from, std::uint32_t eventName); + + template + Message &dispatch(T to, U from, std::uint32_t eventName) { + return dispatch(static_cast(to), static_cast(from), eventName); + } + + private: + Message eventPool[MSGPOOL_MAX]; + Message invalidMessage; + std::unordered_map inboxMap; + }; + +} // namespace Adagio + +#endif // GL_ADAGIO_MESSAGEDISPATCHSERVICE_H diff --git a/src/game/components/events/MessageInbox.h b/src/game/components/events/MessageInbox.h new file mode 100644 index 0000000..9fae946 --- /dev/null +++ b/src/game/components/events/MessageInbox.h @@ -0,0 +1,10 @@ +#ifndef GL_ADAGIO_MESSAGEINBOX_H +#define GL_ADAGIO_MESSAGEINBOX_H + +#include "../../../event/MessageCollection.h" + +struct MessageInbox { + Adagio::MessageCollection messages; +}; + +#endif //GL_ADAGIO_MESSAGEINBOX_H diff --git a/src/game/components/events/hooks/MessageInboxHooks.cpp b/src/game/components/events/hooks/MessageInboxHooks.cpp new file mode 100644 index 0000000..37f9b32 --- /dev/null +++ b/src/game/components/events/hooks/MessageInboxHooks.cpp @@ -0,0 +1,18 @@ +#include "../../../../event/MessageDispatchService.h" +#include "../MessageInbox.h" +#include "MessageInboxHooks.h" + +void RegisterInboxWithMessageService(entt::registry ®istry, entt::entity id) { + Adagio::MessageDispatchService *eventService = registry.ctx().get(); + if (eventService) { + MessageInbox &component = registry.get(id); + eventService->registerInbox(id, component); + } +} + +void UnregisterInboxWithMessageService(entt::registry ®istry, entt::entity id) { + Adagio::MessageDispatchService *eventService = registry.ctx().get(); + if (eventService) { + eventService->unregisterInbox(id); + } +} diff --git a/src/game/components/events/hooks/MessageInboxHooks.h b/src/game/components/events/hooks/MessageInboxHooks.h new file mode 100644 index 0000000..6346edf --- /dev/null +++ b/src/game/components/events/hooks/MessageInboxHooks.h @@ -0,0 +1,10 @@ +#ifndef GL_ADAGIO_MESSAGEINBOXHOOKS_H +#define GL_ADAGIO_MESSAGEINBOXHOOKS_H + +#include + +void RegisterInboxWithMessageService(entt::registry ®istry, entt::entity id); + +void UnregisterInboxWithMessageService(entt::registry ®istry, entt::entity id); + +#endif //GL_ADAGIO_MESSAGEINBOXHOOKS_H diff --git a/src/literals/CrcLookup.h b/src/literals/CrcLookup.h new file mode 100644 index 0000000..fffff1f --- /dev/null +++ b/src/literals/CrcLookup.h @@ -0,0 +1,263 @@ +#ifndef GL_ADAGIO_CRCLOOKUP_H +#define GL_ADAGIO_CRCLOOKUP_H + +/* This is a hardcoded lookup table for the CRC algorithm */ + +#define ADAGIO_CRC_LOOKUP {0x00000000,\ +0x80db8e60,\ +0xc06d4730,\ +0x40b6c950,\ +0xe0b62398,\ +0x606dadf8,\ +0x20db64a8,\ +0xa000eac8,\ +0x70db114c,\ +0xf0009f2c,\ +0xb0b6567c,\ +0x306dd81c,\ +0x906d32d4,\ +0x10b6bcb4,\ +0x500075e4,\ +0xd0dbfb84,\ +0xb8ed0826,\ +0x38368646,\ +0x78804f16,\ +0xf85bc176,\ +0x585b2bbe,\ +0xd880a5de,\ +0x98366c8e,\ +0x18ede2ee,\ +0xc836196a,\ +0x48ed970a,\ +0x085b5e5a,\ +0x8880d03a,\ +0x28803af2,\ +0xa85bb492,\ +0xe8ed7dc2,\ +0x6836f3a2,\ +0xdc760413,\ +0x5cad8a73,\ +0x1c1b4323,\ +0x9cc0cd43,\ +0x3cc0278b,\ +0xbc1ba9eb,\ +0xfcad60bb,\ +0x7c76eedb,\ +0xacad155f,\ +0x2c769b3f,\ +0x6cc0526f,\ +0xec1bdc0f,\ +0x4c1b36c7,\ +0xccc0b8a7,\ +0x8c7671f7,\ +0x0cadff97,\ +0x649b0c35,\ +0xe4408255,\ +0xa4f64b05,\ +0x242dc565,\ +0x842d2fad,\ +0x04f6a1cd,\ +0x4440689d,\ +0xc49be6fd,\ +0x14401d79,\ +0x949b9319,\ +0xd42d5a49,\ +0x54f6d429,\ +0xf4f63ee1,\ +0x742db081,\ +0x349b79d1,\ +0xb440f7b1,\ +0x6e3b8209,\ +0xeee00c69,\ +0xae56c539,\ +0x2e8d4b59,\ +0x8e8da191,\ +0x0e562ff1,\ +0x4ee0e6a1,\ +0xce3b68c1,\ +0x1ee09345,\ +0x9e3b1d25,\ +0xde8dd475,\ +0x5e565a15,\ +0xfe56b0dd,\ +0x7e8d3ebd,\ +0x3e3bf7ed,\ +0xbee0798d,\ +0xd6d68a2f,\ +0x560d044f,\ +0x16bbcd1f,\ +0x9660437f,\ +0x3660a9b7,\ +0xb6bb27d7,\ +0xf60dee87,\ +0x76d660e7,\ +0xa60d9b63,\ +0x26d61503,\ +0x6660dc53,\ +0xe6bb5233,\ +0x46bbb8fb,\ +0xc660369b,\ +0x86d6ffcb,\ +0x060d71ab,\ +0xb24d861a,\ +0x3296087a,\ +0x7220c12a,\ +0xf2fb4f4a,\ +0x52fba582,\ +0xd2202be2,\ +0x9296e2b2,\ +0x124d6cd2,\ +0xc2969756,\ +0x424d1936,\ +0x02fbd066,\ +0x82205e06,\ +0x2220b4ce,\ +0xa2fb3aae,\ +0xe24df3fe,\ +0x62967d9e,\ +0x0aa08e3c,\ +0x8a7b005c,\ +0xcacdc90c,\ +0x4a16476c,\ +0xea16ada4,\ +0x6acd23c4,\ +0x2a7bea94,\ +0xaaa064f4,\ +0x7a7b9f70,\ +0xfaa01110,\ +0xba16d840,\ +0x3acd5620,\ +0x9acdbce8,\ +0x1a163288,\ +0x5aa0fbd8,\ +0xda7b75b8,\ +0xb71dc104,\ +0x37c64f64,\ +0x77708634,\ +0xf7ab0854,\ +0x57abe29c,\ +0xd7706cfc,\ +0x97c6a5ac,\ +0x171d2bcc,\ +0xc7c6d048,\ +0x471d5e28,\ +0x07ab9778,\ +0x87701918,\ +0x2770f3d0,\ +0xa7ab7db0,\ +0xe71db4e0,\ +0x67c63a80,\ +0x0ff0c922,\ +0x8f2b4742,\ +0xcf9d8e12,\ +0x4f460072,\ +0xef46eaba,\ +0x6f9d64da,\ +0x2f2bad8a,\ +0xaff023ea,\ +0x7f2bd86e,\ +0xfff0560e,\ +0xbf469f5e,\ +0x3f9d113e,\ +0x9f9dfbf6,\ +0x1f467596,\ +0x5ff0bcc6,\ +0xdf2b32a6,\ +0x6b6bc517,\ +0xebb04b77,\ +0xab068227,\ +0x2bdd0c47,\ +0x8bdde68f,\ +0x0b0668ef,\ +0x4bb0a1bf,\ +0xcb6b2fdf,\ +0x1bb0d45b,\ +0x9b6b5a3b,\ +0xdbdd936b,\ +0x5b061d0b,\ +0xfb06f7c3,\ +0x7bdd79a3,\ +0x3b6bb0f3,\ +0xbbb03e93,\ +0xd386cd31,\ +0x535d4351,\ +0x13eb8a01,\ +0x93300461,\ +0x3330eea9,\ +0xb3eb60c9,\ +0xf35da999,\ +0x738627f9,\ +0xa35ddc7d,\ +0x2386521d,\ +0x63309b4d,\ +0xe3eb152d,\ +0x43ebffe5,\ +0xc3307185,\ +0x8386b8d5,\ +0x035d36b5,\ +0xd926430d,\ +0x59fdcd6d,\ +0x194b043d,\ +0x99908a5d,\ +0x39906095,\ +0xb94beef5,\ +0xf9fd27a5,\ +0x7926a9c5,\ +0xa9fd5241,\ +0x2926dc21,\ +0x69901571,\ +0xe94b9b11,\ +0x494b71d9,\ +0xc990ffb9,\ +0x892636e9,\ +0x09fdb889,\ +0x61cb4b2b,\ +0xe110c54b,\ +0xa1a60c1b,\ +0x217d827b,\ +0x817d68b3,\ +0x01a6e6d3,\ +0x41102f83,\ +0xc1cba1e3,\ +0x11105a67,\ +0x91cbd407,\ +0xd17d1d57,\ +0x51a69337,\ +0xf1a679ff,\ +0x717df79f,\ +0x31cb3ecf,\ +0xb110b0af,\ +0x0550471e,\ +0x858bc97e,\ +0xc53d002e,\ +0x45e68e4e,\ +0xe5e66486,\ +0x653deae6,\ +0x258b23b6,\ +0xa550add6,\ +0x758b5652,\ +0xf550d832,\ +0xb5e61162,\ +0x353d9f02,\ +0x953d75ca,\ +0x15e6fbaa,\ +0x555032fa,\ +0xd58bbc9a,\ +0xbdbd4f38,\ +0x3d66c158,\ +0x7dd00808,\ +0xfd0b8668,\ +0x5d0b6ca0,\ +0xddd0e2c0,\ +0x9d662b90,\ +0x1dbda5f0,\ +0xcd665e74,\ +0x4dbdd014,\ +0x0d0b1944,\ +0x8dd09724,\ +0x2dd07dec,\ +0xad0bf38c,\ +0xedbd3adc,\ +0x6d66b4bc} + +#endif //GL_ADAGIO_CRCLOOKUP_H diff --git a/src/literals/HashString.h b/src/literals/HashString.h new file mode 100644 index 0000000..e650118 --- /dev/null +++ b/src/literals/HashString.h @@ -0,0 +1,27 @@ +#ifndef GL_ADAGIO_HASHSTRING_H +#define GL_ADAGIO_HASHSTRING_H + +#include +#include +#include "CrcLookup.h" + +namespace Adagio { + static constexpr std::uint32_t crcLookupTable[256] = ADAGIO_CRC_LOOKUP; + + constexpr std::uint32_t crc(const char *str, size_t length) { + std::uint32_t reg = 0; + for (size_t i = 0; i < length + 4; i++) { + std::uint8_t topByte = (reg >> 24) & 0xFF; + std::uint8_t nextMsgByte = (i < length) ? str[i] : 0; + reg = (reg << 8) | nextMsgByte; + reg ^= crcLookupTable[topByte]; + } + return reg; + } +} + +constexpr std::uint32_t operator ""_hs(const char *string, std::size_t size) { + return Adagio::crc(string, size); +} + +#endif // GL_ADAGIO_HASHSTRING_H diff --git a/test/event/EcsHooks.test.cpp b/test/event/EcsHooks.test.cpp new file mode 100644 index 0000000..9fc2de6 --- /dev/null +++ b/test/event/EcsHooks.test.cpp @@ -0,0 +1,30 @@ +#include "../game/EcsTestingHarness.h" +#include "../../../src/game/components/events/MessageInbox.h" +#include "../../../src/event/MessageDispatchService.h" +#include "../../../src/game/components/events/hooks/MessageInboxHooks.h" +#include + +TEST_CASE("MessageInbox ECS Hooks", "[event]") { + Adagio::MessageDispatchService messageService; + EcsTestingHarness harness; + auto ®istry = harness.registry; + registry.ctx().emplace(&messageService); + registry.on_construct().connect<&RegisterInboxWithMessageService>(); + registry.on_destroy().connect<&UnregisterInboxWithMessageService>(); + + SECTION("A component is registered with the message service", "[event]") { + auto entity = registry.create(); + auto &inbox = registry.emplace(entity); + messageService.dispatch(entity, 0, "test"_hs); + REQUIRE(inbox.messages.size() == 1); + REQUIRE(inbox.messages.first->name == "test"_hs); + } + + SECTION("A component can be deregistered with the message service", "[event]") { + auto entity = registry.create(); + auto &inbox = registry.emplace(entity); + registry.destroy(entity); + messageService.dispatch(entity, 0, "test"_hs); + REQUIRE(inbox.messages.empty()); + } +} diff --git a/test/event/Message.test.cpp b/test/event/Message.test.cpp new file mode 100644 index 0000000..feee778 --- /dev/null +++ b/test/event/Message.test.cpp @@ -0,0 +1,41 @@ +#include "../../src/event/Message.h" +#include + +TEST_CASE("Message object", "[event]") { + SECTION("Takes in event arguments", "[event]") { + Adagio::Message event; + event.addIntArg("test"_hs, 1337).addBoolArg( + "test2"_hs, false); + auto value = event.getArg(0); + auto value2 = event.getArg(1); + REQUIRE(value->eventName == "test"_hs); + REQUIRE(value->type == Adagio::TYPE_INT); + REQUIRE(value->m_Int == 1337); + REQUIRE(value2->eventName == "test2"_hs); + REQUIRE(value2->type == Adagio::TYPE_BOOL); + REQUIRE(value2->m_Bool == false); + }SECTION("Throws an error if you add too many arguments", "[event]") { + Adagio::Message event; + event.addFloatArg("test"_hs, 3.14159f).addUintArg("test2"_hs, 1337).addBoolArg("test3"_hs, true).addIntArg( + "test4"_hs, -1337); + REQUIRE_THROWS(event.addIntArg("exception"_hs, 666)); + } SECTION("Can look up an argument by hash name", "[event]") { + Adagio::Message event; + event.addIntArg("abcd"_hs, 1337).addBoolArg("efgh"_hs, true); + REQUIRE(event.getArgByName("abcd"_hs)->m_Int == 1337); + REQUIRE(event.getArgByName("efgh"_hs)->m_Bool); + REQUIRE(event.getArgByName("nonexist"_hs) == nullptr); + } SECTION("Can clear event arguments", "[event]") { + Adagio::Message event; + event.addIntArg("abcd"_hs, 1337).addBoolArg("efgh"_hs, true); + REQUIRE(event.getArgumentCount() == 2); + event.nullifyArguments(); + REQUIRE(event.getArgumentCount() == 0); + REQUIRE(event.getArg(0)->eventName == 0); + } SECTION("Has from, to, and active properties", "[event]") { + Adagio::Message event; + event.from = 1337; + event.to = 666; + event.active = true; + } +} diff --git a/test/event/MessageCollection.test.cpp b/test/event/MessageCollection.test.cpp new file mode 100644 index 0000000..03f85ca --- /dev/null +++ b/test/event/MessageCollection.test.cpp @@ -0,0 +1,69 @@ +#include "../../src/event/MessageCollection.h" +#include + +TEST_CASE("MessageCollection", "[event]") { + Adagio::MessageCollection collection; + + SECTION("Can calculate count", "[event]") { + REQUIRE(collection.empty()); + } + + SECTION("Can add a single event", "[event]") { + Adagio::Message event; + event.name = "test"_hs; + collection.append(&event); + REQUIRE(collection.first == &event); + REQUIRE(collection.size() == 1); + } + + SECTION("Can add two events", "[event]") { + Adagio::Message eventA, eventB; + collection.append(&eventA); + collection.append(&eventB); + REQUIRE(collection[0] == &eventA); + REQUIRE(collection[1] == &eventB); + REQUIRE(collection.size() == 2); + } + + SECTION("Can add many events", "[event]") { + Adagio::Message events[10]; + for (int i = 0; i < 10; i++) { + collection.append(&events[i]); + REQUIRE(collection[i] == &events[i]); + } + REQUIRE(collection.size() == 10); + } + + SECTION("Can remove an event in the middle of the collection", "[event]") { + Adagio::Message events[10]; + for (auto &event: events) { + collection.append(&event); + } + collection.remove(3); + REQUIRE(collection.size() == 9); + REQUIRE(collection[2]->links.next == &events[4]); + REQUIRE(events[4].links.prev == collection[2]); + } + + SECTION("Can remove the only event", "[event]") { + Adagio::Message event; + collection.append(&event); + collection.remove(0); + REQUIRE(collection.empty()); + REQUIRE(collection.first == nullptr); + REQUIRE(collection.last == nullptr); + } + + SECTION("Can pop off the first element", "[event]") { + Adagio::Message events[10]; + for (auto &event: events) { + collection.append(&event); + } + Adagio::Message *first = collection.first; + while (Adagio::Message *e = collection.shift()) { + REQUIRE(first == e); + first = collection.first; + } + REQUIRE(collection.empty()); + } +} diff --git a/test/event/MessageDispatchService.test.cpp b/test/event/MessageDispatchService.test.cpp new file mode 100644 index 0000000..8859543 --- /dev/null +++ b/test/event/MessageDispatchService.test.cpp @@ -0,0 +1,75 @@ +#include "../../src/event/MessageDispatchService.h" +#include + +struct TestInbox { + Adagio::MessageCollection messages; +}; + + +TEST_CASE("MessageDispatchService can send an event to an inbox", "[event]") { + Adagio::MessageDispatchService events; + TestInbox inbox; + events.registerInbox(1, inbox); + auto event = events.dispatch(1, 0, "test"_hs).addBoolArg("isTesting"_hs, true); + REQUIRE(event.active); + REQUIRE(event.from == 0); + REQUIRE(event.to == 1); + REQUIRE(event.name == "test"_hs); + REQUIRE(inbox.messages.size() == 1); + REQUIRE(inbox.messages[0]->getArg(0)->m_Bool); +} + +TEST_CASE("MessageDispatchService can send multiple events to an inbox", "[event]") { + Adagio::MessageDispatchService events; + TestInbox inbox; + events.registerInbox(1, inbox); + events.dispatch(1, 0, "test"_hs); + events.dispatch(1, 0, "test2"_hs); + REQUIRE(inbox.messages.size() == 2); + REQUIRE(inbox.messages[0]->name == "test"_hs); + REQUIRE(inbox.messages[1]->name == "test2"_hs); +} + +TEST_CASE("MessageDispatchService properly pools message objects", "[event]") { + Adagio::MessageDispatchService events; + TestInbox inbox; + events.registerInbox(1, inbox); + Adagio::Message *const event = &events.dispatch(1, 0, "test"_hs); + inbox.messages.remove(0); + Adagio::Message *const newEvent = &events.dispatch(1, 0, "newEvent"_hs); + REQUIRE(event == newEvent); +} + +TEST_CASE("MessageDispatchService does not give up a pooled message if no one receives it", "[event]") { + Adagio::MessageDispatchService events; + TestInbox inbox; + events.registerInbox(1, inbox); + Adagio::Message *const unsentMessage = &events.dispatch(1337, 0, "invalid"_hs); + Adagio::Message *const sentMessage = &events.dispatch(1, 0, "test"_hs); + REQUIRE(sentMessage != unsentMessage); + REQUIRE(unsentMessage->name != "invalid"_hs); +} + +TEST_CASE("MessageDispatchService can unregister an inbox", "[event]") { + Adagio::MessageDispatchService events; + TestInbox inbox; + events.registerInbox(1, inbox); + events.unregisterInbox(1); + events.dispatch(1, 0, "test"_hs).addBoolArg("isTest"_hs, true); + REQUIRE(inbox.messages.empty()); +} + +TEST_CASE("MessageDispatchService will fail silently if message pool is full", "[event]") { + Adagio::MessageDispatchService events; + TestInbox inbox; + events.registerInbox(1, inbox); + for (int i = 0; i < MSGPOOL_MAX; i++) { + events.dispatch(1, 0, "test"_hs); + } + REQUIRE(inbox.messages.size() == MSGPOOL_MAX); + + Adagio::Message &message = events.dispatch(1, 0, "test"_hs); + REQUIRE(message.name == 0xdeadbeef); + REQUIRE_FALSE(message.active); + REQUIRE(inbox.messages.size() == MSGPOOL_MAX); +} diff --git a/test/literals/HashString.test.cpp b/test/literals/HashString.test.cpp new file mode 100644 index 0000000..028bf26 --- /dev/null +++ b/test/literals/HashString.test.cpp @@ -0,0 +1,22 @@ +#include "../../src/literals/HashString.h" +#include + +TEST_CASE("HashString literal", "[literals]") { + // TESTING TIP: + // Use a breakpoint to verify that these are generated at compile time + unsigned int hash = "potato"_hs; + auto hash2 = "POTATO"_hs; + auto hash3 = "potatp"_hs; + auto hash4 = "abcdefghijklmnopqrstuvwxyz"_hs; + REQUIRE(hash != 0); + REQUIRE(hash != hash2); + REQUIRE(hash2 != hash3); + REQUIRE(hash3 != hash4); +} + +TEST_CASE("crc32", "[literals]") { + const unsigned int hash = Adagio::crc("test", 4); + const unsigned int hash2 = Adagio::crc("tets", 4); + REQUIRE(hash != 0); + REQUIRE(hash != hash2); +}