Ir al contenido principal

Buenas prácticas

Problemas comunes y mejores prácticas a seguir al crear componentes personalizados.

Referencias obsoletas

Cuando a tu componente se le pasa (world, component) para añadir, marcar o eliminar callbacks, no siempre es seguro hacer referencia a component en una función anidada y usarla después de que el callback haya retornado.

Ejemplo incorrecto

ecs.registerComponent({
name: 'age-counter',
data: {
age: ecs.i32,
interval: ecs.i32,
},
add: (world, component) => {
const interval = world.time.setInterval(() => {
// This is not safe because we're accessing data after some amount of time
// has passed, it's not guaranteed to still be valid.
component.data.age += 1
}, 1000)

// This is safe because we're assigning to data within the add function
component.data.interval = interval
},
tick: (world, component) => {
console.log('I am', component.data.age, 'seconds old')
},
remove: (world, component) => {
world.time.clearTimeout(component.data.interval)
}
})

Ejemplo correcto

ecs.registerComponent({
name: 'age-counter',
data: {
age: ecs.i32,
interval: ecs.i32,
},
add: (world, component) => {
const {eid, dataAttribute} = component
const interval = world.time.setInterval(() => {
// This is safe because we're re-acquiring a cursor at the time we need it,
// instead of using a stale cursor from before.
const data = dataAttribute.cursor(eid)
data.age += 1
}, 1000)

component.data.interval = interval
},
tick: (world, component) => {
console.log('I am', component.data.age, 'seconds old')
},
remove: (world, component) => {
// world.time.clearTimeout(component.data.interval)
}
})

En el ejemplo anterior, dataAttribute se utiliza como método estable para acceder a los datos del componente dentro de una función anidada. Esto garantiza que los datos permanezcan válidos y actualizados incluso cuando la función se llama de forma asíncrona.

Además, la variable eid se desestructura en lugar de acceder directamente a component.eid porque component.eid puede cambiar dependiendo de qué entidad esté recibiendo la devolución de llamada. El uso de una variable desestructurada evita posibles referencias obsoletas.

De los argumentos pasados a las llamadas de retorno de los componentes, he aquí la validez de cada uno:

advertencia

Desestructure siempre eid antes de utilizarlo en lugar de acceder a component.eid, ya que acceder directamente a component.eid puede dar lugar a referencias obsoletas.

contexto¿Cambios tras la salida de la devolución de llamada?¿Puede utilizarse en una función anidada?De por vida
en el mundo real❌ No✅ SíExperiencia de vida
eid✅ Sí✅ SíDuración de la entidad
esquema y datos✅ Sí❌ NoNivel superior de Callback
schemaAttribute & dataAttribute❌ No✅ SíDuración de la entidad

Cursores invalidados

Los objetos Cursor actúan como interfaces para leer y escribir datos en el estado ECS. Cada vez que se solicita un cursor para un componente, se reutiliza la misma instancia de cursor, pero apunta a una ubicación diferente en la memoria. Como resultado, la referencia de un cursor puede dejar de ser válida, es decir, puede que ya no apunte a los datos esperados.

Ejemplo incorrecto

const cursor1 = MyComponent.get(world, entity1)
console.log(cursor1.name) // 'entity1'

const cursor2 = MyComponent.get(world, entity2)
console.log(cursor2.name) // 'entity2'

// Unexpected bugs may occur if using cursor1 after another access of the component
console.log(cursor1.name) // 'entity2'
console.log(cursor1 === cursor2) // 'true' - it's the same object, just initialized differently each time

Oyentes colgantes

Evite asumir que cualquier objeto o componente persistirá indefinidamente. A medida que el proyecto evoluciona o se introducen nuevas funciones, es importante asegurarse de que la lógica del componente es robusta, incluida la limpieza adecuada de los escuchadores de eventos.

Las Máquinas de Estado son una gran manera de gestionar y limpiar los Receptores de Eventos.

Ejemplo correcto

ecs.registerComponent({
name: 'Game Manager',
schema: {
// Add data that can be configured on the component.
scoreDisplay: ecs.eid, // how many coins you've collected
},
schemaDefaults: {
// Add defaults for the schema fields.
},
data: {
// Add data that cannot be configured outside of the component.
score: ecs.i32, // The integer value of the score
},
stateMachine: ({world, eid, schemaAttribute, dataAttribute}) => {
// Add score event
const coinCollect = () => {
const data = dataAttribute.cursor(eid)
data.score += 1
ecs.Ui.set(world, schemaAttribute.get(eid).scoreDisplay, {
text: data.score.toString(),
})
}

ecs.defineState('default').initial().onEnter(() => {
world.events.addListener(world.events.globalId, 'coinCollect', coinCollect)
}).onExit(() => {
world.events.removeListener(world.events.globalId, 'coinCollect', coinCollect)
})
},
})