From c2cbca138786c25d22c4226ec8a5700dbb05d2c8 Mon Sep 17 00:00:00 2001 From: HF Date: Tue, 4 Jan 2022 11:31:10 +0100 Subject: [PATCH] add per-canvas online counter --- src/actions/index.js | 6 ++++ src/actions/types.js | 1 + src/client.js | 2 +- src/components/CanvasItem.jsx | 12 +++++-- src/components/OnlineBox.jsx | 37 +++++++++++++++---- src/components/windows/CanvasSelect.jsx | 5 ++- src/reducers/gui.js | 11 ++++++ src/reducers/ranks.js | 10 +++++- src/socket/SocketEvents.js | 10 ++++-- src/socket/SocketServer.js | 47 +++++++++++++++++++------ src/socket/packets/OnlineCounter.js | 43 ++++++++++++++++++---- src/styles/default.css | 3 +- webpack.config.web.babel.js | 4 +-- 13 files changed, 157 insertions(+), 34 deletions(-) diff --git a/src/actions/index.js b/src/actions/index.js index f710136..e2792f9 100644 --- a/src/actions/index.js +++ b/src/actions/index.js @@ -58,6 +58,12 @@ export function toggleAutoZoomIn() { }; } +export function toggleOnlineCanvas() { + return { + type: 'TOGGLE_ONLINE_CANVAS', + }; +} + export function toggleMute() { return { type: 'TOGGLE_MUTE', diff --git a/src/actions/types.js b/src/actions/types.js index 8b4057a..c580329 100644 --- a/src/actions/types.js +++ b/src/actions/types.js @@ -17,6 +17,7 @@ export type Action = | { type: 'TOGGLE_GRID' } | { type: 'TOGGLE_PIXEL_NOTIFY' } | { type: 'TOGGLE_AUTO_ZOOM_IN' } + | { type: 'TOGGLE_ONLINE_CANVAS' } | { type: 'TOGGLE_MUTE' } | { type: 'TOGGLE_OPEN_PALETTE' } | { type: 'TOGGLE_COMPACT_PALETTE' } diff --git a/src/client.js b/src/client.js index 0f2820f..b627071 100644 --- a/src/client.js +++ b/src/client.js @@ -51,7 +51,7 @@ function init() { ProtocolClient.on('cooldownPacket', (coolDown) => { store.dispatch(receiveCoolDown(coolDown)); }); - ProtocolClient.on('onlineCounter', ({ online }) => { + ProtocolClient.on('onlineCounter', (online) => { store.dispatch(receiveOnline(online)); }); ProtocolClient.on('chatMessage', ( diff --git a/src/components/CanvasItem.jsx b/src/components/CanvasItem.jsx index 56e84ca..7b41479 100644 --- a/src/components/CanvasItem.jsx +++ b/src/components/CanvasItem.jsx @@ -9,7 +9,9 @@ import { t } from 'ttag'; import { THREE_CANVAS_HEIGHT } from '../core/constants'; -const CanvasItem = ({ canvasId, canvas, selCanvas }) => ( +const CanvasItem = ({ + canvasId, canvas, selCanvas, online, +}) => (
selCanvas(canvasId)} @@ -23,6 +25,12 @@ const CanvasItem = ({ canvasId, canvas, selCanvas }) => ( />

{canvas.title}
+ {(online) && ( + <> + {t`Online Users`}:  + {online}
+ + )} {canvas.desc}
{t`Cooldown`}:  @@ -33,7 +41,7 @@ const CanvasItem = ({ canvasId, canvas, selCanvas }) => ( {t`Stacking till`}:  {canvas.cds / 1000}s
{t`Ranked`}:  - {(canvas.ranked) ? 'Yes' : 'No'}
+ {(canvas.ranked) ? t`Yes` : t`No`}
{(canvas.req !== -1) ? {t`Requirements`}:
: null} {(canvas.req !== -1) ? {t`User Account`} : null} diff --git a/src/components/OnlineBox.jsx b/src/components/OnlineBox.jsx index d861cc3..66f4a13 100644 --- a/src/components/OnlineBox.jsx +++ b/src/components/OnlineBox.jsx @@ -4,9 +4,11 @@ */ import React from 'react'; -import { useSelector, shallowEqual } from 'react-redux'; -import { FaUser, FaPaintBrush } from 'react-icons/fa'; +import { useSelector, useDispatch, shallowEqual } from 'react-redux'; +import { FaUser, FaPaintBrush, FaFlipboard } from 'react-icons/fa'; import { t } from 'ttag'; + +import { toggleOnlineCanvas } from '../actions'; import { numberToString } from '../core/utils'; @@ -15,19 +17,42 @@ const OnlineBox = () => { online, totalPixels, name, + onlineCanvas, + canvasId, ] = useSelector((state) => [ state.ranks.online, state.ranks.totalPixels, state.user.name, + state.gui.onlineCanvas, + state.canvas.canvasId, ], shallowEqual); + const dispatch = useDispatch(); + + const onlineUsers = (onlineCanvas) ? online[canvasId] : online.total; return (

- {(online || name) + {(onlineUsers || name) ? ( -
- {(online) - && {online}  } +
dispatch(toggleOnlineCanvas())} + > + {(onlineUsers) + && ( + + {onlineUsers} + + {(onlineCanvas) && } +   + + )} {(name != null) && ( diff --git a/src/components/windows/CanvasSelect.jsx b/src/components/windows/CanvasSelect.jsx index 1b11f78..99450d8 100644 --- a/src/components/windows/CanvasSelect.jsx +++ b/src/components/windows/CanvasSelect.jsx @@ -12,9 +12,10 @@ import { changeWindowType, selectCanvas } from '../../actions'; const CanvasSelect = ({ windowId }) => { - const [canvases, showHiddenCanvases] = useSelector((state) => [ + const [canvases, showHiddenCanvases, online] = useSelector((state) => [ state.canvas.canvases, state.canvas.showHiddenCanvases, + state.ranks.online, ], shallowEqual); const dispatch = useDispatch(); const selCanvas = useCallback((canvasId) => dispatch(selectCanvas(canvasId)), @@ -44,6 +45,8 @@ const CanvasSelect = ({ windowId }) => { (!canvases[canvasId].hid || showHiddenCanvases) && ( { logger.error(`WebSocket Client Error for ${ws.name}: ${e.message}`); @@ -95,9 +92,7 @@ class SocketServer { ws.on('pong', heartbeat); ws.on('close', () => { - // is close called on terminate? - // possible memory leak? - ipCounter.delete(ip); + ipCounter.delete(getIPFromRequest(req)); this.deleteAllChunks(ws); }); @@ -114,6 +109,7 @@ class SocketServer { this.broadcastPixelBuffer = this.broadcastPixelBuffer.bind(this); this.reloadUser = this.reloadUser.bind(this); this.ping = this.ping.bind(this); + this.onlineCounterBroadcast = this.onlineCounterBroadcast.bind(this); socketEvents.on('broadcast', this.broadcast); socketEvents.on('pixelUpdate', this.broadcastPixelBuffer); @@ -170,7 +166,7 @@ class SocketServer { }); }); - setInterval(SocketServer.onlineCounterBroadcast, 10 * 1000); + setInterval(this.onlineCounterBroadcast, 10 * 1000); setInterval(this.ping, 45 * 1000); } @@ -294,8 +290,36 @@ class SocketServer { }); } - static onlineCounterBroadcast() { - const online = ipCounter.amount() || 0; + onlineCounterBroadcast() { + const online = {}; + online.total = ipCounter.amount() || 0; + const ipsPerCanvas = {}; + const it = this.wss.clients.keys(); + let client = it.next(); + while (!client.done) { + const ws = client.value; + if (ws.readyState === WebSocket.OPEN + && ws.user + ) { + const canvasId = ws.canvasId || 0; + const { ip } = ws.user; + // only count unique IPs per canvas + if (!ipsPerCanvas[canvasId]) { + ipsPerCanvas[canvasId] = [ip]; + } else if (ipsPerCanvas[canvasId].includes(ip)) { + client = it.next(); + continue; + } else { + ipsPerCanvas[canvasId].push(ip); + } + //-- + if (!online[canvasId]) { + online[canvasId] = 0; + } + online[canvasId] += 1; + } + client = it.next(); + } socketEvents.broadcastOnlineCounter(online); } @@ -406,6 +430,7 @@ class SocketServer { } case RegisterCanvas.OP_CODE: { const canvasId = RegisterCanvas.hydrate(buffer); + if (!canvases[canvasId]) return; if (ws.canvasId !== null && ws.canvasId !== canvasId) { this.deleteAllChunks(ws); } diff --git a/src/socket/packets/OnlineCounter.js b/src/socket/packets/OnlineCounter.js index 320ff52..c301a09 100644 --- a/src/socket/packets/OnlineCounter.js +++ b/src/socket/packets/OnlineCounter.js @@ -1,19 +1,48 @@ +/* + * + * Numbers of online players per canvas + * + */ + const OP_CODE = 0xA7; export default { OP_CODE, + // CLIENT (receiver) + /* + * { + * total: totalOnline, + * canvasId: online, + * .... + * } + */ hydrate(data) { - // CLIENT (receiver) - const online = data.getInt16(1); - return { online }; + const online = {}; + online.total = data.getUint16(1); + let off = data.byteLength; + while (off > 3) { + const onlineUsers = data.getUint16(off -= 2); + const canvas = data.getUint8(off -= 1); + online[canvas] = onlineUsers; + } + return online; }, - dehydrate({ online }) { + + dehydrate(online) { // SERVER (sender) if (!process.env.BROWSER) { - const buffer = Buffer.allocUnsafe(1 + 2); - buffer.writeUInt8(OP_CODE, 0); + const canvasIds = Object.keys(online); - buffer.writeInt16BE(online, 1); + const buffer = Buffer.allocUnsafe(3 + canvasIds.length * (1 + 2)); + buffer.writeUInt8(OP_CODE, 0); + buffer.writeUInt16BE(online.total, 1); + let cnt = 1; + for (let p = 0; p < canvasIds.length; p += 1) { + const canvasId = canvasIds[p]; + const onlineUsers = online[canvasId]; + buffer.writeUInt8(Number(canvasId), cnt += 2); + buffer.writeUInt16BE(onlineUsers, cnt += 1); + } return buffer; } diff --git a/src/styles/default.css b/src/styles/default.css index 22ae3e3..dc59d1d 100644 --- a/src/styles/default.css +++ b/src/styles/default.css @@ -330,6 +330,7 @@ tr:nth-child(even) { .onlinebox { left: 16px; bottom: 57px; + cursor: pointer; white-space: nowrap; } @@ -800,7 +801,7 @@ tr:nth-child(even) { padding: 0; } -.actionbuttons:hover, .coorbox:hover, .menu > div:hover { +.actionbuttons:hover, .coorbox:hover, .menu > div:hover, .onlinebox:hover { background-color: #d2d2d2cc; } diff --git a/webpack.config.web.babel.js b/webpack.config.web.babel.js index 5d0b80b..ce165f4 100644 --- a/webpack.config.web.babel.js +++ b/webpack.config.web.babel.js @@ -107,8 +107,8 @@ export default ({ externals: [ /\/proxies\.json$/, /\/canvases\.json$/, - /^\.\/styleassets\.json$/, - /^\.\/assets\.json$/, + /\/styleassets\.json$/, + /\/assets\.json$/, nodeExternals(), ],