|
|
@@ -25,6 +25,8 @@ var process_successful #tracks if the last run process was successful
|
|
|
var help_data := {} #stores help data for each node to display in help popup
|
|
|
var HelpWindowScene = preload("res://scenes/main/help_window.tscn")
|
|
|
var uiscale = 1.0 #tracks scaling for retina screens
|
|
|
+var process_info = {} #tracks the data of the currently running process
|
|
|
+var process_running := false #tracks if a process is currently running
|
|
|
|
|
|
# Called when the node enters the scene tree for the first time.
|
|
|
func _ready() -> void:
|
|
|
@@ -754,7 +756,7 @@ func run_thread_with_branches():
|
|
|
|
|
|
#If trim is enabled trim input audio
|
|
|
if Global.trim_infile == true:
|
|
|
- run_command(cdpprogs_location + "/sfedit", ["cut", "1", starting_infile, "%s_trimmed.wav" % Global.outfile, str(Global.infile_start), str(Global.infile_stop)])
|
|
|
+ await run_command(cdpprogs_location + "/sfedit", ["cut", "1", starting_infile, "%s_trimmed.wav" % Global.outfile, str(Global.infile_start), str(Global.infile_stop)])
|
|
|
starting_infile = Global.outfile + "_trimmed.wav"
|
|
|
# Mark trimmed file for cleanup if needed
|
|
|
if delete_intermediate_outputs:
|
|
|
@@ -775,7 +777,7 @@ func run_thread_with_branches():
|
|
|
# Merge inputs if this node has more than one input
|
|
|
if input_files.size() > 1:
|
|
|
# Prepare final merge output file name
|
|
|
- var runmerge = merge_many_files(process_count, input_files)
|
|
|
+ var runmerge = await merge_many_files(process_count, input_files)
|
|
|
var merge_output = runmerge[0]
|
|
|
var converted_files = runmerge[1]
|
|
|
|
|
|
@@ -805,9 +807,9 @@ func run_thread_with_branches():
|
|
|
var pvoc_stereo_files = []
|
|
|
|
|
|
for infile in current_infile:
|
|
|
- var makeprocess = make_process(node, process_count, infile, slider_data)
|
|
|
+ var makeprocess = await make_process(node, process_count, infile, slider_data)
|
|
|
# run the command
|
|
|
- run_command(makeprocess[0], makeprocess[3])
|
|
|
+ await run_command(makeprocess[0], makeprocess[3])
|
|
|
await get_tree().process_frame
|
|
|
var output_file = makeprocess[1]
|
|
|
pvoc_stereo_files.append(output_file)
|
|
|
@@ -822,20 +824,20 @@ func run_thread_with_branches():
|
|
|
|
|
|
output_files[node_name] = pvoc_stereo_files
|
|
|
else:
|
|
|
- var input_stereo = is_stereo(current_infile)
|
|
|
+ var input_stereo = await is_stereo(current_infile)
|
|
|
if input_stereo == true:
|
|
|
#audio file is stereo and needs to be split for pvoc processing
|
|
|
var pvoc_stereo_files = []
|
|
|
##Split stereo to c1/c2
|
|
|
- run_command(cdpprogs_location + "/housekeep",["chans", "2", current_infile])
|
|
|
+ await run_command(cdpprogs_location + "/housekeep",["chans", "2", current_infile])
|
|
|
|
|
|
# Process left and right seperately
|
|
|
for channel in ["c1", "c2"]:
|
|
|
var dual_mono_file = current_infile.get_basename() + "_%s.wav" % channel
|
|
|
|
|
|
- var makeprocess = make_process(node, process_count, dual_mono_file, slider_data)
|
|
|
+ var makeprocess = await make_process(node, process_count, dual_mono_file, slider_data)
|
|
|
# run the command
|
|
|
- run_command(makeprocess[0], makeprocess[3])
|
|
|
+ await run_command(makeprocess[0], makeprocess[3])
|
|
|
await get_tree().process_frame
|
|
|
var output_file = makeprocess[1]
|
|
|
pvoc_stereo_files.append(output_file)
|
|
|
@@ -850,16 +852,16 @@ func run_thread_with_branches():
|
|
|
#with this stereo process CDP will throw errors in the console even though its fine
|
|
|
if is_windows:
|
|
|
dual_mono_file = dual_mono_file.replace("/", "\\")
|
|
|
- run_command(delete_cmd, [dual_mono_file])
|
|
|
+ await run_command(delete_cmd, [dual_mono_file])
|
|
|
process_count += 1
|
|
|
|
|
|
# Store output file path for this node
|
|
|
output_files[node_name] = pvoc_stereo_files
|
|
|
else:
|
|
|
#input file is mono run through process
|
|
|
- var makeprocess = make_process(node, process_count, current_infile, slider_data)
|
|
|
+ var makeprocess = await make_process(node, process_count, current_infile, slider_data)
|
|
|
# run the command
|
|
|
- run_command(makeprocess[0], makeprocess[3])
|
|
|
+ await run_command(makeprocess[0], makeprocess[3])
|
|
|
await get_tree().process_frame
|
|
|
var output_file = makeprocess[1]
|
|
|
|
|
|
@@ -885,9 +887,9 @@ func run_thread_with_branches():
|
|
|
var pvoc_stereo_files = []
|
|
|
|
|
|
for infile in current_infile:
|
|
|
- var makeprocess = make_process(node, process_count, infile, slider_data)
|
|
|
+ var makeprocess = await make_process(node, process_count, infile, slider_data)
|
|
|
# run the command
|
|
|
- run_command(makeprocess[0], makeprocess[3])
|
|
|
+ await run_command(makeprocess[0], makeprocess[3])
|
|
|
await get_tree().process_frame
|
|
|
var output_file = makeprocess[1]
|
|
|
pvoc_stereo_files.append(output_file)
|
|
|
@@ -903,7 +905,7 @@ func run_thread_with_branches():
|
|
|
|
|
|
#interleave left and right
|
|
|
var output_file = Global.outfile.get_basename() + str(process_count) + "_interleaved.wav"
|
|
|
- run_command(cdpprogs_location + "/submix", ["interleave", pvoc_stereo_files[0], pvoc_stereo_files[1], output_file])
|
|
|
+ await run_command(cdpprogs_location + "/submix", ["interleave", pvoc_stereo_files[0], pvoc_stereo_files[1], output_file])
|
|
|
# Store output file path for this node
|
|
|
output_files[node_name] = output_file
|
|
|
|
|
|
@@ -913,12 +915,12 @@ func run_thread_with_branches():
|
|
|
|
|
|
else:
|
|
|
#Detect if input file is mono or stereo
|
|
|
- var input_stereo = is_stereo(current_infile)
|
|
|
+ var input_stereo = await is_stereo(current_infile)
|
|
|
if input_stereo == true:
|
|
|
if node.get_meta("stereo_input") == true: #audio file is stereo and process is stereo, run file through process
|
|
|
- var makeprocess = make_process(node, process_count, current_infile, slider_data)
|
|
|
+ var makeprocess = await make_process(node, process_count, current_infile, slider_data)
|
|
|
# run the command
|
|
|
- run_command(makeprocess[0], makeprocess[3])
|
|
|
+ await run_command(makeprocess[0], makeprocess[3])
|
|
|
await get_tree().process_frame
|
|
|
var output_file = makeprocess[1]
|
|
|
|
|
|
@@ -933,16 +935,16 @@ func run_thread_with_branches():
|
|
|
|
|
|
else: #audio file is stereo and process is mono, split stereo, process and recombine
|
|
|
##Split stereo to c1/c2
|
|
|
- run_command(cdpprogs_location + "/housekeep",["chans", "2", current_infile])
|
|
|
+ await run_command(cdpprogs_location + "/housekeep",["chans", "2", current_infile])
|
|
|
|
|
|
# Process left and right seperately
|
|
|
var dual_mono_output = []
|
|
|
for channel in ["c1", "c2"]:
|
|
|
var dual_mono_file = current_infile.get_basename() + "_%s.wav" % channel
|
|
|
|
|
|
- var makeprocess = make_process(node, process_count, dual_mono_file, slider_data)
|
|
|
+ var makeprocess = await make_process(node, process_count, dual_mono_file, slider_data)
|
|
|
# run the command
|
|
|
- run_command(makeprocess[0], makeprocess[3])
|
|
|
+ await run_command(makeprocess[0], makeprocess[3])
|
|
|
await get_tree().process_frame
|
|
|
var output_file = makeprocess[1]
|
|
|
dual_mono_output.append(output_file)
|
|
|
@@ -957,12 +959,12 @@ func run_thread_with_branches():
|
|
|
#with this stereo process CDP will throw errors in the console even though its fine
|
|
|
if is_windows:
|
|
|
dual_mono_file = dual_mono_file.replace("/", "\\")
|
|
|
- run_command(delete_cmd, [dual_mono_file])
|
|
|
+ await run_command(delete_cmd, [dual_mono_file])
|
|
|
process_count += 1
|
|
|
|
|
|
|
|
|
var output_file = Global.outfile.get_basename() + str(process_count) + "_interleaved.wav"
|
|
|
- run_command(cdpprogs_location + "/submix", ["interleave", dual_mono_output[0], dual_mono_output[1], output_file])
|
|
|
+ await run_command(cdpprogs_location + "/submix", ["interleave", dual_mono_output[0], dual_mono_output[1], output_file])
|
|
|
|
|
|
# Store output file path for this node
|
|
|
output_files[node_name] = output_file
|
|
|
@@ -972,9 +974,9 @@ func run_thread_with_branches():
|
|
|
intermediate_files.append(output_file)
|
|
|
|
|
|
else: #audio file is mono, run through the process
|
|
|
- var makeprocess = make_process(node, process_count, current_infile, slider_data)
|
|
|
+ var makeprocess = await make_process(node, process_count, current_infile, slider_data)
|
|
|
# run the command
|
|
|
- run_command(makeprocess[0], makeprocess[3])
|
|
|
+ await run_command(makeprocess[0], makeprocess[3])
|
|
|
await get_tree().process_frame
|
|
|
var output_file = makeprocess[1]
|
|
|
|
|
|
@@ -1008,7 +1010,7 @@ func run_thread_with_branches():
|
|
|
|
|
|
# If multiple outputs go to the outputfile node, merge them
|
|
|
if final_outputs.size() > 1:
|
|
|
- var runmerge = merge_many_files(process_count, final_outputs)
|
|
|
+ var runmerge = await merge_many_files(process_count, final_outputs)
|
|
|
final_output_dir = runmerge[0]
|
|
|
var converted_files = runmerge[1]
|
|
|
|
|
|
@@ -1030,7 +1032,7 @@ func run_thread_with_branches():
|
|
|
var fixed_path = file_path
|
|
|
if is_windows:
|
|
|
fixed_path = fixed_path.replace("/", "\\")
|
|
|
- run_command(delete_cmd, [fixed_path])
|
|
|
+ await run_command(delete_cmd, [fixed_path])
|
|
|
await get_tree().process_frame
|
|
|
#delete break files
|
|
|
for file_path in breakfiles:
|
|
|
@@ -1038,16 +1040,16 @@ func run_thread_with_branches():
|
|
|
var fixed_path = file_path
|
|
|
if is_windows:
|
|
|
fixed_path = fixed_path.replace("/", "\\")
|
|
|
- run_command(delete_cmd, [fixed_path])
|
|
|
+ await run_command(delete_cmd, [fixed_path])
|
|
|
await get_tree().process_frame
|
|
|
|
|
|
var final_filename = "%s.wav" % Global.outfile
|
|
|
var final_output_dir_fixed_path = final_output_dir
|
|
|
if is_windows:
|
|
|
final_output_dir_fixed_path = final_output_dir_fixed_path.replace("/", "\\")
|
|
|
- run_command(rename_cmd, [final_output_dir_fixed_path, final_filename.get_file()])
|
|
|
+ await run_command(rename_cmd, [final_output_dir_fixed_path, final_filename.get_file()])
|
|
|
else:
|
|
|
- run_command(rename_cmd, [final_output_dir_fixed_path, "%s.wav" % Global.outfile])
|
|
|
+ await run_command(rename_cmd, [final_output_dir_fixed_path, "%s.wav" % Global.outfile])
|
|
|
final_output_dir = Global.outfile + ".wav"
|
|
|
|
|
|
output_audio_player.play_outfile(final_output_dir)
|
|
|
@@ -1059,8 +1061,8 @@ func run_thread_with_branches():
|
|
|
|
|
|
|
|
|
func is_stereo(file: String) -> bool:
|
|
|
- var output = run_command(cdpprogs_location + "/sfprops", ["-c", file])
|
|
|
- output = int(output[0].strip_edges()) #convert output from cmd to clean int
|
|
|
+ var output = await run_command(cdpprogs_location + "/sfprops", ["-c", file])
|
|
|
+ output = int(output.strip_edges()) #convert output from cmd to clean int
|
|
|
if output == 1:
|
|
|
return false
|
|
|
elif output == 2:
|
|
|
@@ -1081,7 +1083,7 @@ func merge_many_files(process_count: int, input_files: Array) -> Array:
|
|
|
|
|
|
# STEP 1: Check each file's channel count
|
|
|
for f in input_files:
|
|
|
- var stereo = is_stereo(f)
|
|
|
+ var stereo = await is_stereo(f)
|
|
|
if stereo == false:
|
|
|
mono_files.append(f)
|
|
|
elif stereo == true:
|
|
|
@@ -1092,7 +1094,7 @@ func merge_many_files(process_count: int, input_files: Array) -> Array:
|
|
|
if mono_files.size() > 0 and stereo_files.size() > 0:
|
|
|
for mono_file in mono_files:
|
|
|
var stereo_file = "%s_stereo.wav" % mono_file.get_basename()
|
|
|
- run_command(cdpprogs_location + "/submix", ["interleave", mono_file, mono_file, stereo_file])
|
|
|
+ await run_command(cdpprogs_location + "/submix", ["interleave", mono_file, mono_file, stereo_file])
|
|
|
if process_successful == false:
|
|
|
log_console("Failed to interleave mono file: %s" % mono_file, true)
|
|
|
else:
|
|
|
@@ -1110,7 +1112,7 @@ func merge_many_files(process_count: int, input_files: Array) -> Array:
|
|
|
quoted_inputs.append(f)
|
|
|
quoted_inputs.insert(0, "mergemany")
|
|
|
quoted_inputs.append(merge_output)
|
|
|
- run_command(cdpprogs_location + "/submix", quoted_inputs)
|
|
|
+ await run_command(cdpprogs_location + "/submix", quoted_inputs)
|
|
|
|
|
|
if process_successful == false:
|
|
|
log_console("Failed to to merge files to" + merge_output, true)
|
|
|
@@ -1184,8 +1186,8 @@ func make_process(node: Node, process_count: int, current_infile: String, slider
|
|
|
var calculated_brk = []
|
|
|
|
|
|
#get length of input file in seconds
|
|
|
- var infile_length = run_command(cdpprogs_location + "/sfprops", ["-d", current_infile])
|
|
|
- infile_length = float(infile_length[0].strip_edges())
|
|
|
+ var infile_length = await run_command(cdpprogs_location + "/sfprops", ["-d", current_infile])
|
|
|
+ infile_length = float(infile_length.strip_edges())
|
|
|
|
|
|
#scale values from automation window to the right length for file and correct slider values
|
|
|
#need to check how time is handled in all files that accept it, zigzag is x = outfile position, y = infile position
|
|
|
@@ -1214,8 +1216,8 @@ func make_process(node: Node, process_count: int, current_infile: String, slider
|
|
|
cleanup.append(brk_file_path)
|
|
|
else:
|
|
|
if time == true:
|
|
|
- var infile_length = run_command(cdpprogs_location + "/sfprops", ["-d", current_infile])
|
|
|
- infile_length = float(infile_length[0].strip_edges())
|
|
|
+ var infile_length = await run_command(cdpprogs_location + "/sfprops", ["-d", current_infile])
|
|
|
+ infile_length = float(infile_length.strip_edges())
|
|
|
value = infile_length * (value / 100) #calculate percentage time of the input file
|
|
|
line += ("%s%.2f " % [flag, value]) if flag.begins_with("-") else ("%.2f " % value)
|
|
|
args.append(("%s%.2f " % [flag, value]) if flag.begins_with("-") else ("%.2f " % value))
|
|
|
@@ -1237,62 +1239,101 @@ func write_breakfile(points: Array, path: String):
|
|
|
else:
|
|
|
print("Failed to open file for writing.")
|
|
|
|
|
|
-func run_command(command: String, args: Array) -> Array:
|
|
|
+func run_command(command: String, args: Array) -> String:
|
|
|
var is_windows = OS.get_name() == "Windows"
|
|
|
|
|
|
- var output: Array = []
|
|
|
- var error: Array = []
|
|
|
-
|
|
|
- var exit_code := 0
|
|
|
+ console_output.append_text(command + " " + " ".join(args) + "\n")
|
|
|
+ console_output.scroll_to_line(console_output.get_line_count() - 1)
|
|
|
+ await get_tree().process_frame
|
|
|
+
|
|
|
if is_windows:
|
|
|
#exit_code = OS.execute("cmd.exe", ["/C", command], output, true, false)
|
|
|
args.insert(0, command)
|
|
|
args.insert(0, "/C")
|
|
|
- exit_code = OS.execute("cmd.exe", args, output, true, false)
|
|
|
+ process_info = OS.execute_with_pipe("cmd.exe", args, false)
|
|
|
else:
|
|
|
- exit_code = OS.execute(command, args, output, true, false)
|
|
|
-
|
|
|
- var output_str := ""
|
|
|
- for item in output:
|
|
|
- output_str += item + "\n"
|
|
|
+ process_info = OS.execute_with_pipe(command, args, false)
|
|
|
+ # Check if the process was successfully started
|
|
|
+ if !process_info.has("pid"):
|
|
|
+ print("Failed to start process.")
|
|
|
+ return ""
|
|
|
+
|
|
|
+ process_running = true
|
|
|
+
|
|
|
+ # Start monitoring the process output and status
|
|
|
+ return await monitor_process(process_info["pid"], process_info["stdio"], process_info["stderr"])
|
|
|
|
|
|
- var error_str := ""
|
|
|
- for item in error:
|
|
|
- error_str += item + "\n"
|
|
|
+func monitor_process(pid: int, stdout: FileAccess, stderr: FileAccess) -> String:
|
|
|
+ var output := ""
|
|
|
|
|
|
- if is_windows:
|
|
|
- args.remove_at(0)
|
|
|
- console_output.append_text(" ".join(args) + "\n")
|
|
|
- else:
|
|
|
- console_output.append_text(command + " " + " ".join(args) + "\n")
|
|
|
- console_output.scroll_to_line(console_output.get_line_count() - 1)
|
|
|
+ while OS.is_process_running(pid):
|
|
|
+ await get_tree().process_frame
|
|
|
+
|
|
|
+ while stdout.get_position() < stdout.get_length():
|
|
|
+ var line = stdout.get_line()
|
|
|
+ output += line
|
|
|
+ console_output.append_text(line + "\n")
|
|
|
+ console_output.scroll_to_line(console_output.get_line_count() - 1)
|
|
|
+ while stderr.get_position() < stderr.get_length():
|
|
|
+ var line = stderr.get_line()
|
|
|
+ console_output.append_text(line + "\n")
|
|
|
+ console_output.scroll_to_line(console_output.get_line_count() - 1)
|
|
|
|
|
|
+ var exit_code = OS.get_process_exit_code(pid)
|
|
|
if exit_code == 0:
|
|
|
- if output_str.contains("ERROR:"): #checks if CDP reported an error but passed exit code 0 anyway
|
|
|
- console_output.append_text("[color=#9c2828][b]Processes failed[/b][/color]\n")
|
|
|
+ if output.contains("ERROR:"): #checks if CDP reported an error but passed exit code 0 anyway
|
|
|
+ console_output.append_text("[color=#9c2828][b]Processes failed[/b][/color]\n\n")
|
|
|
console_output.scroll_to_line(console_output.get_line_count() - 1)
|
|
|
- console_output.append_text(output_str + "\n")
|
|
|
- process_successful = false
|
|
|
else:
|
|
|
- console_output.append_text("[color=#638382]Processes ran successfully[/color]\n")
|
|
|
+ console_output.append_text("[color=#638382]Processes ran successfully[/color]\n\n")
|
|
|
console_output.scroll_to_line(console_output.get_line_count() - 1)
|
|
|
- console_output.append_text(output_str + "\n")
|
|
|
- process_successful = true
|
|
|
-
|
|
|
else:
|
|
|
- console_output.append_text("[color=#9c2828][b]Processes failed with exit code: %d[/b][/color]\n" % exit_code)
|
|
|
+ console_output.append_text("[color=#9c2828][b]Processes failed with exit code: %d[/b][/color]\n" % exit_code + "\n")
|
|
|
console_output.scroll_to_line(console_output.get_line_count() - 1)
|
|
|
- console_output.append_text(output_str + "\n")
|
|
|
- console_output.append_text(error_str + "\n")
|
|
|
- if output_str.contains("as an internal or external command"): #check for cdprogs location error on windows
|
|
|
- console_output.append_text("[color=#9c2828][b]Please make sure your cdprogs folder is set to the correct location in the Settings menu. The default location is C:\\CDPR8\\_cdp\\_cdprogs[/b][/color]\n")
|
|
|
- if output_str.contains("command not found"): #check for cdprogs location error on unix systems
|
|
|
- console_output.append_text("[color=#9c2828][b]Please make sure your cdprogs folder is set to the correct location in the Settings menu. The default location is ~/cdpr8/_cdp/_cdprogs[/b][/color]\n")
|
|
|
- process_successful = false
|
|
|
-
|
|
|
+ if output.contains("as an internal or external command"): #check for cdprogs location error on windows
|
|
|
+ console_output.append_text("[color=#9c2828][b]Please make sure your cdprogs folder is set to the correct location in the Settings menu. The default location is C:\\CDPR8\\_cdp\\_cdprogs[/b][/color]\n\n")
|
|
|
+ console_output.scroll_to_line(console_output.get_line_count() - 1)
|
|
|
+ if output.contains("command not found"): #check for cdprogs location error on unix systems
|
|
|
+ console_output.append_text("[color=#9c2828][b]Please make sure your cdprogs folder is set to the correct location in the Settings menu. The default location is ~/cdpr8/_cdp/_cdprogs[/b][/color]\n\n")
|
|
|
+ console_output.scroll_to_line(console_output.get_line_count() - 1)
|
|
|
+
|
|
|
return output
|
|
|
|
|
|
|
|
|
+
|
|
|
+
|
|
|
+ #if is_windows:
|
|
|
+ #args.remove_at(0)
|
|
|
+ #console_output.append_text(" ".join(args) + "\n")
|
|
|
+ #else:
|
|
|
+ #console_output.append_text(command + " " + " ".join(args) + "\n")
|
|
|
+ #console_output.scroll_to_line(console_output.get_line_count() - 1)
|
|
|
+ #
|
|
|
+ #if exit_code == 0:
|
|
|
+ #if output_str.contains("ERROR:"): #checks if CDP reported an error but passed exit code 0 anyway
|
|
|
+ #console_output.append_text("[color=#9c2828][b]Processes failed[/b][/color]\n")
|
|
|
+ #console_output.scroll_to_line(console_output.get_line_count() - 1)
|
|
|
+ #console_output.append_text(output_str + "\n")
|
|
|
+ #process_successful = false
|
|
|
+ #else:
|
|
|
+ #console_output.append_text("[color=#638382]Processes ran successfully[/color]\n")
|
|
|
+ #console_output.scroll_to_line(console_output.get_line_count() - 1)
|
|
|
+ #console_output.append_text(output_str + "\n")
|
|
|
+ #process_successful = true
|
|
|
+ #
|
|
|
+ #else:
|
|
|
+ #console_output.append_text("[color=#9c2828][b]Processes failed with exit code: %d[/b][/color]\n" % exit_code)
|
|
|
+ #console_output.scroll_to_line(console_output.get_line_count() - 1)
|
|
|
+ #console_output.append_text(output_str + "\n")
|
|
|
+ #console_output.append_text(error_str + "\n")
|
|
|
+ #if output_str.contains("as an internal or external command"): #check for cdprogs location error on windows
|
|
|
+ #console_output.append_text("[color=#9c2828][b]Please make sure your cdprogs folder is set to the correct location in the Settings menu. The default location is C:\\CDPR8\\_cdp\\_cdprogs[/b][/color]\n")
|
|
|
+ #if output_str.contains("command not found"): #check for cdprogs location error on unix systems
|
|
|
+ #console_output.append_text("[color=#9c2828][b]Please make sure your cdprogs folder is set to the correct location in the Settings menu. The default location is ~/cdpr8/_cdp/_cdprogs[/b][/color]\n")
|
|
|
+ #process_successful = false
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
|
|
|
func path_exists_through_all_nodes() -> bool:
|
|
|
var all_nodes = {}
|