Repository: tripleslash/rocket Branch: master Commit: ce3a936f21bc Files: 5 Total size: 122.0 KB Directory structure: gitextract_zivlhdr6/ ├── .gitignore ├── CMakeLists.txt ├── README.md ├── example.cpp └── rocket.hpp ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .vscode .clangd build compile_commands.json ================================================ FILE: CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.2) project(rocket CXX) option(BUILD_EXAMPLES "Build examples" ON) # C++ standard set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # generate compile_commands.json if possible set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # include dir of rocket.hpp include_directories(${CMAKE_CURRENT_SOURCE_DIR}) if(BUILD_EXAMPLES) message(STATUS "build examples") add_executable(example example.cpp) endif() ================================================ FILE: README.md ================================================ # rocket - Fast C++ Observer Pattern Rocket is a public-domain, single-header implementation of a signal/slots library for C++. The library was developed because existing solutions were too inflexible, too slow, or came as a part of a larger dependency (for example boost::signals and boost::signals2). ## Design goals 1. Efficiency. The library takes special care to not use cache unfriendly code (such as calling virtual methods) unless absolutely necessary. 2. Low memory footprint (does not allocate during signal emission). 3. Modern C++. No bloat from overloading 50 template specializations for each function. 4. Single header file implementation. 5. No dependencies. The API was heavily inspired by boost::signals2. If you are already familiar with boost::signals2, switching to rocket will be a no brainer. ## What makes rocket unique? 1. The library provides both a thread-safe and a thread-unsafe implementation. No efficiency loss due to locks or atomics in the thread-unsafe implementation. 2. Policy based design. Specify at declaration time and invocation time of the signal how _you_ want the call results to be returned. 3. The signals are reentrant. This property is a must have for any event processing library because it must be possible to recursively emit signals, or disconnect slots from within a signal handler. 4. Support for smart `scoped_connection`'s and `scoped_connection_container`'s. 5. Support for automatic lifetime tracking of observers via `rocket::trackable`. 6. Allows slots to get an instance to the `current_connection` object (see example 5). 7. Allows slots to preemtively abort the emission of the signal (see example 6). 8. Support for Qt-style `queued_connection`'s. If a slot is connected to a signal with this flag, the slots execution will be scheduled on the same thread that connected the slot to the signal (see example 7). 9. Supports `set_interval` and `set_timeout` functions to connect periodic events to the current threads dispatch queue (you can opt-out of this feature by defining `ROCKET_NO_TIMERS`). ## Performance Because the main focus of this library was to provide an efficient single threaded implementation, `rocket::signal` has about the same overhead as an iteration through an `std::list>`. Here are some performance benchmarks between boost::signals2 and rocket for registering 10 slots to each signal type and emitting each one 1000 times. | Library | Avg. execution time | | -------------- | -------------------:| | boost::signals2 | 810.389 µs | | rocket::signal | 98.155 µs | ## 1. Creating your first signal ```cpp #include int main() { rocket::signal thread_unsafe_signal; rocket::thread_safe_signal thread_safe_signal; // Connecting the first handler to our signal thread_unsafe_signal.connect([]() { std::cout << "First handler called!" << std::endl; }); // Connecting a second handler to our signal using alternative syntax thread_unsafe_signal += []() { std::cout << "Second handler called!" << std::endl; }; // Invoking the signal thread_unsafe_signal(); } // Output: // First handler called! // Second handler called! ``` ## 2. Connecting class methods to the signal ```cpp #include #include class Subject { public: void setName(const std::string& newName) { if (name != newName) { name = newName; nameChanged(newName); } } public: rocket::signal nameChanged; private: std::string name; }; class Observer { public: Observer(Subject& subject) { // Register the `onNameChanged`-function of this object as a listener and // store the resultant connection object in the listener's connection set. // This is all your need to do for the most common case, if you want the // connection to be broken when the observer is destroyed. connections += { subject.nameChanged.connect<&Observer::onNameChanged>(this) }; } void onNameChanged(const std::string& name) { std::cout << "Subject received new name: " << name << std::endl; } private: rocket::scoped_connection_container connections; }; int main() { Subject s; Observer o{ s }; s.setName("Peter"); } // Output: // Subject received new name: Peter ``` ### Another example: Binding pure virtual interface methods ```cpp #include #include #include class ILogger { public: virtual void logMessage(const std::string& message) = 0; }; class ConsoleLogger : public ILogger { public: void logMessage(const std::string& message) override { std::cout << "New log message: " << message << std::endl; } }; class App { public: void run() { if (work()) { onSuccess("I finished my work!"); } } bool work() { return true; } public: rocket::signal onSuccess; }; int main() { std::unique_ptr app = std::make_unique(); std::unique_ptr logger = std::make_unique(); app->onSuccess.connect<&ILogger::logMessage>(logger.get()); app->run(); } // Output: // New log message: I finished my work! ``` ## 3.a Handling lifetime and scope of connection objects What if we want to destroy our logger instance from example 2 but continue to use the app instance? **Solution:** We use `scoped_connection`-objects to track our connected slots! ```cpp // [...] (See example 2) int main() { std::unique_ptr app = std::make_unique(); { std::unique_ptr logger = std::make_unique(); rocket::scoped_connection connection = app->onSuccess .connect(logger.get(), &ILogger::logMessage); app->run(); } //<-- `logger`-instance is destroyed at the end of this block //<-- The `connection`-object is also destroyed here // and therefore removed from App::onSuccess. // Run the app a second time // // This would normally cause a crash / undefined behavior because the logger // instance is destroyed at this point, but App::onSuccess still referenced it // in example 2. app->run(); } // Output: // New log message: I finished my work! ``` ## 3.b Advanced lifetime tracking The library can also track the lifetime of your class objects for you, if the connected slot instances inherit from the `rocket::trackable` base class. ```cpp // [...] (See example 2) struct ILogger : rocket::trackable { virtual void logMessage(const std::string& message) = 0; }; // [...] (See example 2) int main() { std::unique_ptr app = std::make_unique(); { std::unique_ptr logger = std::make_unique(); app->onSuccess.connect(logger.get(), &ILogger::logMessage); app->run(); } //<-- `logger`-instance is destroyed at the end of this block //<-- Because `ILogger` inherits from `rocket::trackable`, the signal knows // about its destruction and will automatically disconnect the slot! // Run the app a second time // // This would normally cause a crash / undefined behavior because the logger // instance is destroyed at this point, but App::onSuccess still referenced it // in example 2. app->run(); } ``` ## 4. Getting return values from a call to a signal Slots can also return values to the emitting signal. Because a signal can have several slots attached to it, the return values are collected by using the so called `value collectors`. The default value collector returns an `optional` from a call to a `signal::operator()` However, this behaviour can be overriden at declaration time of the signal as well as during signal invocation. ```cpp #include #include int main() { rocket::signal signal; // The library supports argument and return type transformation between the // signal and the slots. We show this by connecting the `float sqrtf(float)` // function to a signal with an `int` argument and `int` return value. signal.connect(std::sqrtf); std::cout << "Computed value: " << *signal(16); } // Output: // Computed value: 4 ``` ```cpp #include #include #include int main() { // Because we set `rocket::range` as the value collector for this signal // calling operator() now returns the return values of all connected slots. rocket::signal> signal; // Lets connect a couple more functions to our signal and print all the // return values. signal.connect(std::sinf); signal.connect(std::cosf); std::cout << std::fixed << std::setprecision(2); for (auto result : signal(3.14159)) { std::cout << result << std::endl; } // We can also override the return value collector at invocation time std::cout << "First return value: " << signal.invoke>(3.14159); std::cout << std::endl; std::cout << "Last return value: " << signal.invoke>(3.14159); } // Output: // 0.00 // -1.00 // First return value: 0.00 // Last return value: -1.00 ``` ## 5. Accessing the current connection object inside a slot Sometimes it is desirable to get an instance to the current connection object inside of a slot function. An example would be if you want to make a callback that only fires once and then disconnects itself from the signal that called it. ```cpp #include int main() { rocket::signal signal; signal.connect([] { std::cout << "Slot called. Now disconnecting..." << std::endl; // `current_connection` is stored in thread-local-storage. rocket::current_connection().disconnect(); }); signal(); signal(); signal(); } // Output: // Slot called. Now disconnecting... ``` ## 6. Preemtively aborting the emission of a signal A slot can preemtively abort the emission of a signal if it needs to. This is useful in scenarios where your slot functions try to find some value and you just want the result of the first slot that found one and stop other slots from running. ```cpp #include int main() { rocket::signal signal; signal.connect([] { std::cout << "First slot called. Aborting emission of other slots." << std::endl; rocket::abort_emission(); // Notice that this doesn't disconnect the other slots. It just breaks out of the // signal emitting loop. }); signal.connect([] { std::cout << "Second slot called. Should never happen." << std::endl; }); signal(); } // Output: // First slot called. Aborting emission of other slots. ``` ## 7. Using `queued_connection` to build a message queue between threads An observer can connect slots to a subject with the `queued_connection`-flag. Instead of calling the slot directly when the signal is invoked, rocket will schedule the execution in the same thread from where the observer called the `connect`-function. With this system it is extremely easy to build a message queue between different threads. Lets say we have a subject called `ModelFileLoaderThread`. It loads files from disc and does some expensive preprocessing. We also have an observer. The `RenderThread`. The `RenderThread` now wants to know whenever a new file is fully loaded, so it can display it in the scene. ```cpp class ModelFileLoaderThread { public: void start() { shouldRun = true; thread = std::thread(&ModelFileLoaderThread::run, this); } void shutdown() { shouldRun = false; thread.join(); } void pushLoadRequest(const std::string& fileName) { std::scoped_lock guard{ mutex }; loadRequests.push_front(fileName); } private: void run() { while (shouldRun) { std::forward_list requests; { std::scoped_lock guard{ mutex }; loadRequests.swap(requests); } for (auto& fileName : requests) { ModelFilePtr modelFile = new ModelFile(fileName); if (modelFile->loadModel()) { modelLoaded(modelFile); } else { modelLoadFailed(fileName); } } std::this_thread::sleep_for(std::chrono::milliseconds(50)); } } public: rocket::thread_safe_signal modelLoaded; rocket::thread_safe_signal modelLoadFailed; private: std::thread thread; volatile bool shouldRun = false; std::mutex mutex; std::forward_list loadRequests; }; ``` ```cpp class RenderThread { public: void initialize() { modelLoaderThread.start(); // Connect to the thread using queued_connection flag modelLoaderThread.modelLoaded.connect<&RenderThread::onModelLoaded>(this, rocket::queued_connection); modelLoaderThread.modelLoadFailed.connect<&RenderThread::onModelLoadFailed>(this, rocket::queued_connection); } void shutdown() { modelLoaderThread.shutdown(); } void render() { rocket::dispatch_queued_calls(); //<-- This call is required so rocket can call the queued slots from this thread for (IRenderablePtr& renderable : renderables) { renderable->render(); } } private: // These slots are actually executed from inside the `rocket::dispatch_queued_calls` method void onModelLoaded(ModelFilePtr const& modelFile) { // No lock needed, because onModelLoaded is called on render thread! renderables.push_back(new ModelRenderer(modelFile)); } void onModelLoadFailed(std::string const& fileName) { // Show log message } private: std::list renderables; ModelFileLoaderThread modelLoaderThread; }; ``` ## 8. Using `set_interval` and `set_timeout` Other than signals and slots, rocket can also schedule timers for you! They work similar to the `queued_connections` shown in example 7. ```cpp // [...] (See example 7) class RenderThread { public: void initialize() { modelLoaderThread.start(); connections += { // Register a timer that gets called every 5000 ms rocket::set_interval<&RenderThread::onClearRenderablesTimerExpired>(this, 5000), // Connect to the thread using queued_connection flag modelLoaderThread.modelLoaded.connect<&RenderThread::onModelLoaded>(this, rocket::queued_connection), modelLoaderThread.modelLoadFailed.connect<&RenderThread::onModelLoadFailed>(this, rocket::queued_connection) }; } void shutdown() { modelLoaderThread.shutdown(); } void render() { rocket::dispatch_queued_calls(); //<-- This call is required so rocket can call the queued slots from this thread for (IRenderablePtr& renderable : renderables) { // Will be rendered for at most 5 seconds because `onClearRenderablesTimerExpired` periodically clears the renderables renderable->render(); } } private: // These slots are actually executed from inside the `rocket::dispatch_queued_calls` method void onModelLoaded(ModelFilePtr const& modelFile) { // No lock needed, because onModelLoaded is called on render thread! renderables.push_back(new ModelRenderer(modelFile)); } void onModelLoadFailed(std::string const& fileName) { // Show log message } void onClearRenderablesTimerExpired() { // Called every 5000 ms from inside the `rocket::dispatch_queud_calls` method renderables.clear(); } private: rocket::scoped_connection_container connections; std::list renderables; ModelFileLoaderThread modelLoaderThread; }; ``` ================================================ FILE: example.cpp ================================================ #include "rocket.hpp" #include struct Testing : rocket::trackable { int hello(float a) { std::cout << "Testing: " << a << std::endl; return 0; } }; struct NonDefaultConstructible { explicit NonDefaultConstructible(int x) : value{ x } { } ~NonDefaultConstructible() { std::cout << "Destructor called for value: " << value << std::endl; } NonDefaultConstructible(NonDefaultConstructible&& n) : value{ n.value } { n.value = 0; } NonDefaultConstructible& operator = (NonDefaultConstructible&& n) { value = n.value; n.value = 0; return *this; } private: int value; NonDefaultConstructible() = delete; NonDefaultConstructible(NonDefaultConstructible const&) = delete; NonDefaultConstructible& operator = (NonDefaultConstructible const&) = delete; }; struct TestShared : std::enable_shared_from_this { int hello(int a) { return 321; } }; int main() { NonDefaultConstructible n{ 1337 }; { rocket::stable_list list; list.push_back(std::move(n)); } { rocket::stable_list list1{ 1, 2, 3, 4, 5 }; rocket::stable_list list2{ list1.crbegin(), list1.crend() }; list2.resize(3); std::cout << "List size: " << list2.size() << std::endl; for (auto elem : list2) std::cout << elem << ' '; std::cout << std::endl; } rocket::signal test; test.connect([](int x) { return x * 3; }); test.connect([](int x) { return x * 1; }); test.connect([](int x) { return x * 2; }); { // Give me the minimal value of all slots typedef rocket::minimum selector; std::cout << "Minimum: " << test.invoke(5) << std::endl; } { // Give me the last result in an optional (default behaviour) rocket::optional r{ test(5) }; std::cout << "Optional: " << *r << std::endl; } // Connect a new slot via scoped connections { rocket::scoped_connection scoped{ test.connect([](int x) { return x * 4; }) }; // Give me all results in a list typedef rocket::range selector; std::cout << "Range: "; for (int x : test.invoke(5)) { std::cout << x << " "; } std::cout << std::endl; } { // The connections are only connected as long as the testing-object is alive Testing testing; test.connect(testing, &Testing::hello); test.connect(testing, &Testing::hello); test(1337); } { auto classPtr = std::make_shared(); auto f = rocket::bind_weak_ptr(classPtr, &TestShared::hello); f(2); } { // A slot that kills itself after the first call test.connect([](int) { // Get the connection object associated with this slot and kill it rocket::current_connection().disconnect(); std::cout << "called slot disconnect!" << std::endl; return 0; }); test(1337); test(1337); } { // A slot that aborts emission after the first call test.connect([](int) { rocket::abort_emission(); std::cout << "called abort!" << std::endl; return 0; }); test.connect([](int) { std::cout << "This should never show up, as the previous slot aborts the emission!" << std::endl; return 0; }); test(1337); } std::cin.get(); return 0; } ================================================ FILE: rocket.hpp ================================================ /*********************************************************************************** * rocket - lightweight & fast signal/slots & utility library * * * * v2.1 - public domain * * no warranty is offered or implied; use this code at your own risk * * * * AUTHORS * * * * Written by Michael Bleis * * * * * * LICENSE * * * * This software is dual-licensed to the public domain and under the following * * license: you are granted a perpetual, irrevocable license to copy, modify, * * publish, and distribute this file as you see fit. * ***********************************************************************************/ #ifndef ROCKET_HPP_INCLUDED #define ROCKET_HPP_INCLUDED /*********************************************************************************** * CONFIGURATION * * ------------------------------------------------------------------------------- * * Define this if you want to disable exceptions. * * ------------------------------------------------------------------------------- */ #ifndef ROCKET_NO_EXCEPTIONS # define ROCKET_NO_EXCEPTIONS #endif /*********************************************************************************** * ------------------------------------------------------------------------------- * * Define this if you want to disable the `stable_list` collection in rocket. * * ------------------------------------------------------------------------------- */ #ifndef ROCKET_NO_STABLE_LIST # define ROCKET_NO_STABLE_LIST #endif /*********************************************************************************** * ------------------------------------------------------------------------------- * * Define this if you want to disable `set_timeout` and `set_interval` features. * * ------------------------------------------------------------------------------- */ #ifndef ROCKET_NO_TIMERS # define ROCKET_NO_TIMERS #endif /*********************************************************************************** * ------------------------------------------------------------------------------- * * Define this if you want to disable the connection blocking feature. * * ------------------------------------------------------------------------------- */ #ifndef ROCKET_NO_BLOCKING_CONNECTIONS # define ROCKET_NO_BLOCKING_CONNECTIONS #endif /*********************************************************************************** * ------------------------------------------------------------------------------- * * Define this if you want to disable the queued connection feature. * * ------------------------------------------------------------------------------- */ #ifndef ROCKET_NO_QUEUED_CONNECTIONS # define ROCKET_NO_QUEUED_CONNECTIONS #endif /*********************************************************************************** * ------------------------------------------------------------------------------- * * Define this if you want to disable the smart pointer extensions feature. * * ------------------------------------------------------------------------------- */ #ifndef ROCKET_NO_SMARTPOINTER_EXTENSIONS # define ROCKET_NO_SMARTPOINTER_EXTENSIONS #endif /*********************************************************************************** * ------------------------------------------------------------------------------- * * Redefine this if your compiler doesn't support the `thread_local`-keyword. * * For Visual Studio < 2015 you can define it to `__declspec(thread)` for example. * * ------------------------------------------------------------------------------- */ #ifndef ROCKET_THREAD_LOCAL # define ROCKET_THREAD_LOCAL thread_local #endif #include #include #include #include #include #include #include #include #include #include #include #include #ifndef ROCKET_NO_QUEUED_CONNECTIONS # include # include # include # include # include #endif #ifndef ROCKET_NO_EXCEPTIONS # include #endif #ifndef ROCKET_NO_SMARTPOINTER_EXTENSIONS # include #endif #if !defined(ROCKET_NO_STABLE_LIST) || !defined(ROCKET_NO_QUEUED_CONNECTIONS) # include #endif #if !defined(ROCKET_NO_TIMERS) || !defined(ROCKET_NO_QUEUED_CONNECTIONS) # include #endif #if __has_cpp_attribute(likely) # define ROCKET_LIKELY [[likely]] #else # define ROCKET_LIKELY #endif #if __has_cpp_attribute(unlikely) # define ROCKET_UNLIKELY [[unlikely]] #else # define ROCKET_UNLIKELY #endif #if __has_cpp_attribute(maybe_unused) # define ROCKET_MAYBE_UNUSED [[maybe_unused]] #else # define ROCKET_MAYBE_UNUSED #endif #if __has_cpp_attribute(nodiscard) # define ROCKET_NODISCARD [[nodiscard]] #else # define ROCKET_NODISCARD #endif #if __has_cpp_attribute(no_unique_address) # define ROCKET_NO_UNIQUE_ADDRESS [[no_unique_address]] #else # if defined(_MSC_VER) && __cplusplus >= 202002L # define ROCKET_NO_UNIQUE_ADDRESS [[msvc::no_unique_address]] # else # define ROCKET_NO_UNIQUE_ADDRESS # endif #endif namespace rocket { template struct minimum { using value_type = T; using result_type = T; template void operator()(U&& value) { if (!has_value || value < current) { current = std::forward(value); has_value = true; } } ROCKET_NODISCARD result_type result() { return std::move(current); } private: value_type current{}; bool has_value{ false }; }; template struct maximum { using value_type = T; using result_type = T; template void operator()(U&& value) { if (!has_value || value > current) { current = std::forward(value); has_value = true; } } ROCKET_NODISCARD result_type result() { return std::move(current); } private: value_type current{}; bool has_value{ false }; }; template struct first { using value_type = T; using result_type = T; template void operator()(U&& value) { if (!has_value) { current = std::forward(value); has_value = true; } } ROCKET_NODISCARD result_type result() { return std::move(current); } private: value_type current{}; bool has_value{ false }; }; template struct last { using value_type = T; using result_type = T; template void operator()(U&& value) { current = std::forward(value); } ROCKET_NODISCARD result_type result() { return std::move(current); } private: value_type current{}; }; template struct range { using value_type = T; using result_type = std::list; template void operator()(U&& value) { values.emplace_back(std::forward(value)); } ROCKET_NODISCARD result_type result() { return std::move(values); } private: std::list values; }; #ifndef ROCKET_NO_EXCEPTIONS struct error : std::exception { }; struct bad_optional_access final : error { const char* what() const noexcept override { return "rocket: Bad optional access."; } }; struct invocation_slot_error final : error { const char* what() const noexcept override { return "rocket: One of the slots has raised an exception during the signal invocation."; } }; #endif template using optional = std::optional; template struct intrusive_ptr final { using value_type = T; using element_type = T; using pointer = T*; using reference = T&; template friend struct intrusive_ptr; constexpr intrusive_ptr() noexcept : ptr{ nullptr } { } constexpr intrusive_ptr(std::nullptr_t) noexcept : ptr{ nullptr } { } explicit intrusive_ptr(pointer p) noexcept : ptr{ p } { if (ptr) { ptr->addref(); } } intrusive_ptr(intrusive_ptr const& p) noexcept : ptr{ p.ptr } { if (ptr) { ptr->addref(); } } intrusive_ptr(intrusive_ptr&& p) noexcept : ptr{ p.ptr } { p.ptr = nullptr; } template explicit intrusive_ptr(intrusive_ptr const& p) noexcept : ptr{ p.ptr } { if (ptr) { ptr->addref(); } } template explicit intrusive_ptr(intrusive_ptr&& p) noexcept : ptr{ p.ptr } { p.ptr = nullptr; } ~intrusive_ptr() noexcept { if (ptr) { ptr->release(); } } ROCKET_NODISCARD pointer get() const noexcept { return ptr; } pointer detach() noexcept { pointer p = ptr; ptr = nullptr; return p; } ROCKET_NODISCARD operator pointer() const noexcept { return ptr; } ROCKET_NODISCARD pointer operator->() const noexcept { assert(ptr != nullptr); return ptr; } ROCKET_NODISCARD reference operator*() const noexcept { assert(ptr != nullptr); return *ptr; } ROCKET_NODISCARD pointer* operator&() noexcept { assert(ptr == nullptr); return &ptr; } ROCKET_NODISCARD pointer const* operator&() const noexcept { return &ptr; } intrusive_ptr& operator=(pointer p) noexcept { if (p) { p->addref(); } pointer o = ptr; ptr = p; if (o) { o->release(); } return *this; } intrusive_ptr& operator=(std::nullptr_t) noexcept { if (ptr) { ptr->release(); ptr = nullptr; } return *this; } intrusive_ptr& operator=(intrusive_ptr const& p) noexcept { return (*this = p.ptr); } intrusive_ptr& operator=(intrusive_ptr&& p) noexcept { if (ptr) { ptr->release(); } ptr = p.ptr; p.ptr = nullptr; return *this; } template intrusive_ptr& operator=(intrusive_ptr const& p) noexcept { return (*this = p.ptr); } template intrusive_ptr& operator=(intrusive_ptr&& p) noexcept { if (ptr) { ptr->release(); } ptr = p.ptr; p.ptr = nullptr; return *this; } void swap(pointer* pp) noexcept { pointer p = ptr; ptr = *pp; *pp = p; } void swap(intrusive_ptr& p) noexcept { swap(&p.ptr); } private: pointer ptr; }; template ROCKET_NODISCARD inline bool operator==(intrusive_ptr const& a, intrusive_ptr const& b) noexcept { return a.get() == b.get(); } template ROCKET_NODISCARD inline bool operator==(intrusive_ptr const& a, U* b) noexcept { return a.get() == b; } template ROCKET_NODISCARD inline bool operator==(T* a, intrusive_ptr const& b) noexcept { return a == b.get(); } template ROCKET_NODISCARD inline bool operator!=(intrusive_ptr const& a, intrusive_ptr const& b) noexcept { return a.get() != b.get(); } template ROCKET_NODISCARD inline bool operator!=(intrusive_ptr const& a, U* b) noexcept { return a.get() != b; } template ROCKET_NODISCARD inline bool operator!=(T* a, intrusive_ptr const& b) noexcept { return a != b.get(); } template ROCKET_NODISCARD inline bool operator<(intrusive_ptr const& a, intrusive_ptr const& b) noexcept { return a.get() < b.get(); } template ROCKET_NODISCARD inline bool operator<(intrusive_ptr const& a, U* b) noexcept { return a.get() < b; } template ROCKET_NODISCARD inline bool operator<(T* a, intrusive_ptr const& b) noexcept { return a < b.get(); } template ROCKET_NODISCARD inline bool operator<=(intrusive_ptr const& a, intrusive_ptr const& b) noexcept { return a.get() <= b.get(); } template ROCKET_NODISCARD inline bool operator<=(intrusive_ptr const& a, U* b) noexcept { return a.get() <= b; } template ROCKET_NODISCARD inline bool operator<=(T* a, intrusive_ptr const& b) noexcept { return a <= b.get(); } template ROCKET_NODISCARD inline bool operator>(intrusive_ptr const& a, intrusive_ptr const& b) noexcept { return a.get() > b.get(); } template ROCKET_NODISCARD inline bool operator>(intrusive_ptr const& a, U* b) noexcept { return a.get() > b; } template ROCKET_NODISCARD inline bool operator>(T* a, intrusive_ptr const& b) noexcept { return a > b.get(); } template ROCKET_NODISCARD inline bool operator>=(intrusive_ptr const& a, intrusive_ptr const& b) noexcept { return a.get() >= b.get(); } template ROCKET_NODISCARD inline bool operator>=(intrusive_ptr const& a, U* b) noexcept { return a.get() >= b; } template ROCKET_NODISCARD inline bool operator>=(T* a, intrusive_ptr const& b) noexcept { return a >= b.get(); } template ROCKET_NODISCARD inline bool operator==(intrusive_ptr const& a, std::nullptr_t) noexcept { return a.get() == nullptr; } template ROCKET_NODISCARD inline bool operator==(std::nullptr_t, intrusive_ptr const& b) noexcept { return nullptr == b.get(); } template ROCKET_NODISCARD inline bool operator!=(intrusive_ptr const& a, std::nullptr_t) noexcept { return a.get() != nullptr; } template ROCKET_NODISCARD inline bool operator!=(std::nullptr_t, intrusive_ptr const& b) noexcept { return nullptr != b.get(); } template ROCKET_NODISCARD inline T* get_pointer(intrusive_ptr const& p) noexcept { return p.get(); } template ROCKET_NODISCARD inline intrusive_ptr static_pointer_cast(intrusive_ptr const& p) noexcept { return intrusive_ptr{ static_cast(p.get()) }; } template ROCKET_NODISCARD inline intrusive_ptr const_pointer_cast(intrusive_ptr const& p) noexcept { return intrusive_ptr{ const_cast(p.get()) }; } template ROCKET_NODISCARD inline intrusive_ptr dynamic_pointer_cast(intrusive_ptr const& p) noexcept { return intrusive_ptr{ dynamic_cast(p.get()) }; } template ROCKET_NODISCARD inline intrusive_ptr reinterpret_pointer_cast(intrusive_ptr const& p) noexcept { return intrusive_ptr{ reinterpret_cast(p.get()) }; } struct ref_count final { unsigned long addref() noexcept { return ++count; } unsigned long release() noexcept { return --count; } ROCKET_NODISCARD unsigned long get() const noexcept { return count; } private: unsigned long count{ 0 }; }; struct ref_count_atomic final { unsigned long addref() noexcept { return ++count; } unsigned long release() noexcept { return --count; } ROCKET_NODISCARD unsigned long get() const noexcept { return count.load(std::memory_order_relaxed); } private: std::atomic count{ 0 }; }; template struct ref_counted { ref_counted() noexcept = default; ref_counted(ref_counted const&) noexcept { } ref_counted& operator=(ref_counted const&) noexcept { return *this; } void addref() noexcept { count.addref(); } void release() noexcept { if (count.release() == 0) { delete static_cast(this); } } protected: ~ref_counted() noexcept = default; private: RefCount count{}; }; #ifndef ROCKET_NO_STABLE_LIST template class stable_list final { struct link_element final : ref_counted { link_element() noexcept = default; ~link_element() noexcept { if (next) { // If we have a next element upon destruction value()->~T();// then this link is used, else it's a dummy } } template void construct(Args&&... args) noexcept(noexcept(T{ std::forward(args)... })) { new (storage()) T{ std::forward(args)... }; } T* value() noexcept { return std::launder(static_cast(storage())); } void* storage() noexcept { return static_cast(&buffer); } intrusive_ptr next; intrusive_ptr prev; alignas(T) std::byte buffer[sizeof(T)]; }; intrusive_ptr head; intrusive_ptr tail; std::size_t elements; public: template struct iterator_base final { using iterator_category = std::bidirectional_iterator_tag; using value_type = std::remove_const_t; using difference_type = ptrdiff_t; using reference = U&; using pointer = U*; template friend class stable_list; iterator_base() noexcept = default; ~iterator_base() noexcept = default; iterator_base(iterator_base const& i) noexcept : element{ i.element } { } iterator_base(iterator_base&& i) noexcept : element{ std::move(i.element) } { } template explicit iterator_base(iterator_base const& i) noexcept : element{ i.element } { } template explicit iterator_base(iterator_base&& i) noexcept : element{ std::move(i.element) } { } iterator_base& operator=(iterator_base const& i) noexcept { element = i.element; return *this; } iterator_base& operator=(iterator_base&& i) noexcept { element = std::move(i.element); return *this; } template iterator_base& operator=(iterator_base const& i) noexcept { element = i.element; return *this; } template iterator_base& operator=(iterator_base&& i) noexcept { element = std::move(i.element); return *this; } iterator_base& operator++() noexcept { element = element->next; return *this; } iterator_base operator++(int) noexcept { iterator_base i{ *this }; ++(*this); return i; } iterator_base& operator--() noexcept { element = element->prev; return *this; } iterator_base operator--(int) noexcept { iterator_base i{ *this }; --(*this); return i; } ROCKET_NODISCARD reference operator*() const noexcept { return *element->value(); } ROCKET_NODISCARD pointer operator->() const noexcept { return element->value(); } template ROCKET_NODISCARD bool operator==(iterator_base const& i) const noexcept { return element == i.element; } template ROCKET_NODISCARD bool operator!=(iterator_base const& i) const noexcept { return element != i.element; } private: intrusive_ptr element; iterator_base(link_element* p) noexcept : element{ p } { } }; using value_type = T; using reference = T&; using pointer = T*; using const_pointer = const T*; using size_type = std::size_t; using difference_type = std::ptrdiff_t; using iterator = iterator_base; using const_iterator = iterator_base; using reverse_iterator = std::reverse_iterator; using const_reverse_iterator = std::reverse_iterator; stable_list() { init(); } ~stable_list() { destroy(); } stable_list(stable_list const& l) { init(); insert(end(), l.begin(), l.end()); } stable_list(stable_list&& l) : head{ std::move(l.head) } , tail{ std::move(l.tail) } , elements{ l.elements } { l.init(); } stable_list(std::initializer_list l) { init(); insert(end(), l.begin(), l.end()); } template stable_list(Iterator ibegin, Iterator iend) { init(); insert(end(), ibegin, iend); } explicit stable_list(size_type count, value_type const& value) { init(); insert(end(), count, value); } explicit stable_list(size_type count) { init(); insert(end(), count, value_type{}); } stable_list& operator=(stable_list const& l) { if (this != &l) { clear(); insert(end(), l.begin(), l.end()); } return *this; } stable_list& operator=(stable_list&& l) { destroy(); head = std::move(l.head); tail = std::move(l.tail); elements = l.elements; l.init(); return *this; } ROCKET_NODISCARD iterator begin() noexcept { return iterator{ head->next }; } ROCKET_NODISCARD iterator end() noexcept { return iterator{ tail }; } ROCKET_NODISCARD const_iterator begin() const noexcept { return const_iterator{ head->next }; } ROCKET_NODISCARD const_iterator end() const noexcept { return const_iterator{ tail }; } ROCKET_NODISCARD const_iterator cbegin() const noexcept { return const_iterator{ head->next }; } ROCKET_NODISCARD const_iterator cend() const noexcept { return const_iterator{ tail }; } ROCKET_NODISCARD reverse_iterator rbegin() noexcept { return reverse_iterator{ end() }; } ROCKET_NODISCARD reverse_iterator rend() noexcept { return reverse_iterator{ begin() }; } ROCKET_NODISCARD const_reverse_iterator rbegin() const noexcept { return const_reverse_iterator{ cend() }; } ROCKET_NODISCARD const_reverse_iterator rend() const noexcept { return const_reverse_iterator{ cbegin() }; } ROCKET_NODISCARD const_reverse_iterator crbegin() const noexcept { return const_reverse_iterator{ cend() }; } ROCKET_NODISCARD const_reverse_iterator crend() const noexcept { return const_reverse_iterator{ cbegin() }; } ROCKET_NODISCARD reference front() noexcept { return *begin(); } ROCKET_NODISCARD reference back() noexcept { return *rbegin(); } ROCKET_NODISCARD value_type const& front() const noexcept { return *cbegin(); } ROCKET_NODISCARD value_type const& back() const noexcept { return *crbegin(); } ROCKET_NODISCARD bool empty() const noexcept { return cbegin() == cend(); } void clear() noexcept { erase(begin(), end()); } void push_front(value_type const& value) { insert(begin(), value); } void push_front(value_type&& value) { insert(begin(), std::move(value)); } void push_back(value_type const& value) { insert(end(), value); } void push_back(value_type&& value) { insert(end(), std::move(value)); } template reference emplace_front(Args&&... args) { return *emplace(begin(), std::forward(args)...); } template reference emplace_back(Args&&... args) { return *emplace(end(), std::forward(args)...); } void pop_front() noexcept { head->next = head->next->next; head->next->prev = head; --elements; } void pop_back() noexcept { tail->prev = tail->prev->prev; tail->prev->next = tail; --elements; } iterator insert(iterator const& pos, value_type const& value) { return iterator{ make_link(pos.element, value) }; } iterator insert(iterator const& pos, value_type&& value) { return iterator{ make_link(pos.element, std::move(value)) }; } template iterator insert(iterator const& pos, Iterator ibegin, Iterator iend) { iterator iter{ end() }; while (ibegin != iend) { iterator tmp{ insert(pos, *ibegin++) }; if (iter == end()) { iter = std::move(tmp); } } return iter; } iterator insert(iterator const& pos, std::initializer_list l) { return insert(pos, l.begin(), l.end()); } iterator insert(iterator const& pos, size_type count, value_type const& value) { iterator iter{ end() }; for (size_type i = 0; i < count; ++i) { iterator tmp{ insert(pos, value) }; if (iter == end()) { iter = std::move(tmp); } } return iter; } template iterator emplace(iterator const& pos, Args&&... args) { return iterator{ make_link(pos.element, std::forward(args)...) }; } void append(value_type const& value) { insert(end(), value); } void append(value_type&& value) { insert(end(), std::move(value)); } template void append(Iterator ibegin, Iterator iend) { insert(end(), ibegin, iend); } void append(std::initializer_list l) { insert(end(), std::move(l)); } void append(size_type count, value_type const& value) { insert(end(), count, value); } void assign(size_type count, value_type const& value) { clear(); append(count, value); } template void assign(Iterator ibegin, Iterator iend) { clear(); append(ibegin, iend); } void assign(std::initializer_list l) { clear(); append(std::move(l)); } void resize(size_type count) { resize(count, value_type{}); } void resize(size_type count, value_type const& value) { size_type cursize = size(); if (count > cursize) { for (size_type i = cursize; i < count; ++i) { push_back(value); } } else { for (size_type i = count; i < cursize; ++i) { pop_back(); } } } ROCKET_NODISCARD size_type size() const noexcept { return elements; } ROCKET_NODISCARD size_type max_size() const noexcept { return std::numeric_limits::max(); } iterator erase(iterator const& pos) noexcept { pos.element->prev->next = pos.element->next; pos.element->next->prev = pos.element->prev; --elements; return iterator{ pos.element->next }; } iterator erase(iterator const& first, iterator const& last) noexcept { auto link = first.element; while (link != last.element) { auto next = link->next; link->prev = first.element->prev; link->next = last.element; --elements; link = std::move(next); } first.element->prev->next = last.element; last.element->prev = first.element->prev; return last; } void remove(value_type const& value) noexcept { for (auto itr = begin(); itr != end(); ++itr) { if (*itr == value) { erase(itr); } } } template void remove_if(Predicate const& pred) { for (auto itr = begin(); itr != end(); ++itr) { if (pred(*itr)) { erase(itr); } } } void swap(stable_list& other) noexcept { if (this != &other) { head.swap(other.head); tail.swap(other.tail); std::swap(elements, other.elements); } } private: void init() { head = new link_element; tail = new link_element; head->next = tail; tail->prev = head; elements = 0; } void destroy() { clear(); head->next = nullptr; tail->prev = nullptr; } template link_element* make_link(link_element* l, Args&&... args) { intrusive_ptr link{ new link_element }; link->construct(std::forward(args)...); link->prev = l->prev; link->next = l; link->prev->next = link; link->next->prev = link; ++elements; return link; } }; #endif//~ ROCKET_NO_STABLE_LIST template struct threading_policy { static constexpr bool is_thread_safe = ThreadSafe; }; using thread_safe_policy = threading_policy; using thread_unsafe_policy = threading_policy; namespace detail { template struct expand_signature; template struct expand_signature final { using return_type = R; using signature_type = R(Args...); }; template using get_return_type = typename expand_signature::return_type; struct shared_lock final : ref_counted { std::mutex mutex; }; template struct shared_lock_state; template <> struct shared_lock_state final { using threading_policy = thread_unsafe_policy; constexpr void lock() noexcept { } constexpr bool try_lock() noexcept { return true; } constexpr void unlock() noexcept { } constexpr void swap(shared_lock_state&) noexcept { } }; template <> struct shared_lock_state final { using threading_policy = thread_safe_policy; shared_lock_state() : lock_primitive{ new shared_lock } { } ~shared_lock_state() = default; shared_lock_state(shared_lock_state const& s) : lock_primitive{ s.lock_primitive } { } shared_lock_state(shared_lock_state&& s) : lock_primitive{ std::move(s.lock_primitive) } { s.lock_primitive = new shared_lock; } shared_lock_state& operator=(shared_lock_state const& rhs) { lock_primitive = rhs.lock_primitive; return *this; } shared_lock_state& operator=(shared_lock_state&& rhs) { lock_primitive = std::move(rhs.lock_primitive); rhs.lock_primitive = new shared_lock; return *this; } void lock() { lock_primitive->mutex.lock(); } bool try_lock() { return lock_primitive->mutex.try_lock(); } void unlock() { lock_primitive->mutex.unlock(); } void swap(shared_lock_state& s) noexcept { lock_primitive.swap(s.lock_primitive); } intrusive_ptr lock_primitive; }; template struct connection_base; template <> struct connection_base : ref_counted> { using threading_policy = thread_unsafe_policy; virtual ~connection_base() noexcept = default; ROCKET_NODISCARD bool is_connected() const noexcept { return prev != nullptr; } void disconnect() noexcept { if (prev != nullptr) { next->prev = prev; prev->next = next; // To mark a connection as disconnected, just set its prev-link to null but // leave the next link alive so we can still traverse through the connections // if the slot gets disconnected during signal emit. prev = nullptr; } } #ifndef ROCKET_NO_QUEUED_CONNECTIONS ROCKET_NODISCARD std::thread::id get_tid() const noexcept { return std::thread::id{}; } ROCKET_NODISCARD constexpr bool is_queued() const noexcept { return false; } #endif//~ ROCKET_NO_QUEUED_CONNECTIONS #ifndef ROCKET_NO_BLOCKING_CONNECTIONS void block() noexcept { ++block_count; } void unblock() noexcept { if (block_count > 0) { --block_count; } } ROCKET_NODISCARD bool is_blocked() const noexcept { return block_count > 0; } unsigned long block_count{ 0 }; #endif//~ ROCKET_NO_BLOCKING_CONNECTIONS intrusive_ptr next; intrusive_ptr prev; }; template <> struct connection_base : ref_counted, ref_count_atomic> { using threading_policy = thread_safe_policy; virtual ~connection_base() noexcept = default; ROCKET_NODISCARD bool is_connected() const noexcept { return prev != nullptr; } void disconnect() noexcept { std::scoped_lock guard{ lock->mutex }; if (prev != nullptr) { next->prev = prev; prev->next = next; // To mark a connection as disconnected, just set its prev-link to null but // leave the next link alive so we can still traverse through the connections // if the slot gets disconnected during signal emit. prev = nullptr; } } #ifndef ROCKET_NO_QUEUED_CONNECTIONS ROCKET_NODISCARD std::thread::id const& get_tid() const noexcept { return thread_id; } ROCKET_NODISCARD bool is_queued() const noexcept { return thread_id != std::thread::id{} && thread_id != std::this_thread::get_id(); } #endif//~ ROCKET_NO_QUEUED_CONNECTIONS #ifndef ROCKET_NO_BLOCKING_CONNECTIONS void block() noexcept { std::scoped_lock guard{ lock->mutex }; ++block_count; } void unblock() noexcept { std::scoped_lock guard{ lock->mutex }; if (block_count > 0) { --block_count; } } ROCKET_NODISCARD bool is_blocked() const noexcept { return (*static_cast(&block_count)) > 0; } unsigned long block_count{ 0 }; #endif//~ ROCKET_NO_BLOCKING_CONNECTIONS intrusive_ptr next; intrusive_ptr prev; intrusive_ptr lock; #ifndef ROCKET_NO_QUEUED_CONNECTIONS std::thread::id thread_id; #endif }; template struct functional_connection : connection_base { std::function slot; }; #ifndef ROCKET_NO_TIMERS struct timed_connection final : functional_connection { std::chrono::time_point expires_at; std::chrono::microseconds interval; }; #endif // Should make sure that this is POD struct thread_local_data final { void* current_connection; bool is_thread_safe_connection; bool emission_aborted; }; ROCKET_NODISCARD inline thread_local_data* get_thread_local_data() noexcept { static ROCKET_THREAD_LOCAL thread_local_data th; return &th; } struct connection_scope final { connection_scope(void* base, bool is_thread_safe, thread_local_data* th) noexcept : th{ th } , prev{ th->current_connection } , prevThreadSafe{ th->is_thread_safe_connection } { th->current_connection = base; th->is_thread_safe_connection = is_thread_safe; } ~connection_scope() noexcept { th->current_connection = prev; th->is_thread_safe_connection = prevThreadSafe; } thread_local_data* th; void* prev; bool prevThreadSafe; }; struct abort_scope final { abort_scope(thread_local_data* th) noexcept : th{ th } , prev{ th->emission_aborted } { th->emission_aborted = false; } ~abort_scope() noexcept { th->emission_aborted = prev; } thread_local_data* th; bool prev; }; #ifndef ROCKET_NO_SMARTPOINTER_EXTENSIONS template struct weak_mem_fn final { explicit weak_mem_fn(std::weak_ptr c, R (Class::*method)(Args...)) : weak{ std::move(c) } , method{ method } { } template auto operator()(Args1&&... args) const { if constexpr (std::is_void_v) { if (auto strong = weak.lock()) { (strong.get()->*method)(std::forward(args)...); } } else { if (auto strong = weak.lock()) { return optional{ (strong.get()->*method)(std::forward(args)...) }; } return optional{}; } } private: std::weak_ptr weak; R (Class::*method)(Args...); }; template struct shared_mem_fn final { explicit shared_mem_fn(std::shared_ptr c, R (Class::*method)(Args...)) : shared{ std::move(c) } , method{ method } { } template R operator()(Args1&&... args) const { return (shared.get()->*method)(std::forward(args)...); } private: std::shared_ptr shared; R (Class::*method)(Args...); }; #endif//~ ROCKET_NO_SMARTPOINTER_EXTENSIONS } // namespace detail #ifndef ROCKET_NO_SMARTPOINTER_EXTENSIONS template inline auto bind_weak_ptr(std::weak_ptr c, R (Class::*method)(Args...)) { return detail::weak_mem_fn{ std::move(c), method }; } template inline auto bind_weak_ptr(std::shared_ptr c, R (Class::*method)(Args...)) { return detail::weak_mem_fn{ std::move(c), method }; } template inline auto bind_shared_ptr(std::shared_ptr c, R (Class::*method)(Args...)) { return detail::shared_mem_fn{ std::move(c), method }; } #endif struct connection { connection() noexcept : base{ nullptr } , is_thread_safe{ false } { } ~connection() noexcept { release(); } connection(connection&& rhs) noexcept : base{ rhs.base } , is_thread_safe{ rhs.is_thread_safe } { rhs.base = nullptr; rhs.is_thread_safe = false; } connection(connection const& rhs) noexcept : base{ rhs.base } , is_thread_safe{ rhs.is_thread_safe } { addref(); } explicit connection(void* base, bool is_thread_safe) noexcept : base{ base } , is_thread_safe{ is_thread_safe } { addref(); } connection& operator=(connection&& rhs) noexcept { release(); base = rhs.base; is_thread_safe = rhs.is_thread_safe; rhs.base = nullptr; rhs.is_thread_safe = false; return *this; } connection& operator=(connection const& rhs) noexcept { if (this != &rhs) { release(); base = rhs.base; is_thread_safe = rhs.is_thread_safe; addref(); } return *this; } ROCKET_NODISCARD bool operator==(connection const& rhs) const noexcept { return base == rhs.base && is_thread_safe == rhs.is_thread_safe; } ROCKET_NODISCARD bool operator!=(connection const& rhs) const noexcept { return base != rhs.base || is_thread_safe != rhs.is_thread_safe; } ROCKET_NODISCARD explicit operator bool() const noexcept { return is_connected(); } ROCKET_NODISCARD bool is_connected() const noexcept { if (base != nullptr) { if (is_thread_safe) { auto safe{ static_cast*>(base) }; return std::launder(safe)->is_connected(); } else { auto unsafe{ static_cast*>(base) }; return std::launder(unsafe)->is_connected(); } } return false; } #ifndef ROCKET_NO_BLOCKING_CONNECTIONS ROCKET_NODISCARD bool is_blocked() const noexcept { if (base != nullptr) { if (is_thread_safe) { auto safe{ static_cast*>(base) }; return std::launder(safe)->is_blocked(); } else { auto unsafe{ static_cast*>(base) }; return std::launder(unsafe)->is_blocked(); } } return false; } void block() noexcept { if (base != nullptr) { if (is_thread_safe) { auto safe{ static_cast*>(base) }; std::launder(safe)->block(); } else { auto unsafe{ static_cast*>(base) }; std::launder(unsafe)->block(); } } } void unblock() noexcept { if (base != nullptr) { if (is_thread_safe) { auto safe{ static_cast*>(base) }; std::launder(safe)->unblock(); } else { auto unsafe{ static_cast*>(base) }; std::launder(unsafe)->unblock(); } } } #endif//~ ROCKET_NO_BLOCKING_CONNECTIONS void disconnect() noexcept { if (base != nullptr) { if (is_thread_safe) { auto safe{ static_cast*>(base) }; std::launder(safe)->disconnect(); } else { auto unsafe{ static_cast*>(base) }; std::launder(unsafe)->disconnect(); } release(); } } void swap(connection& other) noexcept { if (this != &other) { void* tmp_base{ base }; bool tmp_is_thread_safe{ is_thread_safe }; base = other.base; is_thread_safe = other.is_thread_safe; other.base = tmp_base; other.is_thread_safe = tmp_is_thread_safe; } } private: void* base; bool is_thread_safe; void addref() noexcept { if (base != nullptr) { if (is_thread_safe) { auto safe{ static_cast*>(base) }; std::launder(safe)->addref(); } else { auto unsafe{ static_cast*>(base) }; std::launder(unsafe)->addref(); } } } void release() noexcept { if (base != nullptr) { if (is_thread_safe) { auto safe{ static_cast*>(base) }; std::launder(safe)->release(); } else { auto unsafe{ static_cast*>(base) }; std::launder(unsafe)->release(); } base = nullptr; is_thread_safe = false; } } }; struct scoped_connection final : connection { scoped_connection() noexcept = default; ~scoped_connection() noexcept { disconnect(); } scoped_connection(connection const& rhs) noexcept : connection{ rhs } { } scoped_connection(connection&& rhs) noexcept : connection{ std::move(rhs) } { } scoped_connection(scoped_connection&& rhs) noexcept : connection{ std::move(rhs) } { } scoped_connection& operator=(connection&& rhs) noexcept { disconnect(); connection::operator=(std::move(rhs)); return *this; } scoped_connection& operator=(scoped_connection&& rhs) noexcept { disconnect(); connection::operator=(std::move(rhs)); return *this; } scoped_connection& operator=(connection const& rhs) noexcept { disconnect(); connection::operator=(rhs); return *this; } private: scoped_connection(scoped_connection const&) = delete; scoped_connection& operator=(scoped_connection const&) = delete; }; struct scoped_connection_container final { scoped_connection_container() = default; ~scoped_connection_container() = default; scoped_connection_container(scoped_connection_container&& s) : connections{ std::move(s.connections) } { } scoped_connection_container& operator=(scoped_connection_container&& rhs) { connections = std::move(rhs.connections); return *this; } scoped_connection_container(std::initializer_list list) { append(list); } void append(connection const& conn) { connections.push_front(scoped_connection{ conn }); } void append(std::initializer_list list) { for (auto const& connection : list) { append(connection); } } scoped_connection_container& operator+=(connection const& conn) { append(conn); return *this; } scoped_connection_container& operator+=(std::initializer_list list) { for (auto const& connection : list) { append(connection); } return *this; } void disconnect() noexcept { connections.clear(); } private: scoped_connection_container(scoped_connection_container const&) = delete; scoped_connection_container& operator=(scoped_connection_container const&) = delete; std::forward_list connections; }; struct trackable { void add_tracked_connection(connection const& conn) { container.append(conn); } void disconnect_tracked_connections() noexcept { container.disconnect(); } private: scoped_connection_container container; }; ROCKET_NODISCARD inline connection current_connection() noexcept { auto th = detail::get_thread_local_data(); return connection{ th->current_connection, th->is_thread_safe_connection }; } inline void abort_emission() noexcept { detail::get_thread_local_data()->emission_aborted = true; } #ifndef ROCKET_NO_BLOCKING_CONNECTIONS struct scoped_connection_blocker final { scoped_connection_blocker(connection c) noexcept : conn{ std::move(c) } { conn.block(); } ~scoped_connection_blocker() noexcept { conn.unblock(); } private: scoped_connection_blocker(scoped_connection_blocker const&) = delete; scoped_connection_blocker& operator=(scoped_connection_blocker const&) = delete; connection conn; }; #endif//~ ROCKET_NO_BLOCKING_CONNECTIONS namespace detail { #ifndef ROCKET_NO_TIMERS struct timer_queue final { using slot_type = std::function; timer_queue() { init(); } ~timer_queue() noexcept { destroy(); } timer_queue(timer_queue&& q) : head{ std::move(q.head) } , tail{ std::move(q.tail) } { q.init(); } timer_queue(timer_queue const& q) { init(); copy(q); } timer_queue& operator=(timer_queue&& rhs) { destroy(); head = std::move(rhs.head); tail = std::move(rhs.tail); rhs.init(); return *this; } timer_queue& operator=(timer_queue const& rhs) { if (this != &rhs) { clear(); copy(rhs); } return *this; } template connection set_interval(slot_type slot, std::chrono::duration const& interval) { assert(slot != nullptr); auto expires_at = std::chrono::steady_clock::now() + interval; auto interval_microsecs = std::chrono::duration_cast(interval); connection_base* base = make_link(tail, std::move(slot), std::move(expires_at), std::move(interval_microsecs)); return connection{ static_cast(base), false }; } template connection set_interval(std::chrono::duration const& interval) { return set_interval([] { (*Method)(); }, interval); } template connection set_interval( Instance& object, R (Class::*method)(), std::chrono::duration const& interval) { connection c{ set_interval([&object, method] { (object.*method)(); }, interval) }; if constexpr (std::is_convertible_v) { static_cast(object).add_tracked_connection(c); } return c; } template connection set_interval(Instance& object, std::chrono::duration const& interval) { connection c{ set_interval([&object] { (object.*Method)(); }, interval) }; if constexpr (std::is_convertible_v) { static_cast(object).add_tracked_connection(c); } return c; } template connection set_interval( Instance* object, R (Class::*method)(), std::chrono::duration const& interval) { connection c{ set_interval([object, method] { (object->*method)(); }, interval) }; if constexpr (std::is_convertible_v) { static_cast(object)->add_tracked_connection(c); } return c; } template connection set_interval(Instance* object, std::chrono::duration const& interval) { connection c{ set_interval([object] { (object->*Method)(); }, interval) }; if constexpr (std::is_convertible_v) { static_cast(object)->add_tracked_connection(c); } return c; } template connection set_timeout(slot_type slot, std::chrono::duration const& timeout) { assert(slot != nullptr); auto expires_at = std::chrono::steady_clock::now() + timeout; connection_base* base = make_link(tail, std::move(slot), std::move(expires_at), std::chrono::microseconds(-1)); return connection{ static_cast(base), false }; } template connection set_timeout(std::chrono::duration const& timeout) { return set_timeout([] { (*Method)(); }, timeout); } template connection set_timeout( Instance& object, R (Class::*method)(), std::chrono::duration const& timeout) { connection c{ set_timeout([&object, method] { (object.*method)(); }, timeout) }; if constexpr (std::is_convertible_v) { static_cast(object).add_tracked_connection(c); } return c; } template connection set_timeout(Instance& object, std::chrono::duration const& timeout) { connection c{ set_timeout([&object] { (object.*Method)(); }, timeout) }; if constexpr (std::is_convertible_v) { static_cast(object).add_tracked_connection(c); } return c; } template connection set_timeout( Instance* object, R (Class::*method)(), std::chrono::duration const& timeout) { connection c{ set_timeout([object, method] { (object->*method)(); }, timeout) }; if constexpr (std::is_convertible_v) { static_cast(object)->add_tracked_connection(c); } return c; } template connection set_timeout(Instance* object, std::chrono::duration const& timeout) { connection c{ set_timeout([object] { (object->*Method)(); }, timeout) }; if constexpr (std::is_convertible_v) { static_cast(object)->add_tracked_connection(c); } return c; } void clear() noexcept { intrusive_ptr current{ head->next }; while (current != tail) { intrusive_ptr next{ current->next }; current->next = tail; current->prev = nullptr; current = std::move(next); } head->next = tail; tail->prev = head; } void swap(timer_queue& other) noexcept { if (this != &other) { head.swap(other.head); tail.swap(other.tail); } } bool dispatch(std::chrono::time_point execute_until) { # ifndef ROCKET_NO_EXCEPTIONS bool error{ false }; # endif bool not_enough_time{ false }; std::chrono::time_point now = std::chrono::steady_clock::now(); { detail::thread_local_data* th{ detail::get_thread_local_data() }; detail::abort_scope ascope{ th }; intrusive_ptr current{ head->next }; intrusive_ptr end{ tail }; while (current != end) { assert(current != nullptr); if (current->prev != nullptr # ifndef ROCKET_NO_BLOCKING_CONNECTIONS && current->block_count == 0 # endif ) ROCKET_LIKELY { detail::connection_scope cscope{ current, false, th }; timed_connection* conn = std::launder(static_cast(static_cast(current))); if (conn->expires_at <= now) { if (conn->interval.count() < 0) { conn->disconnect(); } else { conn->expires_at = now + conn->interval; } # ifndef ROCKET_NO_EXCEPTIONS try { # endif conn->slot(); # ifndef ROCKET_NO_EXCEPTIONS } catch (...) { error = true; } # endif if (execute_until != std::chrono::time_point{}) ROCKET_UNLIKELY { // Check if we already spent the maximum allowed time executing callbacks if (execute_until <= std::chrono::steady_clock::now()) { not_enough_time = true; break; } } if (th->emission_aborted) ROCKET_UNLIKELY { break; } } } current = current->next; } } # ifndef ROCKET_NO_EXCEPTIONS if (error) ROCKET_UNLIKELY { throw invocation_slot_error{}; } # endif return not_enough_time; } private: using connection_base = detail::connection_base; void init() { head = new connection_base; tail = new connection_base; head->next = tail; tail->prev = head; } void destroy() noexcept { clear(); head->next = nullptr; tail->prev = nullptr; } void copy(timer_queue const& q) { intrusive_ptr current{ q.head->next }; intrusive_ptr end{ q.tail }; while (current != end) { timed_connection* conn = std::launder(static_cast(static_cast(current))); make_link(tail, conn->slot, conn->expires_at, conn->interval); current = current->next; } } timed_connection* make_link( connection_base* l, slot_type slot, std::chrono::time_point expires_at, std::chrono::microseconds interval) { intrusive_ptr link{ new timed_connection }; link->slot = std::move(slot); link->expires_at = std::move(expires_at); link->interval = std::move(interval); link->prev = l->prev; link->next = l; link->prev->next = link; link->next->prev = link; return link; } intrusive_ptr head; intrusive_ptr tail; }; inline timer_queue* get_timer_queue() noexcept { static ROCKET_THREAD_LOCAL timer_queue queue; return &queue; } #endif//~ ROCKET_NO_TIMERS #ifndef ROCKET_NO_QUEUED_CONNECTIONS struct call_queue final { void put(std::thread::id tid, std::packaged_task task) { std::scoped_lock guard{ mutex }; queue[tid].emplace_back(std::move(task)); } bool dispatch(std::chrono::time_point execute_until) { std::thread::id tid = std::this_thread::get_id(); std::deque> thread_queue; { std::scoped_lock guard{ mutex }; auto iterator = queue.find(tid); if (iterator != queue.end()) { thread_queue.swap(iterator->second); queue.erase(iterator); } } auto itr = thread_queue.begin(); auto end = thread_queue.end(); while (itr != end) { (itr++)->operator()(); if (execute_until != std::chrono::time_point{}) ROCKET_UNLIKELY { // check if we already spent the maximum allowed time executing callbacks if (execute_until <= std::chrono::steady_clock::now()) { break; } } } if (itr != end) ROCKET_UNLIKELY { // readd unfinished work to the queue auto rbegin = std::make_reverse_iterator(end); auto rend = std::make_reverse_iterator(itr); std::scoped_lock guard{ mutex }; std::deque>& original_queue = queue[tid]; for (auto it = rbegin; it != rend; ++it) { original_queue.push_front(std::move(*it)); } } return itr != end; } private: std::mutex mutex; std::unordered_map>> queue; }; inline call_queue* get_call_queue() noexcept { static call_queue queue; return &queue; } template struct decay { typedef typename std::remove_reference::type U; typedef typename std::conditional< std::is_array::value, typename std::remove_extent::type*, typename std::conditional< std::is_function::value, typename std::add_pointer::type, typename std::conditional< std::is_const::value || !std::is_reference::value, typename std::remove_cv::type, T>::type>::type>::type type; }; template struct unwrap_refwrapper { using type = T; }; template struct unwrap_refwrapper> { using type = T&; }; template using special_decay_t = typename unwrap_refwrapper::type>::type; // This make_tuple implementation is different from std::make_tuple. // This one preserves non-const references as actual reference values. // However const references will be stored by value. // make_tuple(int const&) => tuple // make_tuple(int&) => tuple template ROCKET_NODISCARD auto make_tuple(Types&&... args) { return std::tuple...>(std::forward(args)...); } #endif//~ ROCKET_NO_QUEUED_CONNECTIONS } // namespace detail #if !defined(ROCKET_NO_TIMERS) || !defined(ROCKET_NO_QUEUED_CONNECTIONS) template inline void dispatch_queued_calls(std::chrono::duration const& max_time_to_execute) { std::chrono::time_point execute_until{}; if (max_time_to_execute.count() > 0) ROCKET_UNLIKELY { execute_until = std::chrono::steady_clock::now() + max_time_to_execute; } # ifndef ROCKET_NO_TIMERS bool not_enough_time = detail::get_timer_queue()->dispatch(execute_until); if (not_enough_time) ROCKET_UNLIKELY { return; } # endif # ifndef ROCKET_NO_QUEUED_CONNECTIONS detail::get_call_queue()->dispatch(execute_until); # endif } inline void dispatch_queued_calls() { dispatch_queued_calls(std::chrono::microseconds::zero()); } #endif #ifndef ROCKET_NO_TIMERS template inline connection set_interval(std::function slot, std::chrono::duration const& interval) { return detail::get_timer_queue()->template set_interval(std::move(slot), interval); } template inline connection set_interval(std::chrono::duration const& interval) { return detail::get_timer_queue()->template set_interval(interval); } template inline connection set_interval( Instance& object, R (Class::*method)(), std::chrono::duration const& interval) { return detail::get_timer_queue()->template set_interval( object, method, interval); } template inline connection set_interval(Instance& object, std::chrono::duration const& interval) { return detail::get_timer_queue()->template set_interval(object, interval); } template inline connection set_interval( Instance* object, R (Class::*method)(), std::chrono::duration const& interval) { return detail::get_timer_queue()->template set_interval( object, method, interval); } template inline connection set_interval(Instance* object, std::chrono::duration const& interval) { return detail::get_timer_queue()->template set_interval(object, interval); } template inline connection set_timeout(std::function slot, std::chrono::duration const& timeout) { return detail::get_timer_queue()->template set_timeout(std::move(slot), timeout); } template inline connection set_timeout(std::chrono::duration const& timeout) { return detail::get_timer_queue()->template set_timeout(timeout); } template inline connection set_timeout( Instance& object, R (Class::*method)(), std::chrono::duration const& timeout) { return detail::get_timer_queue()->template set_timeout( object, method, timeout); } template inline connection set_timeout(Instance& object, std::chrono::duration const& timeout) { return detail::get_timer_queue()->template set_timeout(object, timeout); } template inline connection set_timeout( Instance* object, R (Class::*method)(), std::chrono::duration const& timeout) { return detail::get_timer_queue()->template set_timeout( object, method, timeout); } template inline connection set_timeout(Instance* object, std::chrono::duration const& timeout) { return detail::get_timer_queue()->template set_timeout(object, timeout); } // Overloads for milliseconds inline connection set_interval(std::function slot, unsigned long interval_ms) { return detail::get_timer_queue()->template set_interval<>( std::move(slot), std::chrono::milliseconds(interval_ms)); } template inline connection set_interval(unsigned long interval_ms) { return detail::get_timer_queue()->template set_interval(std::chrono::milliseconds(interval_ms)); } template inline connection set_interval(Instance& object, R (Class::*method)(), unsigned long interval_ms) { return detail::get_timer_queue()->template set_interval( object, method, std::chrono::milliseconds(interval_ms)); } template inline connection set_interval(Instance& object, unsigned long interval_ms) { return detail::get_timer_queue()->template set_interval( object, std::chrono::milliseconds(interval_ms)); } template inline connection set_interval(Instance* object, R (Class::*method)(), unsigned long interval_ms) { return detail::get_timer_queue()->template set_interval( object, method, std::chrono::milliseconds(interval_ms)); } template inline connection set_interval(Instance* object, unsigned long interval_ms) { return detail::get_timer_queue()->template set_interval( object, std::chrono::milliseconds(interval_ms)); } inline connection set_timeout(std::function slot, unsigned long timeout_ms) { return detail::get_timer_queue()->template set_timeout<>( std::move(slot), std::chrono::milliseconds(timeout_ms)); } template inline connection set_timeout(unsigned long timeout_ms) { return detail::get_timer_queue()->template set_timeout(std::chrono::milliseconds(timeout_ms)); } template inline connection set_timeout(Instance& object, R (Class::*method)(), unsigned long timeout_ms) { return detail::get_timer_queue()->template set_timeout( object, method, std::chrono::milliseconds(timeout_ms)); } template inline connection set_timeout(Instance& object, unsigned long timeout_ms) { return detail::get_timer_queue()->template set_timeout( object, std::chrono::milliseconds(timeout_ms)); } template inline connection set_timeout(Instance* object, R (Class::*method)(), unsigned long timeout_ms) { return detail::get_timer_queue()->template set_timeout( object, method, std::chrono::milliseconds(timeout_ms)); } template inline connection set_timeout(Instance* object, unsigned long timeout_ms) { return detail::get_timer_queue()->template set_timeout( object, std::chrono::milliseconds(timeout_ms)); } inline void clear_timers() noexcept { detail::get_timer_queue()->clear(); } #endif//~ ROCKET_NO_TIMERS template struct default_collector final : last> { }; template <> struct default_collector { using value_type = void; using result_type = void; void operator()() noexcept { /* do nothing for void types */ } void result() noexcept { /* do nothing for void types */ } }; enum connection_flags : unsigned int { direct_connection = 0, #ifndef ROCKET_NO_QUEUED_CONNECTIONS queued_connection = 1 << 0, #endif connect_as_first_slot = 1 << 1, }; template < class Signature, class Collector = default_collector>, class ThreadingPolicy = thread_unsafe_policy> struct signal; template struct signal final { using threading_policy = ThreadingPolicy; using signature_type = R(Args...); using slot_type = std::function; signal() { init(); } ~signal() noexcept { std::scoped_lock guard{ lock_state }; destroy(); } signal(signal&& s) { static_assert( std::is_same_v, "Thread safe signals can't be moved or swapped."); head = std::move(s.head); tail = std::move(s.tail); s.init(); } signal(signal const& s) { init(); std::scoped_lock guard{ s.lock_state }; copy(s); } signal& operator=(signal&& rhs) { static_assert( std::is_same_v, "Thread safe signals can't be moved or swapped."); destroy(); head = std::move(rhs.head); tail = std::move(rhs.tail); rhs.init(); return *this; } signal& operator=(signal const& rhs) { if (this != &rhs) { std::scoped_lock guard{ lock_state, rhs.lock_state }; clear_without_lock(); copy(rhs); } return *this; } connection connect(slot_type slot, connection_flags flags = direct_connection) { assert(slot != nullptr); #ifndef ROCKET_NO_QUEUED_CONNECTIONS std::thread::id tid{}; if constexpr (std::is_same_v) { if ((flags & queued_connection) != 0) ROCKET_UNLIKELY { tid = std::this_thread::get_id(); } } else { assert((flags & queued_connection) == 0); } #endif bool first = (flags & connect_as_first_slot) != 0; std::scoped_lock guard{ lock_state }; connection_base* base = make_link( first ? head->next : tail, std::move(slot) #ifndef ROCKET_NO_QUEUED_CONNECTIONS , tid #endif ); return connection{ static_cast(base), std::is_same_v }; } template connection connect(R1 (*method)(Args1...), connection_flags flags = direct_connection) { return connect([method](Args const&... args) { return R((*method)(Args1(args)...)); }, flags); } template connection connect(connection_flags flags = direct_connection) { return connect([](Args const&... args) { return R((*Method)(args...)); }, flags); } template connection connect(Instance& object, R1 (Class::*method)(Args1...), connection_flags flags = direct_connection) { connection c{ connect( [&object, method](Args const&... args) { return R((object.*method)(Args1(args)...)); }, flags) }; if constexpr (std::is_convertible_v) { static_cast(object).add_tracked_connection(c); } return c; } template connection connect(Instance& object, connection_flags flags = direct_connection) { connection c{ connect([&object](Args const&... args) { return R((object.*Method)(args...)); }, flags) }; if constexpr (std::is_convertible_v) { static_cast(object).add_tracked_connection(c); } return c; } template connection connect(Instance* object, R1 (Class::*method)(Args1...), connection_flags flags = direct_connection) { connection c{ connect( [object, method](Args const&... args) { return R((object->*method)(Args1(args)...)); }, flags) }; if constexpr (std::is_convertible_v) { static_cast(object)->add_tracked_connection(c); } return c; } template connection connect(Instance* object, connection_flags flags = direct_connection) { connection c{ connect([object](Args const&... args) { return R((object->*Method)(args...)); }, flags) }; if constexpr (std::is_convertible_v) { static_cast(object)->add_tracked_connection(c); } return c; } connection operator+=(slot_type slot) { return connect(std::move(slot)); } void clear() noexcept { std::scoped_lock guard{ lock_state }; clear_without_lock(); } void swap(signal& other) noexcept { static_assert( std::is_same_v, "Thread safe signals can't be moved or swapped."); if (this != &other) { head.swap(other.head); tail.swap(other.tail); } } ROCKET_NODISCARD std::size_t get_slot_count() const noexcept { std::size_t count{ 0 }; std::scoped_lock guard{ lock_state }; intrusive_ptr current{ head->next }; intrusive_ptr end{ tail }; while (current != end) { if (current->prev != nullptr) { ++count; } current = current->next; } return count; } template auto invoke(Args const&... args) const { #ifndef ROCKET_NO_EXCEPTIONS bool error{ false }; #endif ValueCollector collector{}; { detail::thread_local_data* th{ detail::get_thread_local_data() }; detail::abort_scope ascope{ th }; lock_state.lock(); intrusive_ptr current{ head->next }; intrusive_ptr end{ tail }; while (current != end) { assert(current != nullptr); if (current->prev != nullptr #ifndef ROCKET_NO_BLOCKING_CONNECTIONS && current->block_count == 0 #endif ) ROCKET_LIKELY { detail::connection_scope cscope{ current, std::is_same_v, th }; lock_state.unlock(); functional_connection* conn = std::launder(static_cast(static_cast(current))); if constexpr (std::is_same_v) { #ifndef ROCKET_NO_EXCEPTIONS try { #endif if constexpr (std::is_void_v) { conn->slot(args...); collector(); } else { collector(conn->slot(args...)); } #ifndef ROCKET_NO_EXCEPTIONS } catch (...) { error = true; } #endif } else { #ifndef ROCKET_NO_QUEUED_CONNECTIONS if (current->is_queued()) ROCKET_UNLIKELY { if constexpr (std::is_void_v) { std::packaged_task task( [current, args = detail::make_tuple(args...)] { if (current->is_connected()) ROCKET_LIKELY { detail::thread_local_data* th{ detail::get_thread_local_data() }; detail::connection_scope cscope{ current, std::is_same_v, th }; functional_connection* conn = std::launder(static_cast( static_cast(current))); std::apply(conn->slot, args); } }); detail::get_call_queue()->put(current->get_tid(), std::move(task)); } else { // If we are calling a queued slot, and our signal requires a return value // we actually have to block the thread until the slot was dispatched std::packaged_task task( [current, &collector, args = std::forward_as_tuple(args...)] { if (current->is_connected()) ROCKET_LIKELY { detail::thread_local_data* th{ detail::get_thread_local_data() }; detail::connection_scope cscope{ current, std::is_same_v, th }; functional_connection* conn = std::launder(static_cast( static_cast(current))); collector(std::apply(conn->slot, args)); } }); std::future future{ task.get_future() }; detail::get_call_queue()->put(current->get_tid(), std::move(task)); # ifndef ROCKET_NO_EXCEPTIONS try { # endif future.get(); # ifndef ROCKET_NO_EXCEPTIONS } catch (...) { error = true; } # endif } } else #endif//~ ROCKET_NO_QUEUED_CONNECTIONS { #ifndef ROCKET_NO_EXCEPTIONS try { #endif if constexpr (std::is_void_v) { conn->slot(args...); collector(); } else { collector(conn->slot(args...)); } #ifndef ROCKET_NO_EXCEPTIONS } catch (...) { error = true; } #endif } } lock_state.lock(); if (th->emission_aborted) ROCKET_UNLIKELY { break; } } current = current->next; } lock_state.unlock(); } #ifndef ROCKET_NO_EXCEPTIONS if (error) ROCKET_UNLIKELY { throw invocation_slot_error{}; } #endif return collector.result(); } auto operator()(Args const&... args) const { return invoke(args...); } private: using shared_lock_state = detail::shared_lock_state; using connection_base = detail::connection_base; using functional_connection = detail::functional_connection; void init() { head = new connection_base; tail = new connection_base; head->next = tail; tail->prev = head; } void destroy() noexcept { clear_without_lock(); head->next = nullptr; tail->prev = nullptr; } void clear_without_lock() noexcept { intrusive_ptr current{ head->next }; while (current != tail) { intrusive_ptr next{ current->next }; current->next = tail; current->prev = nullptr; current = std::move(next); } head->next = tail; tail->prev = head; } void copy(signal const& s) { intrusive_ptr current{ s.head->next }; intrusive_ptr end{ s.tail }; while (current != end) { functional_connection* conn = std::launder(static_cast(static_cast(current))); make_link( tail, conn->slot #ifndef ROCKET_NO_QUEUED_CONNECTIONS , conn->get_tid() #endif ); current = current->next; } } functional_connection* make_link( connection_base* l, slot_type slot #ifndef ROCKET_NO_QUEUED_CONNECTIONS , ROCKET_MAYBE_UNUSED std::thread::id tid #endif ) { intrusive_ptr link{ new functional_connection }; if constexpr (std::is_same_v) { link->lock = lock_state.lock_primitive; #ifndef ROCKET_NO_QUEUED_CONNECTIONS link->thread_id = std::move(tid); #endif } link->slot = std::move(slot); link->prev = l->prev; link->next = l; link->prev->next = link; link->next->prev = link; return link; } intrusive_ptr head; intrusive_ptr tail; ROCKET_NO_UNIQUE_ADDRESS mutable shared_lock_state lock_state; }; template >> using thread_safe_signal = signal; template ROCKET_NODISCARD inline std::function slot(Instance& object, R (Class::*method)(Args...)) { return [&object, method](Args const&... args) { return (object.*method)(args...); }; } template ROCKET_NODISCARD inline std::function slot(Instance* object, R (Class::*method)(Args...)) { return [object, method](Args const&... args) { return (object->*method)(args...); }; } template inline void swap(intrusive_ptr& p1, intrusive_ptr& p2) noexcept { p1.swap(p2); } #ifndef ROCKET_NO_STABLE_LIST template inline void swap(stable_list& l1, stable_list& l2) noexcept { l1.swap(l2); } #endif//~ ROCKET_NO_STABLE_LIST inline void swap(connection& c1, connection& c2) noexcept { c1.swap(c2); } template inline void swap( signal& s1, signal& s2) noexcept { s1.swap(s2); } }// namespace rocket #endif