UMassBattleObstSubsystem
Source/MassBattle/Public/Subsystems/MassBattleObstSubsystem.h
概述 Overview
确定性障碍物实体生成管理中心。三种障碍组件(Box / Capsule / Spline)在 BeginPlay 时向本子系统注册;路由统一(2026-08-29):锁步会话中每个障碍物都走命令路径(FNetworkTag/hash/快照公民),单机则入待处理队列,首个 tick 上子系统把待处理注册按 ComponentPath 排序后依次从障碍物槽位位图分配 UID 生成实体——保证锁步双端 UID 分配顺序一致。障碍物 UID 绝不触碰自然计数器,而是从 [ObstacleSlotUIDBase, 2^31) 的两张占用位图分配:先是样条组块(段 UID = 组槽基址 + 段号,块宽 10000),随后 box/capsule 单槽。派生 FTickableGameObject,仅在初始生成完成前 tick。
Deterministic obstacle entity spawning manager. The three obstacle component types (Box / Capsule / Spline) register with this subsystem during BeginPlay; routing is unified (2026-08-29): in lockstep sessions every obstacle takes the command path (FNetworkTag/hash/snapshot citizen), while standalone play queues pending registrations sorted by ComponentPath on the first tick, spawning entities with UIDs allocated from the obstacle slot bitmaps in that order — so lockstep peers allocate UIDs identically. Obstacle UIDs never touch the natural counter; they are allocated from two occupied-bitmaps in [ObstacleSlotUIDBase, 2^31): spline group blocks first (segment UID = group-slot base + index, 10000-wide blocks), then box/capsule single slots. Derives FTickableGameObject and only ticks until the initial spawn completes.
继承 Inheritance
枚举 Enums
障碍组件类型,FPendingObstacleRegistration.Type 用它区分待处理注册对应的组件种类。
Obstacle component type; FPendingObstacleRegistration.Type uses it to tell which component kind a pending registration refers to.
| 值 Value | 说明 Description |
|---|---|
Box | 盒形障碍(UMassBattleObstacleBoxComponent)。Box obstacle (UMassBattleObstacleBoxComponent). |
Capsule | 胶囊障碍(UMassBattleObstacleCapsuleComponent)。Capsule obstacle (UMassBattleObstacleCapsuleComponent). |
Spline | 样条障碍(UMassBattleObstacleSplineComponent),一段段实体。Spline obstacle (UMassBattleObstacleSplineComponent), spawned as one entity per segment. |
结构体 Structs
一条待处理的障碍注册记录。组件 BeginPlay 注册时压入 PendingRegistrations,ProcessPendingRegistrations 在首个 tick 按 ComponentPath 排序后逐条生成实体。三个组件指针中只有与 Type 匹配的那一个有效。
One pending obstacle registration. Pushed into PendingRegistrations when a component registers during BeginPlay; ProcessPendingRegistrations sorts by ComponentPath on the first tick and spawns entities per entry. Only the component pointer matching Type is valid.
成员变量 Members
| 名称 Name | 类型 Type | 默认值 | 说明 Description |
|---|---|---|---|
Type | EObstacleType | — | 本注册对应的障碍类型。Obstacle type of this registration. |
BoxComponent | TWeakObjectPtr<UMassBattleObstacleBoxComponent> | — | 盒组件弱指针(仅 Type == Box 时有效)。Weak pointer to the box component (valid only when Type == Box). |
CapsuleComponent | TWeakObjectPtr<UMassBattleObstacleCapsuleComponent> | — | 胶囊组件弱指针(仅 Type == Capsule 时有效)。Weak pointer to the capsule component (valid only when Type == Capsule). |
SplineComponent | TWeakObjectPtr<UMassBattleObstacleSplineComponent> | — | 样条组件弱指针(仅 Type == Spline 时有效)。Weak pointer to the spline component (valid only when Type == Spline). |
ComponentPath | FString | — | 组件完整路径(GetPathName(),如 /Game/Maps/Level:PersistentLevel.Actor.Component)。确定性排序的稳定标识——关卡重载后保持不变。Full component path from GetPathName(), e.g. /Game/Maps/Level:PersistentLevel.Actor.Component. Stable identifier for deterministic sorting — unchanged across level reloads. |
公开方法 Public Methods
默认构造函数,函数体为空,无初始化逻辑。
Default constructor with an empty body; no initialization logic.
Input
无参数。
No parameters.
Output
无返回值,无副作用。
No return value, no side effects.
USubsystem 生命周期入口。先调用父类实现,再通过两个惰性 getter(GetMassAPISubsystem / GetMassBattleSubsystem)预热子系统缓存,并把两张槽位位图(SplineGroupSlotBits / ObstacleSlotBits)按定容零初始化(全空闲)。
USubsystem lifecycle entry. Calls the parent implementation first, warms up the subsystem caches via the two lazy getters (GetMassAPISubsystem / GetMassBattleSubsystem), then sizes and zero-initializes both slot bitmaps (SplineGroupSlotBits / ObstacleSlotBits, fixed capacity, all free).
Input
| 参数 Param | 类型 Type | 说明 Description |
|---|---|---|
Collection | FSubsystemCollectionBase& | 子系统集合,由引擎传入。Subsystem collection provided by the engine. |
Output
无返回值。副作用:填充 MassAPISubsystem / MassBattleSubsystem 缓存成员,初始化两张槽位位图。
No return value. Side effects: fills the MassAPISubsystem / MassBattleSubsystem cache members and initializes both slot bitmaps.
USubsystem 生命周期出口。清空 PendingRegistrations,把三个缓存成员(MassAPISubsystem / MassBattleSubsystem / CurrentWorld)置空,最后调用父类实现。
USubsystem lifecycle exit. Empties PendingRegistrations, nulls the three cache members (MassAPISubsystem / MassBattleSubsystem / CurrentWorld), then calls the parent implementation.
Input
无参数。
No parameters.
Output
无返回值。副作用:释放待处理注册队列与子系统缓存。
No return value. Side effect: releases the pending registration queue and subsystem caches.
FTickableGameObject tick 入口:调用 ProcessPendingRegistrations()。首个 tick 处理完所有待注册组件后 bInitialSpawnComplete 置 true,IsTickable() 随之返回 false,此后不再 tick。
FTickableGameObject tick entry: calls ProcessPendingRegistrations(). Once the first tick drains all pending registrations, bInitialSpawnComplete becomes true, IsTickable() returns false, and the subsystem stops ticking.
Input
| 参数 Param | 类型 Type | 说明 Description |
|---|---|---|
DeltaTime | float | 帧时间步长(秒),本实现未使用。Frame delta time in seconds; unused by this implementation. |
Output
无返回值。副作用:首个 tick 内排序并生成全部待注册障碍实体,置位 bInitialSpawnComplete。
No return value. Side effects: sorts and spawns all pending obstacle entities on the first tick, sets bInitialSpawnComplete.
FTickableGameObject 统计 ID,RETURN_QUICK_DECLARE_CYCLE_STAT 宏生成,供 stat 系统归组。
FTickableGameObject stat ID generated by RETURN_QUICK_DECLARE_CYCLE_STAT, used to group this tickable in the stat system.
Input
无参数。
No parameters.
Output
返回 TStatId。其余内联 tickable 接口:IsTickableWhenPaused() 恒 false、IsTickableInEditor() 恒 false、GetTickableGameObjectWorld() 返回 GetWorld()。
Returns the TStatId. Other inline tickable overrides: IsTickableWhenPaused() always false, IsTickableInEditor() always false, GetTickableGameObjectWorld() returns GetWorld().
是否继续 tick:初始生成完成前且世界有效时返回 true;完成后返回 false,停止一切 tick 开销。
Whether to keep ticking: true until the initial spawn completes and the world is valid; false afterwards, stopping all tick cost.
Input
无参数。
No parameters.
Output
返回 !bInitialSpawnComplete && GetWorld() != nullptr。
Returns !bInitialSpawnComplete && GetWorld() != nullptr.
静态访问器:从世界上下文解析世界并取 UMassBattleObstSubsystem。上下文或世界无效时返回 nullptr(不崩溃)。
Static accessor: resolves the world from the world context and returns its UMassBattleObstSubsystem. Returns nullptr when the context or world is invalid (does not crash).
Input
| 参数 Param | 类型 Type | 说明 Description |
|---|---|---|
WorldContext | const UObject* | 任意能提供世界上下文的 UObject;无效时直接返回 nullptr。Any UObject providing world context; returns nullptr immediately if invalid. |
Output
返回子系统指针;无效时为 nullptr。
Returns the subsystem pointer, or nullptr when unavailable.
静态访问器的引用版本:内部调用 GetPtr,失败时 check 断言崩溃——用于子系统必须存在的路径。
Reference version of the static accessor: calls GetPtr internally and check-asserts on failure — for paths where the subsystem must exist.
Input
| 参数 Param | 类型 Type | 说明 Description |
|---|---|---|
WorldContext | const UObject* | 任意 UObject 世界上下文。Any UObject world context. |
Output
返回子系统引用;取不到时触发 check 断言。
Returns a subsystem reference; triggers a check assertion when unavailable.
惰性访问并缓存 UMassAPISubsystem:命中缓存直接返回;否则从当前世界取子系统并写入 CurrentWorld / MassAPISubsystem 缓存。
Lazily accesses and caches UMassAPISubsystem: returns the cache on hit; otherwise fetches the subsystem from the current world and fills the CurrentWorld / MassAPISubsystem caches.
Input
无参数。
No parameters.
Output
返回 UMassAPISubsystem 指针(世界不可用时为 nullptr)。副作用:写入两个缓存成员。
Returns the UMassAPISubsystem pointer (nullptr when the world is unavailable). Side effect: fills the two cache members.
惰性访问并缓存 UMassBattleSubsystem,行为与 GetMassAPISubsystem 完全对称。
Lazily accesses and caches UMassBattleSubsystem; fully symmetric to GetMassAPISubsystem.
Input
无参数。
No parameters.
Output
返回 UMassBattleSubsystem 指针(世界不可用时为 nullptr)。副作用:写入 CurrentWorld / MassBattleSubsystem 缓存。
Returns the UMassBattleSubsystem pointer (nullptr when the world is unavailable). Side effect: fills the CurrentWorld / MassBattleSubsystem caches.
盒障碍注册入口(由 UMassBattleObstacleBoxComponent::BeginPlay 调用),统一路由(RouteObstacle):① 锁步会话 → 一律走命令路径——组件标记 bCommandRouted=true、加入 CommandRoutedBoxes;owner 已复制时先分配联结 LockstepNetID 并注册宿主(客户端经 OnRep 注册),随后 RequestLockstepObstacleSpawn(TypeIndex 0;地图路径障碍保持 NetID=-1,按对象路径解析);② 非锁步单机 → 加锁压入 PendingRegistrations 等待首个 tick 排序后从槽位位图分配。运行时生成的非复制 owner 在锁步会话中被拒绝(Gate-1 响亮报错,防静默发散)。
Box obstacle registration entry (called by UMassBattleObstacleBoxComponent::BeginPlay), unified routing (RouteObstacle): ① lockstep session → always the command path — the component gets bCommandRouted=true and joins CommandRoutedBoxes; when the owner is replicated a join LockstepNetID is allocated first and the host registered (clients register via OnRep), then RequestLockstepObstacleSpawn (TypeIndex 0; map-path obstacles keep NetID=-1 and resolve by object path); ② standalone non-lockstep → push into PendingRegistrations under lock for the first-tick sorted slot allocation. A runtime-spawned NON-replicated owner in a lockstep session is refused (Gate-1 fail-loud, preventing silent divergence).
Input
| 参数 Param | 类型 Type | 说明 Description |
|---|---|---|
Component | UMassBattleObstacleBoxComponent* | 注册的盒组件;无效时直接返回。The box component to register; returns immediately if invalid. |
Output
无返回值。副作用:视路由不同——发送锁步命令并加入命令路由清单 / 向 PendingRegistrations 追加一条记录(持 RegistrationLock)。
No return value. Side effects: depending on the route — sends a lockstep command and joins the command-routed manifest, or appends a record to PendingRegistrations (holding RegistrationLock).
胶囊障碍注册入口(由 UMassBattleObstacleCapsuleComponent::BeginPlay 调用),统一路由与 RegisterBoxObstacle 相同,命令路径 TypeIndex 为 1(加入 CommandRoutedCapsules)。
Capsule obstacle registration entry (called by UMassBattleObstacleCapsuleComponent::BeginPlay); unified routing identical to RegisterBoxObstacle, command path uses TypeIndex 1 (joins CommandRoutedCapsules).
Input
| 参数 Param | 类型 Type | 说明 Description |
|---|---|---|
Component | UMassBattleObstacleCapsuleComponent* | 注册的胶囊组件;无效时直接返回。The capsule component to register; returns immediately if invalid. |
Output
无返回值。副作用同 RegisterBoxObstacle。
No return value. Same side effects as RegisterBoxObstacle.
样条障碍注册入口(由 UMassBattleObstacleSplineComponent::BeginPlay 调用),统一路由同上,命令路径 TypeIndex 为 2(加入 CommandRoutedSplines);本地路径首个 tick 分配的组槽会传给 SpawnSplineObstacleEntities。
Spline obstacle registration entry (called by UMassBattleObstacleSplineComponent::BeginPlay); same unified routing, command path uses TypeIndex 2 (joins CommandRoutedSplines); the local path's first-tick branch passes the allocated group slot to SpawnSplineObstacleEntities.
Input
| 参数 Param | 类型 Type | 说明 Description |
|---|---|---|
Component | UMassBattleObstacleSplineComponent* | 注册的样条组件;无效时直接返回。The spline component to register; returns immediately if invalid. |
Output
无返回值。副作用同 RegisterBoxObstacle(立即分支生成一簇段实体)。
No return value. Same side effects as RegisterBoxObstacle (the immediate branch spawns a cluster of segment entities).
盒障碍注销(组件 EndPlay 调用):若组件的 EntityHandle 有效——① 反查 UID 并 FreeObstacleUIDs 归还槽位;② 经 RemoveStaticObstacleByUniqueID 从网格只增静态半区移除条目并触发确定性重排序(实体销毁后 GridUpdateProcessor 的停用分支永远跑不到,不移除则条目永久残留;动态条目下一帧重建自愈);③ MassAPI->DestroyEntityDefer 延迟销毁实体。
Box obstacle unregistration (called from EndPlay), when the component's EntityHandle is valid: ① resolves the UID and returns the slot via FreeObstacleUIDs; ② removes the grid's append-only static-half entry via RemoveStaticObstacleByUniqueID and triggers the deterministic re-sort (once the entity is destroyed, GridUpdateProcessor's deactivation branch can never run — not removing here ghosts the entry forever; dynamic entries self-heal on the next rebuild); ③ destroys the entity via MassAPI->DestroyEntityDefer.
Input
| 参数 Param | 类型 Type | 说明 Description |
|---|---|---|
Component | UMassBattleObstacleBoxComponent* | 要注销的组件;组件或 UMassAPISubsystem 无效时直接返回。Component to unregister; returns immediately if the component or UMassAPISubsystem is invalid. |
Output
无返回值。副作用:归还槽位、移除静态网格条目并重排序、向命令缓冲写入实体销毁命令。
No return value. Side effects: returns the slot, removes the static grid entry with re-sort, writes an entity destroy command into the command buffer.
胶囊障碍注销,行为与 UnregisterBoxObstacle 相同(归还槽位 + 静态半区移除 + 延迟销毁)。
Capsule obstacle unregistration; identical behavior to UnregisterBoxObstacle (slot return + static-half removal + deferred destroy).
Input
| 参数 Param | 类型 Type | 说明 Description |
|---|---|---|
Component | UMassBattleObstacleCapsuleComponent* | 要注销的组件。Component to unregister. |
Output
无返回值。副作用:延迟销毁实体。
No return value. Side effect: destroys the entity deferred.
样条障碍注销:遍历组件 ObstacleHandles 反查全部段 UID,经 FreeObstacleUIDs 归还组槽(各段 UID 都映射到同一组槽);从任一有效段 UID 反解组槽后按组调用 RemoveStaticSegmentObstacleByGroupID 移除静态半区条目并重排序(逐段全网格扫描代价过高);最后逐个延迟销毁段实体并清空句柄数组。
Spline obstacle unregistration: iterates the component's ObstacleHandles, resolves all segment UIDs and returns the group slot via FreeObstacleUIDs (every segment UID maps to the same group slot); recovers the group slot from any valid segment UID, removes the static-half entries per group via RemoveStaticSegmentObstacleByGroupID with the deterministic re-sort (a per-segment full-grid scan is too costly); finally destroys each segment entity deferred and empties the handle array.
Input
| 参数 Param | 类型 Type | 说明 Description |
|---|---|---|
Component | UMassBattleObstacleSplineComponent* | 要注销的组件。Component to unregister. |
Output
无返回值。副作用:归还组槽、按组移除静态网格条目并重排序、延迟销毁全部段实体并清空 ObstacleHandles。
No return value. Side effects: returns the group slot, removes the static grid entries per group with re-sort, destroys all segment entities deferred and empties ObstacleHandles.
查询初始生成是否完成(内联只读)。
Queries whether the initial spawn has completed (inline read-only).
Input
无参数。
No parameters.
Output
返回 bInitialSpawnComplete。
Returns bInitialSpawnComplete.
UID → 实体句柄映射注册。校验 UID 非负且句柄有效后,双写(shuangxie)到全局 UMassBattleSubsystem::RegisterUIDEntity——障碍 UID 与 agent 等共享同一全局注册表。
UID → entity handle mapping registration. After validating the UID is non-negative and the handle is valid, dual-writes to the global UMassBattleSubsystem::RegisterUIDEntity — obstacle UIDs share the same global registry as agents and other entity kinds.
Input
| 参数 Param | 类型 Type | 说明 Description |
|---|---|---|
UniqueID | int32 | 实体 UID;负数直接跳过。Entity UID; negative values are skipped. |
Handle | FEntityHandle | 实体句柄;MassAPI->IsValid 不通过则跳过。Entity handle; skipped when MassAPI->IsValid fails. |
Output
无返回值。副作用:向 UMassBattleSubsystem 的 UID 注册表写入映射。
No return value. Side effect: writes the mapping into UMassBattleSubsystem's UID registry.
障碍物 UID 绝不触碰自然计数器(NextGlobalUID),而是从障碍物槽位空间 [ObstacleSlotUIDBase, 2^31) 的两张占用位图分配:先是样条组块(段 UID = SplineGroupSlotUIDBase(槽) + 段号,块宽 10000),随后 box/capsule 单槽——首个空闲槽分配,与 cellgrid / 渲染槽位数组同族惯例。确定性:每次占用都有双端对称成因——(a) 静态冲刷批分配前按路径排序(同地图 ⇒ 同排序集合 ⇒ 双端同槽序),(b) 动态(命令路径)障碍物携带服务器盖章的槽/UID,接收端只标记不分配,(c) 释放在同一锁步 tick;hint 游标仅为优化——相同占用集必然扫出相同的首个空闲槽。线程:所有变更经槽位自旋锁(位 set/clear 是对所在 uint64 字的读-改-写,并发无锁写同字会静默丢更新);现有调用点均在 game thread,锁是前瞻性防护。
Obstacle UIDs never touch the natural counter (NextGlobalUID); they are allocated from two occupied-bitmaps in the obstacle slot space [ObstacleSlotUIDBase, 2^31): spline group blocks first (segment UID = SplineGroupSlotUIDBase(slot) + index, 10000-wide blocks), then box/capsule single slots — first-free-slot allocation, the same idiom as the cellgrid / render slot arrays. Determinism: every occupation has a peer-symmetric cause — (a) static flush batches are path-sorted before allocation (same map ⇒ same sorted set ⇒ same slot sequence on every peer), (b) dynamic (command-path) obstacles carry a server-stamped slot/UID and receivers MARK, never allocate, (c) frees happen at the same lockstep tick; the hint cursor is an optimization only — identical occupied sets always yield the identical first-free scan. Threading: all mutation goes through the slot-bits spinlock (a bit set/clear is a read-modify-write on the containing uint64 word; concurrent unlocked writers on the same word silently lose updates); current call sites are game-thread, the lock is future-proofing.
| 成员 Member | 类型 Type | 说明 Description |
|---|---|---|
NumSplineGroupSlots | static constexpr int32 | 1 << 14 = 16384 个组槽,每组段宽 10000。1 << 14 = 16384 group slots × 10000 segments per group. |
SplineGroupBlockWidth | static constexpr int32 | 每组块的段 UID 宽度(10000)。Segment-UID width of each group block (10000). |
NumObstacleSlots | static constexpr int32 | 1 << 20 = 1048576 个 box/capsule 单槽。1 << 20 = 1048576 box/capsule single slots. |
SplineGroupSlotUIDBase | static FORCEINLINE int32 (int32 Slot) | 组槽的 UID 基址 = ObstacleSlotUIDBase + Slot * 10000。UID base of a group slot = ObstacleSlotUIDBase + Slot * 10000. |
ObstacleSlotToUID | static FORCEINLINE int32 (int32 Slot) | box/capsule 单槽 → UID(基址 = 组块区之后)。box/capsule single slot → UID (base sits after the group-block region). |
UIDToObstacleSlot / UIDToSplineGroupSlot | static FORCEINLINE int32 (int32 UID) | UID → 槽位反解(单槽直接相减;组槽除以块宽)。UID → slot inverse (single slots subtract directly; group slots divide by the block width). |
分配 / 盖章 / 释放 / 诊断
| 方法 Method | 返回 Return | 说明 Description |
|---|---|---|
AllocSplineGroupSlot / AllocObstacleSlot | int32 | 首个空闲槽分配,返回前置位并推进 hint;空间耗尽返回 -1(各只报错一次)。First-free allocation; sets the bit before returning and advances the hint; returns -1 when exhausted (errors logged once each). |
MarkSplineGroupSlotOccupied / MarkObstacleSlotOccupied | void | 盖章语义(命令路径接收端 / 清单重放):标记服务器已分配的槽,绝不本地分配;幂等。Stamp semantics (command-path receivers / manifest replay): mark the server-allocated slot, never allocate locally; idempotent. |
FreeObstacleUIDs | void | 按 UID 集释放(按区间分派:组槽 vs 单槽;未知区间记日志忽略),回拨 hint 使槽可复用。Obstacle.Destroy 执行与本地路径 EndPlay 调用。Free by UID set (dispatches by range: group slot vs single slot; unknown ranges logged and ignored), rewinding the hint so slots are reusable. Called at Obstacle.Destroy execution and local-path EndPlay. |
GetOccupiedSplineGroupSlotCount / GetOccupiedObstacleSlotCount | int32 const | 诊断占用计数(DIAG 探针 / 调试面板)。Diagnostic occupant counts (DIAG probes / debug panel). |
Input
见各方法签名(槽号或 UID 集合)。
See each signature (slot index or UID set).
Output
分配返回槽号(-1 = 耗尽);计数返回占用数;其余无返回值。副作用:位图置位/清位与 hint 推进/回拨,全部持槽位自旋锁。
Allocators return the slot (-1 = exhausted); counters return occupancy; the rest return nothing. Side effects: bitmap bit set/clear with hint advance/rewind, all under the slot-bits spinlock.
锁步网络模式的注册路径,仅服务器执行(客户端直接返回等待命令)。流程:① 校验 Owner、HasAuthority() 与 GameMode;② 退化样条拒绝——采样点 <2 的样条建不出段实体(客户端重绑会永久扫描双端都不存在的实体),烧组槽之前响亮报错返回;③ 服务器从障碍物槽位位图预分配(样条:AllocSplineGroupSlot 组槽号;box/capsule:AllocObstacleSlot 单槽 UID)并盖章进命令——接收端只标记盖章槽、绝不本地分配,自然计数器全程不参与;④ 按类型捕获几何——Box 用缩放后半边长(GetUnscaledBoxExtent() * Scale)、Capsule 用缩放半径/半高、Spline 用组件本地空间采样点(SampleSplineObstacleLocalPoints);⑤ 经 BuildObstacleSpawnCommandPayload 拼 Obstacle.Spawn 命令载荷(Name=ComponentPath, Int=[UID/组槽, TypeIndex, LockstepNetID, FlagBits], Float=变换+几何)经 AMassBattleNetworkGameModeBase::SendLockstepCommandToClients 下发;⑥ 调用组件 SetLockstepEntityUIDs 记录预占 UID。
Registration path for lockstep networked mode; server-only (clients return immediately and wait for the command). Flow: ① validates Owner, HasAuthority(), and the GameMode; ② degenerate-spline refusal — a spline sampling <2 points can build no segment entities (the client-side rebind would scan forever for entities that exist on no peer), so it fails loudly BEFORE burning a group slot; ③ the server preallocates from the obstacle slot bitmaps (spline: a group slot via AllocSplineGroupSlot; box/capsule: a single-slot UID via AllocObstacleSlot) and stamps it into the command — receivers MARK the stamped slot and never allocate; the natural counter is never involved; ④ captures geometry per type — Box: scaled half extents (GetUnscaledBoxExtent() * Scale), Capsule: scaled radius/half height, Spline: local-space sample points (SampleSplineObstacleLocalPoints); ⑤ builds the Obstacle.Spawn payload via BuildObstacleSpawnCommandPayload (Name = ComponentPath, Int = [UID/group slot, TypeIndex, LockstepNetID, FlagBits], Float = transform + geometry) and sends it via AMassBattleNetworkGameModeBase::SendLockstepCommandToClients; ⑥ records the preallocated UIDs on the component via SetLockstepEntityUIDs.
Input
| 参数 Param | 类型 Type | 说明 Description |
|---|---|---|
ComponentPath | const FString& | 组件完整路径,随命令下发,双端按路径解析组件。Full component path carried in the command; both peers resolve the component by path. |
TypeIndex | int32 | 0 = Box,1 = Capsule,2 = Spline。0 = Box, 1 = Capsule, 2 = Spline. |
SpawnTransform | const FTransform& | 生成变换(位置/旋转/缩放),写入命令 Float 载荷。Spawn transform (location/rotation/scale), serialized into the Float payload. |
BoxComp / CapsuleComp / SplineComp | UMassBattleObstacle*Component* | 与 TypeIndex 对应的组件指针,其余为 nullptr。Component pointer matching TypeIndex; the others are nullptr. |
Output
返回 void,但Obstacle.Spawn 命令执行结果可经 Spawn*FromCommand 返回值获知。副作用:占用一个障碍物槽位(组槽或单槽)、向所有客户端下发锁步命令、在组件上记录预占 UID;非权威端 / 槽空间耗尽 / 退化样条时无操作(已报错)。
Returns void, but the Obstacle.Spawn execution result is surfaced by the Spawn*FromCommand return values. Side effects: occupies one obstacle slot (group or single), sends a lockstep command to all clients, records the preallocated UIDs on the component; no-op for non-authority peers / exhausted slot space / degenerate splines (already logged).
联结键归一化:PIE 下完整对象路径的世界前缀每端不同(服务器 UEDPIE_0_…、客户端 UEDPIE_1_…)——原始路径跨端永不匹配。保留世界分隔符 ':' 之后、自关卡名起的子路径,双端与地图一致。三个 Spawn*FromCommand 都用它归一化 FObstacleNetBinding.ComponentPath。
Join-key normalization: under PIE a full object path's world prefix differs per peer (UEDPIE_0_… on the server, UEDPIE_1_… on the client) — raw paths never match cross-peer. Keeps the sub-path from the level name onward (after the world ':'), map-stable on both peers. All three Spawn*FromCommand builds use it to normalize FObstacleNetBinding.ComponentPath.
Input
| 参数 Param | 类型 Type | 说明 Description |
|---|---|---|
InPath | const FString& | 组件完整对象路径。Full component object path. |
Output
返回归一化后的子路径 FName。
Returns the normalized sub-path as an FName.
锁步命令模式的盒实体构建(Obstacle.Spawn 执行分支调用)。变换、缩放后半边长与标志位全部来自命令载荷(服务器注册时捕获;标志位取载荷位域 FlagBits——绝不读各端活组件,同步状态均非复制属性),实体是确定性来源——bStatic=false 强制网格处理器走实体动态分支而非读 UObject。fragment 组合:FObstacleTag + FMainType(Obstacle) + FLocating + FRotating + FBoxObstGridData + FDeterminism(UID 种子)+ FBoxObstacle(bStatic=false, BoundComponent) + FObstacleNetBinding(LockstepNetID)。构建成功后写回 Component->EntityHandle 并注册 UID 映射。
Box entity build for the lockstep command path (called from the Obstacle.Spawn execution branch). Transform, scaled half extents AND flags all come from the command payload (server-captured at registration; flags read the payload bitfield FlagBits — the live per-peer component is NEVER read for synced state, none of it being replicated); the entity is the deterministic source — bStatic=false forces the grid processor onto the entity dynamic branch instead of reading the UObject. Fragment composition: FObstacleTag + FMainType(Obstacle) + FLocating + FRotating + FBoxObstGridData + FDeterminism (UID seed) + FBoxObstacle(bStatic=false, BoundComponent) + FObstacleNetBinding(LockstepNetID). On success writes back Component->EntityHandle and registers the UID mapping.
Input
| 参数 Param | 类型 Type | 说明 Description |
|---|---|---|
Component | UMassBattleObstacleBoxComponent* | 目标组件,必须已存在(命令在组件到达前会延迟重试)。Target component; must already exist (the command retries until the component arrives). |
UniqueID | int32 | 命令携带的预分配 UID。Preallocated UID carried by the command. |
SpawnTransform | const FTransform& | 命令载荷中的生成变换。Spawn transform from the command payload. |
ScaledHalfExtents | const FVector& | 服务器捕获的缩放后半边长。Server-captured scaled half extents. |
LockstepNetID | int32 | 命令携带的联结 ID,写入 FObstacleNetBinding。Join ID carried by the command; written into FObstacleNetBinding. |
FlagBits | uint8 | 命令载荷标志位域(ObstacleFlag_*:bActivated/bInsideOut/bOverrideSpeedLimit 等)。Command payload flag bitfield (ObstacleFlag_*: bActivated/bInsideOut/bOverrideSpeedLimit etc.). |
ComponentPathName | const FName& | 组件路径名,经 NormalizeObstacleJoinPath 归一化后写入 FObstacleNetBinding.ComponentPath(恢复端联结键)。Component path name; normalized via NormalizeObstacleJoinPath into FObstacleNetBinding.ComponentPath (the restore-side join key). |
Output
返回构建是否成功;组件或 UMassAPISubsystem 无效、实体构建失败时返回 false。副作用:写回 EntityHandle、注册 UID 映射。
Returns whether the build succeeded; false when the component or UMassAPISubsystem is invalid or the entity build fails. Side effects: writes back EntityHandle, registers the UID mapping.
锁步命令模式的胶囊实体构建。变换、缩放半径/半高与标志位来自命令载荷(位域 FlagBits,绝不读活组件);fragment 组合:FObstacleTag + FMainType + FLocating + FRotating(含 Direction)+ FCollider(Height = (HalfHeight - Radius) * 2)+ FAvoiding(bCanAvoid=false) + FCapsuleObstacle(bStatic=false, bLockstepSynced=true) + FCapsuleObstGridData(bStatic=false, bIsCylinder) + FDeterminism(UID≥0 时 SeedAtSpawn) + FObstacleNetBinding(LockstepNetID)。
Capsule entity build for the lockstep command path. Transform, scaled radius/half height and flags come from the command payload (bitfield FlagBits; the live component is never read); fragment composition: FObstacleTag + FMainType + FLocating + FRotating (with Direction) + FCollider (Height = (HalfHeight - Radius) * 2) + FAvoiding(bCanAvoid=false) + FCapsuleObstacle(bStatic=false, bLockstepSynced=true) + FCapsuleObstGridData(bStatic=false, bIsCylinder) + FDeterminism (SeedAtSpawn when UID ≥ 0) + FObstacleNetBinding(LockstepNetID).
Input
| 参数 Param | 类型 Type | 说明 Description |
|---|---|---|
Component | UMassBattleObstacleCapsuleComponent* | 目标组件。Target component. |
UniqueID | int32 | 命令携带的预分配 UID。Preallocated UID carried by the command. |
SpawnTransform | const FTransform& | 命令载荷中的生成变换。Spawn transform from the command payload. |
Radius / HalfHeight | float | 服务器注册时捕获的缩放半径/半高(cm)。Server-captured scaled radius / half height (cm). |
LockstepNetID | int32 | 命令携带的联结 ID,写入 FObstacleNetBinding。Join ID carried by the command; written into FObstacleNetBinding. |
FlagBits | uint8 | 命令载荷标志位域(含 ObstacleFlag_IsCylinder 等)。Command payload flag bitfield (including ObstacleFlag_IsCylinder etc.). |
ComponentPathName | const FName& | 组件路径名,归一化后写入 FObstacleNetBinding.ComponentPath。Component path name; normalized into FObstacleNetBinding.ComponentPath. |
Output
返回构建是否成功;失败返回 false。副作用:写回 EntityHandle、注册 UID 映射。
Returns whether the build succeeded; false on failure. Side effects: writes back EntityHandle, registers the UID mapping.
锁步命令模式的样条段实体构建(每段一个实体)。本地采样点与标志位来自命令载荷(位域 FlagBits,含 ObstacleFlag_FlipDirection),段数注册时固定(LocalPoints.Num() - 1,上限受 10000 宽组块约束)。每段 UID = SplineGroupSlotUIDBase(GroupSlot) + i(组槽基址 = ObstacleSlotUIDBase + GroupSlot * 10000),fragment:FObstacleTag + FMainType + FSegmentObstGridData(i, GroupSlot) + FDeterminism + FSplineObstacle(bStatic=false, LocalSamplePoints, WorldBaseLoc/Rot, bFlipDirection) + FObstacleNetBinding(LockstepNetID)(N 段共享一个 NetID)。成功后以组槽调用 MarkSplineGroupSlotOccupied 盖章、重置 ObstacleHandles 并用全部段 UID 调用 SetLockstepEntityUIDs。
Spline segment entity build for the lockstep command path (one entity per segment). Local sample points and flags come from the command payload (bitfield FlagBits, including ObstacleFlag_FlipDirection); segment count is fixed at registration (LocalPoints.Num() - 1, capped by the 10000-wide group block). Each segment UID = SplineGroupSlotUIDBase(GroupSlot) + i (group base = ObstacleSlotUIDBase + GroupSlot * 10000); fragments: FObstacleTag + FMainType + FSegmentObstGridData(i, GroupSlot) + FDeterminism + FSplineObstacle(bStatic=false, LocalSamplePoints, WorldBaseLoc/Rot, bFlipDirection) + FObstacleNetBinding(LockstepNetID) (N segments share one NetID). On success stamps the group slot via MarkSplineGroupSlotOccupied, resets ObstacleHandles and calls SetLockstepEntityUIDs with all segment UIDs.
Input
| 参数 Param | 类型 Type | 说明 Description |
|---|---|---|
Component | UMassBattleObstacleSplineComponent* | 目标组件。Target component. |
GroupSlot | int32 | 命令携带的样条组槽号,段 UID 由 SplineGroupSlotUIDBase(GroupSlot) + i 派生。Spline group slot carried by the command; segment UIDs derive as SplineGroupSlotUIDBase(GroupSlot) + i. |
SpawnTransform | const FTransform& | 命令载荷中的生成变换,写入每段的 WorldBaseLoc/Rot。Spawn transform from the command payload, written to each segment's WorldBaseLoc/Rot. |
LocalPoints | const TArray<FVector3f>& | 服务器捕获的组件本地空间采样点;Num() < 2 时直接失败返回 false。Server-captured local-space sample points; returns false immediately when Num() < 2. |
LockstepNetID | int32 | 命令携带的联结 ID——全部段实体共享,写入各自的 FObstacleNetBinding。Join ID carried by the command — shared by all segment entities, written into each FObstacleNetBinding. |
FlagBits | uint8 | 命令载荷标志位域(含 ObstacleFlag_FlipDirection)。Command payload flag bitfield (including ObstacleFlag_FlipDirection). |
ComponentPathName | const FName& | 组件路径名,归一化后写入各段的 FObstacleNetBinding.ComponentPath。Component path name; normalized into each segment's FObstacleNetBinding.ComponentPath. |
Output
返回全部段实体是否构建成功(任一段失败即返回 false)。副作用:重建 ObstacleHandles、注册每段 UID 映射、设置组件锁步 UID 列表。
Returns whether all segment entities built successfully (false if any segment fails). Side effects: rebuilds ObstacleHandles, registers each segment UID mapping, sets the component's lockstep UID list.
命令路由障碍物网络模板——合成 TemplateKey(如 ObstacleTemplates/Box.CmdRouted,仅定原型,几何在片段里),使快照恢复端能按键找到模板(后加入端从未执行过生成命令)。
Command-routed obstacle network template — synthetic TemplateKeys (e.g. ObstacleTemplates/Box.CmdRouted, archetype-only; geometry lives in fragments) so snapshot restore can find a template on peers that never executed a spawn command (late join).
Input
| 参数 Param | 类型 Type | 说明 Description |
|---|---|---|
TypeIndex | int32 | 0 = Box,1 = Capsule,2 = Spline。0 = Box, 1 = Capsule, 2 = Spline. |
Output
返回该类型的合成模板键。
Returns the synthetic template key for the type.
幂等——把三个合成模板注册进 SnapshotTemplateRegistry(仅网络模式)。
Idempotent — registers all three synthetic templates into the SnapshotTemplateRegistry (networked mode only).
Input
无参数。
No parameters.
Output
无返回值。副作用:首次调用写入模板注册表。
No return value. Side effect: populates the template registry on first call.
恢复后宿主重绑:遍历每个存活命令路由组件,经 FObstacleNetBinding 联结重链到其重建实体(agent RebindToEntity 模式——组件未复制到则保持 bPendingRebind 由自身 tick 重试)。快照恢复流程末尾调用。
Post-restore host rebind: walks every alive command-routed component and re-links it to its rebuilt entity via the FObstacleNetBinding join (agent RebindToEntity pattern — components not yet replicated keep bPendingRebind and retry on their own tick). Called at the end of the snapshot-restore flow.
Input
无参数。
No parameters.
Output
无返回值。副作用:重建组件→实体绑定(或置位待重绑标志)。
No return value. Side effect: rebuilds component→entity bindings (or arms the pending-rebind flags).
命令载荷标志位域契约(Obstacle.Spawn 的 IntPayload[3])。
Command payload flag bitfield contract (IntPayload[3] of Obstacle.Spawn).
| 常量 Constant | 位 Bit | 说明 Description |
|---|---|---|
ObstacleFlag_Activated | 1 << 0 | 是否激活。Activated. |
ObstacleFlag_InsideOut | 1 << 1 | 盒体内外翻转。Box inside-out. |
ObstacleFlag_OverrideSpeedLimit | 1 << 2 | 覆盖速度上限。Speed-limit override. |
ObstacleFlag_IsCylinder | 1 << 3 | 胶囊按圆柱处理。Capsule treated as cylinder. |
ObstacleFlag_FlipDirection | 1 << 4 | 样条采样方向翻转。Spline sampling direction flipped. |
ObstacleFlag_IsStatic | 1 << 5 | 静态身份(2026-08-29):服务器捕获的 !bIsDynamicObstacle。静态命令路由障碍物单次注册进网格静态半区——与单机路径网络对齐(旧实现把全部命令路由障碍物强制为动态、每帧重注册)。缺位(旧命令流)= 动态,向后兼容。Static identity (2026-08-29): server-captured !bIsDynamicObstacle. Static cmd-routed obstacles register ONCE into the grid's static half — network parity with the standalone path (the old code forced every cmd-routed obstacle dynamic, re-registering per frame). Bit absent (legacy command streams) = dynamic, backward compatible. |
ObstacleFlag_ForceHybridFF | 1 << 6 | Hybrid 求解器覆盖(2026-08-29):服务器捕获自组件的 bForceHybridAgentUseForceFieldMode。Hybrid agent 看到带标志障碍物时对其保持 ForceField 求解(不因接触障碍切 ORCA);纯 ORCA agent 不受影响。缺位(旧命令流)= false,向后兼容。Hybrid-agent solver override (2026-08-29): server-captured from the component's bForceHybridAgentUseForceFieldMode. Hybrid agents that see a flagged obstacle keep the ForceField solver for it (no ORCA switch on obstacle contact); pure ORCA agents are unaffected. Bit absent (legacy command streams) = false, backward compatible. |
按 UID 集移除静态网格条目(Obstacle.Destroy 执行器 + 本地 Unregister* 调用)。盒/胶囊 UID 走 RemoveStaticObstacleByUniqueID;样条段 UID 折叠为逐组一次 RemoveStaticSegmentObstacleByGroupID(逐段全网格扫描代价过高)。任何变更后触发确定性重排序。
Static-grid removal by UID set (called from the Obstacle.Destroy executor + local Unregister*). Box/capsule UIDs hit RemoveStaticObstacleByUniqueID; spline segment UIDs collapse to one per-group RemoveStaticSegmentObstacleByGroupID (a per-segment full-grid scan is too costly). Triggers the deterministic re-sort on any mutation.
Input
| 参数 Param | 类型 Type | 说明 Description |
|---|---|---|
UniqueIDs | const TArray<int32>& | 要移除的障碍物 UID 集合。Set of obstacle UIDs to remove. |
Output
无返回值。副作用:静态半区条目移除 + 确定性重排序。
No return value. Side effects: static-half entry removal + deterministic re-sort.
从命令路由清单登记表移除组件(组件 EndPlay 调用)——命令路由销毁不走常规 Unregister* 路径,登记表需显式移除。
Removes a component from the command-routed manifest registry (called from component EndPlay) — command-routed teardown skips the normal Unregister* path, so the registry needs an explicit removal.
Input
| 参数 Param | 类型 Type | 说明 Description |
|---|---|---|
Component | const UActorComponent* | 要移除的障碍组件。The obstacle component to remove. |
Output
无返回值。副作用:从对应的 CommandRouted* 数组移除该组件。
No return value. Side effect: removes the component from its CommandRouted* array.
待处理注册的串行处理(首个 tick 由 Tick 调用):队列为空则直接置 bInitialSpawnComplete=true;否则当 UMassBattleSubsystem::bDeterministic 开启时按 ComponentPath 字典序排序,再按排序顺序逐条从障碍物槽位位图分配首个空闲槽生成实体(Box/Capsule 经 AllocObstacleSlot + ObstacleSlotToUID 单实体、Spline 经 AllocSplineGroupSlot 一簇段实体,弱指针失效的条目跳过)——排序批次序列就是跨端身份契约(同地图 ⇒ 同排序集合 ⇒ 同槽位)。最后清空队列并置完成标志。
Serial processing of pending registrations (called by Tick on the first tick): an empty queue just sets bInitialSpawnComplete=true; otherwise, when UMassBattleSubsystem::bDeterministic is on, sorts by ComponentPath lexicographically, then spawns entities in sorted order allocating first-free slots from the obstacle slot bitmaps (Box/Capsule via AllocObstacleSlot + ObstacleSlotToUID, single entity; Spline via AllocSplineGroupSlot, a cluster of segment entities; entries with invalid weak pointers are skipped) — the sorted batch sequence IS the identity contract across peers (same map ⇒ same sorted set ⇒ same slots). Finally empties the queue and sets the completion flag.
Input
无参数(数据来自成员 PendingRegistrations)。
No parameters (data comes from the member PendingRegistrations).
Output
无返回值。副作用:排序并消费全部待处理注册、消耗若干全局 UID、置位 bInitialSpawnComplete。
No return value. Side effects: sorts and consumes all pending registrations, consumes global UIDs, sets bInitialSpawnComplete.
本地(非锁步)盒障碍实体生成:一行 BuildEntity 构建——FObstacleTag + FMainType(Obstacle) + FBoxObstGridData(UniqueID, !bIsDynamicObstacle, bInsideOut, bOverrideSpeedLimit) + FDeterminism(UniqueID) + FBoxObstacle(!bIsDynamicObstacle, Component)。实体有效时注册 UID 映射。
Local (non-lockstep) box obstacle entity spawn: a one-line BuildEntity — FObstacleTag + FMainType(Obstacle) + FBoxObstGridData(UniqueID, !bIsDynamicObstacle, bInsideOut, bOverrideSpeedLimit) + FDeterminism(UniqueID) + FBoxObstacle(!bIsDynamicObstacle, Component). Registers the UID mapping when the entity is valid.
Input
| 参数 Param | 类型 Type | 说明 Description |
|---|---|---|
Component | UMassBattleObstacleBoxComponent* | 目标组件;组件或 UMassAPISubsystem 无效时直接返回。Target component; returns immediately when the component or UMassAPISubsystem is invalid. |
UniqueID | int32 | 调用方(注册路径)分配的 UID。UID assigned by the caller (registration path). |
Output
无返回值。副作用:写回 Component->EntityHandle、注册 UID 映射。
No return value. Side effects: writes back Component->EntityHandle, registers the UID mapping.
本地(非锁步)胶囊障碍实体生成,用 FMassEntityTemplateData 手动拼装:几何从组件实时读取(GetScaledCapsuleRadius / GetScaledCapsuleHalfHeight),fragment 组合与 SpawnCapsuleObstacleEntityFromCommand 相同(区别:bStatic 取 !bIsDynamicObstacle,无 bLockstepSynced)。
Local (non-lockstep) capsule obstacle entity spawn built manually via FMassEntityTemplateData: geometry is read live from the component (GetScaledCapsuleRadius / GetScaledCapsuleHalfHeight); fragment composition matches SpawnCapsuleObstacleEntityFromCommand (difference: bStatic = !bIsDynamicObstacle, no bLockstepSynced).
Input
| 参数 Param | 类型 Type | 说明 Description |
|---|---|---|
Component | UMassBattleObstacleCapsuleComponent* | 目标组件。Target component. |
UniqueID | int32 | 调用方分配的 UID。UID assigned by the caller. |
Output
无返回值。副作用:写回 Component->EntityHandle、注册 UID 映射。
No return value. Side effects: writes back Component->EntityHandle, registers the UID mapping.
本地(非锁步)样条段实体生成:段数按 SampleDist > 0 用 CeilToInt(总长 / SampleDist) 计算,否则取点数减一(闭环绕回点数);NumPoints < 2 直接返回;段数受 10000 宽组块上限约束。每段一个实体,UID = SplineGroupSlotUIDBase(GroupSlot) + i,fragment:FObstacleTag + FMainType + FSegmentObstGridData(i, GroupSlot) + FDeterminism(段 UID) + FSplineObstacle(!bIsDynamicObstacle, Component);句柄加入 Component->ObstacleHandles 并逐个注册 UID 映射。
Local (non-lockstep) spline segment entity spawn: segment count is CeilToInt(total length / SampleDist) when SampleDist > 0, otherwise point count minus one (point count for closed loops); returns immediately when NumPoints < 2; the count is capped by the 10000-wide group block. One entity per segment, UID = SplineGroupSlotUIDBase(GroupSlot) + i; fragments: FObstacleTag + FMainType + FSegmentObstGridData(i, GroupSlot) + FDeterminism(segment UID) + FSplineObstacle(!bIsDynamicObstacle, Component); handles accumulate into Component->ObstacleHandles and each registers its UID mapping.
Input
| 参数 Param | 类型 Type | 说明 Description |
|---|---|---|
Component | UMassBattleObstacleSplineComponent* | 目标组件。Target component. |
GroupSlot | int32 | 组槽号,段 UID 由 SplineGroupSlotUIDBase(GroupSlot) + i 派生。Group slot; segment UIDs derive as SplineGroupSlotUIDBase(GroupSlot) + i. |
Output
无返回值。副作用:生成一簇段实体、填充 ObstacleHandles、注册每段 UID 映射。
No return value. Side effects: spawns a cluster of segment entities, fills ObstacleHandles, registers each segment UID mapping.
成员变量 Members
世界缓存:惰性 getter 首次命中时记录当前世界。
World cache: records the current world on the first lazy getter hit.
UMassAPISubsystem 惰性缓存。
Lazily-resolved UMassAPISubsystem cache.
UMassBattleSubsystem 惰性缓存。
Lazily-resolved UMassBattleSubsystem cache.
待处理注册队列:组件 BeginPlay 注册时压入(持 RegistrationLock),首个 tick 由 ProcessPendingRegistrations 排序、生成并清空。
Pending registration queue: appended during component BeginPlay (under RegistrationLock), sorted/spawned/emptied by ProcessPendingRegistrations on the first tick.
命令路由障碍物组件(late-join 清单来源)。Register* 在路由选中命令路径时 AddUnique 填充;组件 EndPlay 时由 RemoveCommandRoutedObstacle 清理。弱引用——GC 安全。
Command-routed obstacle components (the late-join manifest source). Populated by Register* via AddUnique when routing selects the command path; cleaned by RemoveCommandRoutedObstacle at component EndPlay. Weak — GC-safe.
初始生成完成标志。置位后 IsTickable() 返回 false,后续注册走立即生成分支(AllocNextUID + 立即 spawn)。
Initial-spawn-complete flag. Once set, IsTickable() returns false and later registrations take the immediate-spawn branch (AllocNextUID + immediate spawn).
保护 PendingRegistrations 的临界区,使 BeginPlay 注册与首个 tick 处理并发安全。
Critical section guarding PendingRegistrations, keeping BeginPlay registration concurrent-safe with first-tick processing.
槽位位图状态:按两空间位数分配的 uint64 字数组(Initialize 定容零初始化);hint = 最低可能空闲下标(扫描起点,释放时回拨,仅优化)。全部持槽位自旋锁 ObstacleSlotBitsLock(std::atomic<bool> exchange-acquire / store-release,与 FEntityFlagFragment 的标志锁同一惯例——临界区仅几次字操作,故不用 FCriticalSection)访问。另含两个"耗尽已报错"标志 bSplineGroupSlotExhaustedLogged / bObstacleSlotExhaustedLogged(各只报错一次)。
Slot bitmap state: uint64 word arrays sized to both spaces' bit counts (fixed-capacity, zero-initialized in Initialize); the hints are the lowest possibly-free index (scan start, rewound on free, optimization only). All access under the slot-bits spinlock ObstacleSlotBitsLock (std::atomic<bool> exchange-acquire / store-release, the same idiom as FEntityFlagFragment's flag lock — every critical section is a handful of word operations, hence no FCriticalSection). Also carries the two exhaustion-once flags bSplineGroupSlotExhaustedLogged / bObstacleSlotExhaustedLogged (errors logged once each).
管线角色 Pipeline Role
在 MassBattle 管线中的位置(调用关系均经 grep 验证):
① 障碍组件 BeginPlay(MassBattleObstacleBoxComponent.cpp:38 / Capsule / Spline 同位置)调用 Register*Obstacle 注册;统一路由把锁步会话送入服务器 RequestLockstepObstacleSpawn(槽位预分配 + 下发 Obstacle.Spawn 命令),单机入队等首个 tick 排序槽位冲刷;EndPlay 走 Unregister* 归还槽位并销毁实体。
② Obstacle.Spawn 命令由 MassBattleNetworkGameStateBase 的锁步命令执行分支消费(MassBattleNetworkGameStateBase.cpp:820/834/852 调用 Spawn*ObstacleEntityFromCommand;组件未复制到达时命令延迟 ExecuteTick + 3 重试)。
③ 生成出的障碍实体(带 FBoxObstGridData / FCapsuleObstGridData / FSegmentObstGridData)由 MassBattleGridUpdateProcessor::RebuildObstacleGrid 每帧读入 hash grid(RegisterObstacleCell,GridUpdateProcessor.cpp:379/537/626);静态盒停用还会调用 RemoveStaticObstacleByUniqueID(GridUpdateProcessor.cpp:329)。
④ UID 映射经 RegisterUniqueIDEntity 双写进 UMassBattleSubsystem::RegisterUIDEntity 全局注册表。
⑤ Obstacle.Destroy 命令执行器(MassBattleNetworkGameStateBase.cpp:1046)调用 RemoveStaticGridEntriesByUIDs(静态半区移除 + 确定性重排序),槽位经 FreeObstacleUIDs 归还;本地路径 EndPlay 的 Unregister* 同样归还槽位并移除静态条目。
⑥ 快照应用末尾 MassBattleNetworkSubsystem(MassBattleNetworkSubsystem.cpp:3410)调用 RebindAllCommandRoutedObstacles,把存活命令路由组件经 FObstacleNetBinding 联结重链到重建实体;组件 EndPlay 经 RemoveCommandRoutedObstacle 从清单登记表移除。
Position in the MassBattle pipeline (all call relations grep-verified):
① obstacle component BeginPlay (MassBattleObstacleBoxComponent.cpp:38, same spot for Capsule/Spline) calls Register*Obstacle; unified routing sends lockstep sessions down the server-side RequestLockstepObstacleSpawn path issuing the Obstacle.Spawn command (with slot preallocation), while standalone play queues for the first-tick sorted slot flush; EndPlay goes through Unregister* to free slots and destroy entities.
② the Obstacle.Spawn command is consumed by the lockstep-command execution branch of MassBattleNetworkGameStateBase (MassBattleNetworkGameStateBase.cpp:820/834/852 call Spawn*ObstacleEntityFromCommand; when the component has not replicated yet, the command retries at ExecuteTick + 3).
③ the spawned obstacle entities (carrying FBoxObstGridData / FCapsuleObstGridData / FSegmentObstGridData) are read into the hash grid every frame by MassBattleGridUpdateProcessor::RebuildObstacleGrid (RegisterObstacleCell, GridUpdateProcessor.cpp:379/537/626); a deactivated static box also triggers RemoveStaticObstacleByUniqueID (GridUpdateProcessor.cpp:329).
④ UID mappings dual-write into the global registry via RegisterUniqueIDEntity → UMassBattleSubsystem::RegisterUIDEntity.
⑤ the Obstacle.Destroy command executor (MassBattleNetworkGameStateBase.cpp:1046) calls RemoveStaticGridEntriesByUIDs (static-half removal + deterministic re-sort), with slots returned via FreeObstacleUIDs; the local-path EndPlay Unregister* likewise frees slots and removes static entries.
⑥ at the end of snapshot apply, MassBattleNetworkSubsystem (MassBattleNetworkSubsystem.cpp:3410) calls RebindAllCommandRoutedObstacles, re-linking alive command-routed components to their rebuilt entities via the FObstacleNetBinding join; component EndPlay removes entries from the manifest registry via RemoveCommandRoutedObstacle.
补充说明 Notes
头文件中 // Counter for generating unique obstacle IDs 注释下方的计数器成员已被移除——障碍物 UID 现在绝不触碰自然计数器(NextGlobalUID),一律从障碍物槽位空间 [ObstacleSlotUIDBase(= 2^30), 2^31) 的两张占用位图分配(本地排序冲刷、命令路径服务器预分配两条路径都是如此)。样条段 UID 约定为 SplineGroupSlotUIDBase(组槽) + 段号(组槽基址 = ObstacleSlotUIDBase + 组槽 * 10000,块宽 10000),本地与命令路径一致。
The counter member under the // Counter for generating unique obstacle IDs comment in the header has been removed — obstacle UIDs now never touch the natural counter (NextGlobalUID); they are always allocated from the two occupied-bitmaps of the obstacle slot space [ObstacleSlotUIDBase (= 2^30), 2^31) (in both the local sorted-flush and the command-path server-preallocation paths). Spline segment UIDs follow the SplineGroupSlotUIDBase(group slot) + segment index convention (group base = ObstacleSlotUIDBase + group slot * 10000, 10000-wide blocks), consistent between the local and command paths.
BuildObstacleSpawnManifest(late-join 障碍物生成清单,快照捕获点重建 Obstacle.Spawn 命令)已随 ObstacleSpawnManifest 一起移除(2026-08-28)——命令路由障碍物现为快照公民,随 Entities 走,不再需要清单重放。BuildObstacleSpawnCommandPayload(protected)现由 RequestLockstepObstacleSpawn 专用,载荷新增 NetID 与标志位(IntPayload[3])。
BuildObstacleSpawnManifest (the late-join obstacle spawn manifest rebuilt at snapshot capture) was removed together with ObstacleSpawnManifest (2026-08-28) — command-routed obstacles are snapshot citizens now and ride Entities, no manifest replay needed. BuildObstacleSpawnCommandPayload (protected) is now used solely by RequestLockstepObstacleSpawn; the payload gained the NetID and the flag bits (IntPayload[3]).