Prechádzať zdrojové kódy

locator: ::handle and extended ::reset

Michele Caini 3 rokov pred
rodič
commit
4200bb9ebb

+ 32 - 0
docs/md/locator.md

@@ -7,6 +7,7 @@
 
 * [Introduction](#introduction)
 * [Service locator](#service-locator)
+  * [Opaque handles](#opaque-handles)
 <!--
 @endcond TURN_OFF_DOXYGEN
 -->
@@ -54,3 +55,34 @@ All arguments are used only if necessary, that is, if a service doesn't already
 exist and therefore the fallback service is constructed and returned. In all
 other cases, they are discarded.<br/>
 Finally, to reset a service, use the `reset` function.
+
+## Opaque handles
+
+Sometimes it's useful to be able to obtain a _copy_ of a service to be assigned
+to a different locator. For example, when working across boundaries it's common
+to _share_ a service with a dynamically loaded module.<br/>
+Options aren't much in this case. Among these is the possibility of _exporting_
+services and assigning them to a different locator.
+
+This is what the `handle` and `reset` functions allow to do.<br/>
+The former returns an opaque object useful for _exporting_ (or rather, obtaining
+a reference to) a service. The latter also accepts an optional argument to a
+handle which then allows users to reset a service by initializing it with an
+opaque handle:
+
+```cpp
+auto handle = entt::locator<interface>::handle();
+entt::locator<interface>::reset(handle);
+```
+
+It's worth noting that it's possible to get handles for uninitialized services
+and use them with other locators. Of course, all a user will get is to have an
+uninitialized service elsewhere as well.
+
+Note that exporting a service allows users to _share_ the object currently set
+in a locator. Replacing it won't replace the element even where a service has
+been configured with a handle to the previous item.<br/>
+In other words, if for example an audio service is replaced with a null object
+to silence an application and the original service was shared, this operation
+won't propagate to the other locators. Therefore, a module that share the
+ownership of the original audio service is still able to emit sounds.

+ 31 - 13
src/entt/locator/locator.hpp

@@ -24,9 +24,17 @@ namespace entt {
  * @tparam Service Service type.
  */
 template<typename Service>
-struct locator final {
+class locator final {
+    class service_handle {
+        friend class locator<Service>;
+        std::shared_ptr<Service> value{};
+    };
+
+public:
     /*! @brief Service type. */
     using type = Service;
+    /*! @brief Service node type. */
+    using node_type = service_handle;
 
     /*! @brief Default constructor, deleted on purpose. */
     locator() = delete;
@@ -38,7 +46,7 @@ struct locator final {
      * @return True if the service locator contains a value, false otherwise.
      */
     [[nodiscard]] static bool has_value() noexcept {
-        return (service != nullptr);
+        return (service.value != nullptr);
     }
 
     /**
@@ -52,7 +60,7 @@ struct locator final {
      */
     [[nodiscard]] static Service &value() noexcept {
         ENTT_ASSERT(has_value(), "Service not available");
-        return *service;
+        return *service.value;
     }
 
     /**
@@ -68,7 +76,7 @@ struct locator final {
      */
     template<typename Impl = Service, typename... Args>
     [[nodiscard]] static Service &value_or(Args &&...args) {
-        return service ? *service : emplace<Impl>(std::forward<Args>(args)...);
+        return service.value ? *service.value : emplace<Impl>(std::forward<Args>(args)...);
     }
 
     /**
@@ -80,8 +88,8 @@ struct locator final {
      */
     template<typename Impl = Service, typename... Args>
     static Service &emplace(Args &&...args) {
-        service = std::make_shared<Impl>(std::forward<Args>(args)...);
-        return *service;
+        service.value = std::make_shared<Impl>(std::forward<Args>(args)...);
+        return *service.value;
     }
 
     /**
@@ -95,18 +103,28 @@ struct locator final {
      */
     template<typename Impl = Service, typename Allocator, typename... Args>
     static Service &allocate_emplace(Allocator alloc, Args &&...args) {
-        service = std::allocate_shared<Impl>(alloc, std::forward<Args>(args)...);
-        return *service;
+        service.value = std::allocate_shared<Impl>(alloc, std::forward<Args>(args)...);
+        return *service.value;
     }
 
-    /*! @brief Resets a service. */
-    static void reset() noexcept {
-        service.reset();
+    /**
+     * @brief Returns a handle to the underlying service.
+     * @return A handle to the underlying service.
+     */
+    static node_type handle() noexcept {
+        return service;
+    }
+
+    /**
+     * @brief Resets or replaces a service.
+     * @param handle Optional handle with which to replace the service.
+     */
+    static void reset(const node_type &handle = {}) noexcept {
+        service = handle;
     }
 
 private:
-    // std::shared_ptr because of its type erased allocator which is pretty useful here
-    inline static std::shared_ptr<Service> service = nullptr;
+    inline static service_handle service{};
 };
 
 } // namespace entt

+ 10 - 1
test/entt/locator/locator.cpp

@@ -35,6 +35,7 @@ using ServiceLocatorDeathTest = ServiceLocator;
 
 TEST(ServiceLocator, Functionalities) {
     ASSERT_FALSE(entt::locator<base_service>::has_value());
+    ASSERT_FALSE(derived_service::invoked);
     ASSERT_FALSE(null_service::invoked);
 
     entt::locator<base_service>::value_or<null_service>().invoke();
@@ -42,10 +43,18 @@ TEST(ServiceLocator, Functionalities) {
     ASSERT_TRUE(entt::locator<base_service>::has_value());
     ASSERT_TRUE(null_service::invoked);
 
+    auto handle = entt::locator<base_service>::handle();
     entt::locator<base_service>::reset();
 
     ASSERT_FALSE(entt::locator<base_service>::has_value());
-    ASSERT_FALSE(derived_service::invoked);
+
+    entt::locator<base_service>::reset(handle);
+
+    ASSERT_TRUE(entt::locator<base_service>::has_value());
+
+    entt::locator<base_service>::reset(decltype(handle){});
+
+    ASSERT_FALSE(entt::locator<base_service>::has_value());
 
     entt::locator<base_service>::emplace<derived_service>();
     entt::locator<base_service>::value().invoke();