Files
esengine/packages/editor-app/src/stores/BuildSettingsStore.ts
T

515 lines
18 KiB
TypeScript
Raw Normal View History

2025-12-13 19:44:08 +08:00
/**
* Build Settings Store - 构建设置状态管理
* Build Settings State Management
*
* 使用 Zustand 替代 BuildSettingsPanel 中的大量 useEffect 和 useState
* Using Zustand to replace numerous useEffect and useState in BuildSettingsPanel
*/
import { create } from 'zustand';
import { subscribeWithSelector } from 'zustand/middleware';
import type {
BuildService,
BuildProgress,
BuildConfig,
WebBuildConfig,
WeChatBuildConfig,
ProjectService,
BuildSettingsConfig
} from '@esengine/editor-core';
import { BuildPlatform, BuildStatus } from '@esengine/editor-core';
import { EngineService } from '../services/EngineService';
// ============= Types =============
export type PlatformType =
| 'windows'
| 'macos'
| 'linux'
| 'android'
| 'ios'
| 'web'
| 'wechat-minigame';
export interface BuildProfile {
id: string;
name: string;
platform: PlatformType;
isActive?: boolean;
}
export interface SceneEntry {
path: string;
enabled: boolean;
}
export interface BuildSettings {
scenes: SceneEntry[];
scriptingDefines: string[];
companyName: string;
productName: string;
version: string;
developmentBuild: boolean;
sourceMap: boolean;
compressionMethod: 'Default' | 'LZ4' | 'LZ4HC';
buildMode: 'split-bundles' | 'single-bundle' | 'single-file';
}
export interface BuildResult {
success: boolean;
outputPath: string;
duration: number;
warnings: string[];
error?: string;
}
interface BuildSettingsState {
// 配置状态 | Profile state
profiles: BuildProfile[];
selectedPlatform: PlatformType;
selectedProfile: BuildProfile | null;
settings: BuildSettings;
// UI 状态 | UI state
expandedSections: Record<string, boolean>;
// 构建状态 | Build state
isBuilding: boolean;
buildProgress: BuildProgress | null;
buildResult: BuildResult | null;
showBuildProgress: boolean;
// 服务引用 | Service references
_buildService: BuildService | null;
_projectService: ProjectService | null;
_projectPath: string | null;
// 内部状态 | Internal state
_initialized: boolean;
_saveTimeout: NodeJS.Timeout | null;
_progressInterval: NodeJS.Timeout | null;
}
interface BuildSettingsActions {
// 初始化 | Initialization
initialize: (params: {
projectPath: string;
buildService?: BuildService;
projectService?: ProjectService;
availableScenes?: string[];
}) => void;
cleanup: () => void;
// 配置操作 | Profile actions
setSelectedPlatform: (platform: PlatformType) => void;
setSelectedProfile: (profile: BuildProfile | null) => void;
addProfile: () => void;
// 设置操作 | Settings actions
updateSettings: (partial: Partial<BuildSettings>) => void;
setSceneEnabled: (index: number, enabled: boolean) => void;
addScene: (path: string) => void;
addDefine: (define: string) => void;
removeDefine: (index: number) => void;
// UI 操作 | UI actions
toggleSection: (section: string) => void;
// 构建操作 | Build actions
startBuild: () => Promise<void>;
cancelBuild: () => void;
closeBuildProgress: () => void;
}
export type BuildSettingsStore = BuildSettingsState & BuildSettingsActions;
// ============= Constants =============
const DEFAULT_SETTINGS: BuildSettings = {
scenes: [],
scriptingDefines: [],
companyName: 'DefaultCompany',
productName: 'MyGame',
version: '0.1.0',
developmentBuild: false,
sourceMap: false,
compressionMethod: 'Default',
buildMode: 'split-bundles',
};
const DEFAULT_PROFILES: BuildProfile[] = [
{ id: 'web-dev', name: 'Web - Development', platform: 'web', isActive: true },
{ id: 'web-prod', name: 'Web - Production', platform: 'web' },
{ id: 'wechat', name: 'WeChat Mini Game', platform: 'wechat-minigame' },
];
// ============= Helper Functions =============
const getPlatformEnum = (platformType: PlatformType): BuildPlatform => {
const platformMap: Record<PlatformType, BuildPlatform> = {
'web': BuildPlatform.Web,
'wechat-minigame': BuildPlatform.WeChatMiniGame,
'windows': BuildPlatform.Desktop,
'macos': BuildPlatform.Desktop,
'linux': BuildPlatform.Desktop,
'android': BuildPlatform.Android,
'ios': BuildPlatform.iOS
};
return platformMap[platformType];
};
// ============= Store =============
export const useBuildSettingsStore = create<BuildSettingsStore>()(
subscribeWithSelector((set, get) => ({
// 初始状态 | Initial state
profiles: DEFAULT_PROFILES,
selectedPlatform: 'web',
selectedProfile: DEFAULT_PROFILES[0] ?? null,
settings: DEFAULT_SETTINGS,
expandedSections: {
sceneList: true,
scriptingDefines: true,
platformSettings: true,
playerSettings: true,
},
isBuilding: false,
buildProgress: null,
buildResult: null,
showBuildProgress: false,
_buildService: null,
_projectService: null,
_projectPath: null,
_initialized: false,
_saveTimeout: null,
_progressInterval: null,
// ===== 初始化 | Initialization =====
initialize: ({ projectPath, buildService, projectService, availableScenes }) => {
const state = get();
if (state._initialized) return;
set({
_buildService: buildService || null,
_projectService: projectService || null,
_projectPath: projectPath,
_initialized: true,
});
// 从 projectService 加载已保存的设置
// Load saved settings from projectService
if (projectService) {
const savedSettings = projectService.getBuildSettings();
if (savedSettings) {
set(prev => ({
settings: {
...prev.settings,
scriptingDefines: savedSettings.scriptingDefines || [],
companyName: savedSettings.companyName || prev.settings.companyName,
productName: savedSettings.productName || prev.settings.productName,
version: savedSettings.version || prev.settings.version,
developmentBuild: savedSettings.developmentBuild ?? prev.settings.developmentBuild,
sourceMap: savedSettings.sourceMap ?? prev.settings.sourceMap,
compressionMethod: savedSettings.compressionMethod || prev.settings.compressionMethod,
buildMode: savedSettings.buildMode || prev.settings.buildMode
}
}));
}
// 初始化场景列表
// Initialize scene list
if (availableScenes && availableScenes.length > 0) {
const savedScenes = savedSettings?.scenes || [];
set(prev => ({
settings: {
...prev.settings,
scenes: availableScenes.map(path => ({
path,
enabled: savedScenes.length > 0 ? savedScenes.includes(path) : true
}))
}
}));
}
}
},
cleanup: () => {
const state = get();
// 清理定时器 | Clear timers
if (state._saveTimeout) {
clearTimeout(state._saveTimeout);
}
if (state._progressInterval) {
clearInterval(state._progressInterval);
}
set({
_buildService: null,
_projectService: null,
_projectPath: null,
_initialized: false,
_saveTimeout: null,
_progressInterval: null,
isBuilding: false,
buildProgress: null,
buildResult: null,
showBuildProgress: false,
});
},
// ===== 配置操作 | Profile actions =====
setSelectedPlatform: (platform) => {
const { profiles } = get();
const profile = profiles.find(p => p.platform === platform);
set({
selectedPlatform: platform,
selectedProfile: profile || null
});
},
setSelectedProfile: (profile) => {
set({
selectedProfile: profile,
selectedPlatform: profile?.platform || get().selectedPlatform
});
},
addProfile: () => {
const { selectedPlatform, profiles } = get();
const newProfile: BuildProfile = {
id: `profile-${Date.now()}`,
name: `${selectedPlatform} - New Profile`,
platform: selectedPlatform,
};
set({
profiles: [...profiles, newProfile],
selectedProfile: newProfile
});
},
// ===== 设置操作 | Settings actions =====
updateSettings: (partial) => {
set(prev => ({
settings: { ...prev.settings, ...partial }
}));
// 防抖保存 | Debounced save
const state = get();
if (state._saveTimeout) {
clearTimeout(state._saveTimeout);
}
const timeout = setTimeout(() => {
const { _projectService, settings } = get();
if (_projectService) {
const configToSave: BuildSettingsConfig = {
scenes: settings.scenes.filter(s => s.enabled).map(s => s.path),
scriptingDefines: settings.scriptingDefines,
companyName: settings.companyName,
productName: settings.productName,
version: settings.version,
developmentBuild: settings.developmentBuild,
sourceMap: settings.sourceMap,
compressionMethod: settings.compressionMethod,
buildMode: settings.buildMode
};
_projectService.updateBuildSettings(configToSave);
}
}, 500);
set({ _saveTimeout: timeout });
},
setSceneEnabled: (index, enabled) => {
set(prev => ({
settings: {
...prev.settings,
scenes: prev.settings.scenes.map((s, i) =>
i === index ? { ...s, enabled } : s
)
}
}));
// 触发保存 | Trigger save
get().updateSettings({});
},
addScene: (path) => {
const { settings } = get();
if (settings.scenes.some(s => s.path === path)) return;
set(prev => ({
settings: {
...prev.settings,
scenes: [...prev.settings.scenes, { path, enabled: true }]
}
}));
get().updateSettings({});
},
addDefine: (define) => {
set(prev => ({
settings: {
...prev.settings,
scriptingDefines: [...prev.settings.scriptingDefines, define]
}
}));
get().updateSettings({});
},
removeDefine: (index) => {
set(prev => ({
settings: {
...prev.settings,
scriptingDefines: prev.settings.scriptingDefines.filter((_, i) => i !== index)
}
}));
get().updateSettings({});
},
// ===== UI 操作 | UI actions =====
toggleSection: (section) => {
set(prev => ({
expandedSections: {
...prev.expandedSections,
[section]: !prev.expandedSections[section]
}
}));
},
// ===== 构建操作 | Build actions =====
startBuild: async () => {
const { selectedProfile, settings, _buildService, _projectPath } = get();
if (!selectedProfile || !_projectPath || !_buildService) {
console.warn('Cannot start build: missing profile, path, or service');
return;
}
set({
isBuilding: true,
buildProgress: null,
buildResult: null,
showBuildProgress: true,
});
// 启动进度轮询(但不用 setInterval,用 buildService 的回调)
// Start progress polling (but use buildService callback, not setInterval)
try {
const platform = getPlatformEnum(selectedProfile.platform);
const baseConfig = {
platform,
outputPath: `${_projectPath}/build/${selectedProfile.platform}`,
isRelease: !settings.developmentBuild,
sourceMap: settings.sourceMap,
scenes: settings.scenes.filter(s => s.enabled).map(s => s.path)
};
let buildConfig: BuildConfig;
if (platform === BuildPlatform.Web) {
// 从 AssetLoaderFactory 获取插件注册的扩展名
// Get plugin-registered extensions from AssetLoaderFactory
let assetExtensions: string[] | undefined;
let assetTypeMap: Record<string, string> | undefined;
try {
const assetManager = EngineService.getInstance().getAssetManager();
if (assetManager) {
const loaderFactory = assetManager.getLoaderFactory();
assetExtensions = loaderFactory.getAllSupportedExtensions();
assetTypeMap = loaderFactory.getExtensionTypeMap();
}
} catch (e) {
console.warn('Failed to get asset extensions from loader factory:', e);
}
const webConfig: WebBuildConfig = {
...baseConfig,
platform: BuildPlatform.Web,
buildMode: settings.buildMode,
generateHtml: true,
minify: !settings.developmentBuild,
generateAssetCatalog: true,
assetLoadingStrategy: 'on-demand',
assetExtensions,
assetTypeMap
};
buildConfig = webConfig;
} else if (platform === BuildPlatform.WeChatMiniGame) {
const wechatConfig: WeChatBuildConfig = {
...baseConfig,
platform: BuildPlatform.WeChatMiniGame,
appId: '',
useSubpackages: false,
mainPackageLimit: 4096,
usePlugins: false
};
buildConfig = wechatConfig;
} else {
buildConfig = baseConfig;
}
// 使用回调更新进度,而不是轮询
// Use callback to update progress, not polling
const result = await _buildService.build(buildConfig, (progress) => {
set({ buildProgress: progress });
});
set({
buildResult: {
success: result.success,
outputPath: result.outputPath,
duration: result.duration,
warnings: result.warnings,
error: result.error
}
});
} catch (error) {
console.error('Build failed:', error);
set({
buildResult: {
success: false,
outputPath: '',
duration: 0,
warnings: [],
error: error instanceof Error ? error.message : String(error)
}
});
} finally {
set({ isBuilding: false });
}
},
cancelBuild: () => {
const { _buildService } = get();
if (_buildService) {
_buildService.cancelBuild();
}
},
closeBuildProgress: () => {
const { isBuilding } = get();
if (!isBuilding) {
set({
showBuildProgress: false,
buildProgress: null,
buildResult: null,
});
}
},
}))
);
// ============= Selectors =============
/** 获取当前平台的配置列表 | Get profiles for current platform */
export const selectProfilesForPlatform = (state: BuildSettingsStore): BuildProfile[] => {
return state.profiles.filter(p => p.platform === state.selectedPlatform);
};
/** 获取启用的场景列表 | Get enabled scenes */
export const selectEnabledScenes = (state: BuildSettingsStore): string[] => {
return state.settings.scenes.filter(s => s.enabled).map(s => s.path);
};