+8 votes

I had a problem earlier in Godot, wherein it was taking me several ms (almost 10!) to instance a simple scene. I eventually discovered the root cause: instancing nodes from a thread seemed to incur a serious performance penalty. Using my network client as an example:

const MESSAGE_RECEIVED = "message_received"
var thread = Thread.new()

func start():
  thread.start(self, '_run', null)

func _run():
 while true:
   var msg = _block_while_waiting_for_tcp_message()
   emit_signal(MESSAGE_RECEIVED, msg)

Code that connected to MESSAGE_RECEIVED and instanced a scene based on the data in the message took anywhere from 10-30ms to instance an object. Instancing 500 scenes like this took about 11 seconds.

However, changing this line:
emit_signal(MESSAGE_RECEIVED, msg)
to this:
call_deferred("emit_signal", MESSAGE_RECEIVED, msg)
gave me the expected result, and was able to render all 500 scenes within 100ms.

My assumption is that calling PackedScene.instance() inside of a thread incurs a context switch (background thread -> main thread) before the scene is instanced.

call_deferred on the other hand might simply perform that context switch once, then process all of its messages, which would explain the great improvement performance-wise. Docs are a little scare on the semantics of all the function calls involved, though.

Does anyone know if my understanding of call_deferred is correct?

in Engine by (34 points)

1 Answer

+12 votes
Best answer

I believe call_deferred pushes the message and its data in a queue that is dequeued in the main thread, involving only one mutex lock.
Using emit_signal directly from the thread has the risk of messing with the main thread, and I think Godot is locking too much concurrent mutexes to instance the scene in that case, involving a huge performance loss.

However I can't find such explanation in the doc, so I can be wrong.

Another usage of call_deferred is to postpone the call of a function instead of executing it immediately, so for example you can use it in _ready to create more child nodes (because at this point the engine is locking the tree).

by (27,823 points)
edited by

After browsing through the source for quite a bit, looks like this is more or less the situation.

Welcome to Godot Engine Q&A, where you can ask questions and receive answers from other members of the community.

Please make sure to read How to use this Q&A? before posting your first questions.
Social login is currently unavailable. If you've previously logged in with a Facebook or GitHub account, use the I forgot my password link in the login box to set a password for your account. If you still can't access your account, send an email to webmaster@godotengine.org with your username.