mirror of
https://github.com/barsdeveloper/ueblueprint.git
synced 2026-05-22 22:27:30 +08:00
Attributes initialization refactoring (#19)
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user