## 一、逻辑与表现分离

①、逻辑层仅处理数据，准备好用于渲染的数据。

②、表现层对已有的数据，进行插值表现，且不会改动数据。

③、两者的帧率可以不同。

## 二、时间相关

①、客户端通过Ping包方式预测服务器的战斗时间。

②、RTT计算方式,发一个Ping包,包含发送时的本地时间戳，服务器接收到以后，返回这个时间，客户端接收到后，用最新的本地时间戳减去这个时间，就可以得到RTT。

③、RTT计算时，服务器还会返回一个服务器的战斗时间，取最低RTT的一半，加上服务器返回的战斗时间，即可以估算为战斗时间。

④、多次模拟，越来越接近服务器的战斗时间。

## 三、预输入

①、非锁步同步中，客户端先于服务器运行，比如快个3帧。

②、理想状态下，服务器因为有收包缓冲在，每帧都能收到每个客户端的输入。

③、但网络延迟高时，客户端发送的输入，可能超过了服务器的收包缓冲。

④、所以基于RTT的计算，在延迟高时：

客户端会增大预输入的帧数，比如原本发送超过服务器3帧的输入，改成6帧。

服务器也会增大收包缓冲，在收到最新的同帧号输入时，会替换之前的输入。

缓冲可以动态调整。

⑤、一般预输入只处理预测失败概率低的操作，比如持续移动。

⑥、客户端冗余发送

## 四、预测

①、客户端先行于服务器，所以需要预测其他客户端的行为。

②、一般基于连续输入原则，比如连续的移动输入等。

③、对于高敏感操作，一般不进行预测。

## 五、回滚

①、在本地帧输入数据与服务器发送过来的帧输入不一致时，发生回滚。

②、找到最近可用的快照，进行推进。

③、逻辑层回滚技巧：

基于帧索引的可序列化数据，比如随机数状态，属性等。

针对于大型的，序列化成本较高的数据，可记录每帧变动的指令，在回滚时，逐帧逐条回滚这些指令。

④、表现层回滚技巧：

表现层一般设计为可插值，而非多帧状态累加。

有一些效果，可等到逻辑帧确认后再表现。

插值回滚，而不是瞬间表现到位。

⑤、ECS回滚参考：

Entity/Component不增删时，拷贝整个Entity/Component的数据块。

Entity删除时，可标记为禁用，等确定不回滚后再删除，增加时，操作记录回退。

Component增删时，操作记录回退。

## 六、追帧

①、落后太多时，可选择最近目标帧的快照进行处理。

②、一般会设置当前帧可用于追帧的时间，避免追帧卡顿。

## 七、快照

①、较大的间隔生成全量快照，比如1秒。

②、较小的间隔生成增量快照，记录基于全量快照的变动。

③、OOP中，一般通过标签，或者内建数据结构类实现序列化、反序列化、数据变动记录。

④、ECS中，一般关注Entity数量、Component数量和数据即可。

⑤、序列化一般使用自定义二进制，且数据一般为整数，如果对大小有要求，可以参考整数压缩方案。

## 八、日志

①、常规日志，比如输入，行为，随机数，属性等。

②、自动日志，大致实现思路：

遍历所有涉及逻辑的代码文件，通过正则表达式,查找到所有的有值类型参数的方法。

生成记录代码，包括宏开关，记录方法ID(方法名经过映射后的ID),参数数量,每个参数大小的代码和记录值的泛型接口代码。

③、哈希相关：

每次记录可算哈希，每帧可算哈希，多帧哈希可叠加。

哈希计算是可能会有冲突的，尽可能的降低计算成本与哈希冲突。

④、不一致定位，定位哈希不同的帧和定位同帧下的不同步点。

## 九、不同步

①、基于日志定位。

②、可能的原因：

使用了浮点数相关的模块，比如内置数学API、物理模拟、动作、自定义库等。

使用了字典，遍历顺序不一致。

多线程操作数据先后，执行结果处理先后。

“我”的开发角度，使得逻辑倾向于其中一方。

工具类、单例类有未纳入序列化的数据。

## 十、定点数
①、选择合适精度，这涉及计算性能与内存开销。

②、开方，快速近似查表代替循环。

③、反三角函数，查表代替泰勒展开（循环）。

## 十一、多世界

①、尽量不要使用有状态（数据）的单例类。

②、可以存在无状态（数据）的静态类，工具类，辅助类等。

## 十二、其他

①、帧同步中，没有秒的概念，只有帧的概念，秒的相关数据都需要转换成帧。

②、逻辑帧中的延迟模块，一定是由逻辑帧去轮询，比如自定义协程、延迟模块等。

③、客户端和服务器都有帧缓冲，客户端快照也会有缓冲，以减少内存使用。

④、预测帧数高时，预输入帧数可对应降低。