From 731a451dfebf0f6ddfc945b23cf0e7fced41e0b5 Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Sat, 19 May 2012 01:27:57 +0200 Subject: [PATCH] Move handling of watch graph to separate classes --- .../DependencyResolver/RuleWatchGraph.php | 134 ++++++++++++++++ .../DependencyResolver/RuleWatchNode.php | 84 ++++++++++ src/Composer/DependencyResolver/Solver.php | 144 +++--------------- 3 files changed, 242 insertions(+), 120 deletions(-) create mode 100644 src/Composer/DependencyResolver/RuleWatchGraph.php create mode 100644 src/Composer/DependencyResolver/RuleWatchNode.php diff --git a/src/Composer/DependencyResolver/RuleWatchGraph.php b/src/Composer/DependencyResolver/RuleWatchGraph.php new file mode 100644 index 000000000..df736086e --- /dev/null +++ b/src/Composer/DependencyResolver/RuleWatchGraph.php @@ -0,0 +1,134 @@ + + * Jordi Boggiano + * + * 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 + */ +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; + } +} diff --git a/src/Composer/DependencyResolver/RuleWatchNode.php b/src/Composer/DependencyResolver/RuleWatchNode.php new file mode 100644 index 000000000..c1589bdb0 --- /dev/null +++ b/src/Composer/DependencyResolver/RuleWatchNode.php @@ -0,0 +1,84 @@ + + * Jordi Boggiano + * + * 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 + */ +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; + } + } +} diff --git a/src/Composer/DependencyResolver/Solver.php b/src/Composer/DependencyResolver/Solver.php index 255f9308f..0e6f094ab 100644 --- a/src/Composer/DependencyResolver/Solver.php +++ b/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;