Saltar al contenido principal
El modelo de tu juego

Esta página aún no está traducida: se muestra la versión en inglés.

Esquema como código

Declara el modelo en código, súbelo y recibe los tipos de vuelta. Así llega un desarrollador al esquema: el panel de administración y el código editan el mismo modelo, y la generación de código cierra el ciclo para cada motor.

Cuándo usarlo

  • Quieres que el modelo de datos viva en el código y se revise como código: declaras, schema diff, schema push.
  • Los tipos del motor nunca deben desfasarse del modelo desplegado: schema codegen vuelve a generar los tipos de Unreal C++ y Unity C#.
  • Un cambio incompatible tiene que poder leerse y cancelarse antes de ejecutarse: propuesta → plan → aplicación.
  • Reutilizas un mismo paquete (Stat, Interactable) en varios proyectos: lo declaras una sola vez como preset.
  • No lo necesitas si un operador solo ajusta valores en el panel de administración. Aun así, el cambio del modelo aparece en la comparación con el código.

Quién hace qué

ActorQué hace en esta página
schema-authordeclara en código entidades, partes y enumeraciones, las compara con el modelo desplegado y las sube
operatorrevisa el resumen en el panel, propone y aplica migraciones
ciel pipeline de compilación, que se ejecuta con una clave backend-service: sube el esquema al hacer merge y después vuelve a generar los tipos de los motores

En resumen

Se declara Item con una parte Stats incrustada y una enumeración, y se sube todo como un solo esquema
[Entity("item")]
public class Item
{
    public string Name = "";
    public Rarity Rarity;              // an enum declared the same way
    public Stats Stats = new();        // a part — embedded, no lifecycle of its own
}

[Part("stats")]
public class Stats { public int Power; public int Weight; }
@Entity('item')
export class Item {
  name = '';
  rarity!: Rarity;                   // an enum declared the same way
  stats = new Stats();               // a part — embedded, no lifecycle of its own
}

@Part('stats')
export class Stats { power = 0; weight = 0; }
@entity("item")
class Item:
    name: str = ""
    rarity: Rarity                     # an enum declared the same way
    stats: Stats = Stats()             # a part — embedded, no lifecycle of its own

@part("stats")
class Stats:
    power: int = 0
    weight: int = 0
Próximamente — Go

Todavía no está decidido cómo declarar con atributos en Go (O-1). Los ejemplos llegarán cuando se elija la forma de escribirlo.

USTRUCT(PSPart = "stats")
struct FItemStats
{
    GENERATED_BODY()

    UPROPERTY() int32 Power;
    UPROPERTY() int32 Weight;
};

UCLASS(PSEntity = "item")
class UItem : public UObject
{
    GENERATED_BODY()

    UPROPERTY() FString    Name;
    UPROPERTY() EPSRarity  Rarity;       // an enum declared the same way
    UPROPERTY() FItemStats Stats;        // a part — embedded, no lifecycle of its own
};
// declarations compile into the same pushed model — playserv push from the UE project or CI
Unity C# is the same C# API here — los mismos atributos compilan en Unity, sobre la base 2021.3; aquí no hay sintaxis de C# 12
[Entity("item")]
public class Item
{
    public string Name = "";
    public Rarity Rarity;              // an enum declared the same way
    public Stats Stats = new();        // a part — embedded, no lifecycle of its own
}

[Part("stats")]
public class Stats { public int Power; public int Weight; }
playserv schema diff       # local declarations vs deployed schema
playserv schema push       # with a revision precondition — no blind overwrites
playserv schema codegen    # regenerate Unreal C++ / Unity C# types

Subir es un paso explícito

Guardar un archivo no sube nada. Una declaración llega al modelo desplegado solo cuando se ejecuta playserv push (o schema push), desde tu máquina o desde CI, y lleva consigo la revisión contra la que se hizo la comparación. Una edición en el panel aparece en schema diff como una divergencia; llevar un cambio en cualquiera de las dos direcciones siempre es un comando que ejecutas tú.

El modelo

Qué lleva una declaración.

LlevaQué es
keyel nombre estable con el que se hace referencia a ella. Renombrar en el código es un cambio de nombre, no un borrado seguido de una creación
kinduna entidad, una parte o una enumeración, declarada en código
modo de propiedadseed: el código crea el registro si no existe y una nueva subida no toca los valores, así que a partir de ahí los administra la consola de administración; o managed: el registro siempre pertenece al código, cada subida lleva los valores a lo declarado y las ediciones desde la consola de administración se rechazan en lugar de aplicarse y perderse en la siguiente subida
presetopcional: un paquete reutilizable —un preset de entidad, como stats o world-objects— que se declara una vez y se aplica como un tipo. No introduce ningún tipo nuevo de declaración

Qué garantiza una subida.

SiempreQué es
la correspondenciase hace por clave, nunca por símbolo: si renombras el símbolo y vuelves a subir, queda un solo registro y no dos
el informedice exactamente qué va a cambiar antes de aplicarlo y, después, qué sobrescribió
el origense puede distinguir: un registro creado al subir desde el código se diferencia de uno creado de otra manera
la revisiónviaja con la subida, y la subida se aplica completa o no se aplica

Qué garantiza la generación de código.

SiempreQué es
la regeneraciónocurre después de cada subida, y los tipos generados nunca se editan a mano: si los regeneras y comparas, no hay cambios
los nombressiguen a la declaración, dondequiera que se haya escrito: el campo Rarity de Item se convierte en UPSItem::Rarity en Unreal y en Item.Rarity en Unity
las dos direccionesno se separan: todo lo que se declara en código aparece en el panel, y todo lo que un operador crea en el panel se compara limpiamente con el código, así que schema diff muestra cada diferencia

Qué garantiza una migración.

SiempreQué es
cuándo hace faltacuando un cambio en la declaración de datos persistentes reescribe valores que ya existen
qué declarauna versión, una vista previa, una aplicación ordenada, una reversión si algo falla y un resultado final observable
la coexistenciamientras hay dos versiones de datos activas, las lecturas y escrituras declaran qué versiones aceptan: el runtime nunca deduce la compatibilidad a partir de los nombres de los campos

Errores

  • Quien llama sin el rol recibe forbidden, y el modelo desplegado no se toca: un rechazo nunca deja una subida a medias. Es un rechazo distinto al de una revisión desactualizada, que es precondition_failed y significa que la comparación se hizo contra un esquema que cambió desde entonces: vuelve a comparar y sube otra vez.
  • Un valor fuera de un límite declarado se rechaza al escribirlo, nunca se recorta, y lo mismo pasa con una cadena que supera su longitud declarada. Recortar daría un valor válido pero incorrecto.
  • Una secuencia UTF-8 inválida se rechaza al escribirla, en lugar de repararse.
  • Un cambio persistente incompatible sin una migración declarada no se puede publicar.

Límites

Los topes que dependen de la forma de la declaración se comprueban al declarar —al desplegar o al publicar— y no en el primer uso, en todos los casos en que el síntoma en tiempo de ejecución no parecería un rechazo. Las cifras llegarán con el capítulo sobre los límites de la plataforma.