فهرست منبع

doc: updated documentation

Michele Caini 6 سال پیش
والد
کامیت
7a3b7593d0
3فایلهای تغییر یافته به همراه136 افزوده شده و 161 حذف شده
  1. 2 5
      TODO
  2. 66 0
      docs/md/core.md
  3. 68 156
      docs/md/lib.md

+ 2 - 5
TODO

@@ -21,11 +21,8 @@
 * see warning (vs2017 only): d:\a\entt\entt\src\entt\entity\registry.hpp(108)
 * see warning (vs2017 only): d:\a\entt\entt\src\entt\entity\registry.hpp(108)
 * remove duktape example (and eventually provide a new one)?
 * remove duktape example (and eventually provide a new one)?
 * meta: rebind meta types from any across boundaries (eg conv corner case across boundary)
 * meta: rebind meta types from any across boundaries (eg conv corner case across boundary)
+* review and suppress warnings, if any
+* extract only the type within type_info, hash its name (more portable)
 * registry
 * registry
   - ::assure -> pool_type<T> &
   - ::assure -> pool_type<T> &
   - ::group improve, reduce code
   - ::group improve, reduce code
-
-* Mission: get rid of named types
-  - add tests for type_info specializations (eg dispatcher_plugin_std)
-  - update doc: dispatcher, emitter, registry, meta, across boundaries
-  - review and suppress warnings, if any

+ 66 - 0
docs/md/core.md

@@ -12,6 +12,8 @@
   * [Wide characters](wide-characters)
   * [Wide characters](wide-characters)
   * [Conflicts](#conflicts)
   * [Conflicts](#conflicts)
 * [Monostate](#monostate)
 * [Monostate](#monostate)
+* [Type info](#type-info)
+  * [Conflicts](#conflicts)
 <!--
 <!--
 @endcond TURN_OFF_DOXYGEN
 @endcond TURN_OFF_DOXYGEN
 -->
 -->
@@ -182,3 +184,67 @@ entt::monostate<"mykey"_hs>{} = 42;
 const bool b = entt::monostate<"mykey"_hs>{};
 const bool b = entt::monostate<"mykey"_hs>{};
 const int i = entt::monostate<entt::hashed_string{"mykey"}>{};
 const int i = entt::monostate<entt::hashed_string{"mykey"}>{};
 ```
 ```
+
+# Type info
+
+The `type_info` class template is meant to provide some basic information about
+types of all kinds.<br/>
+Currently, the only information available is the numeric identifier associated
+with a given type:
+
+```cpp
+auto id = entt::type_info<my_type>::id();
+```
+
+In general, the `id` function is also `constexpr` but this isn't guaranteed for
+all compilers and platforms (although it's valid with the most well-known and
+popular compilers).<br/>
+This function **can** use non-standard features of the language for its own
+purposes. This allows it to provide compile-time identifiers that remain stable
+between different runs. However, it's possible to force the use of standard
+features only by defining the macro `ENTT_STANDARD_CPP`. In this case, there is
+no guarantee that the identifiers are stable across executions though. Moreover,
+identifiers are generated at runtime and are no longer a compile-time thing.
+
+An external type system can also be used if needed. In fact, `type_info` can be
+specialized by type and is also _sfinae-friendly_ in order to allow more refined
+specializations such as:
+
+```cpp
+template<typename Type>
+struct entt::type_info<Type, std::void_d<decltype(Type::custom_id())>> {
+    static constexpr ENTT_ID_TYPE id() ENTT_NOEXCEPT {
+        return Type::custom_id();
+    }
+};
+```
+
+Note that this class template and its specializations are widely used within
+`EnTT`. It also plays a very important role in making `EnTT` work transparently
+across boundaries.<br/>
+Please refer to the dedicated section for more details.
+
+## Conflicts
+
+Since the default, non-standard implementation makes use of hashed strings, it
+may happen that two types are assigned the same numeric identifier.<br/>
+In fact, although this is quite rare, it's not entirely excluded.
+
+In this case, there are many ways to deal with it:
+
+* The most trivial one is to define the `ENTT_STANDARD_CPP` macro. Note that
+  runtime identifiers don't suffer from this problem. However, this solution
+  doesn't work well with a plugin system, where the libraries aren't linked.
+
+* Another possibility is to specialize the `type_info` class for one of the
+  conflicting types, in order to assign it a custom identifier. This is probably
+  the easiest solution that also preserves the feature of the tool.
+
+* A fully customized identifier generation policy (based for example on enum
+  classes or preprocessing steps) may represent yet another option.
+
+These are just some examples of possible approaches to the problem but there are
+many others. As already mentioned above, since users have full control over
+their types, this problem is in any case easy to solve and should not worry too
+much.<br/>
+In all likelihood, it will never happen to run into a conflict anyway.

+ 68 - 156
docs/md/lib.md

@@ -6,12 +6,9 @@
 # Table of Contents
 # Table of Contents
 
 
 * [Introduction](#introduction)
 * [Introduction](#introduction)
-* [Named types and traits class](#named-types-and-traits-class)
-  * [Do not mix types](#do-not-mix-types)
-* [Macros, macros everywhere](#macros-macros-everywhere)
-  * [Conflicts](#conflicts)
-* [Runtime reflection system](#runtime-reflection-system)
-* [Allocations: the dark side of the force](#allocations-the-dark-side-of-the-force)
+* [The EnTT way](#the-entt-way)
+* [Meta context](#meta-context)
+* [Memory management](#memory-management)
 <!--
 <!--
 @endcond TURN_OFF_DOXYGEN
 @endcond TURN_OFF_DOXYGEN
 -->
 -->
@@ -19,178 +16,93 @@
 # Introduction
 # Introduction
 
 
 `EnTT` has historically had a limit when used across boundaries on Windows in
 `EnTT` has historically had a limit when used across boundaries on Windows in
-general and on GNU/Linux when default visibility was set to _hidden_. The
-limitation is due mainly to a custom utility used to assign unique, sequential
-identifiers to different types. Unfortunately, this tool is used by several core
-classes (the `registry` among the others) that are thus almost unusable across
-boundaries.<br/>
-The reasons for that are beyond the purposes of this document. However, the good
-news is that `EnTT` also offers now a way to overcome this limit and to push
-things across boundaries without problems when needed.
-
-# Named types and traits class
-
-To allow a type to work properly across boundaries when used by a class that
-requires to assign unique identifiers to types, users must specialize a class
-template to literally give a compile-time name to the type itself.<br/>
-The name of the class template is `name_type_traits` and the specialization must
-be such that it exposes a static constexpr data member named `value` having type
-either `ENTT_ID_TYPE` or `entt::hashed_string::hash_type`. Its value is the user
-defined unique identifier assigned to the specific type.<br/>
-Identifiers are not to be sequentially generated in this case.
-
-As an example:
+general and on GNU/Linux when default visibility was set to hidden. The
+limitation was mainly due to a custom utility used to assign unique, sequential
+identifiers to different types.<br/>
+Fortunately, nowadays using `EnTT` across boundaries is straightforward. In
+fact, everything just works transparently in almost all cases. There are only a
+few exceptions, easy to deal with anyway.
 
 
-```cpp
-struct my_type { /* ... */ };
-
-template<>
-struct entt::named_type_traits<my_type> {
-    static constexpr auto value = "my_type"_hs;
-};
-```
-
-Because of the rules of the language, the specialization must reside in the
-global namespace or in the `entt` namespace. There is no way to change this rule
-unfortunately, because it doesn't depend on the library itself.
-
-The good aspect of this approach is that it's not intrusive at all. The other
-way around was in fact forcing users to inherit all their classes from a common
-base. Something to avoid, at least from my point of view.<br/>
-However, despite the fact that it's not intrusive, it would be great if it was
-also easier to use and a bit less error-prone. This is why a bunch of macros
-exist to ease defining named types.
-
-## Do not mix types
-
-Someone might think that this trick is valid only for the types to push across
-boundaries. This isn't how things work. In fact, the problem is more complex
-than that.<br/>
-As a rule of thumb, users should never mix named and non-named types. Whenever
-a type is given a name, all the types must be given a name. As an example,
-consider the `registry` class template: in case it is pushed across boundaries,
-all the types of components should be assigned a name to avoid subtle bugs.
-
-Indeed, this constraint can be relaxed in many cases. However, it is difficult
-to define a general rule to follow that is not the most stringent, unless users
-know exactly what they are doing. Therefore, I won't elaborate on giving further
-details on the topic.
-
-# Macros, macros everywhere
-
-The library comes with a set of predefined macros to use to declare named types
-or export already existing ones. In particular:
-
-* `ENTT_NAMED_TYPE` can be used to assign a name to already existing types. This
-  macro must be used in the global namespace even when the types to be named are
-  not.
-
-  ```cpp
-  ENTT_NAMED_TYPE(my_type)
-  ENTT_NAMED_TYPE(ns::another_type)
-  ```
-
-* `ENTT_NAMED_STRUCT` can be used to define and export a struct at the same
-  time. It accepts also an optional namespace in which to define the given type.
-  This macro must be used in the global namespace.
-
-  ```cpp
-  ENTT_NAMED_STRUCT(my_type, { /* struct definition */})
-  ENTT_NAMED_STRUCT(ns, another_type, { /* struct definition */})
-  ```
-
-* `ENTT_NAMED_CLASS` can be used to define and export a class at the same
-  time. It accepts also an optional namespace in which to define the given type.
-  This macro must be used in the global namespace.
-
-  ```cpp
-  ENTT_NAMED_CLASS(my_type, { /* class definition */})
-  ENTT_NAMED_CLASS(ns, another_type, { /* class definition */})
-  ```
-
-Nested namespaces are supported out of the box as well in all cases. As an
-example:
-
-```cpp
-ENTT_NAMED_STRUCT(nested::ns, my_type, { /* struct definition */})
-```
-
-These macros can be used to avoid specializing the `named_type_traits` class
-template. In all cases, the name of the class is used also as a seed to generate
-the compile-time unique identifier.
+# The EnTT way
 
 
-## Conflicts
+Many classes in `EnTT` make extensive use of type erasure for their purposes.
+This isn't a problem in itself (in fact, it's the basis of an API so convenient
+to use). However, a way is needed to recognize the objects whose type has been
+erased on the other side of a boundary.<br/>
+The `type_info` class template is how identifiers are generated and thus made
+available to the rest of the library.
 
 
-When using macros, unique identifiers are 32/64 bit integers generated by
-hashing strings during compilation. Therefore, conflicts are rare but still
-possible. In case of conflicts, everything simply will get broken at runtime and
-the strangest things will probably take place.<br/>
-Unfortunately, there is no safe way to prevent it. If this happens, it will be
-enough to give a different value to one of the conflicting types to solve the
-problem. To do this, users can either assign a different name to the class or
-directly define a specialization for the `named_type_traits` class template.
+The only case in which this may arouse some interest is in case of conflicts
+between identifiers (definitely uncommon though) or where the default solution
+proposed by `EnTT` is not suitable for the user's purposes.<br/>
+Please refer to the dedicated section for more details.
 
 
-# Runtime reflection system
+# Meta context
 
 
 The runtime reflection system deserves a special mention when it comes to using
 The runtime reflection system deserves a special mention when it comes to using
 it across boundaries.<br/>
 it across boundaries.<br/>
-As in all other cases, it's necessary to give a name to the types. However, this
-time this isn't enough to get the job done.
-
-The runtime reflection system is linked to a static context to which the visible
-components are linked. A component is visible when it's given a name, so the
-named components are implicitly visible.<br/>
-Different contexts don't relate to each other. Therefore, to allow the use of
-named types across boundaries, a context must also be shared.
+Since it's linked to a static context to which the visible components are
+attached and different contexts don't relate to each other, they must be
+_shared_ to allow the use of meta types across boundaries.
 
 
-Sharing a context is straightforward. First of all, the local one must be
-acquired:
+Sharing a context is trivial though. First of all, the local one must be
+acquired in the main space:
 
 
-```
+```cpp
 entt::meta_ctx ctx{};
 entt::meta_ctx ctx{};
 ```
 ```
 
 
-Then, it must be pushed across boundaries and the receiving space must set it as
-its global context, thus releasing the local one that remains available but is
-no longer referred to by the runtime reflection system:
+Then, it must passed to the receiving space that will set it as its global
+context, thus releasing the local one that remains available but is no longer
+referred to by the runtime reflection system:
 
 
-```
+```cpp
 entt::meta_ctx::bind(ctx);
 entt::meta_ctx::bind(ctx);
 ```
 ```
 
 
 From now on, both spaces will refer to the same context and on it will be
 From now on, both spaces will refer to the same context and on it will be
-associated the new visible meta types, no matter _where_ they are created.
-
+attached the new visible meta types, no matter where they are created.<br/>
 A context can also be reset and then associated again locally as:
 A context can also be reset and then associated again locally as:
 
 
-```
+```cpp
 entt::meta_ctx::bind{entt::meta_ctx{});
 entt::meta_ctx::bind{entt::meta_ctx{});
 ```
 ```
 
 
 This is allowed because local and global contexts are separated. Therefore, it's
 This is allowed because local and global contexts are separated. Therefore, it's
 always possible to make the local context the current one again.
 always possible to make the local context the current one again.
 
 
-# Allocations: the dark side of the force
-
-As long as `EnTT` won't support custom allocators, another problem with
-allocations will remain alive instead. This is in fact easily solved, or at
-least it is if one knows it.
-
-To allow users to add types dynamically, the library makes extensive use of type
-erasure techniques and dynamic allocations for pools (whether they are for
-components, events or anything else). The problem occurs when, for example, a
-registry is created on one side of a boundary and a pool is dynamically created
-on the other side. In the best case, everything will crash at the exit, while at
-worst it will do so at runtime.<br/>
-To avoid problems, the pools must be generated from the same side of the
-boundary where the object that owns them is also created. As an example, when
-the registry is created in the main executable and used across boundaries for a
-given type of component, the pool for that type must be created before passing
-around the registry itself. To do this is fortunately quite easy, since it is
-sufficient to invoke any of the methods that involve the given type (continuing
-the example with the registry, a call to `reserve` or `size` is more than
-enough).
-
-Maybe one day some dedicated methods will be added that do nothing but create a
-pool for a given type. Until now it has been preferred to keep the API cleaner
-as they are not strictly necessary.
+Before to release a context, all locally registered types should be reset to
+avoid dangling references. Otherwise, if a type is accessed from another space
+by name, there could be an attempt to address its parts that are no longer
+available.
+
+# Memory Management
+
+There is another subtle problem due to memory management that can lead to
+headaches.<br/>
+This can occur where there are pools of objects (such as components or events)
+dynamically created on demand.
+
+As an example, imagine creating an instance of `registry` in the main executable
+and share it with a plugin. If the latter starts working with a component that
+is unknown to the former, a dedicated pool is created within the registry on
+first use.<br/>
+As one can guess, this pool is instantiated on a different side of the boundary
+from the `registry`. Therefore, the instance is now managing memory from
+different spaces and this can quickly lead to crashes if not properly addressed.
+
+Fortunately, all classes that could potentially suffer from this problem offer
+also a `discard` member function to get rid of these pools:
+
+```cpp
+registry.discard<local_type>();
+```
+
+This is all there is to do to get around this. Again, `discard` is only to be
+invoked if it's certain that the container and pools are instantiated on
+different sides of the boundary.
+
+If in doubts or to avoid risks, simply invoke the `prepare` member function or
+any of the other functions that refer to the desired type to force the
+generation of the pools that are used on both sides of the boundary.<br/>
+This is something to be done usually in the main context when needed.