书签持久化修复

昨天 13:28综合
修复内容总结
问题 1: 书签 key 不匹配(上轮已修改,本轮确认)
HandleBookmarkLoaded 触发时 dataFile_ 可能为 nil,getBookmarkKey() 返回 "default" 导致找不到存在 "stories.ch0_yuanqi" 下的数据
修复:HandleBookmarkLoaded 只缓存全量云端数据,新增 applyCloudBookmark(key) 在 dataFile_ 就绪后再按 key 提取
问题 2: initScene(1) 覆写云端书签(本轮修复)
PuppetTheater.Start() 中 initScene(1) 调用 saveBookmark(),将 {scene:1, line:1} 写入云端,覆盖了正确的 {scene:1, line:9}
修复:添加 initializing_ 标志位,初始化期间 saveBookmarkToCloud() 直接跳过;initScene(1) 和 resume 完成后解除锁定,再统一调用 saveBookmark() 保存最终正确状态
问题 3: resumeToBookmark 未更新 currentLine(本轮修复)
函数执行后 story_.currentLine 仍然是 1 而非 targetLine,导致后续任何 saveBookmark() 调用都会把 line:1 存到云端
修复:在 resumeToBookmark 中添加 story_.currentLine = targetLine
一、洋葱式排错法
本次修复经历了 5 层嵌套问题,每修一层才暴露下一层:
第1层  clientCloud 为 nil(多人模式设计如此)
  ↓ 改用 RemoteEvent + serverCloud
第2层  RemoteEvent 被引擎丢弃("Discarding not allowed")
  ↓ 补 RegisterRemoteEvent
第3层  书签 key 错位("default" vs "stories.ch0_yuanqi")
  ↓ 拆分缓存与应用时机
第4层  initScene(1) 覆写云端数据(line:9 → line:1)
  ↓ 添加 initializing_ 保护期
第5层  resumeToBookmark 不更新 currentLine
  ↓ 补 story_.currentLine = targetLine
方法论:不要期望一次修完。修一层 → 构建 → 用户实测 → 观察日志 → 定位下一层。
二、三条核心原则
原则 1:日志是唯一真相
阶段 关键日志 揭示的问题
第2层 WARNING: Discarding not allowed remote event 41A4D64F 事件未注册
第3层 HandleBookmarkLoaded: key=default, cloudBm=nil key 在数据到达时尚未就绪
第4层 Saving via RemoteEvent: 43 bytes(刷新后 line=1) initScene 触发了保存
每次交付都要带足够的 print,尤其是异步回调的入口和关键变量值。
原则 2:异步时序是状态 bug 的根源
时序问题模式:
  数据到达时机 ≠ 消费者就绪时机
本次案例:
  HandleBookmarkLoaded 先于 PuppetTheater.Start() 触发
  → dataFile_ 为 nil → getBookmarkKey() 返回 "default"
  → 云端数据找不到对应 key
解法——"缓存-延迟应用"模式:
lua
复制
-- 数据到达时:只缓存,不处理
function HandleBookmarkLoaded(...)
    cloudBookmarks_ = allData        -- 缓存全量
    if 消费者已就绪 then
        applyCloudBookmark(key)      -- 就绪则立即应用
    end
end
-- 消费者就绪时:检查缓存是否已到
function initBookmarkCache()
    if cloudLoaded_ and cloudBookmarks_ then
        applyCloudBookmark(key)      -- 数据已到,直接用
    else
        loadBookmarkFromCloud()      -- 数据未到,主动请求
    end
end
无论谁先到,都能正确工作。
原则 3:初始化期间禁止副作用写入
危险模式:
  初始化函数内部调用了"保存"逻辑
  → 用初始默认值覆盖了持久化的真实数据
本次案例:
  Start() → initScene(1) → saveBookmark() → 把 line=1 写入云端
  → 覆盖了用户真正的进度 line=9
解法——"初始化保护期"模式:
lua
复制
initializing_ = true
initScene(1)         -- 内部的 saveBookmark 被阻止写云端
resumeToBookmark()   -- 恢复到正确位置
initializing_ = false
saveBookmark()       -- 解锁后,用最终正确状态统一写入
三、排查清单(可复用)
遇到"数据刷新后丢失"类问题时,按此顺序排查:
□ 1. 传输层:数据能否到达服务端?
     → 查日志有无 "Discarding" / "rejected" / 网络错误
□ 2. 注册层:事件/通道是否双端注册?
     → RegisterRemoteEvent 必须客户端和服务端都调用
□ 3. 寻址层:存和取用的 key 是否一致?
     → 打印保存时的 key 和加载时的 key,逐字对比
□ 4. 时序层:加载回调触发时,消费者是否已就绪?
     → 打印回调触发时的关键上下文变量是否为 nil
□ 5. 覆写层:初始化流程是否意外写入了默认值?
     → 在 save 函数中打印调用栈或加保护标志
□ 6. 一致性层:内存状态与持久化状态是否同步?
     → story_.currentLine 是否等于 bookmark 中的 line
四、一句话总结
异步系统的状态 bug,90% 出在"谁先到"和"谁覆盖了谁"。用缓存解决前者,用保护期解决后者。