Skip to content
Canvas.ts 2.81 KiB
Newer Older
Grant's avatar
Grant committed
import { CanvasConfig } from "@sc07-canvas/lib/src/net";
Grant's avatar
Grant committed
import { prisma } from "./prisma";
import { Redis } from "./redis";
Grant's avatar
Grant committed

class Canvas {
  private CANVAS_SIZE: [number, number];

  constructor() {
    this.CANVAS_SIZE = [100, 100];
  }

Grant's avatar
Grant committed
  getCanvasConfig(): CanvasConfig {
Grant's avatar
Grant committed
    return {
      size: this.CANVAS_SIZE,
      zoom: 7,
Grant's avatar
Grant committed
      pixel: {
        cooldown: 60,
        multiplier: 3,
        maxStack: 6,
      },
Grant's avatar
Grant committed
    };
  }

  /**
   * Latest database pixels -> Redis
   */
  async pixelsToRedis() {
    const redis = await Redis.getClient();
Grant's avatar
Grant committed

Grant's avatar
Grant committed
    const key = Redis.keyRef("pixelColor");
Grant's avatar
Grant committed

    for (let x = 0; x < this.CANVAS_SIZE[0]; x++) {
      for (let y = 0; y < this.CANVAS_SIZE[1]; y++) {
        const pixel = await prisma.pixel.findFirst({
          where: {
            x,
            y,
          },
          orderBy: [
            {
              createdAt: "asc",
            },
          ],
        });

        await redis.set(key(x, y), pixel?.color || "transparent");
      }
    }
  }

  /**
   * Redis pixels -> single Redis comma separated list of hex
   * @returns 1D array of pixel values
   */
  async canvasToRedis() {
    const redis = await Redis.getClient();
Grant's avatar
Grant committed

    const pixels: string[] = [];
Grant's avatar
Grant committed

    for (let x = 0; x < this.CANVAS_SIZE[0]; x++) {
      for (let y = 0; y < this.CANVAS_SIZE[1]; y++) {
        pixels.push(
Grant's avatar
Grant committed
          (await redis.get(Redis.key("pixelColor", x, y))) || "transparent"
Grant's avatar
Grant committed
    await redis.set(Redis.key("canvas"), pixels.join(","), { EX: 60 * 5 });
Grant's avatar
Grant committed

    return pixels;
  }

  /**
   * force an update at a specific position
   */
  async updateCanvasRedisAtPos(x: number, y: number) {
    const redis = await Redis.getClient();
Grant's avatar
Grant committed

    const pixels: string[] = (
Grant's avatar
Grant committed
      (await redis.get(Redis.key("canvas"))) || ""
    ).split(",");
Grant's avatar
Grant committed

    pixels[this.CANVAS_SIZE[0] * y + x] =
Grant's avatar
Grant committed
      (await redis.get(Redis.key("pixelColor", x, y))) || "transparent";
Grant's avatar
Grant committed

Grant's avatar
Grant committed
    await redis.set(Redis.key("canvas"), pixels.join(","), { EX: 60 * 5 });
Grant's avatar
Grant committed
  }

  async getPixelsArray() {
    const redis = await Redis.getClient();
Grant's avatar
Grant committed

Grant's avatar
Grant committed
    if (await redis.exists(Redis.key("canvas"))) {
      const cached = await redis.get(Redis.key("canvas"));
Grant's avatar
Grant committed
      return cached!.split(",");
    }

    return await this.canvasToRedis();
  }

  async setPixel(user: { sub: string }, x: number, y: number, hex: string) {
    const redis = await Redis.getClient();
Grant's avatar
Grant committed

    await prisma.pixel.create({
      data: {
        userId: user.sub,
        color: hex,
        x,
        y,
      },
    });

    await prisma.user.update({
      where: { sub: user.sub },
      data: { lastPixelTime: new Date() },
    });

    await redis.set(`CANVAS:PIXELS[${x},${y}]:COLOR`, hex);

    // maybe only update specific element?
    // i don't think it needs to be awaited
    await this.updateCanvasRedisAtPos(x, y);
  }
}

export default new Canvas();