For the complete documentation index, see llms.txt. This page is also available as Markdown.

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.

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:

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 +----------->+------------->|  |       | | |
|  |        |            |              |  '-------' | |
|  '--------'            |              '------------' |
|                                                      |
'------------------------------------------------------'

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).

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) 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 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.

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.

Identifying Entities

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), 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).

Synchronization

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) 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.

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.

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:

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:

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.

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.

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:

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.

En cuanto al shouldReplace requisito, consulta resolución de conflictos 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 (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:

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 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.

Hay tres tipos de mensajes en el protocolo CRDT:

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.

PutComponentMessage

Actualiza el estado de un component para una entidadconcreta, creando components y entities desconocidos en el CRDT si es necesario. Resuelve conflictos de acuerdo con marca de tiempo.

El estado el campo es una serialización binaria definida por el component. La mayoría de los components usa protocol buffers para codificar mensajes según lo especificado en los archivos .proto del paquete de protocolo de Decentraland. 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 de acuerdo con marca de tiempo.

DeleteEntityMessage

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

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.

crdtGetState

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.

Última actualización