在 Babylon.js 3D 开发中,精确控制代码执行时机是性能优化和正确性的关键。本文将深入剖析微任务、宏任务与帧循环的本质区别,并提供 TypeScript 严格模式下的实战案例。
一、执行机制的本质差异
1. 事件循环中的位置
// 严格模式下的类型定义
type LogCallback = () => void;
console.log("🎬 宏任务开始");
// 微任务 (Microtask)
Promise.resolve().then((): void => {
console.log("⚡ 微任务:Promise.resolve().then()");
});
// 宏任务 (Macrotask)
setTimeout((): void => {
console.log("⏰ 宏任务:setTimeout()");
}, 0);
// 帧回调 (Animation Frame)
requestAnimationFrame((): void => {
console.log("🖼️ 帧任务:requestAnimationFrame()");
});
console.log("🏁 宏任务结束");
// 实际输出顺序:
// 🎬 宏任务开始
// 🏁 宏任务结束
// ⚡ 微任务:Promise.resolve().then()
// 🖼️ 帧任务:requestAnimationFrame()
// ⏰ 宏任务:setTimeout()
执行优先级:微任务 → 帧任务 → 宏任务
二、Babylon.js 场景下的核心区别
| 特性 | Promise.resolve().then() | setTimeout(..., 0) | requestAnimationFrame |
|---|---|---|---|
| 执行时机 | 当前同步代码块结束后立即执行 | 至少 4ms 延迟(浏览器最小化间隔) | 下次重绘前(约 16ms@60fps) |
| Broader 集成 | 无显式帧上下文 | 无帧上下文 | 可访问 scene.deltaTime |
| 性能影响 | 极小开销 | 中等延迟 | 与渲染管线同步 |
| 适用场景 | 状态同步、批量更新 | 延迟初始化、防抖 | 动画、渲染相关操作 |
三、Promise.resolve().then() 的 Babylon.js 实战案例
案例 1:材质加载后的 Inspector 刷新
// 严格模式类型定义
interface MaterialConfig {
baseColor: BABYLON.Color3;
metallic: number;
}
class PbrMaterialLoader {
private scene: BABYLON.Scene;
private readonly pendingMaterials: Map<string, BABYLON.PBRMaterial> = new Map();
constructor(scene: BABYLON.Scene) {
this.scene = scene;
}
/**
* 批量加载材质,避免重复触发渲染
*/
loadMaterialAsync(name: string, config: MaterialConfig): Promise<BABYLON.PBRMaterial> {
const material = new BABYLON.PBRMaterial(name, this.scene);
// 批量收集材质
this.pendingMaterials.set(name, material);
// 微任务调度:所有同步加载完成后统一处理
if (this.pendingMaterials.size === 1) {
Promise.resolve().then((): void => {
this.flushMaterialBatch();
});
}
return Promise.resolve(material);
}
private flushMaterialBatch(): void {
this.pendingMaterials.forEach((material: BABYLON.PBRMaterial, name: string): void => {
// 批量设置材质属性
material.metallic = 0.8;
material.albedoColor = new BABYLON.Color3(1, 1, 1);
// 只在批量处理完成后触发一次 Inspector 更新
if (window.BABYLON && (window as any).INSPECTOR) {
(window as any).INSPECTOR.refresh();
}
});
this.pendingMaterials.clear();
console.log(`批量处理完成,共 ${this.pendingMaterials.size} 个材质`);
}
}
案例 2:相机控制后的精确拾取
class PrecisePicker {
private scene: BABYLON.Scene;
private camera: BABYLON.ArcRotateCamera;
constructor(scene: BABYLON.Scene, camera: BABYLON.ArcRotateCamera) {
this.scene = scene;
this.camera = camera;
}
/**
* 相机平滑移动后获取目标网格
*/
pickAfterCameraMove(targetPosition: BABYLON.Vector3): Promise<BABYLON.Nullable<BABYLON.AbstractMesh>> {
return new Promise((resolve): void => {
// 开始相机动画
BABYLON.Animation.CreateAndStartAnimation(
"cameraMove",
this.camera,
"target",
30,
30,
this.camera.target,
targetPosition,
BABYLON.Animation.ANIMATIONLOOPMODE_CONSTANT
);
// 微任务调度:等待相机矩阵更新
Promise.resolve().then((): void => {
// 确保相机世界矩阵已更新
this.camera.computeWorldMatrix(true);
// 执行拾取
const pickResult = this.scene.pick(
this.scene.getEngine().getRenderWidth() / 2,
this.scene.getEngine().getRenderHeight() / 2
);
resolve(pickResult.pickedMesh);
});
});
}
}
案例 3:纹理加载与 UI 状态同步
class TextureManager {
private scene: BABYLON.Scene;
private loadingCount = 0;
constructor(scene: BABYLON.Scene) {
this.scene = scene;
}
/**
* 加载纹理并同步 UI 状态
*/
loadTextureWithUI(url: string, onProgress?: (loaded: number, total: number) => void): BABYLON.Texture {
this.loadingCount++;
// 微任务调度:DOM 更新后显示加载状态
Promise.resolve().then((): void => {
const loadingElement = document.getElementById('loading-status');
if (loadingElement) {
loadingElement.style.display = 'block';
loadingElement.textContent = `加载中... ${this.loadingCount} 个资源`;
}
});
const texture = new BABYLON.Texture(
url,
this.scene,
false, // noMipmap
true, // invertY
BABYLON.Texture.TRILINEAR_SAMPLINGMODE,
(): void => {
this.loadingCount--;
// 微任务调度:批量更新 UI
Promise.resolve().then((): void => {
onProgress?.(this.loadingCount, this.loadingCount + 1);
if (this.loadingCount === 0) {
const statusEl = document.getElementById('loading-status');
if (statusEl) {
statusEl.style.display = 'none';
}
}
});
}
);
return texture;
}
}
案例 4:物理引擎步进后的位置读取
class PhysicsSyncManager {
private scene: BABYLON.Scene;
private physicsPlugin: BABYLON.CannonJSPlugin;
constructor(scene: BABYLON.Scene) {
this.scene = scene;
this.physicsPlugin = new BABYLON.CannonJSPlugin();
this.scene.enablePhysics(new BABYLON.Vector3(0, -9.81, 0), this.physicsPlugin);
}
/**
* 应用冲量后获取精确位置
*/
applyImpulseAndGetPosition(
mesh: BABYLON.Mesh,
impulse: BABYLON.Vector3,
contactPoint: BABYLON.Vector3
): Promise<BABYLON.Vector3> {
const impostor = mesh.physicsImpostor;
if (!impostor) {
throw new Error('网格未启用物理');
}
impostor.applyImpulse(impulse, contactPoint);
// 微任务调度:等待物理引擎完成计算
return Promise.resolve().then((): BABYLON.Vector3 => {
// 强制同步物理世界
(this.scene as any)._physicsEngine?._step(BABYLON.Engine.LastCreatedScene!);
// 获取精确位置
const position = mesh.getAbsolutePosition().clone();
console.log('物理计算后的位置:', position);
return position;
});
}
}
案例 5:批量网格创建与场景优化
class OptimizedMeshFactory {
private scene: BABYLON.Scene;
private meshBatch: Array<{
name: string;
size: BABYLON.Vector3;
position: BABYLON.Vector3;
}> = [];
constructor(scene: BABYLON.Scene) {
this.scene = scene;
}
/**
* 批量收集网格配置,微任务调度统一创建
*/
createMeshLater(
name: string,
size: BABYLON.Vector3,
position: BABYLON.Vector3
): void {
this.meshBatch.push({ name, size, position });
// 微任务调度:批量创建
if (this.meshBatch.length === 1) {
Promise.resolve().then((): void => {
this.executeBatchCreation();
});
}
}
private executeBatchCreation(): void {
console.log(`批量创建 ${this.meshBatch.length} 个网格`);
// 合并网格优化
const meshes: BABYLON.Mesh[] = [];
this.meshBatch.forEach((config): void => {
const mesh = BABYLON.MeshBuilder.CreateBox(config.name, {
size: config.size.x
}, this.scene);
mesh.position = config.position;
meshes.push(mesh);
});
// 批量创建完成后执行合并
if (meshes.length > 1) {
const merged = BABYLON.Mesh.MergeMeshes(meshes, true, true, undefined, false, true);
if (merged) {
merged.name = "MergedBatch";
}
}
// 清理批次
this.meshBatch = [];
}
}
四、三者的精确对比测试
class ExecutionOrderTester {
private scene: BABYLON.Scene;
constructor(scene: BABYLON.Scene) {
this.scene = scene;
}
/**
* 在 Babylon.js 渲染循环中测试执行顺序
*/
runTest(): void {
let frameCount = 0;
this.scene.registerBeforeRender((): void => {
if (frameCount === 0) {
console.log(`=== Frame ${frameCount} 开始 ===`);
// 微任务
Promise.resolve().then((): void => {
console.log("微任务:矩阵更新后立即执行");
// 可以安全访问最新 worldMatrix
const mesh = this.scene.getMeshByName("test");
if (mesh) {
const matrix = mesh.getWorldMatrix(true); // 强制更新
console.log("微任务获取矩阵:", matrix.m[12]);
}
});
// 帧任务
requestAnimationFrame((): void => {
console.log("帧任务:下次渲染前执行");
});
// 宏任务
setTimeout((): void => {
console.log("宏任务:至少 4ms 后执行");
}, 0);
console.log("=== Frame ${frameCount} 结束 ===");
}
frameCount++;
});
}
}
输出结果:
=== Frame 0 开始 ===
=== Frame 0 结束 ===
微任务:矩阵更新后立即执行
微任务获取矩阵: 10.5
帧任务:下次渲染前执行
=== Frame 1 开始 ===
=== Frame 1 结束 ===
宏任务:至少 4ms 后执行
五、最佳实践决策树
// 决策辅助函数
function chooseAsyncMethod(
needImmediate: boolean,
needFrameContext: boolean,
needMinimumDelay: boolean
): 'microtask' | 'frame' | 'macro' {
if (needImmediate) return 'microtask';
if (needFrameContext) return 'frame';
if (needMinimumDelay) return 'macro';
return 'microtask'; // 默认推荐
}
// 使用示例
chooseAsyncMethod(true, false, false); // → microtask (Promise.resolve().then)
chooseAsyncMethod(false, true, false); // → frame (requestAnimationFrame)
chooseAsyncMethod(false, false, true); // → macro (setTimeout)
六、总结
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 物理计算后读位置 | Promise.resolve().then() | 同步状态,无需等待渲染 |
| 材质批量加载 | Promise.resolve().then() | 最小延迟,批量优化 |
| 相机动画后拾取 | Promise.resolve().then() | 精确控制执行时机 |
| 复杂动画更新 | requestAnimationFrame | 与渲染管线同步 |
| 延迟初始化 | setTimeout(..., 0) | 强制让出主线程 |
核心原则:在 Babylon.js 中,优先使用 Promise.resolve().then() 处理状态同步和批量操作,它提供了微秒级的精确控制,是性能优化的利器。
2705

被折叠的 条评论
为什么被折叠?



