Skip to content

Commit d4e3049

Browse files
committed
First commit.
0 parents  commit d4e3049

21 files changed

+7309
-0
lines changed

.editorconfig

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
root = true
2+
3+
[*]
4+
indent_style = space
5+
end_of_line = lf
6+
charset = utf-8
7+
trim_trailing_whitespace = true
8+
insert_final_newline = true
9+
max_line_length = 100
10+
indent_size = 2
11+
12+
[*.md]
13+
trim_trailing_whitespace = false

.gitignore

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
*.log
2+
npm-debug.log*
3+
node_modules
4+
coverage
5+
.nyc_output
6+
.DS_Store
7+
.vscode
8+
.idea
9+
dist
10+
compiled
11+
.awcache
12+
.rpt2_cache
13+
docs

.npmignore

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
src/
2+
tests/**.ts
3+
dist/lib/tests/**.*
4+
node_modules
5+
*.log
6+
npm-debug.log*
7+
coverage
8+
.nyc_output
9+
.DS_Store
10+
.vscode
11+
.idea
12+
compiled
13+
.awcache
14+
.rpt2_cache

.travis.yml

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
language: nodejs
2+
3+
node_js: 8.12.0
4+
5+
before_script:
6+
- npm install
7+
- npm run build
8+
9+
script:
10+
- npm run test
11+
- npm run test:coveralls
12+
13+
after_success:
14+
- npm pack
15+
16+
deploy:
17+
- provider: pages
18+
skip_cleanup: true
19+
local_dir: docs/
20+
github_token: $GITHUB_TOKEN
21+
on:
22+
tags: true
23+
- provider: releases
24+
api_key: $GITHUB_TOKEN
25+
file_glob: true
26+
file: "{when-ts}-*.tgz"
27+
skip_cleanup: true
28+
on:
29+
tags: true
30+
- provider: npm
31+
32+
api_key: $NPM_TOKEN
33+
on:
34+
tags: true
35+
repo: voodooattack/when-ts

LICENSE

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
Copyright (c) 2018 Abdullah A. Hassan
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy
4+
of this software and associated documentation files (the "Software"), to deal
5+
in the Software without restriction, including without limitation the rights
6+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
copies of the Software, and to permit persons to whom the Software is
8+
furnished to do so, subject to the following conditions:
9+
10+
The above copyright notice and this permission notice shall be included in all
11+
copies or substantial portions of the Software.
12+
13+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19+
SOFTWARE.

README.md

+281
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
# When: TypeScript Reference Implementation
2+
##### A software design pattern for building event-based recombinant state machines
3+
4+
[![npm](https://img.shields.io/npm/v/when-ts.svg)](https://www.npmjs.com/package/when-ts)
5+
[![GitHub license](https://img.shields.io/github/license/voodooattack/when-ts.svg)](https://github.com/voodooattack/when-ts/blob/master/LICENSE)
6+
[![GitHub issues](https://img.shields.io/github/issues/voodooattack/when-ts.svg)](https://github.com/voodooattack/when-ts/issues)
7+
[![Build Status](https://travis-ci.org/voodooattack/when-ts.svg?branch=master)](https://travis-ci.org/voodooattack/when-ts) [![Coverage Status](https://coveralls.io/repos/github/voodooattack/when-ts/badge.svg)](https://coveralls.io/github/voodooattack/when-ts)
8+
![npm type definitions](https://img.shields.io/npm/types/when-ts.svg)
9+
10+
### Introduction
11+
12+
This is a reference implementation for a new software design pattern that allows for composable even-based state machines with complete (including temporal) control over their state.
13+
14+
Please note that this spec and reference implementation are still in alpha and the specs are not yet final.
15+
16+
#### Features:
17+
18+
- Discrete: if your actions only deal with the state object, then every state transition is 100% predictable.
19+
- Temporal: time can be rewound at any given moment (tick) by default, and the state machine will transition to a previously known state in time, along with any future information in the form of an optional state mutation to apply.
20+
- Recombinant: the pattern is based on [gene expression](https://en.wikipedia.org/wiki/Gene_expression), and since state machines are composed of events (`condition -> behaviour` pairs) that are quite similar to how real genes are theorised to work (`activation region -> coding region`), this means that genetic recombination can be applied to `when` state machines by transferring new events from one machine to another. Mutating the machine (DNA) by transferring events (genes) from one machine to the other will introduce new behaviour.
21+
22+
#### Possible Proposals
23+
24+
Here are some possible expansions on the idea. These require further discussion before they're mature enough to include:
25+
26+
- Inhibitors that can suppress an action: these would inhibit a certain event and prevent it from triggering. Alternatively, the possibility for an action to disable/inhibit another action during a tick could be introduced.
27+
- Sexual reproduction of state machines: possible use of a similar mechanic to the one used in organic cells to combine two different programs (DNA) by randomly selecting an equal half of each.
28+
- Mutation: Possible, but difficult since we can't swap code like basepairs. The simplest possible mutation would be a random swap of conditions between two randomly selected actions.
29+
30+
This would all lead to more emergent behaviour in agents produced by recombination.
31+
32+
#### Pattern
33+
34+
*The following is a description of the pattern itself, and not this specific implementation.*
35+
36+
This pattern itself is completely generic and can be implemented in any programming language available today with varying degrees of adaptability potential, depending on the features of the target language.
37+
38+
##### Program state
39+
40+
A `MachineState` consists of user-defined variables and is passed to every condition and action independently.
41+
42+
An external tick counter (`history.tick`) exists and can be considered part of the state (but is not included inside the state object). It is a special variable that is automatically incremented with every new tick. Can be used to reference discrete points in time.
43+
44+
##### Main loop
45+
46+
The goal of the main loop is to move execution forward by mutating the current `state`.
47+
48+
To do this, `when` implements a loop that constantly evaluates a set of rules (`program`). Every iteration of this loop is called a `tick`, when a condition evaluates to `true`, the `action` associated with the condition is evaluated. `actions` can return a partial `MachineState` object containing new variable values for the next `state`, or they can return nothing.
49+
50+
Note that the main loop will always pass an immutable copy of the previous state to actions. Any new mutations caused by actions will only appear during the next `tick`. This is to prevent interactions between different `actions` during the same `tick`.
51+
52+
If multiple actions try to modify the same variable during the same `tick`, the last `action` to execute takes precedence.
53+
54+
The main loop will abort by default if no conditions evaluate to `true` during a single `tick`. This prevents the program from running forever.
55+
56+
#### State Manager
57+
58+
- A State Manager (`history`) is accessible from events. It is responsible ofr managing an array of previous states (`history.records`), in which states are recorded as the program advances.
59+
60+
- A state machine can exit by calling `exit()` from any event, the returned value is the last recorded state. A single argument can be passed to `exit()` to override the returned state.
61+
62+
- Events can use `history.tick` to access the current tick counter.
63+
64+
- Events can access the last recorded states from `history.currentState`.
65+
66+
- Events can access the next state being actively mutated by the current tick through the read-only property `history.nextState`.
67+
68+
- The state can be rewound to a previously recorded state using the `history.rewind(n)` method. `history.rewind(2)` will cause the program to rewind by two full ticks (the tick counter will be decremented as needed). If this occurs inside an event handler, further events will not be processed.
69+
70+
- `history.rewind` accepts a second parameter with optional variable to pass after rewinding to the past state, `history.rewind(2, { backToTheFuture: true })` will rewind and mutate the past state by setting the variable `backToTheFuture` to `true`.
71+
72+
- State history can be erased at any time using `history.clear();`.
73+
74+
- State recording can be configured or disabled at any time by manipulating `history.limit`.
75+
76+
- Setting a finite `limit` during startup is strongly advised. `history.limit` defaults to `Infinity`.
77+
78+
**Examples of `limit`:**
79+
80+
- `history.limit = Infinity;` Record an infinite amount of state. (This is the default, which may cause memory issues if your state objects are very big and/or your program stays running for a long time)
81+
82+
- `history.limit = 4;` Only record the most recent 4 states. Discards any stored older states.
83+
84+
- `history.limit = 0;` No further state recording allowed, and acts the same as `history.limit = 1`. Discards any older history, and `history.record` will only show the previous state.
85+
86+
#### Note on Recombination
87+
88+
This is not part of the current spec, but is currently offered by the TypeScript reference implementation. You can combine any two machines by calling `machine1.recombine(machine2)`, see the [TypeScript API documentation](https://voodooattack.github.io/when-ts/) for more details.
89+
90+
#### Abstract Syntax
91+
92+
Here are some abstract syntax examples for a full pseudo-language based on this pattern. In this theoretical language, the program itself is a state machine, variables of the `MachineState` are global variables, and all of the primitives described above are part of the language itself.
93+
94+
You can read about the original idea (slightly outdated) [in this proposal](https://gist.github.com/voodooattack/ccb1d18112720a8de5be660dbb80541c).
95+
96+
This is mostly pseudo-javascript with two extra `when` and `exit` keywords.
97+
98+
##### Examples
99+
100+
- A prime number generator:
101+
102+
```javascript
103+
let counter = 2; // starting counting up from 2
104+
let current = 3; // start looking at 3
105+
let primes = []; // array to store saved primes
106+
107+
// increment the counter with every tick till we hit the potential prime
108+
when(counter < current) {
109+
counter++;
110+
}
111+
112+
// not a prime number, reset and increment current search
113+
when(counter < current && current % counter === 0) {
114+
counter = 2;
115+
current++;
116+
}
117+
118+
// if this is ever triggered, then we're dealing with a prime.
119+
when(counter >= current) {
120+
// save the prime
121+
primes.push(current);
122+
// print it to the console
123+
console.log(current);
124+
// reset the variables and look for the next one
125+
counter = 2;
126+
current++;
127+
}
128+
129+
// exit when we've found enough primes
130+
when(primes.length >= 10) {
131+
exit();
132+
}
133+
```
134+
135+
Predicted exit state after `exit`:
136+
```json
137+
{
138+
counter: 2,
139+
current: 30,
140+
primes: [ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29 ]
141+
}
142+
```
143+
144+
### Installation
145+
146+
You need to install `reflect-metadata` in your project.
147+
148+
`npm install when-ts reflect-metadata`
149+
150+
Additionally, you must add the following to your project's `tsconfig.json` for the TypeScript decorator to work:
151+
152+
```json
153+
{
154+
/* Experimental Options */
155+
/* Enables experimental support for ES7 decorators. */
156+
"experimentalDecorators": true,
157+
/* Enables experimental support for emitting type metadata for decorators. */
158+
"emitDecoratorMetadata": true
159+
}
160+
```
161+
162+
### API
163+
164+
See the [API documentation](https://voodooattack.github.io/when-ts/) for more information.
165+
166+
### Usage
167+
168+
- Simple example:
169+
170+
```typescript
171+
import { EventMachine, when } from 'when-ts';
172+
173+
type State = { // the state of our program
174+
value: number; // a counter that will be incremented once per tick
175+
}
176+
177+
class TestMachine extends EventMachine<State> {
178+
constructor() {
179+
super({ value: 0 }); // pass the initial state to the event machine
180+
}
181+
182+
@when(true) // define a condition for this block to execute, in this case always
183+
reportOncePerTick(s: State, m: TestMachine) {
184+
console.log(`beginning tick #${m.history.tick} with state`, s);
185+
}
186+
187+
@when(state => state.value < 5) // this only executes when `value` is less than 5
188+
incrementOncePerTick(s: State) { // increment `value` once per tick
189+
return { value: s.value + 1 };
190+
}
191+
192+
@when(state => state.value >= 5) // this will only execute when `value` is >= 5
193+
exitWhenDone(s: State, m: TestMachine) {
194+
console.log(`finished on tick #${m.history.tick}, exiting`, s);
195+
if (m.history.tick >= 5)
196+
m.exit(); // exit the state machine
197+
}
198+
}
199+
200+
const test = new TestMachine();
201+
202+
const result = test.run(); // this does will block until the machine exits, unlike `.step()`
203+
204+
console.log('state machine exits with:', result);
205+
```
206+
207+
- The same prime machine from earlier, implemented in TypeScript:
208+
209+
```typescript
210+
import { StateMachine, when, MachineState } from 'when-ts';
211+
212+
interface PrimeState extends MachineState {
213+
counter: number;
214+
current: number;
215+
primes: number[];
216+
}
217+
218+
class PrimeMachine extends StateMachine<PrimeState> {
219+
constructor() {
220+
super({ counter: 2, current: 3, primes: [2] });
221+
}
222+
223+
@when(state => state.counter < state.current)
224+
incrementCounterOncePerTick({ counter }: PrimeState) {
225+
return { counter: counter + 1 };
226+
}
227+
228+
@when(state => state.counter < state.current && state.current % state.counter === 0)
229+
resetNotPrime({ counter, primes, current }: PrimeState) {
230+
return { counter: 2, current: current + 1 };
231+
}
232+
233+
@when(state => state.counter >= state.current)
234+
capturePrime({ counter, primes, current }: PrimeState) {
235+
return { counter: 2, current: current + 1, primes: [...primes, current] };
236+
}
237+
238+
@when(state => state.primes.length >= 10)
239+
exitMachine() {
240+
this.exit();
241+
}
242+
}
243+
244+
const primeMachine = new PrimeMachine();
245+
246+
const result = primeMachine.run();
247+
248+
if (result)
249+
console.log(result!.primes);
250+
251+
```
252+
253+
### Contributions
254+
255+
All contributions and pull requests are welcome.
256+
257+
If you have something to suggest or an idea you'd like to discuss, then please submit an issue or a pull request.
258+
259+
Please make sure that test coverage does not drop below the set limits in `package.json`.
260+
261+
### License (MIT)
262+
263+
Copyright (c) 2018 Abdullah A. Hassan
264+
265+
Permission is hereby granted, free of charge, to any person obtaining a copy
266+
of this software and associated documentation files (the "Software"), to deal
267+
in the Software without restriction, including without limitation the rights
268+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
269+
copies of the Software, and to permit persons to whom the Software is
270+
furnished to do so, subject to the following conditions:
271+
272+
The above copyright notice and this permission notice shall be included in all
273+
copies or substantial portions of the Software.
274+
275+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
276+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
277+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
278+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
279+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
280+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
281+
SOFTWARE.

0 commit comments

Comments
 (0)