前段时间发了一篇利用文件摘要简化游戏资源的引用管理,后来在知乎专栏的评论里与 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

Comments
Write a Comment

Tags

随笔   游戏开发   Programming   C/C++   优化   Unity   C++   知乎   游戏设计   比特币   Unity3D   区块链   软件开发   Bitcoin   引擎设计   系统架构   Production   idtech   中国文化   加密货币   项目管理   游戏评论   资源管理   资源流水线   效率   道德经   网络   方法论   模板编程   Blockchain   Lua   Blockchain Computing   Oculus   GDC   渲染   VR   PerfAssist   Unity MemoryProfiler   BCH   经济学   信息过载   行业报告   字体   Productivity   图形   网络编程   Dice   协程   EMC   Premake   测试   中间件   Game Engine   新手引导   区块链游戏   Methodology   CI   命令行解析   goroutine   ndk   Ethereum   nanomsg   自动化   Scripting   摘录   Debugging   同步技术   cppcon   C++模板   DOOM3   技术评估   Unity GC   C++11   学习方法   Surface Pro 3   Engine Evaluation   CRT   文化   笔记   golang   图形编程   多线程   ETH   Bitcoin Cash   cppcon14   Bitcoin SV   Visual Studio   Unity Coroutine   跨语言可变参数列表   团队协作   货币   Deployment   Visual Assist   工程改进   Michael Abrash   exp   开放世界   量子计算   域名   虚拟现实   系统重构   slua   遮挡剔除   完美转发   协作式调度   Modern C++   类型推导   Memory Debugging   个人成长   小故事   BTC   暴雪   产品   历史   错误处理   Unity Profiler   MOD  

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