ソースを参照

locator:
* service_locator -> locator
* API review + allocator support
* updated doc and tests

Michele Caini 4 年 前
コミット
ed34a3f5d4
4 ファイル変更124 行追加120 行削除
  1. 1 1
      TODO
  2. 24 43
      docs/md/locator.md
  3. 54 46
      src/entt/locator/locator.hpp
  4. 45 30
      test/entt/locator/locator.cpp

+ 1 - 1
TODO

@@ -1,4 +1,4 @@
-* long term feature: shared_ptr less locator and resource cache
+* long term feature: resource cache
 * debugging tools (#60): the issue online already contains interesting tips on this, look at it
 * work stealing job system (see #100) + mt scheduler based on const awareness for types
 * add examples (and credits) from @alanjfs :)

+ 24 - 43
docs/md/locator.md

@@ -14,62 +14,43 @@
 # Introduction
 
 Usually service locators are tightly bound to the services they expose and it's
-hard to define a general purpose solution. This template based implementation
-tries to fill the gap and to get rid of the burden of defining a different
-specific locator for each application.<br/>
-This class is tiny, partially unsafe and thus risky to use. Moreover it doesn't
-fit probably most of the scenarios in which a service locator is required. Look
-at it as a small tool that can sometimes be useful if users know how to handle
-it.
+hard to define a general purpose solution.<br/>
+This tiny class tries to fill the gap and to get rid of the burden of defining a
+different specific locator for each application.
 
 # Service locator
 
-The API is straightforward. The basic idea is that services are implemented by
-means of interfaces and rely on polymorphism.<br/>
-The locator is instantiated with the base type of the service if any and a
-concrete implementation is provided along with all the parameters required to
-initialize it. As an example:
+The service locator API tries to mimic that of `std::optional` and adds some
+extra functionalities on top of it such as allocator support.<br/>
+There are a couple of functions to set up a service, namely `emplace` and
+`allocate_emplace`:
 
 ```cpp
-// the service has no base type, a locator is used to treat it as a kind of singleton
-entt::service_locator<my_service>::set(params...);
-
-// sets up an opaque service
-entt::service_locator<audio_interface>::set<audio_implementation>(params...);
-
-// resets (destroys) the service
-entt::service_locator<audio_interface>::reset();
+entt::locator<interface>::emplace<service>(argument);
+entt::locator<interface>::allocate_emplace<service>(allocator, argument);
 ```
 
-The locator can also be queried to know if an active service is currently set
-and to retrieve it if necessary (either as a pointer or as a reference):
+The difference is that the latter expects an allocator as the first argument and
+uses it to allocate the service itself.<br/>
+Once a service has been set up, it's retrieved using the value function:
 
 ```cpp
-// no service currently set
-auto empty = entt::service_locator<audio_interface>::empty();
-
-// gets a (possibly empty) shared pointer to the service ...
-std::shared_ptr<audio_interface> ptr = entt::service_locator<audio_interface>::get();
-
-// ... or a reference, but it's undefined behaviour if the service isn't set yet
-audio_interface &ref = entt::service_locator<audio_interface>::ref();
+interface &service = entt::locator<interface>::value();
 ```
 
-A common use is to wrap the different locators in a container class, creating
-aliases for the various services:
+Since the service may not be set (and therefore this function may result in an
+undefined behavior), the `has_value` and `value_or` functions are also available
+to test a service locator and to get a fallback service in case there is none:
 
 ```cpp
-struct locator {
-    using camera = entt::service_locator<camera_interface>;
-    using audio = entt::service_locator<audio_interface>;
-    // ...
-};
-
-// ...
-
-void init() {
-    locator::camera::set<camera_null>();
-    locator::audio::set<audio_implementation>(params...);
+if(entt::locator<interface>::has_value()) {
     // ...
 }
+
+interface &service = entt::locator<interface>::value_or<fallback_impl>(argument);
 ```
+
+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.

+ 54 - 46
src/entt/locator/locator.hpp

@@ -10,94 +10,102 @@ namespace entt {
 /**
  * @brief Service locator, nothing more.
  *
- * A service locator can be used to do what it promises: locate services.<br/>
+ * A service locator is used to do what it promises: locate services.<br/>
  * Usually service locators are tightly bound to the services they expose and
- * thus it's hard to define a general purpose class to do that. This template
- * based implementation tries to fill the gap and to get rid of the burden of
- * defining a different specific locator for each application.
+ * thus it's hard to define a general purpose class to do that. This tiny class
+ * tries to fill the gap and to get rid of the burden of defining a different
+ * specific locator for each application.
  *
- * @tparam Service Type of service managed by the locator.
+ * @note
+ * Users shouldn't retain references to a service. The recommended way is to
+ * retrieve the service implementation currently set each and every time the
+ * need for it arises. The risk is to incur in unexpected behaviors otherwise.
+ *
+ * @tparam Service Service type.
  */
 template<typename Service>
-struct service_locator {
-    /*! @brief Type of service offered. */
-    using service_type = Service;
+struct locator {
+    /*! @brief Service type. */
+    using type = Service;
 
     /*! @brief Default constructor, deleted on purpose. */
-    service_locator() = delete;
+    locator() = delete;
     /*! @brief Default destructor, deleted on purpose. */
-    ~service_locator() = delete;
+    ~locator() = delete;
 
     /**
-     * @brief Tests if a valid service implementation is set.
-     * @return True if the service is set, false otherwise.
+     * @brief Checks whether a service locator contains a value.
+     * @return True if the service locator contains a value, false otherwise.
      */
-    [[nodiscard]] static bool empty() ENTT_NOEXCEPT {
-        return !static_cast<bool>(service);
+    [[nodiscard]] static bool has_value() ENTT_NOEXCEPT {
+        return (service != nullptr);
     }
 
     /**
-     * @brief Returns a weak pointer to a service implementation, if any.
+     * @brief Returns a reference to a valid service, if any.
      *
-     * Clients of a service shouldn't retain references to it. The recommended
-     * way is to retrieve the service implementation currently set each and
-     * every time the need of using it arises. Otherwise users can incur in
-     * unexpected behaviors.
+     * @warning
+     * Invoking this function can result in undefined behavior if the service
+     * hasn't been set yet.
      *
-     * @return A reference to the service implementation currently set, if any.
+     * @return A reference to the service currently set, if any.
      */
-    [[nodiscard]] static std::weak_ptr<Service> get() ENTT_NOEXCEPT {
-        return service;
+    [[nodiscard]] static Service &value() ENTT_NOEXCEPT {
+        ENTT_ASSERT(has_value(), "Service not available");
+        return *service;
     }
 
     /**
-     * @brief Returns a weak reference to a service implementation, if any.
-     *
-     * Clients of a service shouldn't retain references to it. The recommended
-     * way is to retrieve the service implementation currently set each and
-     * every time the need of using it arises. Otherwise users can incur in
-     * unexpected behaviors.
+     * @brief Returns a service if available or sets it from a fallback type.
      *
-     * @warning
-     * In case no service implementation has been set, a call to this function
-     * results in undefined behavior.
+     * Arguments are used only if a service doesn't already exist. In all other
+     * cases, they are discarded.
      *
-     * @return A reference to the service implementation currently set, if any.
+     * @tparam Args Types of arguments to use to construct the fallback service.
+     * @tparam Impl Fallback service type.
+     * @param args Parameters to use to construct the fallback service.
+     * @return A reference to a valid service.
      */
-    [[nodiscard]] static Service &ref() ENTT_NOEXCEPT {
-        return *service;
+    template<typename Impl = Service, typename... Args>
+    [[nodiscard]] static Service &value_or(Args &&...args) ENTT_NOEXCEPT {
+        return service ? *service : emplace<Impl>(std::forward<Args>(args)...);
     }
 
     /**
      * @brief Sets or replaces a service.
-     * @tparam Impl Type of the new service to use.
+     * @tparam Impl Service type.
      * @tparam Args Types of arguments to use to construct the service.
      * @param args Parameters to use to construct the service.
+     * @return A reference to a valid service.
      */
     template<typename Impl = Service, typename... Args>
-    static void set(Args &&...args) {
+    static Service &emplace(Args &&...args) {
         service = std::make_shared<Impl>(std::forward<Args>(args)...);
+        return *service;
     }
 
     /**
-     * @brief Sets or replaces a service.
-     * @param ptr Service to use to replace the current one.
+     * @brief Sets or replaces a service using a given allocator.
+     * @tparam Impl Service type.
+     * @tparam Allocator Type of allocator used to manage memory and elements.
+     * @tparam Args Types of arguments to use to construct the service.
+     * @param alloc The allocator to use.
+     * @param args Parameters to use to construct the service.
+     * @return A reference to a valid service.
      */
-    static void set(std::shared_ptr<Service> ptr) {
-        ENTT_ASSERT(static_cast<bool>(ptr), "Null service not allowed");
-        service = std::move(ptr);
+    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;
     }
 
-    /**
-     * @brief Resets a service.
-     *
-     * The service is no longer valid after a reset.
-     */
+    /*! @brief Resets a service. */
     static void reset() {
         service.reset();
     }
 
 private:
+    // std::shared_ptr because of its type erased allocator which is pretty useful here
     inline static std::shared_ptr<Service> service = nullptr;
 };
 

+ 45 - 30
test/entt/locator/locator.cpp

@@ -1,53 +1,68 @@
-#include <memory>
 #include <gtest/gtest.h>
 #include <entt/locator/locator.hpp>
+#include "../common/throwing_allocator.hpp"
 
-struct a_service {};
+struct base_service {
+    virtual ~base_service() = default;
+    virtual void invoke() {}
+};
+
+struct null_service: base_service {
+    void invoke() override {
+        invoked = true;
+    }
 
-struct another_service {
-    virtual ~another_service() = default;
-    virtual void f(bool) = 0;
-    bool check{false};
+    static inline bool invoked{};
 };
 
-struct derived_service: another_service {
-    derived_service(int)
-        : another_service{} {}
+struct derived_service: base_service {
+    void invoke() override {
+        invoked = true;
+    }
 
-    void f(bool b) override {
-        check = b;
+    static inline bool invoked{};
+};
+
+struct ServiceLocator: ::testing::Test {
+    void SetUp() override {
+        null_service::invoked = false;
+        derived_service::invoked = false;
     }
 };
 
-TEST(ServiceLocator, Functionalities) {
-    ASSERT_TRUE(entt::service_locator<a_service>::empty());
-    ASSERT_TRUE(entt::service_locator<another_service>::empty());
+using ServiceLocatorDeathTest = ServiceLocator;
 
-    entt::service_locator<a_service>::set();
+TEST(ServiceLocator, Functionalities) {
+    ASSERT_FALSE(entt::locator<base_service>::has_value());
+    ASSERT_FALSE(null_service::invoked);
 
-    ASSERT_FALSE(entt::service_locator<a_service>::empty());
-    ASSERT_TRUE(entt::service_locator<another_service>::empty());
+    entt::locator<base_service>::value_or<null_service>().invoke();
 
-    entt::service_locator<a_service>::reset();
+    ASSERT_TRUE(entt::locator<base_service>::has_value());
+    ASSERT_TRUE(null_service::invoked);
 
-    ASSERT_TRUE(entt::service_locator<a_service>::empty());
-    ASSERT_TRUE(entt::service_locator<another_service>::empty());
+    entt::locator<base_service>::reset();
 
-    entt::service_locator<a_service>::set(std::make_shared<a_service>());
+    ASSERT_FALSE(entt::locator<base_service>::has_value());
+    ASSERT_FALSE(derived_service::invoked);
 
-    ASSERT_FALSE(entt::service_locator<a_service>::empty());
-    ASSERT_TRUE(entt::service_locator<another_service>::empty());
+    entt::locator<base_service>::emplace<derived_service>();
+    entt::locator<base_service>::value().invoke();
 
-    entt::service_locator<another_service>::set<derived_service>(42);
+    ASSERT_TRUE(entt::locator<base_service>::has_value());
+    ASSERT_TRUE(derived_service::invoked);
 
-    ASSERT_FALSE(entt::service_locator<a_service>::empty());
-    ASSERT_FALSE(entt::service_locator<another_service>::empty());
+    derived_service::invoked = false;
+    entt::locator<base_service>::allocate_emplace<derived_service>(std::allocator<derived_service>{}).invoke();
 
-    entt::service_locator<another_service>::get().lock()->f(!entt::service_locator<another_service>::get().lock()->check);
+    ASSERT_TRUE(entt::locator<base_service>::has_value());
+    ASSERT_TRUE(derived_service::invoked);
+}
 
-    ASSERT_TRUE(entt::service_locator<another_service>::get().lock()->check);
+TEST(ServiceLocatorDeathTest, UninitializedValue) {
+    ASSERT_NO_FATAL_FAILURE(entt::locator<base_service>::value_or().invoke());
 
-    entt::service_locator<another_service>::ref().f(!entt::service_locator<another_service>::get().lock()->check);
+    entt::locator<base_service>::reset();
 
-    ASSERT_FALSE(entt::service_locator<another_service>::get().lock()->check);
+    ASSERT_DEATH(entt::locator<base_service>::value().invoke(), "");
 }