@benev/archimedes 0.1.0-1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +23 -0
- package/README.md +19 -0
- package/package.json +44 -0
- package/s/_archive/GLOSSARY.md +31 -0
- package/s/_archive/README.md +209 -0
- package/s/_archive/core/authority.ts +62 -0
- package/s/_archive/core/contact/codecs.i.ts +3 -0
- package/s/_archive/core/contact/codecs.ts +14 -0
- package/s/_archive/core/contact/contact.test.ts +32 -0
- package/s/_archive/core/contact/contact.ts +63 -0
- package/s/_archive/core/contact/types.ts +24 -0
- package/s/_archive/core/contact/wiring.i.ts +3 -0
- package/s/_archive/core/contact/wiring.ts +212 -0
- package/s/_archive/core/fiber.ts +94 -0
- package/s/_archive/core/liaison.ts +73 -0
- package/s/_archive/core/parcels/inbox.ts +83 -0
- package/s/_archive/core/parcels/parceller.ts +20 -0
- package/s/_archive/core/parcels/parcels.test.ts +136 -0
- package/s/_archive/core/parcels/types.ts +5 -0
- package/s/_archive/core/parcels/utils/nanny.ts +23 -0
- package/s/_archive/core/simulator.ts +11 -0
- package/s/_archive/core/speculator.ts +63 -0
- package/s/_archive/core/types.ts +32 -0
- package/s/_archive/core/utils/handle-telegrams.ts +34 -0
- package/s/_archive/core/utils/is-input-dispatch.ts +10 -0
- package/s/_archive/core/utils/make-telegram-for-inputs.ts +12 -0
- package/s/_archive/core/utils/on-channel-message.ts +11 -0
- package/s/_archive/eureka/eureka.ts +20 -0
- package/s/_archive/eureka/index.ts +0 -0
- package/s/_archive/eureka/integration/context.ts +9 -0
- package/s/_archive/eureka/integration/simulator.ts +75 -0
- package/s/_archive/eureka/integration/types.ts +12 -0
- package/s/_archive/eureka/integration/utils/inputs.ts +24 -0
- package/s/_archive/eureka/integration/utils/relevance.ts +10 -0
- package/s/_archive/eureka/parts/entity.ts +37 -0
- package/s/_archive/eureka/parts/system.ts +38 -0
- package/s/_archive/eureka/parts/types.ts +21 -0
- package/s/_archive/eureka/parts/world.ts +140 -0
- package/s/_archive/eureka/testing/eureka.test.ts +27 -0
- package/s/_archive/eureka/testing/integration.test.ts +40 -0
- package/s/_archive/eureka/testing/situations/health.ts +43 -0
- package/s/_archive/eureka/testing/situations/integration.ts +45 -0
- package/s/_archive/index.ts +6 -0
- package/s/_archive/session/client.ts +56 -0
- package/s/_archive/session/host.ts +71 -0
- package/s/_archive/session/meta/meta-client.ts +5 -0
- package/s/_archive/session/meta/meta-host.ts +18 -0
- package/s/_archive/session/meta/types.ts +15 -0
- package/s/_archive/session/parts/client-on.ts +8 -0
- package/s/_archive/session/parts/fiber-rpc.ts +28 -0
- package/s/_archive/session/parts/host-on.ts +9 -0
- package/s/_archive/session/parts/hub.ts +40 -0
- package/s/_archive/session/parts/netfibers.ts +14 -0
- package/s/_archive/session/parts/seat.ts +35 -0
- package/s/_archive/session/parts/spoke.ts +10 -0
- package/s/_archive/sugar/easy-host.ts +68 -0
- package/s/_archive/tests.test.ts +17 -0
- package/s/_archive/tools/averager.ts +29 -0
- package/s/_archive/tools/bucket.ts +17 -0
- package/s/_archive/tools/chronicle.ts +32 -0
- package/s/_archive/tools/disposers.ts +5 -0
- package/s/_archive/tools/id-counter.ts +13 -0
- package/s/_archive/tools/loop.ts +12 -0
- package/s/_archive/tools/pub.ts +22 -0
- package/s/_archive/tools/ticker.ts +22 -0
- package/s/_archive/tools/u8.ts +7 -0
- package/s/_archive/transports/sparrow/fibers.ts +19 -0
- package/s/_archive/transports/sparrow/start-sparrow-host.ts +26 -0
- package/s/_archive/transports/sparrow/utils/netfibers-from-cable.ts +10 -0
- package/s/ecs/index.ts +7 -0
- package/s/ecs/parts/changers.ts +16 -0
- package/s/ecs/parts/make-id.ts +7 -0
- package/s/ecs/parts/world.ts +68 -0
- package/s/ecs/test/setup-example-world.ts +42 -0
- package/s/ecs/test/setup-lifecycle-counts.ts +17 -0
- package/s/ecs/test.ts +99 -0
- package/s/ecs/types.ts +19 -0
- package/s/ecs/utils/is-match.ts +7 -0
- package/s/ecs/utils/optimizer.ts +38 -0
- package/s/index.ts +3 -0
- package/s/net/test.ts +9 -0
- package/s/net/types.ts +1 -0
- package/s/sim/test.ts +9 -0
- package/s/sim/types.ts +1 -0
- package/s/test.ts +8 -0
- package/x/ecs/index.d.ts +4 -0
- package/x/ecs/index.js +5 -0
- package/x/ecs/index.js.map +1 -0
- package/x/ecs/parts/changers.d.ts +4 -0
- package/x/ecs/parts/changers.js +11 -0
- package/x/ecs/parts/changers.js.map +1 -0
- package/x/ecs/parts/make-id.d.ts +1 -0
- package/x/ecs/parts/make-id.js +5 -0
- package/x/ecs/parts/make-id.js.map +1 -0
- package/x/ecs/parts/world.d.ts +11 -0
- package/x/ecs/parts/world.js +59 -0
- package/x/ecs/parts/world.js.map +1 -0
- package/x/ecs/test/setup-example-world.d.ts +10 -0
- package/x/ecs/test/setup-example-world.js +31 -0
- package/x/ecs/test/setup-example-world.js.map +1 -0
- package/x/ecs/test/setup-lifecycle-counts.d.ts +6 -0
- package/x/ecs/test/setup-lifecycle-counts.js +15 -0
- package/x/ecs/test/setup-lifecycle-counts.js.map +1 -0
- package/x/ecs/test.d.ts +13 -0
- package/x/ecs/test.js +85 -0
- package/x/ecs/test.js.map +1 -0
- package/x/ecs/types.d.ts +12 -0
- package/x/ecs/types.js +2 -0
- package/x/ecs/types.js.map +1 -0
- package/x/ecs/utils/is-match.d.ts +2 -0
- package/x/ecs/utils/is-match.js +4 -0
- package/x/ecs/utils/is-match.js.map +1 -0
- package/x/ecs/utils/optimizer.d.ts +9 -0
- package/x/ecs/utils/optimizer.js +37 -0
- package/x/ecs/utils/optimizer.js.map +1 -0
- package/x/index.d.ts +1 -0
- package/x/index.js +2 -0
- package/x/index.js.map +1 -0
- package/x/net/test.d.ts +4 -0
- package/x/net/test.js +7 -0
- package/x/net/test.js.map +1 -0
- package/x/net/types.d.ts +1 -0
- package/x/net/types.js +2 -0
- package/x/net/types.js.map +1 -0
- package/x/sim/test.d.ts +4 -0
- package/x/sim/test.js +7 -0
- package/x/sim/test.js.map +1 -0
- package/x/sim/types.d.ts +1 -0
- package/x/sim/types.js +2 -0
- package/x/sim/types.js.map +1 -0
- package/x/test.d.ts +1 -0
- package/x/test.js +6 -0
- package/x/test.js.map +1 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
|
|
2
|
+
import {coalesce} from "@e280/stz"
|
|
3
|
+
import {StdCable} from "sparrow-rtc"
|
|
4
|
+
|
|
5
|
+
import {json} from "./codecs.js"
|
|
6
|
+
import {Codec} from "./types.js"
|
|
7
|
+
import {u8} from "../../tools/u8.js"
|
|
8
|
+
import {Contact} from "./contact.js"
|
|
9
|
+
import {onChannelMessage} from "../utils/on-channel-message.js"
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* bob sends whatever alice sends. (alice.send->bob.send)
|
|
13
|
+
*
|
|
14
|
+
* bob is forwarding alice's outgoing mail.
|
|
15
|
+
*
|
|
16
|
+
* ```
|
|
17
|
+
* alice bob
|
|
18
|
+
* ----- ---
|
|
19
|
+
* [send] --------> [send]
|
|
20
|
+
*
|
|
21
|
+
* [recv] [recv]
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export function send<O>(alice: Contact<any, O>, ...bobs: Contact<any, O>[]) {
|
|
25
|
+
return alice.send.on((output, reliable) => {
|
|
26
|
+
for (const bob of bobs)
|
|
27
|
+
bob.send(output, reliable)
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* bob receives whatever alice receives. (alice.recv->bob.recv)
|
|
33
|
+
*
|
|
34
|
+
* bob is cc'd on alice's incoming mail.
|
|
35
|
+
*
|
|
36
|
+
* ```
|
|
37
|
+
* alice bob
|
|
38
|
+
* ----- ---
|
|
39
|
+
* [send] [send]
|
|
40
|
+
*
|
|
41
|
+
* [recv] --------> [recv]
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export function recv<I>(alice: Contact<I, any>, ...bobs: Contact<I, any>[]) {
|
|
45
|
+
return alice.recv.on((input, reliable) => {
|
|
46
|
+
for (const bob of bobs)
|
|
47
|
+
bob.recv(input, reliable)
|
|
48
|
+
})
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* bob sends and receives what alice sends and receives. (alice.send->bob.send + alice.recv->bob.recv)
|
|
53
|
+
*
|
|
54
|
+
* bob is alice's trusted mail confidant. he forwards her outgoing mail, and he's even cc'd on her incoming mail.
|
|
55
|
+
*
|
|
56
|
+
* ```
|
|
57
|
+
* alice bob
|
|
58
|
+
* ----- ---
|
|
59
|
+
* [send] --------> [send]
|
|
60
|
+
*
|
|
61
|
+
* [recv] --------> [recv]
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
export function mirror<I, O = I>(alice: Contact<I, O>, ...bobs: Contact<I, O>[]) {
|
|
65
|
+
return coalesce(
|
|
66
|
+
send(alice, ...bobs),
|
|
67
|
+
recv(alice, ...bobs),
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* bob sends what alice sends, alice receives what bob receives. (alice.send->bob.send + bob.recv->alice.recv)
|
|
73
|
+
*
|
|
74
|
+
* bob is alice's local post office. he forwards her incoming and outgoing mail.
|
|
75
|
+
*
|
|
76
|
+
* ```
|
|
77
|
+
* alice bob
|
|
78
|
+
* ----- ---
|
|
79
|
+
* [send] --------> [send]
|
|
80
|
+
*
|
|
81
|
+
* [recv] <-------- [recv]
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
export function relay<I, O = I>(alice: Contact<I, O>, ...bobs: Contact<I, O>[]) {
|
|
85
|
+
return coalesce(
|
|
86
|
+
send(alice, ...bobs),
|
|
87
|
+
...bobs.map(bob => recv(bob, alice))
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* bob receives whatever alice sends. (alice.send->bob.recv)
|
|
93
|
+
*
|
|
94
|
+
* bob is the recipient of alice's love letters.
|
|
95
|
+
*
|
|
96
|
+
* ```
|
|
97
|
+
* alice bob
|
|
98
|
+
* ----- ---
|
|
99
|
+
* [send] ---\ [send]
|
|
100
|
+
* \
|
|
101
|
+
* [recv] \--> [recv]
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
export function delivery<O>(alice: Contact<any, O>, ...bobs: Contact<O, any>[]) {
|
|
105
|
+
return alice.send.on((data, reliable) => {
|
|
106
|
+
for (const bob of bobs)
|
|
107
|
+
bob.recv(data, reliable)
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* bob sends whatever alice receives. (alice.recv->bob.send)
|
|
113
|
+
*
|
|
114
|
+
* bob is a federal agent illegally snooping on alice's incoming mail, and sending copies back to headquarters without a warrant.
|
|
115
|
+
*
|
|
116
|
+
* ```
|
|
117
|
+
* alice bob
|
|
118
|
+
* ----- ---
|
|
119
|
+
* [send] /--> [send]
|
|
120
|
+
* /
|
|
121
|
+
* [recv] ---/ [recv]
|
|
122
|
+
* ```
|
|
123
|
+
*/
|
|
124
|
+
export function spy<I>(alice: Contact<I, any>, ...bobs: Contact<any, I>[]) {
|
|
125
|
+
return alice.recv.on((data, reliable) => {
|
|
126
|
+
for (const bob of bobs)
|
|
127
|
+
bob.send(data, reliable)
|
|
128
|
+
})
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* alice and bob both receive what the other sends.
|
|
133
|
+
*
|
|
134
|
+
* bob and alice are in love, and are sending each other letters.
|
|
135
|
+
*
|
|
136
|
+
* ```
|
|
137
|
+
* alice bob
|
|
138
|
+
* ----- ---
|
|
139
|
+
* [send] ---\ /--- [send]
|
|
140
|
+
* x
|
|
141
|
+
* [recv] <--/ \--> [recv]
|
|
142
|
+
* ```
|
|
143
|
+
*/
|
|
144
|
+
export function exchange<I, O = I>(alice: Contact<I, O>, ...bobs: Contact<O, I>[]) {
|
|
145
|
+
return coalesce(
|
|
146
|
+
delivery(alice, ...bobs),
|
|
147
|
+
...bobs.map(bob => delivery(bob, alice))
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* alice and bob both send what the other receives.
|
|
153
|
+
*
|
|
154
|
+
* bob and alice are having a divorce, and they are both snooping on each other's mail, and sending copies to their lawyers.
|
|
155
|
+
*
|
|
156
|
+
* ```
|
|
157
|
+
* alice bob
|
|
158
|
+
* ----- ---
|
|
159
|
+
* [send] <--\ /--> [send]
|
|
160
|
+
* x
|
|
161
|
+
* [recv] ---/ \--- [recv]
|
|
162
|
+
* ```
|
|
163
|
+
*/
|
|
164
|
+
export function spyExchange<I, O = I>(alice: Contact<I, O>, ...bobs: Contact<O, I>[]) {
|
|
165
|
+
return coalesce(
|
|
166
|
+
spy(alice, ...bobs),
|
|
167
|
+
...bobs.map(bob => spy(bob, alice))
|
|
168
|
+
)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* bob becomes a relay for alice, but bob sends and receives bytes.
|
|
173
|
+
*
|
|
174
|
+
* ```
|
|
175
|
+
* alice bob
|
|
176
|
+
* ----- ---
|
|
177
|
+
* [send] --------> [send(bytes)]
|
|
178
|
+
*
|
|
179
|
+
* [recv] <-------- [recv(bytes)]
|
|
180
|
+
* ```
|
|
181
|
+
*/
|
|
182
|
+
export function relayBinary(alice: Contact, bob: Contact<Uint8Array>, codec: Codec = json) {
|
|
183
|
+
return coalesce(
|
|
184
|
+
alice.send.on((output, reliable) => bob.send(codec.encode(output), reliable)),
|
|
185
|
+
bob.recv.on((input, reliable) => alice.recv(codec.decode(input), reliable)),
|
|
186
|
+
)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* cable becomes a relay for alice.
|
|
191
|
+
*
|
|
192
|
+
* ```
|
|
193
|
+
* alice cable
|
|
194
|
+
* ----- -----
|
|
195
|
+
* [send] --------> [send]
|
|
196
|
+
*
|
|
197
|
+
* [recv] <-------- [recv]
|
|
198
|
+
* ```
|
|
199
|
+
*/
|
|
200
|
+
export function relayCable(alice: Contact, cable: StdCable, codec: Codec = json) {
|
|
201
|
+
const bob = new Contact<Uint8Array>()
|
|
202
|
+
return coalesce(
|
|
203
|
+
relayBinary(alice, bob, codec),
|
|
204
|
+
bob.send.on((output, reliable) => {
|
|
205
|
+
if (reliable) cable.reliable.send(u8(output))
|
|
206
|
+
else cable.unreliable.send(u8(output))
|
|
207
|
+
}),
|
|
208
|
+
onChannelMessage(cable.reliable, input => bob.recv(input, true)),
|
|
209
|
+
onChannelMessage(cable.unreliable, input => bob.recv(input, false)),
|
|
210
|
+
)
|
|
211
|
+
}
|
|
212
|
+
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
|
|
2
|
+
import {StdCable} from "sparrow-rtc"
|
|
3
|
+
import {encode, decode} from "@msgpack/msgpack"
|
|
4
|
+
|
|
5
|
+
import {pub} from "../tools/pub.js"
|
|
6
|
+
import {disposers} from "../tools/disposers.js"
|
|
7
|
+
import {onChannelMessage} from "./utils/on-channel-message.js"
|
|
8
|
+
|
|
9
|
+
/** an arbitrary data channel */
|
|
10
|
+
export class Bicomm<I, O = I> {
|
|
11
|
+
send = pub<[O]>()
|
|
12
|
+
recv = pub<[I]>()
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** infer a fiber's input data type */
|
|
16
|
+
export type FiberInput<F extends Fiber<any>> = (
|
|
17
|
+
F extends Fiber<infer M>
|
|
18
|
+
? M
|
|
19
|
+
: never
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
/** network interface */
|
|
23
|
+
export class Fiber<I = any, O = I> {
|
|
24
|
+
reliable = new Bicomm<I, O>()
|
|
25
|
+
unreliable = new Bicomm<I, O>()
|
|
26
|
+
|
|
27
|
+
/** create another fiber that is the remote end connected to this one */
|
|
28
|
+
extrude() {
|
|
29
|
+
const alice = this
|
|
30
|
+
const bob = new Fiber<O, I>()
|
|
31
|
+
const detach = Fiber.entangle(alice, bob)
|
|
32
|
+
return [bob, detach] as [Fiber<O, I>, () => void]
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** this fiber becomes a proxy of the cable */
|
|
36
|
+
attachCable(cable: StdCable) {
|
|
37
|
+
this.reliable.send.on(output => cable.reliable.send(new Uint8Array(encode(output))))
|
|
38
|
+
this.unreliable.send.on(output => cable.unreliable.send(new Uint8Array(encode(output))))
|
|
39
|
+
return disposers(
|
|
40
|
+
onChannelMessage(cable.reliable, input => this.reliable.recv(decode(input) as I)),
|
|
41
|
+
onChannelMessage(cable.unreliable, input => this.unreliable.recv(decode(input) as I)),
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** create a fiber as a proxy to the given cable */
|
|
46
|
+
static fromCable<I = any, O = I>(cable: StdCable) {
|
|
47
|
+
const fiber = new Fiber<I, O>()
|
|
48
|
+
fiber.attachCable(cable)
|
|
49
|
+
return fiber
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** weld two fibers together: messages sent to one are received by the other */
|
|
53
|
+
static entangle<I, O = I>(alice: Fiber<I, O>, bob: Fiber<O, I>) {
|
|
54
|
+
return disposers(
|
|
55
|
+
alice.reliable.send.on(m => bob.reliable.recv(m)),
|
|
56
|
+
alice.unreliable.send.on(m => bob.unreliable.recv(m)),
|
|
57
|
+
bob.reliable.send.on(m => alice.reliable.recv(m)),
|
|
58
|
+
bob.unreliable.send.on(m => alice.unreliable.recv(m)),
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** create two fibers that are entangled together: messages sent to one are received by the other */
|
|
63
|
+
static makeEntangledPair<M>() {
|
|
64
|
+
const alice = new this<M>()
|
|
65
|
+
const bob = new this<M>()
|
|
66
|
+
const detangle = this.entangle<M>(alice, bob)
|
|
67
|
+
return [alice, bob, detangle] as [Fiber<M>, Fiber<M>, () => void]
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** roll multiple subfibers into a single megafiber */
|
|
71
|
+
static multiplex<C extends {[key: string]: Fiber}>(fibers: C) {
|
|
72
|
+
const megafiber = new Fiber<{[K in keyof C]: [K, FiberInput<C[K]>]}[keyof C]>()
|
|
73
|
+
|
|
74
|
+
for (const [key, subfiber] of Object.entries(fibers)) {
|
|
75
|
+
subfiber.reliable.send.on(x => megafiber.reliable.send([key, x]))
|
|
76
|
+
subfiber.unreliable.send.on(x => megafiber.unreliable.send([key, x as any]))
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
megafiber.reliable.recv.on(([key, data]) => {
|
|
80
|
+
const subfiber = fibers[key as any]
|
|
81
|
+
if (!subfiber) throw new Error(`unknown subfiber "${key as any}"`)
|
|
82
|
+
subfiber.reliable.recv(data as any)
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
megafiber.unreliable.recv.on(([key, data]) => {
|
|
86
|
+
const subfiber = fibers[key as any]
|
|
87
|
+
if (!subfiber) throw new Error(`unknown subfiber "${key as any}"`)
|
|
88
|
+
subfiber.unreliable.recv(data as any)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
return megafiber
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
|
|
2
|
+
import {Ping, Pingponger, Pong} from "@e280/renraku"
|
|
3
|
+
import {AuthorId} from "./types.js"
|
|
4
|
+
import {Fiber} from "./fiber.js"
|
|
5
|
+
import {Bucket} from "../tools/bucket.js"
|
|
6
|
+
import {Parcel} from "./parcels/types.js"
|
|
7
|
+
import {ParcelInbox} from "./parcels/inbox.js"
|
|
8
|
+
import {Parceller} from "./parcels/parceller.js"
|
|
9
|
+
|
|
10
|
+
export type Datagram<Data> = ["data", Data]
|
|
11
|
+
|
|
12
|
+
export type Mail<Data> = (
|
|
13
|
+
| Ping
|
|
14
|
+
| Pong
|
|
15
|
+
| ["data", Data]
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
export class Liaison<Data> {
|
|
19
|
+
pingponger: Pingponger
|
|
20
|
+
inbox = new ParcelInbox<Mail<Data>>()
|
|
21
|
+
parceller = new Parceller<Mail<Data>>()
|
|
22
|
+
outbox = new Bucket<Parcel<Mail<Data>>>()
|
|
23
|
+
|
|
24
|
+
constructor(
|
|
25
|
+
/** author id of the remote partner (it's their id, not ours) */
|
|
26
|
+
public authorId: AuthorId,
|
|
27
|
+
|
|
28
|
+
public fiber: Fiber<Parcel<Mail<Data>>[]>,
|
|
29
|
+
) {
|
|
30
|
+
|
|
31
|
+
this.pingponger = new Pingponger({
|
|
32
|
+
timeout: 60_000,
|
|
33
|
+
send: p => {
|
|
34
|
+
const parcel = this.parceller.wrap(p)
|
|
35
|
+
fiber.unreliable.send([parcel])
|
|
36
|
+
},
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
const handleIncoming = (parcels: Parcel<Mail<Data>>[]) => parcels.forEach(
|
|
40
|
+
parcel => this.inbox.give(parcel)
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
this.fiber.reliable.recv.on(handleIncoming)
|
|
44
|
+
this.fiber.unreliable.recv.on(handleIncoming)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
queue(data: Data) {
|
|
48
|
+
const parcel = this.parceller.wrap(["data", data])
|
|
49
|
+
this.outbox.give(parcel)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
send(data?: Data) {
|
|
53
|
+
const parcels = this.outbox.take()
|
|
54
|
+
if (data) parcels.push(this.parceller.wrap(["data", data]))
|
|
55
|
+
this.fiber.unreliable.send(parcels)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
recv() {
|
|
59
|
+
const datas: Data[] = []
|
|
60
|
+
for (const message of this.inbox.take()) {
|
|
61
|
+
switch (message[0]) {
|
|
62
|
+
case "ping":
|
|
63
|
+
case "pong":
|
|
64
|
+
this.pingponger.recv(message)
|
|
65
|
+
break
|
|
66
|
+
default:
|
|
67
|
+
datas.push(message[1])
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return datas
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
|
|
2
|
+
import {Nanny} from "./utils/nanny.js"
|
|
3
|
+
import {Parcel, ParcelId} from "./types.js"
|
|
4
|
+
import {Averager} from "../../tools/averager.js"
|
|
5
|
+
|
|
6
|
+
/** inbox delays messages with a buffer time, and actively corrects for network packet jitter */
|
|
7
|
+
export class ParcelInbox<P> {
|
|
8
|
+
#start: number
|
|
9
|
+
#offsets: Averager
|
|
10
|
+
#buffer = new Map<ParcelId, Parcel<P>>
|
|
11
|
+
#nanny = new Nanny()
|
|
12
|
+
|
|
13
|
+
constructor(
|
|
14
|
+
public delay = 25,
|
|
15
|
+
public smoothing = 5,
|
|
16
|
+
private now = () => Date.now(),
|
|
17
|
+
) {
|
|
18
|
+
this.#start = now()
|
|
19
|
+
this.#offsets = new Averager(smoothing)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** put a parcel into this inbox */
|
|
23
|
+
give(parcel: Parcel<P>) {
|
|
24
|
+
const [id, time] = parcel
|
|
25
|
+
if (this.#buffer.has(id)) return
|
|
26
|
+
this.#buffer.set(id, parcel)
|
|
27
|
+
this.#offsets.add(this.#offset(time))
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** extract all *available* parcels from this inbox */
|
|
31
|
+
take(): P[] {
|
|
32
|
+
const ready: Parcel<P>[] = []
|
|
33
|
+
const localtime = this.#localtime
|
|
34
|
+
|
|
35
|
+
for (const parcel of this.#buffer.values()) {
|
|
36
|
+
const [id, time] = parcel
|
|
37
|
+
|
|
38
|
+
// computing jitter corrective timings
|
|
39
|
+
const offset = this.#offset(time, localtime)
|
|
40
|
+
const abberation = offset - this.#offsets.average
|
|
41
|
+
const correctedTime = (time + offset) - abberation
|
|
42
|
+
const since = localtime - correctedTime
|
|
43
|
+
|
|
44
|
+
// surface parcels that are 'ready' after jitter corrections
|
|
45
|
+
if (since >= this.delay) {
|
|
46
|
+
ready.push(parcel)
|
|
47
|
+
this.#buffer.delete(id)
|
|
48
|
+
|
|
49
|
+
// also surface any parcels that have a smaller id
|
|
50
|
+
for (const parcelB of this.#buffer.values()) {
|
|
51
|
+
const [idB] = parcelB
|
|
52
|
+
if (idB < id) {
|
|
53
|
+
ready.push(parcelB)
|
|
54
|
+
this.#buffer.delete(idB)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return ready
|
|
61
|
+
.sort(sortById)
|
|
62
|
+
.filter(this.#nanny.removeDuplicates)
|
|
63
|
+
.filter(this.#nanny.removeDisorderly)
|
|
64
|
+
.map(getPayload)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
get #localtime() {
|
|
68
|
+
return this.now() - this.#start
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
#offset(time: number, localtime = this.#localtime) {
|
|
72
|
+
return localtime - time
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function getPayload<P>([,,payload]: Parcel<P>): P {
|
|
77
|
+
return payload
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function sortById([idA]: Parcel<any>, [idB]: Parcel<any>) {
|
|
81
|
+
return idA - idB
|
|
82
|
+
}
|
|
83
|
+
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
|
|
2
|
+
import {Parcel} from "./types.js"
|
|
3
|
+
|
|
4
|
+
/** facility for wrapping messages into parcels that are ready to be handled by an inbox */
|
|
5
|
+
export class Parceller<P> {
|
|
6
|
+
#id = 0
|
|
7
|
+
#start: number
|
|
8
|
+
|
|
9
|
+
constructor(private now = () => Date.now()) {
|
|
10
|
+
this.#start = now()
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** parcelize a payload (wrap it into a parcel) */
|
|
14
|
+
wrap(payload: P): Parcel<P> {
|
|
15
|
+
const id = this.#id++
|
|
16
|
+
const time = this.now() - this.#start
|
|
17
|
+
return [id, time, payload]
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
|
|
2
|
+
import {Science, test, expect} from "@e280/science"
|
|
3
|
+
import {ParcelInbox} from "./inbox.js"
|
|
4
|
+
import {Parceller} from "./parceller.js"
|
|
5
|
+
import {loop} from "../../tools/loop.js"
|
|
6
|
+
|
|
7
|
+
export default Science.suite({
|
|
8
|
+
|
|
9
|
+
"one parcel": test(async() => {
|
|
10
|
+
let now = 0
|
|
11
|
+
const outbox = new Parceller<string>(() => now)
|
|
12
|
+
const inbox = new ParcelInbox<string>(100, 20, () => now)
|
|
13
|
+
const parcel = outbox.wrap("hello")
|
|
14
|
+
inbox.give(parcel)
|
|
15
|
+
now = 1000
|
|
16
|
+
const payloads = inbox.take()
|
|
17
|
+
expect(payloads.length).is(1)
|
|
18
|
+
expect(payloads.at(0)).is("hello")
|
|
19
|
+
}),
|
|
20
|
+
|
|
21
|
+
"many parcels": test(async() => {
|
|
22
|
+
let now = 0
|
|
23
|
+
const outbox = new Parceller<string>(() => now)
|
|
24
|
+
const inbox = new ParcelInbox<string>(100, 20, () => now)
|
|
25
|
+
const parcels = [...loop(100)].map(_ => {
|
|
26
|
+
now++
|
|
27
|
+
return outbox.wrap("a")
|
|
28
|
+
})
|
|
29
|
+
parcels.forEach(parcel => {
|
|
30
|
+
now++
|
|
31
|
+
inbox.give(parcel)
|
|
32
|
+
})
|
|
33
|
+
now = 1000
|
|
34
|
+
const payloads = inbox.take()
|
|
35
|
+
expect(payloads.length).is(100)
|
|
36
|
+
}),
|
|
37
|
+
|
|
38
|
+
"out of order packets are corrected": test(async() => {
|
|
39
|
+
let now = 0
|
|
40
|
+
const outbox = new Parceller<string>(() => now)
|
|
41
|
+
const inbox = new ParcelInbox<string>(100, 20, () => now)
|
|
42
|
+
|
|
43
|
+
const a = [...loop(10)].map(() => {
|
|
44
|
+
now += 1
|
|
45
|
+
return outbox.wrap("a")
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
const b = [...loop(10)].map(() => {
|
|
49
|
+
now += 1
|
|
50
|
+
return outbox.wrap("b")
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
const c = [...loop(10)].map(() => {
|
|
54
|
+
now += 1
|
|
55
|
+
return outbox.wrap("c")
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
// out of order
|
|
59
|
+
const parcels = [...b, ...a, ...c]
|
|
60
|
+
|
|
61
|
+
now = 100
|
|
62
|
+
parcels.forEach(parcel => {
|
|
63
|
+
now += 1
|
|
64
|
+
inbox.give(parcel)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
now = 300
|
|
68
|
+
const payloads = inbox.take()
|
|
69
|
+
expect(payloads.length).is(30)
|
|
70
|
+
expect(payloads.at(5)).is("a")
|
|
71
|
+
expect(payloads.at(15)).is("b")
|
|
72
|
+
expect(payloads.at(25)).is("c")
|
|
73
|
+
}),
|
|
74
|
+
|
|
75
|
+
"literally do the buffering": test(async() => {
|
|
76
|
+
let now = 0
|
|
77
|
+
const outbox = new Parceller<string>(() => now)
|
|
78
|
+
const inbox = new ParcelInbox<string>(100, 20, () => now)
|
|
79
|
+
|
|
80
|
+
const parcels = [...loop(20)].map(() => {
|
|
81
|
+
now++
|
|
82
|
+
return outbox.wrap("a")
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
now = 400
|
|
86
|
+
for (const parcel of parcels) {
|
|
87
|
+
now++
|
|
88
|
+
inbox.give(parcel)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
expect(inbox.take().length).is(0)
|
|
92
|
+
|
|
93
|
+
now = 510
|
|
94
|
+
expect(inbox.take().length).is(10)
|
|
95
|
+
|
|
96
|
+
now = 520
|
|
97
|
+
expect(inbox.take().length).is(10)
|
|
98
|
+
|
|
99
|
+
now = 600
|
|
100
|
+
expect(inbox.take().length).is(0)
|
|
101
|
+
}),
|
|
102
|
+
|
|
103
|
+
"specific abberation/jitter adjustment": test(async() => {
|
|
104
|
+
let now = 0
|
|
105
|
+
const outbox = new Parceller<string>(() => now)
|
|
106
|
+
const inbox = new ParcelInbox<string>(100, 20, () => now)
|
|
107
|
+
|
|
108
|
+
now = 100 // many parcels at once, establishes an average
|
|
109
|
+
const a = [...loop(20)].map(() => outbox.wrap("a"))
|
|
110
|
+
|
|
111
|
+
now = 110 // one special parcel at a different time
|
|
112
|
+
const b = outbox.wrap("b")
|
|
113
|
+
|
|
114
|
+
//////
|
|
115
|
+
|
|
116
|
+
// ingest most of the parcels at this time
|
|
117
|
+
now = 200
|
|
118
|
+
a.forEach(p => inbox.give(p))
|
|
119
|
+
|
|
120
|
+
// except our one special parcel will be particularly late
|
|
121
|
+
now = 275 // late!
|
|
122
|
+
inbox.give(b)
|
|
123
|
+
|
|
124
|
+
//////
|
|
125
|
+
|
|
126
|
+
// the first 20 should be available by now
|
|
127
|
+
now = 309
|
|
128
|
+
expect(inbox.take().length).is(20)
|
|
129
|
+
|
|
130
|
+
// the last special one should have been moved earlier,
|
|
131
|
+
// thus available by now
|
|
132
|
+
now = 320
|
|
133
|
+
expect(inbox.take().length).is(1)
|
|
134
|
+
}),
|
|
135
|
+
})
|
|
136
|
+
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
|
|
2
|
+
import {Parcel} from "../types.js"
|
|
3
|
+
|
|
4
|
+
export class Nanny {
|
|
5
|
+
biggest: number = -1
|
|
6
|
+
|
|
7
|
+
removeDuplicates = ([idA]: Parcel<any>, _index: number, parcels: Parcel<any>[]) => {
|
|
8
|
+
let count = 0
|
|
9
|
+
for (const [idB] of parcels) {
|
|
10
|
+
if (idB === idA) count += 1
|
|
11
|
+
if (count > 1) return false
|
|
12
|
+
}
|
|
13
|
+
return true
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
removeDisorderly = ([id]: Parcel<any>) => {
|
|
17
|
+
if (id <= this.biggest)
|
|
18
|
+
return false
|
|
19
|
+
this.biggest = id
|
|
20
|
+
return true
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
|
|
2
|
+
import {AuthorId, Schema, Telegram} from "./types.js"
|
|
3
|
+
import {handleTelegram} from "./utils/handle-telegrams.js"
|
|
4
|
+
|
|
5
|
+
export abstract class Simulator<xSchema extends Schema = any> {
|
|
6
|
+
static handleTelegram = handleTelegram
|
|
7
|
+
constructor(public state: xSchema["state"]) {}
|
|
8
|
+
abstract simulate(telegram: Telegram<xSchema>): xSchema["delta"]
|
|
9
|
+
abstract tailor(authorId: AuthorId, telegram: Telegram<xSchema>): Telegram<xSchema>
|
|
10
|
+
}
|
|
11
|
+
|