Add model & behavior archive function #44

Merged
MrKBear merged 4 commits from dev-mrkbear into master 2022-04-23 20:33:01 +08:00
11 changed files with 314 additions and 56 deletions

View File

@ -123,7 +123,7 @@ class HeaderWindowsAction extends Component<IMixinElectronProps> {
* *
*/ */
@useSettingWithEvent("language") @useSettingWithEvent("language")
@useStatusWithEvent("fileChange") @useStatusWithEvent("fileSave")
class HeaderBar extends Component<IHeaderBarProps & IMixinStatusProps & IMixinSettingProps> { class HeaderBar extends Component<IHeaderBarProps & IMixinStatusProps & IMixinSettingProps> {
public render(): ReactNode { public render(): ReactNode {

View File

@ -30,7 +30,8 @@ function randomColor(unNormal: boolean = false) {
} }
interface IStatusEvent { interface IStatusEvent {
fileChange: void; fileSave: void;
fileLoad: void;
renderLoop: number; renderLoop: number;
physicsLoop: number; physicsLoop: number;
mouseModChange: void; mouseModChange: void;
@ -145,7 +146,19 @@ class Status extends Emitter<IStatusEvent> {
this.on("behaviorAttrChange", updateBehaviorParameter); this.on("behaviorAttrChange", updateBehaviorParameter);
// 映射文件状态改变事件 // 映射文件状态改变事件
this.archive.on("fileChange", () => this.emit("fileChange")); this.archive.on("fileSave", () => this.emit("fileSave"));
// 处理存档加载事件
this.archive.on("fileLoad", () => {
// 触发对象修改
this.emit("objectChange");
this.emit("labelChange");
this.emit("behaviorChange");
// 映射
this.emit("fileLoad");
})
} }
public bindRenderer(renderer: AbstractRenderer) { public bindRenderer(renderer: AbstractRenderer) {

View File

@ -1,22 +1,27 @@
import { Emitter, EventType } from "@Model/Emitter"; import { Emitter, EventType } from "@Model/Emitter";
import { IArchiveCtrlObject } from "@Model/CtrlObject"; import { CtrlObject, IArchiveCtrlObject } from "@Model/CtrlObject";
import { Model } from "@Model/Model"; import { Model } from "@Model/Model";
import { IArchiveLabel } from "@Model/Label"; import { IArchiveLabel, Label } from "@Model/Label";
import { Group, IArchiveGroup } from "@Model/Group";
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 { interface IArchiveEvent {
fileChange: Archive; fileSave: Archive;
fileLoad: Archive;
} }
interface IArchiveObject { interface IArchiveObject {
nextIndividualId: number; nextIndividualId: number;
objectPool: IArchiveCtrlObject[]; objectPool: IArchiveCtrlObject[];
labelPool: IArchiveLabel[]; labelPool: IArchiveLabel[];
behaviorPool: IArchiveBehavior[];
} }
class Archive< class Archive<M extends any = any> extends Emitter<IArchiveEvent> {
M extends any = any,
E extends Record<EventType, any> = {}
> extends Emitter<E & IArchiveEvent> {
/** /**
* *
@ -39,44 +44,224 @@ class Archive<
public fileData?: M; public fileData?: M;
/** /**
* *
*
*/ */
public save(model: Model): string { public parseModel2Archive(model: Model): string {
// 存贮 CtrlObject // 存贮 CtrlObject
const objectPool: IArchiveCtrlObject[] = []; const objectPool: IArchiveCtrlObject[] = [];
model.objectPool.forEach(obj => { model.objectPool.forEach(obj => {
objectPool.push(obj.toArchive()); let archiveObject = obj.toArchive();
// 处理每个群的个体
if (archiveObject.objectType === "G") {
const archiveGroup: IArchiveGroup = archiveObject as any;
const group: Group = obj as Group;
const individuals: IArchiveIndividual[] = [];
group.individuals.forEach((item) => {
individuals.push(item.toArchive());
});
archiveGroup.individuals = individuals;
}
objectPool.push(archiveObject);
}) })
// 存储 Label // 存储 Label
const labelPool: IArchiveLabel[] = []; const labelPool: IArchiveLabel[] = [];
model.labelPool.forEach(obj => { model.labelPool.forEach(obj => {
labelPool.push(obj.toArchive()); labelPool.push(obj.toArchive());
}) });
// 存储全部行为
const behaviorPool: IArchiveBehavior[] = [];
model.behaviorPool.forEach(obj => {
behaviorPool.push(obj.toArchive());
});
// 生成存档对象
const fileData: IArchiveObject = { const fileData: IArchiveObject = {
nextIndividualId: model.nextIndividualId, nextIndividualId: model.nextIndividualId,
objectPool: objectPool, objectPool: objectPool,
labelPool: labelPool labelPool: labelPool,
behaviorPool: behaviorPool
}; };
console.log(fileData); return JSON.stringify(fileData);
console.log({value: 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<Individual> = [];
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: <T extends IRealObjectType>(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.isSaved = true;
this.emit( ...["fileChange", this] as any ); this.emit("fileSave", this);
return "";
} }
/** /**
* *
* 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 { Archive };
export default Archive;

View File

@ -3,7 +3,11 @@ import { v4 as uuid } from "uuid";
import type { Individual } from "@Model/Individual"; import type { Individual } from "@Model/Individual";
import type { Group } from "@Model/Group"; import type { Group } from "@Model/Group";
import type { Model } from "@Model/Model"; import type { Model } from "@Model/Model";
import { getDefaultValue, IParameter, IParameterOption, IParameterValue } from "@Model/Parameter"; import {
archiveObject2Parameter,
getDefaultValue, IArchiveParameterValue, IArchiveParseFn,
IParameter, IParameterOption, IParameterValue, parameter2ArchiveObject
} from "@Model/Parameter";
/** /**
* *
@ -113,6 +117,17 @@ class BehaviorRecorder<
} }
} }
interface IArchiveBehavior {
behaviorId: string;
name: string;
id: string;
color: number[];
priority: number;
currentGroupKey: string[];
deleteFlag: boolean;
parameter: IArchiveParameterValue<IParameter>;
}
/** /**
* *
*/ */
@ -189,6 +204,33 @@ class Behavior<
return this.deleteFlag; return this.deleteFlag;
} }
public toArchive(): IArchiveBehavior {
return {
behaviorId: this.behaviorId,
name: this.name,
id: this.id,
color: this.color.concat([]),
priority: this.priority,
currentGroupKey: this.currentGroupKey.concat([]) as any,
deleteFlag: this.deleteFlag,
parameter: parameter2ArchiveObject(
this.parameter, this.parameterOption
)
};
}
public fromArchive(archive: IArchiveBehavior, paster: IArchiveParseFn): void {
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;
}
/** /**
* *
*/ */
@ -241,6 +283,6 @@ class Behavior<
type IRenderBehavior = BehaviorInfo | Behavior; type IRenderBehavior = BehaviorInfo | Behavior;
export { export {
Behavior, BehaviorRecorder, IAnyBehavior, IAnyBehaviorRecorder, BehaviorInfo, IRenderBehavior Behavior, BehaviorRecorder, IAnyBehavior, IAnyBehaviorRecorder,
}; BehaviorInfo, IRenderBehavior, IArchiveBehavior
export default { Behavior }; };

View File

@ -4,16 +4,18 @@ import type { IAnyObject, Model } from "@Model/Model";
import type { ObjectID } from "@Model/Model"; import type { ObjectID } from "@Model/Model";
import { import {
parameter2ArchiveObject, archiveObject2Parameter, parameter2ArchiveObject, archiveObject2Parameter,
IArchiveParseFn, IObjectParamArchiveType, object2ArchiveObject IArchiveParseFn, IObjectParamArchiveType, object2ArchiveObject,
IArchiveParameterValue, IParameter
} from "@Model/Parameter"; } from "@Model/Parameter";
interface IArchiveCtrlObject { interface IArchiveCtrlObject {
objectType: "R" | "G";
displayName: CtrlObject["displayName"]; displayName: CtrlObject["displayName"];
color: CtrlObject["color"]; color: CtrlObject["color"];
display: CtrlObject["display"]; display: CtrlObject["display"];
update: CtrlObject["update"]; update: CtrlObject["update"];
id: string; id: string;
renderParameter: any; renderParameter: IArchiveParameterValue<IParameter>;
deleteFlag: CtrlObject["deleteFlag"]; deleteFlag: CtrlObject["deleteFlag"];
labels: IObjectParamArchiveType[]; labels: IObjectParamArchiveType[];
} }

View File

@ -4,7 +4,11 @@ import type { Behavior, IAnyBehavior } from "@Model/Behavior";
import { Label } from "@Model/Label"; import { Label } from "@Model/Label";
import { Range } from "@Model/Range"; import { Range } from "@Model/Range";
import { Model, ObjectID } from "@Model/Model"; import { Model, ObjectID } from "@Model/Model";
import { getDefaultValue, IArchiveParseFn, IObjectParamArchiveType, object2ArchiveObject } from "@Model/Parameter"; import { IArchiveIndividual } from "@Model/Individual";
import {
getDefaultValue, IArchiveParseFn,
IObjectParamArchiveType, object2ArchiveObject
} from "@Model/Parameter";
enum GenMod { enum GenMod {
Point = "p", Point = "p",
@ -12,6 +16,7 @@ enum GenMod {
} }
interface IArchiveGroup { interface IArchiveGroup {
individuals: IArchiveIndividual[];
genMethod: Group["genMethod"]; genMethod: Group["genMethod"];
genPoint: Group["genPoint"]; genPoint: Group["genPoint"];
genRange: IObjectParamArchiveType | undefined; genRange: IObjectParamArchiveType | undefined;
@ -32,6 +37,11 @@ class Group extends CtrlObject<IArchiveGroup> {
*/ */
public individuals: Set<Individual> = new Set(); public individuals: Set<Individual> = new Set();
/**
* individuals ,
*/
public cacheIndividualsArray: Array<Individual> = [];
/** /**
* *
*/ */
@ -421,6 +431,7 @@ class Group extends CtrlObject<IArchiveGroup> {
public override toArchive(): IArchiveCtrlObject & IArchiveGroup { public override toArchive(): IArchiveCtrlObject & IArchiveGroup {
return { return {
...super.toArchive(), ...super.toArchive(),
objectType: "G",
genMethod: this.genMethod, genMethod: this.genMethod,
genPoint: this.genPoint.concat([]), genPoint: this.genPoint.concat([]),
genRange: object2ArchiveObject(this.genRange) as IObjectParamArchiveType, genRange: object2ArchiveObject(this.genRange) as IObjectParamArchiveType,
@ -456,4 +467,4 @@ class Group extends CtrlObject<IArchiveGroup> {
} }
} }
export { Group, GenMod }; export { Group, GenMod, IArchiveGroup };

View File

@ -234,13 +234,13 @@ class Individual {
} }
} }
this.id = this.id, this.id = archive.id,
this.position = this.position.concat([]), this.position = archive.position.concat([]),
this.velocity = this.velocity.concat([]), this.velocity = archive.velocity.concat([]),
this.acceleration = this.acceleration.concat([]), this.acceleration = archive.acceleration.concat([]),
this.force = this.force.concat([]), this.force = archive.force.concat([]),
this.metaData = metaData; this.metaData = metaData;
} }
} }
export { Individual }; export { Individual, IArchiveIndividual };

View File

@ -3,10 +3,11 @@ import { Range } from "@Model/Range";
import { Label } from "@Model/Label"; import { Label } from "@Model/Label";
import { Behavior, IAnyBehavior } from "@Model/Behavior"; import { Behavior, IAnyBehavior } from "@Model/Behavior";
import { Individual } from "@Model/Individual"; import { Individual } from "@Model/Individual";
import { CtrlObject } from "@Model/CtrlObject";
type IObjectParamArchiveType = { type IObjectParamArchiveType = {
__LIVING_TOGETHER_OBJECT_ID: string; __LIVING_TOGETHER_OBJECT_ID: string;
__LIVING_TOGETHER_OBJECT_TYPE: string; __LIVING_TOGETHER_OBJECT_TYPE: "Individual" | "Range" | "Group" | "Label" | "Behavior";
} }
type IObjectParamCacheType<P, Q = P> = { type IObjectParamCacheType<P, Q = P> = {
@ -225,36 +226,36 @@ function getDefaultValue<P extends IParameter> (option: IParameterOption<P>): IP
return defaultObj; return defaultObj;
} }
type IRealObjectType = Range | Group | Label | IAnyBehavior; type IRealObjectType = Range | Group | Label | IAnyBehavior | Individual | CtrlObject;
type IArchiveParseFn = (archive: IObjectParamArchiveType) => IRealObjectType | undefined; type IArchiveParseFn = (archive: IObjectParamArchiveType) => IRealObjectType | undefined;
function object2ArchiveObject(object: IRealObjectType | IRealObjectType[] | any, testArray: boolean = true): function object2ArchiveObject(object: IRealObjectType | IRealObjectType[] | any, testArray: boolean = true):
IObjectParamArchiveType | IObjectParamArchiveType[] | undefined { IObjectParamArchiveType | IObjectParamArchiveType[] | undefined {
if (object instanceof Individual) { if (object instanceof Individual) {
return { return {
__LIVING_TOGETHER_OBJECT_ID: "Individual", __LIVING_TOGETHER_OBJECT_ID: object.id,
__LIVING_TOGETHER_OBJECT_TYPE: object.id __LIVING_TOGETHER_OBJECT_TYPE: "Individual"
} }
} }
else if (object instanceof Range) { else if (object instanceof Range) {
return { return {
__LIVING_TOGETHER_OBJECT_ID: "Range", __LIVING_TOGETHER_OBJECT_ID: object.id,
__LIVING_TOGETHER_OBJECT_TYPE: object.id __LIVING_TOGETHER_OBJECT_TYPE: "Range"
} }
} else if (object instanceof Group) { } else if (object instanceof Group) {
return { return {
__LIVING_TOGETHER_OBJECT_ID: "Group", __LIVING_TOGETHER_OBJECT_ID: object.id,
__LIVING_TOGETHER_OBJECT_TYPE: object.id __LIVING_TOGETHER_OBJECT_TYPE: "Group"
} }
} else if (object instanceof Label) { } else if (object instanceof Label) {
return { return {
__LIVING_TOGETHER_OBJECT_ID: "Label", __LIVING_TOGETHER_OBJECT_ID: object.id,
__LIVING_TOGETHER_OBJECT_TYPE: object.id __LIVING_TOGETHER_OBJECT_TYPE: "Label"
} }
} else if (object instanceof Behavior) { } else if (object instanceof Behavior) {
return { return {
__LIVING_TOGETHER_OBJECT_ID: "Behavior", __LIVING_TOGETHER_OBJECT_ID: object.id,
__LIVING_TOGETHER_OBJECT_TYPE: object.id __LIVING_TOGETHER_OBJECT_TYPE: "Behavior"
} }
} else if (Array.isArray(object) && testArray) { } else if (Array.isArray(object) && testArray) {
const hasValue = (item: any): item is IObjectParamArchiveType => !!item; const hasValue = (item: any): item is IObjectParamArchiveType => !!item;
@ -322,7 +323,7 @@ function archiveObject2Parameter<P extends IParameter>
(parameter[key] as any) = { (parameter[key] as any) = {
picker: picker ? parse(picker) : picker, picker: picker ? parse(picker) : picker,
objects: objects ? parse(objects) : objects objects: objects ? Array.isArray(objects) ? objects.map(item => parse(item)) : parse(objects) : objects
} }
} }
@ -358,5 +359,6 @@ export {
IParamType, IParamValue, isObjectType, isVectorType, getDefaultValue, IParamType, IParamValue, isObjectType, isVectorType, getDefaultValue,
IParameterOptionItem, IParameter, IParameterOption, IParameterValue, IParameterOptionItem, IParameter, IParameterOption, IParameterValue,
object2ArchiveObject, parameter2ArchiveObject, archiveObject2Parameter, object2ArchiveObject, parameter2ArchiveObject, archiveObject2Parameter,
IArchiveParseFn, IObjectParamArchiveType, isArchiveObjectType IArchiveParseFn, IObjectParamArchiveType, isArchiveObjectType,
IArchiveParameterValue, IRealObjectType
} }

View File

@ -34,6 +34,7 @@ class Range extends CtrlObject<IArchiveRange> {
public override toArchive(): IArchiveCtrlObject & IArchiveRange { public override toArchive(): IArchiveCtrlObject & IArchiveRange {
return { return {
...super.toArchive(), ...super.toArchive(),
objectType: "R",
position: this.position.concat([]), position: this.position.concat([]),
radius: this.radius.concat([]) radius: this.radius.concat([])
}; };

View File

@ -55,8 +55,10 @@ class SimulatorDesktop extends Component {
}) })
}; };
(window as any).setting = this.setting; (window as any).LT = {
(window as any).status = this.status; status: this.status,
setting: this.setting
};
this.electron = {} as ISimulatorAPI; this.electron = {} as ISimulatorAPI;
if ((window as any).API) { if ((window as any).API) {

View File

@ -76,7 +76,7 @@ class SimulatorWeb extends Component {
} }
// 鱼群模型测试 // 鱼群模型测试
if (true) { if (false) {
let fish1 = this.status.newGroup(); let fish1 = this.status.newGroup();
let fish2 = this.status.newGroup(); let fish2 = this.status.newGroup();
let shark = this.status.newGroup(); let shark = this.status.newGroup();