data_preferences.rst 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. :article_outdated: True
  2. .. _doc_data_preferences:
  3. Data preferences
  4. ================
  5. Ever wondered whether one should approach problem X with data structure
  6. Y or Z? This article covers a variety of topics related to these dilemmas.
  7. .. note::
  8. This article makes references to "[something]-time" operations. This
  9. terminology comes from algorithm analysis'
  10. `Big O Notation <https://rob-bell.net/2009/06/a-beginners-guide-to-big-o-notation/>`_.
  11. Long-story short, it describes the worst-case scenario of runtime length.
  12. In laymen's terms:
  13. "As the size of a problem domain increases, the runtime length of the
  14. algorithm..."
  15. - Constant-time, ``O(1)``: "...does not increase."
  16. - Logarithmic-time, ``O(log n)``: "...increases at a slow rate."
  17. - Linear-time, ``O(n)``: "...increases at the same rate."
  18. - Etc.
  19. Imagine if one had to process 3 million data points within a single frame. It
  20. would be impossible to craft the feature with a linear-time algorithm since
  21. the sheer size of the data would increase the runtime far beyond the time allotted.
  22. In comparison, using a constant-time algorithm could handle the operation without
  23. issue.
  24. By and large, developers want to avoid engaging in linear-time operations as
  25. much as possible. But, if one keeps the scale of a linear-time operation
  26. small, and if one does not need to perform the operation often, then it may
  27. be acceptable. Balancing these requirements and choosing the right
  28. algorithm / data structure for the job is part of what makes programmers'
  29. skills valuable.
  30. Array vs. Dictionary vs. Object
  31. -------------------------------
  32. Godot stores all variables in the scripting API in the
  33. :ref:`Variant <doc_variant_class>` class.
  34. Variants can store Variant-compatible data structures such as
  35. :ref:`Array <class_Array>` and :ref:`Dictionary <class_Dictionary>` as well
  36. as :ref:`Objects <class_Object>`.
  37. Godot implements Array as a ``Vector<Variant>``. The engine stores the Array
  38. contents in a contiguous section of memory, i.e. they are in a row adjacent
  39. to each other.
  40. .. note::
  41. For those unfamiliar with C++, a Vector is the name of the
  42. array object in traditional C++ libraries. It is a "templated"
  43. type, meaning that its records can only contain a particular type (denoted
  44. by angled brackets). So, for example, a
  45. :ref:`PackedStringArray <class_PackedStringArray>` would be something like
  46. a ``Vector<String>``.
  47. Contiguous memory stores imply the following operation performance:
  48. - **Iterate:** Fastest. Great for loops.
  49. - Op: All it does is increment a counter to get to the next record.
  50. - **Insert, Erase, Move:** Position-dependent. Generally slow.
  51. - Op: Adding/removing/moving content involves moving the adjacent records
  52. over (to make room / fill space).
  53. - Fast add/remove *from the end*.
  54. - Slow add/remove *from an arbitrary position*.
  55. - Slowest add/remove *from the front*.
  56. - If doing many inserts/removals *from the front*, then...
  57. 1. invert the array.
  58. 2. do a loop which executes the Array changes *at the end*.
  59. 3. re-invert the array.
  60. This makes only 2 copies of the array (still constant time, but slow)
  61. versus copying roughly 1/2 of the array, on average, N times (linear time).
  62. - **Get, Set:** Fastest *by position*. E.g. can request 0th, 2nd, 10th record, etc.
  63. but cannot specify which record you want.
  64. - Op: 1 addition operation from array start position up to desired index.
  65. - **Find:** Slowest. Identifies the index/position of a value.
  66. - Op: Must iterate through array and compare values until one finds a match.
  67. - Performance is also dependent on whether one needs an exhaustive
  68. search.
  69. - If kept ordered, custom search operations can bring it to logarithmic
  70. time (relatively fast). Laymen users won't be comfortable with this
  71. though. Done by re-sorting the Array after every edit and writing an
  72. ordered-aware search algorithm.
  73. Godot implements Dictionary as an ``OrderedHashMap<Variant, Variant>``. The engine
  74. stores a small array (initialized to 2^3 or 8 records) of key-value pairs. When
  75. one attempts to access a value, they provide it a key. It then *hashes* the
  76. key, i.e. converts it into a number. The "hash" is used to calculate the index
  77. into the array. As an array, the OHM then has a quick lookup within the "table"
  78. of keys mapped to values. When the HashMap becomes too full, it increases to
  79. the next power of 2 (so, 16 records, then 32, etc.) and rebuilds the structure.
  80. Hashes are to reduce the chance of a key collision. If one occurs, the table
  81. must recalculate another index for the value that takes the previous position
  82. into account. In all, this results in constant-time access to all records at
  83. the expense of memory and some minor operational efficiency.
  84. 1. Hashing every key an arbitrary number of times.
  85. - Hash operations are constant-time, so even if an algorithm must do more
  86. than one, as long as the number of hash calculations doesn't become
  87. too dependent on the density of the table, things will stay fast.
  88. Which leads to...
  89. 2. Maintaining an ever-growing size for the table.
  90. - HashMaps maintain gaps of unused memory interspersed in the table
  91. on purpose to reduce hash collisions and maintain the speed of
  92. accesses. This is why it constantly increases in size exponentially by
  93. powers of 2.
  94. As one might be able to tell, Dictionaries specialize in tasks that Arrays
  95. do not. An overview of their operational details is as follows:
  96. - **Iterate:** Fast.
  97. - Op: Iterate over the map's internal vector of hashes. Return each key.
  98. Afterwards, users then use the key to jump to and return the desired
  99. value.
  100. - **Insert, Erase, Move:** Fastest.
  101. - Op: Hash the given key. Do 1 addition operation to look up the
  102. appropriate value (array start + offset). Move is two of these
  103. (one insert, one erase). The map must do some maintenance to preserve
  104. its capabilities:
  105. - update ordered List of records.
  106. - determine if table density mandates a need to expand table capacity.
  107. - The Dictionary remembers in what
  108. order users inserted its keys. This enables it to execute reliable iterations.
  109. - **Get, Set:** Fastest. Same as a lookup *by key*.
  110. - Op: Same as insert/erase/move.
  111. - **Find:** Slowest. Identifies the key of a value.
  112. - Op: Must iterate through records and compare the value until a match is
  113. found.
  114. - Note that Godot does not provide this feature out-of-the-box (because
  115. they aren't meant for this task).
  116. Godot implements Objects as stupid, but dynamic containers of data content.
  117. Objects query data sources when posed questions. For example, to answer
  118. the question, "do you have a property called, 'position'?", it might ask
  119. its :ref:`script <class_Script>` or the :ref:`ClassDB <class_ClassDB>`.
  120. One can find more information about what objects are and how they work in
  121. the :ref:`doc_what_are_godot_classes` article.
  122. The important detail here is the complexity of the Object's task. Every time
  123. it performs one of these multi-source queries, it runs through *several*
  124. iteration loops and HashMap lookups. What's more, the queries are linear-time
  125. operations dependent on the Object's inheritance hierarchy size. If the class
  126. the Object queries (its current class) doesn't find anything, the request
  127. defers to the next base class, all the way up until the original Object class.
  128. While these are each fast operations in isolation, the fact that it must make
  129. so many checks is what makes them slower than both of the alternatives for
  130. looking up data.
  131. .. note::
  132. When developers mention how slow the scripting API is, it is this chain
  133. of queries they refer to. Compared to compiled C++ code where the
  134. application knows exactly where to go to find anything, it is inevitable
  135. that scripting API operations will take much longer. They must locate the
  136. source of any relevant data before they can attempt to access it.
  137. The reason GDScript is slow is because every operation it performs passes
  138. through this system.
  139. C# can process some content at higher speeds via more optimized bytecode.
  140. But, if the C# script calls into an engine class'
  141. content or if the script tries to access something external to it, it will
  142. go through this pipeline.
  143. NativeScript C++ goes even further and keeps everything internal by default.
  144. Calls into external structures will go through the scripting API. In
  145. NativeScript C++, registering methods to expose them to the scripting API is
  146. a manual task. It is at this point that external, non-C++ classes will use
  147. the API to locate them.
  148. So, assuming one extends from Reference to create a data structure, like
  149. an Array or Dictionary, why choose an Object over the other two options?
  150. 1. **Control:** With objects comes the ability to create more sophisticated
  151. structures. One can layer abstractions over the data to ensure the external
  152. API doesn't change in response to internal data structure changes. What's
  153. more, Objects can have signals, allowing for reactive behavior.
  154. 2. **Clarity:** Objects are a reliable data source when it comes to the data
  155. that scripts and engine classes define for them. Properties may not hold the
  156. values one expects, but one doesn't need to worry about whether the property
  157. exists in the first place.
  158. 3. **Convenience:** If one already has a similar data structure in mind, then
  159. extending from an existing class makes the task of building the data
  160. structure much easier. In comparison, Arrays and Dictionaries don't
  161. fulfill all use cases one might have.
  162. Objects also give users the opportunity to create even more specialized data
  163. structures. With it, one can design their own List, Binary Search Tree, Heap,
  164. Splay Tree, Graph, Disjoint Set, and any host of other options.
  165. "Why not use Node for tree structures?" one might ask. Well, the Node
  166. class contains things that won't be relevant to one's custom data structure.
  167. As such, it can be helpful to construct one's own node type when building
  168. tree structures.
  169. .. tabs::
  170. .. code-tab:: gdscript GDScript
  171. extends Object
  172. class_name TreeNode
  173. var _parent: TreeNode = null
  174. var _children := []
  175. func _notification(p_what):
  176. match p_what:
  177. NOTIFICATION_PREDELETE:
  178. # Destructor.
  179. for a_child in _children:
  180. a_child.free()
  181. .. code-tab:: csharp
  182. using Godot;
  183. using System.Collections.Generic;
  184. // Can decide whether to expose getters/setters for properties later
  185. public partial class TreeNode : GodotObject
  186. {
  187. private TreeNode _parent = null;
  188. private List<TreeNode> _children = new();
  189. public override void _Notification(int what)
  190. {
  191. switch (what)
  192. {
  193. case NotificationPredelete:
  194. foreach (TreeNode child in _children)
  195. {
  196. node.Free();
  197. }
  198. break;
  199. }
  200. }
  201. }
  202. From here, one can then create their own structures with specific features,
  203. limited only by their imagination.
  204. Enumerations: int vs. string
  205. ----------------------------
  206. Most languages offer an enumeration type option. GDScript is no different, but
  207. unlike most other languages, it allows one to use either integers or strings for
  208. the enum values (the latter only when using the ``@export_enum`` annotation in GDScript).
  209. The question then arises, "which should one use?"
  210. The short answer is, "whichever you are more comfortable with." This
  211. is a feature specific to GDScript and not Godot scripting in general;
  212. The languages prioritizes usability over performance.
  213. On a technical level, integer comparisons (constant-time) will happen
  214. faster than string comparisons (linear-time). If one wants to keep
  215. up other languages' conventions though, then one should use integers.
  216. The primary issue with using integers comes up when one wants to *print*
  217. an enum value. As integers, attempting to print ``MY_ENUM`` will print
  218. ``5`` or what-have-you, rather than something like ``"MyEnum"``. To
  219. print an integer enum, one would have to write a Dictionary that maps the
  220. corresponding string value for each enum.
  221. If the primary purpose of using an enum is for printing values and one wishes
  222. to group them together as related concepts, then it makes sense to use them as
  223. strings. That way, a separate data structure to execute on the printing is
  224. unnecessary.
  225. AnimatedTexture vs. AnimatedSprite2D vs. AnimationPlayer vs. AnimationTree
  226. --------------------------------------------------------------------------
  227. Under what circumstances should one use each of Godot's animation classes?
  228. The answer may not be immediately clear to new Godot users.
  229. :ref:`AnimatedTexture <class_AnimatedTexture>` is a texture that
  230. the engine draws as an animated loop rather than a static image.
  231. Users can manipulate...
  232. 1. the rate at which it moves across each section of the texture (FPS).
  233. 2. the number of regions contained within the texture (frames).
  234. Godot's :ref:`RenderingServer <class_RenderingServer>` then draws
  235. the regions in sequence at the prescribed rate. The good news is that this
  236. involves no extra logic on the part of the engine. The bad news is
  237. that users have very little control.
  238. Also note that AnimatedTexture is a :ref:`Resource <class_Resource>` unlike
  239. the other :ref:`Node <class_Node>` objects discussed here. One might create
  240. a :ref:`Sprite2D <class_Sprite2D>` node that uses AnimatedTexture as its texture.
  241. Or (something the others can't do) one could add AnimatedTextures as tiles
  242. in a :ref:`TileSet <class_TileSet>` and integrate it with a
  243. :ref:`TileMapLayer <class_TileMapLayer>` for many auto-animating backgrounds that
  244. all render in a single batched draw call.
  245. The :ref:`AnimatedSprite2D <class_AnimatedSprite2D>` node, in combination with the
  246. :ref:`SpriteFrames <class_SpriteFrames>` resource, allows one to create a
  247. variety of animation sequences through spritesheets, flip between animations,
  248. and control their speed, regional offset, and orientation. This makes them
  249. well-suited to controlling 2D frame-based animations.
  250. If one needs to trigger other effects in relation to animation changes (for
  251. example, create particle effects, call functions, or manipulate other
  252. peripheral elements besides the frame-based animation), then one will need to use
  253. an :ref:`AnimationPlayer <class_AnimationPlayer>` node in conjunction with
  254. the AnimatedSprite2D.
  255. AnimationPlayers are also the tool one will need to use if they wish to design
  256. more complex 2D animation systems, such as...
  257. 1. **Cut-out animations:** editing sprites' transforms at runtime.
  258. 2. **2D Mesh animations:** defining a region for the sprite's texture and
  259. rigging a skeleton to it. Then one animates the bones which
  260. stretch and bend the texture in proportion to the bones' relationships to
  261. each other.
  262. 3. A mix of the above.
  263. While one needs an AnimationPlayer to design each of the individual
  264. animation sequences for a game, it can also be useful to combine animations
  265. for blending, i.e. enabling smooth transitions between these animations. There
  266. may also be a hierarchical structure between animations that one plans out for
  267. their object. These are the cases where the :ref:`AnimationTree <class_AnimationTree>`
  268. shines. One can find an in-depth guide on using the AnimationTree
  269. :ref:`here <doc_animation_tree>`.