Optimised pointer
This commit is contained in:
parent
12d9e64461
commit
501fc4377c
@ -18,6 +18,7 @@
|
|||||||
"dexie": "^3.0.3",
|
"dexie": "^3.0.3",
|
||||||
"err-code": "^2.0.3",
|
"err-code": "^2.0.3",
|
||||||
"fake-indexeddb": "^3.1.2",
|
"fake-indexeddb": "^3.1.2",
|
||||||
|
"fast-deep-equal": "^3.1.3",
|
||||||
"fuse.js": "^6.4.1",
|
"fuse.js": "^6.4.1",
|
||||||
"interactjs": "^1.9.7",
|
"interactjs": "^1.9.7",
|
||||||
"konva": "^7.1.8",
|
"konva": "^7.1.8",
|
||||||
|
@ -18,6 +18,8 @@ import MapStageContext, {
|
|||||||
import AuthContext from "../../contexts/AuthContext";
|
import AuthContext from "../../contexts/AuthContext";
|
||||||
import SettingsContext from "../../contexts/SettingsContext";
|
import SettingsContext from "../../contexts/SettingsContext";
|
||||||
import KeyboardContext from "../../contexts/KeyboardContext";
|
import KeyboardContext from "../../contexts/KeyboardContext";
|
||||||
|
import { PlayerUpdaterContext } from "../../contexts/PlayerContext";
|
||||||
|
import PartyContext from "../../contexts/PartyContext";
|
||||||
|
|
||||||
function MapInteraction({
|
function MapInteraction({
|
||||||
map,
|
map,
|
||||||
@ -35,6 +37,7 @@ function MapInteraction({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
!map ||
|
!map ||
|
||||||
|
!mapState ||
|
||||||
(map.type === "file" && !map.file && !map.resolutions) ||
|
(map.type === "file" && !map.file && !map.resolutions) ||
|
||||||
mapState.mapId !== map.id
|
mapState.mapId !== map.id
|
||||||
) {
|
) {
|
||||||
@ -177,6 +180,8 @@ function MapInteraction({
|
|||||||
|
|
||||||
const auth = useContext(AuthContext);
|
const auth = useContext(AuthContext);
|
||||||
const settings = useContext(SettingsContext);
|
const settings = useContext(SettingsContext);
|
||||||
|
const player = useContext(PlayerUpdaterContext);
|
||||||
|
const party = useContext(PartyContext);
|
||||||
|
|
||||||
const mapInteraction = {
|
const mapInteraction = {
|
||||||
stageScale,
|
stageScale,
|
||||||
@ -218,15 +223,19 @@ function MapInteraction({
|
|||||||
/>
|
/>
|
||||||
{/* Forward auth context to konva elements */}
|
{/* Forward auth context to konva elements */}
|
||||||
<AuthContext.Provider value={auth}>
|
<AuthContext.Provider value={auth}>
|
||||||
<SettingsContext.Provider value={settings}>
|
<PlayerUpdaterContext.Provider value={player}>
|
||||||
<KeyboardContext.Provider value={keyboardValue}>
|
<PartyContext.Provider value={party}>
|
||||||
<MapInteractionProvider value={mapInteraction}>
|
<SettingsContext.Provider value={settings}>
|
||||||
<MapStageProvider value={mapStageRef}>
|
<KeyboardContext.Provider value={keyboardValue}>
|
||||||
{mapLoaded && children}
|
<MapInteractionProvider value={mapInteraction}>
|
||||||
</MapStageProvider>
|
<MapStageProvider value={mapStageRef}>
|
||||||
</MapInteractionProvider>
|
{mapLoaded && children}
|
||||||
</KeyboardContext.Provider>
|
</MapStageProvider>
|
||||||
</SettingsContext.Provider>
|
</MapInteractionProvider>
|
||||||
|
</KeyboardContext.Provider>
|
||||||
|
</SettingsContext.Provider>
|
||||||
|
</PartyContext.Provider>
|
||||||
|
</PlayerUpdaterContext.Provider>
|
||||||
</AuthContext.Provider>
|
</AuthContext.Provider>
|
||||||
</Layer>
|
</Layer>
|
||||||
</Stage>
|
</Stage>
|
||||||
|
@ -14,10 +14,16 @@ import DiceTrayButton from "./DiceTrayButton";
|
|||||||
|
|
||||||
import useSetting from "../../helpers/useSetting";
|
import useSetting from "../../helpers/useSetting";
|
||||||
|
|
||||||
import PlayerContext from "../../contexts/PlayerContext";
|
import PartyContext from "../../contexts/PartyContext";
|
||||||
|
import {
|
||||||
|
PlayerUpdaterContext,
|
||||||
|
PlayerStateWithoutPointerContext,
|
||||||
|
} from "../../contexts/PlayerContext";
|
||||||
|
|
||||||
function Party({ gameId, stream, partyStreams, onStreamStart, onStreamEnd }) {
|
function Party({ gameId, stream, partyStreams, onStreamStart, onStreamEnd }) {
|
||||||
const { playerState, setPlayerState, partyState } = useContext(PlayerContext);
|
const setPlayerState = useContext(PlayerUpdaterContext);
|
||||||
|
const playerState = useContext(PlayerStateWithoutPointerContext);
|
||||||
|
const partyState = useContext(PartyContext);
|
||||||
|
|
||||||
const [fullScreen] = useSetting("map.fullScreen");
|
const [fullScreen] = useSetting("map.fullScreen");
|
||||||
const [shareDice, setShareDice] = useSetting("dice.shareDice");
|
const [shareDice, setShareDice] = useSetting("dice.shareDice");
|
||||||
|
34
src/contexts/PartyContext.js
Normal file
34
src/contexts/PartyContext.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
|
||||||
|
const PartyContext = React.createContext();
|
||||||
|
|
||||||
|
export function PartyProvider({ session, children }) {
|
||||||
|
const [partyState, setPartyState] = useState({});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
function handleSocketPartyState(partyState) {
|
||||||
|
if (partyState) {
|
||||||
|
const { [session.id]: _, ...otherMembersState } = partyState;
|
||||||
|
setPartyState(otherMembersState);
|
||||||
|
} else {
|
||||||
|
setPartyState({});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (session.socket) {
|
||||||
|
session.socket.on("party_state", handleSocketPartyState);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (session.socket) {
|
||||||
|
session.socket.off("party_state", handleSocketPartyState);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PartyContext.Provider value={partyState}>{children}</PartyContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PartyContext;
|
@ -1,4 +1,5 @@
|
|||||||
import React, { useState, useEffect, useContext } from "react";
|
import React, { useEffect, useContext, useState } from "react";
|
||||||
|
import compare from "fast-deep-equal";
|
||||||
|
|
||||||
import useNetworkedState from "../helpers/useNetworkedState";
|
import useNetworkedState from "../helpers/useNetworkedState";
|
||||||
import DatabaseContext from "./DatabaseContext";
|
import DatabaseContext from "./DatabaseContext";
|
||||||
@ -6,7 +7,12 @@ import AuthContext from "./AuthContext";
|
|||||||
|
|
||||||
import { getRandomMonster } from "../helpers/monsters";
|
import { getRandomMonster } from "../helpers/monsters";
|
||||||
|
|
||||||
const PlayerContext = React.createContext();
|
export const PlayerStateContext = React.createContext();
|
||||||
|
export const PlayerUpdaterContext = React.createContext(() => {});
|
||||||
|
/**
|
||||||
|
* Store the player state without the pointer data to prevent unnecessary updates
|
||||||
|
*/
|
||||||
|
export const PlayerStateWithoutPointerContext = React.createContext();
|
||||||
|
|
||||||
export function PlayerProvider({ session, children }) {
|
export function PlayerProvider({ session, children }) {
|
||||||
const { userId } = useContext(AuthContext);
|
const { userId } = useContext(AuthContext);
|
||||||
@ -17,14 +23,13 @@ export function PlayerProvider({ session, children }) {
|
|||||||
nickname: "",
|
nickname: "",
|
||||||
timer: null,
|
timer: null,
|
||||||
dice: { share: false, rolls: [] },
|
dice: { share: false, rolls: [] },
|
||||||
pointer: {},
|
pointer: { position: { x: 0, y: 0 }, visible: false },
|
||||||
sessionId: null,
|
sessionId: null,
|
||||||
userId,
|
userId,
|
||||||
},
|
},
|
||||||
session,
|
session,
|
||||||
"player_state"
|
"player_state"
|
||||||
);
|
);
|
||||||
const [partyState, setPartyState] = useState({});
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!database || databaseStatus === "loading") {
|
if (!database || databaseStatus === "loading") {
|
||||||
@ -45,7 +50,7 @@ export function PlayerProvider({ session, children }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
loadNickname();
|
loadNickname();
|
||||||
}, [database, databaseStatus]);
|
}, [database, databaseStatus, setPlayerState]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
@ -64,7 +69,7 @@ export function PlayerProvider({ session, children }) {
|
|||||||
...prevState,
|
...prevState,
|
||||||
userId,
|
userId,
|
||||||
}));
|
}));
|
||||||
}, [userId]);
|
}, [userId, setPlayerState]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function handleSocketConnect() {
|
function handleSocketConnect() {
|
||||||
@ -72,21 +77,11 @@ export function PlayerProvider({ session, children }) {
|
|||||||
setPlayerState({ ...playerState, sessionId: session.id });
|
setPlayerState({ ...playerState, sessionId: session.id });
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSocketPartyState(partyState) {
|
|
||||||
if (partyState) {
|
|
||||||
const { [session.id]: _, ...otherMembersState } = partyState;
|
|
||||||
setPartyState(otherMembersState);
|
|
||||||
} else {
|
|
||||||
setPartyState({});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
session.on("connected", handleSocketConnect);
|
session.on("connected", handleSocketConnect);
|
||||||
|
|
||||||
if (session.socket) {
|
if (session.socket) {
|
||||||
session.socket.on("connect", handleSocketConnect);
|
session.socket.on("connect", handleSocketConnect);
|
||||||
session.socket.on("reconnect", handleSocketConnect);
|
session.socket.on("reconnect", handleSocketConnect);
|
||||||
session.socket.on("party_state", handleSocketPartyState);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
@ -95,19 +90,32 @@ export function PlayerProvider({ session, children }) {
|
|||||||
if (session.socket) {
|
if (session.socket) {
|
||||||
session.socket.off("connect", handleSocketConnect);
|
session.socket.off("connect", handleSocketConnect);
|
||||||
session.socket.off("reconnect", handleSocketConnect);
|
session.socket.off("reconnect", handleSocketConnect);
|
||||||
session.socket.off("party_state", handleSocketPartyState);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const value = {
|
const [playerStateWithoutPointer, setPlayerStateWithoutPointer] = useState(
|
||||||
playerState,
|
playerState
|
||||||
setPlayerState,
|
);
|
||||||
partyState,
|
useEffect(() => {
|
||||||
};
|
const { pointer, ...state } = playerState;
|
||||||
|
if (
|
||||||
|
!playerStateWithoutPointer ||
|
||||||
|
!compare(playerStateWithoutPointer, state)
|
||||||
|
) {
|
||||||
|
setPlayerStateWithoutPointer(state);
|
||||||
|
}
|
||||||
|
}, [playerState, playerStateWithoutPointer]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PlayerContext.Provider value={value}>{children}</PlayerContext.Provider>
|
<PlayerStateContext.Provider value={playerState}>
|
||||||
|
<PlayerUpdaterContext.Provider value={setPlayerState}>
|
||||||
|
<PlayerStateWithoutPointerContext.Provider
|
||||||
|
value={playerStateWithoutPointer}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</PlayerStateWithoutPointerContext.Provider>
|
||||||
|
</PlayerUpdaterContext.Provider>
|
||||||
|
</PlayerStateContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default PlayerContext;
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState, useCallback } from "react";
|
||||||
|
|
||||||
function useNetworkedState(defaultState, session, eventName) {
|
function useNetworkedState(defaultState, session, eventName) {
|
||||||
const [state, _setState] = useState(defaultState);
|
const [state, _setState] = useState(defaultState);
|
||||||
@ -6,10 +6,10 @@ function useNetworkedState(defaultState, session, eventName) {
|
|||||||
const [dirty, setDirty] = useState(false);
|
const [dirty, setDirty] = useState(false);
|
||||||
|
|
||||||
// Update dirty at the same time as state
|
// Update dirty at the same time as state
|
||||||
function setState(update, sync = true) {
|
const setState = useCallback((update, sync = true) => {
|
||||||
_setState(update);
|
_setState(update);
|
||||||
setDirty(sync);
|
setDirty(sync);
|
||||||
}
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (session.socket && dirty) {
|
if (session.socket && dirty) {
|
||||||
@ -31,7 +31,7 @@ function useNetworkedState(defaultState, session, eventName) {
|
|||||||
session.socket.off(eventName, handleSocketEvent);
|
session.socket.off(eventName, handleSocketEvent);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [session.socket]);
|
}, [session.socket, eventName]);
|
||||||
|
|
||||||
return [state, setState];
|
return [state, setState];
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ import MapDataContext from "../contexts/MapDataContext";
|
|||||||
import MapLoadingContext from "../contexts/MapLoadingContext";
|
import MapLoadingContext from "../contexts/MapLoadingContext";
|
||||||
import AuthContext from "../contexts/AuthContext";
|
import AuthContext from "../contexts/AuthContext";
|
||||||
import DatabaseContext from "../contexts/DatabaseContext";
|
import DatabaseContext from "../contexts/DatabaseContext";
|
||||||
import PlayerContext from "../contexts/PlayerContext";
|
import PartyContext from "../contexts/PartyContext";
|
||||||
|
|
||||||
import { omit } from "../helpers/shared";
|
import { omit } from "../helpers/shared";
|
||||||
import useDebounce from "../helpers/useDebounce";
|
import useDebounce from "../helpers/useDebounce";
|
||||||
@ -27,7 +27,7 @@ import Tokens from "../components/token/Tokens";
|
|||||||
*/
|
*/
|
||||||
function NetworkedMapAndTokens({ session }) {
|
function NetworkedMapAndTokens({ session }) {
|
||||||
const { userId } = useContext(AuthContext);
|
const { userId } = useContext(AuthContext);
|
||||||
const { partyState } = useContext(PlayerContext);
|
const partyState = useContext(PartyContext);
|
||||||
const {
|
const {
|
||||||
assetLoadStart,
|
assetLoadStart,
|
||||||
assetLoadFinish,
|
assetLoadFinish,
|
||||||
|
@ -2,29 +2,31 @@ import React, { useState, useContext, useEffect, useRef } from "react";
|
|||||||
import { Group } from "react-konva";
|
import { Group } from "react-konva";
|
||||||
|
|
||||||
import AuthContext from "../contexts/AuthContext";
|
import AuthContext from "../contexts/AuthContext";
|
||||||
|
import PartyContext from "../contexts/PartyContext";
|
||||||
|
import { PlayerUpdaterContext } from "../contexts/PlayerContext";
|
||||||
|
|
||||||
import MapPointer from "../components/map/MapPointer";
|
import MapPointer from "../components/map/MapPointer";
|
||||||
import { isEmpty } from "../helpers/shared";
|
import { isEmpty } from "../helpers/shared";
|
||||||
import { lerp } from "../helpers/vector2";
|
import { lerp, compare } from "../helpers/vector2";
|
||||||
|
|
||||||
// Send pointer updates every 33ms
|
// Send pointer updates every 33ms
|
||||||
const sendTickRate = 33;
|
const sendTickRate = 100;
|
||||||
|
|
||||||
function NetworkedMapPointer({ session, active, gridSize }) {
|
let t = 0;
|
||||||
|
|
||||||
|
function NetworkedMapPointer({ active, gridSize }) {
|
||||||
const { userId } = useContext(AuthContext);
|
const { userId } = useContext(AuthContext);
|
||||||
const [pointerState, setPointerState] = useState({});
|
const setPlayerState = useContext(PlayerUpdaterContext);
|
||||||
|
const partyState = useContext(PartyContext);
|
||||||
|
const [localPointerState, setLocalPointerState] = useState({});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (userId && !(userId in pointerState)) {
|
if (userId && !(userId in localPointerState)) {
|
||||||
setPointerState({
|
setLocalPointerState({
|
||||||
[userId]: { position: { x: 0, y: 0 }, visible: false, id: userId },
|
[userId]: { position: { x: 0, y: 0 }, visible: false, id: userId },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [userId, pointerState]);
|
}, [userId, localPointerState]);
|
||||||
|
|
||||||
const sessionRef = useRef(session);
|
|
||||||
useEffect(() => {
|
|
||||||
sessionRef.current = session;
|
|
||||||
}, [session]);
|
|
||||||
|
|
||||||
// Send pointer updates every sendTickRate to peers to save on bandwidth
|
// Send pointer updates every sendTickRate to peers to save on bandwidth
|
||||||
// We use requestAnimationFrame as setInterval was being blocked during
|
// We use requestAnimationFrame as setInterval was being blocked during
|
||||||
@ -42,8 +44,14 @@ function NetworkedMapPointer({ session, active, gridSize }) {
|
|||||||
|
|
||||||
if (counter > sendTickRate) {
|
if (counter > sendTickRate) {
|
||||||
counter -= sendTickRate;
|
counter -= sendTickRate;
|
||||||
if (ownPointerUpdateRef.current && sessionRef.current) {
|
if (ownPointerUpdateRef.current) {
|
||||||
sessionRef.current.send("pointer", ownPointerUpdateRef.current);
|
const { position, visible } = ownPointerUpdateRef.current;
|
||||||
|
console.log("send time", performance.now() - t);
|
||||||
|
t = performance.now();
|
||||||
|
setPlayerState((prev) => ({
|
||||||
|
...prev,
|
||||||
|
pointer: { position, visible },
|
||||||
|
}));
|
||||||
ownPointerUpdateRef.current = null;
|
ownPointerUpdateRef.current = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -55,7 +63,7 @@ function NetworkedMapPointer({ session, active, gridSize }) {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
function updateOwnPointerState(position, visible) {
|
function updateOwnPointerState(position, visible) {
|
||||||
setPointerState((prev) => ({
|
setLocalPointerState((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
[userId]: { position, visible, id: userId },
|
[userId]: { position, visible, id: userId },
|
||||||
}));
|
}));
|
||||||
@ -75,39 +83,43 @@ function NetworkedMapPointer({ session, active, gridSize }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle pointer data receive
|
// Handle pointer data receive
|
||||||
const syncedPointerStateRef = useRef({});
|
const interpolationsRef = useRef({});
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function handlePeerData({ id, data }) {
|
// TODO: Handle player disconnect while pointer visible
|
||||||
if (id === "pointer") {
|
const interpolations = interpolationsRef.current;
|
||||||
// Setup an interpolation to the current pointer data when receiving a pointer event
|
for (let player of Object.values(partyState)) {
|
||||||
if (syncedPointerStateRef.current[data.id]) {
|
const id = player.userId;
|
||||||
const from = syncedPointerStateRef.current[data.id].to;
|
const pointer = player.pointer;
|
||||||
syncedPointerStateRef.current[data.id] = {
|
if (!id) {
|
||||||
id: data.id,
|
continue;
|
||||||
from: {
|
}
|
||||||
...from,
|
if (!(id in interpolations)) {
|
||||||
time: performance.now(),
|
interpolations[id] = {
|
||||||
},
|
id,
|
||||||
to: {
|
from: null,
|
||||||
...data,
|
to: { ...pointer, time: performance.now() + sendTickRate },
|
||||||
time: performance.now() + sendTickRate,
|
};
|
||||||
},
|
} else if (
|
||||||
};
|
!compare(interpolations[id].to.position, pointer.position, 0.0001) ||
|
||||||
} else {
|
interpolations[id].to.visible !== pointer.visible
|
||||||
syncedPointerStateRef.current[data.id] = {
|
) {
|
||||||
from: null,
|
console.log("receive time", performance.now() - t, pointer.position);
|
||||||
to: { ...data, time: performance.now() + sendTickRate },
|
t = performance.now();
|
||||||
};
|
const from = interpolations[id].to;
|
||||||
}
|
interpolations[id] = {
|
||||||
|
id,
|
||||||
|
from: {
|
||||||
|
...from,
|
||||||
|
time: performance.now(),
|
||||||
|
},
|
||||||
|
to: {
|
||||||
|
...pointer,
|
||||||
|
time: performance.now() + sendTickRate,
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}, [partyState]);
|
||||||
session.on("peerData", handlePeerData);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
session.off("peerData", handlePeerData);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// Animate to the peer pointer positions
|
// Animate to the peer pointer positions
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -116,36 +128,32 @@ function NetworkedMapPointer({ session, active, gridSize }) {
|
|||||||
function animate(time) {
|
function animate(time) {
|
||||||
request = requestAnimationFrame(animate);
|
request = requestAnimationFrame(animate);
|
||||||
let interpolatedPointerState = {};
|
let interpolatedPointerState = {};
|
||||||
for (let syncState of Object.values(syncedPointerStateRef.current)) {
|
for (let interp of Object.values(interpolationsRef.current)) {
|
||||||
if (!syncState.from || !syncState.to) {
|
if (!interp.from || !interp.to) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const totalInterpTime = syncState.to.time - syncState.from.time;
|
const totalInterpTime = interp.to.time - interp.from.time;
|
||||||
const currentInterpTime = time - syncState.from.time;
|
const currentInterpTime = time - interp.from.time;
|
||||||
const alpha = currentInterpTime / totalInterpTime;
|
const alpha = currentInterpTime / totalInterpTime;
|
||||||
|
|
||||||
if (alpha >= 0 && alpha <= 1) {
|
if (alpha >= 0 && alpha <= 1) {
|
||||||
interpolatedPointerState[syncState.id] = {
|
interpolatedPointerState[interp.id] = {
|
||||||
id: syncState.to.id,
|
id: interp.id,
|
||||||
visible: syncState.from.visible,
|
visible: interp.from.visible,
|
||||||
position: lerp(
|
position: lerp(interp.from.position, interp.to.position, alpha),
|
||||||
syncState.from.position,
|
|
||||||
syncState.to.position,
|
|
||||||
alpha
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (alpha > 1 && !syncState.to.visible) {
|
if (alpha > 1 && !interp.to.visible) {
|
||||||
interpolatedPointerState[syncState.id] = {
|
interpolatedPointerState[interp.id] = {
|
||||||
id: syncState.id,
|
id: interp.id,
|
||||||
visible: syncState.to.visible,
|
visible: interp.to.visible,
|
||||||
position: syncState.to.position,
|
position: interp.to.position,
|
||||||
};
|
};
|
||||||
delete syncedPointerStateRef.current[syncState.to.id];
|
delete interpolationsRef.current[interp.id];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!isEmpty(interpolatedPointerState)) {
|
if (!isEmpty(interpolatedPointerState)) {
|
||||||
setPointerState((prev) => ({
|
setLocalPointerState((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
...interpolatedPointerState,
|
...interpolatedPointerState,
|
||||||
}));
|
}));
|
||||||
@ -159,7 +167,7 @@ function NetworkedMapPointer({ session, active, gridSize }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Group>
|
<Group>
|
||||||
{Object.values(pointerState).map((pointer) => (
|
{Object.values(localPointerState).map((pointer) => (
|
||||||
<MapPointer
|
<MapPointer
|
||||||
key={pointer.id}
|
key={pointer.id}
|
||||||
gridSize={gridSize}
|
gridSize={gridSize}
|
||||||
|
@ -5,7 +5,7 @@ import React, { useContext, useState, useEffect, useCallback } from "react";
|
|||||||
import Session from "./Session";
|
import Session from "./Session";
|
||||||
import { isStreamStopped, omit } from "../helpers/shared";
|
import { isStreamStopped, omit } from "../helpers/shared";
|
||||||
|
|
||||||
import PlayerContext from "../contexts/PlayerContext";
|
import PartyContext from "../contexts/PartyContext";
|
||||||
|
|
||||||
import Party from "../components/party/Party";
|
import Party from "../components/party/Party";
|
||||||
|
|
||||||
@ -19,7 +19,7 @@ import Party from "../components/party/Party";
|
|||||||
* @param {NetworkedPartyProps} props
|
* @param {NetworkedPartyProps} props
|
||||||
*/
|
*/
|
||||||
function NetworkedParty({ gameId, session }) {
|
function NetworkedParty({ gameId, session }) {
|
||||||
const { partyState } = useContext(PlayerContext);
|
const partyState = useContext(PartyContext);
|
||||||
const [stream, setStream] = useState(null);
|
const [stream, setStream] = useState(null);
|
||||||
const [partyStreams, setPartyStreams] = useState({});
|
const [partyStreams, setPartyStreams] = useState({});
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ import AuthContext from "../contexts/AuthContext";
|
|||||||
import { MapStageProvider } from "../contexts/MapStageContext";
|
import { MapStageProvider } from "../contexts/MapStageContext";
|
||||||
import DatabaseContext from "../contexts/DatabaseContext";
|
import DatabaseContext from "../contexts/DatabaseContext";
|
||||||
import { PlayerProvider } from "../contexts/PlayerContext";
|
import { PlayerProvider } from "../contexts/PlayerContext";
|
||||||
|
import { PartyProvider } from "../contexts/PartyContext";
|
||||||
|
|
||||||
import NetworkedMapAndTokens from "../network/NetworkedMapAndTokens";
|
import NetworkedMapAndTokens from "../network/NetworkedMapAndTokens";
|
||||||
import NetworkedParty from "../network/NetworkedParty";
|
import NetworkedParty from "../network/NetworkedParty";
|
||||||
@ -104,49 +105,54 @@ function Game() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<PlayerProvider session={session}>
|
<PlayerProvider session={session}>
|
||||||
<MapStageProvider value={mapStageRef}>
|
<PartyProvider session={session}>
|
||||||
<Flex sx={{ flexDirection: "column", height: "100%" }}>
|
<MapStageProvider value={mapStageRef}>
|
||||||
<Flex
|
<Flex sx={{ flexDirection: "column", height: "100%" }}>
|
||||||
sx={{
|
<Flex
|
||||||
justifyContent: "space-between",
|
sx={{
|
||||||
flexGrow: 1,
|
justifyContent: "space-between",
|
||||||
height: "100%",
|
flexGrow: 1,
|
||||||
}}
|
height: "100%",
|
||||||
>
|
}}
|
||||||
<NetworkedParty session={session} gameId={gameId} />
|
>
|
||||||
<NetworkedMapAndTokens session={session} />
|
<NetworkedParty session={session} gameId={gameId} />
|
||||||
|
<NetworkedMapAndTokens session={session} />
|
||||||
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
<Banner
|
||||||
<Banner isOpen={!!peerError} onRequestClose={() => setPeerError(null)}>
|
isOpen={!!peerError}
|
||||||
<Box p={1}>
|
onRequestClose={() => setPeerError(null)}
|
||||||
<Text as="p" variant="body2">
|
>
|
||||||
{peerError} See <Link to="/faq#connection">FAQ</Link> for more
|
<Box p={1}>
|
||||||
information.
|
<Text as="p" variant="body2">
|
||||||
</Text>
|
{peerError} See <Link to="/faq#connection">FAQ</Link> for more
|
||||||
</Box>
|
information.
|
||||||
</Banner>
|
</Text>
|
||||||
<Banner isOpen={offline} onRequestClose={() => {}} allowClose={false}>
|
</Box>
|
||||||
<Box p={1}>
|
</Banner>
|
||||||
<Text as="p" variant="body2">
|
<Banner isOpen={offline} onRequestClose={() => {}} allowClose={false}>
|
||||||
Unable to connect to game, refresh to reconnect.
|
<Box p={1}>
|
||||||
</Text>
|
<Text as="p" variant="body2">
|
||||||
</Box>
|
Unable to connect to game, refresh to reconnect.
|
||||||
</Banner>
|
</Text>
|
||||||
<Banner
|
</Box>
|
||||||
isOpen={!connected && authenticationStatus === "authenticated"}
|
</Banner>
|
||||||
onRequestClose={() => {}}
|
<Banner
|
||||||
allowClose={false}
|
isOpen={!connected && authenticationStatus === "authenticated"}
|
||||||
>
|
onRequestClose={() => {}}
|
||||||
<Box p={1}>
|
allowClose={false}
|
||||||
<Text as="p" variant="body2">
|
>
|
||||||
Disconnected. Attempting to reconnect...
|
<Box p={1}>
|
||||||
</Text>
|
<Text as="p" variant="body2">
|
||||||
</Box>
|
Disconnected. Attempting to reconnect...
|
||||||
</Banner>
|
</Text>
|
||||||
<AuthModal isOpen={authenticationStatus === "unauthenticated"} />
|
</Box>
|
||||||
{authenticationStatus === "unknown" && !offline && <LoadingOverlay />}
|
</Banner>
|
||||||
<MapLoadingOverlay />
|
<AuthModal isOpen={authenticationStatus === "unauthenticated"} />
|
||||||
</MapStageProvider>
|
{authenticationStatus === "unknown" && !offline && <LoadingOverlay />}
|
||||||
|
<MapLoadingOverlay />
|
||||||
|
</MapStageProvider>
|
||||||
|
</PartyProvider>
|
||||||
</PlayerProvider>
|
</PlayerProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -5451,7 +5451,7 @@ fake-indexeddb@^3.1.2:
|
|||||||
realistic-structured-clone "^2.0.1"
|
realistic-structured-clone "^2.0.1"
|
||||||
setimmediate "^1.0.5"
|
setimmediate "^1.0.5"
|
||||||
|
|
||||||
fast-deep-equal@^3.1.1:
|
fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
|
||||||
version "3.1.3"
|
version "3.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
|
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
|
||||||
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
|
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
|
||||||
|
Loading…
Reference in New Issue
Block a user