refactor(arch): 改进 ServiceToken 设计,统一服务获取模式 (#300)
* refactor(arch): 移除全局变量,使用 ServiceToken 模式 - 创建 PluginServiceRegistry 类,提供类型安全的服务注册/获取 - 添加 ProfilerServiceToken 和 CollisionLayerConfigToken - 重构所有 __PROFILER_SERVICE__ 全局变量访问为 getProfilerService() - 重构 __PHYSICS_RAPIER2D__ 全局变量访问为 CollisionLayerConfigToken - 在 Core 类添加 pluginServices 静态属性 - 添加 getService.ts 辅助模块简化服务获取 这是 ServiceToken 模式重构的第一阶段,移除了最常用的两个全局变量。 后续可继续应用到其他模块(Camera/Audio 等)。 * refactor(arch): 改进 ServiceToken 设计,移除重复常量 - tokens.ts: 从 engine-core 导入 createServiceToken(符合规范) - tokens.ts: Token 使用接口 IProfilerService 而非具体类 - 移除 AssetPickerDialog 和 ContentBrowser 中重复的 MANAGED_ASSET_DIRECTORIES - 统一从 editor-core 导入 MANAGED_ASSET_DIRECTORIES * fix(type): 修复 IProfilerService 接口与实现类型不匹配 - 将 ProfilerData 等数据类型移到 tokens.ts 以避免循环依赖 - ProfilerService 显式实现 IProfilerService 接口 - 更新使用方使用 IProfilerService 接口类型而非具体类 * refactor(type): 移除类型重导出,改进类型安全 - 删除 ProfilerService.ts 中的类型重导出,消费方直接从 tokens.ts 导入 - PanelDescriptor 接口添加 titleZh 属性,移除 App.tsx 中的 as any - 改进 useDynamicIcon.ts 的类型安全,使用正确的 Record 类型 * refactor(arch): 为模块添加 ServiceToken 支持 - Material System: 创建 tokens.ts,定义 IMaterialManager 接口和 MaterialManagerToken - Audio: 创建预留 tokens.ts 文件,为未来 AudioManager 服务扩展做准备 - Camera: 创建预留 tokens.ts 文件,为未来 CameraManager 服务扩展做准备 遵循"谁定义接口,谁导出 Token"原则,统一服务访问模式
This commit is contained in:
@@ -55,105 +55,283 @@ for (const file of filesToCopy) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建 game.js
|
// 创建 game.js - 完整物理球可视化演示
|
||||||
|
// Create game.js - Full physics ball visualization demo
|
||||||
const gameJs = `/**
|
const gameJs = `/**
|
||||||
* ESEngine Worker System 微信小游戏测试
|
* ESEngine Worker System 微信小游戏物理演示
|
||||||
* ESEngine Worker System WeChat Mini Game Test
|
* ESEngine Worker System WeChat Mini Game Physics Demo
|
||||||
|
*
|
||||||
|
* 演示 Worker 线程处理物理计算,主线程渲染
|
||||||
|
* Demonstrates Worker thread physics + main thread rendering
|
||||||
*/
|
*/
|
||||||
|
|
||||||
console.log('====================================');
|
// ============ 配置 | Configuration ============
|
||||||
console.log('ESEngine Worker 微信小游戏测试');
|
var CONFIG = {
|
||||||
console.log('====================================');
|
BALL_COUNT: 20, // 球数量 | Number of balls
|
||||||
|
GRAVITY: 400, // 重力 | Gravity
|
||||||
|
GROUND_FRICTION: 0.98, // 地面摩擦 | Ground friction
|
||||||
|
BALL_BOUNCE: 0.85, // 弹性系数 | Bounce factor
|
||||||
|
MIN_RADIUS: 8, // 最小半径 | Min radius
|
||||||
|
MAX_RADIUS: 20, // 最大半径 | Max radius
|
||||||
|
COLORS: ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7', '#DDA0DD', '#98D8C8', '#F7DC6F']
|
||||||
|
};
|
||||||
|
|
||||||
// 检查 Worker API
|
// ============ 全局状态 | Global State ============
|
||||||
console.log('\\n[1] 检查环境...');
|
var canvas = wx.createCanvas();
|
||||||
console.log('wx.createWorker:', typeof wx.createWorker);
|
var ctx = canvas.getContext('2d');
|
||||||
|
|
||||||
// 创建 Worker
|
|
||||||
console.log('\\n[2] 创建 Worker...');
|
|
||||||
var worker = null;
|
var worker = null;
|
||||||
|
var entities = [];
|
||||||
|
var lastTime = Date.now();
|
||||||
|
var frameCount = 0;
|
||||||
|
var fps = 0;
|
||||||
|
var workerReady = false;
|
||||||
|
var pendingRequest = false;
|
||||||
|
|
||||||
try {
|
console.log('====================================');
|
||||||
|
console.log('ESEngine Worker 物理演示');
|
||||||
|
console.log('Canvas:', canvas.width, 'x', canvas.height);
|
||||||
|
console.log('====================================');
|
||||||
|
|
||||||
|
// ============ 初始化实体 | Initialize Entities ============
|
||||||
|
function initEntities() {
|
||||||
|
entities = [];
|
||||||
|
for (var i = 0; i < CONFIG.BALL_COUNT; i++) {
|
||||||
|
var radius = CONFIG.MIN_RADIUS + Math.random() * (CONFIG.MAX_RADIUS - CONFIG.MIN_RADIUS);
|
||||||
|
entities.push({
|
||||||
|
id: i + 1,
|
||||||
|
x: radius + Math.random() * (canvas.width - radius * 2),
|
||||||
|
y: radius + Math.random() * (canvas.height * 0.5), // 上半部分生成
|
||||||
|
dx: (Math.random() - 0.5) * 200,
|
||||||
|
dy: Math.random() * 100,
|
||||||
|
mass: radius * 0.1,
|
||||||
|
bounce: CONFIG.BALL_BOUNCE,
|
||||||
|
friction: CONFIG.GROUND_FRICTION,
|
||||||
|
radius: radius,
|
||||||
|
color: CONFIG.COLORS[i % CONFIG.COLORS.length]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
console.log('Created', entities.length, 'balls');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============ 创建 Worker | Create Worker ============
|
||||||
|
function createWorker() {
|
||||||
|
try {
|
||||||
worker = wx.createWorker('workers/physics-worker.js', {
|
worker = wx.createWorker('workers/physics-worker.js', {
|
||||||
useExperimentalWorker: true
|
useExperimentalWorker: true
|
||||||
});
|
});
|
||||||
console.log('Worker 创建成功!');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Worker 创建失败:', error.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (worker) {
|
|
||||||
// 设置消息处理
|
|
||||||
worker.onMessage(function(res) {
|
worker.onMessage(function(res) {
|
||||||
console.log('\\n[4] 收到 Worker 响应!');
|
pendingRequest = false;
|
||||||
|
|
||||||
if (res.error) {
|
if (res.error) {
|
||||||
console.error('Worker 错误:', res.error);
|
console.error('Worker error:', res.error);
|
||||||
} else if (res.result) {
|
return;
|
||||||
console.log('Worker 处理成功!');
|
|
||||||
console.log('实体数量:', res.result.length);
|
|
||||||
|
|
||||||
// 显示前 3 个实体
|
|
||||||
for (var i = 0; i < Math.min(3, res.result.length); i++) {
|
|
||||||
var e = res.result[i];
|
|
||||||
console.log(' 实体 ' + e.id + ': (' + e.x.toFixed(1) + ', ' + e.y.toFixed(1) + ')');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('\\n========== Worker 测试成功! ==========');
|
if (res.result && Array.isArray(res.result)) {
|
||||||
|
// 更新实体位置(保留颜色等渲染属性)
|
||||||
|
// Update entity positions (keep rendering properties like color)
|
||||||
|
for (var i = 0; i < res.result.length; i++) {
|
||||||
|
var updated = res.result[i];
|
||||||
|
var entity = entities[i];
|
||||||
|
if (entity && updated) {
|
||||||
|
entity.x = updated.x;
|
||||||
|
entity.y = updated.y;
|
||||||
|
entity.dx = updated.dx;
|
||||||
|
entity.dy = updated.dy;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 创建测试数据
|
workerReady = true;
|
||||||
console.log('\\n[3] 发送测试数据...');
|
console.log('Worker created successfully!');
|
||||||
|
} catch (error) {
|
||||||
var entities = [];
|
console.error('Worker creation failed:', error.message);
|
||||||
for (var i = 0; i < 10; i++) {
|
workerReady = false;
|
||||||
entities.push({
|
|
||||||
id: i + 1,
|
|
||||||
x: Math.random() * 300 + 37,
|
|
||||||
y: Math.random() * 400 + 100,
|
|
||||||
dx: (Math.random() - 0.5) * 100,
|
|
||||||
dy: (Math.random() - 0.5) * 50,
|
|
||||||
mass: 1 + Math.random(),
|
|
||||||
bounce: 0.8,
|
|
||||||
friction: 0.98,
|
|
||||||
radius: 5 + Math.random() * 5
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
worker.postMessage({
|
|
||||||
id: Date.now(),
|
|
||||||
entities: entities,
|
|
||||||
deltaTime: 0.016,
|
|
||||||
systemConfig: {
|
|
||||||
gravity: 200,
|
|
||||||
canvasWidth: 375,
|
|
||||||
canvasHeight: 667,
|
|
||||||
groundFriction: 0.98
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('已发送 ' + entities.length + ' 个实体');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建 Canvas 显示
|
// ============ 发送物理更新到 Worker | Send Physics Update to Worker ============
|
||||||
var canvas = wx.createCanvas();
|
function sendToWorker(deltaTime) {
|
||||||
var ctx = canvas.getContext('2d');
|
if (!worker || !workerReady || pendingRequest) return;
|
||||||
|
|
||||||
ctx.fillStyle = '#1a1a2e';
|
// 准备发送数据(只发送物理相关属性)
|
||||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
// Prepare data (only physics-related properties)
|
||||||
|
var physicsData = [];
|
||||||
|
for (var i = 0; i < entities.length; i++) {
|
||||||
|
var e = entities[i];
|
||||||
|
physicsData.push({
|
||||||
|
id: e.id,
|
||||||
|
x: e.x,
|
||||||
|
y: e.y,
|
||||||
|
dx: e.dx,
|
||||||
|
dy: e.dy,
|
||||||
|
mass: e.mass,
|
||||||
|
bounce: e.bounce,
|
||||||
|
friction: e.friction,
|
||||||
|
radius: e.radius
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
ctx.fillStyle = '#ffffff';
|
pendingRequest = true;
|
||||||
ctx.font = '18px Arial';
|
worker.postMessage({
|
||||||
ctx.textAlign = 'center';
|
id: Date.now(),
|
||||||
ctx.fillText('ESEngine Worker 测试', canvas.width / 2, 50);
|
entities: physicsData,
|
||||||
|
deltaTime: deltaTime,
|
||||||
|
systemConfig: {
|
||||||
|
gravity: CONFIG.GRAVITY,
|
||||||
|
canvasWidth: canvas.width,
|
||||||
|
canvasHeight: canvas.height,
|
||||||
|
groundFriction: CONFIG.GROUND_FRICTION
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
ctx.font = '14px Arial';
|
// ============ 渲染 | Render ============
|
||||||
ctx.fillStyle = '#aaaaaa';
|
function render() {
|
||||||
ctx.fillText('查看控制台日志', canvas.width / 2, 80);
|
// 清屏 - 深色背景
|
||||||
|
// Clear screen - dark background
|
||||||
|
ctx.fillStyle = '#1a1a2e';
|
||||||
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
ctx.fillStyle = worker ? '#00ff00' : '#ff0000';
|
// 绘制地面
|
||||||
ctx.fillText('Worker: ' + (worker ? '已创建' : '创建失败'), canvas.width / 2, 110);
|
// Draw ground
|
||||||
|
ctx.fillStyle = '#2d3436';
|
||||||
|
ctx.fillRect(0, canvas.height - 10, canvas.width, 10);
|
||||||
|
|
||||||
|
// 绘制所有球
|
||||||
|
// Draw all balls
|
||||||
|
for (var i = 0; i < entities.length; i++) {
|
||||||
|
var e = entities[i];
|
||||||
|
|
||||||
|
// 球体渐变效果
|
||||||
|
// Ball gradient effect
|
||||||
|
var gradient = ctx.createRadialGradient(
|
||||||
|
e.x - e.radius * 0.3, e.y - e.radius * 0.3, 0,
|
||||||
|
e.x, e.y, e.radius
|
||||||
|
);
|
||||||
|
gradient.addColorStop(0, '#ffffff');
|
||||||
|
gradient.addColorStop(0.3, e.color);
|
||||||
|
gradient.addColorStop(1, shadeColor(e.color, -30));
|
||||||
|
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(e.x, e.y, e.radius, 0, Math.PI * 2);
|
||||||
|
ctx.fillStyle = gradient;
|
||||||
|
ctx.fill();
|
||||||
|
|
||||||
|
// 球体边框
|
||||||
|
// Ball border
|
||||||
|
ctx.strokeStyle = shadeColor(e.color, -50);
|
||||||
|
ctx.lineWidth = 1;
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 绘制 UI
|
||||||
|
// Draw UI
|
||||||
|
ctx.fillStyle = '#ffffff';
|
||||||
|
ctx.font = '14px Arial';
|
||||||
|
ctx.textAlign = 'left';
|
||||||
|
ctx.fillText('ESEngine Worker Physics Demo', 10, 25);
|
||||||
|
ctx.fillText('FPS: ' + fps + ' | Balls: ' + entities.length, 10, 45);
|
||||||
|
ctx.fillText('Worker: ' + (workerReady ? 'Active' : 'Failed'), 10, 65);
|
||||||
|
|
||||||
|
// 提示文字
|
||||||
|
// Hint text
|
||||||
|
ctx.textAlign = 'center';
|
||||||
|
ctx.fillStyle = '#888888';
|
||||||
|
ctx.font = '12px Arial';
|
||||||
|
ctx.fillText('Physics calculated in Worker thread', canvas.width / 2, canvas.height - 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 颜色加深/减淡工具函数
|
||||||
|
// Color shade utility function
|
||||||
|
function shadeColor(color, percent) {
|
||||||
|
var num = parseInt(color.replace('#', ''), 16);
|
||||||
|
var amt = Math.round(2.55 * percent);
|
||||||
|
var R = (num >> 16) + amt;
|
||||||
|
var G = (num >> 8 & 0x00FF) + amt;
|
||||||
|
var B = (num & 0x0000FF) + amt;
|
||||||
|
R = Math.max(0, Math.min(255, R));
|
||||||
|
G = Math.max(0, Math.min(255, G));
|
||||||
|
B = Math.max(0, Math.min(255, B));
|
||||||
|
return '#' + (0x1000000 + R * 0x10000 + G * 0x100 + B).toString(16).slice(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============ 游戏循环 | Game Loop ============
|
||||||
|
function gameLoop() {
|
||||||
|
var now = Date.now();
|
||||||
|
var deltaTime = (now - lastTime) / 1000;
|
||||||
|
lastTime = now;
|
||||||
|
|
||||||
|
// 限制 deltaTime 防止跳帧
|
||||||
|
// Clamp deltaTime to prevent frame skip
|
||||||
|
if (deltaTime > 0.1) deltaTime = 0.1;
|
||||||
|
|
||||||
|
// FPS 计算
|
||||||
|
// FPS calculation
|
||||||
|
frameCount++;
|
||||||
|
if (frameCount >= 30) {
|
||||||
|
fps = Math.round(30 / ((now - (lastTime - deltaTime * 1000 * 30)) / 1000));
|
||||||
|
frameCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送物理计算到 Worker
|
||||||
|
// Send physics calculation to Worker
|
||||||
|
sendToWorker(deltaTime);
|
||||||
|
|
||||||
|
// 渲染
|
||||||
|
// Render
|
||||||
|
render();
|
||||||
|
|
||||||
|
// 下一帧
|
||||||
|
// Next frame
|
||||||
|
requestAnimationFrame(gameLoop);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============ 启动 | Start ============
|
||||||
|
initEntities();
|
||||||
|
createWorker();
|
||||||
|
|
||||||
|
// 简单的 FPS 计算变量
|
||||||
|
var fpsLastTime = Date.now();
|
||||||
|
var fpsFrameCount = 0;
|
||||||
|
|
||||||
|
// 覆盖 FPS 计算逻辑
|
||||||
|
setInterval(function() {
|
||||||
|
var now = Date.now();
|
||||||
|
fps = Math.round(fpsFrameCount * 1000 / (now - fpsLastTime));
|
||||||
|
fpsLastTime = now;
|
||||||
|
fpsFrameCount = 0;
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
// 修改 gameLoop 中的帧计数
|
||||||
|
var originalGameLoop = gameLoop;
|
||||||
|
gameLoop = function() {
|
||||||
|
fpsFrameCount++;
|
||||||
|
|
||||||
|
var now = Date.now();
|
||||||
|
var deltaTime = (now - lastTime) / 1000;
|
||||||
|
lastTime = now;
|
||||||
|
|
||||||
|
if (deltaTime > 0.1) deltaTime = 0.1;
|
||||||
|
|
||||||
|
sendToWorker(deltaTime);
|
||||||
|
render();
|
||||||
|
requestAnimationFrame(gameLoop);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 开始游戏循环
|
||||||
|
// Start game loop
|
||||||
|
console.log('Starting game loop...');
|
||||||
|
requestAnimationFrame(gameLoop);
|
||||||
|
|
||||||
|
// 触摸重置
|
||||||
|
// Touch to reset
|
||||||
|
wx.onTouchStart(function(e) {
|
||||||
|
console.log('Touch detected - resetting balls');
|
||||||
|
initEntities();
|
||||||
|
});
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const gameJsPath = path.join(WECHAT_PROJECT, 'game.js');
|
const gameJsPath = path.join(WECHAT_PROJECT, 'game.js');
|
||||||
|
|||||||
6
examples/wechat-worker-demo/worker-mapping.json
Normal file
6
examples/wechat-worker-demo/worker-mapping.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"generatedAt": "2025-12-08T10:33:50.647Z",
|
||||||
|
"mappings": {
|
||||||
|
"PhysicsWorkerSystem": "workers/physics-worker.js"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,2 +1,6 @@
|
|||||||
export { AudioSourceComponent } from './AudioSourceComponent';
|
export { AudioSourceComponent } from './AudioSourceComponent';
|
||||||
export { AudioPlugin } from './AudioPlugin';
|
export { AudioPlugin } from './AudioPlugin';
|
||||||
|
|
||||||
|
// Service Tokens (reserved for future use)
|
||||||
|
// 服务令牌(预留用于未来扩展)
|
||||||
|
// export { AudioManagerToken, type IAudioManager } from './tokens';
|
||||||
|
|||||||
31
packages/audio/src/tokens.ts
Normal file
31
packages/audio/src/tokens.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
* Audio Module Service Tokens
|
||||||
|
* 音频模块服务令牌
|
||||||
|
*
|
||||||
|
* 遵循"谁定义接口,谁导出 Token"原则。
|
||||||
|
* Following "who defines interface, who exports Token" principle.
|
||||||
|
*
|
||||||
|
* 当前模块仅提供组件,暂无服务定义。
|
||||||
|
* 此文件预留用于未来可能添加的 AudioManager 服务。
|
||||||
|
*
|
||||||
|
* Currently this module only provides components, no services defined yet.
|
||||||
|
* This file is reserved for potential future AudioManager service.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// import { createServiceToken } from '@esengine/engine-core';
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Reserved for future service tokens
|
||||||
|
// 预留用于未来的服务令牌
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// export interface IAudioManager {
|
||||||
|
// // 播放音效 | Play sound effect
|
||||||
|
// playSound(path: string): void;
|
||||||
|
// // 播放背景音乐 | Play background music
|
||||||
|
// playMusic(path: string): void;
|
||||||
|
// // 停止所有音频 | Stop all audio
|
||||||
|
// stopAll(): void;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// export const AudioManagerToken = createServiceToken<IAudioManager>('audioManager');
|
||||||
@@ -1,2 +1,6 @@
|
|||||||
export { CameraComponent, ECameraProjection, CameraProjection } from './CameraComponent';
|
export { CameraComponent, ECameraProjection, CameraProjection } from './CameraComponent';
|
||||||
export { CameraPlugin } from './CameraPlugin';
|
export { CameraPlugin } from './CameraPlugin';
|
||||||
|
|
||||||
|
// Service Tokens (reserved for future use)
|
||||||
|
// 服务令牌(预留用于未来扩展)
|
||||||
|
// export { CameraManagerToken, type ICameraManager } from './tokens';
|
||||||
|
|||||||
31
packages/camera/src/tokens.ts
Normal file
31
packages/camera/src/tokens.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
* Camera Module Service Tokens
|
||||||
|
* 相机模块服务令牌
|
||||||
|
*
|
||||||
|
* 遵循"谁定义接口,谁导出 Token"原则。
|
||||||
|
* Following "who defines interface, who exports Token" principle.
|
||||||
|
*
|
||||||
|
* 当前模块仅提供组件,暂无服务定义。
|
||||||
|
* 此文件预留用于未来可能添加的 CameraManager 服务。
|
||||||
|
*
|
||||||
|
* Currently this module only provides components, no services defined yet.
|
||||||
|
* This file is reserved for potential future CameraManager service.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// import { createServiceToken } from '@esengine/engine-core';
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Reserved for future service tokens
|
||||||
|
// 预留用于未来的服务令牌
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// export interface ICameraManager {
|
||||||
|
// // 获取主相机 | Get main camera
|
||||||
|
// getMainCamera(): CameraComponent | null;
|
||||||
|
// // 设置主相机 | Set main camera
|
||||||
|
// setMainCamera(camera: CameraComponent): void;
|
||||||
|
// // 屏幕坐标转世界坐标 | Screen to world coordinates
|
||||||
|
// screenToWorld(screenX: number, screenY: number): { x: number; y: number };
|
||||||
|
// }
|
||||||
|
|
||||||
|
// export const CameraManagerToken = createServiceToken<ICameraManager>('cameraManager');
|
||||||
@@ -11,6 +11,7 @@ import { SceneManager } from './ECS/SceneManager';
|
|||||||
import { IScene } from './ECS/IScene';
|
import { IScene } from './ECS/IScene';
|
||||||
import { ServiceContainer } from './Core/ServiceContainer';
|
import { ServiceContainer } from './Core/ServiceContainer';
|
||||||
import { PluginManager } from './Core/PluginManager';
|
import { PluginManager } from './Core/PluginManager';
|
||||||
|
import { PluginServiceRegistry } from './Core/PluginServiceRegistry';
|
||||||
import { IPlugin } from './Core/Plugin';
|
import { IPlugin } from './Core/Plugin';
|
||||||
import { WorldManager } from './ECS/WorldManager';
|
import { WorldManager } from './ECS/WorldManager';
|
||||||
import { DebugConfigService } from './Utils/Debug/DebugConfigService';
|
import { DebugConfigService } from './Utils/Debug/DebugConfigService';
|
||||||
@@ -109,6 +110,14 @@ export class Core {
|
|||||||
*/
|
*/
|
||||||
private _pluginManager: PluginManager;
|
private _pluginManager: PluginManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件服务注册表
|
||||||
|
*
|
||||||
|
* 基于 ServiceToken 的类型安全服务注册表。
|
||||||
|
* Type-safe service registry based on ServiceToken.
|
||||||
|
*/
|
||||||
|
private _pluginServiceRegistry: PluginServiceRegistry;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Core配置
|
* Core配置
|
||||||
*/
|
*/
|
||||||
@@ -168,6 +177,11 @@ export class Core {
|
|||||||
this._pluginManager.initialize(this, this._serviceContainer);
|
this._pluginManager.initialize(this, this._serviceContainer);
|
||||||
this._serviceContainer.registerInstance(PluginManager, this._pluginManager);
|
this._serviceContainer.registerInstance(PluginManager, this._pluginManager);
|
||||||
|
|
||||||
|
// 初始化插件服务注册表
|
||||||
|
// Initialize plugin service registry
|
||||||
|
this._pluginServiceRegistry = new PluginServiceRegistry();
|
||||||
|
this._serviceContainer.registerInstance(PluginServiceRegistry, this._pluginServiceRegistry);
|
||||||
|
|
||||||
this.debug = this._config.debug ?? true;
|
this.debug = this._config.debug ?? true;
|
||||||
|
|
||||||
// 初始化调试管理器
|
// 初始化调试管理器
|
||||||
@@ -220,6 +234,39 @@ export class Core {
|
|||||||
return this._instance._serviceContainer;
|
return this._instance._serviceContainer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取插件服务注册表
|
||||||
|
*
|
||||||
|
* 用于基于 ServiceToken 的类型安全服务注册和获取。
|
||||||
|
* For type-safe service registration and retrieval based on ServiceToken.
|
||||||
|
*
|
||||||
|
* @returns PluginServiceRegistry 实例
|
||||||
|
* @throws 如果 Core 实例未创建
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* import { createServiceToken } from '@esengine/ecs-framework';
|
||||||
|
*
|
||||||
|
* // 定义服务令牌
|
||||||
|
* const MyServiceToken = createServiceToken<IMyService>('myService');
|
||||||
|
*
|
||||||
|
* // 注册服务
|
||||||
|
* Core.pluginServices.register(MyServiceToken, myServiceInstance);
|
||||||
|
*
|
||||||
|
* // 获取服务(可选)
|
||||||
|
* const service = Core.pluginServices.get(MyServiceToken);
|
||||||
|
*
|
||||||
|
* // 获取服务(必需,不存在则抛异常)
|
||||||
|
* const service = Core.pluginServices.require(MyServiceToken);
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
public static get pluginServices(): PluginServiceRegistry {
|
||||||
|
if (!this._instance) {
|
||||||
|
throw new Error('Core实例未创建,请先调用Core.create()');
|
||||||
|
}
|
||||||
|
return this._instance._pluginServiceRegistry;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取World管理器
|
* 获取World管理器
|
||||||
*
|
*
|
||||||
|
|||||||
135
packages/core/src/Core/PluginServiceRegistry.ts
Normal file
135
packages/core/src/Core/PluginServiceRegistry.ts
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
/**
|
||||||
|
* 插件服务注册表
|
||||||
|
* Plugin Service Registry
|
||||||
|
*
|
||||||
|
* 基于 ServiceToken 的类型安全服务注册表。
|
||||||
|
* Type-safe service registry based on ServiceToken.
|
||||||
|
*
|
||||||
|
* 设计原则 | Design principles:
|
||||||
|
* 1. 类型安全 - 使用 ServiceToken 携带类型信息
|
||||||
|
* 2. 显式依赖 - 通过导入 token 明确表达依赖关系
|
||||||
|
* 3. 可选依赖 - get 返回 undefined,require 抛异常
|
||||||
|
* 4. 单一职责 - 只负责服务注册和查询,不涉及生命周期管理
|
||||||
|
* 5. 谁定义接口,谁导出 Token - 各模块定义自己的接口和 Token
|
||||||
|
*/
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 服务令牌 | Service Token
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 服务令牌接口
|
||||||
|
* Service token interface
|
||||||
|
*
|
||||||
|
* 用于类型安全的服务注册和获取。
|
||||||
|
* For type-safe service registration and retrieval.
|
||||||
|
*
|
||||||
|
* 注意:__phantom 是必需属性,确保 TypeScript 在跨包类型解析时保留泛型类型信息。
|
||||||
|
* Note: __phantom is a required property to ensure TypeScript preserves generic
|
||||||
|
* type information across packages.
|
||||||
|
*/
|
||||||
|
export interface ServiceToken<T> {
|
||||||
|
readonly id: symbol;
|
||||||
|
readonly name: string;
|
||||||
|
/**
|
||||||
|
* Phantom type 标记(强制类型推断)
|
||||||
|
* Phantom type marker (enforces type inference)
|
||||||
|
*/
|
||||||
|
readonly __phantom: T;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建服务令牌
|
||||||
|
* Create a service token
|
||||||
|
*
|
||||||
|
* @param name 令牌名称 | Token name
|
||||||
|
* @returns 服务令牌 | Service token
|
||||||
|
*/
|
||||||
|
export function createServiceToken<T>(name: string): ServiceToken<T> {
|
||||||
|
// __phantom 仅用于类型推断,运行时不需要实际值
|
||||||
|
// __phantom is only for type inference, no actual value needed at runtime
|
||||||
|
return {
|
||||||
|
id: Symbol(name),
|
||||||
|
name
|
||||||
|
} as ServiceToken<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 插件服务注册表 | Plugin Service Registry
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件服务注册表
|
||||||
|
* Plugin service registry
|
||||||
|
*
|
||||||
|
* 用于跨插件共享服务的类型安全注册表。
|
||||||
|
* Type-safe registry for sharing services between plugins.
|
||||||
|
*/
|
||||||
|
export class PluginServiceRegistry {
|
||||||
|
private _services = new Map<symbol, unknown>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册服务
|
||||||
|
* Register a service
|
||||||
|
*/
|
||||||
|
register<T>(token: ServiceToken<T>, service: T): void {
|
||||||
|
this._services.set(token.id, service);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取服务(可选)
|
||||||
|
* Get a service (optional)
|
||||||
|
*/
|
||||||
|
get<T>(token: ServiceToken<T>): T | undefined {
|
||||||
|
return this._services.get(token.id) as T | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取服务(必需)
|
||||||
|
* Get a service (required)
|
||||||
|
*
|
||||||
|
* @throws 如果服务未注册 | If service is not registered
|
||||||
|
*/
|
||||||
|
require<T>(token: ServiceToken<T>): T {
|
||||||
|
const service = this._services.get(token.id);
|
||||||
|
if (service === undefined) {
|
||||||
|
throw new Error(`Service not found: ${token.name}`);
|
||||||
|
}
|
||||||
|
return service as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查服务是否已注册
|
||||||
|
* Check if a service is registered
|
||||||
|
*/
|
||||||
|
has<T>(token: ServiceToken<T>): boolean {
|
||||||
|
return this._services.has(token.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注销服务
|
||||||
|
* Unregister a service
|
||||||
|
*/
|
||||||
|
unregister<T>(token: ServiceToken<T>): boolean {
|
||||||
|
return this._services.delete(token.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清空所有服务
|
||||||
|
* Clear all services
|
||||||
|
*/
|
||||||
|
clear(): void {
|
||||||
|
this._services.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 释放资源
|
||||||
|
* Dispose resources
|
||||||
|
*
|
||||||
|
* 实现 IService 接口,在服务容器清理时调用。
|
||||||
|
* Implements IService interface, called when service container is cleaned up.
|
||||||
|
*/
|
||||||
|
dispose(): void {
|
||||||
|
this.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,11 @@ export { Core } from './Core';
|
|||||||
export { ServiceContainer, ServiceLifetime } from './Core/ServiceContainer';
|
export { ServiceContainer, ServiceLifetime } from './Core/ServiceContainer';
|
||||||
export type { IService, ServiceType, ServiceIdentifier } from './Core/ServiceContainer';
|
export type { IService, ServiceType, ServiceIdentifier } from './Core/ServiceContainer';
|
||||||
|
|
||||||
|
// 插件服务注册表(基于 ServiceToken 的类型安全服务管理)
|
||||||
|
// Plugin Service Registry (type-safe service management based on ServiceToken)
|
||||||
|
export { PluginServiceRegistry, createServiceToken } from './Core/PluginServiceRegistry';
|
||||||
|
export type { ServiceToken } from './Core/PluginServiceRegistry';
|
||||||
|
|
||||||
// 插件系统
|
// 插件系统
|
||||||
export { PluginManager } from './Core/PluginManager';
|
export { PluginManager } from './Core/PluginManager';
|
||||||
export { PluginState } from './Core/Plugin';
|
export { PluginState } from './Core/Plugin';
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import * as ReactDOM from 'react-dom';
|
|||||||
import * as ReactJSXRuntime from 'react/jsx-runtime';
|
import * as ReactJSXRuntime from 'react/jsx-runtime';
|
||||||
import { Core, createLogger, Scene } from '@esengine/ecs-framework';
|
import { Core, createLogger, Scene } from '@esengine/ecs-framework';
|
||||||
import * as ECSFramework from '@esengine/ecs-framework';
|
import * as ECSFramework from '@esengine/ecs-framework';
|
||||||
|
import { getProfilerService } from './services/getService';
|
||||||
|
|
||||||
// 将 React 暴露到全局,供动态加载的插件使用
|
// 将 React 暴露到全局,供动态加载的插件使用
|
||||||
// editor-runtime.js 将 React 设为 external,需要从全局获取
|
// editor-runtime.js 将 React 设为 external,需要从全局获取
|
||||||
@@ -207,14 +208,15 @@ function App() {
|
|||||||
}, [messageHub, showToast]);
|
}, [messageHub, showToast]);
|
||||||
|
|
||||||
// 监听远程连接状态
|
// 监听远程连接状态
|
||||||
|
// Monitor remote connection status
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const checkConnection = () => {
|
const checkConnection = () => {
|
||||||
const profilerService = (window as any).__PROFILER_SERVICE__;
|
const profilerService = getProfilerService();
|
||||||
const connected = profilerService && profilerService.isConnected();
|
const connected = !!(profilerService && profilerService.isConnected());
|
||||||
|
|
||||||
setIsRemoteConnected((prevConnected) => {
|
setIsRemoteConnected((prevConnected) => {
|
||||||
if (connected !== prevConnected) {
|
if (connected !== prevConnected) {
|
||||||
// 状态发生变化
|
// 状态发生变化 | State has changed
|
||||||
if (connected) {
|
if (connected) {
|
||||||
setStatus(t('header.status.remoteConnected'));
|
setStatus(t('header.status.remoteConnected'));
|
||||||
} else {
|
} else {
|
||||||
@@ -246,7 +248,8 @@ function App() {
|
|||||||
initRef.current = true;
|
initRef.current = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
(window as any).__ECS_FRAMEWORK__ = ECSFramework;
|
// ECS Framework 已通过 PluginSDKRegistry 暴露到全局
|
||||||
|
// ECS Framework is exposed globally via PluginSDKRegistry
|
||||||
|
|
||||||
const editorScene = new Scene();
|
const editorScene = new Scene();
|
||||||
Core.setScene(editorScene);
|
Core.setScene(editorScene);
|
||||||
@@ -775,7 +778,7 @@ function App() {
|
|||||||
const Component = panelDesc.component;
|
const Component = panelDesc.component;
|
||||||
return {
|
return {
|
||||||
id: panelDesc.id,
|
id: panelDesc.id,
|
||||||
title: (panelDesc as any).titleZh && locale === 'zh' ? (panelDesc as any).titleZh : panelDesc.title,
|
title: panelDesc.titleZh && locale === 'zh' ? panelDesc.titleZh : panelDesc.title,
|
||||||
content: <Component key={`${panelDesc.id}-${pluginUpdateTrigger}`} projectPath={currentProjectPath} />,
|
content: <Component key={`${panelDesc.id}-${pluginUpdateTrigger}`} projectPath={currentProjectPath} />,
|
||||||
closable: panelDesc.closable ?? true
|
closable: panelDesc.closable ?? true
|
||||||
};
|
};
|
||||||
@@ -791,7 +794,7 @@ function App() {
|
|||||||
const panelDesc = uiRegistry.getPanel(panelId)!;
|
const panelDesc = uiRegistry.getPanel(panelId)!;
|
||||||
// 优先使用动态标题,否则使用默认标题
|
// 优先使用动态标题,否则使用默认标题
|
||||||
const customTitle = dynamicPanelTitles.get(panelId);
|
const customTitle = dynamicPanelTitles.get(panelId);
|
||||||
const defaultTitle = (panelDesc as any).titleZh && locale === 'zh' ? (panelDesc as any).titleZh : panelDesc.title;
|
const defaultTitle = panelDesc.titleZh && locale === 'zh' ? panelDesc.titleZh : panelDesc.title;
|
||||||
|
|
||||||
// 支持 component 或 render 两种方式
|
// 支持 component 或 render 两种方式
|
||||||
let content: React.ReactNode;
|
let content: React.ReactNode;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
import { X, BarChart3, Maximize2, Minimize2 } from 'lucide-react';
|
import { X, BarChart3, Maximize2, Minimize2 } from 'lucide-react';
|
||||||
import { ProfilerService } from '../services/ProfilerService';
|
import { Core } from '@esengine/ecs-framework';
|
||||||
|
import { ProfilerServiceToken, type IProfilerService } from '../services/tokens';
|
||||||
import { AdvancedProfiler } from './AdvancedProfiler';
|
import { AdvancedProfiler } from './AdvancedProfiler';
|
||||||
import '../styles/ProfilerWindow.css';
|
import '../styles/ProfilerWindow.css';
|
||||||
|
|
||||||
@@ -8,20 +9,20 @@ interface AdvancedProfilerWindowProps {
|
|||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface WindowWithProfiler extends Window {
|
|
||||||
__PROFILER_SERVICE__?: ProfilerService;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function AdvancedProfilerWindow({ onClose }: AdvancedProfilerWindowProps) {
|
export function AdvancedProfilerWindow({ onClose }: AdvancedProfilerWindowProps) {
|
||||||
const [profilerService, setProfilerService] = useState<ProfilerService | null>(null);
|
const [profilerService, setProfilerService] = useState<IProfilerService | null>(null);
|
||||||
const [isConnected, setIsConnected] = useState(false);
|
const [isConnected, setIsConnected] = useState(false);
|
||||||
const [isFullscreen, setIsFullscreen] = useState(false);
|
const [isFullscreen, setIsFullscreen] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const service = (window as WindowWithProfiler).__PROFILER_SERVICE__;
|
try {
|
||||||
|
const service = Core.pluginServices.get(ProfilerServiceToken);
|
||||||
if (service) {
|
if (service) {
|
||||||
setProfilerService(service);
|
setProfilerService(service);
|
||||||
}
|
}
|
||||||
|
} catch {
|
||||||
|
// Core 可能还没有初始化
|
||||||
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -40,22 +40,13 @@ import {
|
|||||||
AlertTriangle
|
AlertTriangle
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { Core } from '@esengine/ecs-framework';
|
import { Core } from '@esengine/ecs-framework';
|
||||||
import { MessageHub, FileActionRegistry, AssetRegistryService, type FileCreationTemplate } from '@esengine/editor-core';
|
import { MessageHub, FileActionRegistry, AssetRegistryService, MANAGED_ASSET_DIRECTORIES, type FileCreationTemplate } from '@esengine/editor-core';
|
||||||
import { TauriAPI, DirectoryEntry } from '../api/tauri';
|
import { TauriAPI, DirectoryEntry } from '../api/tauri';
|
||||||
import { SettingsService } from '../services/SettingsService';
|
import { SettingsService } from '../services/SettingsService';
|
||||||
import { ContextMenu, ContextMenuItem } from './ContextMenu';
|
import { ContextMenu, ContextMenuItem } from './ContextMenu';
|
||||||
import { PromptDialog } from './PromptDialog';
|
import { PromptDialog } from './PromptDialog';
|
||||||
import '../styles/ContentBrowser.css';
|
import '../styles/ContentBrowser.css';
|
||||||
|
|
||||||
/**
|
|
||||||
* Directories managed by asset registry (GUID system)
|
|
||||||
* 被资产注册表(GUID 系统)管理的目录
|
|
||||||
*
|
|
||||||
* Note: This is duplicated from AssetRegistryService to avoid build dependency issues.
|
|
||||||
* Keep in sync with MANAGED_ASSET_DIRECTORIES in AssetRegistryService.ts
|
|
||||||
*/
|
|
||||||
const MANAGED_ASSET_DIRECTORIES = ['assets', 'scripts', 'scenes'] as const;
|
|
||||||
|
|
||||||
interface AssetItem {
|
interface AssetItem {
|
||||||
name: string;
|
name: string;
|
||||||
path: string;
|
path: string;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useState, useEffect } from 'react';
|
|||||||
import { invoke } from '@tauri-apps/api/core';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
import { X, Server, WifiOff, Wifi } from 'lucide-react';
|
import { X, Server, WifiOff, Wifi } from 'lucide-react';
|
||||||
import { SettingsService } from '../services/SettingsService';
|
import { SettingsService } from '../services/SettingsService';
|
||||||
import { ProfilerService } from '../services/ProfilerService';
|
import { getProfilerService } from '../services/getService';
|
||||||
import '../styles/PortManager.css';
|
import '../styles/PortManager.css';
|
||||||
|
|
||||||
interface PortManagerProps {
|
interface PortManagerProps {
|
||||||
@@ -58,7 +58,7 @@ export function PortManager({ onClose }: PortManagerProps) {
|
|||||||
const handleStopServer = async () => {
|
const handleStopServer = async () => {
|
||||||
setIsStopping(true);
|
setIsStopping(true);
|
||||||
try {
|
try {
|
||||||
const profilerService = (window as any).__PROFILER_SERVICE__ as ProfilerService | undefined;
|
const profilerService = getProfilerService();
|
||||||
if (profilerService) {
|
if (profilerService) {
|
||||||
await profilerService.manualStopServer();
|
await profilerService.manualStopServer();
|
||||||
setIsServerRunning(false);
|
setIsServerRunning(false);
|
||||||
@@ -73,7 +73,7 @@ export function PortManager({ onClose }: PortManagerProps) {
|
|||||||
const handleStartServer = async () => {
|
const handleStartServer = async () => {
|
||||||
setIsStarting(true);
|
setIsStarting(true);
|
||||||
try {
|
try {
|
||||||
const profilerService = (window as any).__PROFILER_SERVICE__ as ProfilerService | undefined;
|
const profilerService = getProfilerService();
|
||||||
if (profilerService) {
|
if (profilerService) {
|
||||||
await profilerService.manualStartServer();
|
await profilerService.manualStartServer();
|
||||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { Activity, Cpu, Layers, Package, Wifi, WifiOff, Maximize2, Pause, Play, BarChart3 } from 'lucide-react';
|
import { Activity, Cpu, Layers, Package, Wifi, WifiOff, Maximize2, Pause, Play, BarChart3 } from 'lucide-react';
|
||||||
import { ProfilerService, ProfilerData } from '../services/ProfilerService';
|
import type { ProfilerData } from '../services/tokens';
|
||||||
import { SettingsService } from '../services/SettingsService';
|
import { SettingsService } from '../services/SettingsService';
|
||||||
import { Core } from '@esengine/ecs-framework';
|
import { Core } from '@esengine/ecs-framework';
|
||||||
import { MessageHub } from '@esengine/editor-core';
|
import { MessageHub } from '@esengine/editor-core';
|
||||||
|
import { getProfilerService } from '../services/getService';
|
||||||
import '../styles/ProfilerDockPanel.css';
|
import '../styles/ProfilerDockPanel.css';
|
||||||
|
|
||||||
export function ProfilerDockPanel() {
|
export function ProfilerDockPanel() {
|
||||||
@@ -32,7 +33,7 @@ export function ProfilerDockPanel() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const profilerService = (window as any).__PROFILER_SERVICE__ as ProfilerService | undefined;
|
const profilerService = getProfilerService();
|
||||||
|
|
||||||
if (!profilerService) {
|
if (!profilerService) {
|
||||||
console.warn('[ProfilerDockPanel] ProfilerService not available - plugin may be disabled');
|
console.warn('[ProfilerDockPanel] ProfilerService not available - plugin may be disabled');
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { Core } from '@esengine/ecs-framework';
|
|||||||
import { Activity, BarChart3, Clock, Cpu, RefreshCw, Pause, Play, X, Wifi, WifiOff, Server, Search, Table2, TreePine } from 'lucide-react';
|
import { Activity, BarChart3, Clock, Cpu, RefreshCw, Pause, Play, X, Wifi, WifiOff, Server, Search, Table2, TreePine } from 'lucide-react';
|
||||||
import { ProfilerService } from '../services/ProfilerService';
|
import { ProfilerService } from '../services/ProfilerService';
|
||||||
import { SettingsService } from '../services/SettingsService';
|
import { SettingsService } from '../services/SettingsService';
|
||||||
|
import { getProfilerService } from '../services/getService';
|
||||||
import '../styles/ProfilerWindow.css';
|
import '../styles/ProfilerWindow.css';
|
||||||
|
|
||||||
interface SystemPerformanceData {
|
interface SystemPerformanceData {
|
||||||
@@ -59,7 +60,7 @@ export function ProfilerWindow({ onClose }: ProfilerWindowProps) {
|
|||||||
|
|
||||||
// Check ProfilerService connection status
|
// Check ProfilerService connection status
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const profilerService = (window as any).__PROFILER_SERVICE__ as ProfilerService | undefined;
|
const profilerService = getProfilerService();
|
||||||
|
|
||||||
if (!profilerService) {
|
if (!profilerService) {
|
||||||
return;
|
return;
|
||||||
@@ -186,7 +187,7 @@ export function ProfilerWindow({ onClose }: ProfilerWindowProps) {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (dataSource !== 'remote') return;
|
if (dataSource !== 'remote') return;
|
||||||
|
|
||||||
const profilerService = (window as any).__PROFILER_SERVICE__ as ProfilerService | undefined;
|
const profilerService = getProfilerService();
|
||||||
|
|
||||||
if (!profilerService) {
|
if (!profilerService) {
|
||||||
console.warn('[ProfilerWindow] ProfilerService not available');
|
console.warn('[ProfilerWindow] ProfilerService not available');
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ import {
|
|||||||
Eye, Star, Lock, Settings, Filter, Folder, Sun, Cloud, Mountain, Flag,
|
Eye, Star, Lock, Settings, Filter, Folder, Sun, Cloud, Mountain, Flag,
|
||||||
SquareStack, FolderPlus
|
SquareStack, FolderPlus
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { ProfilerService, RemoteEntity } from '../services/ProfilerService';
|
import type { RemoteEntity } from '../services/tokens';
|
||||||
|
import { getProfilerService } from '../services/getService';
|
||||||
import { confirm } from '@tauri-apps/plugin-dialog';
|
import { confirm } from '@tauri-apps/plugin-dialog';
|
||||||
import { CreateEntityCommand, DeleteEntityCommand, ReparentEntityCommand, DropPosition } from '../application/commands/entity';
|
import { CreateEntityCommand, DeleteEntityCommand, ReparentEntityCommand, DropPosition } from '../application/commands/entity';
|
||||||
import '../styles/SceneHierarchy.css';
|
import '../styles/SceneHierarchy.css';
|
||||||
@@ -264,7 +265,7 @@ export function SceneHierarchy({ entityStore, messageHub, commandManager, isProf
|
|||||||
|
|
||||||
// Subscribe to remote entity data from ProfilerService
|
// Subscribe to remote entity data from ProfilerService
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const profilerService = (window as any).__PROFILER_SERVICE__ as ProfilerService | undefined;
|
const profilerService = getProfilerService();
|
||||||
|
|
||||||
if (!profilerService) {
|
if (!profilerService) {
|
||||||
return;
|
return;
|
||||||
@@ -444,7 +445,7 @@ export function SceneHierarchy({ entityStore, messageHub, commandManager, isProf
|
|||||||
const handleRemoteEntityClick = (entity: RemoteEntity) => {
|
const handleRemoteEntityClick = (entity: RemoteEntity) => {
|
||||||
setSelectedIds(new Set([entity.id]));
|
setSelectedIds(new Set([entity.id]));
|
||||||
|
|
||||||
const profilerService = (window as any).__PROFILER_SERVICE__ as ProfilerService | undefined;
|
const profilerService = getProfilerService();
|
||||||
if (profilerService) {
|
if (profilerService) {
|
||||||
profilerService.requestEntityDetails(entity.id);
|
profilerService.requestEntityDetails(entity.id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,10 @@
|
|||||||
import React, { useState, useEffect, useMemo, useCallback } from 'react';
|
import React, { useState, useEffect, useMemo, useCallback } from 'react';
|
||||||
import { X, Search, Folder, FolderOpen, File, Image, FileText, Music, Video, Database, AlertTriangle } from 'lucide-react';
|
import { X, Search, Folder, FolderOpen, File, Image, FileText, Music, Video, Database, AlertTriangle } from 'lucide-react';
|
||||||
import { Core } from '@esengine/ecs-framework';
|
import { Core } from '@esengine/ecs-framework';
|
||||||
import { ProjectService, AssetRegistryService } from '@esengine/editor-core';
|
import { ProjectService, AssetRegistryService, MANAGED_ASSET_DIRECTORIES } from '@esengine/editor-core';
|
||||||
import { TauriFileSystemService } from '../../services/TauriFileSystemService';
|
import { TauriFileSystemService } from '../../services/TauriFileSystemService';
|
||||||
import './AssetPickerDialog.css';
|
import './AssetPickerDialog.css';
|
||||||
|
|
||||||
/**
|
|
||||||
* Directories managed by asset registry (GUID system)
|
|
||||||
* Only files in these directories can be selected
|
|
||||||
*
|
|
||||||
* Note: Keep in sync with MANAGED_ASSET_DIRECTORIES in AssetRegistryService.ts
|
|
||||||
*/
|
|
||||||
const MANAGED_ASSET_DIRECTORIES = ['assets', 'scripts', 'scenes'] as const;
|
|
||||||
|
|
||||||
interface AssetPickerDialogProps {
|
interface AssetPickerDialogProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
|
|||||||
@@ -4,18 +4,15 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
||||||
|
import { Core } from '@esengine/ecs-framework';
|
||||||
/**
|
import {
|
||||||
* 碰撞层配置接口(用于获取自定义层名称)
|
CollisionLayerConfigToken,
|
||||||
*/
|
type ICollisionLayerConfig
|
||||||
interface CollisionLayerConfigAPI {
|
} from '@esengine/physics-rapier2d';
|
||||||
getLayers(): Array<{ name: string }>;
|
|
||||||
addListener(callback: () => void): void;
|
|
||||||
removeListener(callback: () => void): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 默认层名称(当 CollisionLayerConfig 不可用时使用)
|
* 默认层名称(当 CollisionLayerConfig 不可用时使用)
|
||||||
|
* Default layer names (used when CollisionLayerConfig is unavailable)
|
||||||
*/
|
*/
|
||||||
const DEFAULT_LAYER_NAMES = [
|
const DEFAULT_LAYER_NAMES = [
|
||||||
'Default', 'Player', 'Enemy', 'Projectile',
|
'Default', 'Player', 'Enemy', 'Projectile',
|
||||||
@@ -24,25 +21,18 @@ const DEFAULT_LAYER_NAMES = [
|
|||||||
'Layer12', 'Layer13', 'Layer14', 'Layer15',
|
'Layer12', 'Layer13', 'Layer14', 'Layer15',
|
||||||
];
|
];
|
||||||
|
|
||||||
let cachedConfig: CollisionLayerConfigAPI | null = null;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 尝试获取 CollisionLayerConfig 实例
|
* 尝试获取 CollisionLayerConfig 实例
|
||||||
|
* Try to get CollisionLayerConfig instance
|
||||||
*/
|
*/
|
||||||
function getCollisionConfig(): CollisionLayerConfigAPI | null {
|
function getCollisionConfig(): ICollisionLayerConfig | undefined {
|
||||||
if (cachedConfig) return cachedConfig;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 动态导入以避免循环依赖
|
return Core.pluginServices.get(CollisionLayerConfigToken);
|
||||||
const physicsModule = (window as any).__PHYSICS_RAPIER2D__;
|
|
||||||
if (physicsModule?.CollisionLayerConfig) {
|
|
||||||
cachedConfig = physicsModule.CollisionLayerConfig.getInstance();
|
|
||||||
return cachedConfig;
|
|
||||||
}
|
|
||||||
} catch {
|
} catch {
|
||||||
// 忽略错误
|
// Core 可能还没有初始化
|
||||||
|
// Core might not be initialized yet
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CollisionLayerFieldProps {
|
interface CollisionLayerFieldProps {
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
|
import { Core } from '@esengine/ecs-framework';
|
||||||
import { ComponentData } from './types';
|
import { ComponentData } from './types';
|
||||||
|
import { ProfilerServiceToken, type IProfilerService } from '../../services/tokens';
|
||||||
|
|
||||||
export function formatNumber(value: number, decimalPlaces: number): string {
|
export function formatNumber(value: number, decimalPlaces: number): string {
|
||||||
if (decimalPlaces < 0) {
|
if (decimalPlaces < 0) {
|
||||||
@@ -10,13 +12,21 @@ export function formatNumber(value: number, decimalPlaces: number): string {
|
|||||||
return value.toFixed(decimalPlaces);
|
return value.toFixed(decimalPlaces);
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ProfilerService {
|
/**
|
||||||
requestEntityDetails(entityId: number): void;
|
* 获取 ProfilerService 实例
|
||||||
subscribe(callback: () => void): () => void;
|
* Get ProfilerService instance
|
||||||
}
|
*
|
||||||
|
* 使用 ServiceToken 从 Core.pluginServices 获取服务。
|
||||||
export function getProfilerService(): ProfilerService | undefined {
|
* Uses ServiceToken to get service from Core.pluginServices.
|
||||||
return (window as any).__PROFILER_SERVICE__;
|
*/
|
||||||
|
export function getProfilerService(): IProfilerService | undefined {
|
||||||
|
try {
|
||||||
|
return Core.pluginServices.get(ProfilerServiceToken);
|
||||||
|
} catch {
|
||||||
|
// Core 可能还没有初始化
|
||||||
|
// Core might not be initialized yet
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isComponentData(value: unknown): value is ComponentData {
|
export function isComponentData(value: unknown): value is ComponentData {
|
||||||
|
|||||||
@@ -1,14 +1,22 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import * as LucideIcons from 'lucide-react';
|
import * as LucideIcons from 'lucide-react';
|
||||||
|
|
||||||
type LucideIconName = keyof typeof LucideIcons;
|
/**
|
||||||
|
* 动态获取 Lucide 图标组件
|
||||||
|
* Dynamically get Lucide icon component by name
|
||||||
|
*
|
||||||
|
* @param iconName - 图标名称(如 'Package', 'Settings')
|
||||||
|
* @param fallback - 找不到时的回退组件
|
||||||
|
* @returns Lucide 图标组件
|
||||||
|
*/
|
||||||
export function useDynamicIcon(iconName?: string, fallback?: React.ComponentType) {
|
export function useDynamicIcon(iconName?: string, fallback?: React.ComponentType) {
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
if (!iconName) {
|
if (!iconName) {
|
||||||
return fallback || LucideIcons.Package;
|
return fallback || LucideIcons.Package;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 动态图标查找需要使用 any,因为 lucide-react 的类型定义不支持动态索引
|
||||||
|
// Dynamic icon lookup requires any, as lucide-react types don't support dynamic indexing
|
||||||
const IconComponent = (LucideIcons as any)[iconName];
|
const IconComponent = (LucideIcons as any)[iconName];
|
||||||
return IconComponent || fallback || LucideIcons.Package;
|
return IconComponent || fallback || LucideIcons.Package;
|
||||||
}, [iconName, fallback]);
|
}, [iconName, fallback]);
|
||||||
|
|||||||
@@ -1,24 +1,53 @@
|
|||||||
|
/**
|
||||||
|
* ProfilerService Hook
|
||||||
|
*
|
||||||
|
* 通过 ServiceToken 获取 ProfilerService 实例。
|
||||||
|
* Get ProfilerService instance via ServiceToken.
|
||||||
|
*/
|
||||||
|
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
import { Core } from '@esengine/ecs-framework';
|
||||||
|
import { ProfilerServiceToken, type IProfilerService } from '../services/tokens';
|
||||||
|
|
||||||
export interface ProfilerService {
|
/**
|
||||||
connect(port: number): void;
|
* 获取 ProfilerService 实例的 Hook
|
||||||
disconnect(): void;
|
* Hook to get ProfilerService instance
|
||||||
isConnected(): boolean;
|
*
|
||||||
requestEntityList(): void;
|
* 使用 ServiceToken 从 Core.pluginServices 获取服务,
|
||||||
requestEntityDetails(entityId: number): void;
|
* 提供类型安全的服务访问。
|
||||||
}
|
*
|
||||||
|
* Uses ServiceToken to get service from Core.pluginServices,
|
||||||
export function useProfilerService(): ProfilerService | undefined {
|
* providing type-safe service access.
|
||||||
const [service, setService] = useState<ProfilerService | undefined>(() => {
|
*
|
||||||
return (window as any).__PROFILER_SERVICE__;
|
* @returns ProfilerService 实例,如果未注册则返回 undefined
|
||||||
|
*/
|
||||||
|
export function useProfilerService(): IProfilerService | undefined {
|
||||||
|
const [service, setService] = useState<IProfilerService | undefined>(() => {
|
||||||
|
try {
|
||||||
|
return Core.pluginServices.get(ProfilerServiceToken);
|
||||||
|
} catch {
|
||||||
|
// Core 可能还没有初始化
|
||||||
|
// Core might not be initialized yet
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
// 定期检查服务是否可用(处理服务延迟注册的情况)
|
||||||
|
// Periodically check if service is available (handles delayed service registration)
|
||||||
const checkService = () => {
|
const checkService = () => {
|
||||||
const newService = (window as any).__PROFILER_SERVICE__;
|
try {
|
||||||
|
const newService = Core.pluginServices.get(ProfilerServiceToken);
|
||||||
if (newService !== service) {
|
if (newService !== service) {
|
||||||
setService(newService);
|
setService(newService);
|
||||||
}
|
}
|
||||||
|
} catch {
|
||||||
|
// Core 可能还没有初始化
|
||||||
|
// Core might not be initialized yet
|
||||||
|
if (service !== undefined) {
|
||||||
|
setService(undefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const interval = setInterval(checkService, 1000);
|
const interval = setInterval(checkService, 1000);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type { ServiceContainer } from '@esengine/ecs-framework';
|
import type { ServiceContainer } from '@esengine/ecs-framework';
|
||||||
|
import { Core } from '@esengine/ecs-framework';
|
||||||
import type {
|
import type {
|
||||||
IPlugin,
|
IPlugin,
|
||||||
IEditorModuleLoader,
|
IEditorModuleLoader,
|
||||||
@@ -12,6 +13,7 @@ import type {
|
|||||||
} from '@esengine/editor-core';
|
} from '@esengine/editor-core';
|
||||||
import { MessageHub, SettingsRegistry } from '@esengine/editor-core';
|
import { MessageHub, SettingsRegistry } from '@esengine/editor-core';
|
||||||
import { ProfilerService } from '../../services/ProfilerService';
|
import { ProfilerService } from '../../services/ProfilerService';
|
||||||
|
import { ProfilerServiceToken } from '../../services/tokens';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Profiler 编辑器模块
|
* Profiler 编辑器模块
|
||||||
@@ -87,15 +89,21 @@ class ProfilerEditorModule implements IEditorModuleLoader {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.profilerService = new ProfilerService();
|
this.profilerService = new ProfilerService();
|
||||||
(window as any).__PROFILER_SERVICE__ = this.profilerService;
|
|
||||||
|
// 使用 ServiceToken 注册服务(类型安全)
|
||||||
|
// Register service using ServiceToken (type-safe)
|
||||||
|
Core.pluginServices.register(ProfilerServiceToken, this.profilerService);
|
||||||
}
|
}
|
||||||
|
|
||||||
async uninstall(): Promise<void> {
|
async uninstall(): Promise<void> {
|
||||||
|
// 从服务注册表注销
|
||||||
|
// Unregister from service registry
|
||||||
|
Core.pluginServices.unregister(ProfilerServiceToken);
|
||||||
|
|
||||||
if (this.profilerService) {
|
if (this.profilerService) {
|
||||||
this.profilerService.destroy();
|
this.profilerService.destroy();
|
||||||
this.profilerService = null;
|
this.profilerService = null;
|
||||||
}
|
}
|
||||||
delete (window as any).__PROFILER_SERVICE__;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getMenuItems(): MenuItemDescriptor[] {
|
getMenuItems(): MenuItemDescriptor[] {
|
||||||
|
|||||||
@@ -1,29 +1,13 @@
|
|||||||
import { invoke } from '@tauri-apps/api/core';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
import { SettingsService } from './SettingsService';
|
import { SettingsService } from './SettingsService';
|
||||||
import { LogLevel } from '@esengine/ecs-framework';
|
import { LogLevel } from '@esengine/ecs-framework';
|
||||||
|
import type {
|
||||||
export interface SystemPerformanceData {
|
IProfilerService,
|
||||||
name: string;
|
ProfilerData,
|
||||||
executionTime: number;
|
SystemPerformanceData,
|
||||||
entityCount: number;
|
RemoteEntity,
|
||||||
averageTime: number;
|
AdvancedProfilerDataPayload
|
||||||
percentage: number;
|
} from './tokens';
|
||||||
}
|
|
||||||
|
|
||||||
export interface RemoteEntity {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
enabled: boolean;
|
|
||||||
active: boolean;
|
|
||||||
activeInHierarchy: boolean;
|
|
||||||
componentCount: number;
|
|
||||||
componentTypes: string[];
|
|
||||||
parentId: number | null;
|
|
||||||
childIds: number[];
|
|
||||||
depth: number;
|
|
||||||
tag: number;
|
|
||||||
updateOrder: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RemoteComponentDetail {
|
export interface RemoteComponentDetail {
|
||||||
typeName: string;
|
typeName: string;
|
||||||
@@ -45,29 +29,10 @@ export interface RemoteEntityDetails {
|
|||||||
parentName: string | null;
|
parentName: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ProfilerData {
|
|
||||||
totalFrameTime: number;
|
|
||||||
systems: SystemPerformanceData[];
|
|
||||||
entityCount: number;
|
|
||||||
componentCount: number;
|
|
||||||
fps: number;
|
|
||||||
entities?: RemoteEntity[];
|
|
||||||
}
|
|
||||||
|
|
||||||
type ProfilerDataListener = (data: ProfilerData) => void;
|
type ProfilerDataListener = (data: ProfilerData) => void;
|
||||||
|
|
||||||
/**
|
|
||||||
* 高级性能数据结构(用于高级性能分析器)
|
|
||||||
*/
|
|
||||||
export interface AdvancedProfilerDataPayload {
|
|
||||||
advancedProfiler?: any;
|
|
||||||
performance?: any;
|
|
||||||
systems?: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
type AdvancedProfilerDataListener = (data: AdvancedProfilerDataPayload) => void;
|
type AdvancedProfilerDataListener = (data: AdvancedProfilerDataPayload) => void;
|
||||||
|
|
||||||
export class ProfilerService {
|
export class ProfilerService implements IProfilerService {
|
||||||
private ws: WebSocket | null = null;
|
private ws: WebSocket | null = null;
|
||||||
private isServerRunning = false;
|
private isServerRunning = false;
|
||||||
private wsPort: number;
|
private wsPort: number;
|
||||||
|
|||||||
36
packages/editor-app/src/services/getService.ts
Normal file
36
packages/editor-app/src/services/getService.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* 服务获取辅助函数
|
||||||
|
* Service getter helper functions
|
||||||
|
*
|
||||||
|
* 提供类型安全的服务获取,避免直接访问全局变量。
|
||||||
|
* Provides type-safe service access, avoiding direct global variable access.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Core } from '@esengine/ecs-framework';
|
||||||
|
import type { ServiceToken } from '@esengine/engine-core';
|
||||||
|
import { ProfilerServiceToken, type IProfilerService } from './tokens';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 安全获取插件服务
|
||||||
|
* Safely get plugin service
|
||||||
|
*
|
||||||
|
* 在 Core 未初始化时返回 undefined 而非抛出异常。
|
||||||
|
* Returns undefined instead of throwing when Core is not initialized.
|
||||||
|
*/
|
||||||
|
export function getPluginService<T>(token: ServiceToken<T>): T | undefined {
|
||||||
|
try {
|
||||||
|
return Core.pluginServices.get(token);
|
||||||
|
} catch {
|
||||||
|
// Core 可能还没有初始化
|
||||||
|
// Core might not be initialized yet
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 ProfilerService 实例
|
||||||
|
* Get ProfilerService instance
|
||||||
|
*/
|
||||||
|
export function getProfilerService(): IProfilerService | undefined {
|
||||||
|
return getPluginService(ProfilerServiceToken);
|
||||||
|
}
|
||||||
114
packages/editor-app/src/services/tokens.ts
Normal file
114
packages/editor-app/src/services/tokens.ts
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
/**
|
||||||
|
* 编辑器服务令牌
|
||||||
|
* Editor Service Tokens
|
||||||
|
*
|
||||||
|
* 遵循"谁定义接口,谁导出 Token"原则。
|
||||||
|
* 这些服务定义在 editor-app 中,所以 Token 也在这里定义。
|
||||||
|
*
|
||||||
|
* Following "who defines interface, who exports Token" principle.
|
||||||
|
* These services are defined in editor-app, so Tokens are also defined here.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { createServiceToken } from '@esengine/engine-core';
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Profiler Data Types (定义在这里以避免循环依赖)
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export interface SystemPerformanceData {
|
||||||
|
name: string;
|
||||||
|
executionTime: number;
|
||||||
|
entityCount: number;
|
||||||
|
averageTime: number;
|
||||||
|
percentage: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RemoteEntity {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
enabled: boolean;
|
||||||
|
active: boolean;
|
||||||
|
activeInHierarchy: boolean;
|
||||||
|
componentCount: number;
|
||||||
|
componentTypes: string[];
|
||||||
|
parentId: number | null;
|
||||||
|
childIds: number[];
|
||||||
|
depth: number;
|
||||||
|
tag: number;
|
||||||
|
updateOrder: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProfilerData {
|
||||||
|
totalFrameTime: number;
|
||||||
|
systems: SystemPerformanceData[];
|
||||||
|
entityCount: number;
|
||||||
|
componentCount: number;
|
||||||
|
fps: number;
|
||||||
|
entities?: RemoteEntity[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 高级性能数据结构(用于高级性能分析器)
|
||||||
|
* Advanced profiler data structure
|
||||||
|
*/
|
||||||
|
export interface AdvancedProfilerDataPayload {
|
||||||
|
advancedProfiler?: any;
|
||||||
|
performance?: any;
|
||||||
|
systems?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Profiler Service Token
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ProfilerService 接口(用于类型检查)
|
||||||
|
* ProfilerService interface (for type checking)
|
||||||
|
*
|
||||||
|
* 提供远程性能分析功能,包括:
|
||||||
|
* - WebSocket 连接管理
|
||||||
|
* - 性能数据收集和分发
|
||||||
|
* - 远程日志接收
|
||||||
|
*
|
||||||
|
* Provides remote profiling capabilities including:
|
||||||
|
* - WebSocket connection management
|
||||||
|
* - Performance data collection and distribution
|
||||||
|
* - Remote log reception
|
||||||
|
*/
|
||||||
|
export interface IProfilerService {
|
||||||
|
/** 检查是否已连接 | Check if connected */
|
||||||
|
isConnected(): boolean;
|
||||||
|
|
||||||
|
/** 检查服务器是否运行 | Check if server is running */
|
||||||
|
isServerActive(): boolean;
|
||||||
|
|
||||||
|
/** 手动启动服务器 | Manually start server */
|
||||||
|
manualStartServer(): Promise<void>;
|
||||||
|
|
||||||
|
/** 手动停止服务器 | Manually stop server */
|
||||||
|
manualStopServer(): Promise<void>;
|
||||||
|
|
||||||
|
/** 订阅数据更新 | Subscribe to data updates */
|
||||||
|
subscribe(callback: (data: ProfilerData) => void): () => void;
|
||||||
|
|
||||||
|
/** 订阅高级数据更新 | Subscribe to advanced data updates */
|
||||||
|
subscribeAdvanced(callback: (data: AdvancedProfilerDataPayload) => void): () => void;
|
||||||
|
|
||||||
|
/** 请求实体详情 | Request entity details */
|
||||||
|
requestEntityDetails(entityId: number): void;
|
||||||
|
|
||||||
|
/** 请求高级性能分析数据 | Request advanced profiler data */
|
||||||
|
requestAdvancedProfilerData(): void;
|
||||||
|
|
||||||
|
/** 设置选中的函数 | Set selected function */
|
||||||
|
setProfilerSelectedFunction(functionName: string | null): void;
|
||||||
|
|
||||||
|
/** 销毁服务 | Destroy service */
|
||||||
|
destroy(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ProfilerService 的服务令牌
|
||||||
|
* Service token for ProfilerService
|
||||||
|
*/
|
||||||
|
export const ProfilerServiceToken = createServiceToken<IProfilerService>('profilerService');
|
||||||
@@ -47,6 +47,8 @@ export interface PanelDescriptor {
|
|||||||
id: string;
|
id: string;
|
||||||
/** 面板标题 | Panel title */
|
/** 面板标题 | Panel title */
|
||||||
title: string;
|
title: string;
|
||||||
|
/** 面板中文标题 | Panel title in Chinese */
|
||||||
|
titleZh?: string;
|
||||||
/** 面板图标 | Panel icon */
|
/** 面板图标 | Panel icon */
|
||||||
icon?: string;
|
icon?: string;
|
||||||
/** 面板位置 | Panel position */
|
/** 面板位置 | Panel position */
|
||||||
|
|||||||
@@ -54,3 +54,8 @@ export type { IShaderAssetData, ShaderFileFormat } from './loaders/ShaderLoader'
|
|||||||
// 运行时模块。
|
// 运行时模块。
|
||||||
export { MaterialRuntimeModule, materialRuntimeModule, MaterialSystemPlugin } from './MaterialSystemPlugin';
|
export { MaterialRuntimeModule, materialRuntimeModule, MaterialSystemPlugin } from './MaterialSystemPlugin';
|
||||||
export type { IMaterialRuntimeModule } from './MaterialSystemPlugin';
|
export type { IMaterialRuntimeModule } from './MaterialSystemPlugin';
|
||||||
|
|
||||||
|
// Service Tokens.
|
||||||
|
// 服务令牌。
|
||||||
|
export { MaterialManagerToken } from './tokens';
|
||||||
|
export type { IMaterialManager } from './tokens';
|
||||||
|
|||||||
174
packages/material-system/src/tokens.ts
Normal file
174
packages/material-system/src/tokens.ts
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
/**
|
||||||
|
* Material System Service Tokens
|
||||||
|
* 材质系统服务令牌
|
||||||
|
*
|
||||||
|
* 遵循"谁定义接口,谁导出 Token"原则。
|
||||||
|
* Following "who defines interface, who exports Token" principle.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { createServiceToken } from '@esengine/engine-core';
|
||||||
|
import type { Material } from './Material';
|
||||||
|
import type { Shader } from './Shader';
|
||||||
|
import type { IEngineBridge } from './MaterialManager';
|
||||||
|
import type { IAssetManager } from '@esengine/asset-system';
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Material Manager Interface
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MaterialManager 接口
|
||||||
|
* MaterialManager interface
|
||||||
|
*
|
||||||
|
* 提供材质和着色器管理功能。
|
||||||
|
* Provides material and shader management functionality.
|
||||||
|
*/
|
||||||
|
export interface IMaterialManager {
|
||||||
|
// ========== Initialization | 初始化 ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置引擎桥接
|
||||||
|
* Set engine bridge for Rust communication
|
||||||
|
*/
|
||||||
|
setEngineBridge(bridge: IEngineBridge): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置资产管理器
|
||||||
|
* Set asset manager for loading assets
|
||||||
|
*/
|
||||||
|
setAssetManager(assetManager: IAssetManager): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化内置材质
|
||||||
|
* Initialize built-in materials
|
||||||
|
*/
|
||||||
|
initializeBuiltInMaterials(): Promise<void>;
|
||||||
|
|
||||||
|
// ========== Shader Management | 着色器管理 ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册着色器
|
||||||
|
* Register a shader
|
||||||
|
*/
|
||||||
|
registerShader(shader: Shader): Promise<number>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过 ID 获取着色器
|
||||||
|
* Get shader by ID
|
||||||
|
*/
|
||||||
|
getShader(id: number): Shader | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过名称获取着色器
|
||||||
|
* Get shader by name
|
||||||
|
*/
|
||||||
|
getShaderByName(name: string): Shader | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除着色器
|
||||||
|
* Remove a shader
|
||||||
|
*/
|
||||||
|
removeShader(id: number): boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从路径加载着色器
|
||||||
|
* Load shader from path
|
||||||
|
*/
|
||||||
|
loadShaderByPath(path: string): Promise<number>;
|
||||||
|
|
||||||
|
// ========== Material Management | 材质管理 ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册材质
|
||||||
|
* Register a material
|
||||||
|
*/
|
||||||
|
registerMaterial(material: Material): Promise<number>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过 ID 获取材质
|
||||||
|
* Get material by ID
|
||||||
|
*/
|
||||||
|
getMaterial(id: number): Material | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过名称获取材质
|
||||||
|
* Get material by name
|
||||||
|
*/
|
||||||
|
getMaterialByName(name: string): Material | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除材质
|
||||||
|
* Remove a material
|
||||||
|
*/
|
||||||
|
removeMaterial(id: number): boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从路径加载材质
|
||||||
|
* Load material from path
|
||||||
|
*/
|
||||||
|
loadMaterialByPath(path: string): Promise<number>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 克隆材质
|
||||||
|
* Clone a material
|
||||||
|
*/
|
||||||
|
cloneMaterial(materialId: number, newName?: string): Promise<Material | null>;
|
||||||
|
|
||||||
|
// ========== Built-in Materials | 内置材质 ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取默认材质 ID
|
||||||
|
* Get default material ID
|
||||||
|
*/
|
||||||
|
getDefaultMaterialId(): number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取灰度材质 ID
|
||||||
|
* Get grayscale material ID
|
||||||
|
*/
|
||||||
|
getGrayscaleMaterialId(): number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取着色材质 ID
|
||||||
|
* Get tint material ID
|
||||||
|
*/
|
||||||
|
getTintMaterialId(): number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取闪烁材质 ID
|
||||||
|
* Get flash material ID
|
||||||
|
*/
|
||||||
|
getFlashMaterialId(): number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取轮廓材质 ID
|
||||||
|
* Get outline material ID
|
||||||
|
*/
|
||||||
|
getOutlineMaterialId(): number;
|
||||||
|
|
||||||
|
// ========== Uniform Management | Uniform 管理 ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置材质 uniform 值
|
||||||
|
* Set material uniform value
|
||||||
|
*/
|
||||||
|
setMaterialUniform(materialId: number, name: string, value: any): boolean;
|
||||||
|
|
||||||
|
// ========== Lifecycle | 生命周期 ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 销毁管理器,释放所有资源
|
||||||
|
* Destroy manager and release all resources
|
||||||
|
*/
|
||||||
|
destroy(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Service Token
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MaterialManager 服务令牌
|
||||||
|
* MaterialManager service token
|
||||||
|
*/
|
||||||
|
export const MaterialManagerToken = createServiceToken<IMaterialManager>('materialManager');
|
||||||
@@ -22,9 +22,11 @@ import {
|
|||||||
Physics2DSystemToken,
|
Physics2DSystemToken,
|
||||||
Physics2DWorldToken,
|
Physics2DWorldToken,
|
||||||
PhysicsConfigToken,
|
PhysicsConfigToken,
|
||||||
|
CollisionLayerConfigToken,
|
||||||
type IPhysics2DQuery,
|
type IPhysics2DQuery,
|
||||||
type PhysicsConfig
|
type PhysicsConfig
|
||||||
} from './tokens';
|
} from './tokens';
|
||||||
|
import { CollisionLayerConfig } from './services/CollisionLayerConfig';
|
||||||
|
|
||||||
// 注册 Rapier2D 加载器
|
// 注册 Rapier2D 加载器
|
||||||
import './loaders';
|
import './loaders';
|
||||||
@@ -35,6 +37,7 @@ export {
|
|||||||
Physics2DSystemToken,
|
Physics2DSystemToken,
|
||||||
Physics2DWorldToken,
|
Physics2DWorldToken,
|
||||||
PhysicsConfigToken,
|
PhysicsConfigToken,
|
||||||
|
CollisionLayerConfigToken,
|
||||||
type IPhysics2DQuery,
|
type IPhysics2DQuery,
|
||||||
type PhysicsConfig
|
type PhysicsConfig
|
||||||
} from './tokens';
|
} from './tokens';
|
||||||
@@ -144,6 +147,7 @@ class PhysicsRuntimeModule implements IRuntimeModule {
|
|||||||
context.services.register(Physics2DSystemToken, physicsSystem);
|
context.services.register(Physics2DSystemToken, physicsSystem);
|
||||||
context.services.register(Physics2DWorldToken, physicsSystem.world);
|
context.services.register(Physics2DWorldToken, physicsSystem.world);
|
||||||
context.services.register(Physics2DQueryToken, physicsSystem);
|
context.services.register(Physics2DQueryToken, physicsSystem);
|
||||||
|
context.services.register(CollisionLayerConfigToken, CollisionLayerConfig.getInstance());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -34,7 +34,9 @@ export {
|
|||||||
Physics2DSystemToken,
|
Physics2DSystemToken,
|
||||||
Physics2DWorldToken,
|
Physics2DWorldToken,
|
||||||
PhysicsConfigToken,
|
PhysicsConfigToken,
|
||||||
|
CollisionLayerConfigToken,
|
||||||
type IPhysics2DQuery,
|
type IPhysics2DQuery,
|
||||||
type IPhysics2DWorld,
|
type IPhysics2DWorld,
|
||||||
|
type ICollisionLayerConfig,
|
||||||
type PhysicsConfig
|
type PhysicsConfig
|
||||||
} from './tokens';
|
} from './tokens';
|
||||||
|
|||||||
@@ -136,6 +136,33 @@ export interface PhysicsConfig {
|
|||||||
timestep?: number;
|
timestep?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 碰撞层配置接口
|
||||||
|
* Collision layer config interface
|
||||||
|
*
|
||||||
|
* 跨模块共享的碰撞层配置契约。
|
||||||
|
* Cross-module shared collision layer config contract.
|
||||||
|
*/
|
||||||
|
export interface ICollisionLayerConfig {
|
||||||
|
/**
|
||||||
|
* 获取所有层定义
|
||||||
|
* Get all layer definitions
|
||||||
|
*/
|
||||||
|
getLayers(): ReadonlyArray<{ name: string }>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加监听器
|
||||||
|
* Add listener
|
||||||
|
*/
|
||||||
|
addListener(callback: () => void): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除监听器
|
||||||
|
* Remove listener
|
||||||
|
*/
|
||||||
|
removeListener(callback: () => void): void;
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// 服务令牌 | Service Tokens
|
// 服务令牌 | Service Tokens
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -175,3 +202,12 @@ export const Physics2DSystemToken = createServiceToken<Physics2DSystem>('physics
|
|||||||
* For passing physics configuration (gravity, timestep, etc.).
|
* For passing physics configuration (gravity, timestep, etc.).
|
||||||
*/
|
*/
|
||||||
export const PhysicsConfigToken = createServiceToken<PhysicsConfig>('physicsConfig');
|
export const PhysicsConfigToken = createServiceToken<PhysicsConfig>('physicsConfig');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 碰撞层配置令牌
|
||||||
|
* Collision layer config token
|
||||||
|
*
|
||||||
|
* 用于获取碰撞层配置服务。
|
||||||
|
* For getting collision layer config service.
|
||||||
|
*/
|
||||||
|
export const CollisionLayerConfigToken = createServiceToken<ICollisionLayerConfig>('collisionLayerConfig');
|
||||||
|
|||||||
Reference in New Issue
Block a user