BeePlace¶
Introduction¶
BeePlace is a BeeScreens application in which users can create collaborative works of art. Each user can draw one pixel at a time on the shared canvas, and must wait a certain amount of time before drawing again. This forces collaboration and limits overflow.
How to run¶
- Follow instructions to start the backend at backend code.
- Follow instructions to start the frontend at frontend code.
- Access the frontend at http://localhost:3000.
Architecture and design¶
The BeePlace application is built around three main components:
- Backend - The backend service used by BeePlace.
- Frontend - The frontend service used by BeePlace.
- Common/Package - The common code shared between the frontend and the backend side of BeePlace.
Backend¶
The backend is a NestJS application. It is a WebSocket server that allows users to join the board and put pixels on it.
Code and how to run at backend-code.
Frontend¶
The frontend is a Next.js application. It is a WebSocket client that allows players to users to display the shared canvas and put pixels on it.
Code and how to run at frontend-code.
Common¶
The common code is a TypeScript project shared by the frontend and the backend. It contains all the types and enums used by the frontend and the backend.
Code and how to run at common-code.
Technical details¶
The following section describe how the app works.
The user joins the application¶
Schema¶
7d6624f5-8cb6-47d1-b51c-ba666b4b088e
Implementation details¶
- The frontend create the WebSockets connection with the backend. The connection is bi-directional.
- The frontend generates a fingerprint, a unique identifier for the user and send it to the backend with the
JOIN
event. The backend stores it in the socket instance. - The backend responds to the
JOIN
event with the initial configuration of the board. The frontend now displays the board in the HTML canvas.
Draw a pixel¶
Schema¶
264246e7-7c07-441b-98a0-50d8d8047bd0
Implementation details¶
- When the user draws a pixel, the frontend emits a
PIXEL_FROM_PLAYER
event with aPixel
object type to the backend. The pixel object contains the {x
,y
} coordinates and thecolor
of the pixel. The backend stores the pixel in Redis and the database as explained in the database section. - Every
BOARD_REFRESH_RATE
millisecond (base on environment variable), the backend emits anUPDATE_BOARD
event to all the connected users. The payload contains all the drawn pixels during the interval.
Database¶
What is stored in the database ? The coordinates and the color of all the drawn pixels.
A 3 level storage is used to optimize the application:
- In the memory of the backend
- In a Bitfield Redis
- In the PostgreSQL database
In the memory of the backend¶
Initially, the pixels where directly stored and retrieved from the Redis Bitfield. However, it was a performance bottleneck. The conversions from the Bitfield (a string) to a number array was too slow.
The solution was to store the pixels in the memory of the backend. The pixels are stored in a number array in addition to the Redis Bitfield. The Bitfield is necessary on the first application load, when the backend has restarted.
In a Bitfield Redis¶
The Bitfield is a Redis data structure that allows to store bits in a array of bytes. It is used to store the pixels in a very compact way. Each read or write operation is done in a single Redis command in O(1) time complexity.
Only the color id of the pixel is stored in the Bitfield. To know the coordinates of the pixel, an offset is used. The offset is calculated with the following formula: offset = x + BOARD_SIZE * y
. The BOARD_SIZE
is a constant defined in the backend code.
In the PostgreSQL database¶
The SQL database is not yet used very intensively. It is used to create a history of all pixels placed. This history will be used for future statistics, for example on a future administration dashboard.
The schema of the database's single table pixels is as follows:
Administration¶
For now, the administration is done with protected routes. The user must use the correct api key to access the administration routes (the one defined with the API_KEY
environment variable):
Here are the available actions:
POST /board/read-only
- Set the board in read-only mode, no more pixels can be drawn.POST /board/reset
- Reset the board, all the pixels are removed for all the users.POST /board/reset-area
- Reset the board in a specific area, all the pixels are removed for all the users.POST /board/restore-from-database-backup
- Restore the board state at a specific date from the SQL database.
The payload of the POST
requests are described in the API documentation.
Configure the display mode¶
The presentation of the different displays can be configured here: https://place.beescreens.ch/displays
The chosen configuration will generate links with query parameters for each display. These links displayed on the right can be clicked to display the right portion of the canvas on the right display.
Theses options can be changed:
Display width
- The size in pixels of all the displays widthDisplay height
- The size in pixels of all the displays heightNumber of columns
- The number of columns of displaysNumber of rows
- The number of rows of displays