Class: Factorix::Dependency::Graph
- Inherits:
-
Object
- Object
- Factorix::Dependency::Graph
- Includes:
- TSort
- Defined in:
- lib/factorix/dependency/graph.rb,
lib/factorix/dependency/graph/builder.rb
Overview
Directed graph of MOD dependencies using TSort
This graph represents the dependency relationships between MODs. Nodes are MODs, edges are dependencies. The graph uses Ruby’s TSort module for topological sorting and cycle detection.
Defined Under Namespace
Classes: Builder
Instance Method Summary collapse
-
#add_edge(edge) ⇒ void
Add an edge to the graph.
-
#add_node(node) ⇒ void
Add a node to the graph.
-
#add_uninstalled_mod(mod_info, release, operation: :install) ⇒ void
Add an uninstalled MOD (Category C) to the graph.
-
#cyclic? ⇒ Boolean
Check if the graph contains cycles.
-
#edges ⇒ Array<Factorix::Dependency::Edge>
Get all edges in the graph.
-
#edges_from(mod) ⇒ Array<Factorix::Dependency::Edge>
Get edges from a MOD.
-
#edges_to(mod) ⇒ Array<Factorix::Dependency::Edge>
Get edges to a MOD.
-
#empty? ⇒ Boolean
Check if the graph is empty.
-
#find_enabled_dependents(mod) ⇒ Array<Factorix::MOD>
Find all enabled MODs that have a required dependency on the given MOD.
-
#initialize ⇒ Graph
constructor
Create a new empty dependency graph.
-
#inspect ⇒ String
Detailed inspection string.
-
#node(mod) ⇒ Factorix::Dependency::Node?
Get a node by MOD.
-
#node?(mod) ⇒ Boolean
Check if the graph contains a node for the given MOD.
-
#nodes ⇒ Array<Factorix::Dependency::Node>
Get all nodes.
-
#set_node_operation(mod, operation) ⇒ Node?
Set the planned operation for an existing node.
-
#size ⇒ Integer
Get the number of nodes in the graph.
-
#strongly_connected_components ⇒ Array<Array<Factorix::MOD>>
Find strongly connected components (cycles).
-
#to_s ⇒ String
Get a string representation of the graph.
-
#tsort_each_child(mod) {|Factorix::MOD| ... } ⇒ void
TSort interface: iterate over children of a node.
-
#tsort_each_node {|Factorix::MOD| ... } ⇒ void
TSort interface: iterate over each node.
Constructor Details
#initialize ⇒ Graph
Create a new empty dependency graph
16 17 18 19 |
# File 'lib/factorix/dependency/graph.rb', line 16 def initialize @nodes = {} # MOD => Node @edges = {} # MOD => [Edge] end |
Instance Method Details
#add_edge(edge) ⇒ void
This method returns an undefined value.
Add an edge to the graph
51 52 53 54 55 56 57 |
# File 'lib/factorix/dependency/graph.rb', line 51 def add_edge(edge) from_mod = edge.from_mod raise DependencyGraphError, "Node for #{from_mod} doesn't exist" unless @nodes.key?(from_mod) @edges[from_mod] ||= [] @edges[from_mod] << edge end |
#add_node(node) ⇒ void
This method returns an undefined value.
Add a node to the graph
26 27 28 29 30 31 32 |
# File 'lib/factorix/dependency/graph.rb', line 26 def add_node(node) mod = node.mod raise DependencyGraphError, "Node for #{mod} already exists" if @nodes.key?(mod) @nodes[mod] = node @edges[mod] ||= [] end |
#add_uninstalled_mod(mod_info, release, operation: :install) ⇒ void
This method returns an undefined value.
Add an uninstalled MOD (Category C) to the graph
Creates a node for an uninstalled MOD and adds edges for its dependencies. Used by the install command to extend the graph with MODs fetched from the Portal API.
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
# File 'lib/factorix/dependency/graph.rb', line 68 def add_uninstalled_mod(mod_info, release, operation: :install) mod = MOD[name: mod_info.name] existing_node = @nodes[mod] if existing_node # If already installed but disabled, mark for enabling set_node_operation(mod, :enable) if existing_node.installed? && !existing_node.enabled? return end node = Node[mod:, version: release.version, enabled: false, installed: false, operation:] add_node(node) dependencies = release.info_json[:dependencies] || [] parser = Dependency::Parser.new dependencies.each do |dep_string| dependency = parser.parse(dep_string) next if dependency.mod.base? edge = Edge[from_mod: mod, to_mod: dependency.mod, type: dependency.type, version_requirement: dependency.version_requirement] add_edge(edge) end end |
#cyclic? ⇒ Boolean
Check if the graph contains cycles
162 163 164 165 166 167 |
# File 'lib/factorix/dependency/graph.rb', line 162 def cyclic? tsort false rescue TSort::Cyclic true end |
#edges ⇒ Array<Factorix::Dependency::Edge>
Get all edges in the graph
141 |
# File 'lib/factorix/dependency/graph.rb', line 141 def edges = @edges.values.flatten |
#edges_from(mod) ⇒ Array<Factorix::Dependency::Edge>
Get edges from a MOD
109 |
# File 'lib/factorix/dependency/graph.rb', line 109 def edges_from(mod) = @edges[mod] || [] |
#edges_to(mod) ⇒ Array<Factorix::Dependency::Edge>
Get edges to a MOD
115 |
# File 'lib/factorix/dependency/graph.rb', line 115 def edges_to(mod) = @edges.values.flatten.select {|edge| edge.to_mod == mod } |
#empty? ⇒ Boolean
Check if the graph is empty
157 |
# File 'lib/factorix/dependency/graph.rb', line 157 def empty? = @nodes.empty? |
#find_enabled_dependents(mod) ⇒ Array<Factorix::MOD>
Find all enabled MODs that have a required dependency on the given MOD
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 |
# File 'lib/factorix/dependency/graph.rb', line 121 def find_enabled_dependents(mod) dependents = [] nodes.each do |node| next unless node.enabled? edges_from(node.mod).each do |edge| next unless edge.required? && edge.to_mod == mod dependents << node.mod break end end dependents end |
#inspect ⇒ String
Detailed inspection string
204 205 206 207 |
# File 'lib/factorix/dependency/graph.rb', line 204 def inspect node_list = @nodes.values.join(", ") "#<#{self.class.name} [#{node_list}]>" end |
#node(mod) ⇒ Factorix::Dependency::Node?
Get a node by MOD
98 |
# File 'lib/factorix/dependency/graph.rb', line 98 def node(mod) = @nodes[mod] |
#node?(mod) ⇒ Boolean
Check if the graph contains a node for the given MOD
147 |
# File 'lib/factorix/dependency/graph.rb', line 147 def node?(mod) = @nodes.key?(mod) |
#nodes ⇒ Array<Factorix::Dependency::Node>
Get all nodes
103 |
# File 'lib/factorix/dependency/graph.rb', line 103 def nodes = @nodes.values |
#set_node_operation(mod, operation) ⇒ Node?
Set the planned operation for an existing node
39 40 41 42 43 44 |
# File 'lib/factorix/dependency/graph.rb', line 39 def set_node_operation(mod, operation) node = @nodes[mod] return unless node @nodes[mod] = node.with(operation:) end |
#size ⇒ Integer
Get the number of nodes in the graph
152 |
# File 'lib/factorix/dependency/graph.rb', line 152 def size = @nodes.size |
#strongly_connected_components ⇒ Array<Array<Factorix::MOD>>
Find strongly connected components (cycles)
172 |
# File 'lib/factorix/dependency/graph.rb', line 172 def strongly_connected_components = each_strongly_connected_component.to_a |
#to_s ⇒ String
Get a string representation of the graph
199 |
# File 'lib/factorix/dependency/graph.rb', line 199 def to_s = "#<#{self.class.name} nodes=#{@nodes.size} edges=#{edges.size}>" |
#tsort_each_child(mod) {|Factorix::MOD| ... } ⇒ void
This method returns an undefined value.
TSort interface: iterate over children of a node
185 186 187 188 189 190 191 192 193 194 |
# File 'lib/factorix/dependency/graph.rb', line 185 def tsort_each_child(mod) edges_from(mod).each do |edge| # Only follow required dependency edges for cycle detection # Skip optional, incompatible, load-neutral, and hidden edges # Optional cycles are allowed in Factorio next unless edge.required? yield edge.to_mod if @nodes.key?(edge.to_mod) end end |
#tsort_each_node {|Factorix::MOD| ... } ⇒ void
This method returns an undefined value.
TSort interface: iterate over each node
178 |
# File 'lib/factorix/dependency/graph.rb', line 178 def tsort_each_node(&) = @nodes.each_key(&) |