From b22971ef35bbd234f01fe1050799a80c17a5003f Mon Sep 17 00:00:00 2001 From: Peter Millauer Date: Sun, 10 May 2026 14:56:01 +0200 Subject: [PATCH] Working streams --- .gitignore | 1 + src/client/model/rpc-callbacks.ts | 4 +- .../services/draw/draw.client-service.ts | 20 +++------- .../services/state/state.client-service.ts | 10 ++--- .../services/express/express.service.ts | 12 +++++- src/server/services/state/state.service.ts | 40 +++++++++++++------ 6 files changed, 53 insertions(+), 34 deletions(-) diff --git a/.gitignore b/.gitignore index a547bf3..f241ac4 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ node_modules dist dist-ssr *.local +circles-pictures # Editor directories and files .vscode/* diff --git a/src/client/model/rpc-callbacks.ts b/src/client/model/rpc-callbacks.ts index 51c5b13..fb0cf3c 100644 --- a/src/client/model/rpc-callbacks.ts +++ b/src/client/model/rpc-callbacks.ts @@ -1,3 +1,5 @@ import { Stroke } from "./canvas-state"; -export type ListenCallbackParam = { strokeId: number, stroke: Stroke } \ No newline at end of file +export type StrokeStreamElement = { strokeId: number, stroke: Stroke } + +export type OnStrokeCallback = (state: StrokeStreamElement) => void \ No newline at end of file diff --git a/src/client/services/draw/draw.client-service.ts b/src/client/services/draw/draw.client-service.ts index 9ac4290..9af8612 100644 --- a/src/client/services/draw/draw.client-service.ts +++ b/src/client/services/draw/draw.client-service.ts @@ -23,29 +23,25 @@ export class ClientDrawService { private readonly densityValue = document.getElementById('densityValue')!, private readonly widthSlider = document.getElementById('widthSlider')!, - private readonly widthValue = document.getElementById('widthValue')!, + private readonly widthValue = document.getElementById('widthValue')!, private readonly menuButton = document.getElementById('menuButton')!, private readonly drawer = document.getElementById('drawer')!, private readonly closeButton = document.getElementById('closeButton')!, ) { - this.resizeCanvas() this.setupCanvasEvents() - window.addEventListener("resize", () => this.resizeCanvas()); - this.cctx = canvas.getContext("2d")! this.currentColor = colorPicker!.getAttribute('value')! this.currentDensity = Number(densitySlider!.getAttribute('value'))! this.currentWidth = Number(widthSlider!.getAttribute('value'))! + + this.cctx = canvas.getContext("2d")! } draw() { - - console.log("drawing") const strokes: Stroke[] = this.stateService.getStrokes() this.cctx.clearRect(0, 0, this.canvas.width, this.canvas.height); - - this.cctx.globalAlpha = 1; + strokes.forEach(stroke => { this.cctx.fillStyle = stroke.color; this.drawCurve(stroke.points, stroke.density, stroke.width) @@ -122,7 +118,7 @@ export class ClientDrawService { }); document.addEventListener('click', (e) => { - if(!e.target){ + if (!e.target) { return } if (!this.drawer.contains(e.target as Node) && !this.menuButton.contains(e.target as Node)) { @@ -132,12 +128,6 @@ export class ClientDrawService { } - private resizeCanvas() { - const size = Math.min(window.innerWidth, window.innerHeight) * 0.9; - this.canvas.width = size; - this.canvas.height = size; - } - private drawCurve(points: Point[], density: number, width: number) { if (points.length < 2) return; diff --git a/src/client/services/state/state.client-service.ts b/src/client/services/state/state.client-service.ts index 788a429..45866a8 100644 --- a/src/client/services/state/state.client-service.ts +++ b/src/client/services/state/state.client-service.ts @@ -1,6 +1,6 @@ import { RPCSocket } from "../../../../node_modules/rpclibrary/js/Index"; import { CanvasState, Point, Stroke } from "../../model/canvas-state"; -import { ListenCallbackParam } from "../../model/rpc-callbacks"; +import { StrokeStreamElement } from "../../model/rpc-callbacks"; import { ClientDrawService } from "../draw/draw.client-service"; @@ -14,11 +14,11 @@ export class ClientStateService { const sock = await new RPCSocket(8080, 'localhost').connect(); this.remoteService = sock['StateService'] this.canvasState = await this.getState() - await this.remoteService.listen((listenDto: ListenCallbackParam) => { - if(!this.canvasState.strokes[listenDto.strokeId]){ - this.canvasState.strokes[listenDto.strokeId] = listenDto.stroke + await this.remoteService.onStroke((s: StrokeStreamElement) => { + if(!this.canvasState.strokes[s.strokeId]){ + this.canvasState.strokes[s.strokeId] = s.stroke }else{ - this.canvasState.strokes[listenDto.strokeId].points = [...this.canvasState.strokes[listenDto.strokeId].points, ...listenDto.stroke.points] + this.canvasState.strokes[s.strokeId].points = [...this.canvasState.strokes[s.strokeId].points, ...s.stroke.points] } drawService.draw() }) diff --git a/src/server/services/express/express.service.ts b/src/server/services/express/express.service.ts index 6accabf..358649a 100644 --- a/src/server/services/express/express.service.ts +++ b/src/server/services/express/express.service.ts @@ -13,6 +13,8 @@ export class ExpressService implements Initializable { @Inject(StateService) private stateService: StateService; + private connectionCount = 0; + initialize() { const app = express(); const PORT = 8080; @@ -28,7 +30,15 @@ export class ExpressService implements Initializable { const rpcServer = new RPCServer([ this.stateService, - ]) + ], { + closeHandler: (socket) => { + this.connectionCount -= 1 + if(this.connectionCount === 0){ + this.stateService.finalizePicture() + } + }, + connectionHandler: (socket) => { this.connectionCount += 1 }, + }) rpcServer.attach(httpServer) rpcServer.listen(PORT) diff --git a/src/server/services/state/state.service.ts b/src/server/services/state/state.service.ts index f7107a9..4c0e252 100644 --- a/src/server/services/state/state.service.ts +++ b/src/server/services/state/state.service.ts @@ -1,43 +1,59 @@ import { Singleton, Initializable } from "depents"; import { RPCExporter } from "rpclibrary"; import { CanvasState, Point, Stroke } from "../../../client/model/canvas-state"; -import { ListenCallbackParam } from "../../../client/model/rpc-callbacks"; +import { OnStrokeCallback } from "../../../client/model/rpc-callbacks"; +import * as fs from "fs"; +import * as path from "path"; @Singleton() export class StateService implements Initializable, RPCExporter { name = 'StateService' as const private canvasState: CanvasState = { strokes: [] } - private clients: Array<(param: ListenCallbackParam) => void> = [] + private onStrokeCallbacks: Array = [] initialize() { this.canvasState = { strokes: [] } - this.clients = [] + this.onStrokeCallbacks = [] }; beginStroke = async (stroke: Stroke) => { const strokeId = this.canvasState.strokes.length this.canvasState.strokes.push(stroke) - this.updateclients(strokeId, stroke) + this.updateStrokeListeners(strokeId, stroke) return strokeId } addPoint = async (strokeId: number, point: Point) => { this.canvasState.strokes[strokeId].points.push(point) - this.updateclients(strokeId, { ...this.canvasState.strokes[strokeId], points: [point] }) + this.updateStrokeListeners(strokeId, { ...this.canvasState.strokes[strokeId], points: [point] }) } - listen = async (callback: (state: any) => Promise) => { - this.clients = [...this.clients, callback] - return this.canvasState + onStroke = async (callback: OnStrokeCallback) => { + this.onStrokeCallbacks = [...this.onStrokeCallbacks, callback] + return } getState = async (): Promise => { return this.canvasState } - private updateclients = (strokeId: number, stroke: Stroke) => { - this.clients.forEach((client) => { + finalizePicture = () => { + const finalState = { ...this.canvasState } + this.canvasState = { strokes: [] } + + const picturedir = path.join(__dirname, `../../../../circles-pictures/`); + + if (!fs.existsSync(picturedir)) { + fs.mkdirSync(picturedir); + } + + fs.writeFileSync(path.join(picturedir, `${Date.now()}.json`), JSON.stringify(finalState)) + console.log(finalState) + } + + private updateStrokeListeners = (strokeId: number, stroke: Stroke) => { + this.onStrokeCallbacks.forEach((client) => { client({ strokeId, stroke }) }) } @@ -47,8 +63,8 @@ export class StateService implements Initializable, RPCExporter { this.addPoint, this.getState, { - name: 'listen' as const, - hook: (cb: any) => { this.listen(cb) } + name: 'onStroke' as const, + hook: this.onStroke, } ]