Merge branch 'master' into optimation_optimazations

This commit is contained in:
Rémi Verschelde
2019-04-24 07:05:24 +02:00
committed by GitHub
24 changed files with 716 additions and 29 deletions

View File

@@ -80,13 +80,13 @@ We construct a rotation matrix like so:
//angle is scaled by 0.1 so that the fish only pivots and doesn't rotate all the way around
//pivot is a uniform float
float pivot_angle = cos(time) * 0.1 * pivot;
mat2 rotation_matrix = mat2(vec2(cos(pivot_angle), -sin(pivot_angle)), vec2(sin(pivot_angle), cos(pivot_angle)));
mat2 rotation_matrix = mat2(vec2(cos(pivot_angle), -sin(pivot_angle)), vec2(sin(pivot_angle), cos(pivot_angle)));
And then we apply it in the ``x`` and ``z`` axes by multiplying it by ``VERTEX.xz``.
.. code-block:: glsl
VERTEX.xz = rotation_matrix * VERTEX.xz;
VERTEX.xz = rotation_matrix * VERTEX.xz;
With only the pivot applied you should see something like this:
@@ -121,14 +121,14 @@ we first construct a rotation matrix.
//twist is a uniform float
float twist_angle = cos(time + body) * 0.3 * twist;
mat2 twist_matrix = mat2(vec2(cos(twist_angle), -sin(twist_angle)), vec2(sin(twist_angle), cos(twist_angle)));
mat2 twist_matrix = mat2(vec2(cos(twist_angle), -sin(twist_angle)), vec2(sin(twist_angle), cos(twist_angle)));
We apply the rotation in the ``xy`` axes so that the fish appears to roll around its spine. For
this to work, the fishes spine needs to be centered on the ``z`` axis.
.. code-block:: glsl
VERTEX.xy = twist_matrix * VERTEX.xy;
VERTEX.xy = twist_matrix * VERTEX.xy;
Here is the fish with twist applied:
@@ -219,10 +219,10 @@ to loop over all the instances and set their transform to a random position.
::
for i in range($School.multimesh.instance_count):
var position = Transform()
position = position.translated(Vector3(randf() * 100 - 50, randf() * 50 - 25, randf() * 50 - 25))
$School.multimesh.set_instance_transform(i, position)
for i in range($School.multimesh.instance_count):
var position = Transform()
position = position.translated(Vector3(randf() * 100 - 50, randf() * 50 - 25, randf() * 50 - 25))
$School.multimesh.set_instance_transform(i, position)
Running this script will place the fish in random positions in a box around the position of the
MultiMeshInstance.
@@ -244,7 +244,7 @@ We do that by adding the per-instance custom value ``INSTANCE_CUSTOM`` to ``time
.. code-block:: glsl
float time = (TIME * time_scale) + (6.28318 * INSTANCE_CUSTOM.x);
float time = (TIME * time_scale) + (6.28318 * INSTANCE_CUSTOM.x);
Next, we need to pass a value into ``INSTANCE_CUSTOM``. We do that by adding one line into
the ``for`` loop from above. In the ``for`` loop we assign each instance a set of four

View File

@@ -5,4 +5,6 @@ Creating content
:maxdepth: 1
:name: toc-tutorials-content
procedural_geometry
making_trees

View File

@@ -0,0 +1,150 @@
.. _doc_procedural_geometry:
Procedural geometry generation
===============================
Users often ask how to generate geometry from easily code. This is not very complicated, but it's not obvious.
Godot provides a few classes entirely dedicated to make it this easy. Still, the best tool for the job depends
entirely on the use case.
SurfaceTool
-----------
This is the most common helper. :ref:`SurfaceTool<class_SurfaceTool>` is a class you can instantiate to generate :ref:`Meshes<class_Mesh>`, specifically *Mesh Surfaces*.
It has a similar API to OpenGL 1.x, and it's meant for static content. This means, the mesh is generated once and then used.
Here is a simple example of how to use it to add a single triangle.
.. tabs::
.. code-tab:: gdscript GDScript
var st = SurfaceTool.new()
st.begin(Mesh.PRIMITIVE_TRIANGLE)
# Prepares attributes for add_vertex
st.add_normal( Vector3(0,0,1) )
st.add_uv( Vector2(0,0) )
# Call last for each vertex, adds the above attributes
st.add_vertex( Vector3(-1,-1,0) )
st.add_normal( Vector3(0,0,1) )
st.add_uv( Vector2(0,1) )
st.add_vertex( Vector3(-1,1,0) )
st.add_normal( Vector3(0,0,1) )
st.add_uv( Vector2(1,1) )
st.add_vertex( Vector3(1,1,0) )
# Create indices, indices are optional
st.index()
# Commit to a mesh
var mesh = st.commit()
Just explore the APIs and the possibilities.
ImmediateGeometry
-----------------
Unlike *SurfaceTool*, :ref:`ImmediateGeometry<class_ImmediateGeometry>` is an actual node. It's similar in the "OpenGL 1.x" style API,
but it's actually designed to create content on the fly and modify it every frame efficiently.
Generating complex geometry (several thousand vertices) with this node is inefficient, even if it's done only once. Instead, *ImmediateGeometry* is designed to generate simple geometry that changes every frame.
It's used similar to *SurfaceTool*.
.. tabs::
.. code-tab:: gdscript GDScript
extends ImmediateGeometry
void _process(delta):
# Clean up before drawing
clear()
# Begin draw
begin(Mesh.PRIMITIVE_TRIANGLE)
# Prepares attributes for add_vertex
set_normal( Vector3(0,0,1) )
set_uv( Vector2(0,0) )
# Call last for each vertex, adds the above attributes
add_vertex( Vector3(-1,-1,0) )
set_normal( Vector3(0,0,1) )
set_uv( Vector2(0,1) )
add_vertex( Vector3(-1,1,0) )
set_normal( Vector3(0,0,1) )
set_uv( Vector2(1,1) )
add_vertex( Vector3(1,1,0) )
# End drawing
end()
Arrays
------
Lastly, the final way to do this is to create arrays themselves. This is the most efficient way to create static geometry, and is only
recommended when SurfaceTool is not fast enough.
Similar code as before, but draw a square using indices:
.. tabs::
.. code-tab:: gdscript GDScript
var arrays = []
arrays.resize(Mesh::ARRAY_MAX)
var normal_array = []
var uv_array = []
var vertex_array = []
var index_array = []
normal_array.resize(4)
uv_array.resize(4)
vertex_array.resize(4)
index_array.resize(6)
normal_array[0]=Vector3(0,0,1)
uv_array[0]=Vector2(0,0)
vertex_array[0]=Vector3(-1,-1)
normal_array[1]=Vector3(0,0,1)
uv_array[1]=Vector2(0,1)
vertex_array[1]=Vector3(-1, 1)
normal_array[2]=Vector3(0,0,1)
uv_array[2]=Vector2(1,1)
vertex_array[2]=Vector3( 1, 1)
normal_array[3]=Vector3(0,0,1)
uv_array[3]=Vector2(1,0)
vertex_array[3]=Vector3( 1, -1)
# indices are optional in Godot, but if they exist they are used
index_array[0]=0
index_array[1]=1
index_array[2]=2
index_array[3]=2
index_array[4]=3
index_array[5]=0
arrays[Mesh.ARRAY_VERTEX]=vertex_array
arrays[Mesh.ARRAY_NORMAL]=normal_array
arrays[Mesh.ARRAY_TEX_UV]=uv_array
arrays[Mesh.ARRAY_INDEX]=index_array
var mesh = ArrayMesh.new()
mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES,arrays)

View File

@@ -0,0 +1,183 @@
.. _doc_beziers_and_curves:
Beziers, Curves and Paths
==========================
Introduction
~~~~~~~~~~~~
Bezier curves are a mathematical approximation to natural shapes. Their goal is to represent a curve with
as little information as possible, and with a high level of flexibility.
Unlike other more abstract mathematical concepts, Bezier curves were created for industrial design and are
widely popular in the graphics software industry.
The way they work is very simple, but to understand it, let's start from the most minimal example.
If you are not fresh on interpolation, please read the :ref:`relevant page<doc_interpolation>`
before proceeding.
Quadratic Bezier
----------------
Take three points (the minimum required):
.. image:: img/bezier_quadratic_points.png
To draw the curve between them, just interpolate the two segments that form between the three points, individually (using values 0 to 1). This will result in two points.
.. tabs::
.. code-tab:: gdscript GDScript
func _quadratic_bezier(p0 : Vector2,p1 : Vector2,p2 : Vector2 ,t : float):
var q0 = p0.linear_interpolate(p1,t)
var q1 = p1.linear_interpolate(p2,t)
This will reduce the points from 3 to 2. Do the same process with *q0* and *q1* to obtain a single point *r*.
.. tabs::
.. code-tab:: gdscript GDScript
var r = q0.linear_interpolate(q1,t)
return r
Finally, this point fill follow the curve when t goes from 0 to 1. This type of curve is called *Quadratic Bezier*.
.. image:: img/bezier_quadratic_points2.gif
*(Image credit: Wikipedia)*
Cubic Bezier
----------------
Let's add one more point and make it four.
.. image:: img/bezier_cubic_points.png
Then let's modify the function to take four points as an input, *p0, p1, p2* and *p3*:
.. tabs::
.. code-tab:: gdscript GDScript
func _cubic_bezier(p0 : Vector2,p1 : Vector2,p2 : Vector2,p3 : Vector2 ,t : float):
Interpolate then into three points:
.. tabs::
.. code-tab:: gdscript GDScript
var q0 = p0.linear_interpolate(p1,t)
var q1 = p1.linear_interpolate(p2,t)
var q2 = p2.linear_interpolate(p3,t)
From there to two points:
.. tabs::
.. code-tab:: gdscript GDScript
var r0 = q0.linear_interpolate(q1,t)
var r1 = q1.linear_interpolate(q2,t)
And to one:
.. tabs::
.. code-tab:: gdscript GDScript
var s = r0.linear_interpolate(r1,t)
return s
The result will be a smooth curve interpolating between all four points:
.. image:: img/bezier_cubic_points.gif
*(Image credit: Wikipedia)*
.. note:: For 3D, it's exactly the same, just change Vector2 into Vector3.
Control point form
-------------------
Now, let's take these points and change the way we understand them. Instead of having p0, p1, p2 and p3, we will store them as:
* **POINT0** = **P0**: Is the first point, the source
* **CONTROL0** = **P1** - **P0**: Is a relative vector for the first control point
* **CONTROL1** = **P3** - **P2**: Is a relative vector for the second control point
* **POINT1** = **P3**: Is the second point, the destination
This way, we have two points and two control points (which are relative vectors to the respective points). If visualized, they will look a lot more familiar:
.. image:: img/bezier_cubic_handles.png
This is actually how graphics software presents Bezier curves to the users, and how Godot supports them.
Curve2D, Curve3D, Path and Path2D
-------------------------------
There are two objects that contain curves: :ref:`Curve3D <class_Curve3D>` and :ref:`Curve2D <class_Curve2D>` (for 3D and 2D respectively).
They can contain several points, allowing for longer paths. It is also possible to set them to nodes: :ref:`Path <class_Path>` and :ref:`Path2D <class_Path2D>` (also for 3D and 2D respectively):
.. image:: img/bezier_path_2d.png
Using them, however, may not be completely obvious, so following is a description of the most common use cases for Bezier curves.
Evaluating
-----------
Just evaluating them may be an option, but in most cases it's not very useful. The big drawback with Bezier curves is that if you traverse them at constant speed, from *t=0* to *t=1*, the actual interpolation will *not* move at constant speed. The speed is also an interpolation between the distances between points p0, p1, p2 and p3 and there is not a mathematically simple way to traverse the curve at constant speed.
Let's do a simple example with the following pseudocode:
.. tabs::
.. code-tab:: gdscript GDScript
var t = 0.0
_process(delta):
t+=delta
position = _cubic_bezier(p0,p1,p2,p3,t)
.. image:: img/bezier_interpolation_speed.gif
As you can see, the speed (in pixels per second) of the circle varies, even though *t* is increased at constant speed. This makes beziers difficult to use for anything practical out of the box.
Drawing
-------
Drawing beziers (or objects based on the curve) is a very common use case, but it's also not easy. For pretty much any case, Bezier curves need to be converted to some sort of segments. This is normally difficult, however, without creating a very high amount of them.
The reason is that some sections of a curve (specifically, corners) may requiere considerable amounts of points, while other sections may not:
.. image:: img/bezier_point_amount.png
Additionally, if both control points were 0,0 (remember they are relative vectors), the Bezier curve would just be a straight line (so drawing a high amount of points would be wasteful).
Before drawing Bezier curves, *tesselation* is required. This is often done with a recursive or divide and conquer function that splits the curve until the curvature amount becomes less than a certain threshold.
The *Curve* classes provide this via the
:ref:`Curve2D.tesselate()<class_Curve2D_method_tesselete>` function (which receives optional *stages* of recursion and angle *tolerance* arguments). This way, drawing something based on a curve is easier.
Traversal
---------
The last common use case for the curves is to traverse them. Because of what was mentioned before regarding constant speed, this is also difficult.
To make this easier, the curves need to be *baked* into equidistant points. This way, they can be approximated with regular interpolation (which can be improved further with a cubic option). To do this, just use the :ref:`Curve.interpolate_baked()<class_Curve_method_interpolate_baked>` method together with
:ref:`Curve2D.get_baked_length()<class_Curve2D_method_get_baked_length>`. The first call to either of them will bake the curve internally.
Traversal at constant speed, then, can be done with the following pseudo-code:
.. tabs::
.. code-tab:: gdscript GDScript
var t = 0.0
_process(delta):
t+=delta
position = curve.interpolate_baked( t * curve.get_baked_length(), true)
And the output will, then, move at constant speed:
.. image:: img/bezier_interpolation_baked.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 278 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -8,3 +8,5 @@ Math
vector_math
vectors_advanced
matrices_and_transforms
interpolation
beziers_and_curves

View File

@@ -0,0 +1,107 @@
.. _doc_interpolation:
Interpolation
=============
Introduction
~~~~~~~~~~~~
Interpolation is a very basic operation in graphics programming. It's good to become familiar with it in order to expand your horizons as a graphics developer.
The basic idea is that you want to transition from A to B. A value *t*, represents the states in-between.
As an example if *t* is 0, then the state is A. If *t* is 1, then the state is B. Anything in-between is an *interpolation*.
Between two real (floating point) numbers, a simple interpolation is usually described as:
.. tabs::
.. code-tab:: gdscript GDScript
interpolation = A * (t-1) + B * t
And often simplified to:
.. tabs::
.. code-tab:: gdscript GDScript
interpolation = A + (B - A) * t
which is exactly the same.
The name of this type of interpolation, which transforms a value into another at *constant speed* is *"Linear"*. So, when you hear about *Linear Interpolation*, you know they are referring to this simple formula.
There are other types of interpolations, which will not be covered here. A recommended read afterwards is the :ref:`Bezier <doc_beziers_and_curves>` page.
Vector Interpolation
--------------------
Vector types (Vector2 and Vector3) can also be interpolated, they come with handy functions to do it
:ref:`Vector2.linear_interpolate()<class_Vector2_method_linear_interpolate>` and :ref:`Vector3.linear_interpolate()<class_Vector3_method_linear_interpolate>`.
For cubic interpolation, there are also :ref:`Vector2.cubic_interpolate()<class_Vector2_method_linear_interpolate>` and :ref:`Vector3.cubic_interpolate()<class_Vector3_method_linear_interpolate>`, which do a :ref:`Bezier <doc_beziers_and_curves>` style interpolation.
Here is simple pseudo-code for going from point A to B using interpolation:
.. tabs::
.. code-tab:: gdscript GDScript
func _physics_process(delta):
t+=delta*0.4
$Sprite.position = $A.position.linear_interpolate( $B.position,t )
It will produce the following motion:
.. image:: img/interpolation_vector.gif
Transform interpolation
--------------------
It is also possible to interpolate whole transforms (make sure they have either uniform scale or, at least, the same non-uniform scale).
For this, the function :ref:`Transform.interpolate_with()<class_Transform_method_interpolate_with>` can be used.
Here is an example of transforming a monkey from Position1 to Position2:
.. image:: img/interpolation_positions.png
Using the following pseudocode:
.. tabs::
.. code-tab:: gdscript GDScript
var t = 0.0
func _process(delta):
t+=delta
$Monkey.transform = $Position1.transform.interpolate_with( $Position2.transform, t )
And again, it will produce the following motion:
.. image:: img/interpolation_monkey.gif
Smoothing Motion
----------------
Interpolation can be used to smooth movement, rotation, etc. Here is an example of a circle following the mouse using smoothed motion:
.. tabs::
.. code-tab:: gdscript GDScript
const FOLLOW_SPEED = 4.0
func _physics_process(delta):
var mouse_pos = get_local_mouse_position()
$Sprite.position = $Sprite.position.linear_interpolate( mouse_pos, delta * FOLLOW_SPEED )
Here is how it looks:
.. image:: img/interpolation_follow.gif
This useful for smoothing camera movement, allies following you (ensuring they stay within a certain range), and many other common game patterns.

View File

@@ -1,9 +1,9 @@
.. _doc_using_servers:
Optimization Using Servers
Optimization using Servers
==========================
Engines like Godot provide increased ease of use thanks to it's high level constructs and features.
Engines like Godot provide increased ease of use thanks to their high level constructs and features.
Most of them are accessed and used via the :ref:`Scene System<doc_scene_tree>`. Using nodes and
resources simplifies project organization and asset management in complex games.
@@ -32,10 +32,10 @@ One of the most interesting design decisions for Godot, is the fact that the who
At the core, Godot uses the concept of Servers. They are very low level APIs to control
rendering, physics, sound, etc. The scene system is built on top of them and uses them directly. The most common servers are:
* :ref:`Visual Server<class_VisualServer>`: handles everything related to graphics.
* :ref:`PhysicsServer<class_PhysicsServer>`: handles everything related to 3D physics.
* :ref:`Physics2DServer<class_Physics2DServer>`: handles everything related to 2D physics.
* :ref:`AudioServer<class_AudioServer>`: handles everything related to audio.
* :ref:`VisualServer <class_VisualServer>`: handles everything related to graphics.
* :ref:`PhysicsServer <class_PhysicsServer>`: handles everything related to 3D physics.
* :ref:`Physics2DServer <class_Physics2DServer>`: handles everything related to 2D physics.
* :ref:`AudioServer <class_AudioServer>`: handles everything related to audio.
Just explore their APIs and you will realize that the all functions provided are low-level
implementations of everything Godot allows you to do.
@@ -43,7 +43,7 @@ implementations of everything Godot allows you to do.
RIDs
----
The key to using servers is understanding Resource ID (RID) objects. These are opaque
The key to using servers is understanding Resource ID (:ref:`RID <class_RID>`) objects. These are opaque
handles to the sever implementation. They are allocated and freed manually. Almost every
function in the servers requires RIDs to access the actual resource.
@@ -56,31 +56,34 @@ the internal RID is erased too.
For nodes, there are many functions available:
* For CanvasItem, the :ref:`CanvasItem.get_canvas_item()<class_CanvasItem_method_get_canvas_item>`
* For CanvasItem, the :ref:`CanvasItem.get_canvas_item() <class_CanvasItem_method_get_canvas_item>`
method will return the canvas item RID in the server.
* For CanvasLayer, the :ref:`CanvasLayer.get_canvas()<class_CanvasLayer_method_get_canvas>`
* For CanvasLayer, the :ref:`CanvasLayer.get_canvas() <class_CanvasLayer_method_get_canvas>`
method will return the canvas RID in the server.
* For Viewport, the :ref:`Viewport.get_viewport_rid()<class_Viewport_method_get_viewport_rid>`
* For Viewport, the :ref:`Viewport.get_viewport_rid() <class_Viewport_method_get_viewport_rid>`
method will return the viewport RID in the server.
* For 3D, the :ref:`World<class_World>` resource (obtainable in the *Viewport* and *Spatial* nodes)
* For 3D, the :ref:`World <class_World>` resource (obtainable in the :ref:`Viewport <class_Viewport>`
and :ref:`Spatial <class_Spatial>` nodes)
contains functions to get the *VisualServer Scenario*, and the *PhysicsServer Space*. This
allows creating 3D objects directly with the server API and using them.
* For 2D, the :ref:`World2D<class_World2D>` resource (obtainable in the *Viewport* and *CanvasItem*
* For 2D, the :ref:`World2D <class_World2D>` resource (obtainable in the :ref:`Viewport <class_Viewport>`
and :ref:`CanvasItem <class_CanvasItem>` nodes)
nodes) contains functions to get the *VisualServer Canvas*, and the *Physics2DServer Space*. This
allows creating 2D objects directly with the server API and using them.
* The :ref:`VisualInstance<class_VisualInstance>` class, allows getting the scenario *instance* and
*instance base* via the :ref:`VisualInstance.get_instance()<class_VisualInstance_method_get_instance>`
and :ref:`VisualInstance.get_base()<class_VisualInstance_method_get_base>` respectively.
*instance base* via the :ref:`VisualInstance.get_instance() <class_VisualInstance_method_get_instance>`
and :ref:`VisualInstance.get_base() <class_VisualInstance_method_get_base>` respectively.
Just explore the nodes and resources you are familiar with and find the functions to obtain the server *RIDs*.
It is not advised to control RIDs from objects that already have a node associated. Instead, server
functions should always be used for creating and controlling new ones and interacting with the existing ones.
Creating a Sprite
Creating a sprite
-----------------
This is a simple example of how to create a sprite from code and move it using the low-level Canvas Item API.
This is a simple example of how to create a sprite from code and move it using the low-level
:ref:`CanvasItem <class_CanvasItem>` API.
.. tabs::
.. code-tab:: gdscript GDScript
@@ -138,10 +141,11 @@ The 3D APIs are different than the 2D ones, so the instantiation API must be use
var xform = Transform(Basis(), Vector3(20, 100, 0))
VisualServer.instance_set_transform(instance,xform)
Creating a 2D RigidBody and moving a Sprite with it
Creating a 2D RigidBody and moving a sprite with it
----------------------------------------------------
This creates a *RigidBody* using the *Physics2DServer* API, and moves a *Canvas Item* when the body moves.
This creates a :ref:`RigidBody2D <class_RigidBody2D>` using the :ref:`Physics2DServer <Physics2DServer>` API,
and moves a :ref:`CanvasItem <class_CanvasItem>` when the body moves.
.. tabs::
.. code-tab:: gdscript GDScript
@@ -168,12 +172,13 @@ This creates a *RigidBody* using the *Physics2DServer* API, and moves a *Canvas
# if you have many bodies and a single callback.
Physics2DServer.body_set_force_integration_callback(body, self, "_body_moved", 0)
The 3D version should be very similar, as 2D and 3D physics servers are identical.
The 3D version should be very similar, as 2D and 3D physics servers are identical (using
:ref:`RigidBody <RigidBody>` and :ref:`PhysicsServer <PhysicsServer>` respectively).
Getting data from the servers
-----------------------------
Try to **never** request any information from *VisualServer*, *PhysicsServer* or *Physics2DServer*
Try to **never** request any information from ``VisualServer``, ``PhysicsServer`` or ``Physics2DServer``
by calling functions unless you know what you are doing. These servers will often run asynchronously
for performance and calling any function that returns a value will stall them and force them to process
anything pending until the function is actually called. This will severely decrease performance if you

View File

@@ -9,3 +9,4 @@ Editor plugins
making_main_screen_plugins
import_plugins
spatial_gizmos
inspector_plugins

View File

@@ -0,0 +1,83 @@
.. _doc_inspector_plugins:
Inspector Plugins
=====================
Introduction
------------
Godot 3.1 comes with a new inspector. Adding plugins to it is now possible.
This tutorial will explain the process.
Inspector Plugin
----------------
This short tutorial will explain how to make a simple value editor.
Create an EditorInspectorPlugin first. This is needed to initialize your plugin.
.. tabs::
.. code-tab:: gdscript GDScript
# MyEditorPlugin.gd
extends EditorInspectorPlugin
func can_handle(object):
# if only editing a specific type
# return object is TheTypeIWant
# if everything is supported
return true
func parse_property(object,type,path,hint,hint_text,usage):
if (type==TYPE_INT):
add_custom_property_editor(path,MyEditor.new())
return true # I want this one
else:
return false
Editor
------
Here is an editor for editing integers
.. tabs::
.. code-tab:: gdscript GDScript
# MyEditor.gd
class_name MyEditor
var updating = false
func _spin_changed(value):
if (updating):
return
emit_changed( get_property(), value )
func update_property():
var new_value = get_edited_object()[ get_edited_property() ]
updating=true
spin.set_value( new_value )
updating=false
var spin = EditorSpinSlider.new() # use the new spin slider
func _init():
# if you want to put the editor below the label
# set_bottom_editor( spin )
# else use:
add_child( spin )
# to remember focus when selected back
add_focusable( spin )
spin.set_min(0)
spin.set_max(1000)
spin.connect("value_changed",self,"_spin_changed")

View File

@@ -5,4 +5,5 @@ Multi-threading
:maxdepth: 1
:name: toc-learn-features-threads
using_multiple_threads
thread_safe_apis

View File

@@ -0,0 +1,153 @@
.. _doc_using_multiple_threads:
Using multiple threads
======================
Threads
-------
Threads allow simultaneous execution of code. It allows off-loading work from the main thread.
Godot supports threads and provides many handy functions to use them.
.. note:: If using other languages (C#, C++), it may be easier to use the threading classes they support.
Creating a Thread
------------------
Creating a thread is very simple, just use the following code:
.. tabs::
.. code-tab:: gdscript GDScript
var thread = null
# The thread will start here
func _ready():
thread = Thread.new()
thread.start(self,"_thread_function","Wafflecopter")
# Run here and exit
func _thread_function(userdata):
print("I'm a thread! Userdata is: ",userdata)
# Thread must be disposed (or "Joined"), for portability
func _exit_tree():
thread.wait_to_finish()
Your function will, then, run in a separate thread until it returns.
Even if the function has returned already, the thread must collect it, so call :ref:`Thread.wait_to_finish()<class_Thread_method_wait_to_finish>`, which will wait until the thread is done (if not done yet), then properly dispose of it.
Mutexes
-------
Accessing objects or data from multiple threads is not always supported (if you do it, it will cause unexpected behaviors or crashes). Read the :ref:`Thread Safe APIs<doc_thread_safe_apis>` to understand which engine APIs support multiple thread access.
When processing your own data or calling your own functions, as a rule, try to avoid accessing the same data directly from different threads. You may run into synchronization problems, as the data is not allways updated between CPU cores when modified. Always use a :ref:`Mutex<class_Mutex>` when accessing a piece of data from different threads.
Here is an example of using a mutex:
.. tabs::
.. code-tab:: gdscript GDScript
var counter = 0
var mutex = null
var thread = null
# The thread will start here
func _ready():
mutex = Mutex.new()
thread = Thread.new()
thread.start(self,"_thread_function")
#increase value, protect it with mutex
mutex.lock()
counter+=1
mutex.unlock()
# Increment the value from the thread, too
func _thread_function(userdata):
mutex.lock()
counter+=1
mutex.unlock()
# Thread must be disposed (or "Joined"), for portability
func _exit_tree():
thread.wait_to_finish()
print("Counter is: ",counter) # Should be 2
Semaphores
-----------
Sometimes you want your thread to work *"On Demand"*. In other words, tell it when to work and let it suspend when it isn't doing anything.
For this *:ref:`Semaphores<class_Semaphore>`* are used. The function :ref:`Semaphore.wait()<class_Semaphore_method_wait>` is used in the thread to suspend it until some data arrives.
The main thread, instead, uses :ref:`Semaphore.post()<class_Semaphore_method_post>` to signal that data is ready to be processed:
.. tabs::
.. code-tab:: gdscript GDScript
var counter = 0
var mutex = null
var semaphore = null
var thread = null
var exit_thread = false
# The thread will start here
func _ready():
mutex = Mutex.new()
semaphore = Semaphore.new()
exit_thread=false
thread = Thread.new()
thread.start(self,"_thread_function")
func _thread_function(userdata):
while(true):
semaphore.wait() # wait until posted
mutex.lock()
var should_exit = exit_thread # protect with mutex
mutex.unlock()
if (should_exit):
break
mutex.lock()
counter+=1 # increment counter, protect with mutex
mutex.unlock()
func increment_counter():
semaphore.post() # Make the thread process
func get_counter():
mutex.lock()
# copy counter, protect with mutex
var counter_value = counter
mutex.unlock()
return counter_value
# Thread must be disposed (or "Joined"), for portability
func _exit_tree():
# Set exit condition to true
mutex.lock()
exit_thread = true # protect with mutex
mutex.unlock()
# unblock by posting
semaphore.post()
# wait until it exits
thread.wait_to_finish()
# Print the counter
print("Counter is: ",counter)