Search code examples
godotgdscript

Should the variable value be checked before assigning?


I know this might sound like a silly question but I'm curious should I check my variable value before assigning?

like for example if I'm flipping my skin (Node2D composed of sprite & raycast) based on direction (Vector2) :

func _process(delta):
    ...

    if(direction.x>0):
        skin.scale.x=1
    elif(direction.x<0):
        skin.scale.x=-1
  
   #OR


    if(direction.x>0):
        if(skin.scale.x!=1):
           skin.scale.x=1
    elif(direction.x<0):
        if(skin.scale.x!=-1):
           skin.scale.x=-1

would the skin scale be altered every _process hence consuming more CPU usage
OR
if the value is same will it be ignored?


Solution

  • First of all, given that this is GDScript, so the number of lines will be a performance factor.

    We will look at the C++ side…


    But before that… Be aware that GDScript does some trickery with properties.

    When you say skin.scale Godot will call get_scale on the skin object, which returns a Vector2. And Vector2 is a value type. That Vector2 is not the scale that the object has, but a copy, an snapshot of the value. So, in virtually any other language skin.scale.x=1 is modifying the Vector2 and would have no effect on the scale of the object. Meaning that you should do this:

    skin.scale = Vector2(skin.scale.x + 1, skin.scale.y)
    

    Or this:

    var skin_scale = skin.scale
    skin_scale.x += 1
    skin.scale = skin_scale
    

    Which I bet people using C# would find familiar.

    But you don't need to do that in GDScript. Godot will call set_scale, which is what most people expect. It is a feature!


    So, you set scale, and Godot will call set_scale:

    void Node2D::set_scale(const Size2 &p_scale) {
        if (_xform_dirty) {
            ((Node2D *)this)->_update_xform_values();
        }
        _scale = p_scale;
        // Avoid having 0 scale values, can lead to errors in physics and rendering.
        if (Math::is_zero_approx(_scale.x)) {
            _scale.x = CMP_EPSILON;
        }
        if (Math::is_zero_approx(_scale.y)) {
            _scale.y = CMP_EPSILON;
        }
        _update_transform();
        _change_notify("scale");
    }
    

    The method _change_notify only does something in the editor. It is the Godot 3.x instrumentation for undo/redo et.al.

    And set_scale will call _update_transform:

    void Node2D::_update_transform() {
        _mat.set_rotation_and_scale(angle, _scale);
        _mat.elements[2] = pos;
    
        VisualServer::get_singleton()->canvas_item_set_transform(get_canvas_item(), _mat);
    
        if (!is_inside_tree()) {
            return;
        }
    
        _notify_transform();
    }
    

    Which, as you can see, will update the Transform2D of the Node2D (_mat). Then it is off to the VisualServer.

    And then to _notify_transform. Which is what propagates the change in the scene tree. It is also what calls notification(NOTIFICATION_LOCAL_TRANSFORM_CHANGED) if you have enabled it with set_notify_transform. It looks like this (this is from "canvas_item.h"):

        _FORCE_INLINE_ void _notify_transform() {
            if (!is_inside_tree()) {
                return;
            }
            _notify_transform(this);
            if (!block_transform_notify && notify_local_transform) {
                notification(NOTIFICATION_LOCAL_TRANSFORM_CHANGED);
            }
        }
    

    And you can see it delegates to another _notify_transform that looks like this (this is from "canvas_item.cpp"):

    void CanvasItem::_notify_transform(CanvasItem *p_node) {
        /* This check exists to avoid re-propagating the transform
         * notification down the tree on dirty nodes. It provides
         * optimization by avoiding redundancy (nodes are dirty, will get the
         * notification anyway).
         */
    
        if (/*p_node->xform_change.in_list() &&*/ p_node->global_invalid) {
            return; //nothing to do
        }
    
        p_node->global_invalid = true;
    
        if (p_node->notify_transform && !p_node->xform_change.in_list()) {
            if (!p_node->block_transform_notify) {
                if (p_node->is_inside_tree()) {
                    get_tree()->xform_change_list.add(&p_node->xform_change);
                }
            }
        }
    
        for (CanvasItem *ci : p_node->children_items) {
            if (ci->top_level) {
                continue;
            }
            _notify_transform(ci);
        }
    }
    

    So, no. There is no check to ignore the change if the value is the same.

    However, it is worth noting that Godot invalidates the global transform instead of computing it right away (global_invalid). This is does not make multiple updates to the transform in the same frame free, but it makes them cheaper than otherwise.


    I also remind you that looking at the source code is no replacement for using a profiler.

    Should you check? Perhaps… If there are many children that need to be updated the extra lines are likely cheap enough. If in doubt: measure with a profiler.