-
Notifications
You must be signed in to change notification settings - Fork 6
/
update.ts
191 lines (176 loc) · 5.51 KB
/
update.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
import {
Scope,
EventStream,
EventStreamSeed,
isEventStreamSeed,
isPropertySeed,
Property,
PropertySeed,
isScope,
} from "./abstractions"
import { merge } from "./merge"
import { scan } from "./scan"
import { map } from "./map"
import { applyScopeMaybe } from "./applyscope"
import { toString } from "./tostring"
import { rename } from "./util"
export type UpdateTrigger<T> = EventStream<T> | EventStreamSeed<T>
type UpdateParam<T> = UpdateTrigger<T> | Property<T>
/**
* [Update](#update) pattern consisting of a single EventStream and a accumulator function.
*/
export type UpdatePattern1<I1, O> = [
UpdateTrigger<I1>,
O | ((acc: O, a: I1) => O)
]
/**
* [Update](#update) pattern consisting of 2 Observables and an accumulrator function.
*/
export type UpdatePattern2<I1, I2, O> = [
UpdateTrigger<I1>,
Property<I1>,
O | ((acc: O, a: I1, b: I2) => O)
]
/**
* [Update](#update) pattern consisting of 3 Observables and an accumulrator function.
*/
export type UpdatePattern3<I1, I2, I3, O> = [
UpdateTrigger<I1>,
Property<I1>,
Property<I3>,
O | ((acc: O, a: I1, b: I2, c: I3) => O)
]
/**
* [Update](#update) pattern consisting of 4 Observables and an accumulrator function.
*/
export type UpdatePattern4<I1, I2, I3, I4, O> = [
UpdateTrigger<I1>,
Property<I1>,
Property<I3>,
Property<I4>,
O | ((acc: O, a: I1, b: I2, c: I3, d: I4) => O)
]
/**
* [Update](#update) pattern consisting of 5 Observables and an accumulrator function.
*/
export type UpdatePattern5<I1, I2, I3, I4, I5, O> = [
UpdateTrigger<I1>,
Property<I1>,
Property<I3>,
Property<I4>,
Property<I5>,
O | ((acc: O, a: I1, b: I2, c: I3, d: I4, e: I5) => O)
]
/**
* [Update](#update) pattern consisting of 6 Observables and an accumulrator function.
*/
export type UpdatePattern6<I1, I2, I3, I4, I5, I6, O> = [
UpdateTrigger<I1>,
Property<I1>,
Property<I3>,
Property<I4>,
Property<I5>,
Property<I6>,
O | ((acc: O, a: I1, b: I2, c: I3, d: I4, e: I5, f: I6) => O)
]
/**
* [Update](#update) pattern type, allowing up to 6 sources per pattern.
*/
export type UpdatePattern<O> =
| UpdatePattern1<any, O>
| UpdatePattern2<any, any, O>
| UpdatePattern3<any, any, any, O>
| UpdatePattern4<any, any, any, any, O>
| UpdatePattern5<any, any, any, any, any, O>
| UpdatePattern6<any, any, any, any, any, any, O>
/**
Creates a Property from an initial value and updates the value based on multiple inputs.
The inputs are defined similarly to [`Bacon.when`](#bacon-when), like this:
```js
var result = Bacon.update(
initial,
[x,y,z, (previous,x,y,z) => { ... }],
[x,y, (previous,x,y) => { ... }])
```
As input, each function above will get the previous value of the `result` Property, along with values from the listed Observables.
The value returned by the function will be used as the next value of `result`.
Just like in [`Bacon.when`](#when), only EventStreams will trigger an update, while Properties will be just sampled.
So, if you list a single EventStream and several Properties, the value will be updated only when an event occurs in the EventStream.
Here's a simple gaming example:
```js
let scoreMultiplier = Bacon.constant(1)
let hitUfo = Bacon.interval(1000)
let hitMotherShip = Bacon.later(10000)
let score = Bacon.update(
0,
[hitUfo, scoreMultiplier, (score, _, multiplier) => score + 100 * multiplier ],
[hitMotherShip, (score, _) => score + 2000 ]
)
```
In the example, the `score` property is updated when either `hitUfo` or `hitMotherShip` occur. The `scoreMultiplier` Property is sampled to take multiplier into account when `hitUfo` occurs.
* @param initial
* @param {UpdatePattern<Out>} patterns
* @returns {Property<Out>}
*/
export function update<Out>(
initial: Out,
...patterns: UpdatePattern<Out>[]
): PropertySeed<Out>
export function update<Out>(
scope: Scope,
initial: Out,
...patterns: UpdatePattern<Out>[]
): Property<Out>
export function update<Out>(...args: any[]): any {
let scope: Scope | undefined
let initial: Out
let patterns: UpdatePattern<Out>[]
if (isScope(args[0])) {
scope = args[0]
initial = args[1]
patterns = args.slice(2)
} else {
scope = undefined
initial = args[0]
patterns = args.slice(1)
}
let mutators: EventStreamSeed<Mutation<Out>>[] = patterns.map((pattern) => {
if (pattern.length < 2)
throw Error(`Illegal pattern ${pattern}, length must be >= 2`)
let sources: UpdateParam<Out>[] = pattern.slice(
0,
pattern.length - 1
) as any
const trigger = sources[0]
if (!isEventStreamSeed(trigger))
throw Error(`Illegal pattern ${pattern}, must contain one EventStream`)
const properties = sources.slice(1) as Property<any>[]
for (let prop of properties) {
if (!isPropertySeed(prop))
throw Error(
`Illegal pattern ${pattern}. After one EventStream the rest on the observables must be Properties`
)
}
let combinator = pattern[pattern.length - 1] as (...args: any) => Out
if (!(combinator instanceof Function)) {
const constantValue = combinator
combinator = () => constantValue
}
return map((v1) => {
return (state: Out) => {
const propValues = properties.map((p) => p.get())
return combinator(state, v1, ...propValues)
}
})(trigger as EventStreamSeed<any>)
})
return rename(
["update", [initial, patterns]],
applyScopeMaybe(
scan<Mutation<Out>, Out>(initial, (state, mutation) => mutation(state))(
merge(mutators)
),
scope
)
)
}
type Mutation<V> = (prev: V) => V