From de3bfd755148ac5ee77724a82828d648199a9185 Mon Sep 17 00:00:00 2001 From: YHH <359807859@qq.com> Date: Sun, 28 Sep 2025 20:41:23 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=A6=81=E7=94=A8sab?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/docs.yml | 15 +++ .../{index-DRe9rMYY.js => index-DeXakP9I.js} | 98 +++++++++++++++++-- docs/public/demos/worker-system/index.html | 15 ++- docs/public/sw.js | 39 ++++++++ examples/worker-system-demo/index.html | 2 + examples/worker-system-demo/src/GameScene.ts | 14 +++ examples/worker-system-demo/src/main.ts | 36 ++++++- .../src/systems/PhysicsWorkerSystem.ts | 53 +++++++++- .../src/ECS/Systems/WorkerEntitySystem.ts | 64 +++++++++++- 9 files changed, 319 insertions(+), 17 deletions(-) rename docs/public/demos/worker-system/assets/{index-DRe9rMYY.js => index-DeXakP9I.js} (99%) create mode 100644 docs/public/sw.js diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 346f2cee..1de927a5 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -50,6 +50,21 @@ jobs: - name: Build documentation run: npm run docs:build + - name: Add COOP/COEP headers for SharedArrayBuffer support + run: | + # Create _headers file for Netlify compatibility + echo "/*" > docs/.vitepress/dist/_headers + echo " Cross-Origin-Embedder-Policy: require-corp" >> docs/.vitepress/dist/_headers + echo " Cross-Origin-Opener-Policy: same-origin" >> docs/.vitepress/dist/_headers + + # Create .htaccess file for Apache compatibility (GitHub Pages sometimes uses this) + echo "Header always set Cross-Origin-Embedder-Policy require-corp" > docs/.vitepress/dist/.htaccess + echo "Header always set Cross-Origin-Opener-Policy same-origin" >> docs/.vitepress/dist/.htaccess + + # Create a meta tag fallback in a headers.html file that can be included + echo '' > docs/.vitepress/dist/headers.html + echo '' >> docs/.vitepress/dist/headers.html + - name: Upload artifact uses: actions/upload-pages-artifact@v3 with: diff --git a/docs/public/demos/worker-system/assets/index-DRe9rMYY.js b/docs/public/demos/worker-system/assets/index-DeXakP9I.js similarity index 99% rename from docs/public/demos/worker-system/assets/index-DRe9rMYY.js rename to docs/public/demos/worker-system/assets/index-DeXakP9I.js index 867e6c65..37076832 100644 --- a/docs/public/demos/worker-system/assets/index-DRe9rMYY.js +++ b/docs/public/demos/worker-system/assets/index-DeXakP9I.js @@ -11370,8 +11370,9 @@ class WorkerEntitySystem extends EntitySystem { initializeSharedArrayBuffer() { try { if (!this.isSharedArrayBufferSupported()) { - console.warn(`[${this.systemName}] SharedArrayBuffer not supported, falling back to traditional Worker mode`); + console.warn(`[${this.systemName}] SharedArrayBuffer not supported, falling back to single Worker mode for collision detection integrity`); this.config.useSharedArrayBuffer = false; + this.config.workerCount = 1; return; } const bufferSize = this.config.maxEntities * this.config.entityDataSize * 4; @@ -11379,10 +11380,11 @@ class WorkerEntitySystem extends EntitySystem { this.sharedFloatArray = new Float32Array(this.sharedBuffer); console.log(`[${this.systemName}] SharedArrayBuffer initialized successfully (${bufferSize} bytes)`); } catch (error) { - console.warn(`[${this.systemName}] SharedArrayBuffer init failed, falling back to traditional Worker mode:`, error); + console.warn(`[${this.systemName}] SharedArrayBuffer init failed, falling back to single Worker mode for collision detection integrity:`, error); this.config.useSharedArrayBuffer = false; this.sharedBuffer = null; this.sharedFloatArray = null; + this.config.workerCount = 1; } } /** @@ -11912,11 +11914,11 @@ let PhysicsWorkerSystem = class extends WorkerEntitySystem { Matcher.empty().all(Position, Velocity, Physics), { enableWorker, - workerCount: navigator.hardwareConcurrency || 2, - // 恢复多Worker + // 当 SharedArrayBuffer 可用时使用多 Worker,否则使用单 Worker 保证碰撞检测完整性 + workerCount: this.isSharedArrayBufferAvailable() ? navigator.hardwareConcurrency || 2 : 1, systemConfig: defaultConfig, useSharedArrayBuffer: true - // 使用SharedArrayBuffer进行全局碰撞检测 + // 优先使用 SharedArrayBuffer } ); this.physicsConfig = { @@ -11928,6 +11930,12 @@ let PhysicsWorkerSystem = class extends WorkerEntitySystem { }; this.startTime = 0; } + /** + * 检查 SharedArrayBuffer 是否可用 + */ + isSharedArrayBufferAvailable() { + return typeof SharedArrayBuffer !== "undefined" && self.crossOriginIsolated; + } extractEntityData(entity) { const position = entity.getComponent(Position); const velocity = entity.getComponent(Velocity); @@ -12050,6 +12058,45 @@ let PhysicsWorkerSystem = class extends WorkerEntitySystem { getPhysicsConfig() { return { ...this.physicsConfig }; } + /** + * 强制禁用 SharedArrayBuffer(用于测试降级行为) + */ + forceDisableSharedArrayBuffer() { + console.log(`[${this.systemName}] Manually disabling SharedArrayBuffer for testing`); + this.config.useSharedArrayBuffer = false; + this.config.workerCount = 1; + this.sharedBuffer = null; + this.sharedFloatArray = null; + if (this.workerPool) { + this.workerPool.destroy(); + this.workerPool = null; + } + if (this.config.enableWorker) { + this.initializeWorkerPool(); + } + } + /** + * 获取当前运行状态 + */ + getCurrentStatus() { + const workerInfo = this.getWorkerInfo(); + let mode = "sync"; + if (workerInfo.enabled) { + if (workerInfo.sharedArrayBufferEnabled && workerInfo.sharedArrayBufferSupported) { + mode = "shared-buffer"; + } else if (workerInfo.workerCount === 1) { + mode = "single-worker"; + } else { + mode = "multi-worker"; + } + } + return { + mode, + sharedArrayBufferEnabled: workerInfo.sharedArrayBufferEnabled, + workerCount: workerInfo.workerCount, + workerEnabled: workerInfo.enabled + }; + } /** * 性能监控 */ @@ -12470,6 +12517,18 @@ class GameScene extends Scene { }); } } + /** + * 切换 SharedArrayBuffer 状态 + */ + toggleSharedArrayBuffer() { + this.physicsSystem.forceDisableSharedArrayBuffer(); + } + /** + * 获取物理系统状态 + */ + getPhysicsSystemStatus() { + return this.physicsSystem.getCurrentStatus(); + } /** * 获取系统信息 */ @@ -12533,6 +12592,7 @@ class WorkerDemo { "entityCount", "entityCountValue", "toggleWorker", + "toggleSAB", "gravity", "gravityValue", "friction", @@ -12547,7 +12607,8 @@ class WorkerDemo { "physicsTime", "renderTime", "frameTime", - "memoryUsage" + "memoryUsage", + "sabStatus" ]; for (const id of elementIds) { const element = document.getElementById(id); @@ -12576,6 +12637,12 @@ class WorkerDemo { this.updateWorkerStatus(); }); } + if (this.elements.toggleSAB) { + this.elements.toggleSAB.addEventListener("click", () => { + this.gameScene.toggleSharedArrayBuffer(); + this.updateWorkerStatus(); + }); + } if (this.elements.gravity && this.elements.gravityValue) { const slider = this.elements.gravity; slider.addEventListener("input", () => { @@ -12664,6 +12731,7 @@ class WorkerDemo { const systemInfo = this.gameScene.getSystemInfo(); const workerInfo = systemInfo.physics; const entityCount = systemInfo.entityCount; + const status = this.gameScene.getPhysicsSystemStatus(); if (this.elements.workerStatus) { if (workerInfo.enabled) { this.elements.workerStatus.textContent = `启用 (${workerInfo.workerCount} Workers)`; @@ -12681,6 +12749,24 @@ class WorkerDemo { this.elements.workerLoad.textContent = "N/A"; } } + if (this.elements.sabStatus) { + const modeNames = { + "shared-buffer": "SharedArrayBuffer模式", + "single-worker": "单Worker模式", + "multi-worker": "多Worker模式", + "sync": "同步模式" + }; + this.elements.sabStatus.textContent = modeNames[status.mode] || status.mode; + this.elements.sabStatus.className = status.mode === "shared-buffer" ? "worker-enabled" : "worker-disabled"; + } + if (this.elements.toggleSAB) { + if (status.sharedArrayBufferEnabled) { + this.elements.toggleSAB.textContent = "禁用 SharedArrayBuffer"; + } else { + this.elements.toggleSAB.textContent = "启用 SharedArrayBuffer"; + this.elements.toggleSAB.setAttribute("disabled", "true"); + } + } } getMemoryUsage() { if ("memory" in performance) { diff --git a/docs/public/demos/worker-system/index.html b/docs/public/demos/worker-system/index.html index 8a59c0f5..0d796340 100644 --- a/docs/public/demos/worker-system/index.html +++ b/docs/public/demos/worker-system/index.html @@ -118,7 +118,7 @@ color: #ff4a4a; } - +
@@ -171,6 +171,17 @@
- + \ No newline at end of file diff --git a/docs/public/sw.js b/docs/public/sw.js new file mode 100644 index 00000000..eb0e2f1f --- /dev/null +++ b/docs/public/sw.js @@ -0,0 +1,39 @@ +// Service Worker to add COOP/COEP headers for SharedArrayBuffer support +// This is a workaround for GitHub Pages which doesn't support custom headers + +self.addEventListener('fetch', (event) => { + // Only handle requests for this origin + if (!event.request.url.startsWith(self.location.origin)) { + return; + } + + event.respondWith( + fetch(event.request).then((response) => { + // Clone the response to modify headers + const newResponse = new Response(response.body, { + status: response.status, + statusText: response.statusText, + headers: { + ...Object.fromEntries(response.headers.entries()), + 'Cross-Origin-Embedder-Policy': 'require-corp', + 'Cross-Origin-Opener-Policy': 'same-origin' + } + }); + + return newResponse; + }).catch((error) => { + console.error('Service Worker fetch failed:', error); + return fetch(event.request); + }) + ); +}); + +self.addEventListener('install', (event) => { + console.log('Service Worker: Installing for SharedArrayBuffer support'); + self.skipWaiting(); +}); + +self.addEventListener('activate', (event) => { + console.log('Service Worker: Activated for SharedArrayBuffer support'); + event.waitUntil(self.clients.claim()); +}); \ No newline at end of file diff --git a/examples/worker-system-demo/index.html b/examples/worker-system-demo/index.html index b1f78a73..de3b9004 100644 --- a/examples/worker-system-demo/index.html +++ b/examples/worker-system-demo/index.html @@ -138,6 +138,7 @@
+
@@ -163,6 +164,7 @@
实体数量: 0
Worker状态: 未启用
Worker负载: N/A
+
运行模式: 同步模式
物理系统耗时: 0ms
渲染系统耗时: 0ms
总帧时间: 0ms
diff --git a/examples/worker-system-demo/src/GameScene.ts b/examples/worker-system-demo/src/GameScene.ts index 62bfceeb..12bdcbf9 100644 --- a/examples/worker-system-demo/src/GameScene.ts +++ b/examples/worker-system-demo/src/GameScene.ts @@ -160,6 +160,20 @@ export class GameScene extends Scene { } } + /** + * 切换 SharedArrayBuffer 状态 + */ + public toggleSharedArrayBuffer(): void { + this.physicsSystem.disableSharedArrayBuffer(); + } + + /** + * 获取物理系统状态 + */ + public getPhysicsSystemStatus() { + return this.physicsSystem.getCurrentStatus(); + } + /** * 获取系统信息 */ diff --git a/examples/worker-system-demo/src/main.ts b/examples/worker-system-demo/src/main.ts index 40fb1455..454f244e 100644 --- a/examples/worker-system-demo/src/main.ts +++ b/examples/worker-system-demo/src/main.ts @@ -54,10 +54,10 @@ class WorkerDemo { private initializeUIElements(): void { const elementIds = [ - 'entityCount', 'entityCountValue', 'toggleWorker', + 'entityCount', 'entityCountValue', 'toggleWorker', 'toggleSAB', 'gravity', 'gravityValue', 'friction', 'frictionValue', 'spawnParticles', 'clearEntities', 'resetDemo', 'fps', 'entityCountStat', 'workerStatus', 'workerLoad', - 'physicsTime', 'renderTime', 'frameTime', 'memoryUsage' + 'physicsTime', 'renderTime', 'frameTime', 'memoryUsage', 'sabStatus' ]; for (const id of elementIds) { @@ -93,6 +93,14 @@ class WorkerDemo { }); } + // SharedArrayBuffer切换按钮 + if (this.elements.toggleSAB) { + this.elements.toggleSAB.addEventListener('click', () => { + this.gameScene.toggleSharedArrayBuffer(); + this.updateWorkerStatus(); + }); + } + // 重力滑块 if (this.elements.gravity && this.elements.gravityValue) { @@ -249,6 +257,7 @@ class WorkerDemo { const systemInfo = this.gameScene.getSystemInfo(); const workerInfo = systemInfo.physics; const entityCount = systemInfo.entityCount; + const status = this.gameScene.getPhysicsSystemStatus(); if (this.elements.workerStatus) { if (workerInfo.enabled) { @@ -268,6 +277,29 @@ class WorkerDemo { this.elements.workerLoad.textContent = 'N/A'; } } + + // 更新 SharedArrayBuffer 状态 + if (this.elements.sabStatus) { + const modeNames = { + 'shared-buffer': 'SharedArrayBuffer模式', + 'single-worker': '单Worker模式', + 'multi-worker': '多Worker模式', + 'sync': '同步模式' + }; + + this.elements.sabStatus.textContent = modeNames[status.mode] || status.mode; + this.elements.sabStatus.className = status.mode === 'shared-buffer' ? 'worker-enabled' : 'worker-disabled'; + } + + // 更新 SharedArrayBuffer 按钮文本 + if (this.elements.toggleSAB) { + if (status.sharedArrayBufferEnabled) { + this.elements.toggleSAB.textContent = '禁用 SharedArrayBuffer'; + } else { + this.elements.toggleSAB.textContent = '启用 SharedArrayBuffer'; + this.elements.toggleSAB.setAttribute('disabled', 'true'); // SAB 一旦禁用就无法重新启用 + } + } } private getMemoryUsage(): number { diff --git a/examples/worker-system-demo/src/systems/PhysicsWorkerSystem.ts b/examples/worker-system-demo/src/systems/PhysicsWorkerSystem.ts index 3f1ad533..3070f750 100644 --- a/examples/worker-system-demo/src/systems/PhysicsWorkerSystem.ts +++ b/examples/worker-system-demo/src/systems/PhysicsWorkerSystem.ts @@ -37,13 +37,17 @@ export class PhysicsWorkerSystem extends WorkerEntitySystem { groundFriction: 0.98 }; + // 检查 SharedArrayBuffer 是否可用 + const isSharedArrayBufferAvailable = typeof SharedArrayBuffer !== 'undefined' && self.crossOriginIsolated; + super( Matcher.empty().all(Position, Velocity, Physics), { enableWorker, - workerCount: navigator.hardwareConcurrency || 2, // 恢复多Worker + // 当 SharedArrayBuffer 可用时使用多 Worker,否则使用单 Worker 保证碰撞检测完整性 + workerCount: isSharedArrayBufferAvailable ? (navigator.hardwareConcurrency || 2) : 1, systemConfig: defaultConfig, - useSharedArrayBuffer: true // 使用SharedArrayBuffer进行全局碰撞检测 + useSharedArrayBuffer: true // 优先使用 SharedArrayBuffer } ); } @@ -226,6 +230,49 @@ export class PhysicsWorkerSystem extends WorkerEntitySystem { return { ...this.physicsConfig }; } + /** + * 禁用 SharedArrayBuffer(用于测试降级行为) + */ + public disableSharedArrayBuffer(): void { + console.log(`[${this.systemName}] Disabling SharedArrayBuffer for testing - falling back to single Worker mode`); + + // 使用正式的配置更新 API + this.updateConfig({ + useSharedArrayBuffer: false + }); + } + + /** + * 获取当前运行状态 + */ + public getCurrentStatus(): { + mode: 'shared-buffer' | 'single-worker' | 'multi-worker' | 'sync'; + sharedArrayBufferEnabled: boolean; + workerCount: number; + workerEnabled: boolean; + } { + const workerInfo = this.getWorkerInfo(); + + let mode: 'shared-buffer' | 'single-worker' | 'multi-worker' | 'sync' = 'sync'; + + if (workerInfo.enabled) { + if (workerInfo.sharedArrayBufferEnabled && workerInfo.sharedArrayBufferSupported) { + mode = 'shared-buffer'; + } else if (workerInfo.workerCount === 1) { + mode = 'single-worker'; + } else { + mode = 'multi-worker'; + } + } + + return { + mode, + sharedArrayBufferEnabled: workerInfo.sharedArrayBufferEnabled, + workerCount: workerInfo.workerCount, + workerEnabled: workerInfo.enabled + }; + } + private startTime: number = 0; @@ -329,7 +376,7 @@ export class PhysicsWorkerSystem extends WorkerEntitySystem { let y = sharedFloatArray[offset + 2]; let dx = sharedFloatArray[offset + 3]; let dy = sharedFloatArray[offset + 4]; - const mass = sharedFloatArray[offset + 5]; + // const mass = sharedFloatArray[offset + 5]; // 未使用 const bounce = sharedFloatArray[offset + 6]; const friction = sharedFloatArray[offset + 7]; const radius = sharedFloatArray[offset + 8]; diff --git a/packages/core/src/ECS/Systems/WorkerEntitySystem.ts b/packages/core/src/ECS/Systems/WorkerEntitySystem.ts index 4e1a2087..98155dbc 100644 --- a/packages/core/src/ECS/Systems/WorkerEntitySystem.ts +++ b/packages/core/src/ECS/Systems/WorkerEntitySystem.ts @@ -186,8 +186,8 @@ export abstract class WorkerEntitySystem extends EntitySystem protected config: Required> & { systemConfig?: any }; private workerPool: WebWorkerPool | null = null; private isProcessing = false; - private sharedBuffer: SharedArrayBuffer | null = null; - private sharedFloatArray: Float32Array | null = null; + protected sharedBuffer: SharedArrayBuffer | null = null; + protected sharedFloatArray: Float32Array | null = null; constructor(matcher?: Matcher, config: WorkerSystemConfig = {}) { super(matcher); @@ -248,8 +248,10 @@ export abstract class WorkerEntitySystem extends EntitySystem try { // 检查是否支持SharedArrayBuffer if (!this.isSharedArrayBufferSupported()) { - console.warn(`[${this.systemName}] SharedArrayBuffer not supported, falling back to traditional Worker mode`); + console.warn(`[${this.systemName}] SharedArrayBuffer not supported, falling back to single Worker mode for collision detection integrity`); this.config.useSharedArrayBuffer = false; + // 降级到单Worker模式:确保所有实体在同一个Worker中处理,维持实体间交互的完整性 + this.config.workerCount = 1; return; } @@ -261,10 +263,11 @@ export abstract class WorkerEntitySystem extends EntitySystem console.log(`[${this.systemName}] SharedArrayBuffer initialized successfully (${bufferSize} bytes)`); } catch (error) { - console.warn(`[${this.systemName}] SharedArrayBuffer init failed, falling back to traditional Worker mode:`, error); + console.warn(`[${this.systemName}] SharedArrayBuffer init failed, falling back to single Worker mode for collision detection integrity:`, error); this.config.useSharedArrayBuffer = false; this.sharedBuffer = null; this.sharedFloatArray = null; + this.config.workerCount = 1; } } @@ -633,8 +636,21 @@ export abstract class WorkerEntitySystem extends EntitySystem * 更新Worker配置 */ public updateConfig(newConfig: Partial): void { + const oldConfig = { ...this.config }; Object.assign(this.config, newConfig); + // 如果 SharedArrayBuffer 设置发生变化,需要重新初始化 + if (oldConfig.useSharedArrayBuffer !== this.config.useSharedArrayBuffer) { + this.reinitializeWorkerSystem(); + return; + } + + // 如果 Worker 数量发生变化,需要重新创建 Worker 池 + if (oldConfig.workerCount !== this.config.workerCount) { + this.reinitializeWorkerPool(); + return; + } + // 如果禁用Worker,清理Worker池 if (!this.config.enableWorker && this.workerPool) { this.workerPool.destroy(); @@ -647,6 +663,46 @@ export abstract class WorkerEntitySystem extends EntitySystem } } + /** + * 重新初始化整个 Worker 系统(包括 SharedArrayBuffer) + */ + private reinitializeWorkerSystem(): void { + // 清理现有资源 + if (this.workerPool) { + this.workerPool.destroy(); + this.workerPool = null; + } + this.sharedBuffer = null; + this.sharedFloatArray = null; + + // 如果禁用 SharedArrayBuffer,降级到单 Worker 模式 + if (!this.config.useSharedArrayBuffer) { + this.config.workerCount = 1; + } + + // 重新初始化 + if (this.config.enableWorker && this.isWorkerSupported()) { + if (this.config.useSharedArrayBuffer) { + this.initializeSharedArrayBuffer(); + } + this.initializeWorkerPool(); + } + } + + /** + * 重新初始化 Worker 池(保持 SharedArrayBuffer) + */ + private reinitializeWorkerPool(): void { + if (this.workerPool) { + this.workerPool.destroy(); + this.workerPool = null; + } + + if (this.config.enableWorker && this.isWorkerSupported()) { + this.initializeWorkerPool(); + } + } + /** * 获取系统性能信息 */