# Systems

Las escenas de Decentraland dependen de *systems* para actualizar cualquier dato a lo largo del tiempo, incluida la información almacenada en el [components](https://github.com/decentraland/docs/blob/main/creator/sdk7/sdk7/architecture/entities-components.md).

![](https://1216664193-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoPnXBby9S6MrsW83Y9qZ%2Fuploads%2Fgit-blob-de62a18d51b28bebd55c86e8ef5a8a783467ae62%2Fecs-big-picture.png?alt=media)

*systems* son lo que hace que las escenas sean dinámicas, son funciones que se ejecutan periódicamente en cada tick del bucle de juego de la escena, cambiando lo que se renderizará.

El siguiente ejemplo muestra una declaración básica de un sistema:

```ts
// Definir el sistema
function mySystem() {
  console.log("Se ejecuta en cada tick. Mi sistema está en funcionamiento")
}
// Añadir sistema al engine
engine.addSystem(mySystem)
```

La función en un sistema puede realizar cualquier cosa que desees. Típicamente, actuará sobre todas las entidades que cumplan ciertos [query](https://github.com/decentraland/docs/blob/main/creator/sdk7/sdk7/architecture/querying-components.md), siguiendo cierta lógica para cambiar los valores almacenados en los componentes de la entidad.

```ts
function moveSystem(dt: number) {
  // iterar sobre todas las entidades con un Transform
  for (const [entity] of engine.getEntitiesWith(Transform)) {

  // obtener un componente Transform mutable
  const transform = Transform.getMutable(entity)

  // actualizar el valor de la posición
    transform.position.z += 0.01
  }
}

engine.addSystem(moveSystem)
```

En el ejemplo anterior, el sistema `MoveSystem` es una función que se ejecuta en cada tick del bucle de juego, cambiando la posición de cada entidad en la escena que tiene un Transform.

![](https://1216664193-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoPnXBby9S6MrsW83Y9qZ%2Fuploads%2Fgit-blob-826c0dcfcfc5a6d5719de5a0939214a534078567%2Fecs-system-new.png?alt=media)

Puedes tener múltiples sistemas en tu escena para desacoplar distintos comportamientos, haciendo tu código más limpio y más fácil de escalar y reutilizar. Por ejemplo, un sistema podría encargarse de la física, otro podría hacer que una entidad obstáculo se mueva de un lado a otro continuamente, y otro podría encargarse de la IA de los personajes.

Varios sistemas pueden actuar sobre una sola entidad. Por ejemplo, un personaje no jugador podría moverse por sí mismo según una IA, pero también verse afectado por la gravedad al caminar accidentalmente fuera de un acantilado. En ese escenario, los sistemas de física y de IA ni siquiera necesitan conocerse entre sí. Cada uno reevalúa independientemente su estado actual en cada tick del bucle de juego e implementa su propia lógica separada.

## La función del sistema

La función de un sistema se ejecuta periódicamente, una vez por cada tick del bucle de juego. Esto ocurre automáticamente, no necesitas llamar explícitamente a esta función desde ninguna parte de tu código.

En una escena de Decentraland, puedes pensar en el bucle de juego como la agregación de todas las funciones de sistema en tu escena.

{% hint style="warning" %}
**📔 Nota**: Si añades múltiples instancias del mismo sistema al engine, la función se ejecutará múltiples veces por tick del bucle de juego. Por ejemplo, agregar un sistema dos veces podría resultar en que una entidad se mueva al doble de velocidad esperado, ya que avanza dos incrementos en cada tick.
{% endhint %}

## Manejar entidades por referencia

Algunos componentes y sistemas están pensados para usarse solo en una entidad en la escena. Por ejemplo, en una entidad que almacena la puntuación de un juego o quizás una puerta principal que es única en la escena. Para acceder a una de esas entidades dentro de un sistema, puedes simplemente referirte a la entidad o a sus componentes por nombre en las funciones del sistema.

```ts
export function main(){
	// crear una nueva entidad
	const game = engine.addEntity()

	// añadir componente a esa entidad
	ScoreComponent.create(game)
}

// Definir el sistema
export function UpdateScore() {

  // llamar referencia a entidad individual
  const points = ScoreComponent.get(game).points
  console.log(points)
}

// Añadir sistema al engine
engine.addSystem(UpdateScore)
```

Para proyectos más grandes, recomendamos que mantengas las definiciones de los sistemas en archivos separados de la instanciación de entidades y componentes.

## Iterar sobre una query de componentes

Muchas veces, tu escena tendrá múltiples entidades del mismo tipo que tendrán comportamientos similares. Por ejemplo muchas puertas que se pueden abrir, o muchos enemigos que pueden atacar al jugador. Tiene sentido manejar todas estas entidades similares en un solo sistema, iterando sobre la lista y realizando las mismas comprobaciones en cada una.

No quieres que la función de un sistema itere sobre *todo el* conjunto de entidades en la escena, ya que esto podría ser muy costoso en términos de potencia de procesamiento. Para evitar esto, puedes [consultar componentes](https://github.com/decentraland/docs/blob/main/creator/sdk7/sdk7/architecture/querying-components.md), para iterar solo sobre las entidades relevantes.

Por ejemplo, tu escena puede tener un `PhysicsSystem` que calcula el efecto de la gravedad sobre las entidades de tu escena. Algunas entidades en tu escena, como los árboles, no están pensadas para moverse jamás; por lo que sería inteligente evitar calcular los efectos de la gravedad sobre estas. Puedes definir un `HasPhysics` componente para marcar las entidades que podrían verse afectadas por la gravedad, y luego hacer que `PhysicsSystem` solo trate con las entidades devueltas por esta query.

```ts
// Definir el sistema
export function PhysicsSystem() {
  // iterar sobre todas las entidades con un HasPhysics
  for (const [entity] of engine.getEntitiesWith(HasPhysics)) {

  // obtener un componente Transform mutable
  const transform = Transform.getMutable(entity)

  // Calcular efecto de la física
  }
}

// Añadir sistema al engine
engine.addSystem(PhysicsSystem)
```

## Delta de tiempo entre frames

La función en un sistema puede opcionalmente incluir un argumento llamado `dt`, de tipo `número` (representando *delta time*).

```ts
function MySystem(dt: number) {

  // Actualizar escena
  console.log("tiempo desde el último tick: ", dt)
}

engine.addSystem(MySystem)
```

*delta time* representa el tiempo que ha pasado desde el último tick del bucle de juego, en segundos.

Las escenas de Decentraland se actualizan por defecto a 30 ticks por segundo. Esto significa que el `dt` argumento pasado a todos los sistemas tenderá a ser igual a *1/30* (0.0333...).

Si el procesamiento de un frame toma menos tiempo que este intervalo, entonces el engine esperará el tiempo restante para mantener las actualizaciones con un ritmo regular y `dt` permanecerá igual a *1/30* .

![](https://1216664193-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoPnXBby9S6MrsW83Y9qZ%2Fuploads%2Fgit-blob-9ebcec63913c57ddf96107a817b14797211c1de2%2Fecs-framerate.png?alt=media)

Si el procesamiento de un frame toma más tiempo que *1/30* segundos, el renderizado de ese frame se retrasa. El engine entonces intenta terminar ese frame y mostrarlo lo antes posible. Luego procede al siguiente frame e intenta mostrarlo *1/30* segundos después del último frame. No compensa por el retraso anterior.

![](https://1216664193-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoPnXBby9S6MrsW83Y9qZ%2Fuploads%2Fgit-blob-951edc3e7611eb138145de377ec07dfc8ec4836b%2Fecs-framerate-heavy.png?alt=media)

Idealmente, debes evitar que tu escena pierda frames, ya que esto impacta la calidad de la experiencia del jugador. Dado que esto depende de la potencia de procesamiento de la máquina del jugador, siempre existe la posibilidad de que tu escena deba estar preparada para manejarlo de forma elegante.

El `dt` la variable es útil cuando el procesamiento de frames excede el tiempo por defecto. Asumiendo que el frame actual tomará tanto tiempo como el anterior, esta información puede usarse para calcular cuánto ajustar un cambio gradual, de modo que la tasa de cambio parezca constante y en proporción al lag entre frames.

Ver [posicionamiento de entidades](https://github.com/decentraland/docs/blob/main/creator/sdk7/sdk7/3d-essentials/entity-positioning.md) para ejemplos de cómo usar `dt` para hacer el movimiento más suave.

## Bucle en un intervalo de tiempo

Si quieres que un sistema ejecute algo a un intervalo de tiempo regular, puedes hacerlo combinando el `dt` argumento con un temporizador.

```ts
let timer: number = 10

function LoopSystem(dt: number) {
  timer -= dt
  if (timer <= 0) {
      timer = 10
      // HACER ALGO
    }
}

engine.addSystem(LoopSystem)
```

También existe una función abreviada `setInterval` y `clearInterval` que usa Systems en segundo plano. Esto permite una versión más fácil y corta cuando la intención es ejecutar cierta función cada X cantidad de tiempo.

```ts
const intervalId = timers.setInterval(() => {
    console.log('Imprimiendo esto cada 10 segundos')
}, 10000)
```

Donde el primer argumento, `callback`, es la función que se ejecutará (en este caso, el `console.log()`), y el segundo argumento, `ms`(1000 en este caso), son los milisegundos a esperar entre cada ejecución de la función.

Para detener el `setInterval` function, `clearInterval` se usa.

```
timers.clearInterval(intervalId)
```

Donde `intervalId` es la referencia al `setInterval` return definido antes.

Para casos de uso más complejos, donde puede haber múltiples retrasos y bucles creados dinámicamente, puede valer la pena definir un componente personalizado para almacenar un valor de temporizador individual para cada entidad. Ver [Custom components](https://github.com/decentraland/docs/blob/main/creator/sdk7/sdk7/architecture/custom-components.md).

## Orden de ejecución de los Systems

En algunos casos, cuando tienes múltiples sistemas en ejecución, podría importarte qué sistema se ejecuta primero en tu escena.

Por ejemplo, podrías tener un *physics* sistema que actualiza la posición de las entidades en la escena, y otro *boundaries* sistema que garantiza que ninguna de las entidades esté posicionada fuera de los límites de la escena. En este caso, querrás asegurarte de que el *boundaries* sistema se ejecute al final. De lo contrario, el *physics* sistema podría mover entidades fuera de los límites de la escena pero el *boundaries* sistema no lo descubrirá hasta que se ejecute nuevamente en el siguiente frame.

Al añadir un sistema al engine, establece un `priority` opcional para determinar cuándo se ejecuta el sistema en relación con otros sistemas.

```ts
engine.addSystem(PhysicsSystem, 1)
engine.addSystem(BoundariesSystem, 5)
```

Los Systems con un número de prioridad más bajo se ejecutan primero, así que un sistema con una prioridad de *1* se ejecuta antes que uno con prioridad *5*.

Los Systems a los que no se les da una prioridad explícita tienen una prioridad por defecto de *0*, por lo que estos se ejecutan primero.

Si dos sistemas tienen el mismo número de prioridad, no hay forma de saber con certeza cuál de ellos se ejecutará primero.

## Eliminar un system

Una instancia de un system puede añadirse o eliminarse del engine para activarlo o desactivarlo.

Si un sistema está definido pero no se añade al engine, su función no es llamada por el engine.

Para eliminar un system, primero debes darle un nombre cuando lo añades al engine, para que puedas referirte al sistema más tarde.

```ts
// declarar sistema
function mySystem(dt: number){
  console.log("retardo desde el último tick: ", dt)
}

// añadir sistema (dándole una prioridad y un nombre)
engine.addSystem(mySystem, 1, "DelaySystem")

// eliminar sistema
engine.removeSystem("DelaySystem")
```

Una escena puede potencialmente tener múltiples instancias del mismo sistema ejecutándose juntas, por lo que necesitas decirle al engine cuál de esas eliminar.

Otra forma de eliminar un sistema es declarar un puntero al sistema, y luego pasar ese puntero al `engine.removeSystem()` método.

```ts
// declarar sistema
function mySystem(dt: number){
  console.log("retardo desde el último tick: ", dt)
}

// añadir sistema (creando un puntero)
const mySystemInstance = engine.addSystem(mySystem)

// eliminar sistema
engine.removeSystem(mySystemInstance)
```

Ten en cuenta que el puntero es a la *instance* del sistema, no a la clase del sistema. En el ejemplo anterior, `engine.removeSystem()` no se está pasando `mySystem` (la declaración de la clase del sistema). Se está pasando `mySystemInstance` (la instancia que fue añadida al engine).

Puedes usar el método a continuación para hacer que un sistema se autodestruya cuando su propósito esté completo.

```ts
   const mySystem = function(dt: number){
        time += dt
        if(time > 3){
		engine.removeSystem(mySystem)
        }    
    }
    engine.addSystem(mySystem)
```
