100.00% Lines (52/52)
100.00% Functions (18/18)
| TLA | Baseline | Branch | ||||||
|---|---|---|---|---|---|---|---|---|
| Line | Hits | Code | Line | Hits | Code | |||
| 1 | // | 1 | // | |||||
| 2 | // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com) | 2 | // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com) | |||||
| 3 | // Copyright (c) 2026 Michael Vandeberg | 3 | // Copyright (c) 2026 Michael Vandeberg | |||||
| 4 | // | 4 | // | |||||
| 5 | // Distributed under the Boost Software License, Version 1.0. (See accompanying | 5 | // Distributed under the Boost Software License, Version 1.0. (See accompanying | |||||
| 6 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | 6 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | |||||
| 7 | // | 7 | // | |||||
| 8 | // Official repository: https://github.com/cppalliance/capy | 8 | // Official repository: https://github.com/cppalliance/capy | |||||
| 9 | // | 9 | // | |||||
| 10 | 10 | |||||||
| 11 | #ifndef BOOST_CAPY_EXECUTION_CONTEXT_HPP | 11 | #ifndef BOOST_CAPY_EXECUTION_CONTEXT_HPP | |||||
| 12 | #define BOOST_CAPY_EXECUTION_CONTEXT_HPP | 12 | #define BOOST_CAPY_EXECUTION_CONTEXT_HPP | |||||
| 13 | 13 | |||||||
| 14 | #include <boost/capy/detail/config.hpp> | 14 | #include <boost/capy/detail/config.hpp> | |||||
| 15 | #include <boost/capy/detail/frame_memory_resource.hpp> | 15 | #include <boost/capy/detail/frame_memory_resource.hpp> | |||||
| 16 | #include <boost/capy/detail/type_id.hpp> | 16 | #include <boost/capy/detail/type_id.hpp> | |||||
| 17 | #include <boost/capy/concept/executor.hpp> | 17 | #include <boost/capy/concept/executor.hpp> | |||||
| 18 | #include <concepts> | 18 | #include <concepts> | |||||
| 19 | #include <memory> | 19 | #include <memory> | |||||
| 20 | #include <memory_resource> | 20 | #include <memory_resource> | |||||
| 21 | #include <mutex> | 21 | #include <mutex> | |||||
| 22 | #include <tuple> | 22 | #include <tuple> | |||||
| 23 | #include <type_traits> | 23 | #include <type_traits> | |||||
| 24 | #include <utility> | 24 | #include <utility> | |||||
| 25 | 25 | |||||||
| 26 | namespace boost { | 26 | namespace boost { | |||||
| 27 | namespace capy { | 27 | namespace capy { | |||||
| 28 | 28 | |||||||
| 29 | /** Registers, looks up, and shuts down `service` objects owned by a derived context. | 29 | /** Registers, looks up, and shuts down `service` objects owned by a derived context. | |||||
| 30 | 30 | |||||||
| 31 | An execution context represents a place where function objects are | 31 | An execution context represents a place where function objects are | |||||
| 32 | executed. It provides a service registry where polymorphic services | 32 | executed. It provides a service registry where polymorphic services | |||||
| 33 | can be stored and retrieved by type. Each service type may be stored | 33 | can be stored and retrieved by type. Each service type may be stored | |||||
| 34 | at most once. Services may specify a nested `key_type` to enable | 34 | at most once. Services may specify a nested `key_type` to enable | |||||
| 35 | lookup by a base class type. | 35 | lookup by a base class type. | |||||
| 36 | 36 | |||||||
| 37 | Derived classes such as `io_context` extend this to provide | 37 | Derived classes such as `io_context` extend this to provide | |||||
| 38 | execution facilities like event loops and thread pools. Derived | 38 | execution facilities like event loops and thread pools. Derived | |||||
| 39 | class destructors must call `shutdown()` and `destroy()` to ensure | 39 | class destructors must call `shutdown()` and `destroy()` to ensure | |||||
| 40 | proper service cleanup before member destruction. | 40 | proper service cleanup before member destruction. | |||||
| 41 | 41 | |||||||
| 42 | @par Service Lifecycle | 42 | @par Service Lifecycle | |||||
| 43 | Services are created on first use via `use_service()` or explicitly | 43 | Services are created on first use via `use_service()` or explicitly | |||||
| 44 | via `make_service()`. During destruction, `shutdown()` is called on | 44 | via `make_service()`. During destruction, `shutdown()` is called on | |||||
| 45 | each service in reverse order of creation, then `destroy()` deletes | 45 | each service in reverse order of creation, then `destroy()` deletes | |||||
| 46 | them. Both functions are idempotent. | 46 | them. Both functions are idempotent. | |||||
| 47 | 47 | |||||||
| 48 | @par Thread Safety | 48 | @par Thread Safety | |||||
| 49 | Service registration and lookup functions are thread-safe. | 49 | Service registration and lookup functions are thread-safe. | |||||
| 50 | The `shutdown()` and `destroy()` functions are not thread-safe | 50 | The `shutdown()` and `destroy()` functions are not thread-safe | |||||
| 51 | and must only be called during destruction. | 51 | and must only be called during destruction. | |||||
| 52 | 52 | |||||||
| 53 | @par Example | 53 | @par Example | |||||
| 54 | - | @code | 54 | + | @par !example example | |||
| 55 | - | struct file_service : execution_context::service | ||||||
| 56 | - | { | ||||||
| 57 | - | protected: | ||||||
| 58 | - | void shutdown() override {} | ||||||
| 59 | - | }; | ||||||
| 60 | - | |||||||
| 61 | - | struct posix_file_service : file_service | ||||||
| 62 | - | { | ||||||
| 63 | - | using key_type = file_service; | ||||||
| 64 | - | |||||||
| 65 | - | explicit posix_file_service(execution_context&) {} | ||||||
| 66 | - | }; | ||||||
| 67 | - | |||||||
| 68 | - | class io_context : public execution_context | ||||||
| 69 | - | { | ||||||
| 70 | - | public: | ||||||
| 71 | - | ~io_context() | ||||||
| 72 | - | { | ||||||
| 73 | - | shutdown(); | ||||||
| 74 | - | destroy(); | ||||||
| 75 | - | } | ||||||
| 76 | - | }; | ||||||
| 77 | - | io_context ctx; | ||||||
| 78 | - | ctx.make_service<posix_file_service>(); | ||||||
| 79 | - | ctx.find_service<file_service>(); // returns posix_file_service* | ||||||
| 80 | - | ctx.find_service<posix_file_service>(); // also works | ||||||
| 81 | - | @endcode | ||||||
| 82 | 55 | |||||||
| 83 | 56 | |||||||
| 84 | @see service, ExecutionContext | 57 | @see service, ExecutionContext | |||||
| 85 | */ | 58 | */ | |||||
| 86 | class BOOST_CAPY_DECL | 59 | class BOOST_CAPY_DECL | |||||
| 87 | execution_context | 60 | execution_context | |||||
| 88 | { | 61 | { | |||||
| 89 | detail::type_info const* ti_ = nullptr; | 62 | detail::type_info const* ti_ = nullptr; | |||||
| 90 | 63 | |||||||
| 91 | template<class T, class = void> | 64 | template<class T, class = void> | |||||
| 92 | struct get_key : std::false_type | 65 | struct get_key : std::false_type | |||||
| 93 | {}; | 66 | {}; | |||||
| 94 | 67 | |||||||
| 95 | template<class T> | 68 | template<class T> | |||||
| 96 | struct get_key<T, std::void_t<typename T::key_type>> : std::true_type | 69 | struct get_key<T, std::void_t<typename T::key_type>> : std::true_type | |||||
| 97 | { | 70 | { | |||||
| 98 | using type = typename T::key_type; | 71 | using type = typename T::key_type; | |||||
| 99 | }; | 72 | }; | |||||
| 100 | protected: | 73 | protected: | |||||
| 101 | /** Construct from the most-derived context type. | 74 | /** Construct from the most-derived context type. | |||||
| 102 | 75 | |||||||
| 103 | Records the dynamic type of the context so that | 76 | Records the dynamic type of the context so that | |||||
| 104 | @ref target can later downcast `this` to the | 77 | @ref target can later downcast `this` to the | |||||
| 105 | requested derived type. Derived classes must pass | 78 | requested derived type. Derived classes must pass | |||||
| 106 | `this` typed as the most-derived type (i.e. invoke | 79 | `this` typed as the most-derived type (i.e. invoke | |||||
| 107 | this constructor from the most-derived class with | 80 | this constructor from the most-derived class with | |||||
| 108 | `this` of that type). Passing a pointer typed as a | 81 | `this` of that type). Passing a pointer typed as a | |||||
| 109 | base class records the wrong type and causes | 82 | base class records the wrong type and causes | |||||
| 110 | `target<Derived>()` to return `nullptr`. | 83 | `target<Derived>()` to return `nullptr`. | |||||
| 111 | 84 | |||||||
| 112 | @tparam Derived The most-derived context type. | 85 | @tparam Derived The most-derived context type. | |||||
| 113 | 86 | |||||||
| 114 | @param self `this`, typed as the most-derived context type. | 87 | @param self `this`, typed as the most-derived context type. | |||||
| 115 | Only its type is recorded; the pointer is not stored. | 88 | Only its type is recorded; the pointer is not stored. | |||||
| 116 | */ | 89 | */ | |||||
| 117 | template< typename Derived > | 90 | template< typename Derived > | |||||
| 118 | explicit execution_context( Derived* self ) noexcept; | 91 | explicit execution_context( Derived* self ) noexcept; | |||||
| 119 | 92 | |||||||
| 120 | public: | 93 | public: | |||||
| 121 | //------------------------------------------------ | 94 | //------------------------------------------------ | |||||
| 122 | 95 | |||||||
| 123 | /** Gives a derived service a `shutdown()` hook, run when its owning `execution_context` is destroyed. | 96 | /** Gives a derived service a `shutdown()` hook, run when its owning `execution_context` is destroyed. | |||||
| 124 | 97 | |||||||
| 125 | Services provide extensible functionality to an execution context. | 98 | Services provide extensible functionality to an execution context. | |||||
| 126 | Each service type can be registered at most once. Services are | 99 | Each service type can be registered at most once. Services are | |||||
| 127 | created via `use_service()` or `make_service()` and are owned by | 100 | created via `use_service()` or `make_service()` and are owned by | |||||
| 128 | the execution context for their lifetime. | 101 | the execution context for their lifetime. | |||||
| 129 | 102 | |||||||
| 130 | Derived classes must implement the pure virtual `shutdown()` member | 103 | Derived classes must implement the pure virtual `shutdown()` member | |||||
| 131 | function, which is called when the owning execution context is | 104 | function, which is called when the owning execution context is | |||||
| 132 | being destroyed. The `shutdown()` function should release resources | 105 | being destroyed. The `shutdown()` function should release resources | |||||
| 133 | and cancel outstanding operations without blocking. | 106 | and cancel outstanding operations without blocking. | |||||
| 134 | 107 | |||||||
| 135 | @par Deriving from service | 108 | @par Deriving from service | |||||
| 136 | @li Implement `shutdown()` to perform cleanup. | 109 | @li Implement `shutdown()` to perform cleanup. | |||||
| 137 | @li Accept `execution_context&` as the first constructor parameter. | 110 | @li Accept `execution_context&` as the first constructor parameter. | |||||
| 138 | @li Optionally define `key_type` to enable base-class lookup. | 111 | @li Optionally define `key_type` to enable base-class lookup. | |||||
| 139 | 112 | |||||||
| 140 | @par Example | 113 | @par Example | |||||
| 141 | - | @code | 114 | + | @par !example example | |||
| 142 | - | struct my_service : execution_context::service | ||||||
| 143 | - | { | ||||||
| 144 | - | explicit my_service(execution_context&) {} | ||||||
| 145 | - | protected: | ||||||
| 146 | - | void shutdown() override | ||||||
| 147 | - | { | ||||||
| 148 | - | // Cancel pending operations, release resources | ||||||
| 149 | - | } | ||||||
| 150 | - | }; | ||||||
| 151 | - | @endcode | ||||||
| 152 | 115 | |||||||
| 153 | 116 | |||||||
| 154 | @see execution_context | 117 | @see execution_context | |||||
| 155 | */ | 118 | */ | |||||
| 156 | class BOOST_CAPY_DECL | 119 | class BOOST_CAPY_DECL | |||||
| 157 | service | 120 | service | |||||
| 158 | { | 121 | { | |||||
| 159 | public: | 122 | public: | |||||
| 160 | /// Destructor. | 123 | /// Destructor. | |||||
| HITCBC | 161 | 52 | virtual ~service() = default; | 124 | 54 | virtual ~service() = default; | ||
| 162 | 125 | |||||||
| 163 | protected: | 126 | protected: | |||||
| 164 | /// Construct a service. Only derived classes may do so. | 127 | /// Construct a service. Only derived classes may do so. | |||||
| HITCBC | 165 | 52 | service() = default; | 128 | 54 | service() = default; | ||
| 166 | 129 | |||||||
| 167 | /** Called when the owning execution context shuts down. | 130 | /** Called when the owning execution context shuts down. | |||||
| 168 | 131 | |||||||
| 169 | Implementations should release resources and cancel any | 132 | Implementations should release resources and cancel any | |||||
| 170 | outstanding asynchronous operations. This function must | 133 | outstanding asynchronous operations. This function must | |||||
| 171 | not block and must not throw exceptions. Services are | 134 | not block and must not throw exceptions. Services are | |||||
| 172 | shut down in reverse order of creation. | 135 | shut down in reverse order of creation. | |||||
| 173 | 136 | |||||||
| 174 | @par Exception Safety | 137 | @par Exception Safety | |||||
| 175 | No-throw guarantee. | 138 | No-throw guarantee. | |||||
| 176 | */ | 139 | */ | |||||
| 177 | virtual void shutdown() = 0; | 140 | virtual void shutdown() = 0; | |||||
| 178 | 141 | |||||||
| 179 | private: | 142 | private: | |||||
| 180 | friend class execution_context; | 143 | friend class execution_context; | |||||
| 181 | 144 | |||||||
| 182 | service* next_ = nullptr; | 145 | service* next_ = nullptr; | |||||
| 183 | 146 | |||||||
| 184 | // warning C4251: 'std::type_index' needs to have dll-interface | 147 | // warning C4251: 'std::type_index' needs to have dll-interface | |||||
| 185 | BOOST_CAPY_MSVC_WARNING_PUSH | 148 | BOOST_CAPY_MSVC_WARNING_PUSH | |||||
| 186 | BOOST_CAPY_MSVC_WARNING_DISABLE(4251) | 149 | BOOST_CAPY_MSVC_WARNING_DISABLE(4251) | |||||
| 187 | detail::type_index t0_{detail::type_id<void>()}; | 150 | detail::type_index t0_{detail::type_id<void>()}; | |||||
| 188 | detail::type_index t1_{detail::type_id<void>()}; | 151 | detail::type_index t1_{detail::type_id<void>()}; | |||||
| 189 | BOOST_CAPY_MSVC_WARNING_POP | 152 | BOOST_CAPY_MSVC_WARNING_POP | |||||
| 190 | }; | 153 | }; | |||||
| 191 | 154 | |||||||
| 192 | //------------------------------------------------ | 155 | //------------------------------------------------ | |||||
| 193 | 156 | |||||||
| 194 | /** Copy construction is disabled; a context owns its services. | 157 | /** Copy construction is disabled; a context owns its services. | |||||
| 195 | 158 | |||||||
| 196 | @param other The context that would be copied. | 159 | @param other The context that would be copied. | |||||
| 197 | */ | 160 | */ | |||||
| 198 | execution_context(execution_context const& other) = delete; | 161 | execution_context(execution_context const& other) = delete; | |||||
| 199 | 162 | |||||||
| 200 | /** Copy assignment is disabled; a context owns its services. | 163 | /** Copy assignment is disabled; a context owns its services. | |||||
| 201 | 164 | |||||||
| 202 | @param other The context that would be assigned from. | 165 | @param other The context that would be assigned from. | |||||
| 203 | 166 | |||||||
| 204 | @return A reference to `*this`. | 167 | @return A reference to `*this`. | |||||
| 205 | */ | 168 | */ | |||||
| 206 | execution_context& operator=(execution_context const& other) = delete; | 169 | execution_context& operator=(execution_context const& other) = delete; | |||||
| 207 | 170 | |||||||
| 208 | /** Destructor. | 171 | /** Destructor. | |||||
| 209 | 172 | |||||||
| 210 | Calls `shutdown()` then `destroy()` to clean up all services. | 173 | Calls `shutdown()` then `destroy()` to clean up all services. | |||||
| 211 | 174 | |||||||
| 212 | @par Effects | 175 | @par Effects | |||||
| 213 | All services are shut down and deleted in reverse order | 176 | All services are shut down and deleted in reverse order | |||||
| 214 | of creation. | 177 | of creation. | |||||
| 215 | 178 | |||||||
| 216 | @par Exception Safety | 179 | @par Exception Safety | |||||
| 217 | No-throw guarantee. | 180 | No-throw guarantee. | |||||
| 218 | */ | 181 | */ | |||||
| 219 | ~execution_context(); | 182 | ~execution_context(); | |||||
| 220 | 183 | |||||||
| 221 | /** Construct a default instance. | 184 | /** Construct a default instance. | |||||
| 222 | 185 | |||||||
| 223 | @par Exception Safety | 186 | @par Exception Safety | |||||
| 224 | Strong guarantee. | 187 | Strong guarantee. | |||||
| 225 | */ | 188 | */ | |||||
| 226 | execution_context(); | 189 | execution_context(); | |||||
| 227 | 190 | |||||||
| 228 | /** Return true if a service of type T exists. | 191 | /** Return true if a service of type T exists. | |||||
| 229 | 192 | |||||||
| 230 | @par Thread Safety | 193 | @par Thread Safety | |||||
| 231 | Thread-safe. | 194 | Thread-safe. | |||||
| 232 | 195 | |||||||
| 233 | @tparam T The type of service to check. | 196 | @tparam T The type of service to check. | |||||
| 234 | 197 | |||||||
| 235 | @return `true` if the service exists. | 198 | @return `true` if the service exists. | |||||
| 236 | */ | 199 | */ | |||||
| 237 | template<class T> | 200 | template<class T> | |||||
| HITCBC | 238 | 16 | bool has_service() const noexcept | 201 | 16 | bool has_service() const noexcept | ||
| 239 | { | 202 | { | |||||
| HITCBC | 240 | 16 | return find_service<T>() != nullptr; | 203 | 16 | return find_service<T>() != nullptr; | ||
| 241 | } | 204 | } | |||||
| 242 | 205 | |||||||
| 243 | /** Return a pointer to the service of type T, or nullptr. | 206 | /** Return a pointer to the service of type T, or nullptr. | |||||
| 244 | 207 | |||||||
| 245 | @par Thread Safety | 208 | @par Thread Safety | |||||
| 246 | Thread-safe. | 209 | Thread-safe. | |||||
| 247 | 210 | |||||||
| 248 | @tparam T The type of service to find. | 211 | @tparam T The type of service to find. | |||||
| 249 | 212 | |||||||
| 250 | @return A pointer to the service, or `nullptr` if not present. | 213 | @return A pointer to the service, or `nullptr` if not present. | |||||
| 251 | */ | 214 | */ | |||||
| 252 | template<class T> | 215 | template<class T> | |||||
| HITCBC | 253 | 25 | T* find_service() const noexcept | 216 | 25 | T* find_service() const noexcept | ||
| 254 | { | 217 | { | |||||
| HITCBC | 255 | 25 | std::lock_guard<std::mutex> lock(mutex_); | 218 | 25 | std::lock_guard<std::mutex> lock(mutex_); | ||
| HITCBC | 256 | 25 | return static_cast<T*>(find_impl(detail::type_id<T>())); | 219 | 25 | return static_cast<T*>(find_impl(detail::type_id<T>())); | ||
| HITCBC | 257 | 25 | } | 220 | 25 | } | ||
| 258 | 221 | |||||||
| 259 | /** Return a reference to the service of type T, creating it if needed. | 222 | /** Return a reference to the service of type T, creating it if needed. | |||||
| 260 | 223 | |||||||
| 261 | If no service of type T exists, one is created by calling | 224 | If no service of type T exists, one is created by calling | |||||
| 262 | `T(execution_context&)`. If T has a nested `key_type`, the | 225 | `T(execution_context&)`. If T has a nested `key_type`, the | |||||
| 263 | service is also indexed under that type. | 226 | service is also indexed under that type. | |||||
| 264 | 227 | |||||||
| 265 | @par Constraints | 228 | @par Constraints | |||||
| 266 | @li `T` must derive from `service`. | 229 | @li `T` must derive from `service`. | |||||
| 267 | @li `T` must be constructible from `execution_context&`. | 230 | @li `T` must be constructible from `execution_context&`. | |||||
| 268 | 231 | |||||||
| 269 | @par Exception Safety | 232 | @par Exception Safety | |||||
| 270 | Strong guarantee. If service creation throws, the container | 233 | Strong guarantee. If service creation throws, the container | |||||
| 271 | is unchanged. | 234 | is unchanged. | |||||
| 272 | 235 | |||||||
| 273 | @par Thread Safety | 236 | @par Thread Safety | |||||
| 274 | Thread-safe. | 237 | Thread-safe. | |||||
| 275 | 238 | |||||||
| 276 | @tparam T The type of service to retrieve or create. | 239 | @tparam T The type of service to retrieve or create. | |||||
| 277 | 240 | |||||||
| 278 | @return A reference to the service. | 241 | @return A reference to the service. | |||||
| 279 | */ | 242 | */ | |||||
| 280 | template<class T> | 243 | template<class T> | |||||
| HITCBC | 281 | 11465 | T& use_service() | 244 | 11465 | T& use_service() | ||
| 282 | { | 245 | { | |||||
| 283 | static_assert(std::is_base_of<service, T>::value, | 246 | static_assert(std::is_base_of<service, T>::value, | |||||
| 284 | "T must derive from service"); | 247 | "T must derive from service"); | |||||
| 285 | static_assert(std::is_constructible<T, execution_context&>::value, | 248 | static_assert(std::is_constructible<T, execution_context&>::value, | |||||
| 286 | "T must be constructible from execution_context&"); | 249 | "T must be constructible from execution_context&"); | |||||
| 287 | 250 | |||||||
| 288 | struct impl : factory | 251 | struct impl : factory | |||||
| 289 | { | 252 | { | |||||
| HITCBC | 290 | 11465 | impl() | 253 | 11465 | impl() | ||
| 291 | : factory( | 254 | : factory( | |||||
| 292 | detail::type_id<T>(), | 255 | detail::type_id<T>(), | |||||
| 293 | get_key<T>::value | 256 | get_key<T>::value | |||||
| 294 | ? detail::type_id<typename get_key<T>::type>() | 257 | ? detail::type_id<typename get_key<T>::type>() | |||||
| HITCBC | 295 | 11465 | : detail::type_id<T>()) | 258 | 11465 | : detail::type_id<T>()) | ||
| 296 | { | 259 | { | |||||
| HITCBC | 297 | 11465 | } | 260 | 11465 | } | ||
| 298 | 261 | |||||||
| HITCBC | 299 | 43 | service* create(execution_context& ctx) override | 262 | 45 | service* create(execution_context& ctx) override | ||
| 300 | { | 263 | { | |||||
| HITCBC | 301 | 43 | return new T(ctx); | 264 | 45 | return new T(ctx); | ||
| 302 | } | 265 | } | |||||
| 303 | }; | 266 | }; | |||||
| 304 | 267 | |||||||
| HITCBC | 305 | 11465 | impl f; | 268 | 11465 | impl f; | ||
| HITCBC | 306 | 22930 | return static_cast<T&>(use_service_impl(f)); | 269 | 22930 | return static_cast<T&>(use_service_impl(f)); | ||
| 307 | } | 270 | } | |||||
| 308 | 271 | |||||||
| 309 | /** Construct and add a service. | 272 | /** Construct and add a service. | |||||
| 310 | 273 | |||||||
| 311 | A new service of type T is constructed using the provided | 274 | A new service of type T is constructed using the provided | |||||
| 312 | arguments and added to the container. If T has a nested | 275 | arguments and added to the container. If T has a nested | |||||
| 313 | `key_type`, the service is also indexed under that type. | 276 | `key_type`, the service is also indexed under that type. | |||||
| 314 | 277 | |||||||
| 315 | @par Constraints | 278 | @par Constraints | |||||
| 316 | @li `T` must derive from `service`. | 279 | @li `T` must derive from `service`. | |||||
| 317 | @li `T` must be constructible from `execution_context&, Args...`. | 280 | @li `T` must be constructible from `execution_context&, Args...`. | |||||
| 318 | @li If `T::key_type` exists, `T&` must be convertible to `key_type&`. | 281 | @li If `T::key_type` exists, `T&` must be convertible to `key_type&`. | |||||
| 319 | 282 | |||||||
| 320 | @par Exception Safety | 283 | @par Exception Safety | |||||
| 321 | Strong guarantee. If service creation throws, the container | 284 | Strong guarantee. If service creation throws, the container | |||||
| 322 | is unchanged. | 285 | is unchanged. | |||||
| 323 | 286 | |||||||
| 324 | @par Thread Safety | 287 | @par Thread Safety | |||||
| 325 | Thread-safe. | 288 | Thread-safe. | |||||
| 326 | 289 | |||||||
| 327 | @throws std::invalid_argument if a service of the same type | 290 | @throws std::invalid_argument if a service of the same type | |||||
| 328 | or `key_type` already exists. | 291 | or `key_type` already exists. | |||||
| 329 | 292 | |||||||
| 330 | @tparam T The type of service to create. | 293 | @tparam T The type of service to create. | |||||
| 331 | 294 | |||||||
| 332 | @param args Arguments forwarded to the constructor of T. | 295 | @param args Arguments forwarded to the constructor of T. | |||||
| 333 | 296 | |||||||
| 334 | @return A reference to the created service. | 297 | @return A reference to the created service. | |||||
| 335 | */ | 298 | */ | |||||
| 336 | template<class T, class... Args> | 299 | template<class T, class... Args> | |||||
| HITCBC | 337 | 12 | T& make_service(Args&&... args) | 300 | 12 | T& make_service(Args&&... args) | ||
| 338 | { | 301 | { | |||||
| 339 | static_assert(std::is_base_of<service, T>::value, | 302 | static_assert(std::is_base_of<service, T>::value, | |||||
| 340 | "T must derive from service"); | 303 | "T must derive from service"); | |||||
| 341 | if constexpr(get_key<T>::value) | 304 | if constexpr(get_key<T>::value) | |||||
| 342 | { | 305 | { | |||||
| 343 | static_assert( | 306 | static_assert( | |||||
| 344 | std::is_convertible<T&, typename get_key<T>::type&>::value, | 307 | std::is_convertible<T&, typename get_key<T>::type&>::value, | |||||
| 345 | "T& must be convertible to key_type&"); | 308 | "T& must be convertible to key_type&"); | |||||
| 346 | } | 309 | } | |||||
| 347 | 310 | |||||||
| 348 | struct impl : factory | 311 | struct impl : factory | |||||
| 349 | { | 312 | { | |||||
| 350 | std::tuple<Args&&...> args_; | 313 | std::tuple<Args&&...> args_; | |||||
| 351 | 314 | |||||||
| HITCBC | 352 | 12 | explicit impl(Args&&... a) | 315 | 12 | explicit impl(Args&&... a) | ||
| 353 | : factory( | 316 | : factory( | |||||
| 354 | detail::type_id<T>(), | 317 | detail::type_id<T>(), | |||||
| 355 | get_key<T>::value | 318 | get_key<T>::value | |||||
| 356 | ? detail::type_id<typename get_key<T>::type>() | 319 | ? detail::type_id<typename get_key<T>::type>() | |||||
| 357 | : detail::type_id<T>()) | 320 | : detail::type_id<T>()) | |||||
| HITCBC | 358 | 12 | , args_(std::forward<Args>(a)...) | 321 | 12 | , args_(std::forward<Args>(a)...) | ||
| 359 | { | 322 | { | |||||
| HITCBC | 360 | 12 | } | 323 | 12 | } | ||
| 361 | 324 | |||||||
| HITCBC | 362 | 9 | service* create(execution_context& ctx) override | 325 | 9 | service* create(execution_context& ctx) override | ||
| 363 | { | 326 | { | |||||
| HITCBC | 364 | 26 | return std::apply([&ctx](auto&&... a) { | 327 | 26 | return std::apply([&ctx](auto&&... a) { | ||
| HITCBC | 365 | 11 | return new T(ctx, std::forward<decltype(a)>(a)...); | 328 | 11 | return new T(ctx, std::forward<decltype(a)>(a)...); | ||
| HITCBC | 366 | 27 | }, std::move(args_)); | 329 | 27 | }, std::move(args_)); | ||
| 367 | } | 330 | } | |||||
| 368 | }; | 331 | }; | |||||
| 369 | 332 | |||||||
| HITCBC | 370 | 12 | impl f(std::forward<Args>(args)...); | 333 | 12 | impl f(std::forward<Args>(args)...); | ||
| HITCBC | 371 | 20 | return static_cast<T&>(make_service_impl(f)); | 334 | 20 | return static_cast<T&>(make_service_impl(f)); | ||
| 372 | } | 335 | } | |||||
| 373 | 336 | |||||||
| 374 | //------------------------------------------------ | 337 | //------------------------------------------------ | |||||
| 375 | 338 | |||||||
| 376 | /** Return the memory resource used for coroutine frame allocation. | 339 | /** Return the memory resource used for coroutine frame allocation. | |||||
| 377 | 340 | |||||||
| 378 | The returned pointer is valid for the lifetime of this context. | 341 | The returned pointer is valid for the lifetime of this context. | |||||
| 379 | By default, this returns a pointer to the recycling memory | 342 | By default, this returns a pointer to the recycling memory | |||||
| 380 | resource which pools frame allocations for reuse. | 343 | resource which pools frame allocations for reuse. | |||||
| 381 | 344 | |||||||
| 382 | @return Pointer to the frame allocator. | 345 | @return Pointer to the frame allocator. | |||||
| 383 | 346 | |||||||
| 384 | @see set_frame_allocator | 347 | @see set_frame_allocator | |||||
| 385 | */ | 348 | */ | |||||
| 386 | std::pmr::memory_resource* | 349 | std::pmr::memory_resource* | |||||
| HITCBC | 387 | 1927 | get_frame_allocator() const noexcept | 350 | 1933 | get_frame_allocator() const noexcept | ||
| 388 | { | 351 | { | |||||
| HITCBC | 389 | 1927 | return frame_alloc_; | 352 | 1933 | return frame_alloc_; | ||
| 390 | } | 353 | } | |||||
| 391 | 354 | |||||||
| 392 | /** Set the memory resource used for coroutine frame allocation. | 355 | /** Set the memory resource used for coroutine frame allocation. | |||||
| 393 | 356 | |||||||
| 394 | The caller is responsible for ensuring the memory resource | 357 | The caller is responsible for ensuring the memory resource | |||||
| 395 | remains valid for the lifetime of all coroutines started | 358 | remains valid for the lifetime of all coroutines started | |||||
| 396 | using this context's executor. | 359 | using this context's executor. | |||||
| 397 | 360 | |||||||
| 398 | @par Thread Safety | 361 | @par Thread Safety | |||||
| 399 | Not thread-safe. Must not be called while any thread may | 362 | Not thread-safe. Must not be called while any thread may | |||||
| 400 | be referencing this execution context or its executor. | 363 | be referencing this execution context or its executor. | |||||
| 401 | 364 | |||||||
| 402 | @param mr Pointer to the memory resource. | 365 | @param mr Pointer to the memory resource. | |||||
| 403 | 366 | |||||||
| 404 | @see get_frame_allocator | 367 | @see get_frame_allocator | |||||
| 405 | */ | 368 | */ | |||||
| 406 | void | 369 | void | |||||
| HITCBC | 407 | 1 | set_frame_allocator(std::pmr::memory_resource* mr) noexcept | 370 | 1 | set_frame_allocator(std::pmr::memory_resource* mr) noexcept | ||
| 408 | { | 371 | { | |||||
| HITCBC | 409 | 1 | owned_.reset(); | 372 | 1 | owned_.reset(); | ||
| HITCBC | 410 | 1 | frame_alloc_ = mr; | 373 | 1 | frame_alloc_ = mr; | ||
| HITCBC | 411 | 1 | } | 374 | 1 | } | ||
| 412 | 375 | |||||||
| 413 | /** Set the frame allocator from a standard Allocator. | 376 | /** Set the frame allocator from a standard Allocator. | |||||
| 414 | 377 | |||||||
| 415 | The allocator is wrapped in an internal memory resource | 378 | The allocator is wrapped in an internal memory resource | |||||
| 416 | adapter owned by this context. The wrapper remains valid | 379 | adapter owned by this context. The wrapper remains valid | |||||
| 417 | for the lifetime of this context or until a subsequent | 380 | for the lifetime of this context or until a subsequent | |||||
| 418 | call to set_frame_allocator. | 381 | call to set_frame_allocator. | |||||
| 419 | 382 | |||||||
| 420 | @par Thread Safety | 383 | @par Thread Safety | |||||
| 421 | Not thread-safe. Must not be called while any thread may | 384 | Not thread-safe. Must not be called while any thread may | |||||
| 422 | be referencing this execution context or its executor. | 385 | be referencing this execution context or its executor. | |||||
| 423 | 386 | |||||||
| 424 | @tparam Allocator The allocator type satisfying the | 387 | @tparam Allocator The allocator type satisfying the | |||||
| 425 | standard Allocator requirements. | 388 | standard Allocator requirements. | |||||
| 426 | 389 | |||||||
| 427 | @param a The allocator to use. | 390 | @param a The allocator to use. | |||||
| 428 | 391 | |||||||
| 429 | @see get_frame_allocator | 392 | @see get_frame_allocator | |||||
| 430 | */ | 393 | */ | |||||
| 431 | template<class Allocator> | 394 | template<class Allocator> | |||||
| 432 | requires (!std::is_pointer_v<Allocator>) | 395 | requires (!std::is_pointer_v<Allocator>) | |||||
| 433 | void | 396 | void | |||||
| HITCBC | 434 | 392 | set_frame_allocator(Allocator const& a) | 397 | 398 | set_frame_allocator(Allocator const& a) | ||
| 435 | { | 398 | { | |||||
| 436 | static_assert( | 399 | static_assert( | |||||
| 437 | requires { typename std::allocator_traits<Allocator>::value_type; }, | 400 | requires { typename std::allocator_traits<Allocator>::value_type; }, | |||||
| 438 | "Allocator must satisfy allocator requirements"); | 401 | "Allocator must satisfy allocator requirements"); | |||||
| 439 | static_assert( | 402 | static_assert( | |||||
| 440 | std::is_copy_constructible_v<Allocator>, | 403 | std::is_copy_constructible_v<Allocator>, | |||||
| 441 | "Allocator must be copy constructible"); | 404 | "Allocator must be copy constructible"); | |||||
| 442 | 405 | |||||||
| HITCBC | 443 | 392 | auto p = std::make_shared< | 406 | 398 | auto p = std::make_shared< | ||
| 444 | detail::frame_memory_resource<Allocator>>(a); | 407 | detail::frame_memory_resource<Allocator>>(a); | |||||
| HITCBC | 445 | 392 | frame_alloc_ = p.get(); | 408 | 398 | frame_alloc_ = p.get(); | ||
| HITCBC | 446 | 392 | owned_ = std::move(p); | 409 | 398 | owned_ = std::move(p); | ||
| HITCBC | 447 | 392 | } | 410 | 398 | } | ||
| 448 | 411 | |||||||
| 449 | /** Return a pointer to this context if it matches the | 412 | /** Return a pointer to this context if it matches the | |||||
| 450 | requested type. | 413 | requested type. | |||||
| 451 | 414 | |||||||
| 452 | Performs a type check and downcasts `this` when the | 415 | Performs a type check and downcasts `this` when the | |||||
| 453 | types match, or returns `nullptr` otherwise. Analogous | 416 | types match, or returns `nullptr` otherwise. Analogous | |||||
| 454 | to `std::any_cast< ExecutionContext >( &a )`. | 417 | to `std::any_cast< ExecutionContext >( &a )`. | |||||
| 455 | 418 | |||||||
| 456 | @tparam ExecutionContext The derived context type to | 419 | @tparam ExecutionContext The derived context type to | |||||
| 457 | retrieve. | 420 | retrieve. | |||||
| 458 | 421 | |||||||
| 459 | @return A pointer to this context as the requested | 422 | @return A pointer to this context as the requested | |||||
| 460 | type, or `nullptr` if the type does not match. | 423 | type, or `nullptr` if the type does not match. | |||||
| 461 | */ | 424 | */ | |||||
| 462 | template< typename ExecutionContext > | 425 | template< typename ExecutionContext > | |||||
| HITCBC | 463 | 2 | const ExecutionContext* target() const | 426 | 2 | const ExecutionContext* target() const | ||
| 464 | { | 427 | { | |||||
| HITCBC | 465 | 2 | if ( ti_ && *ti_ == detail::type_id< ExecutionContext >() ) | 428 | 2 | if ( ti_ && *ti_ == detail::type_id< ExecutionContext >() ) | ||
| HITCBC | 466 | 1 | return static_cast< ExecutionContext const* >( this ); | 429 | 1 | return static_cast< ExecutionContext const* >( this ); | ||
| HITCBC | 467 | 1 | return nullptr; | 430 | 1 | return nullptr; | ||
| 468 | } | 431 | } | |||||
| 469 | 432 | |||||||
| 470 | /// @copydoc target() const | 433 | /// @copydoc target() const | |||||
| 471 | template< typename ExecutionContext > | 434 | template< typename ExecutionContext > | |||||
| HITCBC | 472 | 2 | ExecutionContext* target() | 435 | 2 | ExecutionContext* target() | ||
| 473 | { | 436 | { | |||||
| HITCBC | 474 | 2 | if ( ti_ && *ti_ == detail::type_id< ExecutionContext >() ) | 437 | 2 | if ( ti_ && *ti_ == detail::type_id< ExecutionContext >() ) | ||
| HITCBC | 475 | 1 | return static_cast< ExecutionContext* >( this ); | 438 | 1 | return static_cast< ExecutionContext* >( this ); | ||
| HITCBC | 476 | 1 | return nullptr; | 439 | 1 | return nullptr; | ||
| 477 | } | 440 | } | |||||
| 478 | 441 | |||||||
| 479 | protected: | 442 | protected: | |||||
| 480 | /** Shut down all services. | 443 | /** Shut down all services. | |||||
| 481 | 444 | |||||||
| 482 | Calls `shutdown()` on each service in reverse order of creation. | 445 | Calls `shutdown()` on each service in reverse order of creation. | |||||
| 483 | After this call, services remain allocated but are in a stopped | 446 | After this call, services remain allocated but are in a stopped | |||||
| 484 | state. Derived classes should call this in their destructor | 447 | state. Derived classes should call this in their destructor | |||||
| 485 | before any members are destroyed. This function is idempotent; | 448 | before any members are destroyed. This function is idempotent; | |||||
| 486 | subsequent calls have no effect. | 449 | subsequent calls have no effect. | |||||
| 487 | 450 | |||||||
| 488 | @par Effects | 451 | @par Effects | |||||
| 489 | Each service's `shutdown()` member function is invoked once. | 452 | Each service's `shutdown()` member function is invoked once. | |||||
| 490 | 453 | |||||||
| 491 | @par Postconditions | 454 | @par Postconditions | |||||
| 492 | @li All services are in a stopped state. | 455 | @li All services are in a stopped state. | |||||
| 493 | 456 | |||||||
| 494 | @par Exception Safety | 457 | @par Exception Safety | |||||
| 495 | No-throw guarantee. | 458 | No-throw guarantee. | |||||
| 496 | 459 | |||||||
| 497 | @par Thread Safety | 460 | @par Thread Safety | |||||
| 498 | Not thread-safe. Must not be called concurrently with other | 461 | Not thread-safe. Must not be called concurrently with other | |||||
| 499 | operations on this execution_context. | 462 | operations on this execution_context. | |||||
| 500 | */ | 463 | */ | |||||
| 501 | void shutdown() noexcept; | 464 | void shutdown() noexcept; | |||||
| 502 | 465 | |||||||
| 503 | /** Destroy all services. | 466 | /** Destroy all services. | |||||
| 504 | 467 | |||||||
| 505 | Deletes all services in reverse order of creation. Derived | 468 | Deletes all services in reverse order of creation. Derived | |||||
| 506 | classes should call this as the final step of destruction. | 469 | classes should call this as the final step of destruction. | |||||
| 507 | This function is idempotent; subsequent calls have no effect. | 470 | This function is idempotent; subsequent calls have no effect. | |||||
| 508 | 471 | |||||||
| 509 | @par Preconditions | 472 | @par Preconditions | |||||
| 510 | @li `shutdown()` was called. | 473 | @li `shutdown()` was called. | |||||
| 511 | 474 | |||||||
| 512 | @par Effects | 475 | @par Effects | |||||
| 513 | All services are deleted and removed from the container. | 476 | All services are deleted and removed from the container. | |||||
| 514 | 477 | |||||||
| 515 | @par Postconditions | 478 | @par Postconditions | |||||
| 516 | @li The service container is empty. | 479 | @li The service container is empty. | |||||
| 517 | 480 | |||||||
| 518 | @par Exception Safety | 481 | @par Exception Safety | |||||
| 519 | No-throw guarantee. | 482 | No-throw guarantee. | |||||
| 520 | 483 | |||||||
| 521 | @par Thread Safety | 484 | @par Thread Safety | |||||
| 522 | Not thread-safe. Must not be called concurrently with other | 485 | Not thread-safe. Must not be called concurrently with other | |||||
| 523 | operations on this execution_context. | 486 | operations on this execution_context. | |||||
| 524 | */ | 487 | */ | |||||
| 525 | void destroy() noexcept; | 488 | void destroy() noexcept; | |||||
| 526 | 489 | |||||||
| 527 | private: | 490 | private: | |||||
| 528 | struct BOOST_CAPY_DECL | 491 | struct BOOST_CAPY_DECL | |||||
| 529 | factory | 492 | factory | |||||
| 530 | { | 493 | { | |||||
| 531 | // warning C4251: 'std::type_index' needs to have dll-interface | 494 | // warning C4251: 'std::type_index' needs to have dll-interface | |||||
| 532 | BOOST_CAPY_MSVC_WARNING_PUSH | 495 | BOOST_CAPY_MSVC_WARNING_PUSH | |||||
| 533 | BOOST_CAPY_MSVC_WARNING_DISABLE(4251) | 496 | BOOST_CAPY_MSVC_WARNING_DISABLE(4251) | |||||
| 534 | detail::type_index t0; | 497 | detail::type_index t0; | |||||
| 535 | detail::type_index t1; | 498 | detail::type_index t1; | |||||
| 536 | BOOST_CAPY_MSVC_WARNING_POP | 499 | BOOST_CAPY_MSVC_WARNING_POP | |||||
| 537 | 500 | |||||||
| HITCBC | 538 | 11477 | factory( | 501 | 11477 | factory( | ||
| 539 | detail::type_info const& t0_, | 502 | detail::type_info const& t0_, | |||||
| 540 | detail::type_info const& t1_) | 503 | detail::type_info const& t1_) | |||||
| HITCBC | 541 | 11477 | : t0(t0_), t1(t1_) | 504 | 11477 | : t0(t0_), t1(t1_) | ||
| 542 | { | 505 | { | |||||
| HITCBC | 543 | 11477 | } | 506 | 11477 | } | ||
| 544 | 507 | |||||||
| 545 | virtual service* create(execution_context&) = 0; | 508 | virtual service* create(execution_context&) = 0; | |||||
| 546 | 509 | |||||||
| 547 | protected: | 510 | protected: | |||||
| 548 | ~factory() = default; | 511 | ~factory() = default; | |||||
| 549 | }; | 512 | }; | |||||
| 550 | 513 | |||||||
| 551 | service* find_impl(detail::type_index ti) const noexcept; | 514 | service* find_impl(detail::type_index ti) const noexcept; | |||||
| 552 | service& use_service_impl(factory& f); | 515 | service& use_service_impl(factory& f); | |||||
| 553 | service& make_service_impl(factory& f); | 516 | service& make_service_impl(factory& f); | |||||
| 554 | 517 | |||||||
| 555 | // warning C4251: std::mutex, std::shared_ptr need dll-interface | 518 | // warning C4251: std::mutex, std::shared_ptr need dll-interface | |||||
| 556 | BOOST_CAPY_MSVC_WARNING_PUSH | 519 | BOOST_CAPY_MSVC_WARNING_PUSH | |||||
| 557 | BOOST_CAPY_MSVC_WARNING_DISABLE(4251) | 520 | BOOST_CAPY_MSVC_WARNING_DISABLE(4251) | |||||
| 558 | mutable std::mutex mutex_; | 521 | mutable std::mutex mutex_; | |||||
| 559 | std::shared_ptr<void> owned_; | 522 | std::shared_ptr<void> owned_; | |||||
| 560 | BOOST_CAPY_MSVC_WARNING_POP | 523 | BOOST_CAPY_MSVC_WARNING_POP | |||||
| 561 | std::pmr::memory_resource* frame_alloc_ = nullptr; | 524 | std::pmr::memory_resource* frame_alloc_ = nullptr; | |||||
| 562 | service* head_ = nullptr; | 525 | service* head_ = nullptr; | |||||
| 563 | bool shutdown_ = false; | 526 | bool shutdown_ = false; | |||||
| 564 | }; | 527 | }; | |||||
| 565 | 528 | |||||||
| 566 | template< typename Derived > | 529 | template< typename Derived > | |||||
| HITCBC | 567 | 29 | execution_context:: | 530 | 29 | execution_context:: | ||
| 568 | execution_context( Derived* ) noexcept | 531 | execution_context( Derived* ) noexcept | |||||
| HITCBC | 569 | 29 | : execution_context() | 532 | 29 | : execution_context() | ||
| 570 | { | 533 | { | |||||
| HITCBC | 571 | 29 | ti_ = &detail::type_id< Derived >(); | 534 | 29 | ti_ = &detail::type_id< Derived >(); | ||
| HITCBC | 572 | 29 | } | 535 | 29 | } | ||
| 573 | 536 | |||||||
| 574 | } // namespace capy | 537 | } // namespace capy | |||||
| 575 | } // namespace boost | 538 | } // namespace boost | |||||
| 576 | 539 | |||||||
| 577 | #endif | 540 | #endif | |||||