赞 | 65 |
VIP | 231 |
好人卡 | 2 |
积分 | 19 |
经验 | 35171 |
最后登录 | 2024-9-15 |
在线时间 | 1554 小时 |
Lv3.寻梦者
- 梦石
- 0
- 星屑
- 1912
- 在线时间
- 1554 小时
- 注册时间
- 2013-4-13
- 帖子
- 917
|
加入我们,或者,欢迎回来。
您需要 登录 才可以下载或查看,没有帐号?注册会员
x
本帖最后由 沉滞的剑 于 2019-10-29 22:46 编辑
今天来聊一聊存档的话题,
众所周知在RM中存档和读档是由StorageManager完成的
但其实StorageManager底层使用的是RM拓展的JsonEx完成的
这次我们就主要研究其中JsonEx.parse和JsonEx.stringify两个方法
依照惯例, 只粘贴有意义代码并且做可读性上的修改
0.当我们在讨论存档的时候我们在讨论什么
存档的主要目的是让玩家可以回到游戏的某一时刻的状态继续游戏
虽然可以把内存中的所有状态直接用快照的方式保存下来
但是更可靠的方式是将有用的信息以一定格式存储起来
不是所有信息都是必要的, 也不是所有信息都是有用的
对于RM来说只要保存几个游戏对象就足够了
- DataManager.makeSaveContents = function() {
- // A save data does not contain $gameTemp, $gameMessage, and $gameTroop.
- var contents = {};
- contents.system = $gameSystem;
- contents.screen = $gameScreen;
- contents.timer = $gameTimer;
- contents.switches = $gameSwitches;
- contents.variables = $gameVariables;
- contents.selfSwitches = $gameSelfSwitches;
- contents.actors = $gameActors;
- contents.party = $gameParty;
- contents.map = $gameMap;
- contents.player = $gamePlayer;
- return contents;
- };
复制代码
1.序列化
这些都是Game_XXX的实例, 我们知道这些都是对象, 对象的结构是树状的
而contents其实就是这颗巨大的树的根
对于存储来说, 我们把这棵树转换成JSON格式的字符串就可以了
然而这样就遇到了两个问题:
1.如何处理原型链
2.如何处理循环引用
对于1的解决方法是将原型的constructor的记录下来
不过有一些特殊的类比如Date, Function这些类很难处理,
所以不做修改是无法复原原来的对象的
对于2的解决方法是将每个引用对象记录下来
如果遇到重复的对象, 则指向第一次遇到时候的引用
因为我们遍历这颗树的时候采取的遍历方式都是一致的
所以这个引用其实就是一个索引就可以了
下面首先来看一下我们的编码入口方法(和RM原版略有区别)
- const stringify = value => {
- // 初始化循环引用数组
- const circular = [];
- // 编码, 详情请阅下文
- encode(value, circular);
- // 将编码后的对象序列化
- const string = JSON.stringify(value);
- // 还原编码对象
- restoreObject(value, circular);
- // 返回字符串
- return string;
- };
复制代码
注意编码的操作是在原对象上进行的, 得到序列化后的字符串后需要还原
不过还是先来看看编码的部分,
这是一个深度优先遍历, 我们必须能保证能访问到所有子节点
但又不需要访问原型链里的东西(__proto__)
- for (const key in value) {
- encode(value[key], circular);
- }
复制代码
首先对于JS来说数据类型总共分为两种, 基础类型和引用类型
在内存中基础类型存储的是它的值, 而引用类型存储的是指向值的指针
对于我们来说分别就是布尔值, 数字, 字符串, null, undefined等等和对象,数组
我们写一个简单的方法来判断一个变量的类型是什么
RM中判断得更细, 比如是不是数组, 是不是纯对象等等, 其实只需要分为两大类即可
- const isPrimitive = o => o !== Object(o);
复制代码
当我们序列化基础类型的时候只需要将它的值记录下来就可以成为可识别的JSON的值
而对于引用类型我们如果是第一次遇到, 我们要把他记录到数组里, 并且记录它的类型名
否则就只保留一个它在数组里的索引, 我们不需要再对它的属性进行重复遍历了
我的circular比RM的简洁了一些, 只用了数组索引, 不过原理是一致的
- const encode = (value, circular) => {
- if (!isPrimitive(value)) {
- circular.push(value);
- value["@constructor"] = value.constructor.name;
- value["@circularID"] = circular.length - 1;
- for (const key in value) {
- if (circular.includes(value[key])) {
- value[key] = { "@reference": value[key]["@circularID"] };
- } else {
- encode(value[key], circular);
- }
- }
- }
- };
复制代码
然后我们还需要还原被修改过的对象,
我把RM中的两步合并成一步: 1.去掉标签, 2.还原引用
- const restoreObject = (value, circular) => {
- if (!isPrimitive(value)) {
- delete value["@constructor"];
- delete value["@circularID"];
- for (const key in value) {
- const circularID = value[key]["@reference"];
- if (circularID !== undefined) {
- value[key] = circular[circularID];
- } else {
- restoreObject(value[key], circular);
- }
- }
- }
- };
复制代码
这个时候circular里的引用对象还是精确的, 所以直接替换掉索引即可
2.反序列化
反序列化的步骤其实是类似的
- const parse = string => {
- const value = JSON.parse(string);
- const circular = [];
- decode(value, circular);
- return value;
- };
复制代码
在解码的时候我们需要还原原型链的信息
首先根据名称我们可以从顶级对象中获取继承的类名
这就有几个需要考虑的问题
1. 通过匿名定义的类没有名字
2. 忘记设置 Class.prototype.constructor = Class 的类没有名字
3. 自定义的类往往因为不想污染全局变量而有自己的命名空间
这几个情况就是导致存档读取后数据错误, 丢失和报错的原因
解决方法有:
1. 使用具名类,或者设置Class.prototype.constructor
2. 修改decode方法, 让decode可以在一个命名空间数组内寻找类
替代原型链的方法可以使用Object.setPrototypeOf
RM还对不支持这个方法的浏览器做了polyfill
- const decode = (value, circular) => {
- if (!isPrimitive(value)) {
- circular[value["@circularID"]] = value;
- // 下面这一句可以改写来支持独立命名空间的类的访问
- const constructor = window[value["@constructor"]] || Object;
- Object.setPrototypeOf(value, constructor.prototype);
- delete value["@constructor"];
- delete value["@circularID"];
- for (const key in value) {
- const circularID = value[key]["@reference"];
- if (circularID !== undefined) {
- value[key] = circular[circularID];
- } else {
- decode(value[key], circular);
- }
- }
- }
- };
复制代码
注意对于meta数据的清理我也合并为一步完成了
最后附上一段最后的序列化和反序列化的代码,
支持可以通过registerNamespace来添加反序列化时侯对应的命名空间
- const namespaces = [window];
- const registerNamespace = namespaces.push.bind(namespaces);
- const isPrimitive = o => o !== Object(o);
- const encode = (value, circular) => {
- if (!isPrimitive(value)) {
- circular.push(value);
- value["@constructor"] = value.constructor.name;
- value["@circularID"] = circular.length - 1;
- for (const key in value) {
- if (circular.includes(value[key])) {
- value[key] = { "@reference": value[key]["@circularID"] };
- } else {
- encode(value[key], circular);
- }
- }
- }
- };
- const stringify = value => {
- const circular = [];
- encode(value, circular);
- const string = JSON.stringify(value);
- restoreObject(value, circular);
- return string;
- };
- const restoreObject = (value, circular) => {
- if (!isPrimitive(value)) {
- delete value["@constructor"];
- delete value["@circularID"];
- for (const key in value) {
- const circularID = value[key]["@reference"];
- if (circularID !== undefined) {
- value[key] = circular[circularID];
- } else {
- restoreObject(value[key], circular);
- }
- }
- }
- };
- const parse = string => {
- const value = JSON.parse(string);
- const circular = [];
- decode(value, circular);
- return value;
- };
- const getConstructor = name => {
- const constructor = namespaces.find(namespace =>
- namespace.hasOwnProperty(name)
- )[name];
- if (constructor) {
- return constructor;
- }
- return Object;
- };
- const decode = (value, circular) => {
- if (!isPrimitive(value)) {
- circular[value["@circularID"]] = value;
- const constructor = getConstructor(value["@constructor"]);
- Object.setPrototypeOf(value, constructor.prototype);
- delete value["@constructor"];
- delete value["@circularID"];
- for (const key in value) {
- const circularID = value[key]["@reference"];
- if (circularID !== undefined) {
- value[key] = circular[circularID];
- } else {
- decode(value[key], circular);
- }
- }
- }
- };
- // 以下是测试
- {
- const obj = {};
- obj.self = obj;
- obj.array = ["a", "b", obj.self];
- const string = stringify(obj);
- console.debug(parse(string));
- }
复制代码
|
|