Эх сурвалжийг харах

Move handling of watch graph to separate classes

Nils Adermann 13 жил өмнө
parent
commit
731a451dfe

+ 134 - 0
src/Composer/DependencyResolver/RuleWatchGraph.php

@@ -0,0 +1,134 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *     Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\DependencyResolver;
+
+/**
+ * @author Nils Adermann <naderman@naderman.de>
+ */
+class RuleWatchGraph
+{
+    protected $watches = array();
+
+    /**
+     * Alters watch chains for a rule.
+     *
+     * Next1/2 always points to the next rule that is watching the same package.
+     * The watches array contains rules to start from for each package
+     *
+     */
+    public function insert(RuleWatchNode $node)
+    {
+        // skip simple assertions of the form (A) or (-A)
+        if ($node->getRule()->isAssertion()) {
+            return;
+        }
+
+        if (!isset($this->watches[$node->watch1])) {
+            $this->watches[$node->watch1] = null;
+        }
+
+        $node->next1 = $this->watches[$node->watch1];
+        $this->watches[$node->watch1] = $node;
+
+        if (!isset($this->watches[$node->watch2])) {
+            $this->watches[$node->watch2] = null;
+        }
+
+        $node->next2 = $this->watches[$node->watch2];
+        $this->watches[$node->watch2] = $node;
+    }
+
+    public function contains($literalId)
+    {
+        return isset($this->watches[$literalId]);
+    }
+
+    public function walkLiteral($literalId, $level, $skipCallback, $conflictCallback, $decideCallback)
+    {
+        if (!isset($this->watches[$literalId])) {
+            return;
+        }
+
+        $prevNode = null;
+        for ($node = $this->watches[$literalId]; $node !== null; $prevNode = $node, $node = $nextNode) {
+            $nextNode = $node->getNext($literalId);
+
+            if ($node->getRule()->isDisabled()) {
+                continue;
+            }
+
+            $otherWatch = $node->getOtherWatch($literalId);
+
+            if (call_user_func($skipCallback, $otherWatch)) {
+                continue;
+            }
+
+            $ruleLiterals = $node->getRule()->getLiterals();
+
+            if (sizeof($ruleLiterals) > 2) {
+                foreach ($ruleLiterals as $ruleLiteral) {
+                    if ($otherWatch !== $ruleLiteral->getId() &&
+                        !call_user_func($conflictCallback, $ruleLiteral->getId())) {
+
+                        $node = $this->moveWatch($literalId, $ruleLiteral->getId(), $prevNode, $node, $nextNode);
+
+                        continue 2;
+                    }
+                }
+            }
+
+            // yay, we found a unit clause! try setting it to true
+            if (call_user_func($conflictCallback, $otherWatch)) {
+                return $node->getRule();
+            }
+
+            call_user_func($decideCallback, $otherWatch, $level, $node->getRule());
+        }
+
+        return null;
+    }
+
+    public function moveWatch($fromLiteral, $toLiteral, $prevNode, $node, $nextNode) {
+        if ($fromLiteral == $node->watch1) {
+            $node->watch1 = $toLiteral;
+            $node->next1 = (isset($this->watches[$toLiteral])) ? $this->watches[$toLiteral] : null;
+        } else {
+            $node->watch2 = $toLiteral;
+            $node->next2 = (isset($this->watches[$toLiteral])) ? $this->watches[$toLiteral] : null;
+        }
+
+        if ($prevNode) {
+            if ($prevNode->next1 === $node) {
+                $prevNode->next1 = $nextNode;
+            } else {
+                $prevNode->next2 = $nextNode;
+            }
+        } else {
+            $this->watches[$fromLiteral] = $nextNode;
+        }
+
+        $this->watches[$toLiteral] = $node;
+
+        if ($prevNode) {
+            return $prevNode;
+        }
+
+        $tmpNode = new RuleWatchNode(new Rule(array(), null, null));
+        $tmpNode->watch1 = $fromLiteral;
+        $tmpNode->next1 = $nextNode;
+        $tmpNode->watch2 = $fromLiteral;
+        $tmpNode->next2 = $nextNode;
+
+        return $tmpNode;
+    }
+}

+ 84 - 0
src/Composer/DependencyResolver/RuleWatchNode.php

@@ -0,0 +1,84 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *     Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\DependencyResolver;
+
+/**
+ * @author Nils Adermann <naderman@naderman.de>
+ */
+class RuleWatchNode
+{
+    protected $rule;
+
+    public $watch1;
+    public $watch2;
+
+    public $next1;
+    public $next2;
+
+    public function __construct($rule)
+    {
+        $this->rule = $rule;
+
+        $literals = $rule->getLiterals();
+
+        $this->watch1 = (count($literals) > 0) ? $literals[0]->getId() : 0;
+        $this->watch2 = (count($literals) > 1) ? $literals[1]->getId() : 0;
+    }
+
+    /**
+     * Put watch2 on rule's literal with highest level
+     */
+    public function watch2OnHighest($decisionMap)
+    {
+        $literals = $this->rule->getLiterals();
+
+        // if there are only 2 elements, both are being watched anyway
+        if ($literals < 3) {
+            return;
+        }
+
+        $watchLevel = 0;
+
+        foreach ($literals as $literal) {
+            $level = abs($decisionMap[$literal->getPackageId()]);
+
+            if ($level > $watchLevel) {
+                $this->rule->watch2 = $literal->getId();
+                $watchLevel = $level;
+            }
+        }
+    }
+
+    public function getRule()
+    {
+        return $this->rule;
+    }
+
+    public function getNext($literalId)
+    {
+        if ($this->watch1 == $literalId) {
+            return $this->next1;
+        } else {
+            return $this->next2;
+        }
+    }
+
+    public function getOtherWatch($literalId)
+    {
+        if ($this->watch1 == $literalId) {
+            return $this->watch2;
+        } else {
+            return $this->watch1;
+        }
+    }
+}

+ 24 - 120
src/Composer/DependencyResolver/Solver.php

@@ -31,7 +31,7 @@ class Solver
 
     protected $addedMap = array();
     protected $updateMap = array();
-    protected $watches = array();
+    protected $watchGraph;
     protected $decisionMap;
     protected $installedMap;
 
@@ -51,59 +51,6 @@ class Solver
         $this->ruleSetGenerator = new RuleSetGenerator($policy, $pool);
     }
 
-    /**
-     * Alters watch chains for a rule.
-     *
-     * Next1/2 always points to the next rule that is watching the same package.
-     * The watches array contains rules to start from for each package
-     *
-     */
-    private function addWatchesToRule(Rule $rule)
-    {
-        // skip simple assertions of the form (A) or (-A)
-        if ($rule->isAssertion()) {
-            return;
-        }
-
-        if (!isset($this->watches[$rule->watch1])) {
-            $this->watches[$rule->watch1] = null;
-        }
-
-        $rule->next1 = $this->watches[$rule->watch1];
-        $this->watches[$rule->watch1] = $rule;
-
-        if (!isset($this->watches[$rule->watch2])) {
-            $this->watches[$rule->watch2] = null;
-        }
-
-        $rule->next2 = $this->watches[$rule->watch2];
-        $this->watches[$rule->watch2] = $rule;
-    }
-
-    /**
-     * Put watch2 on rule's literal with highest level
-     */
-    private function watch2OnHighest(Rule $rule)
-    {
-        $literals = $rule->getLiterals();
-
-        // if there are only 2 elements, both are being watched anyway
-        if ($literals < 3) {
-            return;
-        }
-
-        $watchLevel = 0;
-
-        foreach ($literals as $literal) {
-            $level = abs($this->decisionMap[$literal->getPackageId()]);
-
-            if ($level > $watchLevel) {
-                $rule->watch2 = $literal->getId();
-                $watchLevel = $level;
-            }
-        }
-    }
-
     private function findDecisionRule(PackageInterface $package)
     {
         foreach ($this->decisionQueue as $i => $literal) {
@@ -265,9 +212,10 @@ class Solver
         }
 
         $this->rules = $this->ruleSetGenerator->getRulesFor($this->jobs, $this->installedMap);
+        $this->watchGraph = new RuleWatchGraph;
 
         foreach ($this->rules as $rule) {
-            $this->addWatchesToRule($rule);
+            $this->watchGraph->insert(new RuleWatchNode($rule));
         }
 
         /* make decisions based on job/update assertions */
@@ -313,6 +261,13 @@ class Solver
         }
     }
 
+    public function decide($literal, $level, $why)
+    {
+        $this->addDecisionId($literal, $level);
+        $this->decisionQueue[] = $this->literalFromId($literal);
+        $this->decisionQueueWhy[] = $why;
+    }
+
     protected function decisionsContain(Literal $l)
     {
         return (
@@ -321,7 +276,7 @@ class Solver
         );
     }
 
-    protected function decisionsContainId($literalId)
+    public function decisionsContainId($literalId)
     {
         $packageId = abs($literalId);
         return (
@@ -344,7 +299,7 @@ class Solver
         );
     }
 
-    protected function decisionsConflictId($literalId)
+    public function decisionsConflictId($literalId)
     {
         $packageId = abs($literalId);
         return (
@@ -390,68 +345,16 @@ class Solver
 
             $this->propagateIndex++;
 
-            // /* foreach rule where 'pkg' is now FALSE */
-            //for (rp = watches + pkg; *rp; rp = next_rp)
-            if (!isset($this->watches[$literal->getId()])) {
-                continue;
-            }
-
-            $prevRule = null;
-            for ($rule = $this->watches[$literal->getId()]; $rule !== null; $prevRule = $rule, $rule = $nextRule) {
-                $nextRule = $rule->getNext($literal);
-
-                if ($rule->isDisabled()) {
-                    continue;
-                }
-
-                $otherWatch = $rule->getOtherWatch($literal);
-
-                if ($this->decisionsContainId($otherWatch)) {
-                    continue;
-                }
-
-                $ruleLiterals = $rule->getLiterals();
-
-                if (sizeof($ruleLiterals) > 2) {
-                    foreach ($ruleLiterals as $ruleLiteral) {
-                        if ($otherWatch !== $ruleLiteral->getId() &&
-                            !$this->decisionsConflict($ruleLiteral)) {
-
-                            if ($literal->getId() === $rule->watch1) {
-                                $rule->watch1 = $ruleLiteral->getId();
-                                $rule->next1 = (isset($this->watches[$ruleLiteral->getId()])) ? $this->watches[$ruleLiteral->getId()] : null;
-                            } else {
-                                $rule->watch2 = $ruleLiteral->getId();
-                                $rule->next2 = (isset($this->watches[$ruleLiteral->getId()])) ? $this->watches[$ruleLiteral->getId()] : null;
-                            }
-
-                            if ($prevRule) {
-                                if ($prevRule->next1 == $rule) {
-                                    $prevRule->next1 = $nextRule;
-                                } else {
-                                    $prevRule->next2 = $nextRule;
-                                }
-                            } else {
-                                $this->watches[$literal->getId()] = $nextRule;
-                            }
-
-                            $this->watches[$ruleLiteral->getId()] = $rule;
-
-                            $rule = $prevRule;
-                            continue 2;
-                        }
-                    }
-                }
-
-                // yay, we found a unit clause! try setting it to true
-                if ($this->decisionsConflictId($otherWatch)) {
-                    return $rule;
-                }
-
-                $this->addDecisionId($otherWatch, $level);
+            $conflict = $this->watchGraph->walkLiteral(
+                $literal->getId(),
+                $level,
+                array($this, 'decisionsContainId'),
+                array($this, 'decisionsConflictId'),
+                array($this, 'decide')
+            );
 
-                $this->decisionQueue[] = $this->literalFromId($otherWatch);
-                $this->decisionQueueWhy[] = $rule;
+            if ($conflict) {
+                return $conflict;
             }
         }
 
@@ -550,8 +453,9 @@ class Solver
 
             $this->learnedWhy[$newRule->getId()] = $why;
 
-            $this->watch2OnHighest($newRule);
-            $this->addWatchesToRule($newRule);
+            $ruleNode = new RuleWatchNode($newRule);
+            $ruleNode->watch2OnHighest($this->decisionMap);
+            $this->watchGraph->insert($ruleNode);
 
             $this->addDecision($learnLiteral, $level);
             $this->decisionQueue[] = $learnLiteral;