Attributes initialization refactoring (#19)

This commit is contained in:
barsdeveloper
2024-03-24 17:30:50 +01:00
committed by GitHub
parent 5973570911
commit cc9e3d833a
93 changed files with 4134 additions and 4082 deletions

View File

@@ -1,121 +1,85 @@
import ComputedType from "./ComputedType.js"
import Configuration from "../Configuration.js"
import MirroredEntity from "./MirroredEntity.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"
import Utility from "../Utility.js"
/** @abstract */
export default class IEntity extends Serializable {
/** @type {String | Union<String[]>} */
static lookbehind = ""
/** @type {AttributeDeclarations} */
static attributes = {
lookbehind: {
attributes: new AttributeInfo({
ignored: true,
}
}
static defaultAttribute = {
nullable: false,
ignored: false,
serialized: false, // Value is written and read as string
expected: false, // Must be there
inlined: false, // The key is a subobject or array and printed as inlined (A.B=123, A(0)=123)
quoted: false, // Key is serialized with quotes
}),
lookbehind: new AttributeInfo({
default: /** @type {String | Union<String[]>} */(""),
ignored: true,
}),
}
constructor(values = {}, suppressWarns = false) {
super()
/** @type {String} */ this.lookbehind
const Self = /** @type {typeof IEntity} */(this.constructor)
let attributes = Self.attributes
if (values.attributes) {
attributes = { ...Self.attributes }
Utility.mergeArrays(Object.keys(values.attributes), Object.keys(attributes))
.forEach(k => {
attributes[k] = {
...IEntity.defaultAttribute,
...attributes[k],
...values.attributes[k],
}
if (!attributes[k].type) {
attributes[k].type = values[k] instanceof Array
? [Utility.getType(values[k][0])]
: Utility.getType(values[k])
}
})
IEntity.defineAttributes(this, attributes)
}
/** @type {AttributeDeclarations?} */ this.attributes
const valuesNames = Object.keys(values)
const attributesNames = Object.keys(attributes)
const allAttributesNames = Utility.mergeArrays(valuesNames, attributesNames)
if (valuesNames.includes("lookbehind")) {
this.lookbehind = undefined // To keep it first
}
for (const attributeName of allAttributesNames) {
if (attributeName == "attributes") {
// Ignore this special attribute describing all the attributes
continue
}
let value = values[attributeName]
let attribute = attributes[attributeName]
if (!suppressWarns && value !== undefined) {
if (
!(attributeName in attributes)
&& !attributeName.startsWith(Configuration.subObjectAttributeNamePrefix)
) {
/** @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 ${attributeName} (of type ${typeName}) in the serialized data is not `
+ `defined in ${Self.name}.attributes`
`UEBlueprint: Attribute ${key} (of type ${typeName}) in the serialized data is not defined in ${Self.name}.attributes`
)
}
}
if (!attribute) {
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[attributeName] = value
this[key] = value
continue
}
const assignAttribute = !attribute.predicate
? v => this[attributeName] = v
Self.attributes.lookbehind
const predicate = AttributeInfo.getAttribute(values, key, "predicate", Self)
const assignAttribute = !predicate
? v => this[key] = v
: v => {
Object.defineProperties(this, {
["#" + attributeName]: {
["#" + key]: {
writable: true,
enumerable: false,
},
[attributeName]: {
[key]: {
enumerable: true,
get() {
return this["#" + attributeName]
return this["#" + key]
},
set(v) {
if (!attribute.predicate?.(v)) {
if (!predicate(v)) {
console.warn(
`UEBlueprint: Tried to assign attribute ${attributeName} to ${Self.name} not `
+ "satisfying the predicate"
`UEBlueprint: Tried to assign attribute ${key} to ${Self.name} not satisfying the predicate`
)
return
}
this["#" + attributeName] = v
this["#" + key] = v
}
},
})
this[attributeName] = v
this[key] = v
}
let defaultValue = attribute.default
let defaultValue = AttributeInfo.getAttribute(values, key, "default", Self)
if (defaultValue instanceof Function) {
defaultValue = defaultValue(this)
}
let defaultType = attribute.type
let defaultType = AttributeInfo.getAttribute(values, key, "type", Self)
if (defaultType instanceof ComputedType) {
defaultType = defaultType.compute(this)
}
@@ -128,16 +92,24 @@ export default class IEntity extends Serializable {
if (value !== undefined) {
// Remember value can still be null
if (value?.constructor === String && attribute.serialized && defaultType !== String) {
value = SerializerFactory
// @ts-expect-error
.getSerializer(defaultType)
.read(/** @type {String} */(value))
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 (Object.hasOwn(attribute, "default")) { // Accept also explicit undefined
if (defaultValue !== undefined) {
assignAttribute(defaultValue)
}
}
@@ -166,31 +138,6 @@ export default class IEntity extends Serializable {
}
}
/** @param {AttributeDeclarations} attributes */
static cleanupAttributes(attributes, prefix = "") {
for (const attributeName in attributes) {
attributes[attributeName] = {
...IEntity.defaultAttribute,
...attributes[attributeName],
}
const attribute = /** @type {AttributeInformation} */(attributes[attributeName])
if (attribute.type === undefined && !(attribute.default instanceof Function)) {
attribute.type = attribute.default instanceof Array
? [Utility.getType(attribute.default[0])]
: Utility.getType(attribute.default)
}
if (!attribute.ignored && attribute.default === undefined && attribute.type === undefined) {
throw new Error(
`UEBlueprint: Expected either "type" or "value" property in ${this.name} attribute ${prefix}`
+ attributeName
)
}
if (attribute.default === null) {
attribute.nullable = true
}
}
}
/**
* @template {new (...args: any) => any} C
* @param {C} type
@@ -224,7 +171,7 @@ export default class IEntity extends Serializable {
}
getLookbehind() {
let lookbehind = this.lookbehind ?? /** @type {EntityConstructor} */(this.constructor).lookbehind
let lookbehind = this.lookbehind ?? AttributeInfo.getAttribute(this, "lookbehind", "default")
lookbehind = lookbehind instanceof Union ? lookbehind.values[0] : lookbehind
return lookbehind
}