@benev/tact 0.4.0-7 → 0.4.0-9

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 (60) hide show
  1. package/README.md +22 -18
  2. package/package.json +6 -5
  3. package/s/lib/index.ts +1 -1
  4. package/s/lib/input/controller.ts +4 -1
  5. package/s/lib/input/data/intent/decode.ts +2 -2
  6. package/s/lib/input/data/intent/encode.ts +2 -2
  7. package/s/lib/input/data/intent/minihash.ts +5 -0
  8. package/s/lib/input/decoder.ts +2 -2
  9. package/s/lib/input/investigate/hash-bindings.ts +5 -3
  10. package/s/lib/input/investigate/investigate.ts +24 -9
  11. package/s/lib/input/test.ts +11 -0
  12. package/s/lib/input/testing/quickbind.ts +3 -3
  13. package/s/lib/ui/deck/hub.ts +4 -3
  14. package/s/lib/ui/deck/players.ts +73 -0
  15. package/s/lib/utils/hash.ts +47 -0
  16. package/x/demo/main.bundle.min.js +104 -104
  17. package/x/demo/main.bundle.min.js.map +4 -4
  18. package/x/gamepad/index.html +1 -1
  19. package/x/index.html +2 -2
  20. package/x/lib/index.d.ts +1 -1
  21. package/x/lib/index.js +1 -1
  22. package/x/lib/index.js.map +1 -1
  23. package/x/lib/input/controller.js +1 -1
  24. package/x/lib/input/controller.js.map +1 -1
  25. package/x/lib/input/data/intent/decode.d.ts +1 -1
  26. package/x/lib/input/data/intent/decode.js +2 -2
  27. package/x/lib/input/data/intent/decode.js.map +1 -1
  28. package/x/lib/input/data/intent/encode.d.ts +1 -1
  29. package/x/lib/input/data/intent/encode.js +2 -2
  30. package/x/lib/input/data/intent/encode.js.map +1 -1
  31. package/x/lib/input/data/intent/minihash.d.ts +1 -0
  32. package/x/lib/input/data/intent/minihash.js +4 -0
  33. package/x/lib/input/data/intent/minihash.js.map +1 -0
  34. package/x/lib/input/decoder.d.ts +1 -1
  35. package/x/lib/input/decoder.js +2 -2
  36. package/x/lib/input/decoder.js.map +1 -1
  37. package/x/lib/input/investigate/hash-bindings.d.ts +1 -1
  38. package/x/lib/input/investigate/hash-bindings.js +5 -3
  39. package/x/lib/input/investigate/hash-bindings.js.map +1 -1
  40. package/x/lib/input/investigate/investigate.d.ts +2 -1
  41. package/x/lib/input/investigate/investigate.js +14 -9
  42. package/x/lib/input/investigate/investigate.js.map +1 -1
  43. package/x/lib/input/test.d.ts +1 -0
  44. package/x/lib/input/test.js +10 -0
  45. package/x/lib/input/test.js.map +1 -1
  46. package/x/lib/input/testing/quickbind.d.ts +3 -2
  47. package/x/lib/input/testing/quickbind.js +3 -3
  48. package/x/lib/input/testing/quickbind.js.map +1 -1
  49. package/x/lib/ui/deck/hub.js +4 -3
  50. package/x/lib/ui/deck/hub.js.map +1 -1
  51. package/x/lib/ui/deck/players.d.ts +16 -0
  52. package/x/lib/ui/deck/players.js +56 -0
  53. package/x/lib/ui/deck/players.js.map +1 -0
  54. package/x/lib/utils/hash.d.ts +1 -0
  55. package/x/lib/utils/hash.js +47 -0
  56. package/x/lib/utils/hash.js.map +1 -0
  57. package/s/lib/ui/deck/collector.ts +0 -52
  58. package/x/lib/ui/deck/collector.d.ts +0 -13
  59. package/x/lib/ui/deck/collector.js +0 -35
  60. package/x/lib/ui/deck/collector.js.map +0 -1
package/README.md CHANGED
@@ -72,13 +72,14 @@ i care about the little things. all input types are boiled down to simple number
72
72
  - 💡 *you can call `tact.dispose()` to release resources.*
73
73
  - 🍏 **encoding, decoding, and your game logic.**
74
74
  ```ts
75
- import {Collector} from "@benev/tact"
75
+ import {Players} from "@benev/tact"
76
76
 
77
- const collector = new Collector(bindings)
77
+ const players = new Players(bindings)
78
78
 
79
79
  setInterval(() => {
80
80
  const buffer = tact.encodeAll()
81
- for (const {playerId, inputs} of collector.decodeAll(buffer)) {
81
+ players.decodeAll(buffer)
82
+ for (const [playerId, inputs] of players) {
82
83
 
83
84
  // your game logic can read these handy input values.
84
85
  inputs.jump.value // button-like values are numbers 0 to 1.
@@ -90,8 +91,8 @@ i care about the little things. all input types are boiled down to simple number
90
91
  }
91
92
  }, 16.67)
92
93
  ```
93
- - 💡 *you could stream the buffers across the network to a remote collector, maybe with [renraku](https://github.com/e280/renraku).*
94
- - 💡 *you could have the tact deck on the clientside, and the collector on the serverside (or a web worker!).*
94
+ - 💡 *maybe you'd like to stream the buffers over a network with [renraku](https://github.com/e280/renraku).*
95
+ - 💡 *maybe you'd like the tact deck on the clientside and the players on the serverside.*
95
96
  - 🍏 **put the `<tact-deck>` element in your html page.**
96
97
  ```html
97
98
  <tact-deck></tact-deck>
@@ -116,11 +117,11 @@ i care about the little things. all input types are boiled down to simple number
116
117
 
117
118
  - ⚙️ **pov: your app isn't a game, no multiplayer, no gamepads.**
118
119
  ```ts
119
- import {setupTact, InputDecoder} from "@benev/tact"
120
+ import {setupTact, Decoder} from "@benev/tact"
120
121
 
121
122
  const tact = await setupTact({presets, maxPlayers: 1})
122
123
  const controller = tact.mouseKeyboard()
123
- const decoder = new InputDecoder(bindings)
124
+ const decoder = new Decoder(bindings)
124
125
  tact.registerElements()
125
126
 
126
127
  setInterval(() => {
@@ -134,11 +135,11 @@ i care about the little things. all input types are boiled down to simple number
134
135
  - 💡 *then the only ui you need is the `<tact-customize>` panel (also available as a sly view `CustomizeView`).*
135
136
  - ⚙️ **pov: your app doesn't even want any ui, or persistence.**
136
137
  ```ts
137
- import {Controller, InputDecoder, KeyboardSource, PointerSource, Sources} from "@benev/tact"
138
+ import {Controller, Decoder, KeyboardSource, PointerSource, Sources} from "@benev/tact"
138
139
 
139
140
  const source = new Sources(new KeyboardSource(), new PointerSource())
140
141
  const controller = new Controller({bindings, source})
141
- const decoder = new InputDecoder(bindings)
142
+ const decoder = new Decoder(bindings)
142
143
 
143
144
  setInterval(() => {
144
145
  const inputs = decoder.decode(controller.encode())
@@ -165,14 +166,14 @@ i care about the little things. all input types are boiled down to simple number
165
166
  ```
166
167
  ```ts
167
168
  asBindings({
168
- actions: {
169
- special: {
170
- castMagicSpell: ["*", ["mod", "shift", "alt"], "KeyR"],
169
+ special: {
170
+ magic: {
171
+ castSpell: ["*", ["mod", "shift", "alt"], "KeyR"],
171
172
  }
172
173
  }
173
174
  })
174
175
 
175
- inputs.actions.special.castMagicSpell.value // 1
176
+ inputs.special.magic.castSpell.value // 1
176
177
  ```
177
178
  - 🧬 **you can differentiate taps versus holds on the same key.**
178
179
  ```ts
@@ -191,21 +192,24 @@ i care about the little things. all input types are boiled down to simple number
191
192
  lookX: ["accumulate", "pointer.x"],
192
193
  })
193
194
 
194
- inputs.lookX.sum // use sum instead of value, to ensure you don't lose any pointer movement.
195
+ inputs.lookX.sum // use sum instead of value.
195
196
  ```
196
197
  - 🧬 **gamepads thumbstick inputs are best done with some math.**
197
198
  ```ts
198
199
  asBindings({
199
200
  forward: ["*",
200
201
 
201
- // user-customizable sensitivity, equivalent with pointer-pixels-per-second.
202
+ // user-customizable sensitivity,
203
+ // equivalent with pointer-pixels-per-second.
202
204
  1234,
203
205
 
204
- // delta time factor, to ensure sensitivity is framerate-independent.
206
+ // delta time factor,
207
+ // to ensure sensitivity is framerate-independent.
205
208
  ["dt"],
206
209
 
207
- // remapping the ranges because gamepads are naughty and often have jiggly deadzones.
208
- ["remap", -0.1, -0.9, 0, 1, "gamepad.axis.1"],
210
+ // remapping the ranges,
211
+ // because different gamepads represent the values differently.
212
+ ["remap", 0, -1, 0, 1, "gamepad.axis.1"],
209
213
  ],
210
214
  })
211
215
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@benev/tact",
3
- "version": "0.4.0-7",
3
+ "version": "0.4.0-9",
4
4
  "description": "🎮 from keypress to couch co-op",
5
5
  "license": "MIT",
6
6
  "author": "Chase Moskal <chasemoskal@gmail.com>",
@@ -21,21 +21,22 @@
21
21
  "count": "find s -path '*/_archive' -prune -o -name '*.ts' -exec wc -l {} +"
22
22
  },
23
23
  "peerDependencies": {
24
- "@e280/sly": "^0.4.9",
24
+ "@e280/sly": "^0.4.12",
25
25
  "@e280/strata": "^0.5.6",
26
26
  "lit": "^3.3.3"
27
27
  },
28
28
  "dependencies": {
29
+ "@awasm/noble": "^0.1.4",
29
30
  "@benev/math": "0.4.0-9",
30
- "@e280/kv": "^0.2.4",
31
+ "@e280/kv": "^0.2.6",
31
32
  "@e280/shiny": "^0.1.1",
32
- "@e280/stz": "^0.4.14"
33
+ "@e280/stz": "^0.4.15"
33
34
  },
34
35
  "devDependencies": {
35
36
  "@e280/hottie": "^0.1.7",
36
37
  "@e280/octo": "^0.1.7",
37
38
  "@e280/science": "^0.1.14",
38
- "@e280/scute": "^0.5.4",
39
+ "@e280/scute": "^0.5.5",
39
40
  "http-server": "^14.1.1",
40
41
  "npm-run-all": "^4.1.5",
41
42
  "typescript": "^7.0.2"
package/s/lib/index.ts CHANGED
@@ -19,7 +19,7 @@ export * from "./ui/deck/controls/types.js"
19
19
  export * from "./ui/deck/setup/deck.js"
20
20
  export * from "./ui/deck/setup/tact.js"
21
21
  export * from "./ui/deck/auto-gamepads.js"
22
- export * from "./ui/deck/collector.js"
22
+ export * from "./ui/deck/players.js"
23
23
  export * from "./ui/deck/hub.js"
24
24
  export * from "./ui/deck/types.js"
25
25
  export * from "./ui/views/customize/view.js"
@@ -58,7 +58,10 @@ export class Controller<B extends Bindings = any> {
58
58
  }
59
59
 
60
60
  encode(time = performance.now()) {
61
- return encodeIntents(this.#investigation.hash, [...this.evaluate(time)])
61
+ return encodeIntents(
62
+ this.#investigation.minihash,
63
+ [...this.evaluate(time)],
64
+ )
62
65
  }
63
66
 
64
67
  clear() {
@@ -8,7 +8,7 @@ export function decodeIntents(buffer: ArrayBuffer) {
8
8
  const dv = new DataView(buffer)
9
9
  let cursor = 0
10
10
 
11
- const hash = dv.getUint32(cursor, endian)
11
+ const minihash = dv.getUint32(cursor, endian)
12
12
  cursor += sizeHash
13
13
 
14
14
  while (cursor < buffer.byteLength) {
@@ -21,6 +21,6 @@ export function decodeIntents(buffer: ArrayBuffer) {
21
21
  intents.push({id, value})
22
22
  }
23
23
 
24
- return {hash, intents}
24
+ return {minihash, intents}
25
25
  }
26
26
 
@@ -3,13 +3,13 @@ import {Intent} from "./types.js"
3
3
  import {endian} from "../endian.js"
4
4
  import {sizeHash, sizeId, sizeIntent, sizeValue} from "./sizes.js"
5
5
 
6
- export function encodeIntents(hash: number, intents: Intent[]) {
6
+ export function encodeIntents(minihash: number, intents: Intent[]) {
7
7
  const byteCount = sizeHash + (intents.length * sizeIntent)
8
8
  const buffer = new ArrayBuffer(byteCount)
9
9
  const dv = new DataView(buffer)
10
10
  let cursor = 0
11
11
 
12
- dv.setUint32(cursor, hash, endian)
12
+ dv.setUint32(cursor, minihash, endian)
13
13
  cursor += sizeHash
14
14
 
15
15
  for (const intent of intents) {
@@ -0,0 +1,5 @@
1
+
2
+ export function minihash(hash: string) {
3
+ return Number.parseInt(hash.slice(0, 8), 16)
4
+ }
5
+
@@ -3,7 +3,7 @@ import {Inputs, Bindings} from "./types.js"
3
3
  import {decodeIntents} from "./data/intent/decode.js"
4
4
  import {investigate} from "./investigate/investigate.js"
5
5
 
6
- export class InputDecoder<B extends Bindings> {
6
+ export class Decoder<B extends Bindings> {
7
7
  #investigation
8
8
 
9
9
  constructor(bindings: B) {
@@ -25,7 +25,7 @@ export class InputDecoder<B extends Bindings> {
25
25
  for (const buffer of buffers) {
26
26
  const decoded = decodeIntents(buffer)
27
27
 
28
- if (decoded.hash !== this.#investigation.hash)
28
+ if (decoded.minihash !== this.#investigation.minihash)
29
29
  throw new Error("intent bindings hash mismatch")
30
30
 
31
31
  for (const input of inputs)
@@ -1,6 +1,7 @@
1
1
 
2
- import {hash32} from "@e280/stz"
2
+ import {hex} from "@e280/stz"
3
3
  import {Bindings} from "../types.js"
4
+ import {hash} from "../../utils/hash.js"
4
5
  import {isExpression} from "./is-expression.js"
5
6
 
6
7
  export function hashBindings(bindings: Bindings) {
@@ -8,7 +9,7 @@ export function hashBindings(bindings: Bindings) {
8
9
 
9
10
  function recurse(b: Bindings, path: string[]) {
10
11
  if (isExpression(b)) {
11
- paths.push(path.join("."))
12
+ paths.push(JSON.stringify(path))
12
13
  }
13
14
  else {
14
15
  for (const [key, value] of Object.entries(b))
@@ -17,6 +18,7 @@ export function hashBindings(bindings: Bindings) {
17
18
  }
18
19
 
19
20
  recurse(bindings, ["b"])
20
- return hash32(...paths)
21
+ paths.sort()
22
+ return hex(hash("tact/bindings/v1", ...paths).slice(16))
21
23
  }
22
24
 
@@ -5,31 +5,46 @@ import {Expression} from "../expression.js"
5
5
  import {hashBindings} from "./hash-bindings.js"
6
6
  import {isExpression} from "./is-expression.js"
7
7
  import {Bindings, Input, Inputs} from "../types.js"
8
+ import {minihash} from "../data/intent/minihash.js"
8
9
 
9
10
  export function investigate<B extends Bindings>(bindings: B) {
10
11
  const hash = hashBindings(bindings)
11
- const inputList: Input[] = []
12
- const rootList: Expression[] = []
13
12
 
14
- function makeInputs(b: Bindings): unknown {
13
+ const leaves: {
14
+ path: string
15
+ input: Input
16
+ expression: Expression
17
+ }[] = []
18
+
19
+ function makeInputs(b: Bindings, path: string[]): unknown {
15
20
  if (isExpression(b)) {
16
21
  const input = makeInput()
17
- inputList.push(input)
18
- rootList.push(b)
22
+ leaves.push({
23
+ path: JSON.stringify(path),
24
+ input,
25
+ expression: b,
26
+ })
19
27
  return input
20
28
  }
21
29
  else {
22
- return obmap(b, makeInputs)
30
+ return obmap(b, (value, key) =>
31
+ makeInputs(value, [...path, key as string])
32
+ )
23
33
  }
24
34
  }
25
35
 
26
- const inputs = makeInputs(bindings) as Inputs<B>
36
+ const inputs = makeInputs(bindings, ["b"]) as Inputs<B>
37
+
38
+ leaves.sort((a, b) =>
39
+ a.path < b.path ? -1 : a.path > b.path ? 1 : 0
40
+ )
27
41
 
28
42
  return {
29
43
  hash,
44
+ minihash: minihash(hash),
30
45
  inputs,
31
- inputList,
32
- rootList,
46
+ inputList: leaves.map(l => l.input),
47
+ rootList: leaves.map(l => l.expression),
33
48
  }
34
49
  }
35
50
 
@@ -5,6 +5,7 @@ import {asBindings} from "./types.js"
5
5
  import {Controller} from "./controller.js"
6
6
  import {PlainSource} from "./sources/plain.js"
7
7
  import {quickbind} from "./testing/quickbind.js"
8
+ import {isCompatible} from "./investigate/is-compatible.js"
8
9
 
9
10
  import parcel from "./data/test.js"
10
11
 
@@ -23,6 +24,16 @@ export default suite({
23
24
  expect(inputs.was).is(0)
24
25
  }),
25
26
 
27
+ "reordered bindings are equivalent": test(async() => {
28
+ const able = quickbind({alpha: "KeyA", bravo: "KeyB"})
29
+ const baker = quickbind({bravo: "KeyB", alpha: "KeyA"})
30
+ expect(isCompatible(able.bindings, baker.bindings)).is(true)
31
+ able.sample({code: "KeyA", value: 1})
32
+ const inputs = baker.decoder.decode(able.controller.encode())
33
+ expect(inputs.alpha.value).is(1)
34
+ expect(inputs.bravo.value).is(0)
35
+ }),
36
+
26
37
  "update bindings": test(async() => {
27
38
  const {sample, solve, controller} = quickbind("KeyA")
28
39
 
@@ -1,5 +1,5 @@
1
1
 
2
- import {InputDecoder} from "../decoder.js"
2
+ import {Decoder} from "../decoder.js"
3
3
  import {Controller} from "../controller.js"
4
4
  import {Bindings, Sample} from "../types.js"
5
5
  import {PlainSource} from "../sources/plain.js"
@@ -7,7 +7,7 @@ import {PlainSource} from "../sources/plain.js"
7
7
  export function quickbind<B extends Bindings>(b: B) {
8
8
  const source = new PlainSource()
9
9
  const controller = new Controller({source, bindings: b})
10
- const decoder = new InputDecoder(controller.bindings)
10
+ const decoder = new Decoder(controller.bindings)
11
11
  const {inputs} = decoder
12
12
 
13
13
  const sample = (...samples: Sample[]) =>
@@ -16,6 +16,6 @@ export function quickbind<B extends Bindings>(b: B) {
16
16
  const solve = (time?: number) =>
17
17
  decoder.decode(controller.encode(time))
18
18
 
19
- return {source, controller, decoder, sample, solve, inputs}
19
+ return {bindings: b, source, controller, decoder, sample, solve, inputs}
20
20
  }
21
21
 
@@ -5,18 +5,19 @@ import {got, IdCounter} from "@e280/stz"
5
5
  import {PlayerId} from "./types.js"
6
6
  import {Bindings} from "../../input/types.js"
7
7
  import {Controller} from "../../input/controller.js"
8
+ import {minihash} from "../../input/data/intent/minihash.js"
8
9
  import {encodeParcels} from "../../input/data/parcel/encode.js"
9
10
  import {encodeIntents} from "../../input/data/intent/encode.js"
10
11
  import {hashBindings} from "../../input/investigate/hash-bindings.js"
11
12
 
12
13
  export class Hub<B extends Bindings = any> {
13
- #hash
14
+ #minihash
14
15
  #ids = new IdCounter()
15
16
  #unassigned = new RSet<Controller<B>>()
16
17
  #players = new RMap<PlayerId, {controllers: RSet<Controller<B>>}>()
17
18
 
18
19
  constructor(readonly bindings: B) {
19
- this.#hash = hashBindings(bindings)
20
+ this.#minihash = minihash(hashBindings(bindings))
20
21
  }
21
22
 
22
23
  get playerIds() {
@@ -86,7 +87,7 @@ export class Hub<B extends Bindings = any> {
86
87
  .filter(([, value]) => value !== 0)
87
88
  .map(([id, value]) => ({id, value}))
88
89
 
89
- return encodeIntents(this.#hash, changedIntents)
90
+ return encodeIntents(this.#minihash, changedIntents)
90
91
  }
91
92
 
92
93
  encodeAll(time = performance.now()) {
@@ -0,0 +1,73 @@
1
+
2
+ import {RMap} from "@e280/strata"
3
+ import {guarantee} from "@e280/stz"
4
+
5
+ import {PlayerId} from "./types.js"
6
+ import {Bindings} from "../../input/types.js"
7
+ import {Decoder} from "../../input/decoder.js"
8
+ import {decodeParcels} from "../../input/data/parcel/decode.js"
9
+
10
+ export class Players<B extends Bindings> {
11
+ #bindings: B
12
+ #decoders = new RMap<PlayerId, Decoder<B>>()
13
+
14
+ constructor(bindings: B) {
15
+ this.#bindings = bindings
16
+ }
17
+
18
+ decodeAll(...buffers: ArrayBuffer[]) {
19
+ const added: PlayerId[] = []
20
+ const removed: PlayerId[] = []
21
+ const grouped = new Map<PlayerId, ArrayBuffer[]>()
22
+
23
+ for (const buffer of buffers)
24
+ for (const {playerId, intents} of decodeParcels(buffer))
25
+ guarantee(grouped, playerId, () => []).push(intents)
26
+
27
+ for (const [playerId, intents] of grouped) {
28
+ let decoder = this.#decoders.get(playerId)
29
+ if (!decoder) {
30
+ decoder = new Decoder(this.#bindings)
31
+ this.#decoders.set(playerId, decoder)
32
+ added.push(playerId)
33
+ }
34
+ decoder.decode(...intents)
35
+ }
36
+
37
+ for (const playerId of this.#decoders.keys()) {
38
+ if (!grouped.has(playerId)) {
39
+ this.#decoders.delete(playerId)
40
+ removed.push(playerId)
41
+ }
42
+ }
43
+
44
+ return {added, removed}
45
+ }
46
+
47
+ has(playerId: PlayerId) {
48
+ return this.#decoders.has(playerId)
49
+ }
50
+
51
+ get(playerId: PlayerId) {
52
+ return this.#decoders.get(playerId)?.inputs
53
+ }
54
+
55
+ keys() {
56
+ return this.#decoders.keys()
57
+ }
58
+
59
+ *values() {
60
+ for (const {inputs} of this.#decoders.values())
61
+ yield inputs
62
+ }
63
+
64
+ *entries() {
65
+ for (const [playerId, {inputs}] of this.#decoders)
66
+ yield [playerId, inputs] as const
67
+ }
68
+
69
+ [Symbol.iterator]() {
70
+ return this.entries()
71
+ }
72
+ }
73
+
@@ -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
+