fix multi tab presence
This commit is contained in:
@@ -12,7 +12,7 @@ import api, { fromWebsocket } from "../api";
|
|||||||
import { me, playerColor, profile } from "../profile";
|
import { me, playerColor, profile } from "../profile";
|
||||||
import { fromEvents, Stream, stream } from "kefir";
|
import { fromEvents, Stream, stream } from "kefir";
|
||||||
import Bus from "kefir-bus";
|
import Bus from "kefir-bus";
|
||||||
import { createObservable, createObservableWithInit, WSEvent } from "../fn";
|
import { createObservable, createObservableWithInit, cx, WSEvent } from "../fn";
|
||||||
import { EdenWS } from "@elysiajs/eden/treaty";
|
import { EdenWS } from "@elysiajs/eden/treaty";
|
||||||
import { TWsIn, TWsOut } from "../../../server/src/table";
|
import { TWsIn, TWsOut } from "../../../server/src/table";
|
||||||
import Player from "./Player";
|
import Player from "./Player";
|
||||||
@@ -72,9 +72,24 @@ export default (props: { tableKey: string }) => {
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
id="table"
|
id="table"
|
||||||
class="fixed bg-radial from-orange-950 to-stone-950 border-neutral-950 border-4 top-40 bottom-20 left-10 right-10 shadow-lg"
|
class={cx(
|
||||||
|
"fixed",
|
||||||
|
|
||||||
|
"bg-radial",
|
||||||
|
"from-orange-950",
|
||||||
|
"to-stone-950",
|
||||||
|
|
||||||
|
"border-4",
|
||||||
|
"border-neutral-950",
|
||||||
|
"shadow-lg",
|
||||||
|
|
||||||
|
"top-40",
|
||||||
|
"bottom-20",
|
||||||
|
"left-10",
|
||||||
|
"right-10"
|
||||||
|
)}
|
||||||
style={{
|
style={{
|
||||||
"border-radius": "50% 50%",
|
"border-radius": "50%",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Show when={view() == null}>
|
<Show when={view() == null}>
|
||||||
|
|||||||
@@ -35,3 +35,5 @@ export const createObservableWithInit = <T>(
|
|||||||
|
|
||||||
return signal;
|
return signal;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const cx = (...classes: string[]) => classes.join(" ");
|
||||||
|
|||||||
@@ -22,11 +22,13 @@
|
|||||||
"elysia-rate-limit": "^4.4.0",
|
"elysia-rate-limit": "^4.4.0",
|
||||||
"kefir": "^3.8.8",
|
"kefir": "^3.8.8",
|
||||||
"kefir-bus": "^2.3.1",
|
"kefir-bus": "^2.3.1",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
"object-hash": "^3.0.0"
|
"object-hash": "^3.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bun": "latest",
|
"@types/bun": "latest",
|
||||||
"@types/kefir": "^3.8.11",
|
"@types/kefir": "^3.8.11",
|
||||||
|
"@types/lodash": "^4.17.20",
|
||||||
"concurrently": "^9.2.0",
|
"concurrently": "^9.2.0",
|
||||||
"prisma": "6.13.0",
|
"prisma": "6.13.0",
|
||||||
"ts-xor": "^1.3.0"
|
"ts-xor": "^1.3.0"
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import dayjs from "dayjs";
|
|||||||
import db from "./db";
|
import db from "./db";
|
||||||
import { liveTable, WsOut, WsIn } from "./table";
|
import { liveTable, WsOut, WsIn } from "./table";
|
||||||
import { Human } from "@prisma/client";
|
import { Human } from "@prisma/client";
|
||||||
|
import _ from "lodash";
|
||||||
|
|
||||||
const api = new Elysia({ prefix: "/api" })
|
const api = new Elysia({ prefix: "/api" })
|
||||||
.post("/whoami", async ({ cookie: { token } }) => {
|
.post("/whoami", async ({ cookie: { token } }) => {
|
||||||
@@ -74,9 +75,9 @@ const api = new Elysia({ prefix: "/api" })
|
|||||||
}) {
|
}) {
|
||||||
const table = liveTable<SimpleGameState, SimpleAction>(tableKey);
|
const table = liveTable<SimpleGameState, SimpleAction>(tableKey);
|
||||||
|
|
||||||
table.outputs.playersPresent.onValue((players) =>
|
table.outputs.playersPresent
|
||||||
send({ players })
|
.skipDuplicates((p1, p2) => _.isEqual(new Set(p1), new Set(p2)))
|
||||||
);
|
.onValue((players) => send({ players }));
|
||||||
table.outputs.gameState.onValue((gameState) =>
|
table.outputs.gameState.onValue((gameState) =>
|
||||||
send({
|
send({
|
||||||
view:
|
view:
|
||||||
@@ -84,7 +85,10 @@ const api = new Elysia({ prefix: "/api" })
|
|||||||
getView(getKnowledge(gameState, humanKey), humanKey),
|
getView(getKnowledge(gameState, humanKey), humanKey),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
table.inputs.presenceChanges.emit({ humanKey, presence: "joined" });
|
table.inputs.connectionChanges.emit({
|
||||||
|
humanKey,
|
||||||
|
presence: "joined",
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
response: WsOut,
|
response: WsOut,
|
||||||
@@ -118,7 +122,7 @@ const api = new Elysia({ prefix: "/api" })
|
|||||||
humanKey,
|
humanKey,
|
||||||
},
|
},
|
||||||
}) {
|
}) {
|
||||||
liveTable(tableKey).inputs.presenceChanges.emit({
|
liveTable(tableKey).inputs.connectionChanges.emit({
|
||||||
humanKey,
|
humanKey,
|
||||||
presence: "left",
|
presence: "left",
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export type TWsIn = typeof WsIn.static;
|
|||||||
type Attributed = { humanKey: string };
|
type Attributed = { humanKey: string };
|
||||||
type TablePayload<GameState, GameAction> = {
|
type TablePayload<GameState, GameAction> = {
|
||||||
inputs: {
|
inputs: {
|
||||||
presenceChanges: TBus<
|
connectionChanges: TBus<
|
||||||
Attributed & { presence: "joined" | "left" },
|
Attributed & { presence: "joined" | "left" },
|
||||||
never
|
never
|
||||||
>;
|
>;
|
||||||
@@ -45,23 +45,30 @@ const tables: {
|
|||||||
export const liveTable = <GameState, GameAction>(key: string) => {
|
export const liveTable = <GameState, GameAction>(key: string) => {
|
||||||
if (!(key in tables)) {
|
if (!(key in tables)) {
|
||||||
const inputs: TablePayload<GameState, GameAction>["inputs"] = {
|
const inputs: TablePayload<GameState, GameAction>["inputs"] = {
|
||||||
presenceChanges: Bus(),
|
connectionChanges: Bus(),
|
||||||
gameProposals: Bus(),
|
gameProposals: Bus(),
|
||||||
gameStarts: Bus(),
|
gameStarts: Bus(),
|
||||||
gameActions: Bus(),
|
gameActions: Bus(),
|
||||||
};
|
};
|
||||||
const { presenceChanges, gameProposals, gameStarts, gameActions } =
|
const { connectionChanges, gameProposals, gameStarts, gameActions } =
|
||||||
inputs;
|
inputs;
|
||||||
|
|
||||||
// =======
|
// =======
|
||||||
const playersPresent = presenceChanges.scan((prev, evt) => {
|
const playerConnectionCounts = connectionChanges.scan((prev, evt) => {
|
||||||
if (evt.presence == "joined") {
|
if (evt.presence == "left" && prev[evt.humanKey] == 1) {
|
||||||
prev.push(evt.humanKey);
|
const { [evt.humanKey]: _, ...rest } = prev;
|
||||||
} else if (evt.presence == "left") {
|
return rest;
|
||||||
prev.splice(prev.indexOf(evt.humanKey), 1);
|
|
||||||
}
|
}
|
||||||
return prev;
|
return {
|
||||||
}, [] as string[]);
|
...prev,
|
||||||
|
[evt.humanKey]:
|
||||||
|
(prev[evt.humanKey] ?? 0) +
|
||||||
|
(evt.presence == "joined" ? 1 : -1),
|
||||||
|
};
|
||||||
|
}, {} as { [key: string]: number });
|
||||||
|
const playersPresent = playerConnectionCounts.map((counts) =>
|
||||||
|
Object.keys(counts)
|
||||||
|
);
|
||||||
|
|
||||||
const gameState = transform(
|
const gameState = transform(
|
||||||
null as SimpleGameState | null,
|
null as SimpleGameState | null,
|
||||||
|
|||||||
11
pnpm-lock.yaml
generated
11
pnpm-lock.yaml
generated
@@ -99,6 +99,9 @@ importers:
|
|||||||
kefir-bus:
|
kefir-bus:
|
||||||
specifier: ^2.3.1
|
specifier: ^2.3.1
|
||||||
version: 2.3.1(kefir@3.8.8)
|
version: 2.3.1(kefir@3.8.8)
|
||||||
|
lodash:
|
||||||
|
specifier: ^4.17.21
|
||||||
|
version: 4.17.21
|
||||||
object-hash:
|
object-hash:
|
||||||
specifier: ^3.0.0
|
specifier: ^3.0.0
|
||||||
version: 3.0.0
|
version: 3.0.0
|
||||||
@@ -109,6 +112,9 @@ importers:
|
|||||||
'@types/kefir':
|
'@types/kefir':
|
||||||
specifier: ^3.8.11
|
specifier: ^3.8.11
|
||||||
version: 3.8.11
|
version: 3.8.11
|
||||||
|
'@types/lodash':
|
||||||
|
specifier: ^4.17.20
|
||||||
|
version: 4.17.20
|
||||||
concurrently:
|
concurrently:
|
||||||
specifier: ^9.2.0
|
specifier: ^9.2.0
|
||||||
version: 9.2.0
|
version: 9.2.0
|
||||||
@@ -474,6 +480,9 @@ packages:
|
|||||||
'@types/kefir@3.8.11':
|
'@types/kefir@3.8.11':
|
||||||
resolution: {integrity: sha512-5TRdFXQYsVUvqIH6nYjslHzBgn4hnptcutXnqAhfbKdWD/799c44hFhQGF3887E2t/Q4jSp3RvNFCaQ+b9w6vQ==}
|
resolution: {integrity: sha512-5TRdFXQYsVUvqIH6nYjslHzBgn4hnptcutXnqAhfbKdWD/799c44hFhQGF3887E2t/Q4jSp3RvNFCaQ+b9w6vQ==}
|
||||||
|
|
||||||
|
'@types/lodash@4.17.20':
|
||||||
|
resolution: {integrity: sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==}
|
||||||
|
|
||||||
'@types/node@24.2.0':
|
'@types/node@24.2.0':
|
||||||
resolution: {integrity: sha512-3xyG3pMCq3oYCNg7/ZP+E1ooTaGB4cG8JWRsqqOYQdbWNY4zbaV0Ennrd7stjiJEFZCaybcIgpTjJWHRfBSIDw==}
|
resolution: {integrity: sha512-3xyG3pMCq3oYCNg7/ZP+E1ooTaGB4cG8JWRsqqOYQdbWNY4zbaV0Ennrd7stjiJEFZCaybcIgpTjJWHRfBSIDw==}
|
||||||
|
|
||||||
@@ -1640,6 +1649,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 24.2.0
|
'@types/node': 24.2.0
|
||||||
|
|
||||||
|
'@types/lodash@4.17.20': {}
|
||||||
|
|
||||||
'@types/node@24.2.0':
|
'@types/node@24.2.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
undici-types: 7.10.0
|
undici-types: 7.10.0
|
||||||
|
|||||||
Reference in New Issue
Block a user