本页尚未翻译,当前显示英文版本。
Visibility
40 名玩家时,整房间快照没问题;到了 200 名就不行了。 可见区域决定谁收到什么,它是一个声明的谓词,而不是逐个对象拨动的开关。广播和按主体数据包是同一模型下声明的两种投递模式,所以在两者之间切换只需改配置,不必重写。它是一种通道优化,不是权限;权限请看 Access & Roles。
同一个声明的模型(谓词、分层、细节层级(detail tier))有两种读取方式:
| 广播 | 按主体数据包 | |
|---|---|---|
| 发送 | 整个房间,发给所有人 | 每个玩家只收到其规则选中的那一部分 |
| 适用 | 小房间;这是默认模式 | 人多的场景,数据包大小必须可预测 |
| 读取声明 | 每个房间一次 | 每个主体一次 |
不能泄露的内容根本不在数据包里,而不是在客户端上隐藏:从不发送,因此这是一项安全属性,而不是带宽属性。
何时使用
- 房间规模超出了整房间广播的承受范围:200 名玩家需要的是每个客户端各自的邻近区域流,而不是每一个 delta。
- 状态不能泄露:战争迷雾和仅所有者可见的字段应该根本不发送,而不是在客户端隐藏。
- 多个会话共用一张地图(map),彼此不能看见:分层只是再加一个谓词。
- 人多时数据包大小必须可预测:给对象数设上限并声明排序,“最近的 N 个”才是一项承诺,而不是密度碰巧造成的结果。
- 位于边界的玩家必须能看到边界另一侧:声明哪些相邻区域可见,因为默认只有玩家自己所在的区域可见。
- 无需使用:如果房间(room)很小。共享数据包这一投递模式(delivery mode)已经够用。
分工
| 主体 | 在本页中的职责 |
|---|---|
schema-author | 声明可见性(visibility)谓词、对象上限及其排序、哪些相邻区域可见,以及投递模式 |
any | 订阅并接收区域允许的内容;可以在声明的范围内为自己调低对象上限 |
概览
在
Tank 上声明半径和分层规则;Ammo 在字段旁限定为仅所有者可见[Entity("tank")]
[Visible(Radius = 60)] // spatial
[Visible(Rule.SameLayer)] // layers of one map
public class Tank
{
[Sync] public Vector3 Position;
[Sync(To = Scope.Owner)] public int Ammo; // per-field scope
}@Entity('tank')
@Visible({ radius: 60 }) // spatial
@Visible(Rule.SameLayer) // layers of one map
export class Tank {
@Sync() position!: Vector3;
@Sync({ to: Scope.Owner }) ammo = 0; // per-field scope
}@entity("tank")
@visible(radius=60) # spatial
@visible(Rule.SAME_LAYER) # layers of one map
class Tank:
position: Vector3 = sync()
ammo: int = sync(to=Scope.OWNER) # per-field scope即将推出 —— Go
Go 中基于特性的声明方式尚未确定(O-1),确定写法后会补上示例。
// multi-entry values are one quoted list (a specifier value is a single token)
UCLASS(PSEntity = "tank",
PSVisible = "radius:60, rule:SameMapInstance") // spatial + instances of one map
class UTank : public UObject
{
GENERATED_BODY()
UPROPERTY(PSSync) FVector3f Position;
UPROPERTY(PSSync = (To = "Owner")) int32 Ammo; // per-field scope
};
// declarations compile into the same pushed model — playserv push from the UE project or CI[Entity("tank")]
[Visible(Radius = 60)] // spatial
[Visible(Rule.SameLayer)] // layers of one map
public class Tank
{
[Sync] public Vector3 Position;
[Sync(To = Scope.Owner)] public int Ammo; // per-field scope
}模型
可见性规则声明什么。
| 声明项 | 含义 |
|---|---|
谓词 | 规则本身,使用与访问谓词、状态转换守卫条件相同的谓词语言。半径、地图实例、队伍和所有权都只是谓词的特例,不是独立的机制:契约中只有这一种谓词语言 |
对象上限 及其排序 | 规则可以限制对象数量,此时选择顺序需要声明,而不是推断:“最近的 N 个”等于谓词加上按距离排序再加上限。单靠谓词无法表达它,因为谓词回答的是“这一行是否符合”,而不是“符合的行里哪个更近” |
相邻区域 | 相邻房间或地图实例中的对象是否可见,以及具体哪些可见。绝不隐式生效:不声明时,区域就是接收方所在的那一个 |
投递模式 | 共享数据包:所有人收到同样的内容,CPU 开销低。按主体数据包:每个接收方按自己的区域收到专属数据包;CPU 开销高,但在大规模人数下必不可少 |
所有区域都成立的事实。
| 始终成立 | 含义 |
|---|---|
可见性 | 不是权限:区域隐藏的内容可能凭权限(permission)拿到,反之亦然(见下文) |
接收方 | 可以调低上限:在声明的最大值以内,且绝不低于声明的最小值。因为对上下文匹配的所有人来说谓词都一样,而数据包大小是接收方自己的问题 |
截断 | 可观测:接收方会得知数据包被截断,以及按哪种排序截断。禁止静默截断,因为它和“没有更多对象”无法区分 |
降级 | 需要声明:组装按主体数据包的预算耗尽时,平台按声明回退到共享数据包,而不是开始随意丢掉接收方 |
数据包形态 | 只是一项弱承诺:数据包的大小和构成不应让人推断出隐藏对象的存在 |
可见性不是权限
区域是通道优化,不是访问控制。不要把两者混为一谈:否则战争迷雾设置会悄悄扩大权限,或者权限会开始取决于距离。数据包大小可能暗示隐藏对象的存在,所以在隐藏对象的存在本身不能泄露的地方,请用 Access & Roles 权限,而不是区域。
“谁看得到这个?”和“他们看到什么?”都可以查询。按主体数据 包的开销是一等的读取项,代码和面板中都能读取。
错误
- 未声明的订阅目标属于校验拒绝。
- 没有订阅权限时返回 forbidden 或 not found,取决于目标是否存在本身是否是秘密:拒绝结果本身不能泄露它拒绝的是什么。
- 无法解析的恢复位置属于错误请求,而不是静默地从当前时刻重新开始。
- 平台关闭的订阅和订阅数量用尽,都属于冲突。
- 扩大视图只能在
fn中进行。 授予覆盖视图和设置细节层级对玩家会话一律以 forbidden 拒绝,其视图保持不变:观战客户端无法扩大自己的授权。 - 调用方视图排除在外的实例返回
not found,与不存在的实例答复相同;返回 forbidden 就等于确认墙后确实有东西。 - 读取按主体数据包开销只能在
fnadm中进行:即云函数或面板,绝不是客户端。
限制
具体数值将随平台限制一章给出;到达每个上限时的行为现在已经确定。
- 按主体数据包的开销:耗尽时按声明降级到共享数据包并发出通知,绝不随意丢掉接收方。
- 每条规则的对象数:按声明的排序设上限,并带有可观测的截断标记。
- 每个主体的订阅数:拒绝新订阅,已有订阅继续有效。
- Delta 大小:delta 拆分而不截断,拆分可观测。
- 发送速率:只是上限,不是保证。