mirror of
https://github.com/Relintai/pandemonium_engine.git
synced 2024-12-23 12:26:59 +01:00
390 lines
14 KiB
Python
390 lines
14 KiB
Python
#!/usr/bin/env python3
|
|
|
|
# PySimpleGUI call reference manual: https://www.pysimplegui.org/en/latest/call%20reference/
|
|
|
|
import PySimpleGUI as sg
|
|
import json
|
|
from os import listdir
|
|
from os.path import isfile, join, isdir, exists
|
|
|
|
# ------------------------------------------------------------------------------------------- Config
|
|
font_size = 9
|
|
|
|
|
|
# ---------------------------------------------------------------------------------------- Utilities
|
|
def load_json(path):
|
|
if exists(path):
|
|
with open(path) as f:
|
|
return json.load(f)
|
|
return {}
|
|
|
|
|
|
def compare_arrays(arr_a, arr_b):
|
|
if len(arr_a) != len(arr_b):
|
|
return False
|
|
|
|
for i in range(len(arr_a)):
|
|
if arr_a[i] != arr_b[i]:
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
def repeat_characters(char, n):
|
|
s = ""
|
|
for i in range(0, n):
|
|
s += char
|
|
return s
|
|
|
|
|
|
# Fetches the directories containing the frame info.
|
|
directories = [f for f in listdir("./") if (isdir(join("./", f)) and len(listdir(join("./", f))) > 0)]
|
|
|
|
# ----------------------------------------------------------------------------------------------- UI
|
|
def create_layout():
|
|
|
|
# Fetch the frame count from the dirs.
|
|
frame_count = -1
|
|
frames_iterations = {}
|
|
frames_description = {}
|
|
for dir in directories:
|
|
dir_path = join("./", dir)
|
|
file_names = [f for f in listdir(dir_path) if isfile(join(dir_path, f))]
|
|
for file_name in file_names:
|
|
file_extension_index = file_name.find("fd-")
|
|
if file_extension_index == 0:
|
|
# This file contains information about the frame.
|
|
# Extract the frame index
|
|
frame_iteration = file_name.count("@") + 1
|
|
if frame_iteration > 1:
|
|
frame_index = int(file_name[3:file_name.index("@")])
|
|
else:
|
|
frame_index = int(file_name[3:file_name.index(".json")])
|
|
|
|
if frame_index in frames_iterations:
|
|
frames_iterations[frame_index] = max(frame_iteration, frames_iterations[frame_index])
|
|
else:
|
|
frames_iterations[frame_index] = frame_iteration
|
|
|
|
frame_count = max(frame_count, frame_index)
|
|
|
|
file_path = join(dir_path, file_name)
|
|
file_json = load_json(file_path)
|
|
if "frame_summary" in file_json:
|
|
if frame_index in frames_description:
|
|
frames_description[frame_index] += " " + file_json["frame_summary"]
|
|
else:
|
|
frames_description[frame_index] = file_json["frame_summary"]
|
|
else:
|
|
frames_description[frame_index] = ""
|
|
|
|
frame_count += 1
|
|
|
|
|
|
# --- UI - Compose timeline ---
|
|
frame_list_values = []
|
|
for frame_index in range(frame_count):
|
|
frame_description = frames_description[frame_index]
|
|
frame_iterations = frames_iterations[frame_index]
|
|
for i in range(0, frame_iterations):
|
|
if i == 0:
|
|
frame_list_values.append("# " + str(frame_index) + " - " + frame_description)
|
|
else:
|
|
frame_list_values.append("# " + str(frame_index) + " @" + str(i + 1) + " - " + frame_description)
|
|
|
|
# Release this array, we don't need anylonger.
|
|
frames_description.clear()
|
|
|
|
frames_list = sg.Listbox(frame_list_values, key="FRAMES_LIST", size = [45, 30], enable_events=True, horizontal_scroll=True, select_mode=sg.LISTBOX_SELECT_MODE_BROWSE)
|
|
frames_list = sg.Frame("Frames", layout=[[frames_list]], vertical_alignment="top")
|
|
|
|
# --- UI - Compose frame detail ---
|
|
# Node list
|
|
nodes_list_listbox = sg.Listbox([], key="NODE_LIST", size = [45, 0], enable_events=True, horizontal_scroll=True, expand_y=True, expand_x=True, select_mode=sg.LISTBOX_SELECT_MODE_MULTIPLE)
|
|
nodes_list_listbox = sg.Frame("Nodes", layout=[[nodes_list_listbox]], vertical_alignment="top", expand_y=True, expand_x=True);
|
|
|
|
# Selected nodes title.
|
|
node_tile_txt = sg.Text("", key="FRAME_SUMMARY", font="Any, " + str(font_size - 1), justification="left", border_width=1, text_color="dark red")
|
|
|
|
# States table
|
|
table_status_header = ["name"]
|
|
table_status_widths = [30]
|
|
for dir in directories:
|
|
table_status_header.append("Begin ["+dir+"]")
|
|
table_status_widths.append(30)
|
|
|
|
for dir in directories:
|
|
table_status_header.append("End ("+dir+")")
|
|
table_status_widths.append(30)
|
|
|
|
table_status = sg.Table([], table_status_header, key="TABLE_STATUS", justification='left', auto_size_columns=False, col_widths=table_status_widths, vertical_scroll_only=False, num_rows=38)
|
|
table_status = sg.Frame("States", layout=[[table_status]], vertical_alignment="top")
|
|
|
|
# Messages table
|
|
tables_logs = []
|
|
for dir in directories:
|
|
tables_logs.append(sg.Frame("Log: " + dir + " Iteration: ", key=dir+"_FRAME_TABLE_LOG", layout=[[sg.Table([], [" #", "Log"], key=dir+"_TABLE_LOG", justification='left', auto_size_columns=False, col_widths=[4, 70], vertical_scroll_only=False, num_rows=25)]], vertical_alignment="top"))
|
|
|
|
logs = sg.Frame("Messages", layout=[tables_logs], vertical_alignment="top")
|
|
|
|
# --- UI - Main Window ---
|
|
layout = [
|
|
[
|
|
sg.Frame("", [[frames_list], [nodes_list_listbox]], vertical_alignment="top", expand_y=True),
|
|
sg.Frame("Frame detail", [[node_tile_txt], [table_status], [logs]], key="FRAME_FRAME_DETAIL", vertical_alignment="top")
|
|
],
|
|
[
|
|
sg.Button("Exit")
|
|
]
|
|
]
|
|
|
|
return layout
|
|
|
|
|
|
# ------------------------------------------------------------------------------------ Create window
|
|
window = sg.Window(title="Network Synchronizer Debugger.", layout=create_layout(), margins=(5, 5), font="Any, " + str(font_size), resizable=True)
|
|
|
|
|
|
# ----------------------------------------------------------------------------------- Event handling
|
|
frame_data = {}
|
|
nodes_list = []
|
|
selected_nodes = []
|
|
used_frame_iteration = {}
|
|
|
|
while True:
|
|
event, event_values = window.read()
|
|
|
|
# EVENT: Close the program.
|
|
if event == "Exit" or event == sg.WIN_CLOSED:
|
|
window.close()
|
|
break
|
|
|
|
# EVENT: Show frame
|
|
if event == "FRAMES_LIST":
|
|
window["NODE_LIST"].update([])
|
|
|
|
if event_values["FRAMES_LIST"] != []:
|
|
frame_description = event_values["FRAMES_LIST"][0]
|
|
if "@" in frame_description:
|
|
frame_iteration = int(frame_description[frame_description.index(" @") + 2:frame_description.index(" - ")])
|
|
selected_frame_index = int(frame_description[2:frame_description.index(" @")])
|
|
else:
|
|
frame_iteration = 1
|
|
selected_frame_index = int(frame_description[2:frame_description.index(" - ")])
|
|
|
|
print("Show frame: ", selected_frame_index, " Iteration: ", frame_iteration)
|
|
|
|
frame_data = {}
|
|
nodes_list = []
|
|
used_frame_iteration = {}
|
|
for dir in directories:
|
|
used_frame_iteration[dir] = frame_iteration
|
|
frame_file_path = join("./", dir, "fd-" + str(selected_frame_index) + repeat_characters("@", frame_iteration - 1) + ".json")
|
|
|
|
if not exists(frame_file_path):
|
|
print("The path: ", frame_file_path, " was not found. Falling back to no iteration path.")
|
|
used_frame_iteration[dir] = 1
|
|
frame_file_path = join("./", dir, "fd-" + str(selected_frame_index) + ".json")
|
|
print("Path: ", frame_file_path)
|
|
|
|
if exists(frame_file_path):
|
|
frame_data_json = load_json(frame_file_path)
|
|
frame_data[dir] = frame_data_json
|
|
|
|
for node_path in frame_data_json["begin_state"]:
|
|
if node_path not in nodes_list:
|
|
# Add this node to the nodelist
|
|
nodes_list.append(node_path)
|
|
|
|
for node_path in frame_data_json["end_state"]:
|
|
if node_path not in nodes_list:
|
|
# Add this node to the nodelist
|
|
nodes_list.append(node_path)
|
|
|
|
for node_path in frame_data_json["node_log"]:
|
|
if node_path not in nodes_list:
|
|
# Add this node to the nodelist
|
|
nodes_list.append(node_path)
|
|
|
|
|
|
# Update the node list.
|
|
window["NODE_LIST"].update(nodes_list)
|
|
|
|
if len(selected_nodes) == 0:
|
|
if len(nodes_list) > 0:
|
|
selected_nodes = [nodes_list[0]]
|
|
else:
|
|
selected_nodes = []
|
|
|
|
window["NODE_LIST"].set_value(selected_nodes)
|
|
event = "NODE_LIST"
|
|
event_values = {"NODE_LIST": selected_nodes}
|
|
|
|
# EVENT: Show node data
|
|
if event == "NODE_LIST":
|
|
|
|
window["FRAME_SUMMARY"].update("")
|
|
window["TABLE_STATUS"].update([])
|
|
|
|
for dir_name in directories:
|
|
window[dir_name + "_FRAME_TABLE_LOG"].update("Log: " + dir_name + "Frame: " + str(selected_frame_index) + " Iteration: " + str(used_frame_iteration[dir_name]))
|
|
window[dir_name + "_TABLE_LOG"].update([["", "[Nothing for this node]"]])
|
|
|
|
if event_values["NODE_LIST"] != []:
|
|
instances_count = len(directories)
|
|
row_size = 1 + (instances_count * 2)
|
|
|
|
# Compose the status table
|
|
states_table_values = []
|
|
states_row_colors = []
|
|
table_logs = {}
|
|
log_row_colors = {}
|
|
|
|
selected_nodes = event_values["NODE_LIST"]
|
|
|
|
for node_path in selected_nodes:
|
|
|
|
# First collects the var names
|
|
vars_names = ["***"]
|
|
for dir in directories:
|
|
if dir in frame_data:
|
|
if "begin_state" in frame_data[dir]:
|
|
if node_path in frame_data[dir]["begin_state"]:
|
|
for var_name in frame_data[dir]["begin_state"][node_path]:
|
|
if var_name not in vars_names:
|
|
vars_names.append(var_name)
|
|
|
|
if "end_state" in frame_data[dir]:
|
|
if node_path in frame_data[dir]["end_state"]:
|
|
for var_name in frame_data[dir]["end_state"][node_path]:
|
|
if var_name not in vars_names:
|
|
vars_names.append(var_name)
|
|
|
|
vars_names.append("---")
|
|
|
|
# Add those to the table.
|
|
for var_name in vars_names:
|
|
|
|
# Initializes the row.
|
|
row = [""] * row_size
|
|
row_index = len(states_table_values)
|
|
|
|
# Special rows
|
|
if var_name == "***":
|
|
# This is a special row to signal the start of a new node data
|
|
row[0] = node_path
|
|
states_table_values.append(row)
|
|
states_row_colors.append((row_index, "black"))
|
|
continue
|
|
elif var_name == "---":
|
|
# This is a special row to signal the end of the node data
|
|
states_table_values.append(row)
|
|
continue
|
|
|
|
row[0] = var_name.replace("*", "🔄")
|
|
|
|
# Set the row data.
|
|
for dir_i, dir_name in enumerate(directories):
|
|
if dir_name in frame_data:
|
|
if "begin_state" in frame_data[dir_name]:
|
|
if node_path in frame_data[dir_name]["begin_state"]:
|
|
if var_name in frame_data[dir_name]["begin_state"][node_path]:
|
|
#print(1, " + (", instances_count, " * 0) + ", dir_i)
|
|
row[1 + (instances_count * 0) + dir_i] = str(frame_data[dir_name]["begin_state"][node_path][var_name])
|
|
|
|
if "end_state" in frame_data[dir_name]:
|
|
if node_path in frame_data[dir_name]["end_state"]:
|
|
if var_name in frame_data[dir_name]["end_state"][node_path]:
|
|
#print(1, " + (", instances_count, " * 1) + ", dir_i)
|
|
row[1 + (instances_count * 1) + dir_i] = str(frame_data[dir_name]["end_state"][node_path][var_name])
|
|
|
|
# Check if different, so mark a worning.
|
|
for state_index in range(2):
|
|
for i in range(instances_count - 1):
|
|
if row[1 + (state_index*instances_count) + i + 0] != row[1 + (state_index*instances_count) + i + 1]:
|
|
row[1 + (state_index*instances_count) + i + 0] = "⚠️ " + row[1 + (state_index*instances_count) + i + 0]
|
|
row[1 + (state_index*instances_count) + i + 1] = "⚠️ " + row[1 + (state_index*instances_count) + i + 1]
|
|
states_row_colors.append((row_index, "dark salmon"))
|
|
break
|
|
|
|
states_table_values.append(row)
|
|
|
|
# Compose the log
|
|
for dir_name in directories:
|
|
if dir_name in frame_data:
|
|
if "node_log" in frame_data[dir_name]:
|
|
if node_path in frame_data[dir_name]["node_log"]:
|
|
|
|
table_logs[dir_name] = table_logs.get(dir_name, [])
|
|
log_row_colors[dir_name] = log_row_colors.get(dir_name, [])
|
|
|
|
table_logs[dir_name] += [["", node_path]]
|
|
log_row_colors[dir_name] += [(len(table_logs[dir_name]) - 1, "black")]
|
|
|
|
for val in frame_data[dir_name]["node_log"][node_path]:
|
|
|
|
# Append the log
|
|
table_logs[dir_name] += [["{:4d}".format(val["i"]), val["m"]]]
|
|
row_index = len(table_logs[dir_name]) - 1
|
|
|
|
# Check if this line should be colored
|
|
if val["m"].find("[WARNING]") == 0:
|
|
log_row_colors[dir_name] += [(row_index, "dark salmon")]
|
|
|
|
elif val["m"].find("[ERROR]") == 0:
|
|
log_row_colors[dir_name] += [(row_index, "red")]
|
|
|
|
elif val["m"].find("[WRITE]") == 0:
|
|
log_row_colors[dir_name] += [(row_index, "cadet blue")]
|
|
|
|
elif val["m"].find("[READ]") == 0:
|
|
log_row_colors[dir_name] += [(row_index, "medium aquamarine")]
|
|
|
|
table_logs[dir_name] += [["", ""]]
|
|
|
|
window["FRAME_FRAME_DETAIL"].update("Frame " + str(selected_frame_index) + " details")
|
|
window["TABLE_STATUS"].update(states_table_values, row_colors=states_row_colors)
|
|
|
|
frame_summary = ""
|
|
for dir_name in directories:
|
|
if dir_name in frame_data:
|
|
frame_summary += frame_data[dir_name]["frame_summary"]
|
|
if dir_name in table_logs:
|
|
window[dir_name + "_TABLE_LOG"].update(table_logs[dir_name], row_colors=log_row_colors[dir_name]);
|
|
|
|
# Check if write and read databuffer is the same.
|
|
for dir_name in directories:
|
|
if dir_name in frame_data and (dir_name == "nonet" or dir_name == "client"):
|
|
are_the_same = compare_arrays(frame_data[dir_name]["data_buffer_writes"], frame_data[dir_name]["data_buffer_reads"])
|
|
if not are_the_same:
|
|
if frame_summary != "":
|
|
frame_summary += "\n"
|
|
frame_summary += "[BUG] The DataBuffer written by `_collect_inputs` and read by `_controller_process` is different. Both should be exactly the same."
|
|
|
|
# Check if the server read correctly the received buffer.
|
|
if "server" in frame_data and "client" in frame_data:
|
|
are_the_same = compare_arrays(frame_data["server"]["data_buffer_reads"], frame_data["client"]["data_buffer_reads"])
|
|
if not are_the_same:
|
|
if frame_summary != "":
|
|
frame_summary += "\n"
|
|
frame_summary += "[BUG] The DataBuffer written by the client is different from the one read on the server."
|
|
|
|
# Check if the client sent the correct inputs to the server.
|
|
if "client" in frame_data:
|
|
for other_frame_index, is_similar in frame_data["client"]["are_inputs_different_results"].items():
|
|
other_frame_index = int(other_frame_index)
|
|
other_file_path = join("./", "client", "fd-" + str(other_frame_index) + ".json")
|
|
other_frame_json = load_json(other_file_path)
|
|
if "data_buffer_reads" in other_frame_json:
|
|
is_really_similar = compare_arrays(other_frame_json["data_buffer_reads"], frame_data["client"]["data_buffer_reads"])
|
|
if is_really_similar != is_similar:
|
|
if frame_summary != "":
|
|
frame_summary += "\n"
|
|
frame_summary += "[BUG] The function `_are_inputs_different` doesn't seems to work:\n"
|
|
frame_summary += " As the inputs read on the frame " + str(frame_data["client"]["frame"]) + " and " + str(other_frame_index) + " are " + ("SIMILAR" if is_really_similar else "DIFFERENT") +" but the net sync considered it "+ ("SIMILAR" if is_similar else "DIFFERENT")
|
|
|
|
window["FRAME_SUMMARY"].update(frame_summary)
|
|
|
|
|
|
# --------------------------------------------------------------------------------------------- Exit
|