@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,45 @@
1
+
2
+ import {setupEureka} from "../../eureka.js"
3
+ import {EurekaContext} from "../../integration/context.js"
4
+
5
+ export class DemoContext extends EurekaContext {}
6
+
7
+ export type DemoComponents = {
8
+ health: number
9
+ bleeding: number
10
+ mana: number
11
+ manaRegen: number
12
+ }
13
+
14
+ export function setupEurekaDemo() {
15
+ const eureka = setupEureka<DemoContext, DemoComponents>()
16
+ const context = new DemoContext()
17
+ const world = eureka.world(context, [
18
+
19
+ eureka.system("mortality")
20
+ .select("health").andMaybe("bleeding")
21
+ .fn((entities, world) => {
22
+ for (const {id, components} of entities) {
23
+
24
+ // process bleeding
25
+ if (components.bleeding)
26
+ components.health -= components.bleeding
27
+
28
+ // process death
29
+ if (components.health <= 0)
30
+ world.delete(id)
31
+ }
32
+ }),
33
+
34
+ eureka.system("wizardry")
35
+ .select("mana").andMaybe("manaRegen")
36
+ .fn((entities, _world) => {
37
+ for (const {components} of entities)
38
+ if (components.manaRegen)
39
+ components.mana += components.manaRegen
40
+ }),
41
+
42
+ ])
43
+ return {context, world}
44
+ }
45
+
@@ -0,0 +1,6 @@
1
+
2
+ export * from "./core/contact/codecs.i.js"
3
+ export * from "./core/contact/contact.js"
4
+ export * from "./core/contact/types.js"
5
+ export * from "./core/contact/wiring.i.js"
6
+
@@ -0,0 +1,56 @@
1
+
2
+ import {Seat} from "./parts/seat.js"
3
+ import {Spoke} from "./parts/spoke.js"
4
+ import {MetaApi} from "./meta/types.js"
5
+ import {Liaison} from "../core/liaison.js"
6
+ import {FiberRpc} from "./parts/fiber-rpc.js"
7
+ import {Simulator} from "../core/simulator.js"
8
+ import {Speculator} from "../core/speculator.js"
9
+ import {makeMetaClientApi} from "./meta/meta-client.js"
10
+ import {InferSimulatorSchema, Telegram} from "../core/types.js"
11
+
12
+ export class SessionClient<xSimulator extends Simulator> {
13
+ static async make<xSimulator extends Simulator>(options: {
14
+ hz: number
15
+ spoke: Spoke
16
+ pastSimulator: xSimulator
17
+ futureSimulator: xSimulator
18
+ }) {
19
+
20
+ const meta = new FiberRpc<MetaApi["host"]>(
21
+ options.spoke.fibers.sub.meta,
22
+ makeMetaClientApi(),
23
+ ).remote as MetaApi["host"]
24
+
25
+ const {hostAuthorId, clientAuthorId} = await meta.hello()
26
+ const liaison = new Liaison<Telegram<any>>(hostAuthorId, options.spoke.fibers.sub.primary)
27
+ const seat = new Seat(options.spoke, liaison)
28
+
29
+ const speculator = new Speculator<InferSimulatorSchema<xSimulator>>(
30
+ clientAuthorId,
31
+ liaison,
32
+ options.pastSimulator,
33
+ options.futureSimulator,
34
+ options.hz,
35
+ )
36
+
37
+ return new this<xSimulator>(
38
+ seat,
39
+ options.pastSimulator,
40
+ options.futureSimulator,
41
+ speculator,
42
+ )
43
+ }
44
+
45
+ constructor(
46
+ public seat: Seat,
47
+ public pastSimulator: xSimulator,
48
+ public futureSimulator: xSimulator,
49
+ public speculator: Speculator<InferSimulatorSchema<xSimulator>>,
50
+ ) {}
51
+
52
+ disconnect() {
53
+ this.seat.disconnect()
54
+ }
55
+ }
56
+
@@ -0,0 +1,71 @@
1
+
2
+ import {MapG} from "@e280/stz"
3
+
4
+ import {Hub} from "./parts/hub.js"
5
+ import {Seat} from "./parts/seat.js"
6
+ import {MetaApi} from "./meta/types.js"
7
+ import {HostOn} from "./parts/host-on.js"
8
+ import {Liaison} from "../core/liaison.js"
9
+ import {FiberRpc} from "./parts/fiber-rpc.js"
10
+ import {Authority} from "../core/authority.js"
11
+ import {Simulator} from "../core/simulator.js"
12
+ import {makeMetaHostApi} from "./meta/meta-host.js"
13
+ import {AuthorId, InferSimulatorSchema, Telegram} from "../core/types.js"
14
+
15
+ export class SessionHost<xSimulator extends Simulator> {
16
+ authority: Authority<InferSimulatorSchema<xSimulator>>
17
+ seats = new MapG<AuthorId, Seat>()
18
+ on = new HostOn()
19
+
20
+ #cleanup = () => {}
21
+
22
+ constructor(
23
+ public hub: Hub,
24
+ public simulator: xSimulator,
25
+ ) {
26
+
27
+ const authority = new Authority(simulator)
28
+ this.authority = authority
29
+
30
+ this.#cleanup = hub.onSpoke(spoke => {
31
+ const authorId = authority.idCounter.next()
32
+
33
+ const liaison = new Liaison<Telegram<any>>(authorId, spoke.fibers.sub.primary)
34
+ authority.liaisons.add(liaison)
35
+ liaison.send(authority.getStateTelegram())
36
+
37
+ new FiberRpc<MetaApi["host"]>(
38
+ spoke.fibers.sub.meta,
39
+ makeMetaHostApi({authority, liaison}),
40
+ ).remote as MetaApi["client"]
41
+
42
+ const seat = new Seat(spoke, liaison)
43
+ this.seats.set(authorId, seat)
44
+ this.on.seated.pub(seat)
45
+
46
+ return () => {
47
+ authority.liaisons.delete(liaison)
48
+ this.#unseat(authorId)
49
+ }
50
+ })
51
+ }
52
+
53
+ #unseat(authorId: AuthorId) {
54
+ const seat = this.seats.get(authorId)
55
+ if (!seat) return undefined
56
+ seat.disconnect()
57
+ this.seats.delete(authorId)
58
+ this.on.unseated.pub(seat)
59
+ }
60
+
61
+ disconnectAll() {
62
+ for (const authorId of this.seats.keys())
63
+ this.#unseat(authorId)
64
+ }
65
+
66
+ dispose() {
67
+ this.#cleanup()
68
+ this.disconnectAll()
69
+ }
70
+ }
71
+
@@ -0,0 +1,5 @@
1
+
2
+ import {asFns, asMessengerRpc} from "@e280/renraku"
3
+
4
+ export const makeMetaClientApi = () => asMessengerRpc(async _meta => asFns({}))
5
+
@@ -0,0 +1,18 @@
1
+
2
+ import {asFns, asMessengerRpc} from "@e280/renraku"
3
+ import {Liaison} from "../../core/liaison.js"
4
+ import {Authority} from "../../core/authority.js"
5
+
6
+ export const makeMetaHostApi = (options: {
7
+ authority: Authority<any>,
8
+ liaison: Liaison<any>
9
+ }) => asMessengerRpc(async _meta => asFns({
10
+
11
+ async hello() {
12
+ return {
13
+ hostAuthorId: options.authority.authorId,
14
+ clientAuthorId: options.liaison.authorId,
15
+ }
16
+ },
17
+ }))
18
+
@@ -0,0 +1,15 @@
1
+
2
+ import {Fns} from "@e280/renraku"
3
+ import type {makeMetaHostApi} from "./meta-host.js"
4
+ import type {makeMetaClientApi} from "./meta-client.js"
5
+
6
+ export type CustomApi = {
7
+ host: Fns
8
+ client: Fns
9
+ }
10
+
11
+ export type MetaApi = {
12
+ host: Awaited<ReturnType<ReturnType<typeof makeMetaHostApi>>>
13
+ client: Awaited<ReturnType<ReturnType<typeof makeMetaClientApi>>>
14
+ }
15
+
@@ -0,0 +1,8 @@
1
+
2
+ import {sub} from "@e280/stz"
3
+ import {Seat} from "./seat.js"
4
+
5
+ export class ClientOn {
6
+ disconnected = sub<[Seat]>()
7
+ }
8
+
@@ -0,0 +1,28 @@
1
+
2
+ import {Fiber} from "../../core/fiber.js"
3
+ import {Conduit, Fns, JsonRpc, Messenger, MessengerRpc, Remote} from "@e280/renraku"
4
+
5
+ export class FiberConduit extends Conduit {
6
+ constructor(public fiber: Fiber<JsonRpc.Bidirectional>) {
7
+ super()
8
+ this.sendRequest.sub(m => fiber.reliable.send(m))
9
+ this.sendResponse.sub(m => fiber.reliable.send(m))
10
+ fiber.reliable.recv.on(m => this.recv.pub(m, {origin: ""}))
11
+ }
12
+ }
13
+
14
+ export class FiberRpc<RemoteFns extends Fns> {
15
+ remote: Remote<RemoteFns>
16
+ dispose: () => void
17
+
18
+ constructor(public fiber: Fiber<JsonRpc.Bidirectional>, rpc: MessengerRpc<any, RemoteFns>) {
19
+ const messenger = new Messenger<any, RemoteFns>({
20
+ rpc,
21
+ conduit: new FiberConduit(fiber),
22
+ timeout: 60_000,
23
+ })
24
+ this.remote = messenger.remote
25
+ this.dispose = () => {}
26
+ }
27
+ }
28
+
@@ -0,0 +1,9 @@
1
+
2
+ import {sub} from "@e280/stz"
3
+ import {Seat} from "./seat.js"
4
+
5
+ export class HostOn {
6
+ seated = sub<[Seat]>()
7
+ unseated = sub<[Seat]>()
8
+ }
9
+
@@ -0,0 +1,40 @@
1
+
2
+ import {Spoke} from "./spoke.js"
3
+
4
+ export type SpokeListener = (spoke: Spoke) => () => void
5
+
6
+ export class Hub {
7
+ spokes = new Set<Spoke>()
8
+ #listeners = new Set<SpokeListener>()
9
+
10
+ /** an incoming spoke has appeared, dispatch all onSpoke listeners */
11
+ addSpoke(spoke: Spoke) {
12
+ this.spokes.add(spoke)
13
+
14
+ const disconnectedFns: (() => void)[] = []
15
+
16
+ for (const fn of this.#listeners) {
17
+
18
+ // call every spoke listener
19
+ const spokeDisconnected = fn(spoke)
20
+
21
+ // accumulate each listener's disconnected callback
22
+ disconnectedFns.push(spokeDisconnected)
23
+ }
24
+
25
+ // when this spoke is disconnected, call every listener's disconnected callback
26
+ return () => {
27
+ this.spokes.delete(spoke)
28
+ disconnectedFns.forEach(d => d())
29
+ }
30
+ }
31
+
32
+ /** add a spoke listener to respond to incoming spokes */
33
+ onSpoke(fn: SpokeListener) {
34
+ this.#listeners.add(fn)
35
+
36
+ // callback to remove this listener
37
+ return () => void this.#listeners.delete(fn)
38
+ }
39
+ }
40
+
@@ -0,0 +1,14 @@
1
+
2
+ import {JsonRpc} from "@e280/renraku"
3
+ import {Fiber} from "../../core/fiber.js"
4
+
5
+ export class Netfibers {
6
+ sub = {
7
+ primary: new Fiber(),
8
+ userland: new Fiber(),
9
+ meta: new Fiber<JsonRpc.Bidirectional>(),
10
+ }
11
+
12
+ megafiber = Fiber.multiplex(this.sub)
13
+ }
14
+
@@ -0,0 +1,35 @@
1
+
2
+ import {asMessengerRpc, Fns} from "@e280/renraku"
3
+
4
+ import {Spoke} from "./spoke.js"
5
+ import {FiberRpc} from "./fiber-rpc.js"
6
+ import {Liaison} from "../../core/liaison.js"
7
+
8
+ export class Seat {
9
+ constructor(
10
+ private spoke: Spoke,
11
+ private liaison: Liaison<any>,
12
+ ) {
13
+ }
14
+
15
+ get authorId() {
16
+ return this.liaison.authorId
17
+ }
18
+
19
+ get rtt() {
20
+ return this.liaison.pingponger.averageRtt
21
+ }
22
+
23
+ get userland() {
24
+ return this.spoke.fibers.sub.userland
25
+ }
26
+
27
+ disconnect() {
28
+ this.spoke.disconnect()
29
+ }
30
+
31
+ userlandApi<RemoteFns extends Fns>(seat: Seat, hostFns: RemoteFns) {
32
+ return new FiberRpc<RemoteFns>(seat.userland, asMessengerRpc(async _meta => ({fns: hostFns}))).remote
33
+ }
34
+ }
35
+
@@ -0,0 +1,10 @@
1
+
2
+ import {Netfibers} from "./netfibers.js"
3
+
4
+ export class Spoke {
5
+ constructor(
6
+ public fibers: Netfibers,
7
+ public disconnect: () => void,
8
+ ) {}
9
+ }
10
+
@@ -0,0 +1,68 @@
1
+
2
+ import {sub} from "@e280/stz"
3
+ import {Ticker} from "../tools/ticker.js"
4
+ import {Hub} from "../session/parts/hub.js"
5
+ import {Fiber} from "../core/fiber.js"
6
+ import {Simulator} from "../core/simulator.js"
7
+ import {SessionHost} from "../session/host.js"
8
+ import {Spoke} from "../session/parts/spoke.js"
9
+ import {SessionClient} from "../session/client.js"
10
+ import {Netfibers} from "../session/parts/netfibers.js"
11
+ import {HubSparrowOptions, startSparrowHost} from "../transports/sparrow/start-sparrow-host.js"
12
+
13
+ export type EasyHostOptions<xSimulator extends Simulator> = {
14
+ hz: number
15
+ makeSimulator: () => xSimulator,
16
+ closed?: () => void
17
+ sparrow?: HubSparrowOptions
18
+ }
19
+
20
+ export class EasyHost<xSimulator extends Simulator> {
21
+ hub = new Hub()
22
+ onClosed = sub()
23
+
24
+ session: SessionHost<xSimulator>
25
+ ticker: Ticker
26
+
27
+ constructor(public options: EasyHostOptions<xSimulator>) {
28
+ this.session = new SessionHost<xSimulator>(this.hub, options.makeSimulator())
29
+ this.ticker = this.session.authority.makeTicker(options.hz)
30
+ }
31
+
32
+ async startHostingViaSparrow(sparrow?: HubSparrowOptions) {
33
+ return startSparrowHost({
34
+ sparrow,
35
+ hub: this.hub,
36
+ closed: () => this.onClosed.pub(),
37
+ })
38
+ }
39
+
40
+ async localClient() {
41
+ // establish fibers on both sides
42
+ const fibers = {
43
+ host: new Netfibers(),
44
+ client: new Netfibers(),
45
+ }
46
+
47
+ // entangle the host and client fibers
48
+ Fiber.entangle(fibers.host.megafiber, fibers.client.megafiber)
49
+
50
+ // create spokes
51
+ const spokes = {
52
+ host: new Spoke(fibers.host, () => {}),
53
+ client: new Spoke(fibers.client, () => {}),
54
+ }
55
+
56
+ // add the host-side spoke the host hub
57
+ this.hub.addSpoke(spokes.host)
58
+
59
+ // create a session client using the client spoke
60
+ return SessionClient.make<xSimulator>({
61
+ hz: this.options.hz,
62
+ spoke: spokes.client,
63
+ pastSimulator: this.options.makeSimulator(),
64
+ futureSimulator: this.options.makeSimulator(),
65
+ })
66
+ }
67
+ }
68
+
@@ -0,0 +1,17 @@
1
+
2
+ import {Science} from "@e280/science"
3
+ import eureka from "./eureka/testing/eureka.test.js"
4
+ import parcels from "./core/parcels/parcels.test.js"
5
+ import contact from "./core/contact/contact.test.js"
6
+ import integration from "./eureka/testing/integration.test.js"
7
+
8
+ import ecs from "../alpha/ecs/test.js"
9
+ import sim from "../alpha/sim/test.js"
10
+
11
+ await Science.run({
12
+ alpha: {ecs, sim},
13
+ eureka,
14
+ integration,
15
+ archimedes: {parcels, contact},
16
+ })
17
+
@@ -0,0 +1,29 @@
1
+
2
+ export class Averager {
3
+ #memory: number[] = []
4
+ #average: number = 0
5
+ #latest: number = 0
6
+
7
+ constructor(public size: number) {}
8
+
9
+ get average() { return this.#average }
10
+ get latest() { return this.#latest }
11
+
12
+ add(n: number) {
13
+ this.#latest = n
14
+ const memory = this.#memory
15
+
16
+ memory.push(n)
17
+
18
+ while (memory.length > this.size)
19
+ memory.shift()
20
+
21
+ if (memory.length > 0) {
22
+ const sum = memory.reduce((p, c) => p + c, 0)
23
+ this.#average = sum / memory.length
24
+ }
25
+
26
+ return this.#average
27
+ }
28
+ }
29
+
@@ -0,0 +1,17 @@
1
+
2
+ export class Bucket<T> {
3
+ #items: T[] = []
4
+
5
+ /** put an item in the bucket */
6
+ give(item: T) {
7
+ this.#items.push(item)
8
+ }
9
+
10
+ /** extract all items out of the bucket */
11
+ take() {
12
+ const items = this.#items
13
+ this.#items = []
14
+ return items
15
+ }
16
+ }
17
+
@@ -0,0 +1,32 @@
1
+
2
+ export type Chron<X> = {item: X, time: number}
3
+
4
+ export class Chronicle<X> {
5
+ #chrons: Chron<X>[] = []
6
+
7
+ constructor(public limit = 100) {}
8
+
9
+ add(item: X, time: number) {
10
+ this.#chrons.push({item, time})
11
+ this.#limit()
12
+ }
13
+
14
+ at(time: number) {
15
+ return this.#chrons
16
+ .filter(chron => chron.time === time)
17
+ .map(chron => chron.item)
18
+ }
19
+
20
+ since(time: number) {
21
+ return this.#chrons
22
+ .filter(chron => chron.time > time)
23
+ .map(chron => chron.item)
24
+ }
25
+
26
+ #limit() {
27
+ const {limit} = this
28
+ while (this.#chrons.length > limit)
29
+ this.#chrons.shift()
30
+ }
31
+ }
32
+
@@ -0,0 +1,5 @@
1
+
2
+ export function disposers(...fns: (() => void)[]) {
3
+ return () => fns.forEach(fn => fn())
4
+ }
5
+
@@ -0,0 +1,13 @@
1
+
2
+ export class IdCounter {
3
+ count: number
4
+
5
+ constructor(start: number = 0) {
6
+ this.count = start
7
+ }
8
+
9
+ next() {
10
+ return this.count++
11
+ }
12
+ }
13
+
@@ -0,0 +1,12 @@
1
+
2
+ export function* loop(n: number) {
3
+ for (let i = 0; i < n; i++)
4
+ yield i
5
+ }
6
+
7
+ export function* loop2d([columns, rows]: [number, number]) {
8
+ for (let y = 0; y < rows; y++)
9
+ for (let x = 0; x < columns; x++)
10
+ yield [x, y] as [number, number]
11
+ }
12
+
@@ -0,0 +1,22 @@
1
+
2
+ export type Fn<P extends any[]> = (...p: P) => void
3
+
4
+ export function pub<P extends any[]>(fn?: Fn<P>) {
5
+ const fns = new Set<Fn<P>>()
6
+
7
+ if (fn)
8
+ fns.add(fn)
9
+
10
+ function publish(...p: P) {
11
+ for (const fn of fns)
12
+ fn(...p)
13
+ }
14
+
15
+ publish.on = (fn: Fn<P>) => {
16
+ fns.add(fn)
17
+ return () => { fns.delete(fn) }
18
+ }
19
+
20
+ return publish
21
+ }
22
+
@@ -0,0 +1,22 @@
1
+
2
+ export class Ticker {
3
+ #active = false
4
+ constructor(public hz: number, private fn: () => void) {}
5
+
6
+ start() {
7
+ if (!this.#active) {
8
+ this.#active = true
9
+ const tick = () => {
10
+ if (!this.#active) return undefined
11
+ this.fn()
12
+ setTimeout(tick, 1000 / this.hz)
13
+ }
14
+ }
15
+ return () => this.stop()
16
+ }
17
+
18
+ stop() {
19
+ this.#active = false
20
+ }
21
+ }
22
+
@@ -0,0 +1,7 @@
1
+
2
+ export function u8(bytes: Uint8Array) {
3
+ return (bytes.buffer instanceof ArrayBuffer)
4
+ ? bytes as Uint8Array<ArrayBuffer>
5
+ : new Uint8Array(bytes)
6
+ }
7
+
@@ -0,0 +1,19 @@
1
+
2
+ import {Sparrow} from "sparrow-rtc"
3
+ import {netfibersFromCable} from "./utils/netfibers-from-cable.js"
4
+
5
+ export async function sparrowFibers(options: {
6
+ invite: string,
7
+ disconnected: () => void,
8
+ }) {
9
+
10
+ const sparrow = await Sparrow.join({
11
+ invite: options.invite,
12
+ disconnected: options.disconnected,
13
+ })
14
+
15
+ const fibers = netfibersFromCable(sparrow.connection.cable)
16
+
17
+ return {sparrow, fibers}
18
+ }
19
+
@@ -0,0 +1,26 @@
1
+
2
+ import {ConnectOptions, Sparrow, StdCable} from "sparrow-rtc"
3
+ import {Hub} from "../../session/parts/hub.js"
4
+ import {Spoke} from "../../session/parts/spoke.js"
5
+ import {netfibersFromCable} from "./utils/netfibers-from-cable.js"
6
+
7
+ export type HubSparrowOptions = Omit<ConnectOptions<StdCable>, "closed" | "welcome" | "cableConfig">
8
+
9
+ export async function startSparrowHost(options: {
10
+ hub: Hub
11
+ closed: () => void
12
+ sparrow?: HubSparrowOptions
13
+ }) {
14
+
15
+ return Sparrow.host({
16
+ ...(options.sparrow ?? {}),
17
+ closed: options.closed,
18
+ welcome: _prospect => connection => {
19
+ const fibers = netfibersFromCable(connection.cable)
20
+ const disconnect = () => connection.disconnect()
21
+ const spoke = new Spoke(fibers, disconnect)
22
+ return options.hub.addSpoke(spoke)
23
+ },
24
+ })
25
+ }
26
+