作者: Amemiya

  • 使用Architectury Loom开发Minecraft 26.1的模组时得到Failed to find official mojang mappings for 26.1的错误

    简单来说

    Minecraft 26.1开始正式取消混淆,原来适用于有混淆的游戏版本的工具链需要更新。

    Architectury Loom需要把dev.architectury.loom换成dev.architectury.loom-no-remap插件,并修改版本号为1.14-SNAPSHOT,用来处理不带混淆的游戏版本。同时Architectury Plugin也需要升级到3.5-SNAPSHOT版本。这样就能正常处理不带混淆的游戏,但这一点不论是在Architectury的文档还是在arch loom GitHub页面的issues里都没有提到


    Minecraft不再混淆

    继19w36a发布官方的混淆映射表以后,Mojang在2025年10月29日宣布,从26.1-snapshot-1开始,Java版的源代码不再进行混淆,25w45a至1.21.11,Mojang同时发布已混淆和未混淆的文件,用来帮助社区进行过渡。

    Fabric Loom提供了一个net.fabricmc.fabric-loom-remap的插件变种,用来处理带混淆的游戏,旧的插件IDfabric-loom是它的别名;而net.fabricmc.fabric-loom则用于处理无混淆的版本。

    在移除混淆后,Loom的modImplementationmodRuntimeOnly等configuration已经不再需要,直接使用implementationruntimeOnly等就可以;remapJar的task也被删除,直接使用jar就可以;dependencies里也不再需要mappings的configuration了。

    Architectury Loom在一个月前开始合并Fabric Loom 1.14

    中国有句话叫“闷声大发财”。

    和Fabric Loom相似地,作为其Fork的Arch Loom也需要做出一些更改。或许是为了旧代码的兼容性,他们使用了dev.architectury.loom-no-remap这一插件ID用于不带混淆的游戏。可能是由于Arch Loom 1.14还没正式发布,loom-no-remap没有在任何相关文档中被提到,现在只有1.14-SNAPSHOT。但是Shedaniel在cloth-config的代码库里已经这样用了1,所以我觉得可以认为这一更改已经确定了,其他开发者可以如此进行迁移。

    Arch Loom的整体变更和Fabric Loom相似,按上面提到的修改就可以。

    除此之外还需要把architectury-plugin升级到3.5-SNAPSHOT版本,旧版本还尝试依赖remapJar的task,会导致运行时出错。同时,Arch Plugin还移除了namedElements,引用common项目的时候也不再需要指定configuration。


    qyl制作的基于Architectury Loom的多加载器模组项目模板已经更新,现在支持26.1,欢迎使用https://github.com/qyl27/architectury-loom-template

    1. https://github.com/shedaniel/cloth-config/commit/48614681f2b39740c1316247499c0cac2d92b524 ↩︎
  • 在MinecraftForge中,无法获取已移除的实体的Capability

    TL;DR:

    实体会在被移除的时候让所有的Capability失效,需要手动调用Entity#reviveCapsgetCapability,完成以后调用Entity#invalidateCaps恢复之前的状态。

    此前因为一些Bug导致在某些情况下即使不reviveCaps也能获取到,但Bug被修复以后就不能用了……


    一个Bug是Bug,多个Bug能Work,修一个就Break(

    在1.13(Forge 25.0),Forge存在实体(Entity)死亡(death)不会导致Capability失效的问题1,Lex推送了一个提交来修复它2,让Capabilities在实体被移除(remove)且不保存数据的时候失效(调用invalidateCaps()方法)。

    实体被移除不完全等于死亡(LivingEntity的死亡是被移除的原因之一),此外玩家跳进末地的返回传送门等情况,也会让实体被移除。因此Forge给实体类上Patch了一个remove(boolean keepData)方法,当keepData是false时才会失效Caps。

    在Forge的Capabilities系统中,Capability由CapabilityProvider持有,通过CapabilityProvider#getCapability方法获取,该方法返回一个Optional。实体就是一个CapabilityProvider,它有一个bool类型的失效标记,如果为true,则获取Capability必定得到一个空的Optional。

    玩家跳进返回传送门时,在代码里被视为通关游戏(wonGame)的重生(respawn),而非像是通过下界传送门或者去往末地那样的移动(travelDimension)。前者会导致玩家实体(ServerPlayer)重生,也就是跳进返回传送门的玩家和在复活点生成的玩家不是同一个对象,虽然没有死亡,但也被移除了,有持久化需求的Capability要在新旧玩家实体之间复制。此时keepData是true,所以Capability仍然有效,以至于相安无事。


    在1.16.5(Forge 36.0),Forge引入了一个新的事件——LivingConversionEvent来代表一个实体变成另一个实体的情况3。这个事件有Pre和Post的变种,前者能够取消或者拿到新实体的EntityType,后者可以拿到新的实体对象。但在Post事件被抛出的时候,并不保证转换前的实体仍然有效。例如:村民转变成僵尸村民时无效——原实体先被移除再抛出事件;而村民转变为女巫的时候有效——抛出事件后再移除原实体。

    此时问题就初现端倪了:LivingEvtity被移除之后,getCapability会拿到一个空的Optional,即使实体仍然持有这个Caps的数据。因此村民变成僵尸村民的时候,直接从实体里拿不到Capability。但是由于村民一定是被杀死后才会变成僵尸村民,听上去也符合直觉。


    在1.17.1(Forge 37.1),由于mojang实体移除机制发生变更,Forge移除了带有keepData参数的Entity#remove方法,现在实体移除时,一律会让实体的Caps失效,如果模组作者想要拿已经移除之实体的Caps,要先调用Entity#reviveCaps(),获取到之后再用Entity#invalidateCaps()使其失效。

    这本来会造成复制玩家Caps的用例发生错误——跳进返回传送门也会导致Caps失效,但由于版本迁移过程中Patch的行号发生偏移,导致调用invalidateCaps方法的那一行Patch被打到实体类里面的别的方法上了4,所以问题并未发生,也没人注意到这个问题。


    直到1.19.1(Forge 41.1),有人修复了Patch里行号的偏移5。对跳进末地返回传送门的玩家实体来说,reviveCaps和invalidateCaps成为了必须的步骤,而不知道这件事的模组作者就要面对玩家从末地回来以后数据消失奇怪的bug了(x


    1. https://github.com/MinecraftForge/MinecraftForge/issues/5307 ↩︎
    2. https://github.com/MinecraftForge/MinecraftForge/commit/e6a73ef36c1bdfb0053c2c122f1c71d376d44651 ↩︎
    3. https://github.com/MinecraftForge/MinecraftForge/commit/b64f4780c5f951da2eb46aed78590cdbb3220700 ↩︎
    4. https://github.com/MinecraftForge/MinecraftForge/commit/9ec1e24dc510875263c0a5a5070bb61b3b4833c7 ↩︎
    5. https://github.com/MinecraftForge/MinecraftForge/commit/0b1b3c68fa527c30006e1bfbf69eecccf311ef34 ↩︎
  • Gradle多模块项目中,不同路径下的同名模块的依赖冲突造成循环依赖

    Gradle用group:name:version:classifier的格式作为依赖的唯一标记,不同路径下的同名模块,这四项都相同,因此在依赖解析的时候出现冲突(经常表现为循环依赖)。这被认为是个Bug,但已经存在了九年多(https://github.com/gradle/gradle/issues/847)。

    三个解决方案:
    1、让同名的模块在不同的group里;
    2、给模块改成不同的名称(在settings.gradle里改,或者直接重命名模块的文件夹);
    3、使用Gradle插件:https://github.com/SgtSilvio/gradle-structure(是一个临时的解决方案,实际上就相当于自动在settings.gradle里改模块名)。

  • 在2025年运行使用fairseq的项目

    什么叫赛博文物修复啊.webp

    fairseq是FaceBook开源的一个用于机器学习中Seq2Seq模型的框架,但在2023年就已经停止更新了。而还有很多项目是使用fairseq框架的,在如今想要再运行起来会遇到各种各样的困难。

    omegaconf无法安装

    问题现象:
    运行pip install -r requirements.txt时出现:

    The conflict is caused by:
    fairseq 0.12.2 depends on omegaconf<2.1
    hydra-core 1.0.7 depends on omegaconf<2.1 and >=2.0.5

    就算尝试手动安装omegaconf==2.0.5也会遇到

    WARNING: Ignoring version 2.0.5 of omegaconf since it has invalid metadata:
    Requested omegaconf==2.0.5 from https://files.pythonhosted.org/packages/e5/f6/043b6d255dd6fbf2025110cea35b87f4c5100a181681d8eab496269f0d5b/omegaconf-2.0.5-py3-none-any.whl has invalid metadata: .* suffix can only be used with `==` or `!=` operators
        PyYAML (>=5.1.*)
                ~~~~~~^
    Please use pip<24.1 if you need to use this version.

    解决方案:
    正如提示所写,使用喜欢的环境管理器(如venv、conda、pixi),把pip固定在24.0及以下版本。

    原因:
    pypa/pip#10263中引入了对指定包版本的规范,在pip 24.1中开始拒绝不符合规范的版本号,omegaconf的旧版本刚好受到影响(它指定了不合规范的PyYAML>=5.1.*依赖版本号,在fd9109c中修复)。

    error: command 'cl.exe' failed: None

    解决方案:
    下载Microsoft C++ 生成工具并选择安装“MSVC v143 生成工具”(选择适合自己机器体系架构的)。

    原因:
    cl.exe是MSVC的C/C++编译器和链接器,fairseq里包含了C++代码需要在安装时编译。

    在Pixi包管理器中安装MSVC?

    安装vs2022_win-64(x64)或者vs2022_win-arm64(ARM64),它可以检测和使用系统里的cl.exe,其中vs2022应该对应生成工具的版本(vs2022或者vs2019)。

    Tips:

    • 由于MSVC没有Conda包的发行版本,所以必须要在系统中安装VS构建工具。
    • conda-forgecxx-compiler包已经依赖了vs2019_win-64

    VC++的yvals.h引用了不存在的crtdbg.h头文件导致构建失败

    解决方案:
    在Visual Studio Installer里安装“Windows SDK”(有很多版本,最好选择和自己系统版本相匹配的)。

  • 事务的四个隔离级别及其递进关系

    为了保证一系列相互关联的数据在修改前后相一致而不出现矛盾的情况,在软件系统中引入了事务(Transaction)概念。

    事务的隔离就是对不同的事务之间不会互相影响的要求。

    显而易见的实现方法就是加锁。

    根据不同的规则,锁分为三种:

    • 写入锁(Write lock,独占锁,eXclusive lock)
      • 表明目标数据正在被写入,所以其他事务不能再对目标获取读取锁。(不能获取读取锁不代表不能读取。)
      • 同时只能有一个事务获取写入锁(因此也叫独占锁)。
    • 读取锁(Read lock,共享锁,Shared lock)
      • 表明目标数据正在被读取,此时其他事务不能获取写入锁。(如果此时有且仅有一个事务获取了读取锁,也可以选择升级为写入锁。)
      • 可以同时被多个事务获取(因此也叫共享锁)。
    • 范围锁(Range lock)
      • 对一个范围内的数据施加的独占锁,没有锁就不能更新其中的数据,也不能在此范围内增删数据。

    隔离级别

    下列隔离级别是从上到下递进的关系,更高的隔离级别避免了较低级别的问题。但隔离级别越高吞吐量越低,所以一般要按需选择确定隔离级别。

    读未提交(Read uncommited)

    是最低的隔离级别,只在写时加写锁,写完释放。

    问题:脏读(Dirty read),即一个事务可能读到别的事务还没提交的数据。

    读已提交(Read commited)

    读已提交=读未提交+读时要加读锁,读完立即释放

    问题:不可重复读(Non-repeatable read),即一个事务中先后执行两次对某一数据的查询有可能读到不同的数据。(例如有另一个事务在这期间提交了更改。)

    可重复读(Repeatable read)

    可重复读=读已提交+读时加读锁,直到事务结束后才释放

    问题:幻读(Phantom reads),即一个事务中先后执行两次查询得到不同的结果集。(因为即使施加了写入锁,在对应的范围内增删数据也是允许的。)

    可序列化(Serializable)

    可序列化=可重复读+当查询时必须加范围锁,事务结束后释放。

    这是最高的隔离级别,解决了上述三种级别中存在的问题。

  • Java的模除符号进行的是取余运算

    TL;DR:

    在Java中,%符号在对负数取余的时候有可能得到负值,而Java里没有无符号整数类型,所以很容易不小心把负整数当作被除数,它会产生负值结果。

    若需要类似Python中%的取模运算,得到最小正余数,应当使用Math.floorMod()(Java 8+)。

    这是一个历史遗留问题

    在K&R C中把%称为模除操作符(Modulus operator),但定义它的操作是取余数(Remainder)。而且当被除数为负数时的行为规定并不明确。后世由C发展而来的语言很多都被这个问题影响了。

    取余数和取模的区别

    余数的定义是(被除数-除数×商)。当被除数是负数,且不能整除时,会产生负的余数。

    而取模在数论中通常使用的是最小正余数。

    那么Math.floorMod()

    直接贴上标准库的Javadoc直观对比:

    操作数a操作数bMath.floorMod(a, b)(a % b)
    +4+3+1+1
    -4+3+2-1
    -4-3-1-1
    +4-3-2+1

    这个方法早在Java 8就有了。

    如果没有Java 8?

    这都什么年代了(((

    没关系,还有Guava能救——IntMath.mod()

  • IEEE 754标准中的十进制浮点数#1:存储编码

    似乎关于IEEE 754标准中十进制浮点数的中文资料不多,所以写了这篇。


    什么是十进制浮点数

    十进制浮点数(Decimal floating-point,DFP)是指以10为底数的浮点数表示法,主要解决的是二进制浮点数(Binary floating-point,BFP,常见的浮点数,底数为2)存在的精度问题(例如在财务计算等场景下,使用二进制浮点数经常无法满足需求)。

    关于IEEE的十进制浮点数的一些历史。

    • IEEE 854中规范引入了十进制浮点数,但其标准不够完善,缺少对用来和其他系统交换的数据编码方式的规定。
    • IEEE 754-2008(IEEE浮点运算标准,此为2008年发布的修订版)中合并了IEEE 854中对十进制浮点数的规范,并且补全了之前的缺陷。ISO/IEC/IEEE 60559:2011标准与此标准内容相同。
    • IEEE 754-2019是现行有效的修订版本,相关的修改不是很多。

    编码

    和二进制浮点数相似,也有符号(Sign)、阶码(Exponent,指数)和尾数(Coefficient)三个部分。但不同点是它不一定都是规格化(Normalized)的,即尾数的最高位不一定是1,所以一些数值可以有多种表示方法,例如1×102=0.1×103;而且多了一个组合位(Combination field),用来扩展阶码和尾数的范围以提高存储空间利用率;剩下的“剩余位”,就是组合位组合剩下的(参见“具体规则”一节的“编码规则”小节)。

    DFP的存储格式:

    长度符号位组合位阶码剩余位(e)尾数剩余位(m)
    32位(Decimal32)15620
    64位(Decimal64)850
    128位(Decimal128)12110
    32k位(Decimal32k)2k+430k-10

    DFP的数据范围:

    长度尾数长度(十进制的数字位数)(p)指数范围(从0到x)数据最大值(Emax归一化后的数据最小值(Emin数据最小非零值(Etiny
    32位719296-95-101
    64位16768384-383-398
    128位34122886144-6143-6176
    32k位(3t)/10+1或者9k-23×2e=48×4k3×2e-11-Emax2-p-Emax

    为了描述的简洁,后续的介绍中会引用表中的字母来代指对应的数据,对照如下:

    • e:阶码剩余位的长度
    • m:尾数剩余位的长度
    • p:尾数以十进制表示的数字位数
    • x:指数的最大取值

    有效数字的两种不同编码方式

    这两种不同的编码方式都可以用于编码阶码和尾数,且不论用哪种方式都不影响编码规则(数据范围相同)。

    • 二进制整数编码的有效位(Binary integer significand field,BIS或BID)
      • 它使用二进制整数(取值在0到(10p-1)之间)来编码有效数字。
      • 适合于在原本用于处理二进制的算数逻辑单元(ALU)上用软件实现DFP。
    • 紧凑十进制编码的有效位(Densely packed decimal significand field)
      • 紧凑十进制编码(Densely packed decimal,DPD)能更好地编码十进制数字,可以加快与二进制浮点数的相互转换。
      • 需要专用的ALU来提高效率,适合用硬件实现的DFP。
      • DPD编码是一种效率较高的BCD编码,但其细节不属于本文的内容,可以参考它的维基百科页面(本文的引用[3])。

    具体规则

    对阶码和尾数的约束

    阶码最多(2+e)位,最高2位二进制只能取00、01、10。

    尾数最多(4+m)位,最高4位二进制只允许0000~1001(即十进制的0~9)。

    编码规则

    符号位的定义和二进制浮点数中的相同,0表示正数,1表示负数。

    当尾数的最高4位是0000~0111(即十进制的0~7)时,省略最高位相同的0,此时组合位前两位是阶码最高2位的三种取值,后三位是尾数剩下的000~111。

    当尾数的最高4位是1000~1001(即十进制的8或9)时,省略最高3位相同的100,此时组合位的前两位是11,再往后两位是阶码最高2位的三种取值,最后一位是尾数剩下的0或者1。

    余下的e位阶码和m位尾数分别存入阶码剩余位和尾数剩余位。

    如果阶码或尾数的编码结果长度n不足以填充所有位时——即长度小于(2+e)或者(4+m),则先填满组合位,剩下的(e-n)或者(m-n)位在左侧补0。

    特殊值的表示

    正负无穷(±∞):组合位是11110,符号位表示正负。

    静默NaN(Quiet NaN,QNaN):组合位为11111,阶码剩余位的最高位是0。

    信号NaN(Signaling NaN,SNaN):组合位为11111,阶码剩余位的最高位是1。

    对于这两种NaN,没有提到的所有位都会被忽略,因此初始化数组为NaN可以通过简单地把内存区域全部填充为某个字节来实现(例如全部填充0x7E=(0111 1110)b,会被解码为SNaN)。

    这两种NaN的主要区别是QNaN用于表示计算无效,例如0/0等,它会静默传播,参与的所有运算结果都为QNaN;而SNaN用于快速失败(Fail-fast),在使用时产生异常。

    参考资料

    1. IEEE Standards Association. IEEE Standard for Floating-Point Arithmetic: IEEE Std 754-2019[S]. New York: IEEE, 2019-07-22. ISBN: 978-1-5044-5924-2. DOI: 10.1109/IEEESTD.2019.8766229. ICS Code: 07.020, 35.080. Available at: https://ieeexplore.ieee.org/servlet/opac?punumber=8766227.
    2. Wikipedia contributors. Decimal floating point[EB/OL]. Wikipedia, The Free Encyclopedia. [2024-09-24]. [2024-11-16]. Available at: https://en.wikipedia.org/w/index.php?title=Decimal_floating_point&oldid=1247444055.
    3. Wikipedia contributors. Densely packed decimal[EB/OL]. Wikipedia, The Free Encyclopedia. [2024-06-30]. [2024-11-18]. Available at: https://en.wikipedia.org/w/index.php?title=Densely_packed_decimal&oldid=1237650875.