diff --git a/app/Util/DependencyGraph.php b/app/Util/DependencyGraph.php new file mode 100644 index 0000000..d589ed3 --- /dev/null +++ b/app/Util/DependencyGraph.php @@ -0,0 +1,123 @@ +nodes = []; + } + + public function add(string $name, array $depends_on = []): void + { + if (!isset($this->nodes[$name])) + { + $node = new DependencyGraphNode($name); + $this->nodes[$name] = $node; + $node->DependsOn = $depends_on; + } + else + { + // Probably unnecessary -- if the same node is added multiple times + // with different dependencies something's probably way messed up, + // but... we'll try our best?! + $this->nodes[$name] = array_unique(array_merge($this->nodes[$name]->DependsOn, $depends_on)); + } + } + + public function link(): void + { + foreach ($this->nodes as $node) + { + foreach ($node->DependsOn as $depends_on) + { + if (!isset($this->nodes[$depends_on])) + { + throw new \Exception('Unmet dependency on node: '.$depends_on); + } + + if (!in_array($node->Name, $this->nodes[$depends_on]->DependedOnBy)) + { + $this->nodes[$depends_on]->DependedOnBy[] = $node->Name; + } + } + } + } + + public function flatten(): array + { + $state = []; + + // Find leaf nodes, add each one to our state array. + foreach ($this->nodes as $node) + { + if (sizeof($node->DependedOnBy) == 0) + { + $state[] = $node; + } + } + + // Then walk through the array and insert each node's dependencies before + // it recursively. + $iter = 0; + for ($i = 0; $i < sizeof($state); $i++) + { + if ($state[$i]->Processed) + { + continue; + } + $state[$i]->Processed = true; + $depends_on = []; + foreach ($state[$i]->DependsOn as $depends_on_name) + { + $depends_on[] = $this->nodes[$depends_on_name]; + } + array_splice($state, $i, 0, $depends_on); + --$i; + } + + // Remove all but the first appearance of a node. + // array_unique guarantees that the _first_ element will be retained, + // so this satisfies what we're trying to do. + return array_unique(array_map(static function($node) { return $node->Name; }, $state)); + } +} + +class DependencyGraphNode +{ + /** @var string */ + public $Name; + + /** @var string */ + public $DependsOn; + + /** @var string */ + public $DependedOnBy; + + /** @var bool */ + public $Processed; + + public function __construct(string $name) + { + $this->Name = $name; + $this->DependsOn = []; + $this->DependedOnBy = []; + $this->Processed = false; + } + + public function dependsOn(string $node): void + { + $this->DependsOn[] = $node; + } + + public function dependedOnBy(string $node): void + { + $this->DependedOnBy[] = $node; + } +}