diff --git a/source/Component/HeaderBar/HeaderBar.tsx b/source/Component/HeaderBar/HeaderBar.tsx index b287681..8322029 100644 --- a/source/Component/HeaderBar/HeaderBar.tsx +++ b/source/Component/HeaderBar/HeaderBar.tsx @@ -123,7 +123,7 @@ class HeaderWindowsAction extends Component { * 头部信息栏 */ @useSettingWithEvent("language") -@useStatusWithEvent("fileChange") +@useStatusWithEvent("fileSave") class HeaderBar extends Component { public render(): ReactNode { diff --git a/source/Context/Status.tsx b/source/Context/Status.tsx index bc857ec..b9fa62f 100644 --- a/source/Context/Status.tsx +++ b/source/Context/Status.tsx @@ -30,7 +30,7 @@ function randomColor(unNormal: boolean = false) { } interface IStatusEvent { - fileChange: void; + fileSave: void; renderLoop: number; physicsLoop: number; mouseModChange: void; @@ -145,7 +145,7 @@ class Status extends Emitter { this.on("behaviorAttrChange", updateBehaviorParameter); // 映射文件状态改变事件 - this.archive.on("fileChange", () => this.emit("fileChange")); + this.archive.on("fileSave", () => this.emit("fileSave")); } public bindRenderer(renderer: AbstractRenderer) { diff --git a/source/Model/Archive.ts b/source/Model/Archive.ts index e446953..a0e1f84 100644 --- a/source/Model/Archive.ts +++ b/source/Model/Archive.ts @@ -1,13 +1,17 @@ import { Emitter, EventType } from "@Model/Emitter"; -import { IArchiveCtrlObject } from "@Model/CtrlObject"; +import { CtrlObject, IArchiveCtrlObject } from "@Model/CtrlObject"; import { Model } from "@Model/Model"; -import { IArchiveLabel } from "@Model/Label"; +import { IArchiveLabel, Label } from "@Model/Label"; import { Group, IArchiveGroup } from "@Model/Group"; -import { IArchiveIndividual } from "@Model/Individual"; -import { IArchiveBehavior } from "@Model/Behavior"; +import { Range } from "@Model/Range"; +import { IArchiveIndividual, Individual } from "@Model/Individual"; +import { Behavior, IArchiveBehavior } from "@Model/Behavior"; +import { getBehaviorById } from "@Behavior/Behavior"; +import { IArchiveParseFn, IObjectParamArchiveType, IRealObjectType } from "@Model/Parameter"; interface IArchiveEvent { - fileChange: Archive; + fileSave: Archive; + fileLoad: Archive; } interface IArchiveObject { @@ -17,10 +21,7 @@ interface IArchiveObject { behaviorPool: IArchiveBehavior[]; } -class Archive< - M extends any = any, - E extends Record = {} -> extends Emitter { +class Archive extends Emitter { /** * 是否为新文件 @@ -43,10 +44,9 @@ class Archive< public fileData?: M; /** - * 保存文件 - * 模型转换为文件 + * 将模型转换为存档对象 */ - public save(model: Model): string { + public parseModel2Archive(model: Model): string { // 存贮 CtrlObject const objectPool: IArchiveCtrlObject[] = []; @@ -89,21 +89,179 @@ class Archive< behaviorPool: behaviorPool }; - console.log(fileData); - console.log({value: JSON.stringify(fileData)}); + return JSON.stringify(fileData); + } + + /** + * 加载存档到模型 + */ + public loadArchiveIntoModel(model: Model, data: string): void { + + // 解析为 JSON 对象 + const archive: IArchiveObject = JSON.parse(data); + console.log(archive); + + // 实例化全部对象 + const objectPool: CtrlObject[] = []; + const individualPool: Individual[] = []; + archive.objectPool.forEach((obj) => { + + let ctrlObject: CtrlObject | undefined = undefined; + + // 处理群 + if (obj.objectType === "G") { + const archiveGroup: IArchiveGroup = obj as any; + const newGroup = new Group(model); + + // 实例化全部个体 + const individuals: Array = []; + archiveGroup.individuals.forEach((item) => { + const newIndividual = new Individual(newGroup); + newIndividual.id = item.id; + individuals.push(newIndividual); + individualPool.push(newIndividual); + }) + + newGroup.cacheIndividualsArray = individuals; + ctrlObject = newGroup; + } + + // 处理范围 + if (obj.objectType === "R") { + ctrlObject = new Range(model); + } + + if (ctrlObject) { + ctrlObject.id = obj.id; + objectPool.push(ctrlObject); + } + }); + + // 实例化全部标签 + const labelPool: Label[] = []; + archive.labelPool.forEach((item) => { + const newLabel = new Label(model); + newLabel.id = item.id; + labelPool.push(newLabel); + }); + + // 实例化全部行为 + const behaviorPool: Behavior[] = []; + archive.behaviorPool.forEach((item) => { + const recorder = getBehaviorById(item.behaviorId); + const newBehavior = recorder.new(); + newBehavior.id = item.id; + behaviorPool.push(newBehavior); + }); + + // 内置标签集合 + const buildInLabel = [model.allGroupLabel, model.allRangeLabel, model.currentGroupLabel] + + const search: (pool: T[], archive: IObjectParamArchiveType) => T | undefined = + (pool, archive) => { + for (let i = 0; i < pool.length; i++) { + if (pool[i].id === archive.__LIVING_TOGETHER_OBJECT_ID) { + return pool[i]; + } + } + + return undefined; + }; + + const searchAll: (archive: IObjectParamArchiveType) => IRealObjectType | undefined = + (archive) => { + return void 0 ?? + search(individualPool, archive) ?? + search(objectPool, archive) ?? + search(labelPool, archive) ?? + search(buildInLabel, archive) ?? + search(behaviorPool, archive); + } + + // 实例搜索函数 + const parseFunction: IArchiveParseFn = (archive) => { + + switch (archive.__LIVING_TOGETHER_OBJECT_TYPE) { + + // 在个体池搜索 + case "Individual": + return search(individualPool, archive) ?? searchAll(archive); + + // 对象类型在对象池中搜索 + case "Range": + case "Group": + return search(objectPool, archive) ?? searchAll(archive); + + // 在标签池搜索 + case "Label": + return search(labelPool, archive) ?? search(buildInLabel, archive) ?? searchAll(archive); + + // 在标签池搜索 + case "Behavior": + return search(behaviorPool, archive) ?? searchAll(archive); + + // 在全部池子搜索 + default: + return searchAll(archive); + } + } + + // 加载对象的属性 + model.objectPool = objectPool.map((obj, index) => { + + // 加载属性 + obj.fromArchive(archive.objectPool[index], parseFunction); + + // 加载个体属性 + if (obj instanceof Group) { + + const archiveGroup: IArchiveGroup = archive.objectPool[index] as any; + + obj.individuals = new Set(obj.cacheIndividualsArray.map((item, i) => { + item.fromArchive(archiveGroup.individuals[i], parseFunction); + return item; + })); + } + + return obj; + }); + + // 加载标签属性 + model.labelPool = labelPool.map((item, index) => { + item.fromArchive(archive.labelPool[index]); + return item; + }); + + // 加载行为属性 + model.behaviorPool = behaviorPool.map((item, index) => { + item.fromArchive(archive.behaviorPool[index], parseFunction); + return item; + }); + } + + /** + * 保存文件 + * 模型转换为文件 + */ + public save(model: Model): void { + + console.log(this.parseModel2Archive(model)); this.isSaved = true; - this.emit( ...["fileChange", this] as any ); - - return ""; + this.emit("fileSave", this); } /** * 加载文件为模型 - * return Model + * @return Model */ - public load(model: Model, data: string) {}; + public load(model: Model, data: string) { + + this.loadArchiveIntoModel(model, data); + + this.isSaved = true; + this.emit("fileLoad", this); + }; } -export { Archive }; -export default Archive; \ No newline at end of file +export { Archive }; \ No newline at end of file diff --git a/source/Model/Behavior.ts b/source/Model/Behavior.ts index ffdd9b4..0b84206 100644 --- a/source/Model/Behavior.ts +++ b/source/Model/Behavior.ts @@ -220,12 +220,12 @@ class Behavior< } public fromArchive(archive: IArchiveBehavior, paster: IArchiveParseFn): void { - this.name = this.name, - this.id = this.id, - this.color = this.color.concat([]), - this.priority = this.priority, - this.currentGroupKey = this.currentGroupKey.concat([]) as any, - this.deleteFlag = this.deleteFlag, + this.name = archive.name, + this.id = archive.id, + this.color = archive.color.concat([]), + this.priority = archive.priority, + this.currentGroupKey = archive.currentGroupKey.concat([]) as any, + this.deleteFlag = archive.deleteFlag, this.parameter = archiveObject2Parameter( archive.parameter, paster ) as any; diff --git a/source/Model/Group.ts b/source/Model/Group.ts index fb3184e..024b737 100644 --- a/source/Model/Group.ts +++ b/source/Model/Group.ts @@ -37,6 +37,11 @@ class Group extends CtrlObject { */ public individuals: Set = new Set(); + /** + * 缓存的 individuals 数组, 用于存档加载 + */ + public cacheIndividualsArray: Array = []; + /** * 个体生成方式 */ diff --git a/source/Model/Individual.ts b/source/Model/Individual.ts index 27fcb9c..d8685c7 100644 --- a/source/Model/Individual.ts +++ b/source/Model/Individual.ts @@ -234,11 +234,11 @@ class Individual { } } - this.id = this.id, - this.position = this.position.concat([]), - this.velocity = this.velocity.concat([]), - this.acceleration = this.acceleration.concat([]), - this.force = this.force.concat([]), + this.id = archive.id, + this.position = archive.position.concat([]), + this.velocity = archive.velocity.concat([]), + this.acceleration = archive.acceleration.concat([]), + this.force = archive.force.concat([]), this.metaData = metaData; } } diff --git a/source/Model/Parameter.ts b/source/Model/Parameter.ts index 6e4dc33..510b325 100644 --- a/source/Model/Parameter.ts +++ b/source/Model/Parameter.ts @@ -3,10 +3,11 @@ import { Range } from "@Model/Range"; import { Label } from "@Model/Label"; import { Behavior, IAnyBehavior } from "@Model/Behavior"; import { Individual } from "@Model/Individual"; +import { CtrlObject } from "@Model/CtrlObject"; type IObjectParamArchiveType = { __LIVING_TOGETHER_OBJECT_ID: string; - __LIVING_TOGETHER_OBJECT_TYPE: string; + __LIVING_TOGETHER_OBJECT_TYPE: "Individual" | "Range" | "Group" | "Label" | "Behavior"; } type IObjectParamCacheType = { @@ -225,36 +226,36 @@ function getDefaultValue

(option: IParameterOption

): IP return defaultObj; } -type IRealObjectType = Range | Group | Label | IAnyBehavior; +type IRealObjectType = Range | Group | Label | IAnyBehavior | Individual | CtrlObject; type IArchiveParseFn = (archive: IObjectParamArchiveType) => IRealObjectType | undefined; function object2ArchiveObject(object: IRealObjectType | IRealObjectType[] | any, testArray: boolean = true): IObjectParamArchiveType | IObjectParamArchiveType[] | undefined { if (object instanceof Individual) { return { - __LIVING_TOGETHER_OBJECT_ID: "Individual", - __LIVING_TOGETHER_OBJECT_TYPE: object.id + __LIVING_TOGETHER_OBJECT_ID: object.id, + __LIVING_TOGETHER_OBJECT_TYPE: "Individual" } } else if (object instanceof Range) { return { - __LIVING_TOGETHER_OBJECT_ID: "Range", - __LIVING_TOGETHER_OBJECT_TYPE: object.id + __LIVING_TOGETHER_OBJECT_ID: object.id, + __LIVING_TOGETHER_OBJECT_TYPE: "Range" } } else if (object instanceof Group) { return { - __LIVING_TOGETHER_OBJECT_ID: "Group", - __LIVING_TOGETHER_OBJECT_TYPE: object.id + __LIVING_TOGETHER_OBJECT_ID: object.id, + __LIVING_TOGETHER_OBJECT_TYPE: "Group" } } else if (object instanceof Label) { return { - __LIVING_TOGETHER_OBJECT_ID: "Label", - __LIVING_TOGETHER_OBJECT_TYPE: object.id + __LIVING_TOGETHER_OBJECT_ID: object.id, + __LIVING_TOGETHER_OBJECT_TYPE: "Label" } } else if (object instanceof Behavior) { return { - __LIVING_TOGETHER_OBJECT_ID: "Behavior", - __LIVING_TOGETHER_OBJECT_TYPE: object.id + __LIVING_TOGETHER_OBJECT_ID: object.id, + __LIVING_TOGETHER_OBJECT_TYPE: "Behavior" } } else if (Array.isArray(object) && testArray) { const hasValue = (item: any): item is IObjectParamArchiveType => !!item; @@ -322,7 +323,7 @@ function archiveObject2Parameter

(parameter[key] as any) = { picker: picker ? parse(picker) : picker, - objects: objects ? parse(objects) : objects + objects: objects ? Array.isArray(objects) ? objects.map(item => parse(item)) : parse(objects) : objects } } @@ -359,5 +360,5 @@ export { IParameterOptionItem, IParameter, IParameterOption, IParameterValue, object2ArchiveObject, parameter2ArchiveObject, archiveObject2Parameter, IArchiveParseFn, IObjectParamArchiveType, isArchiveObjectType, - IArchiveParameterValue + IArchiveParameterValue, IRealObjectType } \ No newline at end of file