Java Generic Sucks

Java’s generics type erasure has been regarded as a failure design since JDK 1.5, but I haven’t realized it until today when I run into a serious issue about it.

The issue

I’m using night-config library to parse TOML config file for my program. I parsed and stored a list of long from TOML. Right after I got my program up and running, I noticed that list.contains(x) failures at random numbers. Those numbers seem to exist in the list and successfully printed out, but I cannot locate the issue based on the information I got.

So I read the TOML parser it self, trying to locate where it handles Integer/Long parsing and found the function that deal with number parsing:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// https://github.com/TheElectronWill/night-config/blob/master/toml/src/main/java/com/electronwill/nightconfig/toml/ValueParser.java#L76
private static Number parseNumber(CharsWrapper valueChars) {
...
long longValue;
try {
longValue = Utils.parseLong(numberChars, base);
} catch (NumberFormatException ex) {
throw new ParsingException("Invalid integer value: " + valueChars);
}
int intValue = (int)longValue;
if (intValue == longValue) {
return intValue;// returns an int if it is enough to represent the value correctly
}
return longValue;
}

Note that: returns an int if it is enough to represent the value correctly

So the output list, assumptively ArrayList<Long> typed, does consist both Integer and Long if Integer is enought to represent the value, calling List.contains will certainly fail if you pass a long parameter to check whether the number, which is actually int, is in the collection.

Having it double-checked by using fastutil’s LongArrayList confirmed my thoughts, it fails to construct because there are int values in a marked ArrayList<Long> collection.

Why blame Java’s generic design?

In this case, ArrayList<Long> is merely a mark that give instructions for compiler to check, but it does not replace the Object[] array in the list, nor does it assert runtime type-checking. The issue may absolutely be avoided or easily solved in C#, Rust and other strong-typed generics language.

Solution

Always use strong-typed fastutil or trove collections for storing primitive values.

Blame Java for it’s bad design.

In this case, cast boxed int or long values to Number and cast it back to Long, then store them in a LongArrayList.

追梦

一种现象

昨天群友突然提到了几年前的一次 brainstorm 以及当时的憧憬,有感而发。

首先说结果肯定又是没做,但是真的给了我一些思考。我们放弃的并不是理想,而是“去思考自己的理想”这么一种过程。

“既然我每次都实现不了,那下一次还是实现不了吧。”这大概是一种创伤后应激,你的奋斗换来的不是成果,也没有人支持你,最后胎死腹中可能还被人笑几句,我相信大部分人都受不了这种打击,于是选择封闭了这种想法。

因为这种创伤,我们选择了逃避,选择随波逐流,将其合理化,认为这是“成长的一个过程”。

一部作品

2012 年的番剧《さくら荘のペットな彼女》(樱花庄的宠物女孩)。引入了一个很好的主题:天才就在你身边的时候,你的理想有意义吗?

这部剧不能从其表面来看,显然它是一部标准的日式高中校园恋爱后空翻后宫番,但这个主题才是真正贯穿全剧或原小说全集的内涵。我们复习一遍,空太遇到了天降女神椎名真白,但是一个普通到不能再普通的普通人在天才面前感受到了巨大的差距。同样的,仁也感受到了与美咲间的巨大差距。这是故事中大部分矛盾的根源。

你提这个干嘛?

实际上这是我的灵感来源,去年也有人就此发表过文章:逃离“樱花庄”:我们每个人,都是空太。这篇文章想表达资本主义对人的异化,以及对共产主义社会和人性解放的向往。

我对这种观点进行一些补充。实际上,不要把理想看的那么崇高。理想也是你的一种需求,一种欲望,你想得到一个远大目标实现后的满足感。

每一个中国青年,从出生的那一刻起,都毋庸置疑的认为自己是天选之子,将来是要改变世界的。而当逐渐的发现,自己并没有成为天选之子的时候,便产生了巨大的落差,所谓的佛系青年,在中国是不存在的,他们虽然表面谦逊佛系,但实际内心把自己所称的一时佛系当成是隐士大丈夫能屈能伸的必要隐忍,是君子伺机而动的代言,一旦真的有什么机会,他们会立刻豁出性命,就比如虽然嘴上臭骂 996,但实际却自己主动加班的不在少数。

因此,在这样的一种认识下,也就很难实现自我反思和纠偏,在孤傲和自负中,一切的矛盾被归结为时运、时命,是暂时的挫折与失败,相比于反思,青年们对勇于面对挫折,抗击挫折的论调更加喜好。然而,如果不进行反思纠偏,矛盾就会周期性、重复性出现。正如前文所言,缺点不是人与他者的对立,而是人的认知与客观规律的对立,不是偶然的而是一种必然,只是其结果以一种具体的形式表现在了他者的反馈上,称之为缺点。因此,部分人所主张的只要逃避,忽视他人看法,退到自己的世界里,问题就不存在的观点是一纸荒唐言,这样的做法仅能做到心理安慰,但却改变不了这种对立所引发的矛盾周期性重复问题。

当代青年遇到的是什么根本问题呢?是急于找到或提升自己在这个社会上的位置。很多矛盾也就归因于此了。

然而你真的想清楚了吗?这个根本问题真的是你需要解决的吗?

一部分读者可能已经明白了,给另一部分读者一些提示:“找准你自己的定位”是不是社会强加给你的一种概念呢?

很显然,它是社会强加给青年的一个按部就班的思路。

小时候我们学习为了考初中,高中,考大学,大学考研,为了找好工作。这个过程是“找清自我定位”中之 - 自己的内卷学习能力。工作换了几家,最终稳定下来,此为找到收入定位。最终你进入了按部就班的人生,盖上了盖送进了火葬场。

这就是对人的异化啊!在这个过程中我们从来就没有想过,“我要干什么”,“我是为了什么”。想到却解决不了这些问题的人选择了逃避,啃老摆烂。

那么为什么我们遵循了这样一种循规蹈矩的生活呢?回到最开始提的现象,理想的成功率很低,我们选择了自我封闭。而循规蹈矩的生活成功率很高,我们选择使用给定的道路。

追逐理想的路上没有反馈可言,因为这是你自己的道路,你无法跟任何人进行比较。热衷于内卷的年轻人可是有明确的比较目标的。《樱花庄》剧中的男主空太也有自己做游戏策划的理想,但是没有正向激励,而且选择性只看到了别人的成功,没有看到别人的失败或别人的努力,陷入消沉。但在众人的支持下,还是能明确自己的目标一路奋进。我们就没有那么幸运了。别说鼓励,我们大部分人可能遭到的更多是冷漠,甚至嘲讽。在如此巨大的心理压力下,正常人都是无法坚持的。这样的压抑真的是难以言说的,简直就像是全世界都在劝你放弃。

造成压力的原因

这就又要说到独立性与同一性了。个体的我们接受了“理想总是失败的”“循规蹈矩是最好的选择”这样的想法,对自己而言首先开始自我否定,对外的表现就是“洗心革面”。在群体角度上,大家都秉持了这样的想法,就开始排他,开始不自主的对理想谈论者产生负面想象,甚至恶意的将他们与说空话大话的人归为一类。久而久之形成了相互影响的趋势。

这是对于普通人来说。而对于那些相对成功者来说,他们的鼓励更会带给人无形的压力——“你说的如此轻松!”如同剧中互相喜欢的空太和真白,空太会因为真白的鼓励而生气。你的大脑不能进行多维度分析,只能进行单维度分析,不同的人不同的价值在同一时刻没办法全部表现。男主在前期想要搬离也变得容易理解了,因为他在樱花庄受到的打击远比在学校中大,而不仅仅因为“这些人很怪”。

社会也不具有一个 general-purpose 的对努力的鼓励和奖励机制,一切以结果为导向,结果就是经典的“努力无用论”。

人们对他人理想的否定也带有矛盾在其中:一种是自私,即不希望别人过好,突破同样的圈子,衬托出自己的失败;另一种是慈悲,因为自己受到了打击,所以不希望别人受到同样的打击。这种矛盾是对立统一的,通过对进步的打压,形成了一个稳定的群体。

方法论

(2021.11.23 更新)

有没有解决之道呢?带领群体进步是上策,但对于个人来说显然困难不小。另一种是,真正找到大家的共性方面,不仅只是趣味相同。

在跟别人的探讨中,我提出对“和而不同,同而不和”的一种理解:人与人之间的相处要靠三观。一种解释方法是把人们的三观看成一种对于人的自我价值,以及周围价值的基于人本身特质的加权认定算法

这里的特质指性格,能力,价值观等。

加权算法的举例分析:

  • 现象:两个人因为有相同/相似的特质而形成共识

    本质:两个人对某个或某些特质的权重相同

    假设两个人拥有同一个特质的不同程度,例如一个钢琴初学者和钢琴大师,如果两人都把钢琴作为重要的部分,两人会有共同语言。

  • 现象:一个人身上有这样的特质,而一个人欣赏这样的特质,两人形成共识

    本质:仍然是权重相同

    反例:一个厌恶弹钢琴的钢琴大师,和一个欣赏钢琴曲的普通人,会出现摩擦和矛盾

  • 现象:两个人认为 A 同等重要,但一个人认为跟 A 比起来 B 更重要,另一个人认为跟 A 比起来 C 更重要,虽然前者的 C 比后者强,两人仍然出现矛盾

    本质:加权算法不同。前者的加权算法可以假设为80%B + 60%A + 50%C,后者的加权算法可以假设为80%C + 60%A + 50%B,无论算法的结果如何,这套算法不同,就会出现认定矛盾

从这套理论分析来看,结果如何是根本不重要的!重要的是你对某事的重视程度如何。而我们追求的也不是绝对的“自我价值实现”,而是在旁人眼里你的自我价值的重要程度(权重)。你在一件事上做的不够好,在别人眼里它的权重是 200%,认为你做得足够好了;你在一件事上做的不够好,别人眼中它的权重是 10%,无论如何都不会得到夸奖。

当然,如果说你得出了“取悦别人”的结论,那就大错特错了。取悦别人不会满足你自己的情绪需求,而理想,信念,这都是自己的情绪需求,是欺骗所不能得到的。要找到肯定你的人,欣赏你的人与你为伴,或是解构矛盾的自己。

所谓的大同社会,无非是找到一套所谓“正确”的三观强加给所有人罢了。这个后现代的社会已然离散化,小圈子化至今,要想正确的分析这些观念,改造或是延申自己的想法,就必须了解共性。当你了解自己的某些决定背后的个人原因和社会原因后,假如前提被推翻,那么就勇敢地改变自己的想法;假如前提依然成立,那么就真诚的接纳自己。我虽然也不能摆脱迷茫,但至少这是我认为的一种正确的方向。

一件小事说起,谈谈游戏的那些“升级”策略

本来在知乎上发过了,想了想在这里再发一遍,万一没了呢。当然我又改了改

引言

“升级”还是“不升级”,这是一个值得考虑的问题。–L 姆雷特

某个聊天记录截图中某人攻击了 Minecraft 1.7.10 Mod 的开发和维护群体,给了我一点启示。

在之前我也认为维护旧版本或者给旧版本增添新内容是个不可理喻的行为。在长期的分析和信息搜集后,我认为有几条开发者考量的因素(至少他们声称是这样):

  1. 新版本拥有更好的优化(如地图生成,网络通信上的优化)

  2. 新版本的底层引入了更多的机制,可以在此基础上实现更多功能

  3. 大部分玩家只玩一两个 mod,重度整合玩家是少数,所以迁移成本低

下面我们来一条一条分析。

  1. 新版本只在 Java 新版本上进行了兼容或升级,但这主要是 Forge 的“功劳”,且 Forge 在 1.17 之前一直不官方声明推荐使用 Java 11。地图生成被抽象成了各种 Features,可是生成速度更慢了。原版的地图加载变得更慢,Enigmatica 6 的世界生成速度甚至不如 GTNH,经常造成假死。而网络通信上,Forge 进行了超级大改造,让开发门槛变得更高,服务端与本地端的兼容更加困难,经常需要完整同步所有文件才能解决 mismatch 问题。渲染上掉帧现象普遍,Sodium 告诉了我们什么是真正的渲染引擎。

  2. Mod 要实现的新内容很大程度上并不版本依赖,你能说 IE 的多方块机器借鉴或直接使用了 MC 的什么新机制吗,都是旧有的 Block,TileEntity。你甚至可以在远古版本实现一个 IE。对于现在的 Mod 来说没有什么是不可能的。

  3. 这是某些 Modder 的一厢情愿,请自行查阅 CurseForge 上各类轻重度整合包的下载量。GTNH 是很火的。

综上所述我认为这些理由站不住脚,不构成 Mod 进行大版本迁移的充分条件。笔者认为 Mod 进行大版本迁移有两种原因:

  1. 习惯:升级到最新版是一种强迫症。

  2. 风气:当一个 Mod 尤其是大型 Mod 进行版本升级后,它的附属和联动 Mod 就会被玩家或整合包作者催着升级。

其实就是这么个简单的逻辑,再加上上面那些可有可无的理由。然而升级是苦痛的,每个大版本都要换一次 mapping,至少要修好几天 bug,并且适应新的结构(尽管它们实现的功能完全一样或者就是改个名字)。在这一过程中程序员很少能体验到价值实现,心情不会太好。在以前版本的经验这个版本也都要寻求其他方法实现。

这就引出了不升级的原因。那就是我当前这个版本还没优化好,为什么我要升级?或者我只是想简单加个功能,慢慢慢慢再完善。这些灵感有时候就是会 slip away,如果你忙于升级的话你的大脑不会给你提供这些灵感。我并没有可靠证据,但事实说明一切。Reika 还是不断在更新,GTNH 还是不断在更新。他们都能不断实现更多玩法和功能,RoC 就有大变样。

那些选择升级的呢?MC 最近发布了一连串新版本,Forge 也进行了一连串大改。我相信大部分 Modder 都和我一样在迁移的路上身心俱疲。从结果来看也是这样。主流 Mod 如 IE,AE2,RefinedStorage 根本没有内容上的突破创新,只进行了材质的换皮。CoFH 系甚至退步了,只有 Mekanism 发展了核裂变线(也不完善)。我们可以看到 Fabric 欣欣向荣,然而只是在尝试打磨和消化旧有的玩法,并且优化性能或者仅仅是代码更漂亮。对于 Modder 来说,时间是宝贵的,大部分人只是作为一个 part-time job 来做这些事情。久而久之,很多经典 Mod 就跟不上了,我相信大家都知道例子。这就是一种 keep-up 的升级。

这一现象并不孤立存在于 Minecraft Mod 之中,Minecraft 本身也陷入了这样的困境。我现在笑称 Minecraft 是个 RPG 游戏了,一堆为 RPG 地图服务的改动,数据包,function,在 Mojang 本就拉垮的架构能力大笔一挥下加入了更大的屎山。Modder 都不知道自己怎么加一个地牢 Loot 了。

Minecraft 难道是个 RPG 游戏吗?

Minecraft 本身就是在玩法上的创新起家的。最早它扩展了沙盒游戏的玩法,拥有更少的限制和更多的创造性。地狱更新,红石更新,末地更新,冒险更新,都让这个游戏的流程越来越多,可玩性越来越高。在 1.7 之后 Minecraft 已经和 Mod 密不可分了,它们作为一个整体共同进步。然而在 1.14 我认为 Mojang 选择了错误的路线,它不知道 Minecraft 要往何处去,于是进行了材质翻新,并且加入了大量冒险内容的扩充。要我说,材质翻新是走的最臭的一步棋,它强制改变了游戏的整体风格,并且 Modder 抽了风的一样也升级了材质,使得老玩家对于新版本的不熟悉和不信任感飙升。

(此处一定会有 n 多人反驳我说你怎么代表老玩家我怎么觉得材质好看,你开心就好,我说了是我的个人观点)这代表着 Minecraft 的一个重大的转变:Minecraft 本身去发展冒险内容,言下之意是,你们 Mod 去搞其他东西。可是冒险内容上的更新会很频繁,而 Mod 的创新速度正如前言是肯定跟不上的。“自大的更换材质”是一种对自己认识不清的一部分,因为不清楚自己的发展路线所以在盲目的更新。也同时暴露出,Mojang 根本不知道自己想干什么了。

要想理解为什么我说这是 Minecraft 走上下坡路的一个重要转折点,我们来看看其他游戏是怎样的。

来看看其他游戏吧

  1. Paradox Interactive(P 社)游戏。使用 Clausewitz 引擎的所有游戏都受限于这个引擎几百年的祖传单核,拉垮机能和狗屎一样的脚本。P 社选择了卖各种 DLC 吞 Mod 的发展方向。游戏后期的玩法因为太卡而基本无法被开发,各种机能受限,许多简化操作的关键命令不实现。这些都是搁置了很多年的问题。最后只能靠推出一代一代新游戏来解决,但人生又有几个十年?而且拿走的都是特别精华的灵感。

  2. 暴雪。暴雪属于是一把好牌玩成臭狗屎的典型。魔兽争霸,星际争霸,在编辑器事件上走到了玩家的对立面。各种“教你玩游戏”,知乎有很多其他相关文章,此处不再赘述。

  3. R6。这砍一刀,那砍一刀,逻辑只有出场率和胜率,那你要干员干嘛,全部小叮当得了。

  4. 命运 2。这砍一刀,那砍一刀,退环境,那你还接着出新内容干嘛,重新发布一遍游戏不就完了。

这些都是明眼看着走下坡路的游戏,他们的共同特点就是游戏核心玩法上没有创新,没有进步,不思进取,而且不接受在这上的 Modding,一旦凉凉就会成为鬼服游戏。

暴雪本来坐拥最大的 MMORPG(魔兽世界)和 RTS 群体,守望先锋和炉石传说也非常火爆,可是在游戏发布没多久就直接用完了灵感,进入摆烂模式,推出日复一日的换皮活动和年复一年的换色皮肤。要我说那么宏大的世界观都喂狗了,包袱回收还不如网红爽文,更别提郭德纲于谦了。

Minecraft 有一个巨大优势在于可以进行完全的,简单的 Modding,这是这些游戏所没有的。但是 Mod 社区终究是脆弱的,需要仔细考量和保护的。StarCraft 1.08b 的那些 EUD 玩法被暴雪一刀砍没了,现在又想通过模拟器加回来,然而以前那些大牛们早已不在了。所以其实这些决定真的只在一念间。

要我说啊,Minecraft 现在最大的突破口就是怎样实现虚拟世界中才能实现的想法,点子,进而组成无穷可能的 MMORPG 世界,书写千万篇玩家们共同谱写的故事,而不是在单机自闭。(当然你要能写出真 AI 也行)

Minecraft performance tuning and more

多年的 Minecraft 服务器运维经验可以整理一下了,当然也讲点别的。

本文内容针对 Forge 1.12.2 版本,其他 Minecraft 版本同理但有所出入

Minecraft 服务器的性能主要有三个大开销,下面分别来讲讲三个部分以及它们的优化策略。

  1. TileEntity Tick

    Tick 的实现可以在 WorldServer#tick 找到。

    众所周知的开销大户,在一个大型整合包的中后期档中占用了主要部分。可以使用 /forge track start tileentity 10 /forge track tileentity 来统计十秒内的 TileEntity tick 时间占用。找到对性能影响最大的几个方块拆除之。

    常见情况:匠魂冶炼炉满了造成的巨幅卡顿,Ender IO 机器的 输入/输出 面放了一个容器。

    当然更常见的情况是人多了,机器多了,虽然每个只占用 1ms,但是积攒起来还是很容易达到 50ms (20 TPS) 的上限。这个时候可能需要和玩家沟通,把生产线拆除甚至换成创造箱子等。如果服务器玩家组分时段上线,也可以考虑限制或禁止区块强制加载。

  2. Entities Tick

    实体的情况比较复杂。玩家身上穿了过多能与世界交互的装备(如吸铁石)就会造成卡顿,同样需要与这些玩家进行沟通。其他情况下一般需要清理掉落物品,减少刷怪,安装 AIImprovements 模组,在后期档直接开启和平模式等。实体可能只占用 10ms 的 Tick 时间,但是聊胜于无吧。

  3. World Generation

    跑图卡顿,在 1.13 之前的单线程世界生成所存在的问题。考虑删除 BOP,RTG 等大量修改世界生成的模组。

  4. 其他部分

    • 虽然 Minecraft 采用了先进的 Netty 异步网络数据处理方式,但是仍然可能发生从网络线程引入的卡顿。典型的情况是一个超大型 应用能源 网络,打开终端之后会因为处理,发送,接收大量数据而造成卡顿。一个带有特殊渲染模型的多方块结构也可能发生这种现象,因为要实时获取多方块机器数据。这种情况下考虑开启 BBR 算法,AE 拆分子网络等。

    • JVM 调优可以直接使用 Thermos 给出的参数:java -XX:+UseG1GC -XX:+UseFastAccessorMethods -XX:+OptimizeStringConcat -XX:MetaspaceSize=1024m -XX:MaxMetaspaceSize=2048m -XX:+AggressiveOpts -XX:MaxGCPauseMillis=10 -XX:+UseStringDeduplication -XX:hashCode=5 -Dfile.encoding=UTF-8

    • 推荐使用 HotSpot JVM 而不是奇奇怪怪的各种 JVM,你不知道这些模组会用到什么奇怪的黑科技。

    • 不要使用洋垃圾 Xeon 开服!

后记

我们很少探索过大型整合包的后期档,很少探索大型 Mod 整合包带来的科技魅力,便是因为后期的性能问题。倘若能在性能和复杂的生产线之间做出很好的平衡,也许我们就可以欣赏到 Minecraft 中那种创造的魅力。

Hello World

Hello World 是 Hexo 的默认页面,是所有编程语言中的一个开始。反正这里也没有太多人看吧,毕竟一般都在最下面,最古老的地方。所以,稍微讲讲发博客的理由。

很早就有写博客的冲动了,当初是搜索到有“免费空间”这么一个东西,也就是空间站,在那里部署了第一个 WordPress。之后很多次都有部署过,但是可能写了很多,又删掉了,或者没有发出来。

在数次思想变化和成长中,每一年都认为自己去年的想法很幼稚,每一天都认为自己前一天的想法很无脑。但其实这些事情又有什么所谓呢?我们本身都是自己的历史,否定自己的过去,不就是否定自己了吗?

不满足于只是 QQ 上交流,而是想把想法记录下来,有的时候也方便说明“这之前讨论过了啊”。俗话说“好记性不如烂笔头”。个人博客其实也是对这种生活中冥想的一种总结整理,在总结的基础上也能找到头脑风暴中犯的一些错误。当然我知道我最大的问题就是文笔不好,看起来并不畅快,懵懵懂懂的,也欢迎读者给出改进建议。

希望未来的自己,或者是看到这一篇有同感的人,或是一个路人,看到这些话语也不要觉得幼稚与无病呻吟了吧。