From 950712b85eb2e9c60963a120d71fc5b658bd0c17 Mon Sep 17 00:00:00 2001 From: Gary Talent Date: Fri, 16 Jul 2021 20:43:07 -0500 Subject: [PATCH] [ox/event] Add Event package --- deps/ox/src/ox/CMakeLists.txt | 1 + deps/ox/src/ox/event/CMakeLists.txt | 47 ++++++ deps/ox/src/ox/event/event.hpp | 11 ++ deps/ox/src/ox/event/signal.cpp | 19 +++ deps/ox/src/ox/event/signal.hpp | 189 +++++++++++++++++++++++ deps/ox/src/ox/event/test/CMakeLists.txt | 10 ++ deps/ox/src/ox/event/test/tests.cpp | 50 ++++++ 7 files changed, 327 insertions(+) create mode 100644 deps/ox/src/ox/event/CMakeLists.txt create mode 100644 deps/ox/src/ox/event/event.hpp create mode 100644 deps/ox/src/ox/event/signal.cpp create mode 100644 deps/ox/src/ox/event/signal.hpp create mode 100644 deps/ox/src/ox/event/test/CMakeLists.txt create mode 100644 deps/ox/src/ox/event/test/tests.cpp diff --git a/deps/ox/src/ox/CMakeLists.txt b/deps/ox/src/ox/CMakeLists.txt index 0a7bfdb3..104479bd 100644 --- a/deps/ox/src/ox/CMakeLists.txt +++ b/deps/ox/src/ox/CMakeLists.txt @@ -3,6 +3,7 @@ if(OX_USE_STDLIB) endif() add_subdirectory(clargs) add_subdirectory(claw) +add_subdirectory(event) add_subdirectory(fs) add_subdirectory(mc) add_subdirectory(model) diff --git a/deps/ox/src/ox/event/CMakeLists.txt b/deps/ox/src/ox/event/CMakeLists.txt new file mode 100644 index 00000000..36e61448 --- /dev/null +++ b/deps/ox/src/ox/event/CMakeLists.txt @@ -0,0 +1,47 @@ +add_library( + OxEvent + signal.cpp +) + +if(NOT MSVC) + target_compile_options(OxEvent PRIVATE -Wsign-conversion) +endif() + +if(NOT OX_BARE_METAL) + set_property( + TARGET + OxEvent + PROPERTY + POSITION_INDEPENDENT_CODE ON + ) +endif() + +target_compile_definitions( + OxEvent PUBLIC + $<$:OX_USE_STDLIB> + $<$:OX_NODEBUG> +) + +target_link_libraries( + OxEvent PUBLIC + $<$:dl> + $<$:gcc> + OxStd +) + +install( + FILES + event.hpp + signal.hpp + DESTINATION + include/ox/event +) + +install(TARGETS OxEvent + LIBRARY DESTINATION lib/ox + ARCHIVE DESTINATION lib/ox +) + +if(OX_RUN_TESTS) + add_subdirectory(test) +endif() \ No newline at end of file diff --git a/deps/ox/src/ox/event/event.hpp b/deps/ox/src/ox/event/event.hpp new file mode 100644 index 00000000..5db43a4a --- /dev/null +++ b/deps/ox/src/ox/event/event.hpp @@ -0,0 +1,11 @@ +/* + * Copyright 2016 - 2021 gary@drinkingtea.net + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#pragma once + +#include "signal.hpp" diff --git a/deps/ox/src/ox/event/signal.cpp b/deps/ox/src/ox/event/signal.cpp new file mode 100644 index 00000000..25e0e300 --- /dev/null +++ b/deps/ox/src/ox/event/signal.cpp @@ -0,0 +1,19 @@ +/* + * Copyright 2016 - 2021 gary@drinkingtea.net + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include "signal.hpp" + +namespace ox { + +template class Signal; + +SignalHandler::~SignalHandler() noexcept { + destruction.emit(this); +} + +} diff --git a/deps/ox/src/ox/event/signal.hpp b/deps/ox/src/ox/event/signal.hpp new file mode 100644 index 00000000..f0ba88bf --- /dev/null +++ b/deps/ox/src/ox/event/signal.hpp @@ -0,0 +1,189 @@ +/* + * Copyright 2016 - 2021 gary@drinkingtea.net + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#pragma once + +#include +#include +#include + +namespace ox { + +class SignalHandler; + +template +class Signal; + +template +class Signal { + protected: + struct BaseSlot { + virtual ~BaseSlot() = default; + virtual Error call(Args...) noexcept = 0; + virtual void cleanup(Signal*) noexcept {} + virtual const void *receiver() noexcept { return nullptr; } + }; + + struct FunctionSlot: public BaseSlot { + Error (*f)(Args...); + + explicit FunctionSlot(Error (*f)(Args...)) noexcept { + this->f = f; + } + + Error call(Args... args) noexcept final { + return f(args...); + } + }; + + template + struct MethodSlot: public BaseSlot { + T m_receiver = nullptr; + Method m_methodPtr; + + MethodSlot(T receiver, Method methodPtr) { + m_receiver = receiver; + m_methodPtr = methodPtr; + } + + Error call(Args... args) noexcept final { + return (m_receiver->*(m_methodPtr))(args...); + } + + void cleanup(Signal *signal) noexcept final { + oxIgnoreError(m_receiver->destruction.disconnectSignal(signal)); + } + + const void *receiver() noexcept final { + return m_receiver; + } + }; + + template + struct SignalMethodSlot: public BaseSlot { + const SignalT *m_receiver = nullptr; + Method m_methodPtr; + + SignalMethodSlot(const SignalT *receiver, Method methodPtr) { + m_receiver = receiver; + m_methodPtr = methodPtr; + } + + Error call(Args... args) noexcept final { + return (m_receiver->*(m_methodPtr))(args...); + } + + void cleanup(Signal*) noexcept final { + } + + const void *receiver() noexcept final { + return m_receiver; + } + }; + + mutable Vector> m_slots; + + public: + ~Signal() noexcept; + + void connect(Error(*f)(Args...)) const noexcept; + + template + void connect(const T *receiver, Method methodPtr) const noexcept; + + template + void connect(T *receiver, Method methodPtr) const noexcept; + + template + void connect(const Signal *receiver, Method methodPtr) const noexcept; + + template + Error disconnectSignal(const Signal *receiver) const noexcept; + + Error disconnectObject(const void *receiver) const noexcept; + + void emit(Args... args) noexcept; + + Error emitCheckError(Args... args) noexcept; +}; + +extern template class Signal; + +class SignalHandler { + public: + Signal destruction; + + virtual ~SignalHandler() noexcept; +}; + +template +Signal::~Signal() noexcept { + for (auto &slot : m_slots) { + slot->cleanup(this); + } +} + +template +void Signal::connect(Error(*f)(Args...)) const noexcept { + m_slots.emplace_back(new FunctionSlot(f)); +} + +template +template +void Signal::connect(const T *receiver, Method methodPtr) const noexcept { + receiver->destruction.connect(this, &Signal::disconnectObject); + m_slots.emplace_back(new MethodSlot(receiver, methodPtr)); +} + +template +template +void Signal::connect(T *receiver, Method methodPtr) const noexcept { + receiver->destruction.connect(this, &Signal::disconnectObject); + m_slots.emplace_back(new MethodSlot(receiver, methodPtr)); +} + +template +template +void Signal::connect(const Signal *receiver, Method methodPtr) const noexcept { + m_slots.emplace_back(new SignalMethodSlot, Method>(receiver, methodPtr)); +} + +template +template +Error Signal::disconnectSignal(const Signal *receiver) const noexcept { + return disconnectObject(receiver); +} + +template +Error Signal::disconnectObject(const void *signal) const noexcept { + for (auto i = 0u; i < m_slots.size(); ++i) { + const auto &slot = m_slots[i]; + if (slot->receiver() == signal) { + oxReturnError(m_slots.erase(i)); + --i; + } + } + return OxError(0); +} + +template +void Signal::emit(Args... args) noexcept { + for (auto &f : m_slots) { + oxIgnoreError(f->call(args...)); + } +} + +template +Error Signal::emitCheckError(Args... args) noexcept { + for (auto &f : m_slots) { + oxReturnError(f->call(args...)); + } + return OxError(0); +} + +} diff --git a/deps/ox/src/ox/event/test/CMakeLists.txt b/deps/ox/src/ox/event/test/CMakeLists.txt new file mode 100644 index 00000000..11f56e94 --- /dev/null +++ b/deps/ox/src/ox/event/test/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.10) + +add_executable( + EventTest + tests.cpp +) + +target_link_libraries(EventTest OxEvent) + +add_test("[ox/event] Test 1" EventTest "test1") \ No newline at end of file diff --git a/deps/ox/src/ox/event/test/tests.cpp b/deps/ox/src/ox/event/test/tests.cpp new file mode 100644 index 00000000..dcca4b93 --- /dev/null +++ b/deps/ox/src/ox/event/test/tests.cpp @@ -0,0 +1,50 @@ +/* + * Copyright 2015 - 2021 gary@drinkingtea.net + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#undef NDEBUG + +#include +#include +#include +#include + +struct TestStruct: public ox::SignalHandler { + int value = 0; + ox::Error method(int i) noexcept { + value = i; + return OxError(0); + } +}; + +std::map> tests = { + { + "test1", + [] { + ox::Signal signal; + signal.connect([](int i) -> ox::Error { + return OxError(i != 5); + }); + TestStruct ts; + signal.connect(&ts, &TestStruct::method); + oxReturnError(signal.emitCheckError(5)); + oxReturnError(OxError(ts.value != 5)); + return OxError(0); + } + }, +}; + +int main(int argc, const char **args) { + if (argc > 1) { + auto testName = args[1]; + if (tests.find(testName) != tests.end()) { + oxAssert(tests[testName](), "Test returned Error"); + return 0; + } + } + return -1; +}