|
|
@@ -0,0 +1,141 @@
|
|
|
+# Push EnTT across boundaries
|
|
|
+
|
|
|
+<!--
|
|
|
+@cond TURN_OFF_DOXYGEN
|
|
|
+-->
|
|
|
+# Table of Contents
|
|
|
+
|
|
|
+* [Introduction](#introduction)
|
|
|
+* [Shared types and traits class](#shared-types-and-traits-class)
|
|
|
+ * [Do not mix types](#do-not-mix-types)
|
|
|
+* [Macros, macros everywhere](#macros-macros-everywhere)
|
|
|
+ * [Conflicts](#conflict)
|
|
|
+* [Note](#note)
|
|
|
+<!--
|
|
|
+@endcond TURN_OFF_DOXYGEN
|
|
|
+-->
|
|
|
+
|
|
|
+# Introduction
|
|
|
+
|
|
|
+`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.
|
|
|
+
|
|
|
+# Shared 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 `shared_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:
|
|
|
+
|
|
|
+```cpp
|
|
|
+struct my_type { /* ... */ };
|
|
|
+
|
|
|
+template<>
|
|
|
+struct entt::shared_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 shared 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 shared and non-shared types. Whenever
|
|
|
+a type is made shareable, all the types should be made shareable. As an example,
|
|
|
+consider the `registry` class template. In case it is pushed across boundaries,
|
|
|
+all the types of components should be shareable 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 shared types
|
|
|
+or export already existing ones. In particular:
|
|
|
+
|
|
|
+* `ENTT_SHARED_TYPE` can be used to make shareable already existing types. This
|
|
|
+ macro must be used in the global namespace even when types to make shareable
|
|
|
+ are not.
|
|
|
+
|
|
|
+ ```cpp
|
|
|
+ ENTT_SHARED_TYPE(my_type)
|
|
|
+ ENTT_SHARED_TYPE(ns::another_type)
|
|
|
+ ```
|
|
|
+
|
|
|
+* `ENTT_SHARED_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_SHARED_STRUCT(my_type, { /* struct definition */})
|
|
|
+ ENTT_SHARED_STRUCT(ns, another_type, { /* struct definition */})
|
|
|
+ ```
|
|
|
+
|
|
|
+* `ENTT_SHARED_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_SHARED_CLASS(my_type, { /* class definition */})
|
|
|
+ ENTT_SHARED_CLASS(ns, another_type, { /* class definition */})
|
|
|
+ ```
|
|
|
+
|
|
|
+Nested namespaces are supported out of the box as well in all cases. As an
|
|
|
+example:
|
|
|
+
|
|
|
+```cpp
|
|
|
+ENTT_SHARED_STRUCT(nested::ns, my_type, { /* struct definition */})
|
|
|
+```
|
|
|
+
|
|
|
+These macros can be used to avoid specializing the `shared_traits` class
|
|
|
+template. In all cases, the name of the class is used also as a seed to generate
|
|
|
+the compile-time unique identifier.
|
|
|
+
|
|
|
+## Conflicts
|
|
|
+
|
|
|
+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 name to one of the conflicting types to solve the
|
|
|
+problem. To do this, users can either assign a different name to the class or
|
|
|
+define directly a specialization for the `shared_traits` class template.
|
|
|
+
|
|
|
+# Note
|
|
|
+
|
|
|
+I'm still working hard to make everything work across boundaries.<br/>
|
|
|
+The classes affected by the problem were `registry`, `dispatcher` and `emitter`.
|
|
|
+Currently, only the `registry` class fully support _shared types_. Using
|
|
|
+`dispatcher` and `emitter` across boundaries isn't allowed yet and can result in
|
|
|
+unexpected behavior on Windows in general and on GNU/Linux when default
|
|
|
+visibility is set to _hidden_.
|
|
|
+
|
|
|
+Stay tuned for future updates.
|