Skip to content

Commit 1fa6266

Browse files
committed
added
1 parent 52ac080 commit 1fa6266

File tree

10 files changed

+238
-154
lines changed

10 files changed

+238
-154
lines changed

.travis.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ env:
77
- HHVM_VERSION=4.62.0
88
- HHVM_VERSION=4.64.0
99
- HHVM_VERSION=4.65.0
10+
- HHVM_VERSION=4.66.0
1011
- HHVM_VERSION=latest
1112
install:
1213
- docker pull hhvm/hhvm-proxygen:$HHVM_VERSION

README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
1-
# event-dispatcher
2-
1+
# Nazg\Dispatcher

composer.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"name": "nazg/event-dispatcher",
3-
"description": "Event Managiment for Hack",
2+
"name": "nazg/dispatcher",
3+
"description": "Dispatcher for Hack",
44
"keywords": [
55
"hhvm",
66
"hack",

src/Dispatcher.hack

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
namespace Nazg\Dispatcher;
2+
3+
use namespace HH\Lib\{Dict, C};
4+
use function array_key_exists;
5+
6+
type DispatchToken = string;
7+
8+
class Dispatcher<TPayload> {
9+
10+
private string $prefix = 'ID_';
11+
private ?TPayload $pendingPayload = null;
12+
13+
public function __construct(
14+
private dict<DispatchToken, (function(TPayload):void)> $callbacks = dict[],
15+
private bool $isDispatching = false,
16+
private dict<DispatchToken, bool> $isHandled = dict[],
17+
private dict<DispatchToken, bool> $isPending = dict[],
18+
private int $lastID = 1,
19+
) { }
20+
21+
public function register(
22+
(function(TPayload):void) $callback
23+
): DispatchToken {
24+
$this->lastID = $this->lastID + 1;
25+
$id = $this->prefix . $this->lastID;
26+
$this->callbacks[$id] = $callback;
27+
return $id;
28+
}
29+
30+
public function unregister(DispatchToken $id): void {
31+
invariant(
32+
array_key_exists($id, $this->callbacks),
33+
'Dispatcher.unregister(...): `%s` does not map to a registered callback.',
34+
$id
35+
);
36+
$this->callbacks = Dict\filter_with_key(
37+
$this->callbacks,
38+
($k, $_) ==> $k !== $id
39+
);
40+
}
41+
42+
public function waitFor(
43+
vec<DispatchToken> $ids
44+
): void {
45+
invariant(
46+
$this->isDispatching,
47+
'waitFor(...): Must be invoked while dispatching.'
48+
);
49+
for ($i = 0; $i < C\count($ids); $i++) {
50+
$id = $ids[$i];
51+
if($this->isPending[$id]) {
52+
invariant(
53+
$this->isHandled[$id],
54+
'waitFor(...): Circular dependency detected while waiting for `%s`.',
55+
$id
56+
);
57+
continue;
58+
}
59+
invariant(
60+
$this->callbacks[$id],
61+
'waitFor(...): `%s` does not map to a registered callback.',
62+
$id
63+
);
64+
$this->invokeCallback($id);
65+
}
66+
}
67+
68+
public function dispatch(TPayload $payload): void {
69+
invariant(
70+
!$this->isDispatching,
71+
'dispatch(...): Cannot dispatch in the middle of a dispatch.'
72+
);
73+
$this->startDispatching($payload);
74+
try {
75+
foreach ($this->callbacks as $key => $value) {
76+
if($this->isPending[$key]) {
77+
continue;
78+
}
79+
$this->invokeCallback($key);
80+
}
81+
} finally {
82+
$this->stopDispatching();
83+
}
84+
}
85+
86+
public function isDispatching(): bool {
87+
return $this->isDispatching;
88+
}
89+
90+
private function invokeCallback(DispatchToken $id): void {
91+
$this->isPending[$id] = true;
92+
if(array_key_exists($id, $this->callbacks)) {
93+
if($this->pendingPayload is nonnull) {
94+
$this->callbacks[$id]($this->pendingPayload);
95+
}
96+
}
97+
$this->isHandled[$id] = true;
98+
}
99+
100+
private function startDispatching(
101+
TPayload $payload
102+
): void {
103+
Dict\map_with_key($this->callbacks, ($k, $_) ==> {
104+
$this->isPending[$k] = false;
105+
$this->isHandled[$k] = false;
106+
});
107+
$this->pendingPayload = $payload;
108+
$this->isDispatching = true;
109+
}
110+
111+
private function stopDispatching(): void {
112+
$this->pendingPayload = null;
113+
$this->isDispatching = false;
114+
}
115+
}

src/Event.hack

Lines changed: 0 additions & 15 deletions
This file was deleted.

src/EventDispatcher.hack

Lines changed: 0 additions & 92 deletions
This file was deleted.

src/ListenerInterface.hack

Lines changed: 0 additions & 10 deletions
This file was deleted.

src/StoppableEventInterface.hack

Lines changed: 0 additions & 12 deletions
This file was deleted.

tests/DispatcherTest.hack

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
use type Nazg\Dispatcher\Dispatcher;
2+
use type Facebook\HackTest\HackTest;
3+
use function Facebook\FBExpect\expect;
4+
5+
final class DispatcherTest extends HackTest {
6+
7+
private int $call = 0;
8+
9+
public async function testShouldReturnInctID(): Awaitable<void> {
10+
$dispatcher = new Dispatcher();
11+
$id = $dispatcher->register(($v) ==> {
12+
$v;
13+
});
14+
expect($id)->toBeSame('ID_2');
15+
$id = $dispatcher->register(($v) ==> {
16+
$v;
17+
});
18+
expect($id)->toBeSame('ID_3');
19+
}
20+
21+
public async function testShouldThrowInvariantException(): Awaitable<void> {
22+
$dispatcher = new Dispatcher();
23+
expect(() ==> $dispatcher->unregister('ID_1'))
24+
->toThrow(InvariantException::class);
25+
}
26+
27+
public async function testShouldExecuteCallbacks(): Awaitable<void> {
28+
$dispatcher = new Dispatcher();
29+
$id = $dispatcher->register(($_) ==> {
30+
$this->call = $this->call + 1;
31+
});
32+
$payload = dict[];
33+
$dispatcher->dispatch($payload);
34+
expect($this->call)->toBeSame(1);
35+
$dispatcher->unregister($id);
36+
$dispatcher->register(($_) ==> {
37+
$this->call = $this->call + 1;
38+
});
39+
$dispatcher->dispatch($payload);
40+
expect($this->call)->toBeSame(2);
41+
}
42+
43+
public async function testShouldWaitForCallbacks(): Awaitable<void> {
44+
$dispatcher = new Dispatcher();
45+
$token = $dispatcher->register(($_) ==> {
46+
$this->call = $this->call + 1;
47+
});
48+
$callback = ($v) ==> {
49+
$this->call = $this->call + 1;
50+
};
51+
$dispatcher->register(($dict) ==> {
52+
$dispatcher->waitFor(vec[$token]);
53+
expect($this->call)->toBeSame(1);
54+
$callback($dict);
55+
});
56+
$payload = dict[];
57+
$dispatcher->dispatch($payload);
58+
expect($this->call)->toBeSame(2);
59+
}
60+
61+
public async function testShouldWaitForAsyncCallbacks(): Awaitable<void> {
62+
$dispatcher = new Dispatcher();
63+
$callback = ($v) ==> {
64+
$this->call = $this->call + 1;
65+
};
66+
67+
$token = await async {
68+
return $dispatcher->register(($_) ==> {
69+
$this->call = $this->call + 1;
70+
});
71+
};
72+
73+
await async {
74+
$dispatcher->register(($dict) ==> {
75+
$dispatcher->waitFor(vec[$token]);
76+
expect($this->call)->toBeSame(1);
77+
$callback($dict);
78+
});
79+
};
80+
$payload = dict[];
81+
$dispatcher->dispatch($payload);
82+
expect($this->call)->toBeSame(2);
83+
}
84+
85+
public async function testShouldThrowDispatching(): Awaitable<void> {
86+
$dispatcher = new Dispatcher();
87+
$dispatcher->register(($payload) ==> {
88+
$this->call = $this->call + 1;
89+
$dispatcher->dispatch($payload);
90+
});
91+
$payload = dict[];
92+
expect(() ==> $dispatcher->dispatch($payload))
93+
->toThrow(InvariantException::class);
94+
expect($this->call)->toBeSame(1);
95+
}
96+
97+
public async function testShouldThrowWaitFor(): Awaitable<void> {
98+
$dispatcher = new Dispatcher();
99+
$token = $dispatcher->register(($payload) ==> {
100+
$this->call = $this->call + 1;
101+
});
102+
expect(() ==> $dispatcher->waitFor(vec[$token]))
103+
->toThrow(InvariantException::class);
104+
}
105+
106+
public async function testShouldThrowWaitForInvalidToken(): Awaitable<void> {
107+
$dispatcher = new Dispatcher();
108+
$token = '1111111';
109+
$dispatcher->register(($_) ==> {
110+
$dispatcher->waitFor(vec[$token]);
111+
});
112+
expect(() ==> $dispatcher->dispatch(dict[]))
113+
->toThrow(OutOfBoundsException::class);
114+
}
115+
116+
public async function afterEachTestAsync(): Awaitable<void> {
117+
$this->call = 0;
118+
}
119+
}

0 commit comments

Comments
 (0)