Changeset View
Changeset View
Standalone View
Standalone View
src/hardpoint/ArcanistHardpointEngine.php
- This file was added.
| <?php | |||||
| final class ArcanistHardpointEngine | |||||
| extends Phobject { | |||||
| private $queries; | |||||
| private $queryHardpointMap = array(); | |||||
| private $requests = array(); | |||||
| private $futureIterator; | |||||
| private $waitFutures = array(); | |||||
| public function setQueries(array $queries) { | |||||
| assert_instances_of($queries, 'ArcanistHardpointQuery'); | |||||
| $this->queries = $queries; | |||||
| $this->queryHardpointMap = null; | |||||
| return $this; | |||||
| } | |||||
| private function getQueriesForHardpoint($hardpoint) { | |||||
| if ($this->queryHardpointMap === null) { | |||||
| $map = array(); | |||||
| foreach ($this->queries as $query_key => $query) { | |||||
| $query->setHardpointEngine($this); | |||||
| $hardpoints = $query->getHardpoints(); | |||||
| foreach ($hardpoints as $query_hardpoint) { | |||||
| $map[$query_hardpoint][$query_key] = $query; | |||||
| } | |||||
| } | |||||
| $this->queryHardpointMap = $map; | |||||
| } | |||||
| return idx($this->queryHardpointMap, $hardpoint, array()); | |||||
| } | |||||
| public function requestHardpoints(array $objects, array $requests) { | |||||
| assert_instances_of($objects, 'ArcanistHardpointObject'); | |||||
| $results = array(); | |||||
| foreach ($requests as $request) { | |||||
| $request = ArcanistHardpointRequest::newFromSpecification($request) | |||||
| ->setEngine($this) | |||||
| ->setObjects($objects); | |||||
| $this->requests[] = $request; | |||||
| $this->startRequest($request); | |||||
| $results[] = $request; | |||||
| } | |||||
| return ArcanistHardpointRequestList::newFromRequests($results); | |||||
| } | |||||
| private function startRequest(ArcanistHardpointRequest $request) { | |||||
| $objects = $request->getObjects(); | |||||
| $hardpoint = $request->getHardpoint(); | |||||
| $queries = $this->getQueriesForHardpoint($hardpoint); | |||||
| $load = array(); | |||||
| foreach ($objects as $object_key => $object) { | |||||
| if (!$object->hasHardpoint($hardpoint)) { | |||||
| throw new Exception( | |||||
| pht( | |||||
| 'Object (with key "%s", of type "%s") has no hardpoint "%s". '. | |||||
| 'Hardpoints on this object are: %s.', | |||||
| $object_key, | |||||
| phutil_describe_type($object), | |||||
| $hardpoint, | |||||
| $object->getHardpointList()->getHardpointListForDisplay())); | |||||
| } | |||||
| // If the object already has the hardpoint attached, we don't have to | |||||
| // do anything. Throw the object away. | |||||
| if ($object->hasAttachedHardpoint($hardpoint)) { | |||||
| unset($objects[$object_key]); | |||||
| continue; | |||||
| } | |||||
| $any_query = false; | |||||
| foreach ($queries as $query_key => $query) { | |||||
| if (!$query->canLoadObject($object)) { | |||||
| continue; | |||||
| } | |||||
| $any_query = true; | |||||
| $load[$query_key][$object_key] = $object; | |||||
| } | |||||
| if (!$any_query) { | |||||
| throw new Exception( | |||||
| pht( | |||||
| 'No query exists which can load hardpoint "%s" for object '. | |||||
| '(with key "%s" of type "%s").', | |||||
| $hardpoint, | |||||
| $object_key, | |||||
| phutil_describe_type($object))); | |||||
| } | |||||
| } | |||||
| if (!$objects) { | |||||
| return; | |||||
| } | |||||
| $any_object = head($objects); | |||||
| $list = $object->getHardpointList(); | |||||
| $definition = $list->getHardpointDefinition($any_object, $hardpoint); | |||||
| $is_vector = ($definition->isVectorHardpoint()); | |||||
| if ($is_vector) { | |||||
| foreach ($objects as $object) { | |||||
| $object->attachHardpoint($hardpoint, array()); | |||||
| } | |||||
| } | |||||
| $request->setHardpointDefinition($definition); | |||||
| foreach ($load as $query_key => $object_map) { | |||||
| $query = id(clone $queries[$query_key]); | |||||
| $task = $request->newTask() | |||||
| ->setQuery($query) | |||||
| ->setObjects($object_map); | |||||
| } | |||||
| } | |||||
| public function waitForRequests(array $wait_requests) { | |||||
| foreach ($wait_requests as $wait_key => $wait_request) { | |||||
| if ($wait_request->getEngine() !== $this) { | |||||
| throw new Exception( | |||||
| pht( | |||||
| 'Attempting to wait on a hardpoint request (with index "%s", for '. | |||||
| 'hardpoint "%s") that is part of a different engine.', | |||||
| $wait_key, | |||||
| $wait_request->getHardpoint())); | |||||
| } | |||||
| } | |||||
| while (true) { | |||||
| $any_progress = false; | |||||
| foreach ($this->requests as $req_key => $request) { | |||||
| $did_update = $request->updateTasks(); | |||||
| if ($did_update) { | |||||
| $any_progress = true; | |||||
| } | |||||
| } | |||||
| // If we made progress by directly executing requests, continue | |||||
| // excuting them until we stop making progress. We want to queue all | |||||
| // reachable futures before we wait on futures. | |||||
| if ($any_progress) { | |||||
| continue; | |||||
| } | |||||
| foreach ($this->requests as $request_key => $request) { | |||||
| if ($request->isComplete()) { | |||||
| unset($this->requests[$request_key]); | |||||
| } | |||||
| } | |||||
| if (!$this->requests) { | |||||
| break; | |||||
| } | |||||
| $resolved_key = $this->updateFutures(); | |||||
| if ($resolved_key === null) { | |||||
| throw new Exception( | |||||
| pht( | |||||
| 'Hardpoint engine can not resolve: no request made progress '. | |||||
| 'during the last update cycle and there are no futures '. | |||||
| 'awaiting resolution.')); | |||||
| } | |||||
| } | |||||
| } | |||||
| private function updateFutures() { | |||||
| $iterator = $this->futureIterator; | |||||
| $is_rewind = false; | |||||
| $wait_futures = $this->waitFutures; | |||||
| if ($wait_futures) { | |||||
| if (!$this->futureIterator) { | |||||
| $iterator = new FutureIterator(array()); | |||||
| foreach ($wait_futures as $wait_future) { | |||||
| $iterator->addFuture($wait_future); | |||||
| } | |||||
| $is_rewind = true; | |||||
| $this->futureIterator = $iterator; | |||||
| } else { | |||||
| foreach ($wait_futures as $wait_future) { | |||||
| $iterator->addFuture($wait_future); | |||||
| } | |||||
| } | |||||
| $this->waitFutures = array(); | |||||
| } | |||||
| $resolved_key = null; | |||||
| if ($iterator) { | |||||
| if ($is_rewind) { | |||||
| $iterator->rewind(); | |||||
| } else { | |||||
| $iterator->next(); | |||||
| } | |||||
| if ($iterator->valid()) { | |||||
| $resolved_key = $iterator->key(); | |||||
| } else { | |||||
| $this->futureIterator = null; | |||||
| } | |||||
| } | |||||
| return $resolved_key; | |||||
| } | |||||
| public function addFutures(array $futures) { | |||||
| assert_instances_of($futures, 'Future'); | |||||
| $this->waitFutures += mpull($futures, null, 'getFutureKey'); | |||||
| // TODO: We could reasonably add these futures to the iterator | |||||
| // immediately and start them here, instead of waiting. | |||||
| return $this; | |||||
| } | |||||
| } | |||||