From 864954a89b05f750c1d2f60256fc6a5aa1522236 Mon Sep 17 00:00:00 2001 From: William Edwards Date: Mon, 30 Sep 2024 01:04:45 -0700 Subject: [PATCH] fix(InteractiveProcess): update to use new Pty node --- core/systems/launcher/interactive_process.gd | 44 +++++++++++++++---- .../core/src/resource/resource_processor.rs | 31 +++++++++---- .../core/src/resource/resource_registry.rs | 32 ++++++++++++++ 3 files changed, 90 insertions(+), 17 deletions(-) diff --git a/core/systems/launcher/interactive_process.gd b/core/systems/launcher/interactive_process.gd index abdcffcd..d8fe4a2f 100644 --- a/core/systems/launcher/interactive_process.gd +++ b/core/systems/launcher/interactive_process.gd @@ -1,6 +1,8 @@ extends Resource class_name InteractiveProcess +# DEPRECATED: Use the [Pty] node instead + ## Class for starting an interacting with a process through a psuedo terminal ## ## Starts an interactive session @@ -9,6 +11,9 @@ var pty: Pty var cmd: String var args: PackedStringArray = [] var pid: int +var registry := load("res://core/systems/resource/resource_registry.tres") as ResourceRegistry +var lines_mutex := Mutex.new() +var lines_buffer := PackedStringArray() var logger := Log.get_logger("InteractiveProcess", Log.LEVEL.INFO) @@ -20,45 +25,66 @@ func _init(command: String, cmd_args: PackedStringArray = []) -> void: ## Start the interactive process # TODO: Fixme func start() -> int: + # Create a new PTY instance and start the process + self.pty = Pty.new() + self.pty.exec(self.cmd, self.args) + self.pty.line_written.connect(_on_line_written) + + # The new [Pty] is a node, so it must be added to the scene tree + self.registry.add_child(self.pty) + return OK +func _on_line_written(line: String): + logger.info("PTY:", line) + self.lines_mutex.lock() + self.lines_buffer.append(line) + self.lines_mutex.unlock() + + ## Send the given input to the running process func send(input: String) -> void: if not pty: return - logger.debug("Writing input: " + input) - if pty.write(input.to_utf8_buffer()) < 0: + logger.info("Writing input: " + input) + if pty.write_line(input) < 0: logger.debug("Unable to write to PTY") ## Read from the stdout of the running process #TODO: Fixme -func read(chunk_size: int = 1024) -> String: +func read(_chunk_size: int = 1024) -> String: if not pty: logger.debug("Unable to read from closed PTY") return "" # Keep reading from the process until the buffer is empty - var output := "" + self.lines_mutex.lock() + var output := "\n".join(self.lines_buffer) + self.lines_buffer = PackedStringArray() + self.lines_mutex.unlock() return output ## Stop the given process func stop() -> void: - logger.debug("Stopping pid: " + str(pid)) - OS.kill(pid) - pty = null + logger.debug("Stopping pid: " + str(self.pid)) + self.pty.kill() + self.registry.remove_child(self.pty) + self.pty = null ## Returns whether or not the interactive process is still running func is_running() -> bool: - return OS.is_process_running(pid) + if not pty: + return false + return pty.running # TODO: Fixme -func output_to_log_file(log_file: FileAccess, chunk_size: int = 1024) -> int: +func output_to_log_file(log_file: FileAccess, _chunk_size: int = 1024) -> int: if not log_file: logger.warn("Unable to log output. Log file has not been opened.") return ERR_FILE_CANT_OPEN diff --git a/extensions/core/src/resource/resource_processor.rs b/extensions/core/src/resource/resource_processor.rs index 77111d8c..44fcba25 100644 --- a/extensions/core/src/resource/resource_processor.rs +++ b/extensions/core/src/resource/resource_processor.rs @@ -1,26 +1,41 @@ -use godot::prelude::*; +use godot::{obj::WithBaseField, prelude::*}; use super::resource_registry::ResourceRegistry; +/// The [ResourceProcessor] allows Godot [Resource] objects to run a process +/// function every frame. Resources must register with the [ResourceRegistry] +/// associated with this [ResourceProcessor] in order to be processed from +/// the scene tree. #[derive(GodotClass)] #[class(init, base=Node)] pub struct ResourceProcessor { base: Base, #[export] registry: Gd, + initialized: bool, } #[godot_api] impl INode for ResourceProcessor { - //fn init(base: Base) -> Self { - // // Load the registry resource - // let mut resource_loader = ResourceLoader::singleton(); - // if let Some(res) = resource_loader.load(res_path.clone().into()) {} + fn process(&mut self, delta: f64) { + if !self.initialized { + // Add any child nodes from the registry + let children = self.registry.bind().get_children(); + for child in children.iter_shared() { + self.base_mut().add_child(child); + } - // Self { base, registry: () } - //} + // Add any future children that get added to the registry + let ptr = self.to_gd(); + let method = Callable::from_object_method(&ptr, "add_child"); + self.registry.connect("child_added".into(), method); - fn process(&mut self, delta: f64) { + // Remove any children that get removed from the registry + let method = Callable::from_object_method(&ptr, "remove_child"); + self.registry.connect("child_removed".into(), method); + + self.initialized = true; + } self.registry.bind_mut().process(delta); } } diff --git a/extensions/core/src/resource/resource_registry.rs b/extensions/core/src/resource/resource_registry.rs index 63f7f0ef..747f67e2 100644 --- a/extensions/core/src/resource/resource_registry.rs +++ b/extensions/core/src/resource/resource_registry.rs @@ -5,11 +5,18 @@ use godot::prelude::*; pub struct ResourceRegistry { base: Base, resources: Array>, + child_nodes: Array>, } #[godot_api] impl ResourceRegistry { + #[signal] + fn child_added(child: Gd); + #[signal] + fn child_removed(child: Gd); + /// Register the given resource with the registry. The given resource will have its "process()" method called every frame by a [ResourceProcessor]. + #[func] pub fn register(&mut self, resource: Gd) { if !resource.has_method("process".into()) { godot_error!( @@ -24,14 +31,39 @@ impl ResourceRegistry { } /// Unregister the given resource from the registry. + #[func] pub fn unregister(&mut self, resource: Gd) { self.resources.erase(&resource); } /// Calls the "process()" method on all registered resources. This should be called from a [Node] in the scene tree like the [ResourceProcessor]. + #[func] pub fn process(&mut self, delta: f64) { for mut resource in self.resources.iter_shared() { resource.call("process".into(), &[delta.to_variant()]); } } + + /// Adds the given node to the [ResourceProcessor] node associated with this registry. + /// This provides a way for resources to add nodes into the scene tree. + #[func] + pub fn add_child(&mut self, child: Gd) { + self.child_nodes.push(child.clone()); + self.base_mut() + .emit_signal("child_added".into(), &[child.to_variant()]); + } + + /// Removes the given node from the scene tree + #[func] + pub fn remove_child(&mut self, child: Gd) { + self.child_nodes.erase(&child); + self.base_mut() + .emit_signal("child_removed".into(), &[child.to_variant()]); + } + + /// Returns a list of all nodes that should be added as children to a [ResourceProcessor] + #[func] + pub fn get_children(&self) -> Array> { + self.child_nodes.clone() + } }