Skip to content

Commit

Permalink
added
Browse files Browse the repository at this point in the history
  • Loading branch information
ytake committed Jul 20, 2020
1 parent 52ac080 commit 1fa6266
Show file tree
Hide file tree
Showing 10 changed files with 238 additions and 154 deletions.
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ env:
- HHVM_VERSION=4.62.0
- HHVM_VERSION=4.64.0
- HHVM_VERSION=4.65.0
- HHVM_VERSION=4.66.0
- HHVM_VERSION=latest
install:
- docker pull hhvm/hhvm-proxygen:$HHVM_VERSION
Expand Down
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
# event-dispatcher

# Nazg\Dispatcher
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "nazg/event-dispatcher",
"description": "Event Managiment for Hack",
"name": "nazg/dispatcher",
"description": "Dispatcher for Hack",
"keywords": [
"hhvm",
"hack",
Expand Down
115 changes: 115 additions & 0 deletions src/Dispatcher.hack
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
namespace Nazg\Dispatcher;

use namespace HH\Lib\{Dict, C};
use function array_key_exists;

type DispatchToken = string;

class Dispatcher<TPayload> {

private string $prefix = 'ID_';
private ?TPayload $pendingPayload = null;

public function __construct(
private dict<DispatchToken, (function(TPayload):void)> $callbacks = dict[],
private bool $isDispatching = false,
private dict<DispatchToken, bool> $isHandled = dict[],
private dict<DispatchToken, bool> $isPending = dict[],
private int $lastID = 1,
) { }

public function register(
(function(TPayload):void) $callback
): DispatchToken {
$this->lastID = $this->lastID + 1;
$id = $this->prefix . $this->lastID;
$this->callbacks[$id] = $callback;
return $id;
}

public function unregister(DispatchToken $id): void {
invariant(
array_key_exists($id, $this->callbacks),
'Dispatcher.unregister(...): `%s` does not map to a registered callback.',
$id
);
$this->callbacks = Dict\filter_with_key(
$this->callbacks,
($k, $_) ==> $k !== $id
);
}

public function waitFor(
vec<DispatchToken> $ids
): void {
invariant(
$this->isDispatching,
'waitFor(...): Must be invoked while dispatching.'
);
for ($i = 0; $i < C\count($ids); $i++) {
$id = $ids[$i];
if($this->isPending[$id]) {
invariant(
$this->isHandled[$id],
'waitFor(...): Circular dependency detected while waiting for `%s`.',
$id
);
continue;
}
invariant(
$this->callbacks[$id],
'waitFor(...): `%s` does not map to a registered callback.',
$id
);
$this->invokeCallback($id);
}
}

public function dispatch(TPayload $payload): void {
invariant(
!$this->isDispatching,
'dispatch(...): Cannot dispatch in the middle of a dispatch.'
);
$this->startDispatching($payload);
try {
foreach ($this->callbacks as $key => $value) {
if($this->isPending[$key]) {
continue;
}
$this->invokeCallback($key);
}
} finally {
$this->stopDispatching();
}
}

public function isDispatching(): bool {
return $this->isDispatching;
}

private function invokeCallback(DispatchToken $id): void {
$this->isPending[$id] = true;
if(array_key_exists($id, $this->callbacks)) {
if($this->pendingPayload is nonnull) {
$this->callbacks[$id]($this->pendingPayload);
}
}
$this->isHandled[$id] = true;
}

private function startDispatching(
TPayload $payload
): void {
Dict\map_with_key($this->callbacks, ($k, $_) ==> {
$this->isPending[$k] = false;
$this->isHandled[$k] = false;
});
$this->pendingPayload = $payload;
$this->isDispatching = true;
}

private function stopDispatching(): void {
$this->pendingPayload = null;
$this->isDispatching = false;
}
}
15 changes: 0 additions & 15 deletions src/Event.hack

This file was deleted.

92 changes: 0 additions & 92 deletions src/EventDispatcher.hack

This file was deleted.

10 changes: 0 additions & 10 deletions src/ListenerInterface.hack

This file was deleted.

12 changes: 0 additions & 12 deletions src/StoppableEventInterface.hack

This file was deleted.

119 changes: 119 additions & 0 deletions tests/DispatcherTest.hack
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
use type Nazg\Dispatcher\Dispatcher;
use type Facebook\HackTest\HackTest;
use function Facebook\FBExpect\expect;

final class DispatcherTest extends HackTest {

private int $call = 0;

public async function testShouldReturnInctID(): Awaitable<void> {
$dispatcher = new Dispatcher();
$id = $dispatcher->register(($v) ==> {
$v;
});
expect($id)->toBeSame('ID_2');
$id = $dispatcher->register(($v) ==> {
$v;
});
expect($id)->toBeSame('ID_3');
}

public async function testShouldThrowInvariantException(): Awaitable<void> {
$dispatcher = new Dispatcher();
expect(() ==> $dispatcher->unregister('ID_1'))
->toThrow(InvariantException::class);
}

public async function testShouldExecuteCallbacks(): Awaitable<void> {
$dispatcher = new Dispatcher();
$id = $dispatcher->register(($_) ==> {
$this->call = $this->call + 1;
});
$payload = dict[];
$dispatcher->dispatch($payload);
expect($this->call)->toBeSame(1);
$dispatcher->unregister($id);
$dispatcher->register(($_) ==> {
$this->call = $this->call + 1;
});
$dispatcher->dispatch($payload);
expect($this->call)->toBeSame(2);
}

public async function testShouldWaitForCallbacks(): Awaitable<void> {
$dispatcher = new Dispatcher();
$token = $dispatcher->register(($_) ==> {
$this->call = $this->call + 1;
});
$callback = ($v) ==> {
$this->call = $this->call + 1;
};
$dispatcher->register(($dict) ==> {
$dispatcher->waitFor(vec[$token]);
expect($this->call)->toBeSame(1);
$callback($dict);
});
$payload = dict[];
$dispatcher->dispatch($payload);
expect($this->call)->toBeSame(2);
}

public async function testShouldWaitForAsyncCallbacks(): Awaitable<void> {
$dispatcher = new Dispatcher();
$callback = ($v) ==> {
$this->call = $this->call + 1;
};

$token = await async {
return $dispatcher->register(($_) ==> {
$this->call = $this->call + 1;
});
};

await async {
$dispatcher->register(($dict) ==> {
$dispatcher->waitFor(vec[$token]);
expect($this->call)->toBeSame(1);
$callback($dict);
});
};
$payload = dict[];
$dispatcher->dispatch($payload);
expect($this->call)->toBeSame(2);
}

public async function testShouldThrowDispatching(): Awaitable<void> {
$dispatcher = new Dispatcher();
$dispatcher->register(($payload) ==> {
$this->call = $this->call + 1;
$dispatcher->dispatch($payload);
});
$payload = dict[];
expect(() ==> $dispatcher->dispatch($payload))
->toThrow(InvariantException::class);
expect($this->call)->toBeSame(1);
}

public async function testShouldThrowWaitFor(): Awaitable<void> {
$dispatcher = new Dispatcher();
$token = $dispatcher->register(($payload) ==> {
$this->call = $this->call + 1;
});
expect(() ==> $dispatcher->waitFor(vec[$token]))
->toThrow(InvariantException::class);
}

public async function testShouldThrowWaitForInvalidToken(): Awaitable<void> {
$dispatcher = new Dispatcher();
$token = '1111111';
$dispatcher->register(($_) ==> {
$dispatcher->waitFor(vec[$token]);
});
expect(() ==> $dispatcher->dispatch(dict[]))
->toThrow(OutOfBoundsException::class);
}

public async function afterEachTestAsync(): Awaitable<void> {
$this->call = 0;
}
}
Loading

0 comments on commit 1fa6266

Please sign in to comment.