FastArray:DirtyItem是状态提示不是可靠事件
自动关联目录:FastArray:DirtyItem是状态提示不是可靠事件
核心判断:Fast Array 的职责是让每个连接最终收敛到最新集合状态。
MarkItemDirty不是一次可靠业务事件;缓存TArray元素裸指针、混用 Key 宽度或把回调当 Exactly Once 消息,都会在规模变化和晚加入时暴露。
一、业务冲突:脚本属性需要动态 Key,又不能整表复制
创作者脚本会在运行时添加、修改和删除键值。普通复制整个 Map 会随属性总量增长;RPC 可以发送每次变化,却不能自动恢复晚加入客户端的当前状态。Fast Array 很适合传输集合 Delta,但前提是本地容器先满足稳定身份和结构变更契约。
二、实际实现:Delta 方向正确,索引实现破坏了存储稳定性
一个通用实现用 Fast Array Item 保存 Key + 字节数组,写前比较旧值,新增、修改、删除分别标脏,并在复制回调中派发脚本事件;外层还采用 Push Model。它解决了整表复制,也为 Add/Change/Remove 提供了统一入口。
但为了快速查找,它把 Key 映射到 TArray 元素裸指针。数组扩容会搬迁全部元素,RemoveAllSwap 又会交换尾元素,旧指针随即失效;结构变化后却没有稳定重建全部指针。接口使用更宽整数 Key,Item 中却存更窄 Key,还会产生截断与碰撞。任意字节值没有 Type、Schema 和长度契约,用错误类型读取只能得到静默错误。
另一份实现按值类型拆 Fast Array,使用 Key 查找 Index,并在读取前校验字节长度。这证明不缓存元素地址、显式类型边界在现有技术栈内可行,值得作为升级方向。
三、Fast Array 不是事件日志
同一 Item 在一次网络发送前连续修改多次,客户端可能只观察到最终值;暂时不相关的连接重新进入时收到的是当前集合,不是离开期间的每次变化。复制回调表达“本地状态如何收敛”,不保证每个业务动作 Exactly Once。需要逐次消费的奖励、扣费或触发事件必须使用带序号、去重和确认的事件协议。
四、目标设计:稳定 Key、索引重建、状态与事件分流
struct StateItem : FFastArraySerializerItem {
uint32 key;
ValueType type;
uint16 schemaVersion;
uint32 stateRevision;
ByteArray payload;
};
void Upsert(Key k, TypedValue v) {
int32 i = IndexByKey.FindOrAddIndex(k, Items);
Items[i].Assign(v);
MarkItemDirty(Items[i]);
MarkOuterPropertyDirtyIfRequired();
}
void Remove(Key k) {
int32 i = IndexByKey[k];
Items.RemoveAtSwap(i);
MarkArrayDirty();
ReindexMovedItem(i); // Map 保存 Index,不保存元素地址
}Key 在 API、Item 和 Map 中使用同一宽度;结构变更后只更新受影响 Index,或在小集合中完整重建。值必须携带类型与 Schema,反序列化先验长度、版本和上限。若外层属性启用 Push Model,要按所用引擎版本验证并执行外层属性脏标记,不能假设内部数组键一定唤醒属性复制。
五、决策与验证
状态使用 Fast Array;必须逐次处理的事件使用有界序列日志或可靠命令/回执。测试应覆盖数组多次扩容、Swap Remove、Key 边界、同帧多次改写、晚加入、离开相关性后重入、丢包重传、Schema 不匹配和超大 Payload。指标包括 Delta 字节、全量初始字节、Item 数、Payload 分布、重建/索引耗时、反序列化拒绝数和事件去重数。