don't delete item handlers, add locks, don't expand large lists

jotpluggler
Quantizr (Jimmy) 2 days ago
parent 603927014a
commit 0b6c85c89b
  1. 137
      tools/jotpluggler/views.py

@ -198,9 +198,10 @@ class TimeSeriesPanel(ViewPanel):
class DataTreeNode:
def __init__(self, name: str, full_path: str = ""):
def __init__(self, name: str, full_path: str = "", parent = None):
self.name = name
self.full_path = full_path
self.parent = parent
self.children: dict[str, DataTreeNode] = {}
self.is_leaf = False
self.child_count = 0
@ -225,6 +226,7 @@ class DataTreeView:
self.data_manager.add_observer(self._on_data_loaded)
self.queued_search = None
self.new_data = False
self._ui_lock = threading.RLock()
def create_ui(self, parent_tag: str):
with dpg.child_window(parent=parent_tag, border=False, width=-1, height=-1):
@ -264,7 +266,7 @@ class DataTreeView:
if i < len(parts) - 1:
parent_nodes_to_recheck.add(current_node) # for incremental changes from new data
if part not in current_node.children:
current_node.children[part] = DataTreeNode(name=part, full_path=current_path_prefix)
current_node.children[part] = DataTreeNode(name=part, full_path=current_path_prefix, parent=current_node)
current_node = current_node.children[part]
if not current_node.is_leaf:
@ -278,45 +280,42 @@ class DataTreeView:
return target_tree
def update_frame(self, font):
if self.avg_char_width is None and dpg.is_dearpygui_running():
self.avg_char_width = self.calculate_avg_char_width(font)
if self.new_data:
current_paths = set(self.data_manager.get_all_paths())
new_paths = current_paths - self._all_paths_cache
if new_paths:
all_paths_empty = not self._all_paths_cache
self._all_paths_cache = current_paths
if all_paths_empty:
self._populate_tree()
else:
self._add_paths_to_tree(new_paths, incremental=True)
self.new_data = False
if self.queued_search is not None:
self.current_search = self.queued_search
self._all_paths_cache = set(self.data_manager.get_all_paths())
self._populate_tree()
self.queued_search = None
nodes_processed = 0
while self.build_queue and nodes_processed < self.MAX_NODES_PER_FRAME:
child_node, parent_tag, before_tag = self.build_queue.popleft()
if not child_node.ui_created:
if child_node.is_leaf:
self._create_leaf_ui(child_node, parent_tag, before_tag)
else:
self._create_tree_node_ui(child_node, parent_tag, before_tag)
nodes_processed += 1
with self._ui_lock:
if self.avg_char_width is None and dpg.is_dearpygui_running():
self.avg_char_width = self.calculate_avg_char_width(font)
if self.new_data:
current_paths = set(self.data_manager.get_all_paths())
new_paths = current_paths - self._all_paths_cache
if new_paths:
all_paths_empty = not self._all_paths_cache
self._all_paths_cache = current_paths
if all_paths_empty:
self._populate_tree()
else:
self._add_paths_to_tree(new_paths, incremental=True)
self.new_data = False
if self.queued_search is not None:
self.current_search = self.queued_search
self._all_paths_cache = set(self.data_manager.get_all_paths())
self._populate_tree()
self.queued_search = None
nodes_processed = 0
while self.build_queue and nodes_processed < self.MAX_NODES_PER_FRAME:
child_node, parent_tag, before_tag = self.build_queue.popleft()
if not child_node.ui_created:
if child_node.is_leaf:
self._create_leaf_ui(child_node, parent_tag, before_tag)
else:
self._create_tree_node_ui(child_node, parent_tag, before_tag)
nodes_processed += 1
def search_data(self):
self.queued_search = dpg.get_value("search_input")
def _clear_ui(self):
for handler_tag in self._item_handlers:
dpg.delete_item(handler_tag)
self._item_handlers.clear()
if dpg.does_item_exist("data_tree_container"):
dpg.delete_item("data_tree_container", children_only=True)
@ -341,6 +340,9 @@ class DataTreeView:
label = f"{node.name} ({node.child_count} fields)"
search_term = self.current_search.strip().lower()
should_open = bool(search_term) and len(search_term) > 1 and any(search_term in path for path in self._get_descendant_paths(node))
if should_open and node.parent and node.parent.child_count > 100 and node.child_count > 2:
label += " (+)"
should_open = False
with dpg.tree_node(label=label, parent=parent_tag, tag=tag, default_open=should_open, open_on_arrow=True, open_on_double_click=True, before=before):
with dpg.item_handler_registry(tag=handler_tag):
@ -374,43 +376,42 @@ class DataTreeView:
node.ui_tag = f"value_{node.full_path}"
def _on_item_visible(self, sender, app_data, user_data):
path = user_data
group_tag = f"group_{path}"
value_tag = f"value_{path}"
with self._ui_lock:
path = user_data
group_tag = f"group_{path}"
value_tag = f"value_{path}"
if not self.avg_char_width or not dpg.does_item_exist(group_tag) or not dpg.does_item_exist(value_tag):
return
if not self.avg_char_width or not dpg.does_item_exist(group_tag) or not dpg.does_item_exist(value_tag):
return
value_column_width = dpg.get_item_rect_size("sidebar_window")[0] // 2
dpg.configure_item(group_tag, xoffset=value_column_width)
value_column_width = dpg.get_item_rect_size("sidebar_window")[0] // 2
dpg.configure_item(group_tag, xoffset=value_column_width)
value = self.data_manager.get_value_at(path, self.playback_manager.current_time_s)
if value is not None:
formatted_value = self.format_and_truncate(value, value_column_width, self.avg_char_width)
dpg.set_value(value_tag, formatted_value)
else:
dpg.set_value(value_tag, "N/A")
value = self.data_manager.get_value_at(path, self.playback_manager.current_time_s)
if value is not None:
formatted_value = self.format_and_truncate(value, value_column_width, self.avg_char_width)
dpg.set_value(value_tag, formatted_value)
else:
dpg.set_value(value_tag, "N/A")
def _request_children_build(self, node: DataTreeNode, handler_tag=None):
if not node.children_ui_created and (node.name == "root" or (node.ui_tag is not None and dpg.get_value(node.ui_tag))): # check root or node expanded
if handler_tag and dpg.does_item_exist(handler_tag):
dpg.delete_item(handler_tag)
self._item_handlers.discard(handler_tag)
parent_tag = "data_tree_container" if node.name == "root" else node.ui_tag
sorted_children = sorted(node.children.values(), key=self._natural_sort_key)
for i, child_node in enumerate(sorted_children):
if not child_node.ui_created:
before_tag: int | str = 0
for j in range(i + 1, len(sorted_children)): # when incrementally building get "before_tag" for correct ordering
next_child = sorted_children[j]
if next_child.ui_created:
candidate_tag = f"group_{next_child.full_path}" if next_child.is_leaf else f"tree_{next_child.full_path}"
if dpg.does_item_exist(candidate_tag):
before_tag = candidate_tag
break
self.build_queue.append((child_node, parent_tag, before_tag))
node.children_ui_created = True
with self._ui_lock:
if not node.children_ui_created and (node.name == "root" or (node.ui_tag is not None and dpg.get_value(node.ui_tag))): # check root or node expanded
parent_tag = "data_tree_container" if node.name == "root" else node.ui_tag
sorted_children = sorted(node.children.values(), key=self._natural_sort_key)
for i, child_node in enumerate(sorted_children):
if not child_node.ui_created:
before_tag: int | str = 0
for j in range(i + 1, len(sorted_children)): # when incrementally building get "before_tag" for correct ordering
next_child = sorted_children[j]
if next_child.ui_created:
candidate_tag = f"group_{next_child.full_path}" if next_child.is_leaf else f"tree_{next_child.full_path}"
if dpg.does_item_exist(candidate_tag):
before_tag = candidate_tag
break
self.build_queue.append((child_node, parent_tag, before_tag))
node.children_ui_created = True
def _should_show_path(self, path: str, search_term: str) -> bool:
if 'DEPRECATED' in path and not os.environ.get('SHOW_DEPRECATED'):

Loading…
Cancel
Save