I’m working on an app, which as one of its main features, can export an AVI file, creating a new process with Movie Maker enabled, when you click a button basically.
I’ve been wanting to make a progress bar on the main process, that shows how many frames out of the total amount it has rendered.
I’ve been using OS.create_process for this, but it only returns a PID, which despite the Godot Docs saying it can be used to monitor processes, it can’t seem to do anything other than kill a process or check if it exists (initially my idea was to take the title of the window which is rendering, since it has current frame information).
But I came across the method OS.execute_with_pipe, and it seemed perfect for me, since I can get its output, among other things. I’m using the same way I was using the OS.create_process method.
However, when I call that method and assign its return value to a variable to get its stdio and PID, the process I’m trying to execute won’t be executed, until I close the main process. When I close it, a new process is created.
When I call the method without getting its return value, it opens the new process pretty much right away.
You probably need to use a Thread to run your process without blocking the main thread. Still, you only have access to the stdio and stderr buffers so you won’t be able to access the information you want.
Something like:
extends Node
var thread:Thread
func _ready() -> void:
var path = "<path to your project>"
var process = OS.execute_with_pipe("/usr/bin/godot", ["--write-movie", "movie.avi", "--quit-after", "100", "--path", path])
thread = Thread.new()
thread.start(read_process.bind(process))
func _process(delta: float) -> void:
if not thread.is_alive():
var buffers = thread.wait_to_finish()
print(buffers["stdio"].get_string_from_utf8())
print(buffers["stderr"].get_string_from_utf8())
set_process(false)
func read_process(process:Dictionary) -> Dictionary:
var buffers:Dictionary = {
"stdio": PackedByteArray(),
"stderr": PackedByteArray(),
}
var stdio = process.get("stdio", null) as FileAccess
read(stdio, buffers["stdio"])
var stderr = process.get("stderr", null) as FileAccess
read(stderr, buffers["stderr"])
return buffers
func read(file:FileAccess, buffer:PackedByteArray) -> void:
if is_instance_valid(file):
while file.is_open() and file.get_error() == OK:
buffer.push_back(file.get_8())
It’s a shame if I can’t get something like print(current_frame) output from the render process with the main process in real time to have a progress bar… but thanks for the response.
Yea I managed to figure out it has to do with blocking, but even with that in mind and using thread multiple ways, I couldn’t get it to work.
My last and I think closest attempt was something like this:
var global_path: String
var thread: Thread
var p_info: Dictionary
func _process(_delta: float) -> void:
print(thread.is_alive()) if thread != null else print("smth")
func thread_func() -> void:
var avi_filename: String = global_path.get_file()
if ".avi" not in avi_filename:
avi_filename += ".avi"
var save_dir: String = global_path.get_base_dir()
GlobalVariables.save_settings(GlobalVariables.presets_path, true)
GlobalVariables.save_audio()
var process_info: Dictionary
process_info = OS.execute_with_pipe(OS.get_executable_path(),
[EXPORT_SCENE,
"--path", save_dir,
"--write-movie", avi_filename,
"--resolution", "1280x720",
"--fixed-fps", "60",
"--",
"--load_settings=" + GlobalVariables.render_preset_filename
])
var pipe: FileAccess = process_info["stdio"]
set_deferred("MainUtils.process_info", process_info)
while pipe.is_open() and pipe.get_error() == OK:
continue
func _on_export_value_pressed() -> void:
export_file_dialog.popup()
func _on_export_file_dialog_file_selected(path: String) -> void:
global_path = path
thread = Thread.new()
thread.start(thread_func)
Of course, that resulted in blocking. I’d either end up with the main process blocked, or the render one.
A little tired from trying to implement this feature now, but if I give your suggestion a try, I’ll make sure to respond. I appreciate it!
理论上,OS.execute_with_pipe是能立刻得到返回值的。
如果你对它不自信,你可以使用await关键字。
请不要使用while阻塞主进程。
你可试一试直接读取文件
In theory, OS.exe with ipe can immediately obtain the return value.
If you are not confident in it, you can use the await keyword.
Please do not use ‘while’ to block the main process.
You can try reading the file directly
Here is how I run a pipe process using ping as an example. But you can use it with any command. One thing to be aware of is if any parameters are being escaped, there could be trouble. The key here is await on signal and thread. In this case I get one line at a time using .get_line(). In yours you may have to use a different get method. Try it an let me know.
extends Control
var pipe
var stderr
var pid
var thread
var info
var bin
var args
# OS.execute_with_pipe()
func _ready():
args=["-c10", "yahoo.com"]
bin = "ping"
# Initialize pipe
print("OS.execute_with_pipe:")
info = OS.execute_with_pipe(bin, args)
pipe = info["stdio"]
stderr=info["stderr"]
pid=info["pid"]
# start a thread around the pipe
thread = Thread.new()
thread.start(_thread_func)
get_window().close_requested.connect(clean_thread)
# wait on thread response from pipe
var line=""
while true:
line = await pipe_in_progress
if line == null:
print("Completed!!!!")
break
print(line)
clean_thread()
signal pipe_in_progress
func _thread_func():
var line = ""
var pipe_err
while pipe.is_open():
pipe_err=pipe.get_error()
if pipe_err == OK:
line=pipe.get_line()
pipe_in_progress.emit.call_deferred(line)
pass
else:
line=stderr.get_line()
if line!="":
pipe_in_progress.emit.call_deferred(line)
else:
break
clean_thread()
func clean_thread():
pipe.close()
OS.kill(pid)