Skip to content
Snippets Groups Projects
room.js 2.81 KiB
import { spreadErasureIntervals, flattenErasureIntervals } from "./erasure.js"

class Room extends EventTarget {
  constructor(name) {
    super()
    this.name = name
    this.crdt = null
    this.undoStack = []
  }

  disconnect() {
    this.crdt.destroy()
    this.crdt = null
  }

  getUserID() {
    return this.crdt.getUserID()
  }

  addPath([x, y, w, colour]) {
    const pathID = this.crdt.addPath([x, y, w, colour])

    this.undoStack.push([pathID, 0, 0])

    this.dispatchEvent(new CustomEvent("undoEnabled"))

    return pathID
  }

  extendPath(pathID, [x, y, w, colour]) {
    const pathLength = this.crdt.extendPath(pathID, [x, y, w, colour])

    if (pathLength == 2) {
      this.undoStack[this.undoStack.length - 1] = [pathID, 0, 1]
    } else {
      this.undoStack.push([pathID, pathLength - 2, pathLength - 1])
    }

    this.dispatchEvent(new CustomEvent("undoEnabled"))
  }

  endPath(pathID) {
    this.crdt.endPath(pathID)
  }

  extendErasureIntervals(pathID, pointID, newIntervals) {
    this.crdt.extendErasureIntervals(
      pathID,
      flattenErasureIntervals({ [pointID]: newIntervals }),
    )
  }

  replacePath(pathID, newPoints) {
    this.fastUndo(true)

    newPoints.forEach((point) => this.extendPath(pathID, point))

    this.undoStack.splice(this.undoStack.length - newPoints.length, 1)
  }

  getPaths() {
    const paths = new Map()

    for (const pathID of this.crdt.getPathIDs()) {
      paths.set(pathID, this.crdt.getPathPoints(pathID))
    }

    return paths
  }
  getPathPoints(pathID) {
    return this.crdt.getPathPoints(pathID)
  }

  getErasureIntervals(pathID) {
    return spreadErasureIntervals(this.crdt.getErasureIntervals(pathID))
  }

  canUndo() {
    return this.undoStack.length > 0
  }

  undo() {
    const operation = this.undoStack.pop()

    if (!operation) return

    const [pathID, ...interval] = operation

    this.crdt.extendErasureIntervals(pathID, [interval])
  }

  fastUndo(forReplacing = false) {
    let from = this.undoStack.length - 1

    if (from < 0) return

    // eslint-disable-next-line no-unused-vars
    const [pathID, _, end] = this.undoStack[from]
    const endErasing = forReplacing ? end + 1 : end

    for (; from >= 0; from--) {
      if (this.undoStack[from][0] != pathID) {
        from++
        break
      }
    }

    this.undoStack = this.undoStack.slice(0, Math.max(0, from))

    this.crdt.extendErasureIntervals(pathID, [[0, endErasing]])
  }
}

export const connect = async (roomName, CRDT, connection) => {
  const room = new Room(roomName)

  await CRDT.initialise(room, {
    connection,
    url: "/",
    room: room.name,
    mesh: {
      minPeers: 4,
      maxPeers: 8,
    },
    handshake: {
      initial: 100,
      interval: 500,
    },
    heartbeat: {
      interval: 500,
      minimum: 1000,
      timeout: 10000,
    },
  })

  return room
}