@benev/tact 0.4.0-6 → 0.4.0-8

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 (82) hide show
  1. package/README.md +95 -26
  2. package/package.json +3 -2
  3. package/s/lib/input/controller.ts +4 -1
  4. package/s/lib/input/data/intent/decode.ts +2 -2
  5. package/s/lib/input/data/intent/encode.ts +2 -2
  6. package/s/lib/input/data/intent/minihash.ts +5 -0
  7. package/s/lib/input/decoder.ts +2 -2
  8. package/s/lib/input/evaluate/context.ts +5 -1
  9. package/s/lib/input/evaluate/evaluate.ts +27 -14
  10. package/s/lib/input/investigate/hash-bindings.ts +5 -3
  11. package/s/lib/input/investigate/investigate.ts +24 -9
  12. package/s/lib/input/test.ts +52 -30
  13. package/s/lib/input/testing/quickbind.ts +3 -3
  14. package/s/lib/ui/deck/auto-gamepads.ts +1 -0
  15. package/s/lib/ui/deck/collector.ts +3 -3
  16. package/s/lib/ui/deck/hub.ts +4 -3
  17. package/s/lib/ui/deck/setup/deck.ts +33 -20
  18. package/s/lib/ui/deck/setup/tact.ts +20 -11
  19. package/s/lib/ui/deck/setup/types.ts +1 -0
  20. package/s/lib/ui/deck/setup/utils/broadcast-channel.ts +17 -0
  21. package/s/lib/ui/deck/setup/utils/kv.ts +20 -0
  22. package/s/lib/ui/deck/types.ts +1 -1
  23. package/s/lib/ui/views/customize/view.ts +3 -3
  24. package/s/lib/utils/hash.ts +47 -0
  25. package/x/demo/main.bundle.min.js +104 -104
  26. package/x/demo/main.bundle.min.js.map +4 -4
  27. package/x/gamepad/index.html +1 -1
  28. package/x/index.html +2 -2
  29. package/x/lib/input/controller.js +1 -1
  30. package/x/lib/input/controller.js.map +1 -1
  31. package/x/lib/input/data/intent/decode.d.ts +1 -1
  32. package/x/lib/input/data/intent/decode.js +2 -2
  33. package/x/lib/input/data/intent/decode.js.map +1 -1
  34. package/x/lib/input/data/intent/encode.d.ts +1 -1
  35. package/x/lib/input/data/intent/encode.js +2 -2
  36. package/x/lib/input/data/intent/encode.js.map +1 -1
  37. package/x/lib/input/data/intent/minihash.d.ts +1 -0
  38. package/x/lib/input/data/intent/minihash.js +4 -0
  39. package/x/lib/input/data/intent/minihash.js.map +1 -0
  40. package/x/lib/input/decoder.d.ts +1 -1
  41. package/x/lib/input/decoder.js +2 -2
  42. package/x/lib/input/decoder.js.map +1 -1
  43. package/x/lib/input/evaluate/context.d.ts +3 -1
  44. package/x/lib/input/evaluate/context.js.map +1 -1
  45. package/x/lib/input/evaluate/evaluate.js +24 -11
  46. package/x/lib/input/evaluate/evaluate.js.map +1 -1
  47. package/x/lib/input/investigate/hash-bindings.d.ts +1 -1
  48. package/x/lib/input/investigate/hash-bindings.js +5 -3
  49. package/x/lib/input/investigate/hash-bindings.js.map +1 -1
  50. package/x/lib/input/investigate/investigate.d.ts +2 -1
  51. package/x/lib/input/investigate/investigate.js +14 -9
  52. package/x/lib/input/investigate/investigate.js.map +1 -1
  53. package/x/lib/input/test.d.ts +4 -2
  54. package/x/lib/input/test.js +49 -22
  55. package/x/lib/input/test.js.map +1 -1
  56. package/x/lib/input/testing/quickbind.d.ts +3 -2
  57. package/x/lib/input/testing/quickbind.js +3 -3
  58. package/x/lib/input/testing/quickbind.js.map +1 -1
  59. package/x/lib/ui/deck/auto-gamepads.js +1 -0
  60. package/x/lib/ui/deck/auto-gamepads.js.map +1 -1
  61. package/x/lib/ui/deck/collector.js +2 -2
  62. package/x/lib/ui/deck/collector.js.map +1 -1
  63. package/x/lib/ui/deck/hub.js +4 -3
  64. package/x/lib/ui/deck/hub.js.map +1 -1
  65. package/x/lib/ui/deck/setup/deck.js +23 -13
  66. package/x/lib/ui/deck/setup/deck.js.map +1 -1
  67. package/x/lib/ui/deck/setup/tact.d.ts +2 -2
  68. package/x/lib/ui/deck/setup/tact.js +15 -8
  69. package/x/lib/ui/deck/setup/tact.js.map +1 -1
  70. package/x/lib/ui/deck/setup/types.d.ts +1 -0
  71. package/x/lib/ui/deck/setup/utils/broadcast-channel.d.ts +4 -0
  72. package/x/lib/ui/deck/setup/utils/broadcast-channel.js +10 -0
  73. package/x/lib/ui/deck/setup/utils/broadcast-channel.js.map +1 -0
  74. package/x/lib/ui/deck/setup/utils/kv.d.ts +5 -0
  75. package/x/lib/ui/deck/setup/utils/kv.js +12 -0
  76. package/x/lib/ui/deck/setup/utils/kv.js.map +1 -0
  77. package/x/lib/ui/deck/types.d.ts +1 -0
  78. package/x/lib/ui/views/customize/view.js +3 -3
  79. package/x/lib/ui/views/customize/view.js.map +1 -1
  80. package/x/lib/utils/hash.d.ts +1 -0
  81. package/x/lib/utils/hash.js +47 -0
  82. package/x/lib/utils/hash.js.map +1 -0
@@ -1,31 +1,44 @@
1
1
 
2
- import {IdbMagazine, idbOpen, Kv} from "@e280/kv"
3
-
2
+ import {disposer, ev} from "@e280/stz"
4
3
  import {Hub} from "../hub.js"
5
4
  import {asDeck} from "../types.js"
5
+ import {setupKv} from "./utils/kv.js"
6
6
  import {SetupParams} from "./types.js"
7
7
  import {Controls} from "../controls/controls.js"
8
8
  import {Bindings} from "../../../input/types.js"
9
9
  import {firstPreset} from "../utils/first-preset.js"
10
+ import {setupBroadcastChannel} from "./utils/broadcast-channel.js"
10
11
 
11
12
  export async function setupDeck<B extends Bindings>(params: SetupParams<B>) {
12
- const kv = params.kv
13
- ?? new Kv(new IdbMagazine(await idbOpen("tact", "kv")))
14
-
15
- const broadcastChannel = params.broadcastChannel
16
- ?? new BroadcastChannel("tact")
17
-
18
- const {maxPlayers} = params
19
- const {bindings} = firstPreset(params.presets).profile
20
-
21
- const hub = new Hub(bindings)
22
- const controls = new Controls({...params, kv})
23
-
24
- await controls.load()
25
-
26
- broadcastChannel.onmessage = () => controls.load()
27
- controls.on(() => broadcastChannel.postMessage({}))
28
-
29
- return asDeck({hub, controls, maxPlayers})
13
+ const {dispose, schedule, disposable} = disposer()
14
+
15
+ const namespace = params.namespace
16
+ ? `tact_${params.namespace}`
17
+ : "tact"
18
+
19
+ const {kv} = disposable(
20
+ await setupKv(namespace, params.kv)
21
+ )
22
+
23
+ const {broadcastChannel} = disposable(
24
+ await setupBroadcastChannel(namespace, params.broadcastChannel)
25
+ )
26
+
27
+ try {
28
+ const {maxPlayers} = params
29
+ const {bindings} = firstPreset(params.presets).profile
30
+ const hub = new Hub(bindings)
31
+ const controls = new Controls({...params, kv})
32
+
33
+ schedule(ev(broadcastChannel, {message: () => controls.load()}))
34
+ schedule(controls.on(() => broadcastChannel.postMessage({})))
35
+
36
+ await controls.load()
37
+ return asDeck({hub, controls, maxPlayers, dispose})
38
+ }
39
+ catch (error) {
40
+ dispose()
41
+ throw error
42
+ }
30
43
  }
31
44
 
@@ -1,9 +1,9 @@
1
1
 
2
+ import {disposer} from "@e280/stz"
2
3
  import {register} from "@e280/sly"
3
4
 
4
5
  import {setupDeck} from "./deck.js"
5
6
  import {SetupParams} from "./types.js"
6
- import {Collector} from "../collector.js"
7
7
  import {Bindings} from "../../../input/types.js"
8
8
  import {autoGamepads} from "../auto-gamepads.js"
9
9
  import {ellipsis} from "../../../utils/ellipsis.js"
@@ -23,9 +23,10 @@ import devicesSvg from "../../icons/akar/devices.svg.js"
23
23
  import gameControllerSvg from "../../icons/akar/game-controller.svg.js"
24
24
 
25
25
  export async function setupTact<B extends Bindings>(params: SetupParams<B>) {
26
- const deck = await setupDeck(params)
26
+ const {dispose, schedule, disposable} = disposer()
27
+
28
+ const deck = disposable(await setupDeck(params))
27
29
  const first = firstPreset(deck.controls.presets)
28
- const collector = new Collector(first.profile.bindings)
29
30
  const visualizer = new Visualizer()
30
31
 
31
32
  const elements = {
@@ -36,7 +37,7 @@ export async function setupTact<B extends Bindings>(params: SetupParams<B>) {
36
37
  }
37
38
 
38
39
  function gamepads(defaultProfileKey?: string) {
39
- return autoGamepads(deck.hub, (controller, source) => {
40
+ const stop = autoGamepads(deck.hub, (controller, source) => {
40
41
  const handle = `${source.pad.gamepad.id} ${source.pad.gamepad.index + 1}`
41
42
  deck.controls.attach(controller, {
42
43
  icon: gameControllerSvg,
@@ -47,18 +48,20 @@ export async function setupTact<B extends Bindings>(params: SetupParams<B>) {
47
48
  })
48
49
  return () => deck.controls.detach(controller)
49
50
  })
51
+ schedule(stop)
52
+ return stop
50
53
  }
51
54
 
52
55
  function mouseKeyboard() {
53
56
  const {key, profile} = first
54
57
 
55
- const controller = new Controller({
58
+ const controller = disposable(new Controller({
56
59
  bindings: profile.bindings,
57
- source: new Sources(
58
- new KeyboardSource(),
59
- new PointerSource(),
60
- ),
61
- })
60
+ source: disposable(new Sources(
61
+ disposable(new KeyboardSource()),
62
+ disposable(new PointerSource()),
63
+ )),
64
+ }))
62
65
 
63
66
  deck.hub.plug(controller)
64
67
 
@@ -70,6 +73,11 @@ export async function setupTact<B extends Bindings>(params: SetupParams<B>) {
70
73
  defaultProfileKey: key,
71
74
  })
72
75
 
76
+ schedule(() => {
77
+ deck.hub.remove(controller)
78
+ deck.controls.detach(controller)
79
+ })
80
+
73
81
  return controller
74
82
  }
75
83
 
@@ -79,12 +87,13 @@ export async function setupTact<B extends Bindings>(params: SetupParams<B>) {
79
87
 
80
88
  return {
81
89
  deck,
82
- collector,
83
90
  gamepads,
84
91
  mouseKeyboard,
85
92
  visualizer,
86
93
  elements,
87
94
  registerElements,
95
+ encodeAll: deck.hub.encodeAll.bind(deck.hub),
96
+ dispose,
88
97
  }
89
98
  }
90
99
 
@@ -6,6 +6,7 @@ import {Bindings} from "../../../input/types.js"
6
6
  export type SetupParams<B extends Bindings> = {
7
7
  maxPlayers: number
8
8
  presets: Presets<B>
9
+ namespace?: string
9
10
  kv?: Kv
10
11
  broadcastChannel?: BroadcastChannel
11
12
  }
@@ -0,0 +1,17 @@
1
+
2
+ export async function setupBroadcastChannel(
3
+ namespace: string,
4
+ broadcastChannel: BroadcastChannel | undefined,
5
+ ) {
6
+
7
+ let dispose = () => {}
8
+
9
+ if (!broadcastChannel) {
10
+ const bc = new BroadcastChannel(namespace)
11
+ broadcastChannel = bc
12
+ dispose = () => bc.close()
13
+ }
14
+
15
+ return {broadcastChannel, dispose}
16
+ }
17
+
@@ -0,0 +1,20 @@
1
+
2
+ import {IdbMagazine, idbOpen, Kv} from "@e280/kv"
3
+
4
+ export async function setupKv(
5
+ namespace: string,
6
+ kv: Kv | undefined,
7
+ ) {
8
+
9
+ let dispose = () => {}
10
+
11
+ if (!kv) {
12
+ const idb = await idbOpen(namespace, "kv")
13
+ const magazine = new IdbMagazine(idb)
14
+ kv = new Kv(magazine)
15
+ dispose = () => idb.db.close()
16
+ }
17
+
18
+ return {kv, dispose}
19
+ }
20
+
@@ -4,7 +4,6 @@ import {Bindings} from "../../input/types.js"
4
4
  import {Controls} from "./controls/controls.js"
5
5
 
6
6
  export type PlayerId = number
7
-
8
7
  export type ProfileKey = string
9
8
 
10
9
  export type Profile<B extends Bindings = any> = {
@@ -19,6 +18,7 @@ export type Deck<B extends Bindings = any> = {
19
18
  hub: Hub<B>
20
19
  maxPlayers: number
21
20
  controls: Controls<B>
21
+ dispose: () => void
22
22
  }
23
23
 
24
24
  export const asDeck = <B extends Bindings>(deck: Deck<B>) => deck
@@ -27,17 +27,17 @@ export const [CustomizeView, makeCustomizeElement] = shadowDuplex((options: {con
27
27
  ? profiles.get(profile.basedOn)
28
28
  : profile
29
29
 
30
- const updateBindings = (bindings: Bindings) => {
30
+ const updateBindings = async(bindings: Bindings) => {
31
31
  let key = $profileKey()
32
32
 
33
33
  if (profiles.isPreset(key)) {
34
34
  const basedOn = key
35
35
  const freshKey = makeId()
36
36
  key = freshKey
37
- profiles.clone(freshKey, basedOn)
37
+ await profiles.clone(freshKey, basedOn)
38
38
  }
39
39
 
40
- profiles.update(key, {bindings})
40
+ await profiles.update(key, {bindings})
41
41
  $profileKey(key)
42
42
  }
43
43
 
@@ -0,0 +1,47 @@
1
+
2
+ import {blake3} from "@awasm/noble/js.js"
3
+ import {dataViewFrom, txt} from "@e280/stz"
4
+
5
+ const endian = false
6
+ enum EntropyType {String, Number, Bytes}
7
+
8
+ export function hash(...entropy: (string | number | Uint8Array)[]) {
9
+ if (entropy.length === 0)
10
+ throw new RangeError("hash requires entropy")
11
+
12
+ const hasher = blake3.create()
13
+ const typeTag = new Uint8Array(1)
14
+ const lengthTag = new Uint8Array(4)
15
+ const lengthDataview = dataViewFrom(lengthTag)
16
+
17
+ for (const part of entropy) {
18
+ if (typeof part === "string") {
19
+ const bytes = txt.toBytes(part)
20
+ typeTag[0] = EntropyType.String
21
+ lengthDataview.setUint32(0, bytes.byteLength, endian)
22
+ hasher.update(typeTag)
23
+ hasher.update(lengthTag)
24
+ hasher.update(bytes)
25
+ }
26
+ else if (typeof part === "number") {
27
+ const bytes = new Uint8Array(8)
28
+ dataViewFrom(bytes).setFloat64(0, part, endian)
29
+ typeTag[0] = EntropyType.Number
30
+ lengthDataview.setUint32(0, bytes.byteLength, endian)
31
+ hasher.update(typeTag)
32
+ hasher.update(lengthTag)
33
+ hasher.update(bytes)
34
+ }
35
+ else if (part instanceof Uint8Array) {
36
+ typeTag[0] = EntropyType.Bytes
37
+ lengthDataview.setUint32(0, part.byteLength, endian)
38
+ hasher.update(typeTag)
39
+ hasher.update(lengthTag)
40
+ hasher.update(part)
41
+ }
42
+ else throw new Error("unknown part type")
43
+ }
44
+
45
+ return hasher.digest() as Uint8Array
46
+ }
47
+