Skip to content

Nested Exported Resource requires experimental-threads feature #610

Open
@StatisMike

Description

@StatisMike

The issue was originally posted in the discord thread and some initial testing was executed. Below I will share the original question and initiali findings. Issue is similiar to #597 as it invokes the same multithreading problem with Resource handling of Godot, but contrary to the former, there is no custom ResourceFormatLoader there, only custom Resource (so it is saved as .tres and .res formats) - for this reason, I believe it is worthy to have this as a separate Issue here.

Example and initial data:

struct Test {}

#[gdextension]
unsafe impl ExtensionLibrary for Test {}

#[derive(GodotClass)]
#[class(base=Node)]
struct TestNode {
    #[export]
    nested_resources: Gd<ParentResource>,
}

#[godot_api]
impl INode for TestNode {
    fn init(_base: Base<Node>) -> Self {
        Self {
            nested_resources: Gd::from_init_fn(ParentResource::init),
        }
    }
}

#[derive(GodotClass)]
#[class(base=Resource)]
struct ParentResource {
    base: Base<Resource>,
    #[export]
    sub_resource: Gd<SubResource>,
}

#[godot_api]
impl IResource for ParentResource {
    fn init(base: Base<Resource>) -> Self {
        Self {
            base,
            sub_resource: Gd::from_init_fn(SubResource::init),
        }
    }
}

#[derive(GodotClass)]
#[class(base=Resource)]
struct SubResource {
    #[export]
    value: i32,
    base: Base<Resource>,
}

#[godot_api]
impl IResource for SubResource {
    fn init(base: Base<Resource>) -> Self {
        Self { value: 50, base }
    }
}

Above code panicked with stack trace:

<unnamed>' panicked at ~/.cargo/git/checkouts/gdext-76630c89719e160c/b4a91a6/godot-core/src/lib.rs:160:43:
no panic info available
stack backtrace:
   0: rust_begin_unwind
             at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/std/src/panicking.rs:645:5
   1: core::panicking::panic_fmt
             at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/core/src/panicking.rs:72:14
   2: core::panicking::panic_display
             at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/core/src/panicking.rs:196:5
   3: core::panicking::panic_str
             at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/core/src/panicking.rs:171:5
   4: core::option::expect_failed
             at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/core/src/option.rs:1980:5
   5: core::option::Option<T>::expect
             at /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/core/src/option.rs:894:21
   6: godot_core::private::handle_panic
             at ~/.cargo/git/checkouts/gdext-76630c89719e160c/b4a91a6/godot-core/src/lib.rs:160:28
   7: <rust::ParentResource as godot_core::obj::traits::cap::ImplementsGodotExports>::__register_exports::function
             at ./rust/src/lib.rs:24:10
   8: <unknown>
   9: <unknown>
  10: <unknown>
  11: <unknown>
  12: <unknown>
  13: <unknown>
  14: <unknown>
  15: <unknown>
  16: <unknown>
  17: <unknown>
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
fatal runtime error: failed to initiate panic, error 5

With error message introduced in #581

assertion `left == right` failed: attempted to access binding from different thread than main thread; this is UB - use the "experimental-threads" feature.
  left: ThreadId(1)
right: ThreadId(2)

Removing the #[export] annotation from ParentResource::sub_resource field stop the error from happening, but also removes the possibility of assigning SubResource in the Godot Editor AND saving the ParentResource with SubResource data, either as Bundled or External Resource.

Initial hypothesis and findings

My initial hypothesis was that the user code was in some way callled by Godot Editor, similiar to mentioned ResourceFormatLoader issue. To check it, the simple thread printing function was added to init function of all structs:

pub(crate) fn print_thread(class: &str, method: &str) {
    let thread_id = Os::singleton().get_thread_caller_id();

    godot_print!("Thread: {thread_id}: {class}::{method}");
}

#[godot_api]
impl IResource for SubResource {
    fn init(base: Base<Resource>) -> Self {
        print_thread("SubResource", "init");
        Self { value: 50, base }
    }
}

The thread number that were always printed was 1 though, contrary to mentioned issue, so it seems that the secondary thread was used within no user defined code (at least not explicitly).

Additionally, the same nested export situation was tested with Node-inheriting classes instead of Resource. Panic wasn't happening, so it should be specific to Resources.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugc: engineGodot classes (nodes, resources, ...)c: threadsRelated to multithreading in Godot

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions