refactor: scene parser split parse component and entity (#1334)

This commit is contained in:
Hu Song
2023-02-03 17:12:38 +08:00
committed by GitHub
parent 982552cbc9
commit 3ebec9a60a
5 changed files with 161 additions and 98 deletions

View File

@@ -1,8 +1,28 @@
import type { BackgroundMode } from "@oasis-engine/core";
import { IRefObject } from "@oasis-engine/core/types/asset/IRefObject";
import { IColor } from "../mesh/IModelMesh";
export interface IPrefabFile {
entities: Array<IEntity>;
}
export interface IScene extends IPrefabFile {}
export interface IScene extends IPrefabFile {
scene: {
background: {
mode: BackgroundMode;
color: IColor;
texture?: IRefObject;
sky?: IRefObject;
};
ambient: {
ambientLight: IRefObject;
diffuseSolidColor: IColor;
diffuseIntensity: number;
specularIntensity: number;
};
};
files: Array<{ id: string; type: string; virtualPath: string; path: string }>;
}
export interface IVector3 {
x: number;
@@ -40,6 +60,6 @@ export type IClassObject = {
props?: { [key: string]: IBasicType | IMethodParams };
};
export type IBasicType = string | number | boolean | null | undefined | IReferenceType | IClassObject | IMethodParams;
export type IBasicType = string | number | boolean | null | undefined | IAssetRef | IClassObject | IMethodParams;
export type IReferenceType = { key?: string; refId: string };
export type IAssetRef = { key?: string; refId: string };

View File

@@ -3,39 +3,13 @@ import type { IEntity, IPrefabFile } from "./PrefabDesign";
import { ReflectionParser } from "./ReflectionParser";
export class PrefabParser {
constructor(private _engine: Engine) {}
parse(data: IPrefabFile): Promise<Entity> {
const entitiesMap: Record<string, Entity> = {};
const entitiesConfigMap: Record<string, IEntity> = {};
const promises: Promise<Entity>[] = [];
const entitiesConfig = data.entities;
for (const entity of entitiesConfig) {
entitiesConfigMap[entity.id] = entity;
promises.push(ReflectionParser.parseEntity(entity, this._engine));
}
return Promise.all(promises).then((entities) => {
const rootId = entitiesConfig[0].id;
entities.forEach((entity, index) => {
entitiesMap[entitiesConfig[index].id] = entity;
});
PrefabParser.parseChildren(entitiesConfigMap, entitiesMap, rootId);
return entitiesMap[rootId];
});
}
static parseChildren(
entitiesConfig: { [key: string]: IEntity },
entities: { [key: string]: Entity },
parentId: string
) {
const children = entitiesConfig[parentId].children;
static parseChildren(entitiesConfig: Map<string, IEntity>, entities: Map<string, Entity>, parentId: string) {
const children = entitiesConfig.get(parentId).children;
if (children && children.length > 0) {
const parent = entities[parentId];
const parent = entities.get(parentId);
for (let i = 0; i < children.length; i++) {
const childId = children[i];
const entity = entities[childId];
const entity = entities.get(childId);
parent.addChild(entity);
this.parseChildren(entitiesConfig, entities, childId);
}

View File

@@ -1,5 +1,5 @@
import { Engine, Entity, Loader } from "@oasis-engine/core";
import { IBasicType, IClassObject, IEntity, IReferenceType } from "./PrefabDesign";
import { IBasicType, IClassObject, IEntity, IAssetRef } from "./PrefabDesign";
export class ReflectionParser {
static customParseComponentHandles = new Map<string, Function>();
@@ -12,30 +12,10 @@ export class ReflectionParser {
return ReflectionParser.getEntityByConfig(entityConfig, engine).then((entity) => {
entity.isActive = entityConfig.isActive ?? true;
const { position, rotation, scale } = entityConfig;
if (position) {
entity.transform.setPosition(position.x, position.y, position.z);
}
if (rotation) {
entity.transform.setRotation(rotation.x, rotation.y, rotation.z);
}
if (scale) {
entity.transform.setScale(scale.x, scale.y, scale.z);
}
const promises = [];
for (let i = 0; i < entityConfig.components.length; i++) {
const componentConfig = entityConfig.components[i];
const key = !componentConfig.refId ? componentConfig.class : componentConfig.refId;
let component;
if (key === "Animator") {
component = entity.getComponent(Loader.getClass(key));
}
component = component || entity.addComponent(Loader.getClass(key));
const promise = this.parsePropsAndMethods(component, componentConfig, engine);
promises.push(promise);
}
return Promise.all(promises).then(() => {
return entity;
});
if (position) entity.transform.position.copyFrom(position);
if (rotation) entity.transform.rotation.copyFrom(rotation);
if (scale) entity.transform.scale.copyFrom(scale);
return entity;
});
}
@@ -78,13 +58,13 @@ export class ReflectionParser {
return Promise.all(value.map((item) => this.parseBasicType(item, engine, resourceManager)));
} else if (typeof value === "object" && value != null) {
if (this._isClass(value)) {
// 类对象
// class object
return this.parseClassObject(value, engine, resourceManager);
} else if (this._isRef(value)) {
// 引用对象
// reference object
return resourceManager.getResourceByRef(value);
} else {
// 基础类型
// basic type
return Promise.resolve(value);
}
} else {
@@ -120,17 +100,10 @@ export class ReflectionParser {
}
}
return new Promise((resolve, reject) => {
Promise.all(promises).then(() => {
const handle = this.customParseComponentHandles[instance.constructor.name];
if (handle) {
handle(instance, item, engine).then(() => {
resolve(instance);
});
} else {
resolve(instance);
}
}).catch(reject)
return Promise.all(promises).then(() => {
const handle = this.customParseComponentHandles[instance.constructor.name];
if (handle) return handle(instance, item, engine);
else return instance;
});
}
@@ -152,7 +125,7 @@ export class ReflectionParser {
return value["class"] != undefined;
}
private static _isRef(value: any): value is IReferenceType {
private static _isRef(value: any): value is IAssetRef {
return value["refId"] != undefined;
}
}

View File

@@ -1,36 +1,113 @@
import { Engine, Entity, Scene } from "@oasis-engine/core";
import { Engine, Entity, Loader, Scene } from "@oasis-engine/core";
import { IEntity, IScene } from "../prefab/PrefabDesign";
import { PrefabParser } from "../prefab/PrefabParser";
import { ReflectionParser } from "../prefab/ReflectionParser";
import { SceneParserContext } from "./SceneParserContext";
/** @Internal */
export class SceneParser {
/**
* Parse scene data.
* @param engine - the engine of the parser context
* @param sceneData - scene data which is exported by editor
* @returns a promise of scene
*/
static parse(engine: Engine, sceneData: IScene): Promise<Scene> {
const scene = new Scene(engine);
const entitiesMap: Record<string, Entity> = {};
const entitiesConfigMap: Record<string, IEntity> = {};
const promises: Promise<Entity>[] = [];
const entitiesConfig = sceneData.entities;
for (const entity of entitiesConfig) {
entitiesConfigMap[entity.id] = entity;
promises.push(ReflectionParser.parseEntity(entity, engine));
}
const context = new SceneParserContext(sceneData, scene);
const parser = new SceneParser(context);
parser.start();
return parser.promise;
}
return Promise.all(promises).then((entities) => {
const rootIds = [];
entities.forEach((entity, index) => {
entitiesMap[entitiesConfig[index].id] = entity;
if (!entitiesConfig[index].parent) {
rootIds.push(entitiesConfig[index].id);
}
});
for (const rootId of rootIds) {
PrefabParser.parseChildren(entitiesConfigMap, entitiesMap, rootId);
}
const rootEntities = rootIds.map((id) => entitiesMap[id]);
for (let i = 0; i < rootEntities.length; i++) {
scene.addRootEntity(rootEntities[i]);
}
return scene;
/**
* The promise of parsed scene.
*/
readonly promise: Promise<Scene>;
private _resolve: (scene: Scene) => void;
private _reject: (reason: any) => void;
private _engine: Engine;
constructor(public readonly context: SceneParserContext) {
this._engine = this.context.scene.engine;
this._organizeEntities = this._organizeEntities.bind(this);
this._parseComponents = this._parseComponents.bind(this);
this._clearAndResolveScene = this._clearAndResolveScene.bind(this);
this.promise = new Promise<Scene>((resolve, reject) => {
this._reject = reject;
this._resolve = resolve;
});
}
/** start parse the scene */
start() {
this._parseEntities()
.then(this._organizeEntities)
.then(this._parseComponents)
.then(this._clearAndResolveScene)
.then(this._resolve)
.catch(this._reject);
}
private _parseEntities(): Promise<Entity[]> {
const entitiesConfig = this.context.originalData.entities;
const entityConfigMap = this.context.entityConfigMap;
const entitiesMap = this.context.entityMap;
const rootIds = this.context.rootIds;
const engine = this._engine;
const promises = entitiesConfig.map((entityConfig) => {
entityConfigMap.set(entityConfig.id, entityConfig);
// record root entities
if (!entityConfig.parent) rootIds.push(entityConfig.id);
return ReflectionParser.parseEntity(entityConfig, engine);
});
return Promise.all(promises).then((entities) => {
for (let i = 0, l = entities.length; i < l; i++) {
entitiesMap.set(entitiesConfig[i].id, entities[i]);
}
return entities;
});
}
private _organizeEntities() {
const { entityConfigMap, entityMap, scene, rootIds } = this.context;
for (const rootId of rootIds) {
PrefabParser.parseChildren(entityConfigMap, entityMap, rootId);
}
const rootEntities = rootIds.map((id) => entityMap.get(id));
for (let i = 0; i < rootEntities.length; i++) {
scene.addRootEntity(rootEntities[i]);
}
}
private _parseComponents() {
const entitiesConfig = this.context.originalData.entities;
const entityMap = this.context.entityMap;
const promises = [];
for (let i = 0, l = entitiesConfig.length; i < l; i++) {
const entityConfig = entitiesConfig[i];
const entity = entityMap.get(entityConfig.id);
for (let i = 0; i < entityConfig.components.length; i++) {
const componentConfig = entityConfig.components[i];
const key = !componentConfig.refId ? componentConfig.class : componentConfig.refId;
let component;
// TODO: remove hack code when support additional edit
if (key === "Animator") {
component = entity.getComponent(Loader.getClass(key));
}
component = component || entity.addComponent(Loader.getClass(key));
const promise = ReflectionParser.parsePropsAndMethods(component, componentConfig, entity.engine);
promises.push(promise);
}
}
}
private _clearAndResolveScene() {
const scene = this.context.scene;
this.context.destroy();
return scene;
}
}

View File

@@ -0,0 +1,19 @@
import { Component, Entity, Scene } from "@oasis-engine/core";
import { IEntity, IScene } from "../prefab/PrefabDesign";
export class SceneParserContext {
entityMap: Map<string, Entity> = new Map();
components: Map<string, Component> = new Map();
assets: Map<string, any> = new Map();
entityConfigMap: Map<string, IEntity> = new Map();
rootIds: string[] = [];
constructor(public readonly originalData: IScene, public readonly scene: Scene) {}
destroy() {
this.entityMap.clear();
this.components.clear();
this.assets.clear();
this.entityConfigMap.clear();
this.rootIds.length = 0;
}
}