Loading packages/client/src/components/Toolbar/CanvasMeta.tsx +39 −15 Original line number Diff line number Diff line Loading @@ -12,14 +12,23 @@ import { useEffect, useState } from "react"; import { type ClientConfig } from "@sc07-canvas/lib/net"; import network from "../../lib/network"; const getTimeLeft = (pixels: { available: number }, config: ClientConfig) => { // this implementation matches the server's implementation const getTimeLeft = (available: number, config: ClientConfig) => { /** * when the user last placed a pixel (unix ts) */ const lastPlace = CanvasCore.get().lastPlace; const cooldown = CanvasLib.getPixelCooldown(pixels.available + 1, config); const pixelExpiresAt = CanvasCore.get().lastPlace && CanvasCore.get().lastPlace! + cooldown * 1000; if (!lastPlace) return undefined; const pixelCooldown = pixelExpiresAt && (Date.now() - pixelExpiresAt) / 1000; const cooldown = CanvasLib.getPixelCooldown(available + 1, config); const pixelAvailableAt = lastPlace + // include already collected pixels cooldown * 1000 * available + // pixel we're currently waiting for cooldown * 1000; const pixelCooldown = (Date.now() - pixelAvailableAt) / 1000; if (!pixelCooldown) return undefined; if (pixelCooldown > 0) return 0; Loading @@ -27,24 +36,24 @@ const getTimeLeft = (pixels: { available: number }, config: ClientConfig) => { return Math.abs(pixelCooldown).toFixed(1); }; const PlaceCountdown = () => { const { pixels, config } = useAppContext<true>(); const [timeLeft, setTimeLeft] = useState(getTimeLeft(pixels, config)); const PlaceCountdown = ({ available }: { available: number }) => { const { config } = useAppContext<true>(); const [timeLeft, setTimeLeft] = useState(getTimeLeft(available, config)); useEffect(() => { const timer = setInterval(() => { setTimeLeft(getTimeLeft(pixels, config)); setTimeLeft(getTimeLeft(available, config)); }, 100); return () => { clearInterval(timer); }; }, [pixels]); }, [available]); return ( <> {timeLeft ? pixels.available < config.canvas.pixel.maxStack && timeLeft + "s" ? available < config.canvas.pixel.maxStack && timeLeft + "s" : ""} </> ); Loading @@ -70,9 +79,24 @@ const OnlineCount = () => { }; export const CanvasMeta = () => { const { canvasPosition, cursor, pixels, config } = useAppContext<true>(); const [available, setAvailable] = useState(-1); const { canvasPosition, cursor, config } = useAppContext<true>(); const { isOpen, onOpen, onOpenChange } = useDisclosure(); useEffect(() => { const handle = ({ available }: { available: number }) => { console.log("canvasmeta", available); setAvailable(available); }; network.on("pixels", handle); network.waitForState("pixels").then(([data]) => handle(data)); return () => { network.off("pixels", handle); }; }, []); return ( <> <div id="canvas-meta" className="toolbar-box"> Loading @@ -94,9 +118,9 @@ export const CanvasMeta = () => { <span> Pixels:{" "} <span> {pixels.available}/{config.canvas.pixel.maxStack} {available}/{config.canvas.pixel.maxStack} </span>{" "} <PlaceCountdown /> <PlaceCountdown available={available} /> </span> <span> Users Online:{" "} Loading packages/client/src/contexts/AppContext.tsx +1 −11 Original line number Diff line number Diff line Loading @@ -52,7 +52,6 @@ interface IAppContext { setCanvasPosition: (v: ICanvasPosition) => void; cursor: ICursor; setCursorPos: (x: number | undefined, y: number | undefined) => void; pixels: { available: number }; undo?: { available: true; expireAt: number }; loadChat: boolean; Loading Loading @@ -125,7 +124,6 @@ export const AppContext = ({ children }: PropsWithChildren) => { // --- settings --- const [loadChat, _setLoadChat] = useState(false); const [pixels, setPixels] = useState({ available: 0 }); const [undo, setUndo] = useState<{ available: true; expireAt: number }>(); // overlays visible Loading Loading @@ -221,10 +219,6 @@ export const AppContext = ({ children }: PropsWithChildren) => { setAuth(user); } function handlePixels(pixels: { available: number }) { setPixels(pixels); } function handleUndo( data: { available: false } | { available: true; expireAt: number }, ) { Loading @@ -245,8 +239,6 @@ export const AppContext = ({ children }: PropsWithChildren) => { Network.on("user", handleUser); Network.on("config", handleConfig); Network.waitForState("pixels").then(([data]) => handlePixels(data)); Network.on("pixels", handlePixels); Network.on("undo", handleUndo); Network.on("connected", handleConnect); Loading @@ -267,7 +259,6 @@ export const AppContext = ({ children }: PropsWithChildren) => { Network.off("user", handleUser); Network.off("config", handleConfig); Network.off("pixels", handlePixels); Network.off("undo", handleUndo); Network.off("connected", handleConnect); Network.off("disconnected", handleDisconnect); Loading Loading @@ -297,7 +288,6 @@ export const AppContext = ({ children }: PropsWithChildren) => { setCanvasPosition, cursor, setCursorPos, pixels, undo, loadChat, setLoadChat, Loading @@ -315,7 +305,7 @@ export const AppContext = ({ children }: PropsWithChildren) => { }} > {!config && ( <div className="fixed top-0 left-0 w-full h-full z-[49] backdrop-blur-sm bg-black/30 text-white flex items-center justify-center"> <div className="fixed top-0 left-0 w-full h-full z-49 backdrop-blur-sm bg-black/30 text-white flex items-center justify-center"> <Spinner label="Loading..." /> </div> )} Loading packages/client/src/lib/canvas.ts +4 −0 Original line number Diff line number Diff line Loading @@ -57,6 +57,9 @@ export class CanvasCore extends EventEmitter<CanvasEvents> { Network.on("pixel", this.handlePixel); Network.on("square", this.handleSquare); Network.on("pixelLastPlaced", this.handlePixelLastPlaced); Network.waitForState("pixelLastPlaced").then(([time]) => this.handlePixelLastPlaced(time), ); KeybindManager.on("SNAPSHOT", () => this.snapshot()); } Loading Loading @@ -412,6 +415,7 @@ export class CanvasCore extends EventEmitter<CanvasEvents> { .then((ack) => { if (ack.success) { this.handlePixel(ack.data); this.lastPlace = Date.now(); Network.emit("placeSuccessful"); } else { console.warn( Loading packages/server/src/controllers/SocketController.ts +17 −1 Original line number Diff line number Diff line Loading @@ -30,6 +30,10 @@ const Logger = getLogger("SOCKET"); export type Socket = RawSocket<ClientToServerEvents, ServerToClientEvents>; export type RemoteSocket = RawRemoteSocket<ServerToClientEvents, any>; interface ServerToServerEvents { userStackUpdated: (sub: string, stack: number) => void; } interface Hooks { socketConnection: (socket: Socket) => void; } Loading @@ -42,7 +46,7 @@ export class SocketController { socketConnection: [], }; private static debugHooks: { [k: string]: (...rest: any[]) => void } = {}; io: Server<ClientToServerEvents, ServerToClientEvents>; io: Server<ClientToServerEvents, ServerToClientEvents, ServerToServerEvents>; MOD_NS: Namespace<ModClientToServerEvents, ModServerToClientEvents>; private constructor(server?: http.Server, session?: ISessionProvider) { Loading @@ -59,6 +63,15 @@ export class SocketController { this.io.engine.use(session); this.io.on("connection", this.handleConnection.bind(this)); this.io.on("userStackUpdated", async (sub, stack) => { // the worker process handles stack updates // this will get broadcasted and we should assume it's what the database reflects // we're updating this property directly instead of using #modifyStack as the worker already let the client know // this is mainly used for correctly syncing this number to the client on initial sync const user = await User.fromSub(sub); user.pixelStack = stack; }); this.MOD_NS = this.io.of("/mod"); this.MOD_NS.use(async (socket, next) => { const user = await this.getUserFromSocket(socket, true); Loading Loading @@ -329,6 +342,9 @@ export class SocketController { new Date(Date.now() + CONFIG.canvas.undo.grace_period), ); // flag the user placed (used for next available pixel countdown) await user.markPlaced(); const newPixel: Pixel = { x: pixel.x, y: pixel.y, Loading packages/server/src/jobs/networking.ts +2 −2 Original line number Diff line number Diff line Loading @@ -139,6 +139,8 @@ const runPixelStacking = async () => { continue; } await user.update(true); // time in seconds since last stack gain (including a potential bonus depending on previously remaining time) const timeSinceLastPlace = (Date.now() - user.lastTimeGainStarted.getTime()) / 1000; Loading @@ -147,8 +149,6 @@ const runPixelStacking = async () => { clientConfig, ); await user.update(true); // this impl has the side affect of giving previously offline users all the stack upon reconnecting if ( timeSinceLastPlace >= cooldown && Loading Loading
packages/client/src/components/Toolbar/CanvasMeta.tsx +39 −15 Original line number Diff line number Diff line Loading @@ -12,14 +12,23 @@ import { useEffect, useState } from "react"; import { type ClientConfig } from "@sc07-canvas/lib/net"; import network from "../../lib/network"; const getTimeLeft = (pixels: { available: number }, config: ClientConfig) => { // this implementation matches the server's implementation const getTimeLeft = (available: number, config: ClientConfig) => { /** * when the user last placed a pixel (unix ts) */ const lastPlace = CanvasCore.get().lastPlace; const cooldown = CanvasLib.getPixelCooldown(pixels.available + 1, config); const pixelExpiresAt = CanvasCore.get().lastPlace && CanvasCore.get().lastPlace! + cooldown * 1000; if (!lastPlace) return undefined; const pixelCooldown = pixelExpiresAt && (Date.now() - pixelExpiresAt) / 1000; const cooldown = CanvasLib.getPixelCooldown(available + 1, config); const pixelAvailableAt = lastPlace + // include already collected pixels cooldown * 1000 * available + // pixel we're currently waiting for cooldown * 1000; const pixelCooldown = (Date.now() - pixelAvailableAt) / 1000; if (!pixelCooldown) return undefined; if (pixelCooldown > 0) return 0; Loading @@ -27,24 +36,24 @@ const getTimeLeft = (pixels: { available: number }, config: ClientConfig) => { return Math.abs(pixelCooldown).toFixed(1); }; const PlaceCountdown = () => { const { pixels, config } = useAppContext<true>(); const [timeLeft, setTimeLeft] = useState(getTimeLeft(pixels, config)); const PlaceCountdown = ({ available }: { available: number }) => { const { config } = useAppContext<true>(); const [timeLeft, setTimeLeft] = useState(getTimeLeft(available, config)); useEffect(() => { const timer = setInterval(() => { setTimeLeft(getTimeLeft(pixels, config)); setTimeLeft(getTimeLeft(available, config)); }, 100); return () => { clearInterval(timer); }; }, [pixels]); }, [available]); return ( <> {timeLeft ? pixels.available < config.canvas.pixel.maxStack && timeLeft + "s" ? available < config.canvas.pixel.maxStack && timeLeft + "s" : ""} </> ); Loading @@ -70,9 +79,24 @@ const OnlineCount = () => { }; export const CanvasMeta = () => { const { canvasPosition, cursor, pixels, config } = useAppContext<true>(); const [available, setAvailable] = useState(-1); const { canvasPosition, cursor, config } = useAppContext<true>(); const { isOpen, onOpen, onOpenChange } = useDisclosure(); useEffect(() => { const handle = ({ available }: { available: number }) => { console.log("canvasmeta", available); setAvailable(available); }; network.on("pixels", handle); network.waitForState("pixels").then(([data]) => handle(data)); return () => { network.off("pixels", handle); }; }, []); return ( <> <div id="canvas-meta" className="toolbar-box"> Loading @@ -94,9 +118,9 @@ export const CanvasMeta = () => { <span> Pixels:{" "} <span> {pixels.available}/{config.canvas.pixel.maxStack} {available}/{config.canvas.pixel.maxStack} </span>{" "} <PlaceCountdown /> <PlaceCountdown available={available} /> </span> <span> Users Online:{" "} Loading
packages/client/src/contexts/AppContext.tsx +1 −11 Original line number Diff line number Diff line Loading @@ -52,7 +52,6 @@ interface IAppContext { setCanvasPosition: (v: ICanvasPosition) => void; cursor: ICursor; setCursorPos: (x: number | undefined, y: number | undefined) => void; pixels: { available: number }; undo?: { available: true; expireAt: number }; loadChat: boolean; Loading Loading @@ -125,7 +124,6 @@ export const AppContext = ({ children }: PropsWithChildren) => { // --- settings --- const [loadChat, _setLoadChat] = useState(false); const [pixels, setPixels] = useState({ available: 0 }); const [undo, setUndo] = useState<{ available: true; expireAt: number }>(); // overlays visible Loading Loading @@ -221,10 +219,6 @@ export const AppContext = ({ children }: PropsWithChildren) => { setAuth(user); } function handlePixels(pixels: { available: number }) { setPixels(pixels); } function handleUndo( data: { available: false } | { available: true; expireAt: number }, ) { Loading @@ -245,8 +239,6 @@ export const AppContext = ({ children }: PropsWithChildren) => { Network.on("user", handleUser); Network.on("config", handleConfig); Network.waitForState("pixels").then(([data]) => handlePixels(data)); Network.on("pixels", handlePixels); Network.on("undo", handleUndo); Network.on("connected", handleConnect); Loading @@ -267,7 +259,6 @@ export const AppContext = ({ children }: PropsWithChildren) => { Network.off("user", handleUser); Network.off("config", handleConfig); Network.off("pixels", handlePixels); Network.off("undo", handleUndo); Network.off("connected", handleConnect); Network.off("disconnected", handleDisconnect); Loading Loading @@ -297,7 +288,6 @@ export const AppContext = ({ children }: PropsWithChildren) => { setCanvasPosition, cursor, setCursorPos, pixels, undo, loadChat, setLoadChat, Loading @@ -315,7 +305,7 @@ export const AppContext = ({ children }: PropsWithChildren) => { }} > {!config && ( <div className="fixed top-0 left-0 w-full h-full z-[49] backdrop-blur-sm bg-black/30 text-white flex items-center justify-center"> <div className="fixed top-0 left-0 w-full h-full z-49 backdrop-blur-sm bg-black/30 text-white flex items-center justify-center"> <Spinner label="Loading..." /> </div> )} Loading
packages/client/src/lib/canvas.ts +4 −0 Original line number Diff line number Diff line Loading @@ -57,6 +57,9 @@ export class CanvasCore extends EventEmitter<CanvasEvents> { Network.on("pixel", this.handlePixel); Network.on("square", this.handleSquare); Network.on("pixelLastPlaced", this.handlePixelLastPlaced); Network.waitForState("pixelLastPlaced").then(([time]) => this.handlePixelLastPlaced(time), ); KeybindManager.on("SNAPSHOT", () => this.snapshot()); } Loading Loading @@ -412,6 +415,7 @@ export class CanvasCore extends EventEmitter<CanvasEvents> { .then((ack) => { if (ack.success) { this.handlePixel(ack.data); this.lastPlace = Date.now(); Network.emit("placeSuccessful"); } else { console.warn( Loading
packages/server/src/controllers/SocketController.ts +17 −1 Original line number Diff line number Diff line Loading @@ -30,6 +30,10 @@ const Logger = getLogger("SOCKET"); export type Socket = RawSocket<ClientToServerEvents, ServerToClientEvents>; export type RemoteSocket = RawRemoteSocket<ServerToClientEvents, any>; interface ServerToServerEvents { userStackUpdated: (sub: string, stack: number) => void; } interface Hooks { socketConnection: (socket: Socket) => void; } Loading @@ -42,7 +46,7 @@ export class SocketController { socketConnection: [], }; private static debugHooks: { [k: string]: (...rest: any[]) => void } = {}; io: Server<ClientToServerEvents, ServerToClientEvents>; io: Server<ClientToServerEvents, ServerToClientEvents, ServerToServerEvents>; MOD_NS: Namespace<ModClientToServerEvents, ModServerToClientEvents>; private constructor(server?: http.Server, session?: ISessionProvider) { Loading @@ -59,6 +63,15 @@ export class SocketController { this.io.engine.use(session); this.io.on("connection", this.handleConnection.bind(this)); this.io.on("userStackUpdated", async (sub, stack) => { // the worker process handles stack updates // this will get broadcasted and we should assume it's what the database reflects // we're updating this property directly instead of using #modifyStack as the worker already let the client know // this is mainly used for correctly syncing this number to the client on initial sync const user = await User.fromSub(sub); user.pixelStack = stack; }); this.MOD_NS = this.io.of("/mod"); this.MOD_NS.use(async (socket, next) => { const user = await this.getUserFromSocket(socket, true); Loading Loading @@ -329,6 +342,9 @@ export class SocketController { new Date(Date.now() + CONFIG.canvas.undo.grace_period), ); // flag the user placed (used for next available pixel countdown) await user.markPlaced(); const newPixel: Pixel = { x: pixel.x, y: pixel.y, Loading
packages/server/src/jobs/networking.ts +2 −2 Original line number Diff line number Diff line Loading @@ -139,6 +139,8 @@ const runPixelStacking = async () => { continue; } await user.update(true); // time in seconds since last stack gain (including a potential bonus depending on previously remaining time) const timeSinceLastPlace = (Date.now() - user.lastTimeGainStarted.getTime()) / 1000; Loading @@ -147,8 +149,6 @@ const runPixelStacking = async () => { clientConfig, ); await user.update(true); // this impl has the side affect of giving previously offline users all the stack upon reconnecting if ( timeSinceLastPlace >= cooldown && Loading