class_name CallableNode extends Node ## A node that can be called like a function. ## Positional arguments the node was invoked with. var args: Array = [] ## Named arguments ("options") the node was invoked with. var opts: Dictionary = {} ## Node's return value. var result: Variant = null ## Calls the CallableNode, waits for it to finish, and returns its result. ## ## When a CallableNode is called, the following sequence of events occurs: ## 1. we wait for the parent node to be in the tree ## and self to be out of the tree at the same time ## (if this never happens, then the function will never terminate, ## and thus any await subscribers will never be notified ## and will themselves never terminate); ## 2. the CallableNode is passed the given args and opts; ## 3. the CallableNode is requested to call _ready, ## and then added as a child to the parent; ## 4. if the CallableNode's process_mode is PROCESS_MODE_INHERIT ## or PROCESS_MODE_DISABLED, the most descended process_mode ## in its ancestry not to equal either of those values ## is applied directly to the CallableNode instead ## (falling back to PROCESS_MODE_PAUSABLE if none is found); ## 5. the parent's process mode is set to PROCESS_MODE_DISABLED; ## 6. the function yields until the CallableNode is exiting the tree; ## 7. steps 2-5 are undone; ## 8. The CallableNode's result property is returned. func invoke_existing( parent: Node, p_args: Array = [], p_opts: Dictionary = {} ) -> Variant: var prev_args := args var prev_opts := opts var prev_process_mode := process_mode var parent_prev_process_mode := parent.process_mode # step 1 var ok := func() -> bool: return parent.is_inside_tree() and not is_inside_tree() await Wait.until(ok) # step 2 args = p_args opts = p_opts # step 3 request_ready() parent.add_child(self) # step 4 var process_mode_resolve := self while process_mode_resolve and ( process_mode_resolve.process_mode == Node.PROCESS_MODE_INHERIT or process_mode_resolve.process_mode == Node.PROCESS_MODE_DISABLED ): process_mode_resolve = process_mode_resolve.get_parent() if process_mode_resolve: process_mode = process_mode_resolve.process_mode else: process_mode = Node.PROCESS_MODE_PAUSABLE # step 5 parent.process_mode = Node.PROCESS_MODE_DISABLED # step 6 if is_inside_tree(): await tree_exiting # step 7 args = prev_args opts = prev_opts process_mode = prev_process_mode parent.process_mode = parent_prev_process_mode # step 8 return result ## Invokes an ad-hoc CallableNode. ## ## Can accept CallableNode, PackedScene, GDScript, or resource path String. ## (If and only if CallableNode is given directly, ## its lifetime is not managed by the call to invoke; ## in this case, the call is equivalent ## to calling invoke_existing on the given node.) static func invoke( what: Variant, parent: Node, p_args: Array = [], p_opts: Dictionary = {} ) -> Variant: if what is CallableNode: return await what.invoke_existing(parent, p_args, p_opts) elif what is PackedScene: var inst := what.instantiate() as Node var cn := inst as CallableNode if cn: var p_result = await cn.invoke_existing(parent, p_args, p_opts) cn.queue_free() return p_result elif inst: push_error( "Tried to invoke a PackedScene " + "whose root is not a CallableNode: " + inst.scene_file_path ) inst.free() return null else: return null elif what is GDScript: var inst = what.new() var cn := inst as CallableNode if cn: var p_result = await cn.invoke_existing(parent, p_args, p_opts) cn.queue_free() return p_result elif inst: push_error( "Tried to invoke a GDScript not derived from CallableNode: " + what.resource_path ) if inst is Object and not (inst is RefCounted): inst.free() return null else: return null elif what is String: return await CallableNode.invoke(load(what), parent, p_args, p_opts) else: push_error("No way to infer a CallableNode from value: %s" % what) return null