ULockstepVarStoreBase
Source/MassBattle/Public/Network/LockstepVarStore.h
概述 Overview
BP 声明变量的锁步变量仓库。BP 子类(实际使用的是继承本类的 UMassBattleBPTaskTemplateBase 子类)声明普通变量(int32/float/bool/FVector/FName/FString/枚举/其数组),框架自动:① 每 tick 把它们折叠进确定性 hash(失步检测);② 采集进快照 payload 并在 resync 时恢复(服务器权威值)。变量在构建期按类型分类(ELockstepVarCategory),布局按 UClass 缓存,编码/折叠共用同一字段序与编码格式,保证恢复后折叠与采集时逐位一致。写入纪律(硬约束):变量只能在任务 Tick 处理器(双端同 tick 执行,含追帧)与命令流中写入——UI/广播写入会使双端分叉;resync 重建窗口内只能写可推导状态,此类变量改在 OnResyncRecovered 中重算。
Lockstep variable store for BP-declared variables. A BP subclass (in practice a UMassBattleBPTaskTemplateBase subclass, which inherits this class) declares ordinary variables (int32/float/bool/FVector/FName/FString/enums/arrays of those); the framework automatically ① folds them into the per-tick deterministic hash (desync detection) and ② captures them into the snapshot payload, restoring them on resync (server-authoritative values). Variables are classified by type at build time (ELockstepVarCategory), the layout is cached per UClass, and encoding/folding share the same field order and wire format so a post-restore fold reproduces the capture-side fold bit-for-bit. Write discipline (hard constraint): variables may only be written inside task tick handlers (both peers run them same-tick, fast-forward included) and the command flow — UI/broadcast writes diverge the peers; during the resync rebuild window only derivable state may be written, recompute these variables in OnResyncRecovered instead.
继承 Inheritance
模板 BP 类继承本类后,其声明变量自动成为锁步变量(值类型同步,引用类型跳过并警告)。
Template BP classes inherit this type so their declared variables automatically become lockstep variables (value types synced, reference types skipped with a warning).
ELockstepVarCategory UENUM
变量的存储分类,由 ClassifyProperty 在布局构建期判定,决定 hash 折叠与序列化的编码方式。
Storage classification of a variable, decided by ClassifyProperty at layout build time; it determines both the hash folding and the serialization encoding.
| 值 Value | 说明 Description |
|---|---|
RawBytes | 标量或定长值结构(无 float 叶)——按原始字节折叠/编码。Scalar or fixed-size value struct (no float leaves) — folded/encoded as raw bytes. |
CanonicalizeFloatStruct | 含 float/double 叶的结构——hash 折叠前规范化(-0.0→0.0,memzero padding),序列化仍按原始字节。Struct with float/double leaves — hash canonicalizes before folding (-0.0→0.0, memzero padding); serialization stays raw bytes. |
Text | FString / FName——int32 长度前缀 + TCHAR 数组编码。FString / FName — int32 length prefix + TCHAR array encoding. |
Array | 支持元素类型的 TArray——数量前缀 + 逐元素编码。TArray of supported element types — count prefix + per-element encoding. |
Unsupported | 构建期拒绝(对象/句柄/流/嵌套数组等)——该变量跳过同步并输出一次性警告。Rejected at build time (objects / handles / streams / nested arrays…) — the variable is skipped with a one-shot warning. |
结构体 Structs
单个同步变量的布局记录。由 ClassifyProperty 填充:标量/结构分类记录属性指针与大小(含预计算 float 叶),数组分类额外记录元素信息。字段顺序 = TFieldIterator 顺序。
Layout record for a single synced variable, filled by ClassifyProperty: scalar/struct categories record the property pointer and size (with precomputed float leaves); the array category additionally records element info. Field order follows TFieldIterator order.
成员变量 Members
| 名称 Name | 类型 Type | 默认值 | 说明 Description |
|---|---|---|---|
Property | FProperty* | nullptr | 反射属性指针,提供名称/偏移/大小。Reflected property pointer, providing name / offset / size. |
Category | ELockstepVarCategory | Unsupported | 分类结果。Classification result. |
Size | int32 | 0 | RawBytes/CanonicalizeFloatStruct——属性大小(字节)。RawBytes/CanonicalizeFloatStruct — property size in bytes. |
Struct | UScriptStruct* | nullptr | CanonicalizeFloatStruct——对应结构。CanonicalizeFloatStruct — the struct. |
FloatLeaves | TArray<FHashFloatLeaf> | 空 | CanonicalizeFloatStruct——预计算的 float 叶偏移列表(见 MassBattleHashFold.h)。CanonicalizeFloatStruct — precomputed float leaf offsets (see MassBattleHashFold.h). |
ArrayProperty | FArrayProperty* | nullptr | Array——供 FScriptArrayHelper 正确处理元素构造/析构。Array — for FScriptArrayHelper (correct element ctor/dtor). |
InnerCategory | ELockstepVarCategory | Unsupported | Array——元素分类(嵌套数组/不支持元素使外层整体判为 Unsupported)。Array — element category (nested arrays / unsupported elements reject the whole outer field). |
InnerSize | int32 | 0 | Array——元素大小。Array — element size. |
InnerFloatLeaves | TArray<FHashFloatLeaf> | 空 | Array——元素为含 float 结构时的 float 叶列表。Array — float leaves when the element is a float struct. |
bIsFName | bool | false | 文本通道 FName 标记——FName 以字符串形态同步(entry id 是进程本地的,原始字节重解释曾是 UB);序列化/折叠/恢复经 cpp 的 TextChannelToString 走 FName::ToString。Text-channel FName marker — FName syncs in string form (entry ids are process-local; the raw reinterpret was UB); serialize/fold/restore go through TextChannelToString in the cpp via FName::ToString. |
bInnerIsFName | bool | false | Array——数组元素为 FName 时的同款标记(逐元素走字符串形态)。Array — same marker when the array element is an FName (per-element string form). |
实例类的完整布局。构建序 = TFieldIterator 序;双端共享同一 BP 类,故布局逐位一致。按 UClass 缓存于 LayoutCache——BP 重编译生成新 UClass,缓存自然失效。
Full layout of an instance class. Build order = TFieldIterator order; both peers share the same BP class so layouts are bit-identical. Cached per UClass in LayoutCache — a BP reinstance mints a new UClass, so the cache naturally invalidates.
成员变量 Members
| 名称 Name | 类型 Type | 默认值 | 说明 Description |
|---|---|---|---|
Fields | TArray<FFieldLayout> | 空 | 按声明顺序排列的字段布局列表(仅受支持变量)。Field layouts in declaration order (supported variables only). |
公开方法 Public Methods
把本实例的全部同步变量折叠进 H。字段序与序列化编码完全一致(保证恢复后折叠等于采集时):RawBytes 直接 MixBytesInto;CanonicalizeFloatStruct 经规范化临时缓冲折叠(绝不修改 live 状态);Text 折叠长度前缀 + TCHAR 载荷;Array 折叠数量 + 逐元素。调用者必须先以实例键作盐。
Folds all of this instance's synced variables into H. Field order matches the serialization encoding exactly (so a post-restore fold equals the capture-time fold): RawBytes mix via MixBytesInto; CanonicalizeFloatStruct folds through a canonicalized temp buffer (never mutates live state); Text folds the length prefix + TCHAR payload; Array folds count + per element. The caller salts with the instance key first.
Input
| 参数 Param | 类型 Type | 说明 Description |
|---|---|---|
H | uint64& | 累积 hash 值(引用,逐字段就地混合)。Accumulated hash (by reference, mixed in place per field). |
Output
无返回值。副作用:修改 H。无同步字段时零贡献(调用者侧已由 HasSyncedFields 跳过)。
No return value. Side effect: mutates H. Zero contribution when no synced fields exist (callers skip via HasSyncedFields).
把每个同步变量序列化为一条快照条目(FragmentName = 变量名)。恢复端按名匹配布局,BP 变量增删安全。编码:值类型原始字节(位级恢复)、文本 int32 长度 + TCHAR 载荷(无尾随 null)、数组数量 + 逐元素。
Serializes each synced variable into one snapshot entry (FragmentName = variable name). Restore matches entries by name against the layout, so BP variable add/remove stays safe. Encoding: raw bytes for value types (bit-exact restore), int32 length + TCHAR payload for text (no trailing null), count + per-element for arrays.
Input
| 参数 Param | 类型 Type | 说明 Description |
|---|---|---|
Out | TArray<FFragmentSnapshotBlobEntry>& | 输出条目数组,按布局字段序追加。Output entry array, appended in layout field order. |
Output
无返回值。副作用:向 Out 追加条目。
No return value. Side effect: appends entries to Out.
从快照条目恢复变量:按名匹配布局,未知/缺失名字跳过(边界校验),已匹配条目解码写入对应偏移。任一条目解码失败或残留字节(损坏 payload)时输出 Error 日志并返回 false——调用方据此拒绝整份快照(fail loud)。无同步字段时直接返回 true。
Restores variables from snapshot entries: name-matched against the layout (unknown/missing names skipped, bounds-checked); matched entries decode into the corresponding offset. Any decode failure or residual bytes (malformed payload) logs an Error and returns false, letting the caller reject the whole snapshot (fail loud). Returns true immediately when there are no synced fields.
Input
| 参数 Param | 类型 Type | 说明 Description |
|---|---|---|
Fields | const TArray<FFragmentSnapshotBlobEntry>& | 待恢复的条目数组(通常来自 FVarStoreSnapshotEntry.Fields)。Entries to restore from (typically FVarStoreSnapshotEntry.Fields). |
Output
返回 true = 全部字段恢复成功;false = 载荷损坏(部分字段可能已写入,但调用方应拒绝快照)。副作用:覆写实例变量。
Returns true when all fields restore successfully; false on malformed payload (some fields may already be written, but the caller must reject the snapshot). Side effect: overwrites instance variables.
查询类是否声明了任何受支持的同步变量(布局非空)。hash 折叠与快照采集在调用 FoldInto / SerializeToEntries 前以它跳过零贡献实例。
Queries whether the class declares any supported (synced) variables (non-empty layout). Hash folding and snapshot capture use it to skip zero-contribution instances before calling FoldInto / SerializeToEntries.
Input
无参数。
No parameters.
Output
返回 true 当布局含至少一个字段。
Returns true when the layout has at least one field.
懒构建布局的访问器:按实例的 UClass 查 LayoutCache,命中直接返回;未命中调用 BuildLayout 构建并缓存。BP 重编译生成新 UClass 后缓存自然失效,下次访问重建。
Lazy layout accessor: looks up LayoutCache by the instance's UClass; on hit returns directly, on miss calls BuildLayout and caches. A BP reinstance mints a new UClass so the cache naturally invalidates and rebuilds on next access.
Input
无参数。
No parameters.
Output
返回布局指针(缓存指针,生命周期由 LayoutCache 的 TSharedPtr 持有;类无同步变量时布局 Fields 为空但指针有效)。
Returns the layout pointer (cached; lifetime held by the TSharedPtr in LayoutCache; valid even when Fields is empty).
成员变量 Members
按 UClass 缓存的布局表。BP 重编译生成新 UClass 后旧条目自然作废,无显式失效逻辑。
Per-UClass layout cache. BP reinstance mints a new UClass, so stale entries naturally go dead; no explicit invalidation.
已输出过警告的类集合——每个类对 Transient 跳过与不支持类型只警告一次,避免每实例刷屏。
Set of classes already warned — each class warns once for transient skips and unsupported types, avoiding per-instance log spam.
管线角色 Pipeline Role
逐 tick hash 折叠:UMassBattleSubsystem::FoldVarStoresIntoHash 按 SortedTemplateKeys(注册时插入排序,双端确定性序)遍历模板实例,先以键作盐 MixBytesInto 再调 HasSyncedFields + FoldInto(MassBattleSubsystem.cpp:601-616)。该函数在 UMassBattleNetworkProcessor 的逐 tick 实体 hash 折叠同点调用(MassBattleNetworkProcessor.cpp:1262),并在快照恢复 hash 校验中作为镜像折叠调用(MassBattleNetworkSubsystem.cpp:2691)。
快照采集:UMassBattleSubsystem::CaptureVarStoreBlobs(同键序)调 SerializeToEntries 生成 FVarStoreSnapshotEntry 列表,由 UMassBattleNetworkProcessor 在 bCaptureSnapshot tick 写入 PendingVarStoreSnapshot(NetworkProcessor.cpp:1265),最终进入 payload 的 LockstepVarStores。
快照恢复:ApplyEntitySnapshotPayload 对每个条目经 GetTaskTemplateByKey 解析模板实例后调 DeserializeFromEntries(NetworkSubsystem.cpp:1917-1930)——先于恢复 hash 校验,使恢复后折叠能复现采集侧折叠;解码失败直接返回 false 拒绝应用。变量恢复先于任务重建(2c)与 OnResyncRecovered,使后者可重算依赖这些变量的状态。
Per-tick hash fold: UMassBattleSubsystem::FoldVarStoresIntoHash iterates template instances in SortedTemplateKeys order (insert-sorted at registration — deterministic across peers), salting with the key via MixBytesInto before HasSyncedFields + FoldInto (MassBattleSubsystem.cpp:601-616). It is called at the same point as the per-entity hash fold in UMassBattleNetworkProcessor (MassBattleNetworkProcessor.cpp:1262), and again as the mirror fold in the restore-hash validation (MassBattleNetworkSubsystem.cpp:2691).
Snapshot capture: UMassBattleSubsystem::CaptureVarStoreBlobs (same key order) calls SerializeToEntries to build FVarStoreSnapshotEntry records; UMassBattleNetworkProcessor writes them into PendingVarStoreSnapshot on bCaptureSnapshot ticks (NetworkProcessor.cpp:1265), finally reaching LockstepVarStores in the payload.
Snapshot restore: ApplyEntitySnapshotPayload resolves each template instance via GetTaskTemplateByKey and calls DeserializeFromEntries (NetworkSubsystem.cpp:1917-1930) — before the restore-hash validation so the mirror fold reproduces the capture fold; a decode failure returns false and rejects the apply. Variables are restored before the task rebuild (2c) and OnResyncRecovered, letting the latter recompute derived state.
补充说明 Notes
私有实现细节:BuildLayout(static)跳过框架基类属性(属主为 ULockstepVarStoreBase/UObject)与 Transient 变量(警告一次);ClassifyProperty(static)递归分类——句柄类结构(FEntityHandle/MassEntityHandle/FRandomStream)在 v1 明确不支持 UID 编码,判为 Unsupported。
Private implementation details: BuildLayout (static) skips framework-owned properties (owned by ULockstepVarStoreBase/UObject) and Transient variables (one-shot warning); ClassifyProperty (static) classifies recursively — handle-like structs (FEntityHandle/MassEntityHandle/FRandomStream) are explicitly Unsupported in v1 (no UID encoding).