dependency.gd 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. class_name _ModLoaderDependency
  2. extends RefCounted
  3. # This Class provides methods for working with dependencies.
  4. # Currently all of the included methods are internal and should only be used by the mod loader itself.
  5. const LOG_NAME := "ModLoader:Dependency"
  6. # Run dependency checks on a mod, checking any dependencies it lists in its
  7. # mod_manifest (ie. its manifest.json file). If a mod depends on another mod that
  8. # hasn't been loaded, the dependent mod won't be loaded, if it is a required dependency.
  9. #
  10. # Parameters:
  11. # - mod: A ModData object representing the mod being checked.
  12. # - dependency_chain: An array that stores the IDs of the mods that have already
  13. # been checked to avoid circular dependencies.
  14. # - is_required: A boolean indicating whether the mod is a required or optional
  15. # dependency. Optional dependencies will not prevent the dependent mod from
  16. # loading if they are missing.
  17. #
  18. # Returns: A boolean indicating whether a circular dependency was detected.
  19. static func check_dependencies(mod: ModData, is_required := true, dependency_chain := []) -> bool:
  20. var dependency_type := "required" if is_required else "optional"
  21. # Get the dependency array based on the is_required flag
  22. var dependencies := mod.manifest.dependencies if is_required else mod.manifest.optional_dependencies
  23. # Get the ID of the mod being checked
  24. var mod_id := mod.dir_name
  25. ModLoaderLog.debug("Checking dependencies - mod_id: %s %s dependencies: %s" % [mod_id, dependency_type, dependencies], LOG_NAME)
  26. # Check for circular dependency
  27. if mod_id in dependency_chain:
  28. ModLoaderLog.debug("%s dependency check - circular dependency detected for mod with ID %s." % [dependency_type.capitalize(), mod_id], LOG_NAME)
  29. return true
  30. # Add mod_id to dependency_chain to avoid circular dependencies
  31. dependency_chain.append(mod_id)
  32. # Loop through each dependency listed in the mod's manifest
  33. for dependency_id in dependencies:
  34. # Check if dependency is missing
  35. if not ModLoaderStore.mod_data.has(dependency_id) or not ModLoaderStore.mod_data[dependency_id].is_loadable or not ModLoaderStore.mod_data[dependency_id].is_active:
  36. # Skip to the next dependency if it's optional
  37. if not is_required:
  38. ModLoaderLog.info("Missing optional dependency - mod: -> %s dependency -> %s" % [mod_id, dependency_id], LOG_NAME)
  39. continue
  40. _handle_missing_dependency(mod_id, dependency_id)
  41. # Flag the mod so it's not loaded later
  42. mod.is_loadable = false
  43. else:
  44. var dependency: ModData = ModLoaderStore.mod_data[dependency_id]
  45. # Increase the importance score of the dependency by 1
  46. dependency.importance += 1
  47. ModLoaderLog.debug("%s dependency -> %s importance -> %s" % [dependency_type.capitalize(), dependency_id, dependency.importance], LOG_NAME)
  48. # Check if the dependency has any dependencies of its own
  49. if dependency.manifest.dependencies.size() > 0:
  50. if check_dependencies(dependency, is_required, dependency_chain):
  51. return true
  52. # Return false if all dependencies have been resolved
  53. return false
  54. # Run load before check on a mod, checking any load_before entries it lists in its
  55. # mod_manifest (ie. its manifest.json file). Add the mod to the dependency of the
  56. # mods inside the load_before array.
  57. static func check_load_before(mod: ModData) -> void:
  58. # Skip if no entries in load_before
  59. if mod.manifest.load_before.size() == 0:
  60. return
  61. ModLoaderLog.debug("Load before - In mod %s detected." % mod.dir_name, LOG_NAME)
  62. # For each mod id in load_before
  63. for load_before_id in mod.manifest.load_before:
  64. # Check if the load_before mod exists
  65. if not ModLoaderStore.mod_data.has(load_before_id):
  66. ModLoaderLog.debug("Load before - Skipping %s because it's missing" % load_before_id, LOG_NAME)
  67. continue
  68. var load_before_mod_dependencies := ModLoaderStore.mod_data[load_before_id].manifest.dependencies as PackedStringArray
  69. # Check if it's already a dependency
  70. if mod.dir_name in load_before_mod_dependencies:
  71. ModLoaderLog.debug("Load before - Skipping because it's already a dependency for %s" % load_before_id, LOG_NAME)
  72. continue
  73. # Add the mod to the dependency array
  74. load_before_mod_dependencies.append(mod.dir_name)
  75. ModLoaderStore.mod_data[load_before_id].manifest.dependencies = load_before_mod_dependencies
  76. ModLoaderLog.debug("Load before - Added %s as dependency for %s" % [mod.dir_name, load_before_id], LOG_NAME)
  77. # Get the load order of mods, using a custom sorter
  78. static func get_load_order(mod_data_array: Array) -> Array:
  79. # Add loadable mods to the mod load order array
  80. for mod in mod_data_array:
  81. mod = mod as ModData
  82. if mod.is_loadable:
  83. ModLoaderStore.mod_load_order.append(mod)
  84. # Sort mods by the importance value
  85. ModLoaderStore.mod_load_order.sort_custom(Callable(CompareImportance, "_compare_importance"))
  86. return ModLoaderStore.mod_load_order
  87. # Handles a missing dependency for a given mod ID. Logs an error message indicating the missing dependency and adds
  88. # the dependency ID to the mod_missing_dependencies dictionary for the specified mod.
  89. static func _handle_missing_dependency(mod_id: String, dependency_id: String) -> void:
  90. ModLoaderLog.error("Missing dependency - mod: -> %s dependency -> %s" % [mod_id, dependency_id], LOG_NAME)
  91. # if mod is not present in the missing dependencies array
  92. if not ModLoaderStore.mod_missing_dependencies.has(mod_id):
  93. # add it
  94. ModLoaderStore.mod_missing_dependencies[mod_id] = []
  95. ModLoaderStore.mod_missing_dependencies[mod_id].append(dependency_id)
  96. # Inner class so the sort function can be called by get_load_order()
  97. class CompareImportance:
  98. # Custom sorter that orders mods by important
  99. static func _compare_importance(a: ModData, b: ModData) -> bool:
  100. if a.importance > b.importance:
  101. return true # a -> b
  102. else:
  103. return false # b -> a