- 桌面应用
- 游戏开发
- CLI
【免费下载链接】RimSort
RimSort is an open source mod manager for the video game RimWorld. There is support for Linux, Mac, and Windows, built from the ground up to be a reliable, community-managed alternative to RimPy Mod Manager.
导读
本文以 RimSort 官方用户指南中《排序算法》一章为主体,结合仓库源码(app/sort/、app/controllers/sort_controller.py)与测试用例,系统讲解 RimSort 对激活 Mod 列表排序的完整流程:四层分层的整体架构、字母顺序排序(Alphabetical)的「强制插入」机制、以及自v1.0.10起默认采用的拓扑排序(Topological)算法。读完本文,你将理解两种算法各自保证什么、为何不同算法会得出不同的「正确」顺序、loadTop/loadBottom规则的真实作用,以及如何在遇到排序冲突时通过规则编辑器补充缺失规则。
一、两种算法,一个统一前提:先分层,再排序
RimSort 默认提供两种排序算法来对激活的 Mod 列表排序:字母顺序排序与拓扑排序。自v1.0.10起,默认使用拓扑排序。这两种算法的入口被统一定义为SortMethod枚举(见 app/utils/constants.py):
class SortMethod(str, Enum): ALPHABETICAL = "Alphabetical" TOPOLOGICAL = "Topological"无论选择哪种算法,RimSort都不会对整个激活 Mod 列表做单次遍历排序。相反,Sorter会先将激活列表划分为四个层级,用所选算法独立对每个层级的子图排序,然后按层级顺序拼接结果。
⚠️重要提醒:不同排序算法可能产生不同的「正确」排序结果。这种意义上的正确排序,仅指遵循所有定义规则的排序(即在 RimSort 中不会出现顺序警告)。如果你在使用某些算法时,遇到游戏内问题,很可能是存在未检测到的「缺失」排序规则,此时你需要通过规则编辑器手动定义该规则。我们强烈建议你将新发现的规则报告给 Mod 作者和社区规则数据库!
四个层级的具体划分
- 层级 0— Core、Harmony、Prepatcher、官方 RimWorld DLC,以及所有递归依赖于它们的 Mod。
- 层级 1— 已知框架 Mod(如 Universum、Vanilla Expanded Framework、XMLExtensions),以及带有
loadTop(强制排序至列表顶部)标记的 Mod,加上它们的递归依赖项。 - 层级 2— 其他所有 Mod。
- 层级 3— 带有
loadBottom(强制排序至列表底部)标记的 Mod,以及递归依赖于它们的 Mod。
这意味着你通过规则编辑器或社区规则数据库定义的loadTop/loadBottom规则并不会直接重排 Mod,而是将 Mod路由到更高或更低的层级。带有loadTop标记的 Mod 总是排在任何未标记 Mod 的_之前_,带有loadBottom标记的 Mod 总是排在任何未标记 Mod 的_之后_。
源码视角:四层划分是如何实现的
层级划分的核心逻辑集中在Sorter.generate_dependency_graphs()(见 app/controllers/sort_controller.py):
- 先把完整的编译依赖图(
compiled_data.deps_graph与rev_deps_graph)过滤为仅包含激活 Mod的子图; - 通过
_collect_tier_mods()把已知的层级 0 / 层级 1 Mod 集合用get_dependencies_recursive()(见 app/sort/dependencies.py)递归扩展出全部依赖项; - 通过
_collect_tier_three_mods()用get_reverse_dependencies_recursive()从loadBottomMod反向递归扩展出所有依赖它们的 Mod(见 app/sort/dependencies.py); - 用
extract_tier_subgraph()(见 app/sort/dependencies.py)为每个层级提取只含层级内边的子图; - 层级 2 = 激活 Mod 减去其他三个层级的所有 Mod。
# 层级 2:激活 Mod 中不属于任何其他层级的 Mod all_tiered = tier_zero_mods | tier_one_mods | tier_three_mods tier_two_mods = self._active_package_ids - all_tiered tier_two_graph = extract_tier_subgraph(active_deps, tier_two_mods)「已知框架 Mod」与「Core / Harmony / DLC」并非硬编码在排序逻辑里,而是维护在 app/utils/constants.py 中,例如:
KNOWN_TIER_ZERO_MODS = { "zetrith.prepatcher", "brrainz.harmony", "brrainz.visualexceptions", "ludeon.rimworld", "ludeon.rimworld.royalty", "ludeon.rimworld.ideology", "ludeon.rimworld.biotech", "ludeon.rimworld.anomaly", "ludeon.rimworld.odyssey", }层级 1 则收录了unlimitedhugs.hugslib、imranfish.xmlextensions、oskarpotocki.vanillafactionsexpanded.core、aoba.framework、ebsg.framework、smashphil.vehicleframework等知名框架(见 app/utils/constants.py)。这份名单本身也会随社区规则数据库的维护持续更新。
而loadTop/loadBottom之所以能「路由」Mod,是因为它们最终被编译进了CompiledDependencyData:在 app/models/metadata/metadata_structure.py 中,load_first规则的 Mod 被加入tier_one_mods,load_last规则的 Mod 被加入tier_three_mods。在规则编辑器界面中,loadTop/loadBottom对应的正是「强制加载至顶部 / 底部」复选框(见 app/windows/rule_editor_panel.py)。
二、字母顺序排序算法(Alphabetical)
第一种算法,字母顺序排序,采用更简单的方法进行合理排序。在每个层级内,该方法在应用规则之前先按字母顺序排列你的 Mod。
该算法大致遵循 RimPy 自动排序 Wiki 中描述的步骤:
- Mod 列表根据 Mod 名称按字母顺序排序;
- 从 Mod 的
About.xml文件中提取的规则与外部提供的元数据将被_强制应用_(下文详述具体含义)。
最终结果是一个大体按字母顺序排列的 Mod 列表,其中已定义的加载顺序规则会对部分 Mod 位置进行调整。需要优先加载的 Mod 会已处于合适位置(因字母排序),或被强制插入到依赖项之前。
「强制应用」到底是什么意思?
通过示例说明:假设初始 Mod 列表为[A, B, C, D, E],已按字母排序。RimPy 开始逐个将它们插入到最终加载顺序,首先插入A。ModA无依赖,因此在第一次迭代后顺序为[A]。
接下来插入B,但B有依赖项loadAfter: [D, E](可能是在它的About.xml中指定的)。此时 RimPy 会强制将D和E插入到B之前,但位于A之后。如果D和E自身无其他依赖,插入B后的顺序变为[A, D, E, B]。
但当依赖项之间存在相互规则时(例如D需在E之后加载),直接插入会导致规则冲突。因此算法在插入每个依赖项时,会遍历已插入的依赖项子列表,寻找当前依赖项所依赖的最新出现项。最终加载顺序的迭代过程如下:
[A] [A, B] [A, E, B] [A, E, D, B] ...强制应用本质上指算法以递归方式将依赖项直接插入到依赖它们的 Mod 之前。
源码视角:do_alphabetical_sort与_recursively_force_insert
上述流程在仓库中对应 app/sort/alphabetical_sort.py 的do_alphabetical_sort():先按 Mod 名称(转为小写)对所有激活 Mod 排序(第 42-44 行),然后逐个把 package_id 追加进mods_load_order,每追加一个就调用_recursively_force_insert()(第 73-107 行)递归处理它的依赖:
- 依赖项按名称字母排序后逐个检查;
- 若依赖尚未插入,则从当前插入点往前扫描已插入的子列表,找到「当前依赖项所依赖的最新出现项」之后的位置插入(对应原文档描述的「遍历已插入的依赖项子列表」逻辑);
- 插入后,再对刚插入的依赖项递归执行同样的强制插入。
需要注意的是,Sorter在构造时若收到SortMethod.ALPHABETICAL,会打出一条日志警告,提示字母顺序排序已被弃用,在复杂 Mod 列表下可能产生错误结果,建议切换到拓扑排序(见 app/controllers/sort_controller.py)。
该算法保证什么?
在无冲突的加载规则的前提下,该算法保证遵守所有加载顺序规则。因为当算法遍历按字母排序的 Mod 列表,并逐个插入时,当前 Mod 的依赖项会被强制前置,或依赖项已存在于列表更靠前位置。
三、拓扑排序(Topological):默认算法(v1.0.10)
第二种算法,拓扑排序,采用拓扑排序思想来整理 Mod,也是自v1.0.10起的默认算法。
拓扑排序算法使用toposort包将 Mod 列表排序为「拓扑层级」:第一拓扑层的 Mod 不依赖任何其他 Mod;当第一层 Mod 被处理后,第二层 Mod 将不再依赖其他 Mod;依此类推。这是对有向图线性排序的数学解法——Mod 的loadAfter和loadBefore关系本质上构成有向图。
同一拓扑层级内的 Mod 顺序无关紧要。但 RimSort 实现上会在将拓扑层级加入最终加载顺序前,对其中的 Mod 进行字母顺序排序。
源码视角:do_topo_sort
实现位于 app/sort/topo_sort.py:
try: sorted_dependencies = list(toposort(dependency_graph)) except CircularDependencyError: find_circular_dependencies(dependency_graph) raisetoposort每次产出一个「当前已无依赖」的 Mod 集合(拓扑层级),RimSort 会按 Mod 名称对该集合做小写字母排序(保证层内顺序稳定、可复现),再追加到最终结果中:
for level in sorted_dependencies: temp_mod_list = [ packageid_to_path[package_id] for package_id in level if package_id in packageid_to_path ] sorted_temp = sorted( temp_mod_list, key=lambda p: safe_name(path_to_name.get(p)), reverse=False, ) reordered.extend(sorted_temp)有向图从何而来?在 app/models/metadata/metadata_structure.py 的CompiledDependencyData.build()中,每个 Mod 的loadAfter规则生成deps_graph[pid].add(dep)边,loadBefore规则生成反向边deps_graph[target].add(pid),同时维护反向图rev_deps_graph。此外,若启用相应设置,modDependencies声明会被当作隐式loadAfter边处理;当推断出的依赖与显式规则冲突时(可能产生环),显式规则优先,冲突的推断边会被静默丢弃并记录日志(第 650-664 行)。
遇到环怎么办:find_circular_dependencies
拓扑排序只在无环有向图上才能得到完整结果。若toposort抛出CircularDependencyError,do_topo_sort会调用find_circular_dependencies()(见 app/sort/topo_sort.py):用networkx的nx.simple_cycles()找出所有环,把每个环格式化为A -> B -> C -> A的形式记录到日志,并弹出「无法排序」的警告对话框,详情中列出所有依赖环,供你在规则编辑器中排查修复。此时Sorter.sort()会捕获异常并返回(False, []),放弃本次排序(见 app/controllers/sort_controller.py)。
该算法保证什么?
在无冲突规则的情况下,该算法保证数学意义上的最优排序。注意,最终加载顺序通常与 RimPy 算法结果显著不同,这是符合预期的,因为两种算法的排序逻辑完全不同。
四、测试验证:四层架构的行为约定
仓库中的测试用例(tests/sort/test_sort_controller.py)把上述四层架构的行为固化为可验证的约定,可以帮助你精确理解分层语义:
test_tier_zero_sorted_first— 层级 0 的 Mod(如ludeon.rimworld)总是最先输出;test_tier_one_after_zero_before_two— 层级 1(框架 Mod)排在层级 0 之后、层级 2(普通 Mod)之前;test_tier_three_sorted_last— 层级 3(loadBottomMod)总是排在最后;test_tier_one_transitive_deps_included— 依赖层级 1 Mod 的 Mod 会被一并拉入层级 1;test_reverse_deps_pulled_into_tier_three— 依赖层级 3 Mod 的 Mod 会通过反向图被拉入层级 3;test_inactive_tier_mods_excluded— 未激活的层级 Mod 不会进入排序结果。
另外 tests/sort/test_topo_sort.py 与 tests/sort/test_alphabetical_sort.py 分别对两种算法在依赖图上的行为做了针对性验证,可作为深入阅读算法实现的入口。
五、实战建议:如何选择算法并处理顺序警告
- 默认使用拓扑排序:自
v1.0.10起这是 RimSort 的默认选择。对于大多数 Mod 列表,数学最优的拓扑顺序能更好地保证「所有规则被遵守」。 - 两者结果不同是正常的:字母顺序排序追求「大体字母序 + 规则微调」,拓扑排序追求「纯规则驱动的最优线性序」,因此同一 Mod 列表切换算法后顺序显著变化并不代表出错。
- 遇到游戏内加载问题:优先怀疑存在未检测到的「缺失」排序规则。此时应打开规则编辑器(对应 app/windows/rule_editor_panel.py),为相关 Mod 补充
loadAfter/loadBefore规则,或使用loadTop/loadBottom将其路由到更高 / 更低层级。 - 把新规则回馈社区:将新发现的规则报告给 Mod 作者和社区规则数据库,让更多玩家受益。
- 出现「无法排序」警告时:说明依赖图中存在环(循环依赖),按警告详情中的依赖环链条,在规则编辑器中修正冲突的规则即可。
结语
RimSort 的排序系统把「分层路由」与「层内算法」解耦:四层架构用KNOWN_TIER_ZERO_MODS/KNOWN_TIER_ONE_MODS名单与loadTop/loadBottom规则把 Mod 分派到正确的优先级区间,层内再由字母顺序排序(递归强制插入)或拓扑排序(toposort分层 + 层内字母序)完成细粒度排列。理解这一分层结构,是排查排序警告、编写有效规则的钥匙。相关代码与测试全部集中在 app/sort/、app/controllers/sort_controller.py 与 tests/sort/ 目录中,按图索骥即可深入学习。
- 桌面应用
- 游戏开发
- CLI
【免费下载链接】RimSort
RimSort is an open source mod manager for the video game RimWorld. There is support for Linux, Mac, and Windows, built from the ground up to be a reliable, community-managed alternative to RimPy Mod Manager.
相关推荐
理解拓扑排序:算法原理与LeetCode实战
理解拓扑排序:算法原理与LeetCode实战 1. 拓扑排序基础概念 拓扑排序是一种对有向无环图(DAG)进行线性排序的算法。这种排序满足一个关键特性:对于图中
教程文档知识库RimSort 排序算法深度解析:从 Tiered 分层到 Topological / Alphabetical 两种排序实现
RimSort 排序算法深度解析:从 Tiered 分层到 Topological / Alphabetical 两种排序实现 RimSort( RimWorl
桌面应用游戏开发CLIRimSort 排序算法深度解析:四层分区机制与 Alphabetical / Topological 两种排序器的原理、源码与实战
RimSort 排序算法深度解析:四层分区机制与 Alphabetical / Topological 两种排序器的原理、源码与实战 RimSort(RimWo
桌面应用游戏开发CLI
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考