@benev/tact 0.4.0-1 → 0.4.0-10
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/README.md +134 -46
- package/package.json +7 -6
- package/s/demo/main.css +38 -3
- package/s/index.html.ts +26 -20
- package/s/lib/index.ts +1 -1
- package/s/lib/input/controller.ts +4 -1
- package/s/lib/input/data/intent/decode.ts +2 -2
- package/s/lib/input/data/intent/encode.ts +2 -2
- package/s/lib/input/data/intent/minihash.ts +5 -0
- package/s/lib/input/decoder.ts +2 -2
- package/s/lib/input/evaluate/context.ts +5 -1
- package/s/lib/input/evaluate/evaluate.ts +27 -14
- package/s/lib/input/investigate/hash-bindings.ts +5 -3
- package/s/lib/input/investigate/investigate.ts +24 -9
- package/s/lib/input/test.ts +52 -30
- package/s/lib/input/testing/quickbind.ts +3 -3
- package/s/lib/ui/deck/auto-gamepads.ts +1 -0
- package/s/lib/ui/deck/controls/internal/data.ts +7 -5
- package/s/lib/ui/deck/hub.ts +4 -3
- package/s/lib/ui/deck/players.ts +73 -0
- package/s/lib/ui/deck/setup/deck.ts +33 -20
- package/s/lib/ui/deck/setup/tact.ts +26 -11
- package/s/lib/ui/deck/setup/types.ts +1 -0
- package/s/lib/ui/deck/setup/utils/broadcast-channel.ts +17 -0
- package/s/lib/ui/deck/setup/utils/kv.ts +20 -0
- package/s/lib/ui/deck/types.ts +1 -1
- package/s/lib/ui/views/controllers/style.css.ts +1 -1
- package/s/lib/ui/views/controllers/view.ts +3 -2
- package/s/lib/ui/views/customize/style.css.ts +1 -1
- package/s/lib/ui/views/customize/view.ts +6 -5
- package/s/lib/ui/views/deck/style.css.ts +1 -1
- package/s/lib/ui/views/deck/view.ts +13 -7
- package/s/lib/ui/views/theme.css.ts +1 -1
- package/s/lib/ui/views/visualizer/style.css.ts +8 -6
- package/s/lib/ui/views/visualizer/view.ts +1 -0
- package/s/lib/utils/hash.ts +47 -0
- package/x/demo/main.bundle.min.js +330 -326
- package/x/demo/main.bundle.min.js.map +4 -4
- package/x/demo/main.css +38 -3
- package/x/gamepad/index.html +2 -2
- package/x/gamepad/main.bundle.min.js +8 -8
- package/x/gamepad/main.bundle.min.js.map +4 -4
- package/x/index.html +28 -22
- package/x/index.html.js +26 -20
- package/x/index.html.js.map +1 -1
- package/x/lib/index.d.ts +1 -1
- package/x/lib/index.js +1 -1
- package/x/lib/index.js.map +1 -1
- package/x/lib/input/controller.js +1 -1
- package/x/lib/input/controller.js.map +1 -1
- package/x/lib/input/data/intent/decode.d.ts +1 -1
- package/x/lib/input/data/intent/decode.js +2 -2
- package/x/lib/input/data/intent/decode.js.map +1 -1
- package/x/lib/input/data/intent/encode.d.ts +1 -1
- package/x/lib/input/data/intent/encode.js +2 -2
- package/x/lib/input/data/intent/encode.js.map +1 -1
- package/x/lib/input/data/intent/minihash.d.ts +1 -0
- package/x/lib/input/data/intent/minihash.js +4 -0
- package/x/lib/input/data/intent/minihash.js.map +1 -0
- package/x/lib/input/decoder.d.ts +1 -1
- package/x/lib/input/decoder.js +2 -2
- package/x/lib/input/decoder.js.map +1 -1
- package/x/lib/input/evaluate/context.d.ts +3 -1
- package/x/lib/input/evaluate/context.js.map +1 -1
- package/x/lib/input/evaluate/evaluate.js +24 -11
- package/x/lib/input/evaluate/evaluate.js.map +1 -1
- package/x/lib/input/investigate/hash-bindings.d.ts +1 -1
- package/x/lib/input/investigate/hash-bindings.js +5 -3
- package/x/lib/input/investigate/hash-bindings.js.map +1 -1
- package/x/lib/input/investigate/investigate.d.ts +2 -1
- package/x/lib/input/investigate/investigate.js +14 -9
- package/x/lib/input/investigate/investigate.js.map +1 -1
- package/x/lib/input/test.d.ts +4 -2
- package/x/lib/input/test.js +49 -22
- package/x/lib/input/test.js.map +1 -1
- package/x/lib/input/testing/quickbind.d.ts +3 -2
- package/x/lib/input/testing/quickbind.js +3 -3
- package/x/lib/input/testing/quickbind.js.map +1 -1
- package/x/lib/ui/deck/auto-gamepads.js +1 -0
- package/x/lib/ui/deck/auto-gamepads.js.map +1 -1
- package/x/lib/ui/deck/controls/internal/data.d.ts +1 -1
- package/x/lib/ui/deck/controls/internal/data.js +4 -1
- package/x/lib/ui/deck/controls/internal/data.js.map +1 -1
- package/x/lib/ui/deck/hub.js +4 -3
- package/x/lib/ui/deck/hub.js.map +1 -1
- package/x/lib/ui/deck/players.d.ts +16 -0
- package/x/lib/ui/deck/players.js +56 -0
- package/x/lib/ui/deck/players.js.map +1 -0
- package/x/lib/ui/deck/setup/deck.js +23 -13
- package/x/lib/ui/deck/setup/deck.js.map +1 -1
- package/x/lib/ui/deck/setup/tact.d.ts +3 -0
- package/x/lib/ui/deck/setup/tact.js +19 -8
- package/x/lib/ui/deck/setup/tact.js.map +1 -1
- package/x/lib/ui/deck/setup/types.d.ts +1 -0
- package/x/lib/ui/deck/setup/utils/broadcast-channel.d.ts +4 -0
- package/x/lib/ui/deck/setup/utils/broadcast-channel.js +10 -0
- package/x/lib/ui/deck/setup/utils/broadcast-channel.js.map +1 -0
- package/x/lib/ui/deck/setup/utils/kv.d.ts +5 -0
- package/x/lib/ui/deck/setup/utils/kv.js +12 -0
- package/x/lib/ui/deck/setup/utils/kv.js.map +1 -0
- package/x/lib/ui/deck/types.d.ts +1 -0
- package/x/lib/ui/views/controllers/style.css.js +1 -1
- package/x/lib/ui/views/controllers/view.js +3 -2
- package/x/lib/ui/views/controllers/view.js.map +1 -1
- package/x/lib/ui/views/customize/style.css.js +1 -1
- package/x/lib/ui/views/customize/view.js +6 -5
- package/x/lib/ui/views/customize/view.js.map +1 -1
- package/x/lib/ui/views/deck/style.css.js +1 -1
- package/x/lib/ui/views/deck/view.js +13 -6
- package/x/lib/ui/views/deck/view.js.map +1 -1
- package/x/lib/ui/views/theme.css.js +1 -1
- package/x/lib/ui/views/visualizer/style.css.js +8 -6
- package/x/lib/ui/views/visualizer/style.css.js.map +1 -1
- package/x/lib/ui/views/visualizer/view.js +1 -0
- package/x/lib/ui/views/visualizer/view.js.map +1 -1
- package/x/lib/utils/hash.d.ts +1 -0
- package/x/lib/utils/hash.js +47 -0
- package/x/lib/utils/hash.js.map +1 -0
- package/s/lib/ui/deck/collector.ts +0 -52
- package/x/lib/ui/deck/collector.d.ts +0 -13
- package/x/lib/ui/deck/collector.js +0 -35
- package/x/lib/ui/deck/collector.js.map +0 -1
package/s/lib/input/test.ts
CHANGED
|
@@ -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
|
|
|
@@ -103,16 +114,28 @@ export default suite({
|
|
|
103
114
|
expect(inputs.sum).is(0)
|
|
104
115
|
}),
|
|
105
116
|
|
|
106
|
-
"
|
|
107
|
-
const {sample, solve} = quickbind(
|
|
108
|
-
|
|
109
|
-
)
|
|
110
|
-
|
|
111
|
-
for (const value of [1, 0, 1, 0])
|
|
117
|
+
"single tap": test(async() => {
|
|
118
|
+
const {sample, solve} = quickbind(["taps", 1, 400, "KeyE"])
|
|
119
|
+
const hit = (time: number, value: number) => {
|
|
112
120
|
sample({code: "KeyE", value})
|
|
121
|
+
return solve(time).value
|
|
122
|
+
}
|
|
123
|
+
expect(hit(0, 0)).is(0)
|
|
124
|
+
expect(hit(100, 1)).is(0)
|
|
125
|
+
expect(hit(200, 0)).is(1)
|
|
126
|
+
}),
|
|
113
127
|
|
|
114
|
-
|
|
115
|
-
|
|
128
|
+
"double tap": test(async() => {
|
|
129
|
+
const {sample, solve} = quickbind(["taps", 2, 400, "KeyE"])
|
|
130
|
+
const hit = (time: number, value: number) => {
|
|
131
|
+
sample({code: "KeyE", value})
|
|
132
|
+
return solve(time).value
|
|
133
|
+
}
|
|
134
|
+
expect(hit(0, 0)).is(0)
|
|
135
|
+
expect(hit(100, 1)).is(0)
|
|
136
|
+
expect(hit(200, 0)).is(0)
|
|
137
|
+
expect(hit(300, 1)).is(0)
|
|
138
|
+
expect(hit(400, 0)).is(1)
|
|
116
139
|
}),
|
|
117
140
|
|
|
118
141
|
"held across empty frames": test(async() => {
|
|
@@ -136,30 +159,29 @@ export default suite({
|
|
|
136
159
|
expect(solve(900).value).is(1)
|
|
137
160
|
}),
|
|
138
161
|
|
|
139
|
-
"taps
|
|
162
|
+
"taps vs held": test(async() => {
|
|
140
163
|
const {sample, solve} = quickbind({
|
|
141
|
-
|
|
142
|
-
|
|
164
|
+
tappy: ["taps", 1, 300, "KeyE"],
|
|
165
|
+
holdy: ["held", 300, "KeyE"],
|
|
143
166
|
})
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
expect(
|
|
153
|
-
expect(
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
expect(
|
|
158
|
-
expect(
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
expect(
|
|
162
|
-
expect(holding.holder.value).is(1)
|
|
167
|
+
const hit = (time: number, value: number) => {
|
|
168
|
+
sample({code: "KeyE", value})
|
|
169
|
+
return solve(time)
|
|
170
|
+
}
|
|
171
|
+
const __up = (time: number) => hit(time, 0)
|
|
172
|
+
const DOWN = (time: number) => hit(time, 1)
|
|
173
|
+
|
|
174
|
+
expect(__up(1000).tappy.value).is(0)
|
|
175
|
+
expect(DOWN(1100).tappy.value).is(0)
|
|
176
|
+
expect(__up(1200).tappy.value).is(1)
|
|
177
|
+
expect(__up(1300).tappy.value).is(0)
|
|
178
|
+
|
|
179
|
+
expect(__up(2000).holdy.value).is(0)
|
|
180
|
+
expect(DOWN(2100).holdy.value).is(0)
|
|
181
|
+
expect(DOWN(2200).holdy.value).is(0)
|
|
182
|
+
expect(DOWN(2300).holdy.value).is(0)
|
|
183
|
+
expect(DOWN(2400).holdy.value).is(1)
|
|
184
|
+
expect(__up(2500).tappy.value).is(0) // FAILS
|
|
163
185
|
}),
|
|
164
186
|
|
|
165
187
|
"independent controller consumers": test(async() => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import {
|
|
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
|
|
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,21 +5,23 @@ import {got, IdCounter, setEntries, subby} from "@e280/stz"
|
|
|
5
5
|
import {Profile, ProfileKey} from "../../types.js"
|
|
6
6
|
import {Bindings} from "../../../../input/types.js"
|
|
7
7
|
import {Controller} from "../../../../input/controller.js"
|
|
8
|
+
import {hashBindings} from "../../../../input/investigate/hash-bindings.js"
|
|
8
9
|
import {ControllerHandle, ControllerMeta, ControlsOptions} from "../types.js"
|
|
9
10
|
|
|
10
11
|
export type ControlsData<B extends Bindings> = (
|
|
11
12
|
ReturnType<typeof makeControlsData<B>>
|
|
12
13
|
)
|
|
13
14
|
|
|
14
|
-
export function makeControlsData<B extends Bindings>({
|
|
15
|
-
|
|
16
|
-
presets,
|
|
17
|
-
}: ControlsOptions<B>) {
|
|
18
|
-
|
|
15
|
+
export function makeControlsData<B extends Bindings>(options: ControlsOptions<B>) {
|
|
16
|
+
const {presets} = options
|
|
19
17
|
const fallbackKey = got(Object.keys(presets)[0], "missing presets")
|
|
20
18
|
const fallbackProfile = got(presets[fallbackKey])
|
|
21
19
|
const bindings = fallbackProfile.bindings
|
|
22
20
|
|
|
21
|
+
const kv = options.kv.scope(
|
|
22
|
+
hashBindings(bindings).toString()
|
|
23
|
+
)
|
|
24
|
+
|
|
23
25
|
const profiles = {
|
|
24
26
|
presets: new RMap<ProfileKey, Profile<B>>(),
|
|
25
27
|
customs: new RMap<ProfileKey, Profile<B>>(),
|
package/s/lib/ui/deck/hub.ts
CHANGED
|
@@ -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
|
-
#
|
|
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.#
|
|
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.#
|
|
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
|
+
|
|
@@ -1,31 +1,44 @@
|
|
|
1
1
|
|
|
2
|
-
import {
|
|
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
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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,10 +1,12 @@
|
|
|
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
7
|
import {Bindings} from "../../../input/types.js"
|
|
7
8
|
import {autoGamepads} from "../auto-gamepads.js"
|
|
9
|
+
import {ellipsis} from "../../../utils/ellipsis.js"
|
|
8
10
|
import {firstPreset} from "../utils/first-preset.js"
|
|
9
11
|
import {Controller} from "../../../input/controller.js"
|
|
10
12
|
import {Sources} from "../../../input/sources/sources.js"
|
|
@@ -19,10 +21,14 @@ import {makeControllersElement} from "../../views/controllers/view.js"
|
|
|
19
21
|
|
|
20
22
|
import devicesSvg from "../../icons/akar/devices.svg.js"
|
|
21
23
|
import gameControllerSvg from "../../icons/akar/game-controller.svg.js"
|
|
22
|
-
|
|
24
|
+
|
|
25
|
+
export type Tact = Awaited<ReturnType<typeof setupTact>>
|
|
23
26
|
|
|
24
27
|
export async function setupTact<B extends Bindings>(params: SetupParams<B>) {
|
|
25
|
-
const
|
|
28
|
+
const {dispose, schedule, disposable} = disposer()
|
|
29
|
+
|
|
30
|
+
const deck = disposable(await setupDeck(params))
|
|
31
|
+
const first = firstPreset(deck.controls.presets)
|
|
26
32
|
const visualizer = new Visualizer()
|
|
27
33
|
|
|
28
34
|
const elements = {
|
|
@@ -33,29 +39,31 @@ export async function setupTact<B extends Bindings>(params: SetupParams<B>) {
|
|
|
33
39
|
}
|
|
34
40
|
|
|
35
41
|
function gamepads(defaultProfileKey?: string) {
|
|
36
|
-
|
|
42
|
+
const stop = autoGamepads(deck.hub, (controller, source) => {
|
|
37
43
|
const handle = `${source.pad.gamepad.id} ${source.pad.gamepad.index + 1}`
|
|
38
44
|
deck.controls.attach(controller, {
|
|
39
45
|
icon: gameControllerSvg,
|
|
40
46
|
handle,
|
|
41
47
|
tooltip: handle,
|
|
42
48
|
name: ellipsis(handle, 32),
|
|
43
|
-
defaultProfileKey: defaultProfileKey ??
|
|
49
|
+
defaultProfileKey: defaultProfileKey ?? first.key,
|
|
44
50
|
})
|
|
45
51
|
return () => deck.controls.detach(controller)
|
|
46
52
|
})
|
|
53
|
+
schedule(stop)
|
|
54
|
+
return stop
|
|
47
55
|
}
|
|
48
56
|
|
|
49
57
|
function mouseKeyboard() {
|
|
50
|
-
const {key, profile} =
|
|
58
|
+
const {key, profile} = first
|
|
51
59
|
|
|
52
|
-
const controller = new Controller({
|
|
60
|
+
const controller = disposable(new Controller({
|
|
53
61
|
bindings: profile.bindings,
|
|
54
|
-
source: new Sources(
|
|
55
|
-
new KeyboardSource(),
|
|
56
|
-
new PointerSource(),
|
|
57
|
-
),
|
|
58
|
-
})
|
|
62
|
+
source: disposable(new Sources(
|
|
63
|
+
disposable(new KeyboardSource()),
|
|
64
|
+
disposable(new PointerSource()),
|
|
65
|
+
)),
|
|
66
|
+
}))
|
|
59
67
|
|
|
60
68
|
deck.hub.plug(controller)
|
|
61
69
|
|
|
@@ -67,6 +75,11 @@ export async function setupTact<B extends Bindings>(params: SetupParams<B>) {
|
|
|
67
75
|
defaultProfileKey: key,
|
|
68
76
|
})
|
|
69
77
|
|
|
78
|
+
schedule(() => {
|
|
79
|
+
deck.hub.remove(controller)
|
|
80
|
+
deck.controls.detach(controller)
|
|
81
|
+
})
|
|
82
|
+
|
|
70
83
|
return controller
|
|
71
84
|
}
|
|
72
85
|
|
|
@@ -81,6 +94,8 @@ export async function setupTact<B extends Bindings>(params: SetupParams<B>) {
|
|
|
81
94
|
visualizer,
|
|
82
95
|
elements,
|
|
83
96
|
registerElements,
|
|
97
|
+
encodeAll: deck.hub.encodeAll.bind(deck.hub),
|
|
98
|
+
dispose,
|
|
84
99
|
}
|
|
85
100
|
}
|
|
86
101
|
|
|
@@ -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
|
+
|
package/s/lib/ui/deck/types.ts
CHANGED
|
@@ -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
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
import {html} from "lit"
|
|
3
3
|
import {isHappy, when} from "@e280/stz"
|
|
4
|
-
import {loot, shadowDuplex, useCss, useOnce} from "@e280/sly"
|
|
4
|
+
import {loot, shadowDuplex, useCss, useName, useOnce} from "@e280/sly"
|
|
5
5
|
|
|
6
6
|
import styleCss from "./style.css.js"
|
|
7
7
|
import themeCss from "../theme.css.js"
|
|
@@ -14,6 +14,7 @@ import {ControllerView} from "./subviews/controller.js"
|
|
|
14
14
|
import {Context, droppyNewPlayer} from "../deck/types.js"
|
|
15
15
|
|
|
16
16
|
export const [ControllersView, makeControllersElement] = shadowDuplex((deck: Deck) => {
|
|
17
|
+
useName("tact-controllers")
|
|
17
18
|
useCss(themeCss, styleCss)
|
|
18
19
|
|
|
19
20
|
const {hub} = deck
|
|
@@ -36,7 +37,7 @@ export const [ControllersView, makeControllersElement] = shadowDuplex((deck: Dec
|
|
|
36
37
|
}))
|
|
37
38
|
|
|
38
39
|
return html`
|
|
39
|
-
<div
|
|
40
|
+
<div part=plate ?data-drag-and-drop="${!!context.dnd.dragging}">
|
|
40
41
|
<div class=playerlist>
|
|
41
42
|
${hub.playerIds.map(renderPlayer)}
|
|
42
43
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
import {html} from "lit"
|
|
3
3
|
import {given, when} from "@e280/stz"
|
|
4
|
-
import {shadowDuplex, useCss, useSignal} from "@e280/sly"
|
|
4
|
+
import {shadowDuplex, useCss, useName, useSignal} from "@e280/sly"
|
|
5
5
|
|
|
6
6
|
import styleCss from "./style.css.js"
|
|
7
7
|
import themeCss from "../theme.css.js"
|
|
@@ -16,6 +16,7 @@ import {renderBindings} from "./parts/render-bindings.js"
|
|
|
16
16
|
import {ProfileSelectView} from "../profile-select/view.js"
|
|
17
17
|
|
|
18
18
|
export const [CustomizeView, makeCustomizeElement] = shadowDuplex((options: {controls: Controls}) => {
|
|
19
|
+
useName("tact-customize")
|
|
19
20
|
useCss(themeCss, styleCss)
|
|
20
21
|
const {controls, controls: {profiles}} = options
|
|
21
22
|
const $profileKey = useSignal(controls.fallbackProfileKey)
|
|
@@ -27,17 +28,17 @@ export const [CustomizeView, makeCustomizeElement] = shadowDuplex((options: {con
|
|
|
27
28
|
? profiles.get(profile.basedOn)
|
|
28
29
|
: profile
|
|
29
30
|
|
|
30
|
-
const updateBindings = (bindings: Bindings) => {
|
|
31
|
+
const updateBindings = async(bindings: Bindings) => {
|
|
31
32
|
let key = $profileKey()
|
|
32
33
|
|
|
33
34
|
if (profiles.isPreset(key)) {
|
|
34
35
|
const basedOn = key
|
|
35
36
|
const freshKey = makeId()
|
|
36
37
|
key = freshKey
|
|
37
|
-
profiles.clone(freshKey, basedOn)
|
|
38
|
+
await profiles.clone(freshKey, basedOn)
|
|
38
39
|
}
|
|
39
40
|
|
|
40
|
-
profiles.update(key, {bindings})
|
|
41
|
+
await profiles.update(key, {bindings})
|
|
41
42
|
$profileKey(key)
|
|
42
43
|
}
|
|
43
44
|
|
|
@@ -56,7 +57,7 @@ export const [CustomizeView, makeCustomizeElement] = shadowDuplex((options: {con
|
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
return html`
|
|
59
|
-
<div
|
|
60
|
+
<div part=plate>
|
|
60
61
|
<header class=topbar>
|
|
61
62
|
<div class=corner>
|
|
62
63
|
${ProfileSelectView(options, $profileKey(), $profileKey)}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
import {html} from "lit"
|
|
3
3
|
import {ShinyTabs, TabsControl} from "@e280/shiny"
|
|
4
|
-
import {shadowDuplex, useCss, useOnce} from "@e280/sly"
|
|
4
|
+
import {shadowDuplex, useCss, useName, useOnce} from "@e280/sly"
|
|
5
5
|
|
|
6
6
|
import styleCss from "./style.css.js"
|
|
7
7
|
import themeCss from "../theme.css.js"
|
|
@@ -9,13 +9,12 @@ import gameControllerSvg from "../../icons/akar/game-controller.svg.js"
|
|
|
9
9
|
import settingsHorizontalSvg from "../../icons/akar/settings-horizontal.svg.js"
|
|
10
10
|
|
|
11
11
|
import {Deck} from "../../deck/types.js"
|
|
12
|
-
import {ControllersView} from "../controllers/view.js"
|
|
13
12
|
import {CustomizeView} from "../customize/view.js"
|
|
13
|
+
import {ControllersView} from "../controllers/view.js"
|
|
14
14
|
|
|
15
15
|
export const [DeckView, makeDeckElement] = shadowDuplex((deck: Deck) => {
|
|
16
|
+
useName("tact-deck")
|
|
16
17
|
useCss(themeCss, styleCss)
|
|
17
|
-
|
|
18
|
-
const attrs = {slot: "panel"}
|
|
19
18
|
const tabsControl = useOnce(() => new TabsControl())
|
|
20
19
|
|
|
21
20
|
// TODO remove this
|
|
@@ -25,7 +24,7 @@ export const [DeckView, makeDeckElement] = shadowDuplex((deck: Deck) => {
|
|
|
25
24
|
})
|
|
26
25
|
|
|
27
26
|
return html`
|
|
28
|
-
<div
|
|
27
|
+
<div part=plate theme-card>
|
|
29
28
|
${ShinyTabs.with({
|
|
30
29
|
props: [{control: tabsControl}],
|
|
31
30
|
children: html`
|
|
@@ -39,8 +38,15 @@ export const [DeckView, makeDeckElement] = shadowDuplex((deck: Deck) => {
|
|
|
39
38
|
<span>customize</span>
|
|
40
39
|
</button>
|
|
41
40
|
|
|
42
|
-
${ControllersView.with({
|
|
43
|
-
|
|
41
|
+
${ControllersView.with({
|
|
42
|
+
props: [deck],
|
|
43
|
+
attrs: {slot: "panel"},
|
|
44
|
+
})}
|
|
45
|
+
|
|
46
|
+
${CustomizeView.with({
|
|
47
|
+
props: [deck],
|
|
48
|
+
attrs: {slot: "panel"},
|
|
49
|
+
})}
|
|
44
50
|
`,
|
|
45
51
|
})}
|
|
46
52
|
</div>
|