> For the complete documentation index, see [llms.txt](https://docs.decentraland.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.decentraland.org/contributor/contributor-es/tiempo-de-ejecucion-de-la-escena/modulos-de-tiempo-de-ejecucion/engine-api.md).

# API del Engine

El `EngineApi` el módulo proporciona acceso al framework Entity-Component-System compartido entre el World Explorer (que ejecuta el bucle del juego) y las scenes individuales (que gestionan sus propias entities), además de utilidades relacionadas.

```ts
const engine = require("~system/EngineApi");
```

Este módulo es el más complejo y rico en funcionalidad, y el más probable de recibir actualizaciones y extensiones. Es donde reside gran parte del poder de Decentraland, ya que determina el tipo de experiencias que las personas pueden crear.

Este módulo contiene los siguientes métodos:

* [`function crdtSendToRenderer`](#crdtSendToRenderer)
* [`function crdtGetState`](#crdtGetState)

### Introducción

El `EngineApi` el módulo está diseñado para sincronizar el estado del mundo entre el Explorer y la scene intercambiando actualizaciones, eventos y comandos. Implementa un protocolo de mensajes extensible que permite a las scenes, entre otras cosas:

* Crear y destruir entities.
* Adjuntar, actualizar y eliminar components.
* Lanzar rayos en el entorno 3D y detectar impactos.
* Recibir eventos como la entrada del player.

```
.------------------------------------------------------.
| World Explorer                                       |
|                                                      |
|                   [Engine API]                       |
|                        |                             |
|  .--------.            |              .------------. |
|  |        |<-----------+<-------------+  Runtime   | |
|  |  Juego  |            |   Commands   |  .------.  | |
|  |        |   Events   |              |  | Scene | | |
|  | Engine +----------->+------------->|  |       | | |
|  |        |            |              |  '-------' | |
|  '--------'            |              '------------' |
|                                                      |
'------------------------------------------------------'

```

{% hint style="info" %}
El `EngineApi` el módulo está en proceso de renovación. Si vas a las definiciones de origen, encontrarás métodos heredados que ya no se usan o que pueden implementarse como no-ops en la versión más reciente (más sobre esto más abajo).
{% endhint %}

### Entity-Component-System Framework

Con el `EngineApi` módulo viene una implementación genérica y extensible de un framework ECS compartido, que permite tanto a las scenes como al propio engine del juego crear entities, adjuntar components y actualizar sus estados.

Los cambios realizados desde cualquiera de los dos lados se reflejan en el otro mediante el intercambio de mensajes. El mecanismo utilizado ([véase abajo](#synchronization)) garantiza que ambas partes acuerden el orden de cualquier actualización y alcancen una consistencia eventual sobre el estado del mundo.

El World Explorer implementa un conjunto conocido de components básicos (posiciones, formas, texturas, media y más), y sabe cómo deserializar y aplicar cambios a su estado cuando recibe una actualización. Las scenes que usan el [SDK](https://docs.decentraland.org/creator) también tienen utilidades para crear components personalizados, pero esos son administrados por completo por la scene (no sincronizados) y, por lo tanto, quedan fuera del alcance del protocolo.

{% hint style="info" %}
La mayoría de las scenes usan el Decentraland SDK, que encapsula el `EngineApi` módulo y ofrece una interfaz mucho más agradable y de mayor nivel para los desarrolladores de contenido. Las scenes que acceden directamente al protocolo de mensajería en este módulo son extremadamente raras.
{% endhint %}

#### Identifying Entities <a href="#identifying" id="identifying"></a>

Los IDs de Entity son enteros simples que comienzan con `0`.

Por protocolo, los IDs por debajo de `512` están reservados para el World Explorer, y por lo tanto no son válidos para entities creadas por la scene. Los números `0`, `1` y `2` están realmente en uso (ver [basic entities](https://github.com/decentraland/docs/tree/main/contributor/entities/README.md)), y el resto del rango está disponible para futuras extensiones.

Cada scene tiene su propio rango privado de IDs, que el Explorer puede mapear de forma transparente a un identificador global entre todas las entities del game engine.

Con el tiempo, a medida que se crean y eliminan entities, es perfectamente válido reutilizar IDs que antes se habían liberado; de hecho, esto puede ser necesario por motivos de rendimiento ([véase abajo](https://github.com/decentraland/docs/tree/main/contributor/runtime-modules/crdtStateDeleteEntities/README.md)).

#### Synchronization <a href="#synchronization" id="synchronization"></a>

Tanto el World Explorer como la scene deben gestionar un conjunto de entities y components compartidos, donde las actualizaciones llegan desde ambos lados de forma asíncrona. Sin medidas adicionales, sus versiones del estado del mundo pueden (y lo harán) terminar siendo diferentes.

Para evitar esto, se usa un CRDT ([tipo de dato replicado libre de conflictos](https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type)) para llegar a un acuerdo entre ambas partes intercambiando mensajes con actualizaciones. Esto ofrece una serie de ventajas:

* Ambas partes pueden actualizar el estado de forma independiente y concurrente.
* Los conflictos se gestionan mediante una estrategia de resolución compartida aplicada localmente por ambas partes.
* Se garantiza que ambas partes convergerán a un estado compartido e idéntico.
* Además de una pequeña sobrecarga en el tamaño de los mensajes, no se requieren coordinación adicional ni viajes de ida y vuelta de mensajes.

{% hint style="info" %}
Esta página explica el uso del CRDT para sincronizar el estado entre una scene que se ejecuta localmente y el game engine, pero el verdadero potencial de este mecanismo reside en los escenarios multijugador. Usando este protocolo, los players pueden coordinar su estado del juego y compartir la misma experiencia.
{% endhint %}

Para que esto funcione, todos los mensajes deben ser conmutativos e idempotentes. Los mensajes fuera de orden pueden aplicarse incluso si todavía no se recibió una actualización intermedia supuestamente anterior, y los mensajes iguales pueden volver a procesarse sin romper la consistencia.

Revisemos de nuevo el diagrama arquitectónico anterior, ahora enfocándonos en el CRDT:

```
.-------------------------------------------------------.
| World Explorer                                        |
|                                                       |
| .-------------.                       .------------.  |
| | Game Engine |                       |   Scene     | |
| |  .-------.  |                       |  .-------.  | |
| |  |       +--+-----------------------+->|       |  | |
| |  | CRDT  |  |     Sync Protocol     |  | CRDT  |  | |
| |  |       |<-+-----------------------+--+       |  | |
| |  '-------'  |                       |  '-------'  | |
| |             |                       |             | |
| '-------------'                       '-------------' |
'-------------------------------------------------------'

```

La implementación del mecanismo de sincronización CRDT consta de tres partes:

1. Una representación interna del estado de todas las entities y components.
2. Una estrategia de resolución de conflictos para elegir entre estados en competencia.
3. Un protocolo de mensajería para comunicar y procesar actualizaciones del estado.

**CRDT State**

El CRDT contiene el estado de todos los components de todas sus entities adjuntas, y puede almacenarse en un mapeo de components a estados con marca de tiempo para cada entity. En pseudocódigo:

```ts
// El estado (completamente genérico) de un component para una entity específica, en un punto del tiempo:
type EntityComponentState = { timestamp: number; state: byte[] };

// El estado de un component para todas las entities adjuntas:
type ComponentState = Map<EntityId, EntityComponentState>;

// El estado completo del CRDT:
type CRDTState = Map<ComponentId, ComponentState>;
```

En realidad, los timestamps no son timestamps al estilo Unix. Son un tipo de contador incremental para secuenciar actualizaciones, sin relación con la hora del reloj. Más sobre esto [más abajo](#timestamps).

Como la capa CRDT no conoce (ni necesita conocer) todos los components disponibles, el estado inicial es un mapa vacío. Las entradas se crean sobre la marcha cuando las operaciones involucran un `ComponentId` o `EntityId` que todavía no estaba mapeado. Cuando se elimina una entity, el estado asociado se borra de todos los components.

Ilustremos esto con un poco más de pseudocódigo.

```ts
const crdtState = new Map(); // vacío, haremos espacio automáticamente para nuevos components y entities
```

```ts
function putEntityComponentState(componentId, entityId, entityComponentState) {
  // Si este component todavía no se conoce, agréguelo:
  if (!crdtState[componentId]) {
    crdtState[componentId] = new Map();
  }

  // Si esta entity no tiene estado para este component, agréguelo y listo:
  if (!crdtState[componentId][entityId] && shouldCreate(entityId)) {
    crdtState[componentId][entityId] = entityComponentState;
    return;
  }

  // En este punto, tenemos dos estados en competencia. El que acabamos de recibir no es necesariamente el
  // que debe conservarse. Tenemos que decidir.
  const existingEntityComponentState = crdtState[componentId][entityId];

  // Solo si las reglas de resolución de conflictos lo indican (más sobre esto más adelante), reemplazar el estado:
  if (shouldReplace(entityComponentState, existingEntityComponentState)) {
    crdtState[componentId][entityId] = entityComponentState;
  }
}
```

El pseudocódigo anterior (si se traduce a código real) es obviamente incompleto y subóptimo. En particular, le faltan las definiciones de `shouldCreate` y `shouldReplace`, que encapsulan la estrategia de resolución de conflictos. Estas tienen requisitos:

* `shouldCreate` debe agregar nuevas entities, pero rechazar volver a crear entities eliminadas.
* `shouldReplace` debe preferir conservar estados que tengan timestamps mayores (con algunos casos límite).

Para apoyar el `shouldCreate` requisito, querrás hacer un seguimiento de los IDs de entities eliminadas, para ignorar cualquier futura actualización de estado para esas. Veamos esto en más pseudocódigo:

```ts
const crdtDeletedEntities = new Set();

function shouldCreate(entityId) {
  return !crdtDeletedEntities.contains(entityId);
}

function deleteEntity(entityId) {
  // Marcar esta entity como eliminada:
  crdtDeletedEntities.add(entityId);

  // Eliminar el estado de esta entity en cada component:
  for (componentState of crdtState) {
    delete componentState[entityId];
  }
}
```

{% hint style="info" %}
Ten en cuenta que, en la implementación ingenua anterior, el conjunto de entities eliminadas solo crecerá. En scenes que reciclan entities rápidamente, esto puede llevar a una explosión en el uso de memoria.

Para evitar esto, el Explorer de la Foundation emplea un índice generacional para reutilizar IDs en un espacio numérico finito, limitando la cantidad de memoria necesaria para hacer seguimiento tanto de entities activas como eliminadas.
{% endhint %}

En cuanto al `shouldReplace` requisito, consulta [resolución de conflictos](#crdtConflicts) más abajo.

**Timestamps**

Como se mencionó arriba, cuando hablamos de CRDT timestamps no nos referimos a timestamps Unix reales para un punto en el tiempo. En su lugar, se usa un [Lamport timestamp](https://en.wikipedia.org/wiki/Lamport_timestamp) (un tipo especial de contador incremental compartido) para llevar el control de la secuencia de eventos por ambas partes.

El valor de este contador se actualiza según estas reglas:

1. Inicializa el contador local en `0`.
2. Antes de enviar un mensaje a las partes remotas, incrementa el contador local.
3. Después de recibir un mensaje de una parte remota, establece el contador local en el máximo entre su valor y el valor recibido, más `1`.

En pseudocódigo:

```ts
const localTimestamp = 0;

function sendState(state) {
  send({ state, timestamp: ++localTimestamp });
}

function receiveState() {
  const { state, timestamp } = receive();
  localTimestamp = max(localTimestamp, remoteTimestamp) + 1;
}
```

**Conflict Resolution**

Cuando el CRDT se encuentra con dos estados en competencia, necesita una estrategia de resolución compartida que todas las partes apliquen de forma idéntica, asegurando que todos lleguen a la misma decisión. El protocolo de Decentraland usa reglas muy simples:

1. Si la entity fue eliminada (y el ID nunca se reutilizó), ignora el nuevo estado.
2. Si no había un estado previo, conserva el nuevo estado.
3. Si el nuevo estado tiene un timestamp mayor, conserva el nuevo estado.
4. Si el nuevo estado tiene un timestamp menor, conserva el estado anterior.
5. Si ambos timestamps son iguales, compara los estados byte a byte y conserva el valor menor.

La mayoría de los casos se resolverán mediante las reglas `1`, `2` y `3`.

**Initial Synchronization**

Antes de que una scene empiece a ejecutar su propio código, el runtime llena el CRDT con el estado de todas las [basic entities](https://github.com/decentraland/docs/tree/main/contributor/entities/README.md) y sus components.

Durante esta sincronización inicial, solo el runtime puede establecer el estado compartido. La scene no tiene अनुमति de realizar modificaciones hasta que el proceso se complete.

**Messaging Protocol**

Los mensajes entre el World Explorer y el runtime de la scene son estructuras en una representación binaria serializada. Cada mensaje lleva un encabezado que indica el tipo y la longitud, seguido de una carga útil particular para cada tipo de mensaje.

```
.----------------.--------------.---------------------------------.
| length: uint32 | type: uint32 |     payload: byte[length]       |
'----------------'--------------'---------------------------------'
╵         campos comunes         ╵         dependientes del tipo          ╵

```

Hay tres tipos de mensajes en el protocolo CRDT:

* [`PutComponent`](#PutComponent)
* [`DeleteComponent`](#DeleteComponent)
* [`DeleteEntity`](#DeleteEntity)

Ten en cuenta que no hay mensajes `CreateComponent` o `CreateEntity` . Las reglas CRDT crean automáticamente entities y components que no se conocían previamente, como se [explicó arriba](#crdtStateAutoCreate).

**`PutComponentMessage`**

Actualiza el `estado` de un `component` para una `entidad`concreta, creando components y entities desconocidos en el CRDT si es necesario. [Resuelve conflictos](#crdtConflicts) de acuerdo con `marca de tiempo`.

```
.----------------.-------------------.-------------------------------------------.
| entity: uint32 | component: uint32 | timestamp: uint32 |     state: byte[]     |
'----------------'-------------------'-------------------------------------------'
╵                       campos comunes                    ╵  dependientes del component  ╵

```

El `estado` el campo es una serialización binaria definida por el component. La mayoría de los components usa [protocol buffers](https://protobuf.dev) para codificar mensajes según lo especificado en los archivos `.proto` del paquete de protocolo de [Decentraland](https://github.com/decentraland/protocol). La excepción notable a esta regla es el `Transform` component, que (siendo con mucho el más común) tiene un formato de serialización optimizado.

Esta capa adicional de serialización tiene una ventaja importante: el protocolo CRDT es agnóstico a cualquier implementación de component presente o futura.

Si la actualización contenida en este mensaje se aplica al CRDT, el `estado` campo se copia tal cual en la estructura.

**`DeleteComponentMessage`**

Eliminar el estado de `component` para un `entidad`. [Resuelve conflictos](#crdtConflicts) de acuerdo con `marca de tiempo`.

```
.----------------.-------------------.-------------------.
| entity: uint32 | component: uint32 | timestamp: uint32 |
'----------------'-------------------'-------------------'

```

**`DeleteEntityMessage`**

Eliminar `entidad` (es decir, todo el estado asociado de components), esperando que el identificador nunca se reutilice para una entity diferente.

```
.----------------.
| entity: uint32 |
'----------------'

```

Ten en cuenta que no hay un campo `marca de tiempo` . Dado que la vida útil de `entidad` ha terminado, la estrategia de resolución de conflictos para cualquier actualización fuera de orden es simplemente ignorarlas.

### Métodos

El conjunto de métodos en `EngineApi` proporciona la interfaz para intercambiar mensajes y reconstruir el estado del mundo desde cero.

**`crdtSendToRenderer`**

Enviar un mensaje serializado desde la scene al renderer, devolver un array de mensajes serializados que el renderer tiene para la scene.

```ts
interface Request {
  // El mensaje serializado para esta solicitud:
  data: byte[];
}

interface Response {
  // Un array de mensajes serializados del renderer (si los hay):
  data: byte[][];
}

function crdtSendToRenderer(Request): Promise<Response>;
```

**`crdtGetState`**

```ts
interface Request {}

interface Response {
  // Si el estado tiene entities creadas por la scene:
  hasEntities: boolean;

  // Un array de mensajes que puede reconstruir el estado del CRDT:
  data: byte[][];
}

function crdtGetState(Request): Promise<Response>;
```

### Legacy Scenes

Las scenes del estilo antiguo (es decir, las construidas usando la versión 6 del SDK o anterior) no usan el mecanismo CRDT moderno, sino que llaman métodos en `EngineApi` que ahora están obsoletos.

El soporte para estas scenes no es un requisito para una aplicación de Decentraland conforme al protocolo.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.decentraland.org/contributor/contributor-es/tiempo-de-ejecucion-de-la-escena/modulos-de-tiempo-de-ejecucion/engine-api.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
