Skip to content

Commit 3838492

Browse files
committed
Switch assets.py omni.client calls to async
The four sync calls (stat, copy, read_file) in check_file_path, retrieve_file_path, and read_file parked Kit's main thread on every Nucleus round-trip and triggered Kit's "Detected a blocking function" warning during env init. They now run through a new _drive_kit_async helper, which schedules the *_async coroutine on Kit's asyncio loop and ticks app.update() until it resolves -- same wall time, but Kit keeps rendering during the wait. Add omni.kit.async_engine to every IsaacLab .kit file so its observer steps the asyncio loop each Kit frame; without it the spin loop would deadlock (headless and CI runs would hang on any Nucleus I/O). The no-Kit fallback uses asyncio.run for Python 3.12+ compatibility.
1 parent 24f2cc0 commit 3838492

7 files changed

Lines changed: 49 additions & 4 deletions

apps/isaaclab.python.headless.kit

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ log.outputStreamLevel = "Warn"
2828
"omni.physx.fabric" = {}
2929
"usdrt.scenegraph" = {}
3030
"omni.kit.telemetry" = {}
31+
"omni.kit.async_engine" = {}
3132
"omni.kit.loop" = {}
3233
# this is needed to create physics material through CreatePreviewSurfaceMaterialPrim
3334
"omni.kit.usd.mdl" = {}

apps/isaaclab.python.headless.rendering.kit

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ version = "3.0.0"
1515
keywords = ["experience", "app", "isaaclab", "python", "camera", "minimal"]
1616

1717
[dependencies]
18+
"omni.kit.async_engine" = {}
1819
# Isaac Lab minimal app
1920
"isaaclab.python.headless" = {}
2021
"isaacsim.core.rendering_manager" = {}

apps/isaaclab.python.kit

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ keywords = ["experience", "app", "usd"]
5959
"omni.hydra.engine.stats" = {}
6060
"omni.hydra.rtx" = {}
6161
"omni.kit.mainwindow" = {}
62+
"omni.kit.async_engine" = {}
6263
"omni.kit.manipulator.camera" = {}
6364
"omni.kit.manipulator.prim" = {}
6465
"omni.kit.manipulator.selection" = {}

apps/isaaclab.python.rendering.kit

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ version = "3.0.0"
1515
keywords = ["experience", "app", "isaaclab", "python", "camera", "minimal"]
1616

1717
[dependencies]
18+
"omni.kit.async_engine" = {}
1819
# Isaac Lab minimal app
1920
"isaaclab.python" = {}
2021

apps/isaaclab.python.xr.openxr.headless.kit

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ xr.skipInputDeviceUSDWrites = true
3131
cameras_enabled = true
3232

3333
[dependencies]
34+
"omni.kit.async_engine" = {}
3435
"isaaclab.python.xr.openxr" = {}
3536

3637
# NOTE: xr.profile.ar.enabled is intentionally NOT set here.

apps/isaaclab.python.xr.openxr.kit

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ xr.skipInputDeviceUSDWrites = true
3737
'rtx-transient'.resourcemanager.enableTextureStreaming = false
3838

3939
[dependencies]
40+
"omni.kit.async_engine" = {}
4041
"isaaclab.python" = {}
4142

4243
# Required for XR instruction widget (CopyFabricPrim)

source/isaaclab/isaaclab/utils/assets.py

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,40 @@ def _parse_kit_asset_root() -> str:
5252
"""Path to the ``Isaac/IsaacLab`` directory on the NVIDIA Nucleus Server."""
5353

5454

55+
def _kit_app():
56+
"""Return the running Kit app or ``None`` if Kit is not running."""
57+
try:
58+
import omni.kit.app # noqa: PLC0415
59+
60+
return omni.kit.app.get_app()
61+
except Exception:
62+
return None
63+
64+
65+
def _drive_kit_async(coro):
66+
"""Run an ``omni.client`` ``_async`` coroutine without blocking Kit's main thread.
67+
68+
Schedules the coroutine on Kit's asyncio loop and ticks ``app.update()`` until
69+
it resolves; falls back to a private event loop when Kit isn't running.
70+
"""
71+
import asyncio # noqa: PLC0415
72+
import time # noqa: PLC0415
73+
74+
app = _kit_app()
75+
if app is None:
76+
return asyncio.run(coro)
77+
try:
78+
import omni.kit.async_engine # noqa: PLC0415
79+
80+
task = omni.kit.async_engine.run_coroutine(coro)
81+
except Exception:
82+
task = asyncio.ensure_future(coro)
83+
while not task.done():
84+
app.update()
85+
time.sleep(0)
86+
return task.result()
87+
88+
5589
def check_file_path(path: str) -> Literal[0, 1, 2]:
5690
"""Checks if a file exists on the Nucleus Server or locally.
5791
@@ -70,7 +104,7 @@ def check_file_path(path: str) -> Literal[0, 1, 2]:
70104

71105
import omni.client # noqa: PLC0415
72106

73-
if omni.client.stat(path.replace(os.sep, "/"))[0] == omni.client.Result.OK:
107+
if _drive_kit_async(omni.client.stat_async(path.replace(os.sep, "/")))[0] == omni.client.Result.OK:
74108
return 2
75109
else:
76110
return 0
@@ -131,7 +165,10 @@ def retrieve_file_path(path: str, download_dir: str | None = None, force_downloa
131165
if _UDIM_RE.search(cur_url):
132166
for tile in range(1001, 1101):
133167
tile_url = _UDIM_RE.sub(str(tile), cur_url)
134-
if omni.client.stat(tile_url.replace(os.sep, "/"))[0] == omni.client.Result.OK:
168+
if (
169+
_drive_kit_async(omni.client.stat_async(tile_url.replace(os.sep, "/")))[0]
170+
== omni.client.Result.OK
171+
):
135172
if tile_url not in visited:
136173
to_visit.append(tile_url)
137174
else:
@@ -143,7 +180,9 @@ def retrieve_file_path(path: str, download_dir: str | None = None, force_downloa
143180
os.makedirs(os.path.dirname(target_path), exist_ok=True)
144181

145182
if not os.path.isfile(target_path) or force_download:
146-
result = omni.client.copy(cur_url, target_path, omni.client.CopyBehavior.OVERWRITE)
183+
result = _drive_kit_async(
184+
omni.client.copy_async(cur_url, target_path, omni.client.CopyBehavior.OVERWRITE)
185+
)
147186
if result != omni.client.Result.OK and force_download:
148187
raise RuntimeError(f"Unable to copy file: '{cur_url}'. Is the Nucleus Server running?")
149188

@@ -183,7 +222,7 @@ def read_file(path: str) -> io.BytesIO:
183222
elif file_status == 2:
184223
import omni.client # noqa: PLC0415
185224

186-
file_content = omni.client.read_file(path.replace(os.sep, "/"))[2]
225+
file_content = _drive_kit_async(omni.client.read_file_async(path.replace(os.sep, "/")))[2]
187226
return io.BytesIO(memoryview(file_content).tobytes())
188227
else:
189228
raise FileNotFoundError(f"Unable to find the file: {path}")

0 commit comments

Comments
 (0)