Class: Factorix::Dependency::Graph

Inherits:
Object
  • Object
show all
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

Constructor Details

#initializeGraph

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

Parameters:

Raises:



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

Parameters:

Raises:



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.

Parameters:

  • mod_info (API::MODInfo)

    MOD information from Portal API

  • release (API::Release)

    The release to install

  • operation (Symbol) (defaults to: :install)

    The operation to perform (default: :install)



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

Returns:

  • (Boolean)

    true if the graph has cycles



162
163
164
165
166
167
# File 'lib/factorix/dependency/graph.rb', line 162

def cyclic?
  tsort
  false
rescue TSort::Cyclic
  true
end

#edgesArray<Factorix::Dependency::Edge>

Get all edges in the graph

Returns:



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

Parameters:

Returns:



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

Parameters:

Returns:



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

Returns:

  • (Boolean)


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

Parameters:

Returns:



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

#inspectString

Detailed inspection string

Returns:

  • (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

Parameters:

Returns:



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

Parameters:

Returns:

  • (Boolean)


147
# File 'lib/factorix/dependency/graph.rb', line 147

def node?(mod) = @nodes.key?(mod)

#nodesArray<Factorix::Dependency::Node>

Get all nodes

Returns:



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

Parameters:

  • mod (Factorix::MOD)

    The MOD to update

  • operation (Symbol, nil)

    The planned operation (:install, :enable, :disable, :uninstall, or nil)

Returns:

  • (Node, nil)

    The updated node, or nil if node doesn’t exist



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

#sizeInteger

Get the number of nodes in the graph

Returns:

  • (Integer)


152
# File 'lib/factorix/dependency/graph.rb', line 152

def size = @nodes.size

#strongly_connected_componentsArray<Array<Factorix::MOD>>

Find strongly connected components (cycles)

Returns:



172
# File 'lib/factorix/dependency/graph.rb', line 172

def strongly_connected_components = each_strongly_connected_component.to_a

#to_sString

Get a string representation of the graph

Returns:

  • (String)


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

Parameters:

Yields:



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

Yields:



178
# File 'lib/factorix/dependency/graph.rb', line 178

def tsort_each_node(&) = @nodes.each_key(&)