Created
February 1, 2024 12:39
-
-
Save xeolabs/c426a2044c0f30160ed2a11b03b93e7f to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {PropertySet} from "./PropertySet.js"; | |
import {MetaObject} from "./MetaObject.js"; | |
import {math} from "../scene/math/math.js"; | |
/** | |
* @desc Metadata corresponding to an {@link Entity} that represents a model. | |
* | |
* An {@link Entity} represents a model when {@link Entity#isModel} is ````true```` | |
* | |
* A MetaModel corresponds to an {@link Entity} by having the same {@link MetaModel#id} as the {@link Entity#id}. | |
* | |
* A MetaModel is created by {@link MetaScene#createMetaModel} and belongs to a {@link MetaScene}. | |
* | |
* Each MetaModel is registered by {@link MetaModel#id} in {@link MetaScene#metaModels}. | |
* | |
* A {@link MetaModel} represents its object structure with a tree of {@link MetaObject}s, with {@link MetaModel#rootMetaObject} referencing the root {@link MetaObject}. | |
* | |
* @class MetaModel | |
*/ | |
class MetaModel { | |
/** | |
* Creates a new, unfinalized MetaModel. | |
* | |
* * The MetaModel is immediately registered by {@link MetaModel#id} in {@link MetaScene#metaModels}, even though it's not yet populated. | |
* * The MetaModel then needs to be populated with one or more calls to {@link metaModel#loadData}. | |
* * As we populate it, the MetaModel will create {@link MetaObject}s and {@link PropertySet}s in itself, and in the MetaScene. | |
* * When populated, call {@link MetaModel#finalize} to finish it off, which causes MetaScene to fire a "metaModelCreated" event. | |
*/ | |
constructor(params) { | |
/** | |
* Globally-unique ID. | |
* | |
* MetaModels are registered by ID in {@link MetaScene#metaModels}. | |
* | |
* When this MetaModel corresponds to an {@link Entity} then this ID will match the {@link Entity#id}. | |
* | |
* @property id | |
* @type {String|Number} | |
*/ | |
this.id = params.id; | |
/** | |
* The project ID | |
* @property projectId | |
* @type {String|Number} | |
*/ | |
this.projectId = params.projectId; | |
/** | |
* The revision ID, if available. | |
* | |
* Will be undefined if not available. | |
* | |
* @property revisionId | |
* @type {String|Number} | |
*/ | |
this.revisionId = params.revisionId; | |
/** | |
* The model author, if available. | |
* | |
* Will be undefined if not available. | |
* | |
* @property author | |
* @type {String} | |
*/ | |
this.author = params.author; | |
/** | |
* The date the model was created, if available. | |
* | |
* Will be undefined if not available. | |
* | |
* @property createdAt | |
* @type {String} | |
*/ | |
this.createdAt = params.createdAt; | |
/** | |
* The application that created the model, if available. | |
* | |
* Will be undefined if not available. | |
* | |
* @property creatingApplication | |
* @type {String} | |
*/ | |
this.creatingApplication = params.creatingApplication; | |
/** | |
* The model schema version, if available. | |
* | |
* Will be undefined if not available. | |
* | |
* @property schema | |
* @type {String} | |
*/ | |
this.schema = params.schema; | |
/** | |
* Metadata on the {@link Scene}. | |
* | |
* @property metaScene | |
* @type {MetaScene} | |
*/ | |
this.metaScene = params.metaScene; | |
/** | |
* The {@link PropertySet}s in this MetaModel. | |
* | |
* @property propertySets | |
* @type {PropertySet[]} | |
*/ | |
this.propertySets = []; | |
/** | |
* The root {@link MetaObject}s in this MetaModel's composition structure hierarchy. | |
* | |
* @property rootMetaObject | |
* @type {MetaObject[]} | |
*/ | |
this.rootMetaObjects = []; | |
/** | |
* The {@link MetaObject}s in this MetaModel, each mapped to its ID. | |
* | |
* @property metaObjects | |
* @type {MetaObject[]} | |
*/ | |
this.metaObjects = []; | |
/** | |
* Connectivity graph. | |
* @type {{}} | |
*/ | |
this.graph = params.graph || {}; | |
this.metaScene.metaModels[this.id] = this; | |
/** | |
* True when this MetaModel has been finalized. | |
* @type {boolean} | |
*/ | |
this.finalized = false; | |
} | |
/** | |
* Backwards compatibility with the model having a single root MetaObject. | |
* | |
* @property rootMetaObject | |
* @type {MetaObject|null} | |
*/ | |
get rootMetaObject() { | |
if (this.rootMetaObjects.length == 1) { | |
return this.rootMetaObjects[0]; | |
} | |
return null; | |
} | |
/** | |
* Load metamodel data into this MetaModel. | |
* @param metaModelData | |
*/ | |
loadData(metaModelData, options = {}) { | |
if (this.finalized) { | |
throw "MetaScene already finalized - can't add more data"; | |
} | |
this._globalizeIDs(metaModelData, options) | |
const metaScene = this.metaScene; | |
const propertyLookup = metaModelData.properties; | |
// Create global Property Sets | |
if (metaModelData.propertySets) { | |
for (let i = 0, len = metaModelData.propertySets.length; i < len; i++) { | |
const propertySetData = metaModelData.propertySets[i]; | |
let propertySet = metaScene.propertySets[propertySetData.id]; | |
if (!propertySet) { | |
if (propertyLookup) { | |
this._decompressProperties(propertyLookup, propertySetData.properties); | |
} | |
propertySet = new PropertySet({ | |
id: propertySetData.id, | |
originalSystemId: propertySetData.originalSystemId || propertySetData.id, | |
type: propertySetData.type, | |
name: propertySetData.name, | |
properties: propertySetData.properties | |
}); | |
metaScene.propertySets[propertySet.id] = propertySet; | |
} | |
propertySet.metaModels.push(this); | |
this.propertySets.push(propertySet); | |
} | |
} | |
if (metaModelData.metaObjects) { | |
for (let i = 0, len = metaModelData.metaObjects.length; i < len; i++) { | |
const metaObjectData = metaModelData.metaObjects[i]; | |
const id = metaObjectData.id; | |
let metaObject = metaScene.metaObjects[id]; | |
if (!metaObject) { | |
const type = metaObjectData.type; | |
const originalSystemId = metaObjectData.originalSystemId; | |
const propertySetIds = metaObjectData.propertySets || metaObjectData.propertySetIds; | |
metaObject = new MetaObject({ | |
id, | |
originalSystemId, | |
parentId: metaObjectData.parent, | |
type, | |
name: metaObjectData.name, | |
attributes: metaObjectData.attributes, | |
propertySetIds, | |
external: metaObjectData.external, | |
}); | |
this.metaScene.metaObjects[id] = metaObject; | |
} | |
metaObject.metaModels.push(this); | |
if (!metaObjectData.parent) { | |
this.rootMetaObjects.push(metaObject); | |
metaScene.rootMetaObjects[id] = metaObject; | |
} | |
this.metaObjects.push(metaObject); | |
} | |
} | |
} | |
_decompressProperties(propertyLookup, properties) { | |
for (let i = 0, len = properties.length; i < len; i++) { | |
const property = properties[i]; | |
if (Number.isInteger(property)) { | |
const lookupProperty = propertyLookup[property]; | |
if (lookupProperty) { | |
properties[i] = lookupProperty; | |
} | |
} | |
} | |
} | |
finalize() { | |
if (this.finalized) { | |
throw "MetaScene already finalized - can't re-finalize"; | |
} | |
// Re-link MetaScene's entire MetaObject parent/child hierarchy | |
const metaScene = this.metaScene; | |
for (let objectId in metaScene.metaObjects) { | |
const metaObject = metaScene.metaObjects[objectId]; | |
if (metaObject.children) { | |
metaObject.children = []; | |
} | |
// Re-link each MetaObject's property sets | |
if (metaObject.propertySets) { | |
metaObject.propertySets = []; | |
} | |
if (metaObject.propertySetIds) { | |
for (let i = 0, len = metaObject.propertySetIds.length; i < len; i++) { | |
const propertySetId = metaObject.propertySetIds[i]; | |
const propertySet = metaScene.propertySets[propertySetId]; | |
metaObject.propertySets.push(propertySet); | |
} | |
} | |
} | |
for (let objectId in metaScene.metaObjects) { | |
const metaObject = metaScene.metaObjects[objectId]; | |
if (metaObject.parentId) { | |
const parentMetaObject = metaScene.metaObjects[metaObject.parentId]; | |
if (parentMetaObject) { | |
metaObject.parent = parentMetaObject; | |
(parentMetaObject.children || (parentMetaObject.children = [])).push(metaObject); | |
} | |
} | |
} | |
// Relink MetaObjects to their MetaModels | |
for (let objectId in metaScene.metaObjects) { | |
const metaObject = metaScene.metaObjects[objectId]; | |
metaObject.metaModels = []; | |
} | |
for (let modelId in metaScene.metaModels) { | |
const metaModel = metaScene.metaModels[modelId]; | |
for (let i=0, len=metaModel.metaObjects.length; i < len; i++) { | |
const metaObject = metaModel.metaObjects[i]; | |
metaObject.metaModels.push(metaModel); | |
} | |
} | |
// Rebuild MetaScene's MetaObjects-by-type lookup | |
metaScene.metaObjectsByType = {}; | |
for (let objectId in metaScene.metaObjects) { | |
const metaObject = metaScene.metaObjects[objectId]; | |
const type = metaObject.type; | |
(metaScene.metaObjectsByType[type] || (metaScene.metaObjectsByType[type] = {}))[objectId] = metaObject; | |
} | |
this.finalized = true; | |
this.metaScene.fire("metaModelCreated", this.id); | |
} | |
/** | |
* Gets this MetaModel as JSON. | |
* @returns {{schema: (String|string|*), createdAt: (String|string|*), metaObjects: *[], author: (String|string|*), id: (String|Number|string|number|*), creatingApplication: (String|string|*), projectId: (String|Number|string|number|*), propertySets: *[]}} | |
*/ | |
getJSON() { | |
const json = { | |
id: this.id, | |
projectId: this.projectId, | |
author: this.author, | |
createdAt: this.createdAt, | |
schema: this.schema, | |
creatingApplication: this.creatingApplication, | |
metaObjects: [], | |
propertySets: [] | |
}; | |
for (let i = 0, len = this.metaObjects.length; i < len; i++) { | |
const metaObject = this.metaObjects[i]; | |
const metaObjectCfg = { | |
id: metaObject.id, | |
originalSystemId: metaObject.originalSystemId, | |
extId: metaObject.extId, | |
type: metaObject.type, | |
name: metaObject.name | |
}; | |
if (metaObject.parent) { | |
metaObjectCfg.parent = metaObject.parent.id; | |
} | |
if (metaObject.attributes) { | |
metaObjectCfg.attributes = metaObject.attributes; | |
} | |
if (metaObject.propertySetIds) { | |
metaObjectCfg.propertySetIds = metaObject.propertySetIds; | |
} | |
json.metaObjects.push(metaObjectCfg); | |
} | |
for (let i = 0, len = this.propertySets.length; i < len; i++) { | |
const propertySet = this.propertySets[i]; | |
const propertySetCfg = { | |
id: propertySet.id, | |
originalSystemId: propertySet.originalSystemId, | |
extId: propertySet.extId, | |
type: propertySet.type, | |
name: propertySet.name, | |
propertyies: [] | |
}; | |
for (let j = 0, lenj = propertySet.properties.length; j < lenj; j++) { | |
const property = propertySet.properties[j]; | |
const propertyCfg = { | |
id: property.id, | |
description: property.description, | |
type: property.type, | |
name: property.name, | |
value: property.value, | |
valueType: property.valueType | |
}; | |
propertySetCfg.properties.push(propertyCfg); | |
} | |
json.propertySets.push(propertySetCfg); | |
} | |
return json; | |
} | |
_globalizeIDs(metaModelData, options) { | |
const globalize = !!options.globalizeObjectIds; | |
if (metaModelData.metaObjects) { | |
for (let i = 0, len = metaModelData.metaObjects.length; i < len; i++) { | |
const metaObjectData = metaModelData.metaObjects[i]; | |
// Globalize MetaObject IDs and parent IDs | |
metaObjectData.originalSystemId = metaObjectData.id; | |
if (metaObjectData.parent) { | |
metaObjectData.originalParentSystemId = metaObjectData.parent; | |
} | |
if (globalize) { | |
metaObjectData.id = math.globalizeObjectId(this.id, metaObjectData.id); | |
if (metaObjectData.parent) { | |
metaObjectData.parent = math.globalizeObjectId(this.id, metaObjectData.parent); | |
} | |
} | |
// Globalize MetaObject property set IDs | |
if (globalize) { | |
const propertySetIds = metaObjectData.propertySetIds; | |
if (propertySetIds) { | |
const propertySetGlobalIds = []; | |
for (let j = 0, lenj = propertySetIds.length; j < lenj; j++) { | |
propertySetGlobalIds.push(math.globalizeObjectId(this.id, propertySetIds[j])); | |
} | |
metaObjectData.propertySetIds = propertySetGlobalIds; | |
metaObjectData.originalSystemPropertySetIds = propertySetIds; | |
} | |
} else { | |
metaObjectData.originalSystemPropertySetIds = metaObjectData.propertySetIds; | |
} | |
} | |
} | |
// Globalize global PropertySet IDs | |
if (metaModelData.propertySets) { | |
for (let i = 0, len = metaModelData.propertySets.length; i < len; i++) { | |
const propertySet = metaModelData.propertySets[i]; | |
propertySet.originalSystemId = propertySet.id; | |
if (globalize) { | |
propertySet.id = math.globalizeObjectId(this.id, propertySet.id); | |
} | |
} | |
} | |
} | |
} | |
export {MetaModel}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment