Skip to content

Commit

Permalink
Merge pull request #7640 from Calinou/update-best-practices
Browse files Browse the repository at this point in the history
  • Loading branch information
mhilbrunner committed Aug 1, 2023
1 parent 87f4cfc commit 6b2ea5d
Show file tree
Hide file tree
Showing 14 changed files with 202 additions and 178 deletions.
46 changes: 21 additions & 25 deletions tutorials/best_practices/autoloads_versus_internal_nodes.rst
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
:article_outdated: True

.. _doc_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
:ref:`doc_singletons_autoload`. These autoloaded nodes are not freed when you
change the scene from code with :ref:`SceneTree.change_scene_to_file <class_SceneTree_method_change_scene_to_file>`.

In this guide, you will learn when to use the Autoload feature, and techniques
Expand All @@ -25,7 +23,7 @@ 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
A solution is to code a global, autoloaded 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
Expand All @@ -44,7 +42,7 @@ solves the problem in the short term but causes more problems:

.. note::

About global access, the problem is that Any code anywhere could pass wrong
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.

Expand Down Expand Up @@ -82,30 +80,28 @@ When it comes to data, you can either:
When you should use an Autoload
-------------------------------

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.
GDScript supports the creation of ``static`` functions using ``static func``.
When combined with ``class_name``, this makes it possible to create libraries of
helper functions without having to create an instance to call them. The
limitation of static functions is that they can't reference member variables,
non-static functions or ``self``.

- **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.
Since Godot 4.1, GDScript also supports ``static`` variables using ``static var``.
This means you can now share a variables across instances of a class without
having to create a separate autoload.

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 auto-completion for a type in your entire project.
Still, autoloaded nodes can simplify your code for systems with a wide scope. If
the autoload 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.

.. 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 :kbd:`F6` key.
An autoload is *not* necessarily a singleton. Nothing prevents you from
instantiating copies of an autoloaded node. An autoload 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 :kbd:`F6` key.

As a result, you can get the auto-loaded node, for example an autoload called
As a result, you can get the autoloaded node, for example an autoload called
``Sound``, by calling ``get_node("/root/Sound")``.
73 changes: 37 additions & 36 deletions tutorials/best_practices/data_preferences.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,33 +10,33 @@ Y or Z? This article covers a variety of topics related to these dilemmas.

.. note::

This article makes references to "[something]-time" operations. This
terminology comes from algorithm analysis'
`Big O Notation <https://rob-bell.net/2009/06/a-beginners-guide-to-big-o-notation/>`_.

Long-story short, it describes the worst-case scenario of runtime length.
In laymen's terms:

"As the size of a problem domain increases, the runtime length of the
algorithm..."

- Constant-time, ``O(1)``: "...does not increase."
- Logarithmic-time, ``O(log n)``: "...increases at a slow rate."
- Linear-time, ``O(n)``: "...increases at the same rate."
- Etc.

Imagine if one had to process 3 million data points within a single frame. It
would be impossible to craft the feature with a linear-time algorithm since
the sheer size of the data would increase the runtime far beyond the time allotted.
In comparison, using a constant-time algorithm could handle the operation without
issue.

By and large, developers want to avoid engaging in linear-time operations as
much as possible. But, if one keeps the scale of a linear-time operation
small, and if one does not need to perform the operation often, then it may
be acceptable. Balancing these requirements and choosing the right
algorithm / data structure for the job is part of what makes programmers'
skills valuable.
This article makes references to "[something]-time" operations. This
terminology comes from algorithm analysis'
`Big O Notation <https://rob-bell.net/2009/06/a-beginners-guide-to-big-o-notation/>`_.

Long-story short, it describes the worst-case scenario of runtime length.
In laymen's terms:

"As the size of a problem domain increases, the runtime length of the
algorithm..."

- Constant-time, ``O(1)``: "...does not increase."
- Logarithmic-time, ``O(log n)``: "...increases at a slow rate."
- Linear-time, ``O(n)``: "...increases at the same rate."
- Etc.

Imagine if one had to process 3 million data points within a single frame. It
would be impossible to craft the feature with a linear-time algorithm since
the sheer size of the data would increase the runtime far beyond the time allotted.
In comparison, using a constant-time algorithm could handle the operation without
issue.

By and large, developers want to avoid engaging in linear-time operations as
much as possible. But, if one keeps the scale of a linear-time operation
small, and if one does not need to perform the operation often, then it may
be acceptable. Balancing these requirements and choosing the right
algorithm / data structure for the job is part of what makes programmers'
skills valuable.

Array vs. Dictionary vs. Object
-------------------------------
Expand All @@ -52,12 +52,13 @@ contents in a contiguous section of memory, i.e. they are in a row adjacent
to each other.

.. note::
For those unfamiliar with C++, a Vector is the name of the
array object in traditional C++ libraries. It is a "templated"
type, meaning that its records can only contain a particular type (denoted
by angled brackets). So, for example, a
:ref:`PackedStringArray <class_PackedStringArray>` would be something like
a ``Vector<String>``.

For those unfamiliar with C++, a Vector is the name of the
array object in traditional C++ libraries. It is a "templated"
type, meaning that its records can only contain a particular type (denoted
by angled brackets). So, for example, a
:ref:`PackedStringArray <class_PackedStringArray>` would be something like
a ``Vector<String>``.

Contiguous memory stores imply the following operation performance:

Expand Down Expand Up @@ -294,7 +295,7 @@ faster than string comparisons (linear-time). If one wants to keep
up other languages' conventions though, then one should use integers.

The primary issue with using integers comes up when one wants to *print*
an enum value. As integers, attempting to print MY_ENUM will print
an enum value. As integers, attempting to print ``MY_ENUM`` will print
``5`` or what-have-you, rather than something like ``"MyEnum"``. To
print an integer enum, one would have to write a Dictionary that maps the
corresponding string value for each enum.
Expand All @@ -314,7 +315,7 @@ The answer may not be immediately clear to new Godot users.
the engine draws as an animated loop rather than a static image.
Users can manipulate...

1. the rate at which it moves across each section of the texture (fps).
1. the rate at which it moves across each section of the texture (FPS).

2. the number of regions contained within the texture (frames).

Expand Down Expand Up @@ -346,7 +347,7 @@ the AnimatedSprite2D.
AnimationPlayers are also the tool one will need to use if they wish to design
more complex 2D animation systems, such as...

1. **Cut-Out animations:** editing sprites' transforms at runtime.
1. **Cut-out animations:** editing sprites' transforms at runtime.

2. **2D Mesh animations:** defining a region for the sprite's texture and
rigging a skeleton to it. Then one animates the bones which
Expand Down
60 changes: 32 additions & 28 deletions tutorials/best_practices/godot_interfaces.rst
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
:article_outdated: True

.. _doc_godot_interfaces:

Godot interfaces
Expand Down Expand Up @@ -41,32 +39,36 @@ access.
.. tabs::
.. code-tab:: gdscript GDScript

var preres = preload(path) # Load resource during scene load
var res = load(path) # Load resource when program reaches statement
# If you need an "export const var" (which doesn't exist), use a conditional
# setter for a tool script that checks if it's executing in the editor.
# The `@tool` annotation must be placed at the top of the script.
@tool

# Load resource during scene load.
var preres = preload(path)
# Load resource when program reaches statement.
var res = load(path)

# Note that users load scenes and scripts, by convention, with PascalCase
# names (like typenames), often into constants.
const MyScene : = preload("my_scene.tscn") as PackedScene # Static load
const MyScript : = preload("my_script.gd") as Script
const MyScene = preload("my_scene.tscn") # Static load
const MyScript = preload("my_script.gd")

# This type's value varies, i.e. it is a variable, so it uses snake_case.
export(Script) var script_type: Script

# If need an "export const var" (which doesn't exist), use a conditional
# setter for a tool script that checks if it's executing in the editor.
tool # Must place at top of file.
@export var script_type: Script

# Must configure from the editor, defaults to null.
export(Script) var const_script setget set_const_script
func set_const_script(value):
if Engine.is_editor_hint():
const_script = value
export var const_script: Script:
set(value):
if Engine.is_editor_hint():
const_script = value

# Warn users if the value hasn't been set.
func _get_configuration_warning():
func _get_configuration_warnings():
if not const_script:
return "Must initialize property 'const_script'."
return ""
return ["Must initialize property 'const_script'."]

return []

.. code-tab:: csharp

Expand Down Expand Up @@ -106,7 +108,7 @@ access.
};

// Warn users if the value hasn't been set.
public String _GetConfigurationWarning()
public String _GetConfigurationWarnings()
{
if (EnemyScn == null)
return "Must initialize property 'EnemyScn'.";
Expand Down Expand Up @@ -142,7 +144,7 @@ Nodes likewise have an alternative access point: the SceneTree.
print($Child)

# Fastest. Doesn't break if node moves later.
# Note that `@onready` annotation is GDScript only.
# Note that `@onready` annotation is GDScript-only.
# Other languages must do...
# var child
# func _ready():
Expand All @@ -151,6 +153,12 @@ Nodes likewise have an alternative access point: the SceneTree.
func lookup_and_cache_for_future_access():
print(child)

# Fastest. Doesn't break if node is moved in the Scene tree dock.
# Node must be selected in the inspector as it's an exported property.
@export var child: Node
func lookup_and_cache_for_future_access():
print(child)

# Delegate reference assignment to an external source.
# Con: need to perform a validation check.
# Pro: node makes no requirements of its external structure.
Expand All @@ -169,8 +177,7 @@ Nodes likewise have an alternative access point: the SceneTree.
return

# Fail and terminate.
# Note: Scripts run from a release export template don't
# run `assert` statements.
# NOTE: Scripts run from a release export template don't run `assert`s.
assert(prop, "'prop' wasn't initialized")

# Use an autoload.
Expand Down Expand Up @@ -236,8 +243,7 @@ Nodes likewise have an alternative access point: the SceneTree.
}

// Fail and terminate.
// Note: Scripts run from a release export template don't
// run `Debug.Assert` statements.
// Note: Scripts run from a release export template don't run `Debug.Assert`s.
Debug.Assert(Prop, "'Prop' wasn't initialized");
}

Expand Down Expand Up @@ -287,10 +293,8 @@ following checks, in order:
execute logic that gives the impression that the Object has a property. This
is also the case with the ``_get_property_list`` method.

- Note that this happens even for non-legal symbol names such as in the
case of :ref:`TileSet <class_TileSet>`'s "1/tile_name" property. This
refers to the name of the tile with ID 1, i.e.
``TileSet.tile_get_name(1)``.
- Note that this happens even for non-legal symbol names, such as names
starting with a digit or containing a slash.

As a result, this duck-typed system can locate a property either in the script,
the object's class, or any class that object inherits, but only for things
Expand Down
Loading

0 comments on commit 6b2ea5d

Please sign in to comment.