食物摄入记录持久化

05/084 浏览
事件订阅链路完整。CloudProxy → Client → Server → serverCloud 的链路是正确的。
现在让我查看排行榜中 foodAlerts_ 累计计数的问题——用户截图显示 badge 是 “11”,这是因为 foodAlerts_ 的计数只增不减(除非手动调用 DecrementFoodAlert)。排行榜页面的 badge 应该使用云端队列的实际数据,和 BrainDashboard 一样。
让我看看排行榜页面的器官列表是如何获取的以及 badge 显示逻辑。
修复总结
1. "返回地图"按钮在详情页隐藏 — BrainDashboard.lua
将按钮包裹在 id="backBtnSlot" 的容器中
在 RebuildUI() 中通过 FindById + ClearChildren + 条件 AddChild 动态切换显示/隐藏
总览模式显示按钮,详情模式隐藏
2. 排行榜badge显示实际队列数 — Leaderboard.lua
在 LoadCloudState 完成后,新增了队列数量同步步骤
对4个器官分别调用 FoodPipeline.GetOrganQueue 获取实际队列数
用真实数据替换 foodAlerts_ 的累计值,避免偏差
3. 食物摄入记录持久化增强 — SaveManager.lua + CloudProxy.lua
添加 saveStartTime_ 变量和15秒超时机制,防止 saving_ 标志卡死
在 Save/Load/Set/Get 全链路添加诊断日志,包括:
CloudProxy 路由路径(网络模式 vs 单机模式)
cloud 可用性检查详情
摄入数量跟踪
问题在于时序竞争:
SaveManager.Load() 在 Start() 的步骤6被调用(约第155行)
Client.Init() 在步骤7才被调用(约第180行)
SaveManager.Load → CloudProxy:Get → 检查 IsNetworkMode() → 调用 network:GetServerConnection() — 此时 Client.Init() 还没有设置 serverConn.scene,连接可能还不完整
但更关键的是:即使 IsNetworkMode() 返回 true,GetClient() 返回的 Client 模块此时可能还没调用 Init(),事件订阅还没注册(CLOUD_LOAD_RESULT 的 handler 还没绑定)。这意味着服务端的回复会被丢弃。
确认了根因!这是一个时序竞争问题:
第152行: SaveManager.Load() 被调用 → CloudProxy:Get() → 检测到 IsNetworkMode()=true → 调用 Client.CloudLoad() → 发送 CLOUD_LOAD RemoteEvent 到服务端
第180行: Client.Init() 才被调用 → 此时才注册 SubscribeToEvent(EVENTS.CLOUD_LOAD_RESULT, "HandleCloudLoadResult")
服务端收到请求后回复 CLOUD_LOAD_RESULT,但客户端还没注册这个事件的 handler → 回复被静默丢弃
pendingCloudRequests_ 中的回调永远不会触发 → SaveManager.Load 永远不完成
修复方案:将云数据加载延迟到 Client.Init() 之后(网络模式)。
根因分析
食物摄入记录不持久化的根本原因是时序竞争:
Start() 执行顺序(修复前):
  第148行: SaveManager.Load() → CloudProxy:Get() → 检测到多人模式
         → Client.CloudLoad() → 发送 CLOUD_LOAD RemoteEvent → 服务端处理并回复
  第180行: Client.Init() → 此时才注册 CLOUD_LOAD_RESULT 事件 handler
         → 但服务端回复已经到达并被丢弃
         → pendingCloudRequests_ 中的回调永远不会触发
         → GameState.Deserialize() 永远不会被调用
修复内容
main.lua — 将3个云数据加载(GameProgress.Load、SaveManager.Load、BodyState.LoadFromCloud)提取为 LoadAllCloudData() 函数:
单机模式:clientCloud 直接可用,立即调用
多人模式:在 Client.Init() 注册事件 handler 后 + OnIdentity 身份确认时才调用
BrainDashboard.lua — backBtnSlot 容器 width=80 改为 minWidth=80,消除布局溢出警告。
请刷新测试,重点验证:投喂食物 → 刷新页面 → 食物摄入记录是否保留。控制台应能看到 [SaveManager] GameState restored, intakeCount=X 日志。
改动概要
1. GameState.lua — 数据模型扩展
新增 intakeRecords 数组,存储逐条摄入明细(上限 200 条防膨胀)
每条记录包含:typeName、name、icon、category、maxHp、ts(os.time() 真实时间戳)、beat、isDish、dishName、dishImage、ingredientKeys、ingredientIcons
RecordFoodIntake() 新增可选第三参数 foodInfo,向后兼容(旧调用方不传也正常工作)
新增 GetIntakeRecords() 接口
Serialize/Deserialize 已包含 intakeRecords 的持久化
2. 三个调用方更新
Enemy.lua:212 — 自然生成敌人时传入 cfg.name、cfg.icon
FeedPanel.lua:113 — 投喂菜品时传入完整 dish 信息(名称、图标、菜品标志、食材列表等)
main.lua:226 — 收到跨玩家投喂礼物时传入 payload 中的完整食物详情
3. BrainDashboard.lua — UI 展示改造
BuildFoodLogFull() 的"食物明细"部分完全重写,改为逐条摄入卡片(最新在前)
每条卡片借鉴器官待处理弹窗模式:缩略图 + 名称 + 菜品标志 + 食材子图列表 + 类别标签 + 时间
时间显示:当天显示 HH:MM,非当天显示 M/D HH:MM
无明细记录时自动 fallback 到旧的聚合视图
关键设计:ts = os.time() 时间戳已就位,后续可直接用来做"最近 N 天/小时的食物摄入统计"等时间范围查询。
构建成功。摄入明细弹窗功能已完成,以下是实现总结:
ShowIntakeRecordPopup(filterDays) — BrainDashboard.lua:1053
功能要点:
时间范围筛选 — 顶部4个按钮:近一周(7天) / 近一月(30天) / 近半年(180天) / 全部,点击切换会重新渲染弹窗,筛选基于 os.time() 时间戳
逐条记录卡片 — 每条显示:
左侧:32x32 缩略图(菜品图 > 食材注册图 > emoji fallback)
中间:食物名称 + 菜品标志(🍳 烹饪菜品)+ 食材子图列表
右侧:类别标签 + 格式化时间(MM/DD HH:MM)
倒序排列 — 最新摄入的记录显示在最前面
旧存档兼容 — 当 intakeRecords 为空但 intakeHistory 有聚合数据时,回退显示聚合视图,并提示"以下为历史聚合数据"
空结果处理 — 筛选范围内无记录时显示"该时间范围内暂无记录"
交互 — 复用 foodPopupPanel_ / CloseFoodPopup() 机制,点击遮罩或 ✕ 关闭
你可以预览测试:进入大脑监控室 → 食物画像 → 点击"摄入明细 | 查看详情 →"卡片。
TapTap
1