前段时间发了一篇利用文件摘要简化游戏资源的引用管理,后来在知乎专栏的评论里与 Jare Guo 同学在这个话题上有一些后续的讨论 (见此页面),俺觉得这个讨论挺有收获。对这方面感兴趣的同学来说,也许是一个参考,所以放到一起发上来。如果大家觉得有错误,补充,以及更好的想法,欢迎指正。


__Jare Guo [2015-11-09 18:05 星期一]__

非常棒的一篇文章,但我心里有些不同的看法,欢迎指正:
path:md5 方案,经不起移动路径的同时修改资源。这造成的后果是修改资源的人,一定要在每一步操作后,回编辑器中让编辑器刷新下,再进行下一步操作。如果编辑器未启动,或未监听改动,提交上去后 reference 就会失效,所有人更新下来都会失效。

而一旦发生这种事情,在 git 中是查不到的,因为没有明显的出错的特征。(如果用 uuid,meta file 出错了一看就知道)

这也注定了 path:md5 的方案无法适应大型项目的团队配合,它要求所有人必须在一个大工程里,因为"修改资源的人"每次有操作必须帮助所有"使用资源的人"来提交新的 reference。(这个人可能仅仅有一个子 repo 的提交权限,也不关心其它团队把资源用在了什么地方。)

同理,path:md5 也无法满足资源包导出的需求。

所以我觉得 uuid 虽然坑,但还是有不可取代的地方……


__Gu Lu [2015-11-09 22:00 星期一]__

嗯,多谢分享你的思考。首先肯定一下你的基调,文件摘要肯定不是包治百病的万灵丹,只是在特定场合可以发挥作用的一个工具,存在局限也是必然的,比如说循环引用就会出问题——导致循环地刷摘要。当然也可以通过策略或机制去回避,而这里的重点在于某种启发而不是限制——如何去运用这个工具。

我们一条一条来看:

“修改资源的人,一定要在每一步操作后,回编辑器中让编辑器刷新下,再进行下一步操作。”

这是不必的。更准确的描述是,“普通的原地更新,无需顾忌引用者;只有当对资源 rename/move 后 紧接着要继续更新该资源 时,才需要立即刷新。” 换句话说,路径一致的话可以不考虑 md5,而平常制作阶段,普通的原地更新是 80% 的情况。

有人可能会说,“只要路径一致就认为是同一个资源,这不严谨啊,如果把资源挪走,用另一个替换掉,还用这个名字,不就引用错了吗” 想想文件系统吧,你在桌面建了个快捷方式指向 C:\foo.txt,现在你把这个文件挪到 D 盘,而把另一个 bar.txt 重命名成 foo.txt 放到 C:\ 冒充,这时候你双击桌面的快捷方式,还指望它能自动找到已经到了 D 盘的那个文件,这就不科学了吧,如果一定要实现这个需求,就需要更高级的工具或机制。

“一旦发生这种事情,在 git 中是查不到的,因为没有明显的出错的特征”

不太明白你说的 “引用失效在 git 里查不到” 是什么意思,引用者内含的明文路径天然可以帮助定位有问题的资源,到那个资源所在的路径看一下历史记录就知道谁动过了这个资源。当然,也可能是我误解了你到意思。

明确地讲,当资源 rename/move 后,由于引用者含有明文的路径,当引用失效时,你可以在编辑器中弹框提示 "C:\foo\bar.txt" 找不到 (就如同操作系统中找不到文件时发生的那样) 当然也可以借助摘要去全库范围搜一下,以一些时间开销来换取可能的自动更新。

“path:md5 的方案无法适应大型项目的团队配合,它要求所有人必须在一个大工程里,因为"修改资源的人"每次有操作必须帮助所有"使用资源的人"来提交新的 reference”

这是很典型的“把一整套解决方案 (此例中的编辑器) 中的单个机制提出来,批评它解决不了一般性的工作流问题”的观点。需要注意,工作流问题千变万化,需要不同的策略,机制和约束在一起发挥作用。我只点明一点,所有的资源引用的基础仍是路径,md5 的作用是当路径失效时“潜在的”自动发现和重定位可能的目标资源,目的是在大多数情况下,能辅助开发者去自动地批量地解决这类失效。并不是说有了这个就可以乱搞了,这个逻辑我认为不难理解——你的 foo.exe 依赖同目录下的 foo.config 才能正常启动,现在你一声不吭把 foo.config 删了,还要求 foo.exe 完全正常,臣妾做不到啊 ( foo.config :“怪我咯” )。

“path:md5 也无法满足资源包导出的需求”

chunked md5 恰恰在文件包内单个或多个资源更新时非常有用,可以有效地与增量更新配合,把引用自动更新到增量更新包上去。所以说到这一句就可以明白地看出我们思路的不同,我更关注的是“这个技术在特定的情况下能帮到我们什么”,而不是一个笼统的“这个技术无法满足需求”

技术是为程序员服务的,而程序员才是为需求服务的,不要跳过中间的步骤哦 O(∩_∩)O~~


__Jare Guo [2015-11-09 23:23 星期一]__

我又来了;)

路径一致的话可以不考虑 md5,而平常制作阶段,普通的原地更新是 80% 的情况。

md5 改变后,是没什么大问题。但如果不到编辑器里把所有原先引用的 md5 也同步刷新的话,下次如果有人在不知情的情况下直接移动这个文件,那么原来的引用就会面临 md5 和 path 同时失效的情况。

因此就会鼓励资源生产者在每次修改后,养成刷新 path/md5 的习惯。

但如果这样一来,就又会面临另一个囧况,美术一旦修改了一张图片,可能好几个场景都要刷新 md5,一方面容易往场景的版本管理中加入了无用的历史提交,另一方面很容易产生冲突!

PS: 正如你说的,如果这几个场景还有别人也引用到了,那么它们的 md5 也得跟着刷新……

一旦发生这种事情,在 git 中是查不到的,因为没有明显的出错的特征

(这个问题是我碰巧想到,太钻牛角尖了。)
path:md5 出错时确实可以在编辑器里面给出各种提示,但如果单纯 review 他人的工作,是没办法像 meta file 那么简单的能够发现问题的。

  • meta file 只要保证和源文件一起移动,并且 uuid 保持不变就行。
  • 而 path:md5,哪怕你看到一个美术修改了若干资源,并且顺带改了一堆别的资源,但你无从判断那里面有哪些 md5 是失效的。

我只点明一点,所有的资源引用的基础仍是路径,md5 的作用是当路径失效时“潜在的”自动发现和重定位可能的目标资源

没错,但这个机制赖以生存的前提是 md5 的及时更新,如果不按照我说的,资源"生产者"负责任(越权?)的帮资源"使用者"去更新 md5,那么这个机制就不太靠谱。

path:md5 也无法满足资源包导出的需求

抱歉我的意思并不是为了做增量更新,我指的是要做资源的动态载入。

举例:一旦我从网上购买了一个资源包,当这个包的作者很良心的发布更新时,我会发现我的场景也要跟着升级。

  • 如果这时我没有把场景的升级也提交到 git,下回同事就会帮我把这件事情给做了,结果两个人的 git 就会冲突……
  • 如果我开心的把新场景提交到了 git,但策划正好在这个场景,他就可能会揍我……
  • 如果这个包的作者发布了两次更新,我跳过中间的一次 rename,直接升级到了最新版(改 md5),那么就有可能丢失资源……
  • 如果这个包的作者只发布了一次更新,但这个更新用了他不少心血,不但做了 rename,还改了 md5,那么他的更新会害死很多人,而他自己可能不会意识到……

说白了,资源包就是很典型的资源生产者,和资源消费者互不关心的使用场景,这个场景中使用 path:md5 不太合适,我也没说非要用这个不可,只是想到就说了一下。

我更关注的是“这个技术在特定的情况下能帮到我们什么”,而不是一个笼统的“这个技术无法满足需求”

双手赞成~ 我只是提出了一些 path:md5 不太适合的应用场景,也没说这个技术没有可取之处。事实上如果能把 path:md5 在内网服务器里做一个版本缓存,或者全项目组的人共同维护一份日志文件,那我上述的大部分问题都可以解决。


__Gu Lu [2015-11-10 10:19 星期二]__

嗯,看到你最后提到的方案 ("如果能把 path:md5 在内网服务器里做一个版本缓存,或者全项目组的人共同维护一份日志文件"),我觉得可以少打不少字就能说清楚了。:) 在你的 git repository 内的任何一个引用者内含的外部文件的摘要,都可以对应到该文件的某个历史版本。也就是说,只要你愿意,拿着这个 md5,你可以像拿着 uuid 一样。而比 uuid 的恒定唯一更进一步,你甚至可以定位到特定的一个时间点。

就拿你说的第一个例子来说吧,假设 C:\foo.txt 的摘要是 12345,现在你在 C:\bar.txt 内有前者的引用 "C:\foo.txt:12345"。不管你对 foo.txt 做什么操作,能计算出 12345 这个摘要的 foo.txt 肯定存在于你的 git history 的这个文件的某个时间点上,这就是我为什么一直强调不必主动刷的根本原因。(因为如果你需要,你总能找到它的) 这也就谈不上后续的纯为了刷 md5 去提交了。
从本质上讲,path:md5 对应到你某个资源曾经存在过的某个快照,利用好这一点,能提供比 uuid 更大的价值和更小的负担。

发布问题是一个有必要仔细说一下的问题。

当你发布代码的时候,如果你的 v1.0 版有一个接口 int foo() { return 0; } 那么 v1.1 版你是不会随意删掉或者重命名这个接口的,因为这是你跟客户的协议,就算这个更新用了你再多的心血,你也不能随意地破坏这种约定,对吧。

现在你发布了一个资源包,内含 pack/a, pack/b, pack/c 三个资源,客户已经用在自己的项目里了。如果你出于某种非做不可的理由把 a 重命名成 d,当然需要通知所有的客户:“新版本里 a 已经被换成 d 了” 升级的人有理由注意到他们依赖的资源发生了这种变化,从而选择更新还是不更新。所以通常更好的选择是不要破坏与用户的约定 pack/a, pack/b, pack/c,如果是重大的改动,提供 pack-1.1/a 就可以了,这就是我说的增量更新。

你当然可以说有了 uuid 我怎么挪都无所谓,只要保证跟着挪 uuid 就行,但是,“有这么大的自由度”未必意味着“在与客户的约定中随意利用这种自由度”是合理的。更何况,如果你真正能利用好 “path:md5 对应到你某个资源曾经存在过的某个快照” 这个特性,作为迷你的版本标识符,它能提供给你比 uuid 更大的灵活度,和更小的负担。


欢迎大家再进一步讨论这个问题。文件摘要的想法,我前不久才设计和引入资源管理系统,肯定存在一些未考虑妥当之处。考虑到我目前应用的范围和规模,一些大规模团队的协同问题短期内还不太会遇到,如果有同学能指出潜在的问题,我们就可以有机会做更深入的思考和更细致的考虑,也能形成更健壮的方案。

Gu Lu
2015-11-14

Comments
Write a Comment

知识共享许可协议
本作品由Gu Lu创作,采用知识共享Attribution-NonCommercial-NoDerivatives 4.0 国际许可协议进行许可。