Browse Source

locator: ::handle and extended ::reset

Michele Caini 3 years ago
parent
commit
4200bb9ebb
3 changed files with 73 additions and 14 deletions
  1. 32 0
      docs/md/locator.md
  2. 31 13
      src/entt/locator/locator.hpp
  3. 10 1
      test/entt/locator/locator.cpp

+ 32 - 0
docs/md/locator.md

@@ -7,6 +7,7 @@
 
 
 * [Introduction](#introduction)
 * [Introduction](#introduction)
 * [Service locator](#service-locator)
 * [Service locator](#service-locator)
+  * [Opaque handles](#opaque-handles)
 <!--
 <!--
 @endcond TURN_OFF_DOXYGEN
 @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
 exist and therefore the fallback service is constructed and returned. In all
 other cases, they are discarded.<br/>
 other cases, they are discarded.<br/>
 Finally, to reset a service, use the `reset` function.
 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.
  * @tparam Service Service type.
  */
  */
 template<typename Service>
 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. */
     /*! @brief Service type. */
     using type = Service;
     using type = Service;
+    /*! @brief Service node type. */
+    using node_type = service_handle;
 
 
     /*! @brief Default constructor, deleted on purpose. */
     /*! @brief Default constructor, deleted on purpose. */
     locator() = delete;
     locator() = delete;
@@ -38,7 +46,7 @@ struct locator final {
      * @return True if the service locator contains a value, false otherwise.
      * @return True if the service locator contains a value, false otherwise.
      */
      */
     [[nodiscard]] static bool has_value() noexcept {
     [[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 {
     [[nodiscard]] static Service &value() noexcept {
         ENTT_ASSERT(has_value(), "Service not available");
         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>
     template<typename Impl = Service, typename... Args>
     [[nodiscard]] static Service &value_or(Args &&...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>
     template<typename Impl = Service, typename... Args>
     static Service &emplace(Args &&...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>
     template<typename Impl = Service, typename Allocator, typename... Args>
     static Service &allocate_emplace(Allocator alloc, Args &&...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:
 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
 } // namespace entt

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

@@ -35,6 +35,7 @@ using ServiceLocatorDeathTest = ServiceLocator;
 
 
 TEST(ServiceLocator, Functionalities) {
 TEST(ServiceLocator, Functionalities) {
     ASSERT_FALSE(entt::locator<base_service>::has_value());
     ASSERT_FALSE(entt::locator<base_service>::has_value());
+    ASSERT_FALSE(derived_service::invoked);
     ASSERT_FALSE(null_service::invoked);
     ASSERT_FALSE(null_service::invoked);
 
 
     entt::locator<base_service>::value_or<null_service>().invoke();
     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(entt::locator<base_service>::has_value());
     ASSERT_TRUE(null_service::invoked);
     ASSERT_TRUE(null_service::invoked);
 
 
+    auto handle = entt::locator<base_service>::handle();
     entt::locator<base_service>::reset();
     entt::locator<base_service>::reset();
 
 
     ASSERT_FALSE(entt::locator<base_service>::has_value());
     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>::emplace<derived_service>();
     entt::locator<base_service>::value().invoke();
     entt::locator<base_service>::value().invoke();