分类目录归档:Java

OpenShader月报 #1

TL;DR: 弃坑啦!!!

好吧这是开玩笑的,虽然沉寂了很长时间,怎么看都像是"好难啊不做了"的样子,但项目确实还在继续开发中.本来说好1月或2月发一个预览版本,然而实际上...实际上从1月初到3月初这段时间我基本没怎么碰OpenShader的代码,一方面是家里有事,另一方面当时我在摸鱼两个即兴项目(还特么是两个??),等想起来"哟我还有一个大坑没填"时已经是3月中旬了,所以说现在写一篇月报其实挺符合现实情况.

那么,首先是关于之前开的那些坑填的状况:

  • 池化VBO&MultiDraw (90%)
    池化VBO和MultiDraw已经基本完成了,目前现存的问题是内存占用稍微有点大,似乎还存在内存颠簸的问题,不过这些都是可以优化的. 从表现来看,池化VBO是相当成功的,砖块渲染的开销一下子从之前的第一骤降到了第三第四,甚至低于天空渲染和云渲染的开销...(谁想得到天空渲染怎么会有那么大的开销??),其实这也不意外,一个池化的VBO能承载16个Chunk(是的,16x256x16的那玩意,不是16x16x16的RenderChunk)的顶点数据,并用1次绑定加1到4次DrawCall(取决于这些Chunk的位置,大多数情况下是1~2次,极端情况会是4次)将它们全部渲染出来,在过去这需要16次绑定和16次DrawCall,而且实际情况会比这还要多 --- RenderChunk只有16x16x16,在山地之类的立体地形中渲染1个Chunk需要渲染多个RenderChunk --- 不过也有可能比这少,毕竟有些RenderChunk可能不可见.但无论如何,总体开销都不可能比池化VBO+MultiDraw要低. 在实现VBO内存池时我遇到了个吔屎的问题,我忘了OpenGL中大部分DrawCall类指令的偏移量单位是顶点而不是字节...这意味着顶点大小不同的顶点数据不能存入同一个内存池,因为数据的偏移量必须是顶点大小的整倍数,否则在DrawCall中没法指定偏移量,而顶点大小不同的数据存在一起时对齐会变得非常麻烦,因此最后我设计成使用多个内存池来存储16个Chunk的数据,每个内存池只存储特定一种大小的数据,问题解决了,虽然浪费的内存变得更多了... 不过还有一个小问题是现在KHR_Debug_Callback返回的Debug信息中经常会狂刷"Pixel-path performance warning: Pixel transfer is synchronized with 3D rendering." 主要发生在内存池扩容删除旧缓存时,这是什么鬼...
  • 指令队列 (100%)
    指令队列顺利完工了,但是表现的却不是很好,性能感觉提升了不到10%...而且Debug变得异常艰难,因为报错后只知道是在哪种指令中出错了,甚至都不知道是哪个Stage提交的哪个指令...不过也不意外,Nvidia在AZDO(Approaching Zero Driver Overhead)那篇presentation中提到他们用软件实现的指令队列的性能提升也只有14%. 现在只能期待后继的优化中将尽可能多的工作塞到指令准备时并行执行了,毕竟目前渲染准备阶段占用的时间也挺多的,如果能砍掉这部分时间(无论是优化还是并行执行)那对性能也是有很大的提升.
  • Early-Clear (0% 蛤蛤蛤)
    这一个完全没弄,主要是暂时没有特别大的需求,但今后肯定会有的.

正在进行的工作/已完成的其他工作:

  • 清理临时代码 (90%)
    花了几天清理了很多临时代码,主要是ASM那方面,之前在开发时遇到需要修改MC代码的情况时,就把MC代码复制到项目中的同名类里,利用Java类加载顺序的特性来实现覆盖原始代码.现在用ASM彻底重写了这部分了,为即将到来的开源做好了准备. 至于其他方面的临时代码(大量的TODO) 慢慢解决吧...
  • 采样器 (100%)
    采样器决定了着色器如何从纹理中获取纹素,在过去(去年12月的版本)其实已经有采样器的原型了,但是只能用来控制着色器读取哪些纹理,而不能控制着色器怎么读取,现在开发者可以设置采样器的参数了,包括纹理过滤、各向异性过滤、Wrap、Mipmap以及是否开启比较模式.采样器有什么用呢? Shadersmod中启动shadowHardwareFiltering其实就是开启Shadow map的比较模式. 采样器还有两个版本的实现,一个是针对低版本的传统模式,通过glTexParameter修改纹理的采样参数来实现;另一个是针对拥有ARB_sampler_object扩展的高版本,通过采样器对象来实现.
  • 自定义Uniform (50%)
    虽然OpenShader内置了不少Uniform参数,但毕竟总有我想不到的时候,因此总得有一种方法能让光影包开发者自行设定Uniform,目前的解决方案是让开发者可以添加一个自定义Uniform,然后监听更新事件并手动更新Uniform内容,这样的设计有两个缺陷,一个是只有Mod式的光影包能写代码来处理事件监听,另一个是MinecraftForge的居然要求事件总线的注册要在Mod初始化中进行,否则就会抛一个警告信息...我在考虑要不要设计一种DSL让开发者通过简单的表达式来描述需要获取的内容;或者利用Java的ScriptEngine内置JavaScript的特性,让开发者写JS脚本去获取数据.不过这些都无法回避一个问题,就是运行时Minecraft的字段都是混淆的...如何访问这些被混淆的字段还是个问题.
  • 图像统计 (25%)
    简单地说,图像统计是用来获取渲染结果中的某些信息,比如平均亮度(用于HDR)或中央深度(用于DOF)什么的,这个操作主要面临的问题是如何将获取到的结果再递交回去,比如说将统计到的结果再注入到一个Uniform里,这是最简单的方式,不过会涉及到CPU-GPU同步的停顿,对于这个问题,国产的开源引擎KlayGE的解决方案是使用计算着色器来统计亮度,然后将结果写入一个纹理的固定位置(DX的左上角(0,0)),后续处理时就直接从此纹理的固定位置读数据就行了,整个操作可以一气呵成,无需CPU从GPU那里读回数据,顶多有必要的话加一个屏障保证HDR发生在亮度统计之后即可,写入纹理在OpenGL中对应的操作是Image Load Store,不过假如让我来实现这个的话,我可能会选择SSBO,因为Image Load Store到4.2才进入核心扩展,而4.3就有了SSBO了... 嗯,然后你问没有计算着色器的辣鸡该怎么办? 把纹理慢慢读回内存然后让CPU慢慢跑吧,同步的停顿想着就感人...
  • 兼容Shadersmod光影包 (60%)
    听上去像是一个很不可思议的事情,但考虑到OpenShader在设计时就是具有高度可配置性,那么Shadersmod的光影包不就是一种特殊格式的OpenShader光影包吗(笑),这个月我主要就是在弄这个,期间也找出并解决了OpenShader不少问题.目前的情况是已经能跑我在光影包教程中写的那个MyFirstShader了(不过还存在一些小问题),至于别的...我试了一下SEUS,看上去能跑,但是总感觉有些差别,特别是炫光效果经常会闪烁,估计是纹理采样参数有差别;体积云也会有断层,估计是噪音图的Wrap没有设置成REPEAT而导致;流体砖块的Attribute注入也跪了,滑稽 23333
  • 无尽的Bug... (1%)
    还有很多问题需要修复,有些是缺乏边界条件时的处理,正常使用没问题,但如果使用姿势稍有不对就 --- DUANG! 还有一些就是彻头彻尾的Bug,比如原版MC的RenderChunk载入是使用一个优先队列,距离玩家最近的未加载RenderChunk永远是会被最优先处理的,这保证了玩家在移动时自己身旁的RenderChunk总是被加载出来的,而现在这个设定貌似坏掉了,变成了一个先入先出队列,结果就是当玩家都已经跑进未加载区域了,那边还在吭哧吭哧地加载旧区块...
  • 开源! \o/ \o/ \o/
    说好了的开源项目,怎么迟迟没有开源呢? 最大的阻力:临时代码已经被消灭了,我打算等Shadersmod兼容差不多实现了后就传到Github上,啊当然是创一个新仓库了,我怎么会把现在的私有仓库中羞耻的旧代码亮出来! (笑)

差不多就是这些,我去肝毕设去了(又是弃坑一段时间的节奏?),学校突然把毕设报告从6月提到了5月,药丸药丸啊 ?

4.26更新

基本能跑SEUS 10.1了,除了没有DOF (因为还没实现图像统计) 但是有个奇怪的问题就是亮的吃屎...想不出这是什么原因啊...

4.28更新

能跑SEUS10.2了,SEUS11.0也差不多不过水面有些问题.10.2屏幕过亮的问题是eyeBrightness弄错了,此外还修掉了一些奇怪的问题,不过依然有一些光影会跪的比较惨,群众喜闻乐见的Chocapic13 V6天空渲染会莫名其妙地坏掉.

另外还有一个问题可能要在很久之后修复,甚至是不修复...就是矩阵精度问题,Minecraft一直是直接用glTranslate、glRotate之类的GL函数直接操作OpenGL矩阵,而OpenShader则提供了一个封装(通过ASM魔改1.8新增的GlStateManager来实现,具体原理篇幅有限先跳过了 233),对矩阵的操作会先缓存在应用层,到下一次DrawCall之前再通过glLoadMatrix更新到驱动,这个可以降低GL函数调用的开销,然而带来的问题是OpenShader的矩阵精度好像差了一点点...

这"一点点"对常见的矩阵(模型视图矩阵和投影矩阵)没有影响,但对法线矩阵(gl_NormalMatrix,还记得我在Shadersmod教程的附录里提到的计算方法吗?模型视图矩阵的左上3x3的逆的转置)有着微妙的影响,由GL函数得到的模型视图矩阵算出的法线矩阵在乘完法线后,似乎能近乎神奇地保证法线的单位长度依然保持在1左右,至少是小于等于1;而OpenShader的矩阵呢,乘完后长度稍微大了那么一丁点...非常非常小的一丁点,但在某些鲁棒性较差的算法里就会跪了,比如说我的那个Shadersmod教程里的MyFirstShader...

MyFirstShader里用到了一个将vec3格式的法线编码成vec2格式的算法:

vec2 normalEncode(vec3 n) {
vec2 enc = normalize(n.xy) * (sqrt(-n.z*0.5+0.5));
enc = enc*0.5+0.5;
return enc;
}

...

vec2 normal = normalEncode(gl_NormalMatrix * gl_Normal);

最要命的地方在于那个sqrt(-n.z*0.5+0.5),它假定法线的长度一定不超过1,因此z一定是介于[-1,1]的,由此它才可以放心大胆地计算sqrt(-n.z*0.5+0.5)而不用担心-n.z*0.5+0.5小于0时sqrt蹦个NaN,但是由于矩阵精度问题,这里的法线长度超过1了,于是n.z=1.00001, -n.z*0.5+0.5=-0.000005, sqrt(-0.000005)=NAN,完.IEEE754规定NaN参与的运算都会变成NaN,就像JS中的undefined那样毒性十足,于是整个算法就跪了,编码出的法线在特定的角度(一般是平面垂直玩家视线时)会毫无征兆地突然崩掉.解决的方法其实非常简单,给gl_NormalMatrix * gl_Normal套个normalize就行了,幸运的是市面上几乎所有光影包在获取法线时都是用normalize(gl_NormalMatrix * gl_Normal) (你问我的那个教程光影? 它可不算"流入市面"哦?),因此基本不用担心这个问题,所以我也不太急着修复它...(更何况我也不知道如何修复 hehe)

4.29更新

试了一下发现Chocapic13 V3也会跪掉,原因是Chocapic13 V3使用了一个Shadersmod非常罕见的特性:DRAWBUFFERS中允许空白占位符,比如DRAWBUFFERS:NNN1N2就是gl_FragData[3]向colortex1输出颜色,gl_FragData[5]向colortex2输出颜色,而gl_FragData[0]、[1]、[2]和[4]不向任何RenderTarget输出.我当时没料到还有这种操作 233 V6跪掉的原因是Optifine内置的新版Shadersmod似乎支持在一个pass中向一个缓冲同时即读即写了,奇怪的是它的文档中却明确提到"Writing to color attachments that the composite shader also reads from will generate artifacts" 难道他自己搞了大新闻结果忘了更新文档了吗 ? 其实针对即读即写的情况启用Ping-pong我之前已经打算把它作为OpenShader相对Shadersmod而言的新特性了,不过因为需求不大一直没实现,现在看来是被Optifine将了一军了 233

阅读全文 [...]

OpenShader月报 #0

虽然没有做paperwork的习惯,但我觉得在这时候还是写一篇总结这个月工作进展的文章比较好,毕竟卫星放出来如果没时不时地搞点大新闻的话总会让人觉得弃坑了是吧 233
  • RenderChunk更新优化
    众所周知MC的地图是以16x256x16的Chunk - 即所谓的区块 - 为存储单位,但在渲染时是以16x16x16的RenderChunk为单位进行更新和渲染,问题在于RenderChunk的更新并不是很快,在i5-4590的机器上大概需要6ms每单位,虽然MC已经引入了多线程更新RenderChunk的设计,但这只局限于由地图加载导致的更新,由砖块变化产生的更新依然要求单线程更新,这就导致了当发生大规模的砖块更新时游戏帧率会骤然下降,比如大规模流水或接入高频红石电路的红石灯等.
    RenderChunk的更新操作实际上是遍历区块中16x16x16的部分逐砖块地进行可见性判断,然后将砖块的模型面填充入顶点缓冲中,其中获取光照度和获取砖块的IBlockState这两步操作开销莫名的大,而在原版的更新流程中它们的调用又十分频繁,因此在此做一个简单的缓存就能戏剧性地加快原版更新速度.
    然而Forge还增加了它自己的更新流程,被称为ForgeLightPileline,顾名思义,它是解决在复杂模型下原版光照错误的问题,在默认情况下它是取代原版流程的,它的速度跟未经优化的原版差不多,然而它的可优化空间实在不大,一堆堆的三维数组像节日彩球一样在程序里传来传去还要不停地拆包打包可真没有什么优化手段,但是没有它又还真不行,因此这里采用的措施是增加一个判断器根据被渲染的砖块的模型类型来判断该采用哪个更新流程,毫无疑问这一步并不快,因此加入了一个LUT缓存,采用打表查表的方式来加速判断.
    此外还有一个被临时取消掉了的优化,是让上文提到的发生变化的砖块也进行多线程更新,这一项优化效果斐然,但是却存在两个巨大的缺陷,一个是在首次进入游戏时有大概5%的概率因为某个竞态条件而陷入卡死 - 提交入队列的更新任务莫名其妙地没有被执行,这个问题我调查了几天也没有结果;如果说它可以通过一个条件变量强制在首次更新时采用单线程来"解决"的话,下一个问题根本无法绕过,在更新区块时,某些BlockRenderLayer的变化要到下一帧才会反馈,首先要解释一下BlockRenderLayer(以下简称layer),从1.8开始MC中的砖块被区分为4种layer: SOLID(无Alpha测试,有Mipmap)、CUTOUT_MIPPED(有Alpha测试,有Mipmap)、CUTOUT(有Alpha测试,无Mipmap)和TRANSLUCENT(开启混合). 在渲染时MC会将按照这4个layer逐层渲染砖块,原因很显然,不同的layer需要不同的渲染状态.而在引入多线程更新后遇到的问题就是,当区块发生更新时,某些layer中的顶点变化总会慢一帧才能表现出来.
    比如,草地的layer是CUTOUT_MIPPED,石头和泥土地是SOLID,当你敲碎一个放置在草地旁的石头后,石头破碎的地方不会被填上草地,而且会露出一个空洞,这个空洞直到下一帧时才会补上,然而奇怪的是设断点会发现更新只发生了一次并且是发生在敲碎石头的那一帧,这难以解释为何一次更新的效果直到下一帧才会生效,在更新完毕后将数据从客户端(CPU/内存)上传到服务器(GPU/显存)的操作无论是单线程版本还是多线程版本都会被留在主线程进行,或许是由于内存可见性什么的问题,但不管怎么说,具体原因始终未能确定,因此只能暂时取消掉多线程更新这个优化.
    图:在击碎砖块的瞬间留下的空洞,这个空洞在下一帧即被填上了
  • 自定义砖块贴图
    Shadersmod增加了法线贴图"_N"和高光贴图"_S"两种额外的砖块贴图,现在OpenShader也完成了新增砖块贴图的功能了,开发者可以自定义要载入的贴图的后缀名以及当无贴图时的默认值,比如法线的默认值可以采用0xFF7F7FFF(BGRA格式的(1, 0.5, 0.5, 1)). 不过显然在切换光影包时重新载入一遍贴图会很慢,因此Mod提供了一个选项是提前缓存_N和_S两种后缀的贴图并且在切换光影包时不卸载它们.
  • 界面&选项
    现在已经有了一个类似Shadersmod那样的光影包菜单,并且实现了切换或重载,理论上讲Mod现在已经可以正常使用了.
  • 多线程渲染准备
    在进行正式渲染之前,MC需要先准备渲染数据,比如计算镜头数据、对场景做视锥裁剪等,这一部分在之前是单线程完成的,现在这一步是以Pass为单位进行多线程处理,当Pass数量小于等于CPU核数时,渲染准备所需的时间就从"所有Pass准备时间之和"变成了"最慢的Pass的准备时间",也算是省出了一些时间吧...
正在进行的工作:
  • 池化VBO&MultiDraw (60%)
    当我在测试用的光影包中尝试实现一个朴素三重CSM(不根据视锥做偏移,阴影镜头始终对准玩家)时发现性能急剧下降,从Profiler的结果来看一个问题是最大视距的那个阴影镜头所需要的准备时间太长了,另一个问题是砖块绘制过慢,在10视距时,玩家视角与3个ShadowMapping过程总共需要绘制1000+次RenderChunk,绘制一个RenderChunk时还要为每个layer绑定一次VAO再做一次Drawcall,这样算下来每帧会需要数千次Drawcall和等量的VAO绑定,性能瓶颈妥妥地是在驱动上,事实上确实当显卡占用没增加多少而CPU却有一个核心已经跑满了. 因此问题在于如何降低调用数量,N卡有个BindlessMultiDrawIndirect,可以在不绑定VAO/VBO的情况下进行单指令多渲染,但显然我们不能指望一个供应商独占扩展...因此我把注意力集中到了最简单的MultiDraw上,MultiDraw是个很早的特性,我记得好像是GL1.5时代就有了...它可以一次指令绘制一个几何体中多个不连续的部分,而缺点是中途不能切换纹理等,显然这对MC来说不是问题,关键在于如何将多个RenderChunk中的几个layer的顶点数据都整个在一个VBO中.
    我的解决方案是以4x16x4个RenderChunk为一组(也就是覆盖4x4个Chunk)使用一个手动维护的内存池来共同存储所有layer的顶点数据,这个内存池的实体是在显卡显存中的一个VBO,在客户端程序会负责空间分配以及数据传输等工作,当RenderChunk向内存池首次提交一次数据更新时,内存池会根据数据体积分配一段略大于数据体积的空间,然后返回给提交者一个指针(有意思的是,这个指针是可变值(Mutable),我知道一个可变值指针听上去即不可思议又恶心,但这确实能省下不少事),同时会在一个SortedSet中记录已分配的指针,有个例外是空数据提交产生的指针,这种指针不会被插入到那个SortedSet中.
    每一个内存池都要面临的问题是碎片整理和扩容,理论上这个内存池可以通过glCopySubBuffer来整理碎片,只要将末尾的内存复制到前面的空隙即可(glCopySubBuffer有个特性是复制源范围和目标范围不能重合,这导致了复制空隙紧后面的数据到前面来,以"冒泡"的形式整理碎片的方法不能用了),但我暂时还没实现,现在的做法是在扩容的同时顺便进行碎片整理,glBufferData在扩容的时候不会保留旧的数据,因此得先分配一个新的VBO,然后把数据复制过去,再销毁旧的.在复制时如果遇到空隙的话就会跳过去,同时纠正之后的指针的偏移量(这也是为什么它是可变值)
  • 指令队列 (30%)
    鉴于OpenGL要求必须在主线程渲染的特点,压榨性能的方式之一就是将渲染数据的整理工作通过多线程来处理,主线程只负责指令提交工作,指令队列响应了这一思想,所有渲染操作都被分类为一些指令,一个阻塞队列负责缓存这些指令,协线程负责收集数据和生成渲染指令,并将指令送入队列,主线程从队列取出指令并执行,这应该可以一定程度上提高性能,毕竟拥有更高的并行度,并且主线程执行的工作更单一,不会受CPU缓存污染的困扰. 现在,唯一的问题在于,从队列中读取指令并执行的开销会抵消这些提升吗...
  • Early-Clear (0% :P)
    上文提到了渲染和数据准备是分开的,但事实上有一种渲染操作"基本"无需数据 - 清除缓冲,说它"基本"是因为主视角的清除颜色缓冲还依赖于雾颜色,而雾颜色的计算是在相机计算中进行的,因此可以在完成相机计算后主线程提前开始为各个Pass进行缓冲清除,而协线程继续准备其余的数据.
  • 很...很多... (紫asdfghjkqwertyuiozxcvbnm%)
    显然今年没Demo了! 看看明年1月卫星能不能落地吧! 新年快乐 🙂
阅读全文 [...]

使用SIMD+CriticalNative在Java中加速矩阵运算

对于游戏开发来说,一个健壮高效的数学库是必不可少的,特别是对于3D游戏而言,动作系统在计算骨骼动画时会进行数量可观的矩阵乘法或求逆运算;渲染系统也需要频繁计算变换矩阵.虽然一次矩阵运算消耗的时间可能不多,但对于分秒必争的游戏渲染来说想要力争60fps、死守30fps底线就势必不能放过任何一个免费提升性能的机会. 阅读全文 [...]

从1到100 - 模块化的跨平台程序

前几天完成了被当做作业的小程序,名字相当掩人耳目:Finite Digit Summator,一定程度上是向Digital Differential Analyzer致敬,项目被我扔到了Github上,本身并没有太大应用价值,除了那两幅从東方AA摘下来的字符画,以及一黑黑了两个游戏的梗.

从设计上,它的项目结构很大程度上参考了我以前的项目,以一个核心模块囊括主要功能,然后以多个针对不同平台的子模块负责将功能封装并展现给用户,事实上,除了网页版有一个功能是通过JS重新实现了一遍以外,几乎所有的使用了两遍以上的功能都被集成在了核心模块中,因此可以说下一阶段的目标"实现模块化"我已经完成一半了(笑),剩下的看上去无非是将之前没来得及上线的安卓端做完,修修Bug,刷刷单元测试之类的.

听上去通过模块化来实现跨平台就像当年老一辈眼中实现共产主义一样简单,然而一个实际的跨平台项目想要通过模块化来实现在设计上却是困难重重,最主要的问题在于硬件的局限性和需求的不同.还记得刚才说的"以前的项目"不? 2个月前的寒假时我开了一个新坑,用Java复刻(或者叫抄袭?取决于你怎么看待"yet another alternative implementation"这种东西...)一个Era的开源跨平台版,什么是Era?我放个截图你大概就能知道是什么东西了...

era 阅读全文 [...]

在Gradle中集成Javacc

有时我们会希望在项目中使用一些脚本语言、DSL或特殊格式的配置文件什么的,虽然已经有一些现成的方案,比如使用Java内置的JS引擎,或者使用LuaJ、Groovy之类的外部库,但这些不是局限性略大,就是需要附带庞大的库,比如FML就附带了一个Scala运行时库(以及一个编译器!),而实际上MC现在又有多少个用Scala写的Mod呢?看Kotlin最近势头这么火,估计过几天他们就得附带一个Kotlin库了吧...言归正题,这个时候我们就需要一个自行设计的脚本语言或者配置文件格式了,然而手写一个Parser确实有一定难度,不过好在市面上有一类神奇的东西:"编译器编译器" 阅读全文 [...]

MCMod教程开始恢复更新

MC1.9马上就要发布了,按照"总是差一个版本"的惯例(这是哪的惯例啊),教程准备从1.7更新到1.8了 ? 刚才看了眼第一篇教程,文中介绍的Eclipse居然还是4.3...现在第一篇教程已经更新了,最近那么多人抱怨没法配置开发环境,现在看来一点也不奇怪(捂脸,那篇实在太陈旧了).

想看看当初给MC1.2写ModLoader教程时的原始手稿,结果发现找不到了...这种东西还真能丢啊. 阅读全文 [...]

FGOW1.2.1和FMMv4

Forge在更新到1.8.8之后FGOW1.2.0就不能用了,于是自然而然地就有了FGOW1.2.1,新版本在功能上没有变化,只是支持了使用ForgeGradle2.1的Forge1.8.8和1.8.9.

下载地址:
SkyDrive:http://1drv.ms/21gcxy5
Dropbox:https://www.dropbox.com/s/ekig3gjx32uz3qp/fgow-1.2.1.jar?dl=0
百度网盘:http://pan.baidu.com/s/1geoIkin


此外,ForgeMavenMirror,也就是我们喜闻乐见的ForgeMaven仓库镜像,也更新到v4版本了.
更新内容包括:

  • 缓存了2.0、2.5和2.7的gradle文件,下载地址为"http://forgemavenmirror.sinaapp.com/gradle/gradle-[版本号]-bin.zip",启用它们的方式是修改Forge(其实现在应该叫MDK了)目录下的gradle/wrapper/gradle-wrapper.properties文件,将"distributionUrl="后面的下载地址改为镜像的地址.我之前没有弄这个是因为我不赞同这样做,Gradle的文件策略相当有问题,它是根据下载地址的Hash来识别版本的,这意味着不同下载地址的同一版本Gradle(甚至是同一个地址的https和http下载链接)会被识别为不同文件,你知道我的机器上已经有4个版本的Gradle-2.7-bin了吗?也许他们认为多版本并存很有意义,但我觉得仅凭下载地址来区分的多版本除了虐待硬盘以外毫无意义.不过现在考虑到Gradle已经成了GFW的重点关照对象之一,https链接几乎已经连不通了,这里还是提供了Gradle的缓存.
  • 增加了大量缓存,现在FMM已经可以代替所有的仓库了!对,你可以删掉除FMM以外的所有仓库,经过实测1.8.9可以在只有FMM仓库和本地Forge缓存目录的情况下配置.
  • 智能重定向,过去FMM在失败时只会重定向到Forge的仓库(files.minecraft.net),现在FMM会重定向到"最有可能"的仓库,此外,由于Oschina的Maven镜像复活了,对于Maven中央仓库的资源会重定向到Oschina的镜像.
  • 可选的快速失败,如果你不想要重定向功能的话,可以使用"http://forgemavenmirror.sinaapp.com/mavenff"这个仓库,它会在没找到缓存的情况下直接返回404,而不是重定向,这对于想要继续混合使用其他仓库的人来说很有用.
  • maven-metadata.xml缓存会在每天(北京时间凌晨1点)更新一次.因此,现在快照版本(Snapshot)又会被缓存了(之前由于maven-metadata.xml不会自动更新的问题,一度取消了快照版本的缓存).
  • 一些细微的优化.
阅读全文 [...]

Asm♂Event♂Bus

好吧,标题是逗闷子的,这个项目的名字确实是AsmEventBus,但我总是忍不住在里面插入'♂'.

上一篇日志提到正在做一个Java库(如果你真的以为上一篇日志仅仅是追悼我挂掉的高数的话...就点它的"继续阅读"吧),其中提到"一个基于类生成的事件总线系统",在写完日志后不久,我决定把事件总线系统从库中分离出来,单独作为一个项目. 阅读全文 [...]