Skip to content

Commit

Permalink
Improve memory management: more aggressive message culling + tests (#353
Browse files Browse the repository at this point in the history
)

* Improve memory efficiency of scene node/GUI remove, reset

* Garbage collection, tests

* Cleanup

* Consider (very unlikely) race condition

* Modal cleanup

* type errors

* Remove unused console.log
  • Loading branch information
brentyi authored Dec 11, 2024
1 parent faa9c57 commit 361a70b
Show file tree
Hide file tree
Showing 17 changed files with 758 additions and 579 deletions.
11 changes: 8 additions & 3 deletions src/viser/_gui_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ def __init__(
self._container_handle_from_uuid: dict[str, GuiContainerProtocol] = {
"root": _RootGuiContainer({})
}
self._modal_handle_from_uuid: dict[str, GuiModalHandle] = {}
self._current_file_upload_states: dict[str, _FileUploadState] = {}

# Set to True when plotly.min.js has been sent to client.
Expand Down Expand Up @@ -372,13 +373,17 @@ def _get_container_uuid(self) -> str:
"""Get container ID associated with the current thread."""
return self._target_container_from_thread_id.get(threading.get_ident(), "root")

def _set_container_uid(self, container_uuid: str) -> None:
def _set_container_uuid(self, container_uuid: str) -> None:
"""Set container ID associated with the current thread."""
self._target_container_from_thread_id[threading.get_ident()] = container_uuid

def reset(self) -> None:
"""Reset the GUI."""
self._websock_interface.queue_message(_messages.ResetGuiMessage())
root_container = self._container_handle_from_uuid["root"]
while len(root_container._children) > 0:
next(iter(root_container._children.values())).remove()
while len(self._modal_handle_from_uuid) > 0:
next(iter(self._modal_handle_from_uuid.values())).close()

def set_panel_label(self, label: str | None) -> None:
"""Set the main label that appears in the GUI panel.
Expand Down Expand Up @@ -533,7 +538,7 @@ def add_modal(
)
return GuiModalHandle(
_gui_api=self,
_uid=modal_container_id,
_uuid=modal_container_id,
)

def add_tab_group(
Expand Down
30 changes: 16 additions & 14 deletions src/viser/_gui_handles.py
Original file line number Diff line number Diff line change
Expand Up @@ -598,13 +598,13 @@ class GuiTabHandle:

def __enter__(self) -> GuiTabHandle:
self._container_id_restore = self._parent._impl.gui_api._get_container_uuid()
self._parent._impl.gui_api._set_container_uid(self._id)
self._parent._impl.gui_api._set_container_uuid(self._id)
return self

def __exit__(self, *args) -> None:
del args
assert self._container_id_restore is not None
self._parent._impl.gui_api._set_container_uid(self._container_id_restore)
self._parent._impl.gui_api._set_container_uuid(self._container_id_restore)
self._container_id_restore = None

def __post_init__(self) -> None:
Expand Down Expand Up @@ -662,13 +662,13 @@ def __init__(self, _impl: _GuiHandleState[None]) -> None:

def __enter__(self) -> GuiFolderHandle:
self._container_id_restore = self._impl.gui_api._get_container_uuid()
self._impl.gui_api._set_container_uid(self._impl.uuid)
self._impl.gui_api._set_container_uuid(self._impl.uuid)
return self

def __exit__(self, *args) -> None:
del args
assert self._container_id_restore is not None
self._impl.gui_api._set_container_uid(self._container_id_restore)
self._impl.gui_api._set_container_uuid(self._container_id_restore)
self._container_id_restore = None

def remove(self) -> None:
Expand Down Expand Up @@ -703,34 +703,36 @@ class GuiModalHandle:
"""Use as a context to place GUI elements into a modal."""

_gui_api: GuiApi
_uid: str # Used as container ID of children.
_container_uid_restore: str | None = None
_uuid: str # Used as container ID of children.
_container_uuid_restore: str | None = None
_children: dict[str, SupportsRemoveProtocol] = dataclasses.field(
default_factory=dict
)

def __enter__(self) -> GuiModalHandle:
self._container_uid_restore = self._gui_api._get_container_uuid()
self._gui_api._set_container_uid(self._uid)
self._container_uuid_restore = self._gui_api._get_container_uuid()
self._gui_api._set_container_uuid(self._uuid)
return self

def __exit__(self, *args) -> None:
del args
assert self._container_uid_restore is not None
self._gui_api._set_container_uid(self._container_uid_restore)
self._container_uid_restore = None
assert self._container_uuid_restore is not None
self._gui_api._set_container_uuid(self._container_uuid_restore)
self._container_uuid_restore = None

def __post_init__(self) -> None:
self._gui_api._container_handle_from_uuid[self._uid] = self
self._gui_api._container_handle_from_uuid[self._uuid] = self
self._gui_api._modal_handle_from_uuid[self._uuid] = self

def close(self) -> None:
"""Close this modal and permananently remove all contained GUI elements."""
self._gui_api._websock_interface.queue_message(
GuiCloseModalMessage(self._uid),
GuiCloseModalMessage(self._uuid),
)
for child in tuple(self._children.values()):
child.remove()
self._gui_api._container_handle_from_uuid.pop(self._uid)
self._gui_api._container_handle_from_uuid.pop(self._uuid)
self._gui_api._modal_handle_from_uuid.pop(self._uuid)


def _get_data_url(url: str, image_root: Path | None) -> str:
Expand Down
Loading

0 comments on commit 361a70b

Please sign in to comment.