Commit b09ddd13 authored by Grant's avatar Grant
Browse files

massive performance rewrite

- main canvas & blank canvas drawing moved to separate worker thread (if possible)
- server jobs moved to separate process (fixing CPU leak on heatmap generation)
- pixels now store if they are on top reducing db queries
- remove various methods to store pixel data in redis, reducing delay for various actions

additional fixed: (came up during performance fixes)
- added square fill (fixes #15)
- redraw loop (fixes #59)
- added keybind to deselect current color (fixes #54)
- pixel undos no longer delete the pixel from the db
- server logging now indicates what module triggered the log
parent 78d97b52
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -70,7 +70,7 @@ RUN npm -w packages/server run build
FROM base as run
WORKDIR /home/node/app
COPY --from=dep /home/node/app/ ./
COPY package*.json docker-start.sh .git ./
COPY package*.json docker-start*.sh .git ./

# --- prepare lib ---

+11 −0
Original line number Diff line number Diff line
@@ -19,6 +19,17 @@ services:
        condition: service_healthy
      postgres:
        condition: service_healthy
  worker:
    image: sc07/canvas
    build: .
    environment:
      - REDIS_HOST=redis://redis
      - DATABASE_URL=postgres://postgres@postgres/canvas
    env_file:
      - .env.local
    depends_on:
      - canvas
    command: ./docker-start-worker.sh
  redis:
    restart: always
    image: redis:7-alpine

docker-start-worker.sh

0 → 100644
+3 −0
Original line number Diff line number Diff line
#!/bin/sh

npm -w packages/server run tool start_job_worker
 No newline at end of file
+67 −19
Original line number Diff line number Diff line
import { useCallback, useContext, useEffect, useRef } from "react";
import { useCallback, useContext, useEffect, useRef, useState } from "react";
import { Canvas } from "../lib/canvas";
import { useAppContext } from "../contexts/AppContext";
import { PanZoomWrapper } from "@sc07-canvas/lib/src/renderer";
@@ -23,29 +23,56 @@ export const CanvasWrapper = () => {
        <HeatmapOverlay />
        {config && <Template />}
        <CanvasInner />
        <Cursor />
      </PanZoomWrapper>
    </main>
  );
};

const Cursor = () => {
  const { cursor } = useAppContext();
  const [color, setColor] = useState<string>();

  useEffect(() => {
    console.log("color", color);
  }, [color]);

  useEffect(() => {
    if (typeof cursor.color === "number") {
      const color = Canvas.instance?.Pallete.getColor(cursor.color);
      setColor(color?.hex);
    } else {
      setColor(undefined);
    }
  }, [setColor, cursor.color]);

  if (!color) return <></>;

  return (
    <div
      className="noselect"
      style={{
        position: "absolute",
        top: cursor.y,
        left: cursor.x,
        backgroundColor: "#" + color,
        width: "1px",
        height: "1px",
        opacity: 0.5,
      }}
    ></div>
  );
};

const CanvasInner = () => {
  const canvasRef = useRef<HTMLCanvasElement | null>();
  const canvas = useRef<Canvas>();
  const { config, setCanvasPosition, setCursorPosition, setPixelWhois } =
  const { config, setCanvasPosition, setCursor, setPixelWhois } =
    useAppContext();
  const PanZoom = useContext(RendererContext);

  useEffect(() => {
    if (!canvasRef.current) return;
    canvas.current = new Canvas(canvasRef.current!, PanZoom);

    const handlePixelWhois = ({
      clientX,
      clientY,
    }: {
      clientX: number;
      clientY: number;
    }) => {
  const handlePixelWhois = useCallback(
    ({ clientX, clientY }: { clientX: number; clientY: number }) => {
      if (!canvas.current) {
        console.warn(
          "[CanvasWrapper#handlePixelWhois] canvas instance does not exist"
@@ -83,7 +110,20 @@ const CanvasInner = () => {
      const surrounding = canvas.current.getSurroundingPixels(x, y, 3);

      setPixelWhois({ x, y, surrounding });
    };
    },
    [canvas.current]
  );

  useEffect(() => {
    if (!canvasRef.current) return;
    canvas.current = new Canvas(canvasRef.current!, PanZoom);
    canvas.current.on("canvasReady", () => {
      console.log("[CanvasWrapper] received canvasReady");

      // refresh because canvas might've resized
      const initialRouter = Router.get();
      handleNavigate(initialRouter);
    });

    KeybindManager.on("PIXEL_WHOIS", handlePixelWhois);

@@ -91,7 +131,7 @@ const CanvasInner = () => {
      KeybindManager.off("PIXEL_WHOIS", handlePixelWhois);
      canvas.current!.destroy();
    };
  }, [PanZoom, setCursorPosition]);
  }, [PanZoom]);

  useEffect(() => {
    Router.PanZoom = PanZoom;
@@ -115,10 +155,18 @@ const CanvasInner = () => {
        pos.x > config.canvas.size[0] ||
        pos.y > config.canvas.size[1]
      ) {
        setCursorPosition();
        setCursor((v) => ({
          ...v,
          x: undefined,
          y: undefined,
        }));
      } else {
        // fixes not passing the current value
        setCursorPosition({ ...pos });
        setCursor((v) => ({
          ...v,
          x: pos.x,
          y: pos.y,
        }));
      }
    }, 1);

@@ -127,7 +175,7 @@ const CanvasInner = () => {
    return () => {
      canvas.current!.off("cursorPos", handleCursorPos);
    };
  }, [config, setCursorPosition]);
  }, [config, setCursor]);

  useEffect(() => {
    if (!canvas.current) {
@@ -217,7 +265,7 @@ const CanvasInner = () => {
      PanZoom.removeListener("viewportMove", handleViewportMove);
      Router.off("navigate", handleNavigate);
    };
  }, [PanZoom, setCanvasPosition, setCursorPosition]);
  }, [PanZoom, setCanvasPosition]);

  return (
    <canvas
+1 −2
Original line number Diff line number Diff line
@@ -2,7 +2,6 @@ import {
  Modal,
  ModalBody,
  ModalContent,
  ModalFooter,
  ModalHeader,
  Switch,
} from "@nextui-org/react";
@@ -32,7 +31,7 @@ export const ModModal = () => {
    return () => {
      KeybindManager.off("TOGGLE_MOD_MENU", handleKeybind);
    };
  }, []);
  }, [hasAdmin]);

  const setBypassCooldown = useCallback(
    (value: boolean) => {
Loading