From ad9ec22fd17de69a3ec9a2be0074dbccb89a1185 Mon Sep 17 00:00:00 2001 From: Michele Caini Date: Sat, 23 Feb 2019 23:40:25 +0100 Subject: [PATCH] updated doc --- TODO | 2 - docs/CMakeLists.txt | 2 +- docs/lib.md | 141 ++++++++++++++++++++++++++++++++++++++++++++ docs/shared.md | 37 ------------ 4 files changed, 142 insertions(+), 40 deletions(-) create mode 100644 docs/lib.md delete mode 100644 docs/shared.md diff --git a/TODO b/TODO index 3774c9b43..7c09ac633 100644 --- a/TODO +++ b/TODO @@ -23,8 +23,6 @@ * cleanup - see https://github.com/skypjack/entt/commit/ad5cedc08c83e8cbcc8aaeac9634d44624ffe35a#commitcomment-32380903 ==> can we do more for shared libraries? who knows... see #144 -* get rid of get_pool_data (welcome structured bindings) * to be updated: dispatcher -* to be updated: registry * to be updated: emitter * to be updated: doc diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt index 87eb597f3..2dde62a93 100644 --- a/docs/CMakeLists.txt +++ b/docs/CMakeLists.txt @@ -27,12 +27,12 @@ add_custom_target( CONTRIBUTING.md core.md entity.md + lib.md links.md locator.md meta.md process.md resource.md - shared.md signal.md extra.dox ) diff --git a/docs/lib.md b/docs/lib.md new file mode 100644 index 000000000..0b2fd32a3 --- /dev/null +++ b/docs/lib.md @@ -0,0 +1,141 @@ +# Push EnTT across boundaries + + +# 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) + + +# 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.
+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.
+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.
+Identifiers are not to be sequentially generated in this case. + +As an example: + +```cpp +struct my_type { /* ... */ }; + +template<> +struct entt::shared_traits { + 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.
+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.
+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.
+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.
+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. diff --git a/docs/shared.md b/docs/shared.md deleted file mode 100644 index 636e9ef36..000000000 --- a/docs/shared.md +++ /dev/null @@ -1,37 +0,0 @@ -### EnTT and shared libraries - -To make sure that an application and a shared library that use both `EnTT` can -interact correctly when symbols are hidden by default, there are some tricks to -follow.
-In particular and in order to avoid undefined behaviors, all the instantiation -of the `family` class template shall be made explicit along with the system-wide -specifier to use to export them. - -At the time I'm writing this document, the classes that use internally the above -mentioned class template are `dispatcher`, `emitter` and `registry`. Therefore -and as an example, if you use the `registry` class template in your shared -library and want to set symbols visibility to _hidden_ by default, the following -lines are required to allow it to function properly with a client that also uses -the `registry` somehow: - -* On GNU/Linux: - - ```cpp - namespace entt { - template class __attribute__((visibility("default"))) family; - template class __attribute__((visibility("default"))) family; - } - ``` - -* On Windows: - - ```cpp - namespace entt { - template class __declspec(dllexport) family; - template class __declspec(dllexport) family; - } - ``` - -Otherwise, the risk is that type identifiers are different between the shared -library and the application and this will prevent the whole thing from -functioning correctly for obvious reasons.