Skip to content

Commit 45be2e8

Browse files
author
Emanuel Kozerski
committed
Merge branch 'dev/e-man/deoptimizing-to-optimize-again' into 'main'
REMIX-4801 - Removing the skip decorator prior to the show() method pattern See merge request lightspeedrtx/lightspeed-kit!1068
2 parents 19f2539 + ac87cb2 commit 45be2e8

File tree

63 files changed

+659
-1206
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+659
-1206
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
5151
- REMIX-4818: FilePicker now supports automatic conversion to relative paths for USD edit target compliance
5252
- REMIX-4816: Improved logic graph properties panel UI with icon buttons and clearer dialog messages
5353
- Update hdremix and omni_core_materials to ext-83e59c6-main
54+
- REMIX-4801: Replaced decorator-based widget visibility filtering with subscription lifecycle management
5455

5556
### Fixed
5657
- Improved Pyright configuration by generating a list of search paths

source/extensions/lightspeed.trex.capture_tree.model/config/extension.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[package]
2-
version = "1.4.0"
2+
version = "1.5.0"
33
authors =["Damien Bataille <[email protected]>", "Pierre-Olivier Trottier <[email protected]>"]
44
title = "NVIDIA RTX Remix Capture Tree Model and Delegate"
55
description = "Model, Delegate and Item classes for a TreeView to display Captures"

source/extensions/lightspeed.trex.capture_tree.model/docs/CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
# Changelog
22
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
33

4+
## [1.5.0]
5+
### Added
6+
- Added semantic event subscriptions: subscribe_stage_opened_or_closed(), subscribe_sublayers_changed()
7+
- Added layer event handling in enable_listeners() for sublayer changes
8+
9+
### Changed
10+
- Model now owns all data-related event subscriptions, widgets subscribe to model events
11+
412
## [1.4.0]
513
### Added
614
- Added progress data caching for improved performance

source/extensions/lightspeed.trex.capture_tree.model/lightspeed/trex/capture_tree/model/model.py

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from omni.flux.utils.common import EventSubscription as _EventSubscription
2525
from omni.flux.utils.common import deferred_destroy_tasks as _deferred_destroy_tasks
2626
from omni.flux.utils.common import reset_default_attrs as _reset_default_attrs
27+
from omni.kit.usd import layers as _layers
2728

2829
from .items import CaptureTreeItem
2930

@@ -44,6 +45,7 @@ def __init__(self, context_name, show_progress: bool = True):
4445
"_core_capture": None,
4546
"_core_replacement": None,
4647
"_stage_event_sub": None,
48+
"_layer_event_sub": None,
4749
"_fetch_task": None,
4850
"_progress_cache": None,
4951
}
@@ -57,12 +59,17 @@ def __init__(self, context_name, show_progress: bool = True):
5759
self._core_replacement = _ReplacementCoreSetup(context_name)
5860

5961
self._stage_event_sub = None
62+
self._layer_event_sub = None
6063
self._fetch_task = None
6164

6265
self.__children = []
6366
# Cache for progress data accessible by path
6467
self._progress_cache: dict[str, tuple[int, int]] = {}
68+
69+
# Events
6570
self.__on_progress_updated = _Event()
71+
self.__on_stage_opened_or_closed = _Event()
72+
self.__on_sublayers_changed = _Event()
6673

6774
def refresh(self, paths: list[tuple[str, str]]):
6875
"""Refresh the list"""
@@ -104,17 +111,31 @@ def enable_listeners(self, value: bool):
104111
.get_stage_event_stream()
105112
.create_subscription_to_pop(self.__on_stage_event, name="STAGE_CHANGED_SUB")
106113
)
114+
self._layer_event_sub = (
115+
_layers.get_layers()
116+
.get_event_stream()
117+
.create_subscription_to_pop(self.__on_layer_event, name="LAYER_CHANGED_SUB")
118+
)
107119
else:
108120
self._stage_event_sub = None
121+
self._layer_event_sub = None
109122

110123
def fetch_progress(self, items: list[CaptureTreeItem] | None = None):
111124
self.cancel_tasks()
112125
self._fetch_task = asyncio.ensure_future(self.__fetch_progress(items))
113126

114127
def __on_stage_event(self, event):
115-
if event.type not in [int(usd.StageEventType.CLOSING), int(usd.StageEventType.CLOSED)]:
128+
if event.type in [int(usd.StageEventType.CLOSING), int(usd.StageEventType.CLOSED)]:
129+
self.cancel_tasks()
130+
if event.type in [int(usd.StageEventType.OPENED), int(usd.StageEventType.CLOSED)]:
131+
self.__on_stage_opened_or_closed()
132+
133+
def __on_layer_event(self, event):
134+
payload = _layers.get_layer_event_payload(event)
135+
if not payload:
116136
return
117-
self.cancel_tasks()
137+
if payload.event_type == _layers.LayerEventType.SUBLAYERS_CHANGED:
138+
self.__on_sublayers_changed()
118139

119140
def __task_completed(self):
120141
if self._fetch_task is not None:
@@ -162,6 +183,20 @@ def subscribe_progress_updated(self, func):
162183
"""
163184
return _EventSubscription(self.__on_progress_updated, func)
164185

186+
def subscribe_stage_opened_or_closed(self, func):
187+
"""
188+
Subscribe to stage opened/closed events.
189+
Return the object that will automatically unsubscribe when destroyed.
190+
"""
191+
return _EventSubscription(self.__on_stage_opened_or_closed, func)
192+
193+
def subscribe_sublayers_changed(self, func):
194+
"""
195+
Subscribe to sublayers changed events.
196+
Return the object that will automatically unsubscribe when destroyed.
197+
"""
198+
return _EventSubscription(self.__on_sublayers_changed, func)
199+
165200
def destroy(self):
166201
asyncio.ensure_future(self._deferred_destroy())
167202

source/extensions/lightspeed.trex.capture_tree.model/lightspeed/trex/capture_tree/model/tests/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,5 @@
1515
* limitations under the License.
1616
"""
1717

18-
from .e2e.test_tree import *
18+
from .e2e.test_tree import TestTreeWidget
19+
from .unit.test_enable_listeners import TestCaptureTreeModelEnableListeners

source/extensions/lightspeed.trex.layout.shared.mass_ingestion/lightspeed/trex/layout/shared/mass_ingestion/tests/unit/__init__.py renamed to source/extensions/lightspeed.trex.capture_tree.model/lightspeed/trex/capture_tree/model/tests/unit/__init__.py

File renamed without changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
"""
2+
* SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* https://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
"""
17+
18+
from lightspeed.trex.capture_tree.model import CaptureTreeModel
19+
from omni.kit.test import AsyncTestCase
20+
21+
22+
class TestCaptureTreeModelEnableListeners(AsyncTestCase):
23+
"""Test that CaptureTreeModel.enable_listeners() properly creates/destroys subscriptions."""
24+
25+
async def test_subscriptions_none_by_default(self):
26+
"""Test that subscriptions are None when model is created."""
27+
model = CaptureTreeModel("")
28+
29+
self.assertIsNone(model._stage_event_sub) # noqa: PLW0212
30+
self.assertIsNone(model._layer_event_sub) # noqa: PLW0212
31+
32+
model.destroy()
33+
34+
async def test_subscriptions_created_when_enabled(self):
35+
"""Test that subscriptions are created when enable_listeners(True) is called."""
36+
model = CaptureTreeModel("")
37+
38+
model.enable_listeners(True)
39+
40+
self.assertIsNotNone(model._stage_event_sub) # noqa: PLW0212
41+
self.assertIsNotNone(model._layer_event_sub) # noqa: PLW0212
42+
43+
model.enable_listeners(False)
44+
model.destroy()
45+
46+
async def test_subscriptions_destroyed_when_disabled(self):
47+
"""Test that subscriptions are destroyed when enable_listeners(False) is called."""
48+
model = CaptureTreeModel("")
49+
50+
# First enable
51+
model.enable_listeners(True)
52+
self.assertIsNotNone(model._stage_event_sub) # noqa: PLW0212
53+
self.assertIsNotNone(model._layer_event_sub) # noqa: PLW0212
54+
55+
# Then disable
56+
model.enable_listeners(False)
57+
self.assertIsNone(model._stage_event_sub) # noqa: PLW0212
58+
self.assertIsNone(model._layer_event_sub) # noqa: PLW0212
59+
60+
model.destroy()
61+
62+
async def test_subscriptions_toggle_multiple_times(self):
63+
"""Test that subscriptions can be toggled on/off multiple times."""
64+
model = CaptureTreeModel("")
65+
66+
# Toggle on
67+
model.enable_listeners(True)
68+
self.assertIsNotNone(model._stage_event_sub) # noqa: PLW0212
69+
self.assertIsNotNone(model._layer_event_sub) # noqa: PLW0212
70+
71+
# Toggle off
72+
model.enable_listeners(False)
73+
self.assertIsNone(model._stage_event_sub) # noqa: PLW0212
74+
self.assertIsNone(model._layer_event_sub) # noqa: PLW0212
75+
76+
# Toggle on again
77+
model.enable_listeners(True)
78+
self.assertIsNotNone(model._stage_event_sub) # noqa: PLW0212
79+
self.assertIsNotNone(model._layer_event_sub) # noqa: PLW0212
80+
81+
# Toggle off again
82+
model.enable_listeners(False)
83+
self.assertIsNone(model._stage_event_sub) # noqa: PLW0212
84+
self.assertIsNone(model._layer_event_sub) # noqa: PLW0212
85+
86+
model.destroy()

source/extensions/lightspeed.trex.capture_tree.widget/config/extension.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[package]
2-
version = "1.1.0"
2+
version = "1.1.1"
33
authors =["Emanuel Kozerski <[email protected]>"]
44
title = "NVIDIA RTX Remix Capture Tree UI Widget"
55
description = "Creates a windowed widget with a TreeView to display game Captures from a given path."

source/extensions/lightspeed.trex.capture_tree.widget/docs/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
# Changelog
22
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
33

4+
## [1.1.1]
5+
### Changed
6+
- Refactored to use model's semantic events instead of direct stage/layer subscriptions
7+
- Subscriptions now created in __init__ and controlled by model.enable_listeners()
8+
49
## [1.1.0]
510
### Added
611
- Added WorkspaceWidget interface implementation

source/extensions/lightspeed.trex.capture_tree.widget/lightspeed/trex/capture_tree/widget/setup_ui.py

Lines changed: 8 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
import omni.appwindow
2424
import omni.client
2525
import omni.kit
26-
import omni.kit.usd.layers as _layers
2726
import omni.ui as ui
2827
import omni.usd
2928
from lightspeed.common.constants import GlobalEventNames as _GlobalEventNames
@@ -33,7 +32,6 @@
3332
from lightspeed.trex.capture_tree.model import CaptureTreeDelegate, CaptureTreeModel
3433
from lightspeed.trex.replacement.core.shared import Setup as ReplacementCoreSetup
3534
from lightspeed.trex.utils.widget import TrexMessageDialog, WorkspaceWidget
36-
from lightspeed.trex.utils.widget.decorators import skip_when_widget_is_invisible
3735
from omni.flux.property_widget_builder.model.file import FileAttributeItem as _FileAttributeItem
3836
from omni.flux.property_widget_builder.model.file import FileDelegate as _FileDelegate
3937
from omni.flux.property_widget_builder.model.file import FileModel as _FileModel
@@ -59,6 +57,7 @@ class CaptureWidget(WorkspaceWidget):
5957
PROPERTY_NAME_COLUMN_WIDTH = ui.Pixel(150)
6058

6159
def __init__(self, context_name):
60+
super().__init__()
6261
self._context = omni.usd.get_context(context_name)
6362
self.__file_listener_instance = _get_file_listener_instance()
6463
self._last_capture_tree_view_window_selection = None
@@ -81,38 +80,26 @@ def __init__(self, context_name):
8180
self._core_replacement = ReplacementCoreSetup(context_name)
8281
self._game_icon_hovered_task = None
8382

83+
# Subscribe to model events (always active - model controls when events fire via enable_listeners)
8484
self._sub_model_changed = self._capture_tree_model.subscribe_progress_updated(self._refresh_trees)
85-
self._sub_stage_event = self._context.get_stage_event_stream().create_subscription_to_pop(
86-
self.__on_stage_event, name="StageChanged"
87-
)
85+
self._sub_stage_event = self._capture_tree_model.subscribe_stage_opened_or_closed(self.__on_event)
86+
self._sub_layer_event = self._capture_tree_model.subscribe_sublayers_changed(self.__on_event)
8887

89-
self._layers = _layers.get_layers()
90-
self._sub_layer_event = self._layers.get_event_stream().create_subscription_to_pop(
91-
self.__on_layer_event, name="LayerChange"
92-
)
9388
self.__create_ui()
9489

95-
def show(self, visible):
90+
def show(self, visible: bool):
91+
super().show(visible)
9692
self._capture_tree_model.enable_listeners(visible)
9793
self.root_widget.visible = visible
94+
9895
if visible:
9996
self.refresh_capture_detail_panel()
10097
else:
10198
self._capture_tree_model.cancel_tasks()
10299
self._destroy_capture_properties()
103100

104-
@skip_when_widget_is_invisible(widget="root_widget")
105-
def __on_layer_event(self, event):
106-
"""Layer event callback - automatically filtered when widget invisible."""
107-
payload = _layers.get_layer_event_payload(event)
108-
if not payload:
109-
return
110-
if payload.event_type == _layers.LayerEventType.SUBLAYERS_CHANGED:
111-
self.__on_event()
112-
113-
@skip_when_widget_is_invisible(widget="root_widget")
114101
def _refresh_trees(self, *_):
115-
"""Model progress callback - automatically filtered when widget invisible."""
102+
"""Model progress callback - subscription destroyed when window invisible."""
116103
if self._capture_tree_view_window:
117104
self._capture_tree_view_window.dirty_widgets()
118105
if self._capture_tree_view:
@@ -362,14 +349,6 @@ def _show_error_popup(self, title, message):
362349
self._error_popup.show()
363350
carb.log_error(message)
364351

365-
@skip_when_widget_is_invisible(widget="root_widget")
366-
def __on_stage_event(self, event):
367-
if event.type in [
368-
int(omni.usd.StageEventType.CLOSED),
369-
int(omni.usd.StageEventType.OPENED),
370-
]:
371-
self.__on_event()
372-
373352
def __on_event(self):
374353
self.refresh_capture_detail_panel()
375354

0 commit comments

Comments
 (0)