mirror of
https://github.com/lighttransport/tinyusdz.git
synced 2026-01-18 01:11:17 +01:00
Add granular coroutine yield phases for Tydra conversion
- Remove debug console.log statements from coroutine helpers - Split Tydra conversion into multiple phases with yields: - detecting: Format detection - parsing: USD parsing - setup: Converter environment setup - assets: Asset resolution setup - meshes: Tydra mesh conversion - complete: Done - Each phase yields to event loop, allowing browser repaints - Update progress-demo.js phase mapping with descriptive messages - The Tydra ConvertToRenderScene call is still blocking, but yields occur before and after it 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
108
web/binding.cc
108
web/binding.cc
@@ -125,19 +125,12 @@ EM_JS(void, reportTydraComplete, (int meshCount, int materialCount, int textureC
|
|||||||
EM_JS(emscripten::EM_VAL, yieldToEventLoop_impl, (), {
|
EM_JS(emscripten::EM_VAL, yieldToEventLoop_impl, (), {
|
||||||
// Return a Promise that resolves on next animation frame
|
// Return a Promise that resolves on next animation frame
|
||||||
// This gives the browser a chance to repaint
|
// This gives the browser a chance to repaint
|
||||||
console.log('[Coroutine] Yielding to event loop...');
|
|
||||||
return Emval.toHandle(new Promise(resolve => {
|
return Emval.toHandle(new Promise(resolve => {
|
||||||
if (typeof requestAnimationFrame === 'function') {
|
if (typeof requestAnimationFrame === 'function') {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => resolve());
|
||||||
console.log('[Coroutine] Resumed from yield (rAF)');
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
// Fallback for non-browser environments (Node.js)
|
// Fallback for non-browser environments (Node.js)
|
||||||
setTimeout(() => {
|
setTimeout(resolve, 0);
|
||||||
console.log('[Coroutine] Resumed from yield (setTimeout)');
|
|
||||||
resolve();
|
|
||||||
}, 0);
|
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
@@ -160,12 +153,9 @@ inline emscripten::val yieldWithDelay(int delayMs) {
|
|||||||
|
|
||||||
// Report that async operation is starting (for JS progress UI)
|
// Report that async operation is starting (for JS progress UI)
|
||||||
EM_JS(void, reportAsyncPhaseStart, (const char* phase, float progress), {
|
EM_JS(void, reportAsyncPhaseStart, (const char* phase, float progress), {
|
||||||
const phaseStr = UTF8ToString(phase);
|
|
||||||
const progressPct = (progress * 100).toFixed(0);
|
|
||||||
console.log(`[Coroutine] Phase: ${phaseStr} (${progressPct}%)`);
|
|
||||||
if (typeof Module.onAsyncPhaseStart === 'function') {
|
if (typeof Module.onAsyncPhaseStart === 'function') {
|
||||||
Module.onAsyncPhaseStart({
|
Module.onAsyncPhaseStart({
|
||||||
phase: phaseStr,
|
phase: UTF8ToString(phase),
|
||||||
progress: progress
|
progress: progress
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1332,22 +1322,100 @@ class TinyUSDZLoaderNative {
|
|||||||
// Yield after parsing to allow UI update
|
// Yield after parsing to allow UI update
|
||||||
co_await yieldToEventLoop();
|
co_await yieldToEventLoop();
|
||||||
|
|
||||||
// Phase 3: Converting to RenderScene (Tydra)
|
// Phase 3: Setup conversion environment
|
||||||
reportAsyncPhaseStart("converting", 0.3f);
|
reportAsyncPhaseStart("setup", 0.3f);
|
||||||
|
|
||||||
// Yield again before heavy conversion
|
tinyusdz::tydra::RenderSceneConverterEnv env(stage);
|
||||||
|
env.scene_config.load_texture_assets = loadTextureInNative_;
|
||||||
|
env.material_config.preserve_texel_bitdepth = true;
|
||||||
|
env.mesh_config.lowmem = true;
|
||||||
|
env.mesh_config.enable_bone_reduction = enable_bone_reduction_;
|
||||||
|
env.mesh_config.target_bone_count = target_bone_count_;
|
||||||
|
|
||||||
|
// Yield after setup
|
||||||
co_await yieldToEventLoop();
|
co_await yieldToEventLoop();
|
||||||
|
|
||||||
bool convert_ok = stageToRenderScene(stage, is_usdz, binary);
|
// Phase 4: Setup asset resolution
|
||||||
|
reportAsyncPhaseStart("assets", 0.4f);
|
||||||
|
|
||||||
if (!convert_ok) {
|
if (is_usdz) {
|
||||||
|
bool asset_on_memory = false;
|
||||||
|
if (!tinyusdz::ReadUSDZAssetInfoFromMemory(
|
||||||
|
reinterpret_cast<const uint8_t *>(binary.c_str()), binary.size(),
|
||||||
|
asset_on_memory, &usdz_asset_, &warn_, &error_)) {
|
||||||
|
emscripten::val result = emscripten::val::object();
|
||||||
|
result.set("success", false);
|
||||||
|
result.set("error", "Failed to read USDZ assetInfo");
|
||||||
|
co_return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
tinyusdz::AssetResolutionResolver arr;
|
||||||
|
if (!tinyusdz::SetupUSDZAssetResolution(arr, &usdz_asset_)) {
|
||||||
|
emscripten::val result = emscripten::val::object();
|
||||||
|
result.set("success", false);
|
||||||
|
result.set("error", "Failed to setup AssetResolution for USDZ");
|
||||||
|
co_return result;
|
||||||
|
}
|
||||||
|
env.asset_resolver = arr;
|
||||||
|
} else {
|
||||||
|
tinyusdz::AssetResolutionResolver arr;
|
||||||
|
if (!SetupEMAssetResolution(arr, &em_resolver_)) {
|
||||||
|
emscripten::val result = emscripten::val::object();
|
||||||
|
result.set("success", false);
|
||||||
|
result.set("error", "Failed to setup asset resolution");
|
||||||
|
co_return result;
|
||||||
|
}
|
||||||
|
env.asset_resolver = arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Yield after asset resolution setup
|
||||||
|
co_await yieldToEventLoop();
|
||||||
|
|
||||||
|
// Phase 5: Converting meshes (Tydra)
|
||||||
|
reportAsyncPhaseStart("meshes", 0.5f);
|
||||||
|
|
||||||
|
tinyusdz::tydra::RenderSceneConverter converter;
|
||||||
|
|
||||||
|
// Set up progress callback that reports to JS
|
||||||
|
converter.SetDetailedProgressCallback(
|
||||||
|
[](const tinyusdz::tydra::DetailedProgressInfo &info, void *userptr) -> bool {
|
||||||
|
// Report progress to JS synchronously
|
||||||
|
reportTydraProgress(
|
||||||
|
static_cast<int>(info.meshes_processed),
|
||||||
|
static_cast<int>(info.meshes_total),
|
||||||
|
info.GetStageName(),
|
||||||
|
info.current_mesh_name.c_str(),
|
||||||
|
info.progress
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
nullptr);
|
||||||
|
|
||||||
|
if (stage.metas().startTimeCode.authored()) {
|
||||||
|
env.timecode = stage.metas().startTimeCode.get_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Yield before heavy conversion
|
||||||
|
co_await yieldToEventLoop();
|
||||||
|
|
||||||
|
loaded_ = converter.ConvertToRenderScene(env, &render_scene_);
|
||||||
|
|
||||||
|
// Yield after conversion
|
||||||
|
co_await yieldToEventLoop();
|
||||||
|
|
||||||
|
if (!converter.GetWarning().empty()) {
|
||||||
|
if (!warn_.empty()) warn_ += "\n";
|
||||||
|
warn_ += converter.GetWarning();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!loaded_) {
|
||||||
emscripten::val result = emscripten::val::object();
|
emscripten::val result = emscripten::val::object();
|
||||||
result.set("success", false);
|
result.set("success", false);
|
||||||
result.set("error", error_);
|
result.set("error", converter.GetError());
|
||||||
co_return result;
|
co_return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Phase 4: Complete
|
// Phase 6: Complete
|
||||||
reportAsyncPhaseStart("complete", 1.0f);
|
reportAsyncPhaseStart("complete", 1.0f);
|
||||||
|
|
||||||
// Final yield to ensure UI updates
|
// Final yield to ensure UI updates
|
||||||
|
|||||||
@@ -1102,19 +1102,20 @@ async function loadUSDWithProgress(source, isFile = false) {
|
|||||||
source.name,
|
source.name,
|
||||||
{
|
{
|
||||||
onPhaseStart: (info) => {
|
onPhaseStart: (info) => {
|
||||||
console.log(`[Progress Demo] Coroutine phase: ${info.phase} (${(info.progress * 100).toFixed(0)}%)`);
|
|
||||||
// Map coroutine phases to our progress stages
|
// Map coroutine phases to our progress stages
|
||||||
const phaseMap = {
|
const phaseMap = {
|
||||||
'detecting': { stage: 'parsing', pct: 30 },
|
'detecting': { stage: 'parsing', pct: 30, msg: 'Detecting format...' },
|
||||||
'parsing': { stage: 'parsing', pct: 35 },
|
'parsing': { stage: 'parsing', pct: 35, msg: 'Parsing USD...' },
|
||||||
'converting': { stage: 'parsing', pct: 50 },
|
'setup': { stage: 'parsing', pct: 45, msg: 'Setting up converter...' },
|
||||||
'complete': { stage: 'building', pct: 80 }
|
'assets': { stage: 'parsing', pct: 50, msg: 'Resolving assets...' },
|
||||||
|
'meshes': { stage: 'parsing', pct: 55, msg: 'Converting meshes...' },
|
||||||
|
'complete': { stage: 'building', pct: 80, msg: 'Building scene...' }
|
||||||
};
|
};
|
||||||
const mapped = phaseMap[info.phase] || { stage: 'parsing', pct: 30 + info.progress * 50 };
|
const mapped = phaseMap[info.phase] || { stage: 'parsing', pct: 30 + info.progress * 50, msg: info.phase };
|
||||||
updateProgressUI({
|
updateProgressUI({
|
||||||
stage: mapped.stage,
|
stage: mapped.stage,
|
||||||
percentage: mapped.pct,
|
percentage: mapped.pct,
|
||||||
message: `${info.phase}...`
|
message: mapped.msg
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1181,18 +1182,20 @@ async function loadUSDWithProgress(source, isFile = false) {
|
|||||||
source,
|
source,
|
||||||
{
|
{
|
||||||
onPhaseStart: (info) => {
|
onPhaseStart: (info) => {
|
||||||
console.log(`[Progress Demo] Coroutine phase: ${info.phase} (${(info.progress * 100).toFixed(0)}%)`);
|
// Map coroutine phases to our progress stages
|
||||||
const phaseMap = {
|
const phaseMap = {
|
||||||
'detecting': { stage: 'parsing', pct: 30 },
|
'detecting': { stage: 'parsing', pct: 30, msg: 'Detecting format...' },
|
||||||
'parsing': { stage: 'parsing', pct: 35 },
|
'parsing': { stage: 'parsing', pct: 35, msg: 'Parsing USD...' },
|
||||||
'converting': { stage: 'parsing', pct: 50 },
|
'setup': { stage: 'parsing', pct: 45, msg: 'Setting up converter...' },
|
||||||
'complete': { stage: 'building', pct: 80 }
|
'assets': { stage: 'parsing', pct: 50, msg: 'Resolving assets...' },
|
||||||
|
'meshes': { stage: 'parsing', pct: 55, msg: 'Converting meshes...' },
|
||||||
|
'complete': { stage: 'building', pct: 80, msg: 'Building scene...' }
|
||||||
};
|
};
|
||||||
const mapped = phaseMap[info.phase] || { stage: 'parsing', pct: 30 + info.progress * 50 };
|
const mapped = phaseMap[info.phase] || { stage: 'parsing', pct: 30 + info.progress * 50, msg: info.phase };
|
||||||
updateProgressUI({
|
updateProgressUI({
|
||||||
stage: mapped.stage,
|
stage: mapped.stage,
|
||||||
percentage: mapped.pct,
|
percentage: mapped.pct,
|
||||||
message: `${info.phase}...`
|
message: mapped.msg
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user