Browse Source

Merge pull request #2998 from NathanLovato/best-practices-improvements

Proof best practices: autoloads_versus_internal_nodes
Rémi Verschelde 5 years ago
parent
commit
c15b1eb996

+ 105 - 99
getting_started/workflow/best_practices/autoloads_versus_internal_nodes.rst

@@ -1,103 +1,109 @@
 .. _doc_autoloads_versus_internal_nodes:
 
-Autoloads versus internal nodes
+Autoloads versus regular nodes
+==============================
+
+Godot offers a feature to automatically load nodes at the root of your project,
+allowing you to access them globally, that can fulfill the role of a Singleton:
+:ref:`doc_singletons_autoload`. These auto-loaded nodes are not freed when you
+change the scene from code with :ref:`SceneTree.change_scene <class_SceneTree_method_change_scene>`..
+
+In this guide, you will learn when to use the Autoload feature, and techniques
+you can use to avoid it.
+
+The cutting audio issue
+=======================
+
+Other engines can encourage the use of creating manager classes, singletons that
+organize a lot of functionality into a globally accessible object. Godot offers
+many ways to avoid global state thanks to the node tree and signals.
+
+For example, let's say we are building a platformer and want to collect coins
+that play a sound effect. There's a node for that: the :ref:`AudioStreamPlayer
+<class_AudioStreamPlayer>`. But if we call the ``AudioStreamPlayer`` while it is
+already playing a sound, the new sound interrupts the first.
+
+A solution is to code a global, auto-loaded sound manager class. It generates a
+pool of ``AudioStreamPlayer`` nodes that cycle through as each new request for
+sound effects comes in. Say we call that class ``Sound``, you can use it from
+anywhere in your project by calling ``Sound.play("coin_pickup.ogg")``. This
+solves the problem in the short term but causes more problems:
+
+1. **Global state**: one object is now responsible for all objects' data. If the
+   ``Sound`` class has errors or doesn't have an AudioStreamPlayer available,
+   all the nodes calling it can break.
+
+2. **Global access**: now that any object can call ``Sound.play(sound_path)``
+   from anywhere, there's no longer an easy way to find the source of a bug.
+
+3. **Global resource allocation**: with a pool of ``AudioStreamPlayer`` nodes
+   stored from the start, you can either have too few and face bugs, or too many
+   and use more memory than you need.
+
+.. note::
+
+   About global access, the problem is that Any code anywhere could pass wrong
+   data to the ``Sound`` autoload in our example. As a result, the domain to
+   explore to fix the bug spans the entire project.
+
+   When you keep code inside a scene, only one one or two scripts may be
+   involved in audio.
+
+Contrast this with each scene keeping as many ``AudioStreamPlayer`` nodes as it
+needs within itself and all these problems go away:
+
+1. Each scene manages its own state information. If there is a problem with the
+   data, it will only cause issues in that one scene.
+
+2. Each scene accesses only its own nodes. Now, if there is
+   a bug, it's easy to find which node is at fault.
+
+3. Each scene allocates exactly the amount of resources it needs.
+
+Managing shared functionality or data
+=====================================
+
+Another reason to use an Autoload can be that you want to reuse the same method
+or data across many scenes.
+
+In the case of functions, you can create a new type of ``Node`` that provides
+that feature for an individual scene using the :ref:`class_name
+<doc_scripting_continued_class_name>` keyword in GDScript.
+
+When it comes to data, you can either:
+
+1. Create a new type of :ref:`Resource <class_Resource>` to share the data.
+
+2. Store the data in an object to which each node has access, for example using
+   the ``owner`` property to access the scene's root node.
+
+When you should use an Autoload
 ===============================
 
-Other engines might encourage the use of creating "manager" classes that
-organize lots of functionality into a globally accessible entity. Godot
-thrives by supporting ways to cut down the size of such objects. Instead it
-shifts content into individual nodes as much as possible.
-
-For example, what if a developer is building a platformer and they want to
-collect coins that play a sound effect? Well, there's a node for that:
-the :ref:`AudioStreamPlayer <class_AudioStreamPlayer>`. But they notice during
-their testing that if they "play" an AudioStreamPlayer while it is already playing
-the sound, then the new sound interrupts the first sound, terminating it
-before it can play.
-
-Users tend to think the best solution is to make the whole system smarter by 
-making a SoundManager autoload node. It generates a pool of AudioStreamPlayers
-that cycle through as each new request for sound effects comes in. They then
-make this SoundManager an autoload so that they can access it from anywhere with
-`SFX.play("coin_pickup.ogg")`. Little do they know, they've invited a great
-many complications into their code.
-
-- **global state**: One object is now responsible for all objects' data. If
-  SFX has errors or doesn't have an AudioStreamPlayer available, everything
-  will break.
-
-- **global access**: Now that any object can call `SFX.play(sound_path)`
-  from anywhere, there's no longer an easy way to track where an SFX bug
-  began.
-
-- **global resource allocation**: If all objects' data and processing is
-  centralized from the start, then one must either...
-
-  1. risk under-allocating resources which might lead to faulty behavior.
-
-     - Ex: Have too few AudioStreamPlayers in the object pool. Sound doesn't
-       play or it interrupts another sound.
-
-  2. over-allocate resources and use more memory/processing than it needs.
-
-     - Ex: Have an arbitrarily large number of AudioStreamPlayers, with
-       many of them idling away and not doing anything.
-
-  3. have each object that needs an AudioStreamPlayer register exactly how
-     many it needs and for which sounds. This defeats the purpose of
-     using a 3rd party though; it is now coupled to each object, just
-     as a child node would have been. One has added an unnecessary
-     middleman to the equation.
-
-Contrast this with each scene keeping as many AudioStreamPlayer nodes as it
-needs within itself and all these problems go away.
-
-- Each scene manages its own state information. If there is a problem with the
-  data, it will only cause issues in that one scene.
-
-- Each scene accesses only its own nodes. Now, if there is
-  a bug, tracing which node is responsible (probably the root node of the
-  scene), and where in the code it's making the problematic call (locate
-  where the code references the given node) is going to be much easier.
-
-- Each scene knows exactly how many resources it needs for the task it
-  performs. No waste of memory or processing due to a lack of information.
-
-The typical justifications for the Autoload include, "I have common Xs that
-involve many nodes across many scenes, and I want each scene to have X."
-
-If X is a function, then the solution is to create a new type of
-:ref:`Node <class_Node>` that deals with providing that feature for an
-individual scene or node subtree.
-
-If X is data, then the solution is either 1) to create a new type of
-:ref:`Resource <class_Resource>` to share the data, or 2) to store the data
-in an object to which each node has access (nodes within a scene can use
-`get_owner()` to fetch the scene root for example).
-
-So when *should* one use an autoload?
-
-- **Static Data**: if you need static data, i.e. data that should be
-  associated with a class (so there is only ever one copy of the data), then
-  autoloads are good opportunities for that. Static data doesn't exist in
-  Godot's scripting API, so autoload singletons are the next best thing. If
-  one creates a class as an autoload, and never creates another copy of that
-  class within a scene, then it will function in place of a formal singleton
-  API.
-
-- **Convenience**: autoloaded nodes have a global variable for their name
-  generated in GDScript. This can be very convenient for defining objects
-  that should always exist, but which need object instance information.
-  The alternative is to create a namespace script: a script that's purpose
-  is only to load and create constants to access other Script or PackedScene
-  resources, resulting in something like ``MyAutoload.MyNode.new()``.
-
-  - Note that the introduction of script classes in Godot 3.1 questions
-    the validity of this reason. With them, one can access scripts using an
-    explicit name from GDScript. Using an autoload to get to a namespace
-    script becomes unnecessary, e.g. ``MyScriptClass.MyPreloadedScript.new()``.
-
-If the singleton is managing its own information and not invading the data of
-other objects, then it's a great way to create a "system" class that handles
-a broad-scoped task. For example a targeting system, quest system, or dialogue
-system would be great use cases of singleton implementations.
+Auto-loaded nodes can simplify your code in some cases:
+
+- **Static Data**: if you need data that is exclusive to one class, like a
+  database, then an autoload can be a good tool. There is no scripting API in
+  Godot to create and manage static data otherwise.
+
+- **Static functions**: creating a library of functions that only return values.
+
+- **Systems with a wide scope**: If the singleton is managing its own
+  information and not invading the data of other objects, then it's a great way to
+  create systems that handle broad-scoped tasks. For example, a quest or a
+  dialogue system.
+
+Until Godot 3.1, another use was just for convenience: autoloads have a global
+variable for their name generated in GDScript, allowing you to call them from
+any script file in your project. But now, you can use the ``class_name`` keyword
+instead to get autocompletion for a type in your entire project.
+
+.. note::
+
+   Autoload is not exactly a Singleton. Nothing prevents you from instantiating
+   copies of an auto-loaded node. It is only a tool that makes a node load
+   automatically as a child of the root of your scene tree, regardless of your
+   game's node structure or which scene you run, e.g. by pressing the ``F6`` key.
+
+   As a result, you can get the auto-loaded node, for example an autoload called
+   ``Sound``, by calling ``get_node("/root/Sound")``.