Godot Version
v4.5.1.stable.mono.arch_linux
Question
I’m trying to avoid all of the callback hell that so many languages have had before C# introduced async/await to the world (thank you Anders). I’m still learning Godot but I’m a very experienced C# developer (15+ years), and right away when following some of the tutorials out there I noticed that there was a lots of callback soup going on.
So I’m trying to avoid that by using TaskCompletionSources and returning tasks that I can manually resolve with a single level callback. I’ll give an example below:
public Task Shake(Node2D? obj, float strength, float durationSeconds = 0.8f)
{
if (obj == null)
{
return Task.CompletedTask;
}
TaskCompletionSource tcs = new();
var originalPosition = obj.Position;
var shakeCount = 10;
var tween = obj.CreateTween();
// Do some stuff with the tween to shake the object here
tween.Finished += () =>
{
obj.Position = originalPosition;
tcs.SetResult(); // Once the tween is done, complete the task so we can pick back up in calling code
};
return tcs.Task; // Give back the task so the calling code can await it
}
With this, I should, in theory, be able to call this with an await and have whatever code comes next happen afterward. For example..
public async Task TakeDamage(int damage)
{
Sprite2D.Material = WhiteSpriteMaterial; // Turn the sprite white
Stats.TakeDamage(damage); // Apply the damage
await Shaker.Instance.Shake(this, 16, 0.15f); // Shake, and wait for it to complete..
Sprite2D.Material = null; // Then put the sprite back to normal
}
However I’m running into the wonderful issues of trying to call Godot APIs from other threads. My understanding of this code, though, is that none of it should run on any other threads - I’m not calling .ConfigureAwait(false) so I should end up back on the same thread and context, the code in the actual Shake call gets called synchronously, immediately, and then when the callback inside there (tween.Finished) happens, it should get called on the main thread as well. Clearly I’m missing something though. (Everything works but my debug window lights up with lots of messages)
Has anyone had any success in wrapping the callback soup APIs with async/await APIs?
Some of the messages from debug window:
E 0:01:41:970 NativeCalls.cs:495 @ void Godot.NativeCalls.godot_icall_1_56(nint, nint, nint): Caller thread can't call this function in this node (/root/Battle/Sprite2D). Use call_deferred() or call_thread_group() instead.
<C++ Error> Condition "!is_accessible_from_caller_thread()" is true.
<C++ Source> scene/main/canvas_item.cpp:1170 @ set_material()
<Stack Trace> NativeCalls.cs:495 @ void Godot.NativeCalls.godot_icall_1_56(nint, nint, nint)
CanvasItem.cs:1239 @ void Godot.CanvasItem.SetMaterial(Godot.Material)
CanvasItem.cs:348 @ void Godot.CanvasItem.set_Material(Godot.Material)