Optimization using Servers

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 Scene System. Using nodes and resources simplifies project organization and asset management in complex games.

There are, of course, always drawbacks:

  • There is an extra layer of complexity

  • Performance is lower than using simple APIs directly

  • It is not possible to use multiple threads to control them

  • Se necesita más memoria.

En muchos casos, esto no es realmente un problema (Godot está muy optimizado y la mayoría de las operaciones se manejan con señales, por lo que no se requiere sondeo). Aún así, a veces puede serlo. Por ejemplo, lidiar con decenas de miles de instancias de algo que debe procesarse en cada fotograma puede ser un cuello de botella.

Este tipo de situación hace que los programadores se arrepientan de estar usando un motor de juego y deseen poder volver a una implementación más artesanal y de bajo nivel del código del juego.

Aún así, Godot está diseñado para solucionar este problema.

Servidores

One of the most interesting design decisions for Godot, is the fact that the whole scene system is optional. While it is not currently possible to compile it out, it can be completely bypassed.

En esencia, Godot usa el concepto de servidores. Son APIs de muy bajo nivel para controlar el renderizado, la física, el sonido, etc. El sistema de escenas se construye sobre ellas y las usa directamente. Los servidores más comunes son:

Simplemente explore sus APIs y se dará cuenta de que todas las funciones proporcionadas son implementaciones de bajo nivel de todo lo que Godot le permite hacer.

RIDs

The key to using servers is understanding Resource ID (RID) objects. These are opaque handles to the server implementation. They are allocated and freed manually. Almost every function in the servers requires RIDs to access the actual resource.

Most Godot nodes and resources contain these RIDs from the servers internally, and they can be obtained with different functions. In fact, anything that inherits Resource can be directly casted to an RID (not all resources contain an RID, though, in such cases the RID will be empty). In fact, resources can be passed to server APIs as RIDs. Just make sure to keep references to the resources outside the server, because if the resource is erased, the internal RID is erased too.

Para los nodos, hay muchas funciones disponibles:

Just explore the nodes and resources you are familiar with and find the functions to obtain the server RIDs.

No se recomienda controlar los RIDs de objetos que ya tienen un nodo asociado. En cambio, las funciones del servidor siempre deben usarse para crear y controlar nuevas e interactuar con las existentes.

Creando un sprite

This is a simple example of how to create a sprite from code and move it using the low-level CanvasItem API.

extends Node2D


# VisualServer expects references to be kept around.
var texture


func _ready():
    # Create a canvas item, child of this node.
    var ci_rid = VisualServer.canvas_item_create()
    # Make this node the parent.
    VisualServer.canvas_item_set_parent(ci_rid, get_canvas_item())
    # Draw a texture on it.
    # Remember, keep this reference.
    texture = load("res://my_texture.png")
    # Add it, centered.
    VisualServer.canvas_item_add_texture_rect(ci_rid, Rect2(texture.get_size() / 2, texture.get_size()), texture)
    # Add the item, rotated 45 degrees and translated.
    var xform = Transform2D().rotated(deg2rad(45)).translated(Vector2(20, 30))
    VisualServer.canvas_item_set_transform(ci_rid, xform)

The Canvas Item API in the server allows you to add draw primitives to it. Once added, they can't be modified. The Item needs to be cleared and the primitives re-added (this is not the case for setting the transform, which can be done as many times as desired).

Los primitivos se eliminan de esta manera:

VisualServer.canvas_item_clear(ci_rid)

Instantiating a Mesh into 3D space

Las API 3D son diferentes de las 2D, por lo que se debe utilizar la API de instanciación.

extends Spatial


# VisualServer expects references to be kept around.
var mesh


func _ready():
    # Create a visual instance (for 3D).
    var instance = VisualServer.instance_create()
    # Set the scenario from the world, this ensures it
    # appears with the same objects as the scene.
    var scenario = get_world().scenario
    VisualServer.instance_set_scenario(instance, scenario)
    # Add a mesh to it.
    # Remember, keep the reference.
    mesh = load("res://mymesh.obj")
    VisualServer.instance_set_base(instance, mesh)
    # Move the mesh around.
    var xform = Transform(Basis(), Vector3(20, 100, 0))
    VisualServer.instance_set_transform(instance, xform)

Creating a 2D RigidBody and moving a sprite with it

Esto crea un RigidBody2D usando el API de Physics2DServer, y mueve un CanvasItem cuando se mueve el cuerpo.

# Physics2DServer expects references to be kept around.
var body
var shape


func _body_moved(state, index):
    # Created your own canvas item, use it here.
    VisualServer.canvas_item_set_transform(canvas_item, state.transform)


func _ready():
    # Create the body.
    body = Physics2DServer.body_create()
    Physics2DServer.body_set_mode(body, Physics2DServer.BODY_MODE_RIGID)
    # Add a shape.
    shape = RectangleShape2D.new()
    shape.extents = Vector2(10, 10)
    # Make sure to keep the shape reference!
    Physics2DServer.body_add_shape(body, shape)
    # Set space, so it collides in the same space as current scene.
    Physics2DServer.body_set_space(body, get_world_2d().space)
    # Move initial position.
    Physics2DServer.body_set_state(body, Physics2DServer.BODY_STATE_TRANSFORM, Transform2D(0, Vector2(10, 20)))
    # Add the transform callback, when body moves
    # The last parameter is optional, can be used as index
    # 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 (using RigidBody and PhysicsServer respectively).

Obteniendo datos de servidores

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 call them every frame (and it won't be obvious why).

Because of this, most APIs in such servers are designed so it's not even possible to request information back, until it's actual data that can be saved.