Search code examples
serializationgodotgdscript

Godot/Gdscript: Why is str2var not working on classes with _init method?


Let's define 2 classes

A.gd

class_name A

var v = null

func _init(v_):
    v = v_

B.gd

class_name B

var v = null

Now, when I try to use str2var/var2str, this is what I get

var a = A.new("aaa")
var b = B.new()
b.v = "bbb"

printt("var2str(a):", var2str(a))
printt("var2str(b):", var2str(b))

printt ("var2str(str2var(var2str(a))):", var2str(str2var(var2str(a))))
printt ("var2str(str2var(var2str(b))):", var2str(str2var(var2str(b))))

Output:

var2str(a): Object(Reference,"script":Resource( "res://Scripts/AI/A.gd"),"v":"aaa")

var2str(b): Object(Reference,"script":Resource( "res://Scripts/AI/B.gd"),"v":"bbb")

var2str(str2var(var2str(a))):   Object(Reference,"script":null)

var2str(str2var(var2str(b))):   Object(Reference,"script":Resource( "res://Scripts/AI/B.gd"),"v":"bbb")

Why is str2var(a) not working?

How should I fix it?


Solution

  • The Solution

    Fix it by making the parameter optional, for example:

    class_name A
    
    var v = null
    
    func _init(v_ = null):
        v = v_
    

    With that there is no error. I get this output:

    var2str(a): Object(Reference,"script":Resource( "res://A.gd"),"v":"aaa")
    
    var2str(b): Object(Reference,"script":Resource( "res://B.gd"),"v":"bbb")
    
    var2str(str2var(var2str(a))):   Object(Reference,"script":Resource( "res://A.gd"),"v":"aaa")
    
    var2str(str2var(var2str(b))):   Object(Reference,"script":Resource( "res://B.gd"),"v":"bbb")
    

    The problem

    For abstract, str2var will not pass any arguments to _init. It would not know what to pass anyway.

    The rest of the answer is the process of confirming that str2var will result in calling _init with no argument.


    When I try your code I get this error:

    E 0:00:00.630   _create_instance: Condition "r_error.error != Variant::CallError::CALL_OK" is true. Returned: __null
      <C++ Source>  modules/gdscript/gdscript.cpp:121 @ _create_instance()
      <Stack Trace> main.gd:12 @ _ready()
    

    We can find the line that throws the error in _create_instance by looking at the source.

    Sadly that does not give me much information. So, I decided to search how str2var is implemented.

    We find it inside GDScriptFunctions::call, here. Which calls VariantParser::parse, which calls VariantParser::parse_value. We are interested in the case of an "Object" (here). And that results in a call to ClassDB::instance(type). There type will be "Reference", and then it procedes to set all properties as they come. Being the first one "script":Resource("res://A.gd").

    When we set the script (here), it will result in a call to GDScript::instance_create. Which calls GDScript::_create_instance (here):

    return _create_instance(NULL, 0, p_this, Object::cast_to<Reference>(p_this) != NULL, unchecked_error)
    

    With no argument for _init (The NULL is the argument array, and 0 is the number of arguments). This is the signature for GDScript::_create_instance:

    GDScriptInstance *GDScript::_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_isref, Variant::CallError &r_error)
    

    Of course, initializer->call(instance, p_args, p_argcount, r_error); fails, because _init requires an argument. And we find the line that throws the error further down. Note: initializer is created while parsing the script.