跳到主要内容
基础构件

本页尚未翻译,当前显示英文版本。

Core

**Core 是你唯一需要创建的对象,其余一切都挂在它上面。**传入一个密钥,你就拿到了上下文、身份、类型化的错误、追踪和批量操作。每个模块调用都经过 Core,没有哪个模块自带一份副本。

何时使用

  • 你需要知道自己是谁、身在何处:身份(identity)、角色(role)、已解锁的模块、项目、环境和区域。
  • 云函数(cloud function)需要以玩家身份写入。这次写入记在该玩家名下,记录里同时写明函数和玩家。
  • 重试不能生效两次:批量(batch)操作带有幂等键(idempotency key)。
  • 失败必须能用来分支、能被检索:每次抛出的都是带稳定错误码的类型化 Problem。
  • 无需使用:如果你要处理消息、调用和状态。这些属于原语(primitive):Events、RPC 和 Data & Subscriptions,通过 Entity 寻址,受众由 Groups 定义。上传和下载见 Files & UGC,挂载与命名空间见深入底层。

分工

主体在本页中的职责
任意主体通过 Whoami 读取身份、上下文和角色
backend-service以玩家身份操作;把幂等操作合并成批量
operator查看失败或重试调用的追踪记录

概览

Whoami、隐式上下文,以及可以安全重试的批量操作
var me  = PlayServ.Whoami();              // identity, roles, unlocked modules
var env = PlayServ.Context;               // project · env · region

// retries never double-apply: the batch carries an idempotency key
await PlayServ.Batch(key: orderId, b =>
{
    b.Inventory.Grant(playerId, "starter.pack");
    b.Inventory.Grant(playerId, "starter.emote");
});
const me = playserv.whoami();             // identity, roles, unlocked modules
const env = playserv.context;             // project · env · region

// retries never double-apply: the batch carries an idempotency key
await playserv.batch(orderId, (b) => {
  b.inventory.grant(playerId, 'starter.pack');
  b.inventory.grant(playerId, 'starter.emote');
});
me = playserv.whoami()                    # identity, roles, unlocked modules
env = playserv.context                    # project · env · region

# retries never double-apply: the batch carries an idempotency key
async with playserv.batch(key=order_id) as b:
    b.inventory.grant(player_id, "starter.pack")
    b.inventory.grant(player_id, "starter.emote")
即将推出 —— Go

Go 中基于特性的声明方式尚未确定(O-1),确定写法后会补上示例。

const FPSActor Me = Client->Whoami();               // identity, roles, unlocked modules
const FPSPlatformContext Env = Client->Context();   // project · env · region

// retries never double-apply: each keyed operation is safe to repeat
Client->Commerce->Entitlements->Grant(FPSIdempotencyKey(PackGrantId), PlayerId, PSKeys::Item::StarterPack);
Client->Commerce->Entitlements->Grant(FPSIdempotencyKey(EmoteGrantId), PlayerId, PSKeys::Item::StarterEmote);
// co_await support ships later: a built-in SDK coroutine wrapper that respects Unreal's GC and threading.
Unity C# is the same C# API here — 在 Unity 中基于生成的类型直接运行
var me  = PlayServ.Whoami();              // identity, roles, unlocked modules
var env = PlayServ.Context;               // project · env · region

// retries never double-apply: the batch carries an idempotency key
await PlayServ.Batch(key: orderId, b =>
{
    b.Inventory.Grant(playerId, "starter.pack");
    b.Inventory.Grant(playerId, "starter.emote");
});

身份保存在 Core 中,所以任何调用都不需要 session 或 ctx 参数。

模型

每个错误都包含什么。

字段含义
code拒绝的机器可读名称。稳定:沿用平台现有的错误码,不另造新码
category拒绝的类别,据此判断重试是否有意义
追踪标识本次发生的 id。始终存在,本地错误也不例外,因此技术支持无需复现问题
explanation给人看的文字。不稳定:随时可能修改或本地化
逐字段错误用于校验拒绝:每个被拒字段的字段名、错误码和消息

按错误码分支,而不是按文字

逻辑里用 code 和 category。explanation 是给人看的,随时会变,不另行通知。只暴露消息文本的绑定就是有 bug。

三种来源。

来源发生了什么
platform平台拒绝,错误码来自平台的目录
localSDK 在发送前就拒绝了,错误码来自 SDK 自己公布的列表
unknown调用已经发出,但没有收到回复,所以结果未知

所有拒绝都满足的几点。

  • 被拒绝的操作什么都没有生效,所以错误分支里不需要补偿性读取。例外有两个,出现的地方都会明确说明:结果未知的超时,以及按元素处理的批量操作。
  • 无论错误以什么方式到达,它都是同一个:绑定抛出异常、返回结果,还是在订阅(subscription)上回调,错误码、类别和来源都相同。
  • 超时属于 unknown 来源,不是一个回复。遇到超时怎么办,由每个操作分别声明。

错误

同一个调用被拒绝。rate_limited 还会告诉你何时可以重试
try { await PlayServ.Inventory.Grant(playerId, "starter.pack"); }
catch (Problem p) when (p.Code == "rate_limited")
{
    Hud.RetryAt(p.RetryAfter);
}
try { await playserv.inventory.grant(playerId, 'starter.pack'); }
catch (p) {
  if (Problem.code(p) === 'rate_limited') hud.retryAt(p.retryAfter);
  else throw p;
}
try:
    await playserv.inventory.grant(player_id, "starter.pack")
except Problem as p:
    if p.code == "rate_limited":
        hud.retry_at(p.retry_after)
    else:
        raise
即将推出 —— Go

Go 中基于特性的声明方式尚未确定(O-1),确定写法后会补上示例。

// UE builds run without exceptions — the completion carries the result, read explicitly
Client->Commerce->Entitlements->Grant(FPSIdempotencyKey(GrantId), PlayerId, PSKeys::Item::StarterPack,
    TPSOnResult<void>::CreateWeakLambda(this, [this](const TPSResult<void>& Result)
    {
        if (Result.IsRefused() && Result.Refusal().Code == FPSFailureCode::RateLimited)
        {
            Hud->RetryAt(Result.Refusal().RetryNotBefore);   // TOptional<FDateTime> — an instant, not a delay
        }
    }));
// co_await support ships later: a built-in SDK coroutine wrapper that respects Unreal's GC and threading.
Unity C# is the same C# API here — 在 Unity 中基于生成的类型直接运行
try { await PlayServ.Inventory.Grant(playerId, "starter.pack"); }
catch (Problem p) when (p.Code == "rate_limited")
{
    Hud.RetryAt(p.RetryAfter);
}

**缺少角色的调用方会被直接拒绝,而不是拿到打了折扣的结果。**对于玩家或客户端会话,fn 和 adm 两行都会失败:调用 以玩家身份操作、发出追踪或读取追踪,都会返回错误码为 forbidden 的 Problem。凭据本身有效,只是它的角色不包含这项权限。用同一个幂等键重试也不会有任何变化。

限制

不要重试已被接受的调用

限制在准入时检查。已被接受的调用已经通过了限制,无论等多久都会执行。之后某个调用被拒绝不会影响它,所以重试已被接受的调用会把工作做两遍。

**只有被拒绝时,你才会知道限制的存在。**SDK 不公开当前生效的数值和剩余余量,也不会在接近上限时发出警告,玩家更看不到任何限制。你需要的信息都在拒绝里:

  • 类别,说明重试是否有意义
  • 这是谁的限制
  • 何时可以重试,以及限制按什么窗口计算