godotengine / godot

Godot Engine – Multi-platform 2D and 3D game engine
https://godotengine.org
MIT License
90.29k stars 21.05k forks source link

Scene containing CollisionShape2D throws errors when added to SceneTree asynchronically #19465

Open Fido789 opened 6 years ago

Fido789 commented 6 years ago

Godot version: Godot_v3.0.3-rc3_mono_win64

OS/device including version: Windows 10

Issue description: Scene containing CollisionShape2D throws errors when added to SceneTree asynchronically.

0:00:06:0396 - Index p_index=0 out of size (shapes.size()=0)
----------
Type:Error
Description: 
Time: 0:00:06:0396
C Error: Index p_index=0 out of size (shapes.size()=0)
C Source: servers/physics_2d/collision_object_2d_sw.cpp:68
C Function: set_shape_transform

0:00:06:0403 - Index p_shape_idx=0 out of size (body->get_shape_count()=0)
----------
Type:Error
Description: 
Time: 0:00:06:0403
C Error: Index p_shape_idx=0 out of size (body->get_shape_count()=0)
C Source: servers/physics_2d/physics_2d_server_sw.cpp:701
C Function: body_set_shape_disabled

0:00:06:0411 - Index p_shape_idx=0 out of size (body->get_shape_count()=0)
----------
Type:Error
Description: 
Time: 0:00:06:0411
C Error: Index p_shape_idx=0 out of size (body->get_shape_count()=0)
C Source: servers/physics_2d/physics_2d_server_sw.cpp:710
C Function: body_set_shape_as_one_way_collision

When added synchronically or when added asycnchronically, but without CollisionShape2D node, everything works as expected.

Steps to reproduce: Create a Loader scene with with single node containing the following code:

using System;
using System.Threading.Tasks;

using Godot;

public class Loader : Node
{
    public override void _Ready()
    {
        // Doesn't work
        this.LoadSceneAsync();

        // Works
        //this.LoadScene();
    }

    private async void LoadSceneAsync()
    {
        await Task.Run((Action)this.LoadScene);
    }

    private void LoadScene()
    {
        var scene = (PackedScene)GD.Load("res://Hero.tscn");
        var node = scene.Instance();
        this.CallDeferred(nameof(this.AddNode), node);
    }

    private void AddNode(Node node)
    {
        this.GetTree().GetRoot().AddChild(node);
    }
}

Create another scene res://Hero.tscn with KinematicBody2D as a root node and two child nodes Sprite and CollisionShape2D.

Minimal reproduction project: Test.zip

eon-s commented 6 years ago

Adding nodes to the tree needs to be done on the main thread.

Looks like a duplicate of #17674.

puthre commented 5 years ago

Had the same issue today


extends Node2D

var loader_thread
func testcol(data):
    for x in range(10):
        var a = Area2D.new()
        var cs = CollisionShape2D.new()
        var rect = RectangleShape2D.new()
        var m = MeshInstance2D.new()
        rect.extents = Vector2(100,100)
        cs.shape = rect

        a.add_child(m)

        a.set_process(true)
        a.add_child(cs)
        #add_child(a)
        call_deferred("add_child",a)

func _ready():
    loader_thread = Thread.new()
    loader_thread.start(self,"testcol")

# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
    pass

Isn't call_deferred supposed to queue the function on the main thread? The strange thing is that it works fine if I call it FROM the thread and it throws errors if I defer it

T4g1 commented 4 years ago

Workaround: You can export the collision shapes you want to use and assign them in the _ready function of the node you are trying to add with the deferred add_child.

aaronfranke commented 4 years ago

I think this should be documented as a known limitation, I don't think this is supported.

Yogoda commented 4 years ago

I have the same issue with my zone loading system, and I have the same question as @puthre, I use call_deferred to add the child nodes, so it should add it in the main thread.

What is weird is that I have this issue only if the zone is loaded at startup, it works without error if the zone is loaded afterwards. Also in 3D I don't have this issue at all.

Yogoda commented 4 years ago

I fixed the issue using a workaround:

get_tree().create_timer(0.0).connect("timeout", self, "attach_zone", [zone_id, instance])

creating a timer in the tree to trigger the zone attachment in the main thread, I don't know if there is a better way to solve this issue, weirdly call_deferred doesn't seem to work.

yikescloud commented 2 years ago

Seems like the physics server compute something before it add to the tree, and then throw the error. I use this code just before add level to tree to wait physics done, and it solves the problem.

yield(get_tree(), "physics_frame")

Hanprogramer commented 1 year ago

Here's a workaround for C#: world.Call("add_child", bullet); This somehow works while calling AddChild directly doesn't.