本页尚未翻译,当前显示英文版本。
Schema 即代码
在代码中声明模型,推送,拿回类型。 这是开发者接触 schema 的入口:管理面板和代码编写的是同一个模型,代码生成则为每个引擎补上闭环。
何时使用
- 数据模型应当放在代码里,像代码一样评审:声明、
schema diff、schema push。 - 引擎类型绝不能与已部署的模型脱节:
schema codegen会重新生成 Unreal C++ 和 Unity C# 类型。 - 破坏性变更必须在执行前可读、可取消:提议 → 计划 → 应用。
- 你在多个项目中复用同一组捆绑(
Stat、Interactable):把它声明一次,作为预设(preset)。 - 无需使用:如果运营方(operator)只是在管理面板里调整数值。模型变更照样会以 diff 的形式回到代码中。
分工
| 主体 | 在本页中的职责 |
|---|---|
schema-author | 在代码中声明实体(entity)、部件和枚举,执行 diff 并推送 |
operator | 查看面板中的概览,提议并应用迁移(migration) |
ci | 构建流水线,以 backend-service 密钥运行:合并时推送,随后重新生成引擎类型 |
概览
声明
Item:内嵌 Stats 部件和一个枚举,作为一个 schema 推送[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即将推出 —— Go
Go 中基于特性的声明方式尚未确定(O-1),确定写法后会补上示例。
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[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
推送是一个显式步骤
保存文件时不会上传任何内容。只有从你的机器或 CI 运行 playserv push(或 schema push)时,声明才会进入已部署的模型,并且会带上它做 diff 时所依据的修订版本。在面板中的修改会在 schema diff 中显示为漂移;无论朝哪个方向同步变更,都要由你亲自运行命令。
模 型
声明包含什么。
| 包含 | 含义 |
|---|---|
key | 用来寻址它的稳定名称。在代码中重命名就是重命名,而不是先删除再创建 |
kind | 在代码中声明的实体、部件或枚举 |
所有权模式 | seed:记录不存在时由代码创建,再次推送不会改动其中的值,此后这些值归管理控制台所有;或 managed:始终归代码所有,每次推送都把值恢复为声明的内容,管理控制台中的修改会被直接拒绝,而不是先应用、再在下次推送时丢失 |
preset | 可选:可复用的捆绑,即实体预设,例如 stats 或 world-objects,声明一次,像类型一样应用。它不会引入新的声明种类 |
推送保证什么。
| 始终成立 | 含义 |
|---|---|
匹配 | 按键匹配,绝不按符号:重命名符号后再次推送,只会留下一条记录,而不是两条 |
报告 | 在应用之前准确说明哪些内容会变化,应用之后说明覆盖了什么 |
来源 | 可以区分:由代码推送创建的记录,与其他途径创建的记录能够区分开 |
修订版本 | 随推送一起提交,推送要么整体生效,要么完全不生效 |
代码生成保证什么。
| 始终成立 | 含义 |
|---|---|
重新生成 | 每次推送之后都会执行,生成的类型绝不手动修改:重新生成后再 diff,不会产生任何变化 |
命名 | 无论声明在哪里编写,命名都跟随声明:Item 上的 Rarity 字段在 Unreal 中成为 UPSItem::Rarity,在 Unity 中成为 Item.Rarity |
两个方向 | 不会分叉:代码中声明的一切都会出现在面板中,运营方在面板中编写的一切都能与代码干净地 diff,因此 schema diff 会显示每一处差异 |
迁移保证什么。
| 始终成立 | 含义 |
|---|---|
何时需要迁移 | 持久化声明发生变更,并且会改写现有的值 |
迁移声明什么 | 版本、预览、有序应用、失败时的回滚,以及可观察的完成结果 |
共存 | 两个数据版本同时在线时,读写操作会声明自己接受哪些版本——运行时绝不根据字段名推断兼容性 |
错误
- 没有相应角色的调用方会收到
forbidden,已部署的模型保持不变:拒绝绝不会导致部分推送。这与修订版本过期是两种不同的拒绝:后者返回precondition_failed,表示 diff 所依据的 schema 在此之后已经变化——重新 diff 后再推送。 - 超出声明范围的值会在写入时被拒绝,绝不截断,超过声明长度的字符串同样如此。截断只会得到一个合法但错误的值。
- 无效的 UTF-8 序列会在写入时被拒绝,而不是修复。
- 没有声明迁移的破坏性持久化变更根本无法发布。
限制
与声明形态相关的上限会在声明时检查——部署或发布时——而不是在首次使用时,前提是运行时的症状看起来不像一次拒绝。具体数值将随平台限制一章给出。