首页 / 资讯中心 / 文章详情

Lunar Tear 快照系统(Snapshot)深度剖析:按任务场景保存与恢复用户状态

Lunar Tear 快照系统(Snapshot)深度剖析:按任务场景保存与恢复用户状态 ★ FEATURED ARTICLE
【免费下载链接】lunar-tearPrivate server research project for a certain discontinued mobile game项目地址https://gitcode.com/gh_mirrors/lu/lunar-tear点击查看免费下载Lunar Tear 快照系统是这座私有服务器项目里最容易被忽视、却最保命的机制集合。它覆盖四个层面任务场景级的上下文快照打完任意任务后精确回到原进度、整账号的用户状态快照可导入 SQLite 数据库、主数据目录快照运行时热重载以及数据库文件级备份启动向导自动留档。本文带你从零理解这四套存盘/读档机制各自何时触发、如何工作、在哪些源码位置实现让新手也能安全地回滚与迁移自己的角色数据。一、为什么需要快照先搞懂项目的数据结构Lunar Tear 把所有玩家进度都收敛在一个结构体UserState上等级、体力、宝石、装备Costumes/Weapons、队伍Decks、任务进度Quests、抽卡状态、礼包……几乎每个字段都带一个LatestVersion时间戳。这份活数据在每次 RPC 结束时会被差异写回 SQLite只更新真正变化的表见 save.go 中的diffAndSave。而快照正是围绕这份数据做的三件事临时存一份、持久存一份、热切换一份。┌───────────────────────────────────────────────┐ │ 用户状态 UserState内存活数据 │ │ │ │ 1. 任务场景快照 SavedQuestContext毫秒级临时存 │ │ 2. JSON 快照文件 → import-snapshot 导入库 │ │ 3. 目录快照 Catalogs → 主数据热重载原子切换 │ │ 4. game.db.bak → 向导自动备份整库文件 │ └───────────────────────────────────────────────┘ 记住这条主线内存数据 → 四种快照 → 各自的恢复入口后文逐一拆解。二、核心玩法按任务场景保存与恢复SavedQuestContext这是标题里按任务场景的主角也是整个系统里最精巧的设计。场景为什么打完一个任务需要读档游戏里你可以从主线任务列表菜单直接点开一个已经通关过的任务重玩menu-replay。问题来了重玩过程中主线的场景指针SceneId、路线RouteId、赛季SeasonId、剧情笼PortalCage等进度字段都会被临时改写。如果打完不恢复玩家就会迷路在世界地图的奇怪位置上。Lunar Tear 的解法非常直接——开打前存一份打完原子地恢复时机动作源码位置从菜单点开已通关任务snapshotMainQuestIfNeeded()把 8 个关键字段存入SavedContextquest.go任务结束HandleQuestFinish检测到SavedContext.Active逐字段写回并清空快照quest.go快照本身就是一个 8 字段的小结构体定义在 types.gotype SavedQuestContext struct { Active bool CurrentQuestSceneId int32 HeadQuestSceneId int32 CurrentMainQuestRouteId int32 MainQuestSeasonId int32 IsReachedLastQuestScene bool PortalCageInProgress bool CurrentQuestFlowType int32 }注释原文写得很清楚snapshots player state when entering a menu-replay... On finish, every field is restored atomically so the player returns to the exact pre-replay state.进入菜单重玩时快照玩家状态……结束时逐字段原子恢复让玩家回到重玩前的精确状态。落库快照字段如何存进数据库这 8 个字段并不只是内存里的东西而是真实持久化在user_main_quest表里saved_ctx_active、saved_ctx_current_quest_scene_id等 9 列由两个 goose 迁移完成20260508094826_add_saved_quest_context.sql —— 一次性加入 8 列20260512055406_add_saved_ctx_current_quest_flow_type.sql —— 后补saved_ctx_current_quest_flow_type这意味着即使服务器在重玩中崩溃重启后快照仍在恢复逻辑照样成立——这正是它比纯内存暂存更可靠的原因。细节为什么要IfNeededsnapshotMainQuestIfNeeded第一行就检查if user.MainQuest.SavedContext.Active { return }——已有激活快照就不覆盖。这防止了重玩套重玩时把外层场景覆盖掉。而 quest.go 中可以看到只有isMenuPick从菜单直接点开这条路径会触发快照主线自然推进isMainFlow和正式 ReplayFlow 都不会因为它们本来就该移动场景指针。⚠️ 常见误区有人以为每次开始任务都存档、每次结束都读档。实际上只有菜单重玩已通关任务这一种场景会走快照——正常推主线、打副本都是直接前进不回滚。三、整账号快照用 import-snapshot 一键导入用户状态如果说上面的机制是游戏内自动存读档那么import-snapshot工具就是给运维者准备的手动读档器把一个 JSON 格式的完整用户状态文件灌进数据库。3.1 快照长什么样快照文件就是一个store.UserState的 JSON 序列化如snapshots/scene_1.json包含用户的全部装备、武器、任务进度等字段。导入时会打印一条汇总日志方便核对parsed user 12 (uuidxxxx, costumes340, weapons512, characters28, quests4096)3.2 一条命令完成导入核心逻辑只有 60 来行位于 main.go流程是读文件 →json.Unmarshal到UserState→ 补全内部 Map → 覆写Uuid→ 调用ImportUser整表替换。cd server make import SNAPSHOTsnapshots/scene_1.json UUIDyour-client-uuid或直接调用工具go run ./cmd/import-snapshot \ --snapshot snapshots/scene_1.json \ --uuid your-client-uuid \ --db db/game.db参数默认值说明--snapshot必填JSON 快照文件路径--uuid必填分配给导入用户的 UUID必须与游戏客户端认证时发出的 UUID 一致--dbdb/game.dbSQLite 数据库路径关键点在 user.go 的ImportUser它会替换该UserId在数据库中的全部数据是幂等的整户覆盖而不是增量合并。 实战场景你想用别人导出的满配角色快照开局只需把 UUID 换成自己客户端的即可——这也是新手服里送号最常用的方式。配合 README 里的claim-account工具还能把旧账号名直接认领到新客户端。四、数据库文件级备份wizard 自动留档 交互恢复前两层快照都是逻辑快照最底层还有一张安全网整个db/game.db文件的物理备份。4.1 每次启动向导自动备份backup.go 在每次运行./wizard时执行VACUUM INTO把数据库一致性拷贝为带时间戳的db/backups/game.db.UTC时间戳.bak并自动清理旧档、只保留最近 10 份backupRetainN 10。VACUUM INTO是 SQLite 官方推荐的热备份方式——即使有连接存在也能产出一份完整、自洽的库文件。4.2 一键回滚到任意一份备份cd server make restore这会启动 wizard-restore列出db/backups/下所有.bak文件 → 选择 → 二次确认明确提示该备份之后的进度会丢失→ 复制覆盖db/game.db并清掉-wal/-shm残留。层次粒度触发方式恢复入口SavedQuestContext8 个主线场景字段菜单重玩已通关任务自动任务结束时自动恢复JSON 用户快照单用户全部数据手动导出/保存 JSONmake import数据库备份全库全用户wizard 启动时自动make restore五、另一种快照主数据目录的热重载Holder顺带一提snapshot 一词在项目里还有一个重要含义——只读的目录快照。holder.go 中的Catalogs被注释为 *an immutable snapshot of every catalog... A newCatalogs is built from scratch on every reload and atomically published。每次通过管理 webhook/api/admin/master-data/reload需要设置LUNAR_ADMIN_TOKEN触发热重载时从磁盘重新解析assets/release/20240404193219.bin.e从零构建一份全新的*Catalogs用atomic.Pointer一次性原子换入gRPC 服务每个 RPC 开头调Get()拿到始终一致的视图顺手os.Chtimes更新文件 mtime让客户端版本号变化、自动重新下载资源。这属于多版本快照切换的经典写法不锁、不停服、老请求继续用旧快照跑完新请求立刻看到新数据。六、源码地图按任务场景快照相关的文件都在这关注点文件路径快照触发与恢复核心逻辑server/internal/questflow/quest.goSavedQuestContext结构定义server/internal/store/types.go快照字段落库写入/差异更新server/internal/store/sqlite/save.go数据库迁移9 个 saved_ctx 列server/migrations/20260508094826_add_saved_quest_context.sqlJSON 快照导入工具server/cmd/import-snapshot/main.goImportUser整户替换实现server/internal/store/sqlite/user.go启动自动备份server/cmd/wizard/backup.go交互恢复工具server/cmd/wizard-restore/main.go主数据目录快照 Holderserver/internal/runtime/holder.goMakefile 命令import/restore/migrateserver/Makefile版本演进记录docs/CHANGELOG.md七、给普通用户的三条实用建议跑wizard前别删db/backups/那 10 份.bak是你唯一能时光倒流的地方make restore全靠它们导入快照前先记下 UUID--uuid必须和你客户端认证时上报的一致错一位字符就读档失败make import缺参数时会给出明确报错照提示补上即可重玩任务后场景跳回去了不是 Bug如果你发现主线指针回到了重玩前的位置那正是SavedQuestContext在正常工作——它的设计目标就是重玩结束后回到出发前的精确状态。结语Lunar Tear 的快照体系是一个教科书式的分层设计用8 字节的场景上下文解决高频的游戏内回滚用JSON 全量快照解决账号迁移用VACUUM INTO 文件备份兜底整个库再用原子目录快照支撑不停服热更新。理解这套机制后无论是日常维护自己的私服、导入他人进度还是排查世界地图位置错乱一类的疑难杂症你手里都有了对应的读档按钮。赞分享【免费下载链接】lunar-tearPrivate server research project for a certain discontinued mobile game项目地址https://gitcode.com/gh_mirrors/lu/lunar-tear点击查看免费下载相关推荐1Panel快照管理系统状态保存与恢复1Panel快照管理系统状态保存与恢复 概述 在现代服务器运维中系统状态的备份与恢复是确保业务连续性的关键环节。1Panel作为新一代Linux服务器运维管后端前端运维可观测性Redux DevTools Extension状态快照功能保存与恢复关键调试场景Redux DevTools Extension状态快照功能保存与恢复关键调试场景 你是否遇到过这样的困境在调试Redux应用时好不容易复现了一个复杂的状前端开发工具调试器如何 5 分钟跑通 Qwerty Learner单词与打字一起练的免费打字练习软件如何 5 分钟跑通 Qwerty Learner单词与打字一起练的免费打字练习软件 上礼拜帮实习生配环境他盯着终端里转圈的 npm install 问我有没前端教育创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
阅读完成 · 觉得有帮助?
咨询建站