diff --git a/configs/host_unittest_defconfig b/configs/host_unittest_defconfig index 5cffa2839..a6e020931 100644 --- a/configs/host_unittest_defconfig +++ b/configs/host_unittest_defconfig @@ -11,6 +11,7 @@ CONFIG_HOST_LIBDRM_DISP=y # CONFIG_HOST_UNITTEST_SIGSLOT is not set # CONFIG_HOST_SIGNAL_SLOTS is not set +# CONFIG_HOST_SIGSLOT_THREADED_PRODUCER_CONSUMER is not set # # hardware platform diff --git a/include/linux/license.h b/include/linux/license.h new file mode 100644 index 000000000..12f6aba81 --- /dev/null +++ b/include/linux/license.h @@ -0,0 +1,16 @@ +#ifndef LINUX_LICENSE_H +#define LINUX_LICENSE_H + +#include + +static inline int license_is_gpl_compatible(const char *license) +{ + return (strcmp(license, "GPL") == 0 + || strcmp(license, "GPL v2") == 0 + || strcmp(license, "GPL and additional rights") == 0 + || strcmp(license, "Dual BSD/GPL") == 0 + || strcmp(license, "Dual MIT/GPL") == 0 + || strcmp(license, "Dual MPL/GPL") == 0); +} + +#endif diff --git a/project/3rdparty/include/hope/application.hpp b/project/3rdparty/include/hope/application.hpp new file mode 100644 index 000000000..7d7076884 --- /dev/null +++ b/project/3rdparty/include/hope/application.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include "object.hpp" +#include "eventloop.hpp" + +namespace hope { +namespace detail { +class ObjectData; +} + +namespace test { +class ApplicationTestHelper; +} + +class Application : public Object { +public: + Application(); + ~Application() override; + + void quit(int exit_code); + + void quit() { + quit(0); + } + + int exec(); + +private: + friend class test::ApplicationTestHelper; + + EventLoop m_event_loop; +}; +} diff --git a/project/3rdparty/include/hope/atomicwrapper.hpp b/project/3rdparty/include/hope/atomicwrapper.hpp new file mode 100644 index 000000000..ee02861b8 --- /dev/null +++ b/project/3rdparty/include/hope/atomicwrapper.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include +#include + +namespace hope { +namespace detail { +template +class AtomicWrapper { +public: + AtomicWrapper() = default; + + AtomicWrapper(T &&t) : m_atomic(std::move(t)) { + + } + + AtomicWrapper(const AtomicWrapper &other) : m_atomic(other.m_atomic.load()) { + + } + + AtomicWrapper(AtomicWrapper &&other) noexcept = delete; + + AtomicWrapper &operator=(const AtomicWrapper &other) { + if (&other != this) { + *this = AtomicWrapper(other); + } + + return *this; + } + + AtomicWrapper &operator=(AtomicWrapper &&other) noexcept = delete; + + const std::atomic &value() const { + return m_atomic; + } + + std::atomic &value() { + return m_atomic; + } + +private: + std::atomic m_atomic; +}; +} +} diff --git a/project/3rdparty/include/hope/connection.hpp b/project/3rdparty/include/hope/connection.hpp new file mode 100644 index 000000000..b5fab60d2 --- /dev/null +++ b/project/3rdparty/include/hope/connection.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include + +namespace hope { +class Connection { +public: + Connection(std::int64_t id) : m_valid(true), m_id(id) { + + } + + std::int64_t id() const { + return m_id; + } + + bool valid() const { + return m_valid; + } + + bool operator==(const Connection &other) const { + return (m_id == other.m_id); + } + + bool operator<(const Connection &other) const { + return (m_id < other.m_id); + } + +private: + const bool m_valid = false; + const std::int64_t m_id; +}; +} diff --git a/project/3rdparty/include/hope/event.hpp b/project/3rdparty/include/hope/event.hpp new file mode 100644 index 000000000..84ea29b0d --- /dev/null +++ b/project/3rdparty/include/hope/event.hpp @@ -0,0 +1,8 @@ +#pragma once + +namespace hope { +class Event { +public: + virtual ~Event() = default; +}; +} diff --git a/project/3rdparty/include/hope/eventloop.hpp b/project/3rdparty/include/hope/eventloop.hpp new file mode 100644 index 000000000..40d008c8b --- /dev/null +++ b/project/3rdparty/include/hope/eventloop.hpp @@ -0,0 +1,64 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "event.hpp" +#include "object.hpp" +#include "atomicwrapper.hpp" + +namespace hope { +namespace detail { +class ObjectData; +} + +namespace test { +class EventLoopTestHelper; +} + +class EventLoop : public Object { +public: + using Mutex = std::mutex; + using Locker = std::unique_lock; + using Clock = std::chrono::steady_clock; + using TimePoint = std::chrono::time_point; + + EventLoop(); + ~EventLoop() override; + + bool is_running() const; + + void push_event(std::shared_ptr event, TimePoint when = Clock::now()); + void push_event(std::shared_ptr event, std::chrono::milliseconds duration, TimePoint offset = Clock::now()); + + void quit(int exit_code = 0); + int exec(); + + void register_object(Object *object); + void unregister_object(Object *object); + + void on_event(Event *event) final; + +private: + friend class EventLoopTestHelper; + + int loop(); + + void process_events(const std::vector> &events); + void cleanup_objects(); + + int m_exit_code = 0; + bool m_exit = false; + bool m_is_running = false; + mutable Mutex m_mutex; + mutable Mutex m_objects_mutex; + std::condition_variable m_cond; + std::multimap> m_events; + std::map> m_objects; +}; +} diff --git a/project/3rdparty/include/hope/hope.hpp b/project/3rdparty/include/hope/hope.hpp new file mode 100644 index 000000000..76e621104 --- /dev/null +++ b/project/3rdparty/include/hope/hope.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include "timer.hpp" +#include "event.hpp" +#include "thread.hpp" +#include "signal.hpp" +#include "optional.hpp" +#include "eventloop.hpp" +#include "threaddata.hpp" +#include "objectdata.hpp" +#include "connection.hpp" +#include "application.hpp" +#include "atomicwrapper.hpp" +#include "indexsequence.hpp" +#include "objectinvoker.hpp" +#include "queuedinvokationevent.hpp" diff --git a/project/3rdparty/include/hope/indexsequence.hpp b/project/3rdparty/include/hope/indexsequence.hpp new file mode 100644 index 000000000..f2d53f9f1 --- /dev/null +++ b/project/3rdparty/include/hope/indexsequence.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include + +namespace hope { +namespace detail { +template +struct index_sequence { + +}; + +template +struct index_sequence_builder { + using result = typename index_sequence_builder < K - 1, K - 1, Remainder... >::result; +}; + +template +struct index_sequence_builder<0, Remainder...> { + using result = index_sequence; +}; + +template +using make_index_sequence = typename index_sequence_builder::result; +} +} diff --git a/project/3rdparty/include/hope/object.hpp b/project/3rdparty/include/hope/object.hpp new file mode 100644 index 000000000..6d2a3628f --- /dev/null +++ b/project/3rdparty/include/hope/object.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include +#include +#include + +namespace hope { +class Event; +class Thread; + +namespace detail { +class ObjectData; +} + +class Object { +public: + Object(); + virtual ~Object(); + + void move_to_thread(Thread *thread); + void move_to_thread(std::thread::id thread); + + virtual void on_event(Event *event); + +protected: + Object(bool initialize); + + void terminate(); + void initialize(); + + std::atomic m_initialized; + std::atomic m_terminated; + std::shared_ptr m_data; +}; +} diff --git a/project/3rdparty/include/hope/objectdata.hpp b/project/3rdparty/include/hope/objectdata.hpp new file mode 100644 index 000000000..788027f82 --- /dev/null +++ b/project/3rdparty/include/hope/objectdata.hpp @@ -0,0 +1,92 @@ +#pragma once + +#include +#include +#include +#include + +namespace hope { +class Object; + +namespace detail { +class ObjectData { +public: + ObjectData(std::thread::id id) : m_thread_id(std::move(id)) { + + } + + std::unique_lock lock() { + return lock(*this); + } + + static std::unique_lock lock(const std::shared_ptr &data) { + return data ? lock(*data) : std::unique_lock(); + } + + static std::unique_lock lock(ObjectData &data) { + return std::unique_lock(data.m_mutex); + } + + static std::pair, std::unique_lock> lock(const std::shared_ptr &first, const std::shared_ptr &second) { + if (first && second) { + return lock(*first, *second); + } else if (first) { + return { lock(*first), std::unique_lock() }; + } else if (second) { + return { lock(*second), std::unique_lock() }; + } else { + return { std::unique_lock(), std::unique_lock() }; + } + } + + static std::pair, std::unique_lock> lock(ObjectData &first, ObjectData &second) { + ObjectData *first_address = &first; + ObjectData *second_address = &second; + + if (first_address >= second_address) { + std::swap(first_address, second_address); + } + + std::unique_lock first_lock = lock(*first_address); + std::unique_lock second_lock; + if (first_address != second_address) { + second_lock = lock(*second_address); + } + + return {std::move(first_lock), std::move(second_lock)}; + } + + std::thread::id m_thread_id; + std::mutex m_mutex; +}; + +class ObjectDataRegistry { +public: + static ObjectDataRegistry &instance(); + + ObjectDataRegistry(const ObjectDataRegistry &) = delete; + ObjectDataRegistry(ObjectDataRegistry &&) = delete; + + ObjectDataRegistry &operator=(const ObjectDataRegistry &) = delete; + ObjectDataRegistry &operator=(ObjectDataRegistry &&) = delete; + + std::weak_ptr data(Object *object) { + auto it = m_data.find(object); + return it != m_data.end() ? it->second : std::weak_ptr(); + } + + void register_object_data(Object *object, const std::shared_ptr &data) { + m_data.emplace(object, data); + } + + void unregister_object_data(Object *object) { + m_data.erase(object); + } + +private: + ObjectDataRegistry() = default; + + std::map> m_data; +}; +} +} diff --git a/project/3rdparty/include/hope/objectinvoker.hpp b/project/3rdparty/include/hope/objectinvoker.hpp new file mode 100644 index 000000000..e43249dab --- /dev/null +++ b/project/3rdparty/include/hope/objectinvoker.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include "signal.hpp" + +namespace hope { +class ObjectInvoker { +public: + ObjectInvoker() = delete; + + template + static void invoke(Object *obj, void(Object::*func)(Args... args), ConnectionType type, Args...args) { + Signal signal; + signal.connect(obj, func, type); + signal.emit(args...); + } +}; +} diff --git a/project/3rdparty/include/hope/optional.hpp b/project/3rdparty/include/hope/optional.hpp new file mode 100644 index 000000000..3234e8189 --- /dev/null +++ b/project/3rdparty/include/hope/optional.hpp @@ -0,0 +1,91 @@ +#pragma once + +namespace hope { +namespace detail { +template +class Optional { +public: + Optional() : m_ok(false) { + } + + Optional(T &&data) : m_ok(true), m_data(std::move(data)) { + + } + + Optional(const T &data) : m_ok(true), m_data(data) { + + } + + template + Optional(Args && ... args) : m_ok(true), m_data(std::forward(args...)) { + + } + + Optional(const Optional &) = default; + Optional(Optional &&) noexcept = default; + + Optional &operator=(const Optional &) = default; + Optional &operator=(Optional &&) noexcept = default; + + Optional &operator=(const T &data) { + m_ok = true; + m_data = data; + return *this; + } + + Optional& operator=(T &&data) { + m_ok = true; + m_data = std::move(data); + return *this; + } + + void reset() { + m_ok = false; + } + + T &data() { + return m_data; + } + + const T &data() const { + return m_data; + } + + bool is_some() const { + return m_ok; + } + + bool is_none() const { + return !m_ok; + } + + operator T() { + return m_data; + } + + operator bool() { + return m_ok; + } + + T *operator->() { + return m_ok ? &m_data : nullptr; + } + + const T *operator->() const { + return m_ok ? &m_data : nullptr; + } + + bool operator==(const Optional &other) const { + return (m_ok == other.m_ok) && (m_data == other.m_data); + } + + bool operator!=(const Optional &other) const { + return !operator==(other); + } + +private: + bool m_ok = false; + T m_data; +}; +} +} diff --git a/project/3rdparty/include/hope/queuedinvokationevent.hpp b/project/3rdparty/include/hope/queuedinvokationevent.hpp new file mode 100644 index 000000000..c00857db9 --- /dev/null +++ b/project/3rdparty/include/hope/queuedinvokationevent.hpp @@ -0,0 +1,141 @@ +#pragma once + +#include +#include + +#include "event.hpp" +#include "indexsequence.hpp" + +namespace hope { +class Object; + +class QueuedInvokationEventBase : public Event { +public: + virtual Object *object() = 0; + virtual void invoke() = 0; + virtual void wait() = 0; +}; + +template +class QueuedInvokation final : public QueuedInvokationEventBase { + using ObjectFuncPtr = void(Object::* const)(Args...); + using ObjectFuncArgs = std::tuple; + +public: + QueuedInvokation(Object *object, ObjectFuncPtr object_func, Args...args) + : m_object(object), m_object_func(object_func), m_object_func_args(std::move(args)...) + { + + } + + QueuedInvokation() = default; + QueuedInvokation(const QueuedInvokation &) = delete; + QueuedInvokation(QueuedInvokation &&) noexcept = default; + + QueuedInvokation &operator=(const QueuedInvokation &) = delete; + QueuedInvokation &operator=(QueuedInvokation &&) noexcept = default; + + Object *object() final { + return m_object; + } + + void invoke() final { + invoke_impl(m_object_func_args); + } + + void wait() final { + std::unique_lock lock(m_mutex); + m_cond.wait(lock, [this] { + return m_executed; + }); + } + + template + void invoke_impl(Tuple &a, hope::detail::index_sequence) { + (m_object->*m_object_func)(std::move(std::get(a))...); + set_executed(); + } + + template> + void invoke_impl(std::tuple &t) { + invoke_impl(t, Indices{}); + } + + void set_executed() { + { + std::lock_guard lock(m_mutex); + m_executed = true; + } + + m_cond.notify_all(); + } + + Object *const m_object = nullptr; + ObjectFuncPtr m_object_func = nullptr; + ObjectFuncArgs m_object_func_args; + std::mutex m_mutex; + std::condition_variable m_cond; + bool m_executed = false; +}; + +template +class QueuedInvokation final : public QueuedInvokationEventBase { + using ObjectFuncPtr = void(Object::* const)(); + +public: + QueuedInvokation(Object *object, ObjectFuncPtr object_func) : m_object(object), m_object_func(object_func) { + + } + + QueuedInvokation() = default; + QueuedInvokation(const QueuedInvokation &) = delete; + QueuedInvokation(QueuedInvokation &&) noexcept = default; + + QueuedInvokation &operator=(const QueuedInvokation &) = delete; + QueuedInvokation &operator=(QueuedInvokation &&) noexcept = default; + + Object *object() final { + return m_object; + } + + void invoke() final { + (m_object->*m_object_func)(); + set_executed(); + } + + void wait() final { + std::unique_lock lock(m_mutex); + m_cond.wait(lock, [this] { + return m_executed; + }); + } + +private: + void set_executed() { + { + std::lock_guard lock(m_mutex); + m_executed = true; + } + + m_cond.notify_all(); + } + + Object *const m_object = nullptr; + ObjectFuncPtr m_object_func = nullptr; + std::mutex m_mutex; + std::condition_variable m_cond; + bool m_executed = false; +}; + +template +std::shared_ptr> make_queued_invokation_event(Object *object, void(Object::*object_func)(Args...), Args...args) +{ + return std::make_shared>(object, object_func, std::move(args)...); +} + +template +std::shared_ptr> make_queued_invokation_event(Object *object, void(Object::*object_func)()) +{ + return std::make_shared>(object, object_func); +} +} diff --git a/project/3rdparty/include/hope/signal.hpp b/project/3rdparty/include/hope/signal.hpp new file mode 100644 index 000000000..c8c6b6c26 --- /dev/null +++ b/project/3rdparty/include/hope/signal.hpp @@ -0,0 +1,195 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "optional.hpp" +#include "objectdata.hpp" +#include "threaddata.hpp" +#include "connection.hpp" +#include "queuedinvokationevent.hpp" + +namespace hope { +enum class ConnectionType { + Auto, Direct, Queued, QueuedBlocking +}; + +namespace detail { +template +struct BaseInvoker { + BaseInvoker() : m_valid(true) { + + } + + virtual ~BaseInvoker() = default; + + virtual void invoke(Args... args) const = 0; + + virtual const void *receiver_pointer() const = 0; + virtual const void *receiver_func_pointer() const = 0; + + std::atomic m_valid; +}; + +template +struct Invoker final : public BaseInvoker { + using ReceiverMemFunc = void(Receiver::*)(Args...); + + Invoker(Receiver *receiver, ReceiverMemFunc receiver_func, ConnectionType type) : m_receiver(receiver), m_receiver_func(receiver_func), m_type(type) { + + } + + void invoke(Args...args) const final { + switch (m_type) { + case ConnectionType::Auto: + invoke_auto(std::move(args)...); + break; + + case ConnectionType::Direct: + invoke_direct(std::move(args)...); + break; + + case ConnectionType::Queued: + invoke_queued(std::move(args)...); + break; + + case ConnectionType::QueuedBlocking: + invoke_queued_blocking(std::move(args)...); + break; + } + } + + void invoke_auto(Args...args) const { + if (Optional thread_id = receiver_thread_id()) { + if (std::this_thread::get_id() != thread_id) { + invoke_queued(std::forward(args)...); + } else { + invoke_direct(std::forward(args)...); + } + } + } + + void invoke_queued(Args...args) const { + if (Optional thread_id = receiver_thread_id()) { + auto event = make_queued_invokation_event(m_receiver, m_receiver_func, std::move(args)...); + ThreadDataRegistry::instance().thread_data(thread_id)->push_event(std::move(event)); + } + } + + void invoke_queued_blocking(Args...args) const { + if (Optional thread_id = receiver_thread_id()) { + auto event = make_queued_invokation_event(m_receiver, m_receiver_func, std::move(args)...); + ThreadDataRegistry::instance().thread_data(thread_id)->push_event(event); + event->wait(); + } + } + + void invoke_direct(Args...args) const { + if (is_receiver_alive()) { + (m_receiver->*m_receiver_func)(std::move(args)...); + } + } + + const void *receiver_pointer() const final { + return m_receiver; + } + + const void *receiver_func_pointer() const final { + return &m_receiver_func; + } + +private: + Optional receiver_thread_id() const { + Optional result; + if (auto data = ObjectDataRegistry::instance().data(m_receiver).lock()) { + auto lock = ObjectData::lock(data); + result = data->m_thread_id; + } + + return result; + } + + bool is_receiver_alive() const { + return ObjectDataRegistry::instance().data(m_receiver).lock() != nullptr; + } + + Receiver *const m_receiver = nullptr; + const ReceiverMemFunc m_receiver_func = nullptr; + const ConnectionType m_type; +}; + +template +std::shared_ptr> make_invoker(Receiver *receiver, void(Receiver::*func)(Args...), ConnectionType type) +{ + return std::make_shared>(receiver, func, type); +} +} + +template +class Signal { +public: + using SignalInvoker = std::shared_ptr>; + + Signal() = default; + Signal(const Signal &other) = delete; + Signal(Signal &&other) noexcept = default; + + Signal &operator=(const Signal &other) = delete; + Signal &operator=(Signal &&other) noexcept = default; + + void emit(Args... args) { + for (const auto &pair : objects()) { + if (pair.second->m_valid) { + pair.second->invoke(std::move(args)...); + } + } + } + + template::value, int>::type = 0> + Connection connect(Receiver *receiver, void(Receiver::*func)(Args...args), ConnectionType type = ConnectionType::Auto) { + std::lock_guard lock(m_mutex); + Connection result = get_next_connection_id(); + m_objects.emplace(result, detail::make_invoker(receiver, func, type)); + return result; + } + + void disconnect(Connection c) { + std::lock_guard lock(m_mutex); + auto it = m_objects.find(c); + if (it != m_objects.end()) { + it->second->m_valid = false; + m_objects.erase(it); + } + } + + template::value, int>::type = 0> + void disconnect(Receiver *receiver, void(Receiver::*func)(Args...args)) { + std::lock_guard lock(m_mutex); + for (auto it = m_objects.begin(); it != m_objects.end(); ++it) { + const SignalInvoker& invoker = it->second; + auto it_func = static_cast(invoker->receiver_func_pointer()); + if ((invoker->receiver_pointer() == receiver) && (*it_func == func)) { + m_objects.erase(it); + return; + } + } + } + +private: + std::map objects() const { + std::lock_guard lock(m_mutex); + return m_objects; + } + + Connection get_next_connection_id() { + return m_next_connection_id++; + } + + mutable std::mutex m_mutex; + std::map m_objects; + int64_t m_next_connection_id = 0; +}; +} diff --git a/project/3rdparty/include/hope/thread.hpp b/project/3rdparty/include/hope/thread.hpp new file mode 100644 index 000000000..4e0769b67 --- /dev/null +++ b/project/3rdparty/include/hope/thread.hpp @@ -0,0 +1,41 @@ +#pragma once + +#include +#include +#include + +namespace hope { +class Object; +class EventLoop; + +class Thread { +public: + Thread(); + ~Thread(); + + std::thread::id id() const; + + void quit(); + void start(); + + void wait(); + void move_to_thread(std::unique_ptr obj); + +private: + void exec(); + + enum class State { + Starting, + Started, + Stopping, + Stopped + }; + + mutable std::mutex m_mutex; + State m_state = State::Stopped; + std::condition_variable m_cond; + std::thread m_thread; + std::unique_ptr m_event_loop = nullptr; + std::vector> m_children; +}; +} diff --git a/project/3rdparty/include/hope/threaddata.hpp b/project/3rdparty/include/hope/threaddata.hpp new file mode 100644 index 000000000..5d79e6413 --- /dev/null +++ b/project/3rdparty/include/hope/threaddata.hpp @@ -0,0 +1,88 @@ +#pragma once + +#include +#include +#include +#include + +#include "eventloop.hpp" + +namespace hope { +class EventLoop; + +class ThreadData { +public: + ThreadData() = default; + + ThreadData(std::thread::id id, EventLoop *event_loop) : m_id(id), m_event_loop(event_loop) { + + } + + std::thread::id thread_id() const { + return m_id; + } + + void set_event_loop(EventLoop *event_loop) { + std::lock_guard lock(m_mutex); + m_event_loop = event_loop; + } + + void register_object(Object *object) { + std::lock_guard lock(m_mutex); + if (m_event_loop) { + m_event_loop->register_object(object); + } else { + std::cerr << "No event event loop when registering object " << object << std::endl; + } + } + + void unregister_object(Object *object) { + std::lock_guard lock(m_mutex); + if (m_event_loop) { + m_event_loop->unregister_object(object); + } else { + std::cerr << "No event event loop when unregistering object " << object << std::endl; + } + } + + template + void push_event(T... args) { + std::lock_guard lock(m_mutex); + if (m_event_loop) { + m_event_loop->push_event(std::forward(args)...); + } else { + std::cerr << "No event event loop when pushing event" << std::endl; + } + } + +private: + mutable std::mutex m_mutex; + const std::thread::id m_id; + EventLoop *m_event_loop = nullptr; +}; + +class ThreadDataRegistry { +public: + static ThreadDataRegistry &instance(); + + std::shared_ptr thread_data(const std::thread::id &id) { + std::lock_guard lock(m_mutex); + auto it = m_registry.find(id); + if (it == m_registry.end()) { + it = m_registry.emplace(id, std::make_shared(id, nullptr)).first; + } + + return it->second; + } + + std::shared_ptr current_thread_data() { + return thread_data(std::this_thread::get_id()); + } + +private: + ThreadDataRegistry() = default; + + mutable std::mutex m_mutex; + mutable std::unordered_map> m_registry; +}; +} diff --git a/project/3rdparty/include/hope/timer.hpp b/project/3rdparty/include/hope/timer.hpp new file mode 100644 index 000000000..8f1592490 --- /dev/null +++ b/project/3rdparty/include/hope/timer.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include + +#include "object.hpp" +#include "signal.hpp" + +namespace hope { +class Timer final : public Object { +public: + Timer(); + ~Timer() override; + + std::chrono::milliseconds duration() const; + + void set_duration(std::chrono::milliseconds duration); + + Signal<> &triggered(); + + void start(); + +protected: + void on_event(Event *event) final; + +private: + std::chrono::milliseconds m_duration; + Signal<> m_triggered; +}; +} diff --git a/project/3rdparty/source/Kconfig b/project/3rdparty/source/Kconfig index f31035980..ba9b9e036 100644 --- a/project/3rdparty/source/Kconfig +++ b/project/3rdparty/source/Kconfig @@ -1,3 +1,4 @@ +source "project/3rdparty/source/hope/Kconfig" source "project/3rdparty/source/media/Kconfig" source "project/3rdparty/source/libdrm/Kconfig" source "project/3rdparty/source/rockchip/Kconfig" diff --git a/project/3rdparty/source/Makefile b/project/3rdparty/source/Makefile index 7a89a59ed..7d59f4efc 100644 --- a/project/3rdparty/source/Makefile +++ b/project/3rdparty/source/Makefile @@ -1,3 +1,4 @@ +obj-$(CONFIG_HOPE) += hope/ obj-$(CONFIG_LIBDRM) += libdrm/ obj-$(CONFIG_MEDIA_API) += media/ obj-$(CONFIG_ROCKCHIP) += rockchip/ diff --git a/project/3rdparty/source/hope/Kconfig b/project/3rdparty/source/hope/Kconfig new file mode 100644 index 000000000..28f915781 --- /dev/null +++ b/project/3rdparty/source/hope/Kconfig @@ -0,0 +1,13 @@ +# +# Hope Kconfig +# + +menu "Hope" + +config HOPE + bool "A simple pure cplusplus event loop with signals and slots" + default n + help + A simple pure cplusplus event loop with signals and slots + +endmenu diff --git a/project/3rdparty/source/hope/Makefile b/project/3rdparty/source/hope/Makefile new file mode 100644 index 000000000..049bc56e8 --- /dev/null +++ b/project/3rdparty/source/hope/Makefile @@ -0,0 +1,7 @@ +obj-y += timer.o +obj-y += object.o +obj-y += thread.o +obj-y += eventloop.o +obj-y += objectdata.o +obj-y += threaddata.o +obj-y += application.o diff --git a/project/3rdparty/source/hope/application.cpp b/project/3rdparty/source/hope/application.cpp new file mode 100644 index 000000000..31a4b193a --- /dev/null +++ b/project/3rdparty/source/hope/application.cpp @@ -0,0 +1,26 @@ +#include +#include +#include +#include + +namespace hope { +Application::Application() : Object(false) +{ + initialize(); +} + +Application::~Application() +{ + terminate(); +} + +void Application::quit(int exit_code) +{ + m_event_loop.quit(exit_code); +} + +int Application::exec() +{ + return m_event_loop.exec(); +} +} diff --git a/project/3rdparty/source/hope/eventloop.cpp b/project/3rdparty/source/hope/eventloop.cpp new file mode 100644 index 000000000..c688e789f --- /dev/null +++ b/project/3rdparty/source/hope/eventloop.cpp @@ -0,0 +1,158 @@ +#include +#include +#include + +using namespace hope; +using namespace detail; + +namespace { +class RegisterEvent final : public Event { +public: + RegisterEvent(Object *object) : m_object(object) { + + } + + Object *m_object = nullptr; +}; +} + +EventLoop::EventLoop() : Object(false) +{ + { + auto lock = m_data->lock(); + ThreadDataRegistry::instance().thread_data(m_data->m_thread_id)->set_event_loop(this); + } + + initialize(); +} + +EventLoop::~EventLoop() +{ + terminate(); + { + auto lock = m_data->lock(); + ThreadDataRegistry::instance().thread_data(m_data->m_thread_id)->set_event_loop(nullptr); + } +} + +bool EventLoop::is_running() const +{ + Locker locker(m_mutex); + return m_is_running; +} + +void EventLoop::push_event(std::shared_ptr event, EventLoop::TimePoint when) +{ + Locker lock(m_mutex); + m_events.emplace(when, std::move(event)); + m_cond.notify_one(); +} + +void EventLoop::push_event(std::shared_ptr event, std::chrono::milliseconds duration, EventLoop::TimePoint offset) +{ + Locker lock(m_mutex); + m_events.emplace(offset + duration, std::move(event)); + m_cond.notify_one(); +} + +void EventLoop::quit(int exit_code) +{ + Locker lock(m_mutex); + m_exit = true; + m_exit_code = exit_code; + m_cond.notify_one(); +} + +int EventLoop::exec() +{ + return loop(); +} + +void EventLoop::register_object(Object *object) +{ + std::unique_ptr event(new RegisterEvent(object)); + push_event(std::move(event)); +} + +void EventLoop::unregister_object(Object *object) +{ + Locker lock(m_objects_mutex); + auto it = m_objects.find(object); + if (it != m_objects.end()) { + it->second.value() = false; + } +} + +void EventLoop::on_event(Event *event) +{ + if (auto registerEvent = dynamic_cast(event)) { + Locker lock(m_objects_mutex); + m_objects.emplace(registerEvent->m_object, true); + } else { + Object::on_event(event); + } +} + +int EventLoop::loop() +{ + { + Locker lock(m_mutex); + m_is_running = true; + } + + std::vector> events; + + while (true) { + while (true) { + Locker lock(m_mutex); + if (m_exit) { + m_is_running = false; + return m_exit_code; + } else if (m_events.empty()) { + m_cond.wait(lock); + } else { + const TimePoint now = Clock::now(); + while (!m_events.empty() && (m_events.begin()->first <= now)) { + events.emplace_back(std::move(m_events.begin()->second)); + m_events.erase(m_events.begin()); + } + + if (!events.empty()) { + break; + } + + m_cond.wait_until(lock, m_events.begin()->first); + } + } + + process_events(events); + cleanup_objects(); + + events.clear(); + } +} + +void EventLoop::process_events(const std::vector > &events) +{ + for (const std::shared_ptr &event : events) { + on_event(event.get()); + + for (const auto &object : m_objects) { + if (object.second.value()) { + object.first->on_event(event.get()); + } + } + } +} + +void EventLoop::cleanup_objects() +{ + Locker lock(m_objects_mutex); + for (auto it = m_objects.begin(); it != m_objects.end(); ) { + if (!it->second.value()) { + it = m_objects.erase(it); + } else { + ++it; + } + } +} diff --git a/project/3rdparty/source/hope/object.cpp b/project/3rdparty/source/hope/object.cpp new file mode 100644 index 000000000..08b185ffd --- /dev/null +++ b/project/3rdparty/source/hope/object.cpp @@ -0,0 +1,87 @@ + +#include +#include +#include +#include +#include +#include +#include + +using namespace hope; +using namespace detail; + +Object::Object(bool init) : m_initialized(false) , m_terminated(false) , m_data(std::make_shared(std::this_thread::get_id())) +{ + if (init) { + initialize(); + } +} + +Object::Object() : Object(true) +{ + +} + +void Object::initialize() +{ + if (m_initialized.exchange(true)) { + return; + } + + ObjectDataRegistry::instance().register_object_data(this, m_data); + { + auto lock = m_data->lock(); + ThreadDataRegistry::instance().thread_data(m_data->m_thread_id)->register_object(this); + } +} + +void Object::terminate() +{ + if (m_terminated.exchange(true)) { + return; + } + + { + auto lock = m_data->lock(); + if (m_data->m_thread_id != std::this_thread::get_id()) { + std::cerr << "Destroying an object from different thread" << std::endl; + } + + ThreadDataRegistry::instance().thread_data(m_data->m_thread_id)->unregister_object(this); + } + + ObjectDataRegistry::instance().unregister_object_data(this); +} + +Object::~Object() +{ + terminate(); +} + +void Object::move_to_thread(Thread *thread) +{ + if (!thread) { + std::cerr << "Passing null pointer to move to thread" << std::endl; + return; + } + + move_to_thread(thread->id()); +} + +void Object::move_to_thread(std::thread::id thread) +{ + auto lock = m_data->lock(); + ThreadDataRegistry::instance().thread_data(m_data->m_thread_id)->unregister_object(this); + m_data->m_thread_id = thread; + + ThreadDataRegistry::instance().thread_data(m_data->m_thread_id)->register_object(this); +} + +void Object::on_event(Event *event) +{ + if (auto signal_event = dynamic_cast(event)) { + if (signal_event->object() == this) { + signal_event->invoke(); + } + } +} diff --git a/project/3rdparty/source/hope/objectdata.cpp b/project/3rdparty/source/hope/objectdata.cpp new file mode 100644 index 000000000..3b0468f57 --- /dev/null +++ b/project/3rdparty/source/hope/objectdata.cpp @@ -0,0 +1,11 @@ +#include + +namespace hope { +namespace detail { +ObjectDataRegistry &ObjectDataRegistry::instance() +{ + static ObjectDataRegistry instance; + return instance; +} +} +} diff --git a/project/3rdparty/source/hope/thread.cpp b/project/3rdparty/source/hope/thread.cpp new file mode 100644 index 000000000..f846fcff1 --- /dev/null +++ b/project/3rdparty/source/hope/thread.cpp @@ -0,0 +1,133 @@ +#include +#include +#include + +#include +#include +#include + +using namespace hope; + +Thread::Thread() = default; + +Thread::~Thread() +{ + quit(); + wait(); +} + +std::thread::id Thread::id() const +{ + std::lock_guard lock(m_mutex); + return m_thread.get_id(); +} + +void Thread::start() +{ + std::unique_lock lock(m_mutex); + if ((m_state == State::Started) || (m_state == State::Starting)) { + std::cerr << "Thread already started" << std::endl; + return; + } + + if (m_state == State::Stopping) { + m_cond.wait(lock, [this] { + return (m_state == State::Stopped); + }); + } + + if (m_thread.joinable()) { + m_thread.join(); + } + + assert(m_state == State::Stopped); + + m_state = State::Starting; + m_cond.notify_all(); + + m_thread = std::thread([this] { + exec(); + }); + + m_cond.wait(lock, [this] { + return (m_state == State::Started); + }); + + assert(m_state == State::Started); +} + +void Thread::quit() +{ + std::unique_lock lock(m_mutex); + if ((m_state == State::Stopping) || (m_state == State::Stopped)) { + return; + } + + if (m_state == State::Starting) { + m_cond.wait(lock, [this] { + return (m_state == State::Started); + }); + } + + assert(m_state == State::Started); + + m_state = State::Stopping; + m_cond.notify_all(); + m_event_loop->quit(); +} + +void Thread::wait() +{ + std::unique_lock lock(m_mutex); + if (m_state != State::Stopped) { + m_cond.wait(lock, [this] { + return (m_state == State::Stopped); + }); + } + + if (m_thread.joinable()) { + m_thread.join(); + } +} + +void Thread::move_to_thread(std::unique_ptr obj) +{ + std::unique_lock lock(m_mutex); + if ((m_state == State::Stopping) || (m_state == State::Stopped)) { + std::cerr << "Trying to move and object to a thread that has not been started" << std::endl; + } + + if (m_state == State::Starting) { + m_cond.wait(lock, [this] { + return (m_state == State::Started); + }); + } + + obj->move_to_thread(m_thread.get_id()); + assert(m_state == State::Started); + m_children.push_back(std::move(obj)); +} + +void Thread::exec() +{ + { + m_mutex.lock(); + assert(m_state == State::Starting); + m_event_loop.reset(new EventLoop()); + m_state = State::Started; + m_cond.notify_all(); + m_mutex.unlock(); + } + + m_event_loop->exec(); + + { + m_mutex.lock(); + assert(m_state == State::Stopping); + m_children.clear(); + m_event_loop.reset(); + m_state = State::Stopped; + m_cond.notify_all(); + m_mutex.unlock(); + } +} diff --git a/project/3rdparty/source/hope/threaddata.cpp b/project/3rdparty/source/hope/threaddata.cpp new file mode 100644 index 000000000..b3c0db8f2 --- /dev/null +++ b/project/3rdparty/source/hope/threaddata.cpp @@ -0,0 +1,9 @@ +#include + +namespace hope { +ThreadDataRegistry &ThreadDataRegistry::instance() +{ + static ThreadDataRegistry instance; + return instance; +} +} diff --git a/project/3rdparty/source/hope/timer.cpp b/project/3rdparty/source/hope/timer.cpp new file mode 100644 index 000000000..93d07dfeb --- /dev/null +++ b/project/3rdparty/source/hope/timer.cpp @@ -0,0 +1,59 @@ +#include +#include +#include + +using namespace hope; + +struct TimerEvent final : public Event { +public: + TimerEvent(Timer *event); + ~TimerEvent() override; + + Timer *m_timer = nullptr; +}; + +TimerEvent::TimerEvent(Timer *event) : m_timer(event) +{ + +} + +TimerEvent::~TimerEvent() = default; + +Timer::Timer(): m_duration(std::chrono::milliseconds(0)) +{ + +} + +Timer::~Timer() = default; + +std::chrono::milliseconds Timer::duration() const +{ + return m_duration; +} + +void Timer::set_duration(std::chrono::milliseconds duration) +{ + m_duration = duration; +} + +Signal<> &Timer::triggered() +{ + return m_triggered; +} + +void Timer::start() +{ + auto lock = m_data->lock(); + ThreadDataRegistry::instance().thread_data(m_data->m_thread_id)->push_event(std::unique_ptr(new TimerEvent(this)), m_duration); +} + +void Timer::on_event(Event *event) +{ + if (auto timer_event = dynamic_cast(event)) { + if (timer_event->m_timer == this) { + m_triggered.emit(); + } + } else { + Object::on_event(event); + } +} diff --git a/project/unittest/host/signalslot/Kconfig b/project/unittest/host/signalslot/Kconfig index e4c6ed565..c109ba256 100644 --- a/project/unittest/host/signalslot/Kconfig +++ b/project/unittest/host/signalslot/Kconfig @@ -18,6 +18,10 @@ choice config HOST_SIGNAL_SLOTS bool "host signal slots unittest demo" + config HOST_SIGSLOT_THREADED_PRODUCER_CONSUMER + bool "host signal slots threaded producer consumer unittest demo" + select HOPE + config HOST_SIGSLOT_UNKNOWN bool "host signal-slot unittest unknow demo" diff --git a/project/unittest/host/signalslot/Makefile b/project/unittest/host/signalslot/Makefile index 43e134175..1eeb67692 100644 --- a/project/unittest/host/signalslot/Makefile +++ b/project/unittest/host/signalslot/Makefile @@ -1,2 +1,3 @@ obj-y += sigslot_unittest.o obj-$(CONFIG_HOST_SIGNAL_SLOTS) += host_unittest_signal_slots_example.o +obj-$(CONFIG_HOST_SIGSLOT_THREADED_PRODUCER_CONSUMER) += host_unittest_sigslot_threaded_producer_consumer_example.o diff --git a/project/unittest/host/signalslot/host_unittest_sigslot_threaded_producer_consumer_example.cpp b/project/unittest/host/signalslot/host_unittest_sigslot_threaded_producer_consumer_example.cpp new file mode 100644 index 000000000..942cc4c33 --- /dev/null +++ b/project/unittest/host/signalslot/host_unittest_sigslot_threaded_producer_consumer_example.cpp @@ -0,0 +1,72 @@ +#include +#include + +#include "private.h" + +using Product = std::string; + +struct Producer : public hope::Object { + Producer() { + m_timer.set_duration(std::chrono::seconds(1)); + m_timer.triggered().connect(this, &Producer::on_triggered); + } + + hope::Signal<> &finished() { + return m_finished_signal; + } + + hope::Signal &product_available() { + return m_product_available_signal; + } + + void produce() { + m_timer.start(); + } + +private: + void on_triggered() { + if (num_products != 5) { + auto product = "Product " + std::to_string(num_products++); + std::cout << "Producing product " << product << " from thread " << std::this_thread::get_id() << std::endl; + + m_product_available_signal.emit(std::move(product)); + m_timer.start(); + } else { + m_finished_signal.emit(); + } + } + + hope::Timer m_timer; + hope::Signal m_product_available_signal; + hope::Signal<> m_finished_signal; + int num_products = 0; +}; + +struct Consumer : public hope::Object { + void consume(Product product) { + std::cout << "Consuming " << product << " from thread " << std::this_thread::get_id() << std::endl; + } +}; + +int host_unittest_sigslot_threaded_producer_consumer_exit(void) +{ + return -1; +} + +int host_unittest_sigslot_threaded_producer_consumer_init(int argc, char *argv[]) +{ + hope::Application app; + std::unique_ptr consumer (new Consumer()); + + Producer producer; + producer.product_available().connect(consumer.get(), &Consumer::consume); + producer.finished().connect(&app, &hope::Application::quit); + + hope::Thread consumer_thread; + consumer_thread.start(); + consumer_thread.move_to_thread(std::move(consumer)); + + producer.produce(); + + return app.exec(); +} diff --git a/project/unittest/host/signalslot/private.h b/project/unittest/host/signalslot/private.h index 63fb51e9f..b4005c3cd 100644 --- a/project/unittest/host/signalslot/private.h +++ b/project/unittest/host/signalslot/private.h @@ -28,6 +28,9 @@ extern "C" { int host_unittest_sigslot_exit(void); int host_unittest_sigslot_init(int argc, char *argv[]); +int host_unittest_sigslot_threaded_producer_consumer_exit(void); +int host_unittest_sigslot_threaded_producer_consumer_init(int argc, char *argv[]); + #if defined(__cplusplus) } #endif diff --git a/project/unittest/host/signalslot/sigslot_unittest.cpp b/project/unittest/host/signalslot/sigslot_unittest.cpp index 672ad0d7a..3149809f9 100644 --- a/project/unittest/host/signalslot/sigslot_unittest.cpp +++ b/project/unittest/host/signalslot/sigslot_unittest.cpp @@ -7,6 +7,8 @@ int host_sigslot_unittest_init(int argc, char *argv[]) { #if defined(CONFIG_HOST_SIGNAL_SLOTS) return host_unittest_sigslot_init(argc, argv); +#elif defined(CONFIG_HOST_SIGSLOT_THREADED_PRODUCER_CONSUMER) + return host_unittest_sigslot_threaded_producer_consumer_init(argc, argv); #endif return -1; @@ -16,6 +18,8 @@ int host_sigslot_unittest_exit(void) { #if defined(CONFIG_HOST_SIGNAL_SLOTS) return host_unittest_sigslot_exit(); +#elif defined(CONFIG_HOST_SIGSLOT_THREADED_PRODUCER_CONSUMER) + return host_unittest_sigslot_threaded_producer_consumer_exit(); #endif return -1;