Skip to content

Introduce convenient, type-safe call_deferred alternative #1204

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

goatfryed
Copy link

@goatfryed goatfryed commented Jun 15, 2025

apply_deferred

Implements #1199

I'm note entirely sold on my api design here, but I guess test and partial implementation are a good next step to explore the desired outcome. See comments in code

adds test for call_deferred

This seems like a cyclic test expectation, because async_test uses call_deferred internally and now call_deferred test uses async_test internally, but it was the first best idea i came up with, that wouldn't require changes to the test engine and allows me to await the idle time of the current frame.

todo

  • Finalize signature
  • Expose the same signature on GodotClass. I'm getting familiar with the code base
  • Improve docs. Thankful for any pointers what I should update, once this is finalized

Depending on the discussion of the two loosely coupled topics here, feel free to cherry pick the test improvements or let just ask me to create a dedicated PR for that.

Copy link
Member

@Bromeon Bromeon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for your contribution!

Some first comments:

  • You can mark the PR as draft without needing to change the title. This also prevents accidental merging.
  • You can do a first attempt of running CI locally with check.sh. See book.
  • I don't see the point of try_apply_deferred 🙂 any errors can't be returned to the user, so you might as well just have apply_deferred returning a generic return type R.
  • Regarding formatting of RustDoc comments, please check how other code does it (line length, header + body separation, etc.)

@Bromeon Bromeon added feature Adds functionality to the library c: core Core components c: engine Godot classes (nodes, resources, ...) and removed c: core Core components labels Jun 15, 2025
@Bromeon Bromeon linked an issue Jun 15, 2025 that may be closed by this pull request
@goatfryed goatfryed marked this pull request as draft June 15, 2025 18:30
@goatfryed goatfryed changed the title DRAFT: Introduce convenient, type-safe call_deferred alternative Introduce convenient, type-safe call_deferred alternative Jun 15, 2025
@GodotRust
Copy link

API docs are being generated and will be shortly available at: https://godot-rust.github.io/docs/gdext/pr-1204

@goatfryed
Copy link
Author

I don't see the point of try_apply_deferred 🙂 any errors can't be returned to the user, so you might as well just have apply_deferred returning a generic return type R.

No reason, just unfamiliarty, so I mirrored Callable::from_local_fn closely at first. Great, than let's drop that and keep just the simple version.

You can do a first attempt of running CI locally with check.sh. See book.

I understand that godot-itest 4.1 compat could break, but I'm surprised that local ./check.sh doesn't catch doc-lint issues. I assume I just need to use the linked type symbol to fix this?

image

@goatfryed goatfryed force-pushed the feat/1199-type-safe-call-deferred branch from 67c8ea7 to 944805d Compare June 15, 2025 18:57
@goatfryed
Copy link
Author

I need help with the doc-lint issue :) I don't understand why "it works on my machine" (🫡)...
I see that L289 references Object as well and there it works.

@goatfryed goatfryed force-pushed the feat/1199-type-safe-call-deferred branch from 944805d to b20806a Compare June 15, 2025 19:05
@Bromeon
Copy link
Member

Bromeon commented Jun 15, 2025

but I'm surprised that local ./check.sh doesn't catch doc-lint issues.
[...]
I need help with the doc-lint issue :) I don't understand why "it works on my machine" (🫡)...

That book page states:

The script check.sh in the project root can be used to mimic a minimal version of CI locally.

[...]

If you want to have the full CI experience, you can experiment as much as you like on your own gdext fork, before submitting a pull request.

In other words, check.sh is just a first sanity check. The authority on correctness is the GitHub Actions pipeline. And you can enable GitHub Actions on your repo fork to see the CI status, it's all described on that page 😉

But in your case it's not just notify-docs. The compatibility integration test for Godot 4.1 also fails, because signals() isn't available there. You need to #[cfg(since_api = "4.2")] your test/module out if you don't want it to run under 4.1, see other tests.

@goatfryed goatfryed force-pushed the feat/1199-type-safe-call-deferred branch 2 times, most recently from 3be4a2b to 613b2df Compare June 15, 2025 19:38
@goatfryed
Copy link
Author

Fixed the PR. I guess, to enable the pipeline on my fork is a bit late since I already created the PR. Sorry for triggering your builds so frequently.

I'm still curious why Object::get_class() is fine for L289, but well, I guess i can just qualify my link. Should i qualify L289 as well while I'm at it?

I was a bit surprised that doc lint uses stricter pipeline rules tbh. I read the section that you linked, but I unconsiously assumed, that linting wouldn't be affected. My bad.

@Bromeon
Copy link
Member

Bromeon commented Jun 15, 2025

I'm still curious why Object::get_class() is fine for L289, but well, I guess i can just qualify my link. Should i qualify L289 as well while I'm at it?

I just checked, it's also wrong. The lint probably misses it because Gd::dynamic_class_string() is a private method.

Thanks for bringing it up -- I can fix it, currently merging with some other doc issues that came up 🙂

@goatfryed goatfryed force-pushed the feat/1199-type-safe-call-deferred branch 2 times, most recently from c043bed to f67b834 Compare June 15, 2025 21:08
@Houtamelo
Copy link
Contributor

Thanks a lot for your contribution!

Some first comments:

* You can mark the PR as draft without needing to change the title. This also prevents accidental merging.

* You can do a first attempt of running CI locally with `check.sh`. See [book](https://godot-rust.github.io/book/contribute/dev-tools.html).

* I don't see the point of `try_apply_deferred` 🙂 any errors can't be returned to the user, so you might as well just have `apply_deferred` returning a generic return type `R`.

* Regarding formatting of RustDoc comments, please check how other code does it (line length, header + body separation, etc.)

What is the point of returning a generic type R when the return value of the function is always discarded? Why not just return () (nothing) ?

@Bromeon
Copy link
Member

Bromeon commented Jun 16, 2025

I meant the closure, not apply_deferred itself. It would allow binding functions that have a return value (which is discarded).

@Houtamelo
Copy link
Contributor

I meant the closure, not apply_deferred itself. It would allow binding functions that have a return value (which is discarded).

Why we would want to bind such function? Wouldn't it cause confusion instead of providing any benefit?

@Bromeon
Copy link
Member

Bromeon commented Jun 16, 2025

I was thinking that some methods perform an action and only return an informational return value.

But maybe you're right, let's start with () only -- after all, it's a non-breaking change to extend this in the future 👍

/// Runs the given Closure deferred.
///
/// This can be a type-safe alternative to [`classes::Object::call_deferred`], but does not handle dynamic dispatch, unless explicitly used.
/// This constructor only allows the callable to be invoked from the same thread as creating it.The closure receives a reference to this object back.
Copy link
Contributor

@Yarwin Yarwin Jun 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This constructor only allows the callable to be invoked from the same thread as creating it.

Note: might not be important in current scope, but call_deferred always calls from the main thread and is recommended way to "sync" thread with a main thread in Godot (Godot itself even uses it few times, look for something along the lines of _thread_function).

https://docs.godotengine.org/en/stable/tutorials/performance/thread_safe_apis.html#scene-tree

If you want to call functions from a thread, the call_deferred function may be used (…)

In other words following code:

    godot_print!("Starting from the main thread… {:?}", std::thread::current().id());
    
    std::thread::spawn(move || {
        godot_print!("Hello from the secondary thread! {:?}", std::thread::current().id());
        let c = Callable::from_sync_fn("Thread thread", |_| {
            godot_print!("hello from the main thread!, {:?}", std::thread::current().id());
            Ok(Variant::nil())
        }); 
        c.call_deferred(&[]);
    }).join().unwrap();

Would print:

Starting from the main thread… ThreadId(1)
Hello from the secondary thread! ThreadId(3)
hello from the main thread!, ThreadId(1)

Now, since Gd<T> is NOT thread safe it is debatable if we want to support such operations 🤷. It would open a whole can of worms for sure. (IMO current implementation with from_local_fn is fine! Gd is not thread safe, thus all the hack/syncs can be done with from_sync_fn/CustomCallable and should be responsibility of the user… and we have proper, better tools for syncing threads like for example the channels. Just wanted to note it).

The closure receives a reference to this object back.

I'm not sure what does it mean 🤔? Wouldn't it better to ensure that closure/callable keeps reference to a given object (i.e. if it is refcounted it won't be pruned before idle time/before said apply deferred is applied)?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should maybe enforce this for now, using this function:

gdext/godot-ffi/src/lib.rs

Lines 434 to 441 in 20cd346

/// Check if the current thread is the main thread.
///
/// # Panics
/// - If it is called before the engine bindings have been initialized.
#[cfg(not(wasm_nothreads))]
pub fn is_main_thread() -> bool {
std::thread::current().id() == main_thread_id()
}

Btw, is_main_thread should have an implementation for #[cfg(wasm_nothreads)] returning true, otherwise clients constantly have to differentiate cases.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, enforcing it is good idea, since

    let mut test_node = DeferredTestNode::new_alloc();
    ctx.scene_tree.clone().add_child(&test_node);
    let hack = test_node.instance_id();
    
    std::thread::spawn(move || {
        let mut obj: Gd<DeferredTestNode> = Gd::from_instance_id(hack);
        obj.apply_deferred(|mut this| this.bind_mut().accept());
    }).join().unwrap();

Will always fail anyway.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The closure receives a reference to this object back.

I'm not sure what does it mean 🤔? Wouldn't it better to ensure that closure/callable keeps reference to a given object (i.e. if it is refcounted it won't be pruned before idle time/before said apply deferred is applied)?

I drop that sentence. I wanted to document, that the callable is actually invoked with slef. Or now after signature
update, the inner self. Should be clear though.


Regarding multi threading

Okay, i was a bit too curious and came up with a new method bind_deferred that returns a closure which is a bit more restricted, but thread safe to call ... i think?
Godot recommends call_deferred for thread safe communication back to the main thread and it seems easier than channels for some cases.

If it is interesting enough, we can add it to this pr, but I guess we shouldn't get side tracked and follow up on this topic in a different one.
Check it out here goatfryed/gdext@feat/1199-type-safe-call-deferred...feat/1199-thread-safe-call-deferred

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it is interesting enough, we can add it to this pr, but I guess we shouldn't get side tracked and follow up on this topic in a different one.

Yeah, I think we shouldn't support it for now 🤔; we should only restrict invocations of apply_deferred to a main thread (and note that in the description to avoid any confusion).

Dealing with cross-thread access to user-defined GodotClass instances has been discussed here: #18. IMO until we deal with this problem we shouldn't encourage any workflows related to multi threading.

Godot recommends call_deferred for thread safe communication back to the main thread and it seems easier than channels for some cases.

Yep, thus the mention – it is something people would look for naturally, so noting down that it is available only on main thread would avoid any confusion (and we provide tools to use this pattern unsafely).

@goatfryed goatfryed force-pushed the feat/1199-type-safe-call-deferred branch 4 times, most recently from 4503c5c to 18f1602 Compare June 19, 2025 11:40
@goatfryed goatfryed force-pushed the feat/1199-type-safe-call-deferred branch 2 times, most recently from 00692f0 to 3e988eb Compare June 19, 2025 11:57
@goatfryed
Copy link
Author

goatfryed commented Jun 19, 2025

I've now introduced apply_deferred on the various types users might want to call it on and thus refactored the code a bit.

I saw three variations

  • call on a GodotClass, e.g. self in some impl
  • call on a Gd where T is a user defined class with a base field
  • call on a Gd where T is an engine defined class

Since the implementation for user and engine defined classes differ only in generic types, I had to introduce two distinct traits. Curious, if there is a better way.

The pipeline fails atm for linux_release https://github.com/goatfryed/gdext/actions/runs/15757419567/job/44415859048
If I interpret it right, it's due to todays godot 4.5 release and some bc in another part of the code base? Please help me, if I caused this issue.

@goatfryed goatfryed requested a review from Bromeon June 19, 2025 12:23
@goatfryed goatfryed marked this pull request as ready for review June 19, 2025 12:24
@Bromeon Bromeon force-pushed the feat/1199-type-safe-call-deferred branch from 87c20ec to bf1c7a2 Compare June 23, 2025 20:31
Copy link
Member

@Bromeon Bromeon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

Since the implementation for user and engine defined classes differ only in generic types, I had to introduce two distinct traits. Curious, if there is a better way.

Could you maybe reuse the ToSignalObj trait here? It serves a very similar purpose. (I noticed this hasn't been officially made part of the public API, even though it's used in public bounds -- an oversight. Maybe a good opportunity to rename before making public...). Or possibly UniformObjectDeref?

The pipeline fails atm for linux_release [...]
If I interpret it right, it's due to todays godot 4.5 release and some bc in another part of the code base?

Yes, I rebased and it passed ✔️

Comment on lines 16 to 23
#[derive(GodotConvert, Var, Export, Clone, PartialEq, Debug)]
#[godot(via = GString)]
enum TestState {
Initial,
Accepted,
VerifyEngineClass,
FailedEngineClass,
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are 4 states really necessary?

Can it not be a boolean that's toggled in a deferred call, and then the test verifies that it's indeed changed? That would also avoid current limitations of enums in typed signals.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we already have the enum, i think it's simpler and cleaner to use it to tell the TestNode to execute case specifiy verifications and ensures proper test setup on the type system. Assuming this won't grow to a lot of cases that also require combinations of varifications.

But the values don't really reflect that. They could more general and more on point.

What do you think about Verify / VerifyWithName / Accepted / Failed ?

Copy link
Member

@Bromeon Bromeon Jun 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we already have the enum, i think it's simpler and cleaner to use it to tell the TestNode to execute case specifiy verifications and ensures proper test setup on the type system.

I'm not following 🤔 I'm sure you have some thoughts about this specific design, but you need to elaborate why it's required for the test. I disagree with "simpler" 🙂

What do you mean with "since we already have the enum"? You add it in this PR. Tests should be as simple as possible, with minimal logic besides the property being tested. Here, we are just interested in two properties:

  • the deferred function is not called immediately
  • the deferred function is called eventually

I don't see why 4 states are needed for this.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But how do you check whether some function was called?

For user defined classes this is easy. We define a function that modifies some state dedicated for the test setup and we are happy.
For Engine Defined classes we need to perform some side effect that is then evaluated on the next frame. I've decided to modify the name and now I need some logic in my TestNode to check this different side effect and a way to trigger this different assertion.
The forth tests needs different test logic. The assertion must be done on the next frame. Yes I introduce the enum in this PR, but if I introduce it, it seems fitting to use it rather than a boolean to switch assertion behavior.

That's why i need multiple test states. If you prefer,, I can replace the Enum with a two booleans or keep the boolean and add one boolean. Personally, this seemed most expressive to me and a good thing to warn you that there are actual multiple test states due to multiple test setups.

Note that the implementation is actually different for engine and user defined classes. Of course this might change with your suggestion to check out UniformObjectDeref. I'm still working on that

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I refactored the test and removed the enum. Should be easier now.

@goatfryed goatfryed force-pushed the feat/1199-type-safe-call-deferred branch from bf1c7a2 to 7f1e1d7 Compare June 24, 2025 19:13
Copy link
Member

@Bromeon Bromeon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the update!

What do you think about reusing existing traits?

Comment on lines 18 to 38
/// This can be a type-safe alternative to [`crate::classes::Object::call_deferred`], but does not handle dynamic dispatch, unless explicitly used.
/// This must be used on the main thread.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean with dynamic dispatch in this context? And how can the user "explicitly use" it? Please be specific.

Copy link
Author

@goatfryed goatfryed Jun 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What i wanted to express: I crate::classes::Object::call_deferred allows that the deferred method can be overwritten by GDScript, because it's dynamically dispatched at runtime, while Gd::apply_deferred method calls dispatch statically unless Gd::callv is used.

I removed the line. This is covered in the rust godot book and should be clear with these kind of methods.

@goatfryed
Copy link
Author

Hey thank you for the quick feedback. I'm still working on it. Maybe i should push a bit less or I should indicate, if it's reviewable again. But I don't mind the fast feedback. Just let me know what is best for you.

I took a look at the existing traits and it seems promising, but I can't make it work yet. I seem to run into reoccuring issues. As I mentioned, I'm learning and this teaches me a lot about Rust type system. So it's exciting. ^^

It seems that the following implementation would work for both engine and user defined classes, but is obviously illegal rust.

impl<T, S, Declarer> DeferredUserCallable<T> for S // Declarer E0207, not bound
where
    T: GodotClass + UniformObjectDeref<Declarer>, // works for both concrete bounds:.DeclEngine, bounds::DeclUser
    S: ToSignalObj<T>,
{
    fn apply_deferred<'a,F>(&mut self, mut rust_function: F)
    where
        F: FnMut(&mut T) + 'static,
    {
        assert!(
            is_main_thread(),
            "apply_deferred must be called on main thread"
        );
        let mut this = self.to_signal_obj().clone();
        let callable = Callable::from_local_fn("apply_deferred", move |_| {
            rust_function(T::object_as_mut(&mut this).deref_mut());
            Ok(Variant::nil())
        });
        callable.call_deferred(&[]);
    }
}

I'm currently looking into associated types to solve this, and don't feel blocked yet. But I wouldn't mind a tip, if i'm overlooking something obvious 😄

@Bromeon
Copy link
Member

Bromeon commented Jun 24, 2025

Hey thank you for the quick feedback. I'm still working on it. Maybe i should push a bit less or I should indicate, if it's reviewable again. But I don't mind the fast feedback. Just let me know what is best for you.

Yes, feel free to switch it to draft mode if you're not ready.
While the PR is in "ready for review" state, I assume it's... ready for review 😉


But I wouldn't mind a tip, if i'm overlooking something obvious 😄

Maybe we can reduce the scope and provide only callbacks with Gd<T>, instead of &mut T and Gd<T>. This would also support cases where user-defined classes don't need to be bind_mut()-ed.

Generally we need to be careful about our API complexity budget. Two new trait only to power this one function is too much in my opinion.

@goatfryed goatfryed marked this pull request as draft June 24, 2025 21:29
@TitanNano
Copy link
Contributor

@goatfryed to get the trait bound to work, you have to define it like this:

impl<T, S, D: Declarer> DeferredUserCallable<T> for S
where
    T: GodotClass<Declarer = D> + UniformObjectDeref<D>,
    S: ToSignalObj<T>

But GodotClass is a super trait of UniformObjectDeref so you can reduce it to this:

impl<T, S, D: Declarer> DeferredUserCallable<T> for S
where
    T: UniformObjectDeref<D, Declarer = D>,
    S: ToSignalObj<T>

@goatfryed goatfryed force-pushed the feat/1199-type-safe-call-deferred branch 5 times, most recently from c7e9af5 to 0b9f295 Compare June 24, 2025 23:19
Copy link
Author

@goatfryed goatfryed left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, now the implementation looks nice and avoids multiple traits. Thank you for your patience and support 🙏 I learned a lot.

  • added the trait to prelude
  • cleaned up some last issues regarding pre 4.1 support stemming from that

Just some observation: For DynGd<C,T> this calls the closure with &C.

I did not rename ToSingnalObject in this, since i had some issues coming up with a good name that doesn't introduce confusion or overlap.
I see some overlap in both naming and signature with WithBaseField and the need for a clear separation from ToGodot.
I'd propose to rename ToSingnalObject to ToGd, rename the method to to_gd() and have WithBaseField extend this trait.
Sounds like an opportunity for another PR

///
/// # Usage
///
/// ```no_compile
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to use a realisitic example here, but Engine classes are not available in core docs

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes they are, see examples like here: https://godot-rust.github.io/docs/gdext/master/godot/obj/struct.Gd.html#method.upcast_ref

Might work better if the types are among the "minimal set" of classes (Node, Resource, RefCounted etc) 🙂

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, for classes included in prelude it works, but import of godot::classes fails. Should I change the example, investigate the issue or is this fine for now?

{
assert!(
is_main_thread(),
"`apply_deferred` must be called on the main thread."
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the same rule regarding comments apply to assert statements as well? I noticed different formats in the code base, which is expected from a growing project. Let me know what the preferred style is and if you'd like me to clean them up.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think for asserts we follow mostly Rust style (lowercase, no period) -- except in case of multiple sentences.

But you're right it's not very consistent right now.

@goatfryed goatfryed marked this pull request as ready for review June 24, 2025 23:54
@Bromeon
Copy link
Member

Bromeon commented Jun 25, 2025

I did not rename ToSingnalObject in this, since i had some issues coming up with a good name that doesn't introduce confusion or overlap.
I see some overlap in both naming and signature with WithBaseField and the need for a clear separation from ToGodot.
I'd propose to rename ToSingnalObject to ToGd, rename the method to to_gd() and have WithBaseField extend this trait.

We can't rename it right now, as we keep compatibility with v0.3. Can you add the following comment?

// TODO(v0.4): find more general name for trait.

Copy link
Member

@Bromeon Bromeon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CI ran into a segfault on macOS before. I wonder if it's related to a change in this PR.

{
assert!(
is_main_thread(),
"`apply_deferred` must be called on the main thread."
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think for asserts we follow mostly Rust style (lowercase, no period) -- except in case of multiple sentences.

But you're right it's not very consistent right now.

///
/// # Usage
///
/// ```no_compile
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes they are, see examples like here: https://godot-rust.github.io/docs/gdext/master/godot/obj/struct.Gd.html#method.upcast_ref

Might work better if the types are among the "minimal set" of classes (Node, Resource, RefCounted etc) 🙂

pub trait WithDeferredCall<T: GodotClass> {
/// Runs the given Closure deferred.
///
/// This can be a type-safe alternative to [`crate::classes::Object::call_deferred`]. This method must be used on the main thread.
Copy link
Member

@Bromeon Bromeon Jun 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// This can be a type-safe alternative to [`crate::classes::Object::call_deferred`]. This method must be used on the main thread.
/// This can be a type-safe alternative to [`Object::call_deferred()`][crate::classes::Object::call_deferred]. This method must be used on the main thread.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this would cause the doc test to fail unless i use Object in the module, which in turn would require me to ignore unused warning. Or am I mistaken? I fully qualified this due to failing doc test

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated to [text][ref] syntax -- this one should work.

@goatfryed
Copy link
Author

CI ran into a segfault on macOS before. I wonder if it's related to a change in this PR.

Hm... i ran a full ci on my repository yesterday evening and mac passed in all 5 architectures. Can you run a test on main to rule out some issue with the godot 4.5 beta, before i start digging deeper?

@Bromeon
Copy link
Member

Bromeon commented Jun 25, 2025

I checked, it's unrelated and happens on other branches too. Looks like Godot broke something 😔

@goatfryed goatfryed force-pushed the feat/1199-type-safe-call-deferred branch from 751770c to 88428b1 Compare June 25, 2025 17:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c: engine Godot classes (nodes, resources, ...) feature Adds functionality to the library
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Type-Safe deferred method calls
6 participants