Files
ueblueprint/js/entity/IEntity.js
barsdeveloper a5813d0b4d Niagara and Metasound nodes WIP
* Keep track of entities

* Fix renaming

* Niagara variables wip

* Several niagara decode and test

* Move nodeTemplate code to dedicated file, self node added

* Move node decoding functions to dedicated files

* Move pin decoding logic to dedicated files

* Accept space separated keys in objects

* Build

* Prevent a crash in case of incomplete object

* Avoid creating objects unnecessarily

* types formatting

* Initial metasound style

* Common pcg nodes colors

* Fix string serialization

* Metasound new styles and fixes

* More metasound styles and colors

* WIP

* Several fixes

* More tests and fixes

* Clean gitignore
2024-05-20 12:56:36 +02:00

234 lines
8.7 KiB
JavaScript

import Configuration from "../Configuration.js"
import Utility from "../Utility.js"
import Serializable from "../serialization/Serializable.js"
import SerializerFactory from "../serialization/SerializerFactory.js"
import AttributeInfo from "./AttributeInfo.js"
import ComputedType from "./ComputedType.js"
import MirroredEntity from "./MirroredEntity.js"
import Union from "./Union.js"
/** @abstract */
export default class IEntity extends Serializable {
/** @type {{ [attribute: String]: AttributeInfo }} */
static attributes = {
attributes: new AttributeInfo({
ignored: true,
}),
lookbehind: new AttributeInfo({
default: /** @type {String | Union<String[]>} */(""),
ignored: true,
uninitialized: true,
}),
}
/** @type {String[]} */
#_keys
get _keys() {
return this.#_keys
}
set _keys(keys) {
this.#_keys = keys
}
constructor(values = {}, suppressWarns = false) {
super()
const Self = /** @type {typeof IEntity} */(this.constructor)
/** @type {AttributeDeclarations?} */ this.attributes
/** @type {String} */ this.lookbehind
const valuesKeys = Object.keys(values)
const attributesKeys = values.attributes
? Utility.mergeArrays(Object.keys(values.attributes), Object.keys(Self.attributes))
: Object.keys(Self.attributes)
const allAttributesKeys = Utility.mergeArrays(valuesKeys, attributesKeys)
for (const key of allAttributesKeys) {
let value = values[key]
if (!suppressWarns && !(key in values)) {
if (!(key in Self.attributes) && !key.startsWith(Configuration.subObjectAttributeNamePrefix)) {
const typeName = value instanceof Array ? `[${value[0]?.constructor.name}]` : value.constructor.name
console.warn(
`UEBlueprint: Attribute ${key} (of type ${typeName}) in the serialized data is not defined in ${Self.name}.attributes`
)
}
}
if (!(key in Self.attributes)) {
// Remember attributeName can come from the values and be not defined in the attributes.
// In that case just assign it and skip the rest.
this[key] = value
continue
}
Self.attributes.lookbehind
const predicate = AttributeInfo.getAttribute(values, key, "predicate", Self)
const assignAttribute = !predicate
? v => this[key] = v
: v => {
Object.defineProperties(this, {
["#" + key]: {
writable: true,
enumerable: false,
},
[key]: {
enumerable: true,
get() {
return this["#" + key]
},
set(v) {
if (!predicate(v)) {
console.warn(
`UEBlueprint: Tried to assign attribute ${key} to ${Self.name} not satisfying the predicate`
)
return
}
this["#" + key] = v
}
},
})
this[key] = v
}
let defaultValue = AttributeInfo.getAttribute(values, key, "default", Self)
if (defaultValue instanceof Function) {
defaultValue = defaultValue(this)
}
let defaultType = AttributeInfo.getAttribute(values, key, "type", Self)
if (defaultType instanceof ComputedType) {
defaultType = defaultType.compute(this)
}
if (defaultType instanceof Array) {
defaultType = Array
}
if (defaultType === undefined) {
defaultType = Utility.getType(defaultValue)
}
if (value !== undefined) {
// Remember value can still be null
if (
value?.constructor === String
&& AttributeInfo.getAttribute(values, key, "serialized", Self)
&& defaultType !== String
) {
try {
value = SerializerFactory
.getSerializer(defaultType)
.read(/** @type {String} */(value))
} catch (e) {
assignAttribute(value)
continue
}
}
assignAttribute(Utility.sanitize(value, /** @type {AttributeConstructor<Attribute>} */(defaultType)))
continue // We have a value, need nothing more
}
if (defaultValue !== undefined && !AttributeInfo.getAttribute(values, key, "uninitialized", Self)) {
assignAttribute(defaultValue)
}
}
}
/** @param {AttributeTypeDescription} attributeType */
static defaultValueProviderFromType(attributeType) {
if (attributeType === Boolean) {
return false
} else if (attributeType === Number) {
return 0
} else if (attributeType === BigInt) {
return 0n
} else if (attributeType === String) {
return ""
} else if (attributeType === Array || attributeType instanceof Array) {
return () => []
} else if (attributeType instanceof Union) {
return this.defaultValueProviderFromType(attributeType.values[0])
} else if (attributeType instanceof MirroredEntity) {
return () => new MirroredEntity(attributeType.type, attributeType.getter)
} else if (attributeType instanceof ComputedType) {
return undefined
} else {
return () => new /** @type {AnyConstructor<Attribute>} */(attributeType)()
}
}
/**
* @template {new (...args: any) => any} C
* @param {C} type
* @returns {value is InstanceType<C>}
*/
static isValueOfType(value, type) {
return value != null && (value instanceof type || value.constructor === type)
}
static defineAttributes(object, attributes) {
Object.defineProperty(object, "attributes", {
writable: true,
configurable: false,
})
object.attributes = attributes
}
/**
*
* @param {String} attribute
* @param {(v: any) => void} callback
*/
listenAttribute(attribute, callback) {
const descriptor = Object.getOwnPropertyDescriptor(this, attribute)
const setter = descriptor.set
if (setter) {
descriptor.set = v => {
setter(v)
callback(v)
}
Object.defineProperties(this, { [attribute]: descriptor })
} else if (descriptor.value) {
Object.defineProperties(this, {
["#" + attribute]: {
value: descriptor.value,
writable: true,
enumerable: false,
},
[attribute]: {
enumerable: true,
get() {
return this["#" + attribute]
},
set(v) {
if (v == this["#" + attribute]) {
return
}
callback(v)
this["#" + attribute] = v
}
},
})
}
}
getLookbehind() {
let lookbehind = this.lookbehind ?? AttributeInfo.getAttribute(this, "lookbehind", "default")
lookbehind = lookbehind instanceof Union ? lookbehind.values[0] : lookbehind
return lookbehind
}
unexpectedKeys() {
return Object.keys(this).length - Object.keys(/** @type {typeof IEntity} */(this.constructor).attributes).length
}
/** @param {IEntity} other */
equals(other) {
const thisKeys = Object.keys(this)
const otherKeys = Object.keys(other)
if (thisKeys.length != otherKeys.length) {
return false
}
for (const key of thisKeys) {
if (this[key] instanceof IEntity && !this[key].equals(other[key])) {
return false
} else if (!Utility.equals(this[key], other[key])) {
return false
}
}
return true
}
}