From 48a84ac85bde7347706014864cffa34776c54141 Mon Sep 17 00:00:00 2001 From: blujai831 Date: Tue, 2 Jan 2024 21:47:30 -0800 Subject: [PATCH] Drafted CallableNode class. Will use later down the line for a much cleaner implementation (or possibly outright elision) of what was previously the UI autoload, which should hopefully fix all those errors about awaiters having disappeared by the time a signal emits or a coroutine returns (if I was interpreting them correctly; I can't remember what they actually said, but it was something to that effect). --- util/CallableNode.gd | 120 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 util/CallableNode.gd diff --git a/util/CallableNode.gd b/util/CallableNode.gd new file mode 100644 index 0000000..fc3a16c --- /dev/null +++ b/util/CallableNode.gd @@ -0,0 +1,120 @@ +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. if the CallableNode is in the tree, it is removed; +## 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, +## the inherited process mode of the CallableNode is resolved, +## and its process_mode is set directly to that; +## 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 + if is_inside_tree(): + get_parent().remove_child(self) + # 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 + ): + 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, + args: Array = [], + opts: Dictionary = {} +) -> Variant: + if what is CallableNode: + return await what.invoke_existing(parent, args, opts) + elif what is PackedScene: + var inst := what.instantiate() as Node + var cn := inst as CallableNode + if cn: + var result = await cn.invoke_existing(parent, args, opts) + cn.queue_free() + return 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 result = await cn.invoke_existing(parent, args, opts) + cn.queue_free() + return 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, args, opts) + else: + push_error("No way to infer a CallableNode from value: %s" % what) + return null