☰
RimSort 排序算法深度解析:分层架构、字母顺序排序与拓扑排序的实现原理与实战指南
2026/10/4 1:43:17 网站建设 项目流程
  • 桌面应用
  • 游戏开发
  • 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.

项目地址:https://gitcode.com/gh_mirrors/ri/RimSort
点击查看免费下载

导读

本文以 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):

  1. 先把完整的编译依赖图(compiled_data.deps_graph与rev_deps_graph)过滤为仅包含激活 Mod的子图;
  2. 通过_collect_tier_mods()把已知的层级 0 / 层级 1 Mod 集合用get_dependencies_recursive()(见 app/sort/dependencies.py)递归扩展出全部依赖项;
  3. 通过_collect_tier_three_mods()用get_reverse_dependencies_recursive()从loadBottomMod反向递归扩展出所有依赖它们的 Mod(见 app/sort/dependencies.py);
  4. 用extract_tier_subgraph()(见 app/sort/dependencies.py)为每个层级提取只含层级内边的子图;
  5. 层级 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 中描述的步骤:

  1. Mod 列表根据 Mod 名称按字母顺序排序;
  2. 从 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) raise

toposort每次产出一个「当前已无依赖」的 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 分别对两种算法在依赖图上的行为做了针对性验证,可作为深入阅读算法实现的入口。


五、实战建议:如何选择算法并处理顺序警告

  1. 默认使用拓扑排序:自v1.0.10起这是 RimSort 的默认选择。对于大多数 Mod 列表,数学最优的拓扑顺序能更好地保证「所有规则被遵守」。
  2. 两者结果不同是正常的:字母顺序排序追求「大体字母序 + 规则微调」,拓扑排序追求「纯规则驱动的最优线性序」,因此同一 Mod 列表切换算法后顺序显著变化并不代表出错。
  3. 遇到游戏内加载问题:优先怀疑存在未检测到的「缺失」排序规则。此时应打开规则编辑器(对应 app/windows/rule_editor_panel.py),为相关 Mod 补充loadAfter/loadBefore规则,或使用loadTop/loadBottom将其路由到更高 / 更低层级。
  4. 把新规则回馈社区:将新发现的规则报告给 Mod 作者和社区规则数据库,让更多玩家受益。
  5. 出现「无法排序」警告时:说明依赖图中存在环(循环依赖),按警告详情中的依赖环链条,在规则编辑器中修正冲突的规则即可。

结语

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.

项目地址:https://gitcode.com/gh_mirrors/ri/RimSort
点击查看免费下载

相关推荐

上一篇:TileServer GL 常见问题解决方案
下一篇:AmberELEC 终极指南:5步打造完美复古游戏掌机体验

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询