Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F15444706
D7350.id16603.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
9 KB
Referenced Files
None
Subscribers
None
D7350.id16603.diff
View Options
Index: src/applications/phrequent/storage/PhrequentTimeBlock.php
===================================================================
--- src/applications/phrequent/storage/PhrequentTimeBlock.php
+++ src/applications/phrequent/storage/PhrequentTimeBlock.php
@@ -31,10 +31,12 @@
$timeline = array();
$timeline[] = array(
+ 'event' => $event,
'at' => $event->getDateStarted(),
'type' => 'start',
);
$timeline[] = array(
+ 'event' => $event,
'at' => nonempty($event->getDateEnded(), $now),
'type' => 'end',
);
@@ -46,10 +48,12 @@
foreach ($preempts as $preempt) {
$same_object = ($preempt->getObjectPHID() == $base_phid);
$timeline[] = array(
+ 'event' => $preempt,
'at' => $preempt->getDateStarted(),
'type' => $same_object ? 'start' : 'push',
);
$timeline[] = array(
+ 'event' => $preempt,
'at' => nonempty($preempt->getDateEnded(), $now),
'type' => $same_object ? 'end' : 'pop',
);
@@ -58,36 +62,108 @@
// Now, figure out how much time was actually spent working on the
// object.
- $timeline = isort($timeline, 'at');
+ usort($timeline, array(__CLASS__, 'sortTimeline'));
$stack = array();
$depth = null;
+ // NOTE: "Strata" track the separate layers between each event tracking
+ // the object we care about. Events might look like this:
+ //
+ // |xxxxxxxxxxxxxxxxx|
+ // |yyyyyyy|
+ // |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx|
+ // 9AM 5PM
+ //
+ // ...where we care about event "x". When "y" is popped, that shouldn't
+ // pop the top stack -- we need to pop the stack a level down. Each
+ // event tracking "x" creates a new stratum, and we keep track of where
+ // timeline events are among the strata in order to keep stack depths
+ // straight.
+
+ $stratum = null;
+ $strata = array();
+
+
$ranges = array();
foreach ($timeline as $timeline_event) {
- switch ($timeline_event['type']) {
+ $id = $timeline_event['event']->getID();
+ $type = $timeline_event['type'];
+
+ switch ($type) {
case 'start':
$stack[] = $depth;
$depth = 0;
+ $stratum = count($stack);
+ $strata[$id] = $stratum;
$range_start = $timeline_event['at'];
break;
case 'end':
- if ($depth == 0) {
- $ranges[] = array($range_start, $timeline_event['at']);
+ if ($strata[$id] == $stratum) {
+ if ($depth == 0) {
+ $ranges[] = array($range_start, $timeline_event['at']);
+ $depth = array_pop($stack);
+ } else {
+ // Here, we've prematurely ended the current stratum. Merge all
+ // the higher strata into it. This looks like this:
+ //
+ // V
+ // V
+ // |zzzzzzzz|
+ // |xxxxx|
+ // |yyyyyyyyyyyyy|
+ // |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx|
+
+ $depth = array_pop($stack) + $depth;
+ }
+ } else {
+ // Here, we've prematurely ended a deeper stratum. Merge higher
+ // stata. This looks like this:
+ //
+ // V
+ // V
+ // |aaaaaaa|
+ // |xxxxxxxxxxxxxxxxxxx|
+ // |zzzzzzzzzzzzz|
+ // |xxxxxxx|
+ // |yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy|
+ // |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx|
+
+ $extra = $stack[$strata[$id]];
+ unset($stack[$strata[$id] - 1]);
+ $stack = array_values($stack);
+ $stack[$strata[$id] - 1] += $extra;
+ }
+
+ // Regardless of how we got here, we need to merge down any higher
+ // strata.
+ $target = $strata[$id];
+ foreach ($strata as $strata_id => $id_stratum) {
+ if ($id_stratum >= $target) {
+ $strata[$strata_id]--;
+ }
}
- $depth = array_pop($stack);
+ $stratum = count($stack);
+
+ unset($strata[$id]);
break;
case 'push':
+ $strata[$id] = $stratum;
if ($depth == 0) {
$ranges[] = array($range_start, $timeline_event['at']);
}
$depth++;
break;
case 'pop':
- $depth--;
- if ($depth == 0) {
- $range_start = $timeline_event['at'];
+ if ($strata[$id] == $stratum) {
+ $depth--;
+ if ($depth == 0) {
+ $range_start = $timeline_event['at'];
+ }
+ } else {
+ $stack[$strata[$id]]--;
}
+ unset($strata[$id]);
break;
}
}
@@ -152,4 +228,41 @@
return $result;
}
+
+ /**
+ * Sort events in timeline order. Notably, for events which occur on the same
+ * second, we want to process end events after start events.
+ */
+ public static function sortTimeline(array $u, array $v) {
+ // If these events occur at different times, ordering is obvious.
+ if ($u['at'] != $v['at']) {
+ return ($u['at'] < $v['at']) ? -1 : 1;
+ }
+
+ $u_end = ($u['type'] == 'end' || $u['type'] == 'pop');
+ $v_end = ($v['type'] == 'end' || $v['type'] == 'pop');
+
+ $u_id = $u['event']->getID();
+ $v_id = $v['event']->getID();
+
+ if ($u_end == $v_end) {
+ // These are both start events or both end events. Sort them by ID.
+ if (!$u_end) {
+ return ($u_id < $v_id) ? -1 : 1;
+ } else {
+ return ($u_id < $v_id) ? 1 : -1;
+ }
+ } else {
+ // Sort them (start, end) if they're the same event, and (end, start)
+ // otherwise.
+ if ($u_id == $v_id) {
+ return $v_end ? -1 : 1;
+ } else {
+ return $v_end ? 1 : -1;
+ }
+ }
+
+ return 0;
+ }
+
}
Index: src/applications/phrequent/storage/__tests__/PhrequentTimeBlockTestCase.php
===================================================================
--- src/applications/phrequent/storage/__tests__/PhrequentTimeBlockTestCase.php
+++ src/applications/phrequent/storage/__tests__/PhrequentTimeBlockTestCase.php
@@ -99,7 +99,6 @@
),
$ranges);
-
$event = $this->newEvent('T2', 100, 300);
$event->attachPreemptingEvents(
array(
@@ -118,8 +117,107 @@
$ranges);
}
+ public function testTimelineSort() {
+ $e1 = $this->newEvent('X1', 1, 1)->setID(1);
+
+ $in = array(
+ array(
+ 'event' => $e1,
+ 'at' => 1,
+ 'type' => 'start',
+ ),
+ array(
+ 'event' => $e1,
+ 'at' => 1,
+ 'type' => 'end',
+ ),
+ );
+
+ usort($in, array('PhrequentTimeBlock', 'sortTimeline'));
+
+ $this->assertEqual(
+ array(
+ 'start',
+ 'end',
+ ),
+ ipull($in, 'type'));
+ }
+
+ public function testInstantaneousEvent() {
+
+ $event = $this->newEvent('T1', 8, 8);
+ $event->attachPreemptingEvents(array());
+
+ $block = new PhrequentTimeBlock(array($event));
+
+ $ranges = $block->getObjectTimeRanges(1800);
+ $this->assertEqual(
+ array(
+ 'T1' => array(
+ array(8, 8),
+ ),
+ ),
+ $ranges);
+ }
+
+ public function testPopAcrossStrata() {
+
+ $event = $this->newEvent('T1', 1, 1000);
+ $event->attachPreemptingEvents(
+ array(
+ $this->newEvent('T2', 100, 300),
+ $this->newEvent('T1', 200, 400),
+ $this->newEvent('T3', 250, 275),
+ ));
+
+ $block = new PhrequentTimeBlock(array($event));
+
+ $ranges = $block->getObjectTimeRanges(1000);
+
+ $this->assertEqual(
+ array(
+ 'T1' => array(
+ array(1, 100),
+ array(200, 250),
+ array(275, 1000),
+ ),
+ ),
+ $ranges);
+ }
+
+ public function testEndDeeperStratum() {
+ $event = $this->newEvent('T1', 1, 1000);
+ $event->attachPreemptingEvents(
+ array(
+ $this->newEvent('T2', 100, 900),
+ $this->newEvent('T1', 200, 400),
+ $this->newEvent('T3', 300, 800),
+ $this->newEvent('T1', 350, 600),
+ $this->newEvent('T4', 380, 390),
+ ));
+
+ $block = new PhrequentTimeBlock(array($event));
+
+ $ranges = $block->getObjectTimeRanges(1000);
+
+ $this->assertEqual(
+ array(
+ 'T1' => array(
+ array(1, 100),
+ array(200, 300),
+ array(350, 380),
+ array(390, 600),
+ array(900, 1000),
+ ),
+ ),
+ $ranges);
+ }
+
private function newEvent($object_phid, $start_time, $end_time) {
+ static $id = 0;
+
return id(new PhrequentUserTime())
+ ->setID(++$id)
->setObjectPHID($object_phid)
->setDateStarted($start_time)
->setDateEnded($end_time);
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Fri, Mar 28, 10:48 AM (5 d, 12 h ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7725341
Default Alt Text
D7350.id16603.diff (9 KB)
Attached To
Mode
D7350: Clean up some ordering and strata edge cases in Phrequent
Attached
Detach File
Event Timeline
Log In to Comment