🛠️🐜 Antkeeper superbuild with dependencies included https://antkeeper.com
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

155 lines
6.6 KiB

  1. # Push EnTT across boundaries
  2. <!--
  3. @cond TURN_OFF_DOXYGEN
  4. -->
  5. # Table of Contents
  6. * [Introduction](#introduction)
  7. * [Named types and traits class](#named-types-and-traits-class)
  8. * [Do not mix types](#do-not-mix-types)
  9. * [Macros, macros everywhere](#macros-macros-everywhere)
  10. * [Conflicts](#conflicts)
  11. * [Allocations: the dark side of the force](#allocations-the-dark-side-of-the-force)
  12. <!--
  13. @endcond TURN_OFF_DOXYGEN
  14. -->
  15. # Introduction
  16. `EnTT` has historically had a limit when used across boundaries on Windows in
  17. general and on GNU/Linux when default visibility was set to _hidden_. The
  18. limitation is due mainly to a custom utility used to assign unique, sequential
  19. identifiers to different types. Unfortunately, this tool is used by several core
  20. classes (the `registry` among the others) that are thus almost unusable across
  21. boundaries.<br/>
  22. The reasons for that are beyond the purposes of this document. However, the good
  23. news is that `EnTT` also offers now a way to overcome this limit and to push
  24. things across boundaries without problems when needed.
  25. # Named types and traits class
  26. To allow a type to work properly across boundaries when used by a class that
  27. requires to assign unique identifiers to types, users must specialize a class
  28. template to literally give a compile-time name to the type itself.<br/>
  29. The name of the class template is `name_type_traits` and the specialization must
  30. be such that it exposes a static constexpr data member named `value` having type
  31. either `ENTT_ID_TYPE` or `entt::hashed_string::hash_type`. Its value is the user
  32. defined unique identifier assigned to the specific type.<br/>
  33. Identifiers are not to be sequentially generated in this case.
  34. As an example:
  35. ```cpp
  36. struct my_type { /* ... */ };
  37. template<>
  38. struct entt::named_type_traits<my_type> {
  39. static constexpr auto value = "my_type"_hs;
  40. };
  41. ```
  42. Because of the rules of the language, the specialization must reside in the
  43. global namespace or in the `entt` namespace. There is no way to change this rule
  44. unfortunately, because it doesn't depend on the library itself.
  45. The good aspect of this approach is that it's not intrusive at all. The other
  46. way around was in fact forcing users to inherit all their classes from a common
  47. base. Something to avoid, at least from my point of view.<br/>
  48. However, despite the fact that it's not intrusive, it would be great if it was
  49. also easier to use and a bit less error-prone. This is why a bunch of macros
  50. exist to ease defining named types.
  51. ## Do not mix types
  52. Someone might think that this trick is valid only for the types to push across
  53. boundaries. This isn't how things work. In fact, the problem is more complex
  54. than that.<br/>
  55. As a rule of thumb, users should never mix named and non-named types. Whenever
  56. a type is given a name, all the types must be given a name. As an example,
  57. consider the `registry` class template: in case it is pushed across boundaries,
  58. all the types of components should be assigned a name to avoid subtle bugs.
  59. Indeed, this constraint can be relaxed in many cases. However, it is difficult
  60. to define a general rule to follow that is not the most stringent, unless users
  61. know exactly what they are doing. Therefore, I won't elaborate on giving further
  62. details on the topic.
  63. # Macros, macros everywhere
  64. The library comes with a set of predefined macros to use to declare named types
  65. or export already existing ones. In particular:
  66. * `ENTT_NAMED_TYPE` can be used to assign a name to already existing types. This
  67. macro must be used in the global namespace even when the types to be named are
  68. not.
  69. ```cpp
  70. ENTT_NAMED_TYPE(my_type)
  71. ENTT_NAMED_TYPE(ns::another_type)
  72. ```
  73. * `ENTT_NAMED_STRUCT` can be used to define and export a struct at the same
  74. time. It accepts also an optional namespace in which to define the given type.
  75. This macro must be used in the global namespace.
  76. ```cpp
  77. ENTT_NAMED_STRUCT(my_type, { /* struct definition */})
  78. ENTT_NAMED_STRUCT(ns, another_type, { /* struct definition */})
  79. ```
  80. * `ENTT_NAMED_CLASS` can be used to define and export a class at the same
  81. time. It accepts also an optional namespace in which to define the given type.
  82. This macro must be used in the global namespace.
  83. ```cpp
  84. ENTT_NAMED_CLASS(my_type, { /* class definition */})
  85. ENTT_NAMED_CLASS(ns, another_type, { /* class definition */})
  86. ```
  87. Nested namespaces are supported out of the box as well in all cases. As an
  88. example:
  89. ```cpp
  90. ENTT_NAMED_STRUCT(nested::ns, my_type, { /* struct definition */})
  91. ```
  92. These macros can be used to avoid specializing the `named_type_traits` class
  93. template. In all cases, the name of the class is used also as a seed to generate
  94. the compile-time unique identifier.
  95. ## Conflicts
  96. When using macros, unique identifiers are 32/64 bit integers generated by
  97. hashing strings during compilation. Therefore, conflicts are rare but still
  98. possible. In case of conflicts, everything simply will get broken at runtime and
  99. the strangest things will probably take place.<br/>
  100. Unfortunately, there is no safe way to prevent it. If this happens, it will be
  101. enough to give a different value to one of the conflicting types to solve the
  102. problem. To do this, users can either assign a different name to the class or
  103. directly define a specialization for the `named_type_traits` class template.
  104. # Allocations: the dark side of the force
  105. As long as `EnTT` won't support custom allocators, another problem with
  106. allocations will remain alive instead. This is in fact easily solved, or at
  107. least it is if one knows it.
  108. To allow users to add types dynamically, the library makes extensive use of type
  109. erasure techniques and dynamic allocations for pools (whether they are for
  110. components, events or anything else). The problem occurs when, for example, a
  111. registry is created on one side of a boundary and a pool is dynamically created
  112. on the other side. In the best case, everything will crash at the exit, while at
  113. worst it will do so at runtime.<br/>
  114. To avoid problems, the pools must be generated from the same side of the
  115. boundary where the object that owns them is also created. As an example, when
  116. the registry is created in the main executable and used across boundaries for a
  117. given type of component, the pool for that type must be created before passing
  118. around the registry itself. To do this is fortunately quite easy, since it is
  119. sufficient to invoke any of the methods that involve the given type (continuing
  120. the example with the registry, a call to `reserve` or `size` is more than
  121. enough).
  122. Maybe one day some dedicated methods will be added that do nothing but create a
  123. pool for a given type. Until now it has been preferred to keep the API cleaner
  124. as they are not strictly necessary.