updated doc

This commit is contained in:
Michele Caini
2019-02-23 23:40:25 +01:00
parent 293985ec10
commit ad9ec22fd1
4 changed files with 142 additions and 40 deletions

2
TODO
View File

@@ -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

View File

@@ -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
)

141
docs/lib.md Normal file
View File

@@ -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.

View File

@@ -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.<br/>
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<struct internal_registry_component_family>;
template class __attribute__((visibility("default"))) family<struct internal_registry_handler_family>;
}
```
* On Windows:
```cpp
namespace entt {
template class __declspec(dllexport) family<struct internal_registry_component_family>;
template class __declspec(dllexport) family<struct internal_registry_handler_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.