@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.
Files changed (133) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +19 -0
  3. package/package.json +44 -0
  4. package/s/_archive/GLOSSARY.md +31 -0
  5. package/s/_archive/README.md +209 -0
  6. package/s/_archive/core/authority.ts +62 -0
  7. package/s/_archive/core/contact/codecs.i.ts +3 -0
  8. package/s/_archive/core/contact/codecs.ts +14 -0
  9. package/s/_archive/core/contact/contact.test.ts +32 -0
  10. package/s/_archive/core/contact/contact.ts +63 -0
  11. package/s/_archive/core/contact/types.ts +24 -0
  12. package/s/_archive/core/contact/wiring.i.ts +3 -0
  13. package/s/_archive/core/contact/wiring.ts +212 -0
  14. package/s/_archive/core/fiber.ts +94 -0
  15. package/s/_archive/core/liaison.ts +73 -0
  16. package/s/_archive/core/parcels/inbox.ts +83 -0
  17. package/s/_archive/core/parcels/parceller.ts +20 -0
  18. package/s/_archive/core/parcels/parcels.test.ts +136 -0
  19. package/s/_archive/core/parcels/types.ts +5 -0
  20. package/s/_archive/core/parcels/utils/nanny.ts +23 -0
  21. package/s/_archive/core/simulator.ts +11 -0
  22. package/s/_archive/core/speculator.ts +63 -0
  23. package/s/_archive/core/types.ts +32 -0
  24. package/s/_archive/core/utils/handle-telegrams.ts +34 -0
  25. package/s/_archive/core/utils/is-input-dispatch.ts +10 -0
  26. package/s/_archive/core/utils/make-telegram-for-inputs.ts +12 -0
  27. package/s/_archive/core/utils/on-channel-message.ts +11 -0
  28. package/s/_archive/eureka/eureka.ts +20 -0
  29. package/s/_archive/eureka/index.ts +0 -0
  30. package/s/_archive/eureka/integration/context.ts +9 -0
  31. package/s/_archive/eureka/integration/simulator.ts +75 -0
  32. package/s/_archive/eureka/integration/types.ts +12 -0
  33. package/s/_archive/eureka/integration/utils/inputs.ts +24 -0
  34. package/s/_archive/eureka/integration/utils/relevance.ts +10 -0
  35. package/s/_archive/eureka/parts/entity.ts +37 -0
  36. package/s/_archive/eureka/parts/system.ts +38 -0
  37. package/s/_archive/eureka/parts/types.ts +21 -0
  38. package/s/_archive/eureka/parts/world.ts +140 -0
  39. package/s/_archive/eureka/testing/eureka.test.ts +27 -0
  40. package/s/_archive/eureka/testing/integration.test.ts +40 -0
  41. package/s/_archive/eureka/testing/situations/health.ts +43 -0
  42. package/s/_archive/eureka/testing/situations/integration.ts +45 -0
  43. package/s/_archive/index.ts +6 -0
  44. package/s/_archive/session/client.ts +56 -0
  45. package/s/_archive/session/host.ts +71 -0
  46. package/s/_archive/session/meta/meta-client.ts +5 -0
  47. package/s/_archive/session/meta/meta-host.ts +18 -0
  48. package/s/_archive/session/meta/types.ts +15 -0
  49. package/s/_archive/session/parts/client-on.ts +8 -0
  50. package/s/_archive/session/parts/fiber-rpc.ts +28 -0
  51. package/s/_archive/session/parts/host-on.ts +9 -0
  52. package/s/_archive/session/parts/hub.ts +40 -0
  53. package/s/_archive/session/parts/netfibers.ts +14 -0
  54. package/s/_archive/session/parts/seat.ts +35 -0
  55. package/s/_archive/session/parts/spoke.ts +10 -0
  56. package/s/_archive/sugar/easy-host.ts +68 -0
  57. package/s/_archive/tests.test.ts +17 -0
  58. package/s/_archive/tools/averager.ts +29 -0
  59. package/s/_archive/tools/bucket.ts +17 -0
  60. package/s/_archive/tools/chronicle.ts +32 -0
  61. package/s/_archive/tools/disposers.ts +5 -0
  62. package/s/_archive/tools/id-counter.ts +13 -0
  63. package/s/_archive/tools/loop.ts +12 -0
  64. package/s/_archive/tools/pub.ts +22 -0
  65. package/s/_archive/tools/ticker.ts +22 -0
  66. package/s/_archive/tools/u8.ts +7 -0
  67. package/s/_archive/transports/sparrow/fibers.ts +19 -0
  68. package/s/_archive/transports/sparrow/start-sparrow-host.ts +26 -0
  69. package/s/_archive/transports/sparrow/utils/netfibers-from-cable.ts +10 -0
  70. package/s/ecs/index.ts +7 -0
  71. package/s/ecs/parts/changers.ts +16 -0
  72. package/s/ecs/parts/make-id.ts +7 -0
  73. package/s/ecs/parts/world.ts +68 -0
  74. package/s/ecs/test/setup-example-world.ts +42 -0
  75. package/s/ecs/test/setup-lifecycle-counts.ts +17 -0
  76. package/s/ecs/test.ts +99 -0
  77. package/s/ecs/types.ts +19 -0
  78. package/s/ecs/utils/is-match.ts +7 -0
  79. package/s/ecs/utils/optimizer.ts +38 -0
  80. package/s/index.ts +3 -0
  81. package/s/net/test.ts +9 -0
  82. package/s/net/types.ts +1 -0
  83. package/s/sim/test.ts +9 -0
  84. package/s/sim/types.ts +1 -0
  85. package/s/test.ts +8 -0
  86. package/x/ecs/index.d.ts +4 -0
  87. package/x/ecs/index.js +5 -0
  88. package/x/ecs/index.js.map +1 -0
  89. package/x/ecs/parts/changers.d.ts +4 -0
  90. package/x/ecs/parts/changers.js +11 -0
  91. package/x/ecs/parts/changers.js.map +1 -0
  92. package/x/ecs/parts/make-id.d.ts +1 -0
  93. package/x/ecs/parts/make-id.js +5 -0
  94. package/x/ecs/parts/make-id.js.map +1 -0
  95. package/x/ecs/parts/world.d.ts +11 -0
  96. package/x/ecs/parts/world.js +59 -0
  97. package/x/ecs/parts/world.js.map +1 -0
  98. package/x/ecs/test/setup-example-world.d.ts +10 -0
  99. package/x/ecs/test/setup-example-world.js +31 -0
  100. package/x/ecs/test/setup-example-world.js.map +1 -0
  101. package/x/ecs/test/setup-lifecycle-counts.d.ts +6 -0
  102. package/x/ecs/test/setup-lifecycle-counts.js +15 -0
  103. package/x/ecs/test/setup-lifecycle-counts.js.map +1 -0
  104. package/x/ecs/test.d.ts +13 -0
  105. package/x/ecs/test.js +85 -0
  106. package/x/ecs/test.js.map +1 -0
  107. package/x/ecs/types.d.ts +12 -0
  108. package/x/ecs/types.js +2 -0
  109. package/x/ecs/types.js.map +1 -0
  110. package/x/ecs/utils/is-match.d.ts +2 -0
  111. package/x/ecs/utils/is-match.js +4 -0
  112. package/x/ecs/utils/is-match.js.map +1 -0
  113. package/x/ecs/utils/optimizer.d.ts +9 -0
  114. package/x/ecs/utils/optimizer.js +37 -0
  115. package/x/ecs/utils/optimizer.js.map +1 -0
  116. package/x/index.d.ts +1 -0
  117. package/x/index.js +2 -0
  118. package/x/index.js.map +1 -0
  119. package/x/net/test.d.ts +4 -0
  120. package/x/net/test.js +7 -0
  121. package/x/net/test.js.map +1 -0
  122. package/x/net/types.d.ts +1 -0
  123. package/x/net/types.js +2 -0
  124. package/x/net/types.js.map +1 -0
  125. package/x/sim/test.d.ts +4 -0
  126. package/x/sim/test.js +7 -0
  127. package/x/sim/test.js.map +1 -0
  128. package/x/sim/types.d.ts +1 -0
  129. package/x/sim/types.js +2 -0
  130. package/x/sim/types.js.map +1 -0
  131. package/x/test.d.ts +1 -0
  132. package/x/test.js +6 -0
  133. 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,5 @@
1
+
2
+ export type ParcelId = number
3
+ export type ParcelTime = number
4
+ export type Parcel<P> = [ParcelId, ParcelTime, P]
5
+
@@ -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
+