add per-canvas online counter
This commit is contained in:
parent
6170d35631
commit
c2cbca1387
|
@ -58,6 +58,12 @@ export function toggleAutoZoomIn() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function toggleOnlineCanvas() {
|
||||||
|
return {
|
||||||
|
type: 'TOGGLE_ONLINE_CANVAS',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function toggleMute() {
|
export function toggleMute() {
|
||||||
return {
|
return {
|
||||||
type: 'TOGGLE_MUTE',
|
type: 'TOGGLE_MUTE',
|
||||||
|
|
|
@ -17,6 +17,7 @@ export type Action =
|
||||||
| { type: 'TOGGLE_GRID' }
|
| { type: 'TOGGLE_GRID' }
|
||||||
| { type: 'TOGGLE_PIXEL_NOTIFY' }
|
| { type: 'TOGGLE_PIXEL_NOTIFY' }
|
||||||
| { type: 'TOGGLE_AUTO_ZOOM_IN' }
|
| { type: 'TOGGLE_AUTO_ZOOM_IN' }
|
||||||
|
| { type: 'TOGGLE_ONLINE_CANVAS' }
|
||||||
| { type: 'TOGGLE_MUTE' }
|
| { type: 'TOGGLE_MUTE' }
|
||||||
| { type: 'TOGGLE_OPEN_PALETTE' }
|
| { type: 'TOGGLE_OPEN_PALETTE' }
|
||||||
| { type: 'TOGGLE_COMPACT_PALETTE' }
|
| { type: 'TOGGLE_COMPACT_PALETTE' }
|
||||||
|
|
|
@ -51,7 +51,7 @@ function init() {
|
||||||
ProtocolClient.on('cooldownPacket', (coolDown) => {
|
ProtocolClient.on('cooldownPacket', (coolDown) => {
|
||||||
store.dispatch(receiveCoolDown(coolDown));
|
store.dispatch(receiveCoolDown(coolDown));
|
||||||
});
|
});
|
||||||
ProtocolClient.on('onlineCounter', ({ online }) => {
|
ProtocolClient.on('onlineCounter', (online) => {
|
||||||
store.dispatch(receiveOnline(online));
|
store.dispatch(receiveOnline(online));
|
||||||
});
|
});
|
||||||
ProtocolClient.on('chatMessage', (
|
ProtocolClient.on('chatMessage', (
|
||||||
|
|
|
@ -9,7 +9,9 @@ import { t } from 'ttag';
|
||||||
import { THREE_CANVAS_HEIGHT } from '../core/constants';
|
import { THREE_CANVAS_HEIGHT } from '../core/constants';
|
||||||
|
|
||||||
|
|
||||||
const CanvasItem = ({ canvasId, canvas, selCanvas }) => (
|
const CanvasItem = ({
|
||||||
|
canvasId, canvas, selCanvas, online,
|
||||||
|
}) => (
|
||||||
<div
|
<div
|
||||||
className="cvbtn"
|
className="cvbtn"
|
||||||
onClick={() => selCanvas(canvasId)}
|
onClick={() => selCanvas(canvasId)}
|
||||||
|
@ -23,6 +25,12 @@ const CanvasItem = ({ canvasId, canvas, selCanvas }) => (
|
||||||
/>
|
/>
|
||||||
<p className="modalcvtext">
|
<p className="modalcvtext">
|
||||||
<span className="modaltitle">{canvas.title}</span><br />
|
<span className="modaltitle">{canvas.title}</span><br />
|
||||||
|
{(online) && (
|
||||||
|
<>
|
||||||
|
{t`Online Users`}:
|
||||||
|
<span className="modalinfo">{online}</span><br />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<span className="modalinfo">{canvas.desc}</span><br />
|
<span className="modalinfo">{canvas.desc}</span><br />
|
||||||
{t`Cooldown`}:
|
{t`Cooldown`}:
|
||||||
<span className="modalinfo">
|
<span className="modalinfo">
|
||||||
|
@ -33,7 +41,7 @@ const CanvasItem = ({ canvasId, canvas, selCanvas }) => (
|
||||||
{t`Stacking till`}:
|
{t`Stacking till`}:
|
||||||
<span className="modalinfo"> {canvas.cds / 1000}s</span><br />
|
<span className="modalinfo"> {canvas.cds / 1000}s</span><br />
|
||||||
{t`Ranked`}:
|
{t`Ranked`}:
|
||||||
<span className="modalinfo">{(canvas.ranked) ? 'Yes' : 'No'}</span><br />
|
<span className="modalinfo">{(canvas.ranked) ? t`Yes` : t`No`}</span><br />
|
||||||
{(canvas.req !== -1) ? <span>{t`Requirements`}:<br /></span> : null}
|
{(canvas.req !== -1) ? <span>{t`Requirements`}:<br /></span> : null}
|
||||||
<span className="modalinfo">
|
<span className="modalinfo">
|
||||||
{(canvas.req !== -1) ? <span>{t`User Account`} </span> : null}
|
{(canvas.req !== -1) ? <span>{t`User Account`} </span> : null}
|
||||||
|
|
|
@ -4,9 +4,11 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useSelector, shallowEqual } from 'react-redux';
|
import { useSelector, useDispatch, shallowEqual } from 'react-redux';
|
||||||
import { FaUser, FaPaintBrush } from 'react-icons/fa';
|
import { FaUser, FaPaintBrush, FaFlipboard } from 'react-icons/fa';
|
||||||
import { t } from 'ttag';
|
import { t } from 'ttag';
|
||||||
|
|
||||||
|
import { toggleOnlineCanvas } from '../actions';
|
||||||
import { numberToString } from '../core/utils';
|
import { numberToString } from '../core/utils';
|
||||||
|
|
||||||
|
|
||||||
|
@ -15,19 +17,42 @@ const OnlineBox = () => {
|
||||||
online,
|
online,
|
||||||
totalPixels,
|
totalPixels,
|
||||||
name,
|
name,
|
||||||
|
onlineCanvas,
|
||||||
|
canvasId,
|
||||||
] = useSelector((state) => [
|
] = useSelector((state) => [
|
||||||
state.ranks.online,
|
state.ranks.online,
|
||||||
state.ranks.totalPixels,
|
state.ranks.totalPixels,
|
||||||
state.user.name,
|
state.user.name,
|
||||||
|
state.gui.onlineCanvas,
|
||||||
|
state.canvas.canvasId,
|
||||||
], shallowEqual);
|
], shallowEqual);
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const onlineUsers = (onlineCanvas) ? online[canvasId] : online.total;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{(online || name)
|
{(onlineUsers || name)
|
||||||
? (
|
? (
|
||||||
<div className="onlinebox">
|
<div
|
||||||
{(online)
|
className="onlinebox"
|
||||||
&& <span title={t`User online`}>{online} <FaUser /> </span>}
|
role="button"
|
||||||
|
tabIndex="0"
|
||||||
|
onClick={() => dispatch(toggleOnlineCanvas())}
|
||||||
|
>
|
||||||
|
{(onlineUsers)
|
||||||
|
&& (
|
||||||
|
<span
|
||||||
|
title={(onlineCanvas)
|
||||||
|
? t`Online Users on Canvas`
|
||||||
|
: t`Total Online Users`}
|
||||||
|
>
|
||||||
|
{onlineUsers}
|
||||||
|
<FaUser />
|
||||||
|
{(onlineCanvas) && <FaFlipboard />}
|
||||||
|
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
{(name != null)
|
{(name != null)
|
||||||
&& (
|
&& (
|
||||||
<span title={t`Pixels placed`}>
|
<span title={t`Pixels placed`}>
|
||||||
|
|
|
@ -12,9 +12,10 @@ import { changeWindowType, selectCanvas } from '../../actions';
|
||||||
|
|
||||||
|
|
||||||
const CanvasSelect = ({ windowId }) => {
|
const CanvasSelect = ({ windowId }) => {
|
||||||
const [canvases, showHiddenCanvases] = useSelector((state) => [
|
const [canvases, showHiddenCanvases, online] = useSelector((state) => [
|
||||||
state.canvas.canvases,
|
state.canvas.canvases,
|
||||||
state.canvas.showHiddenCanvases,
|
state.canvas.showHiddenCanvases,
|
||||||
|
state.ranks.online,
|
||||||
], shallowEqual);
|
], shallowEqual);
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const selCanvas = useCallback((canvasId) => dispatch(selectCanvas(canvasId)),
|
const selCanvas = useCallback((canvasId) => dispatch(selectCanvas(canvasId)),
|
||||||
|
@ -44,6 +45,8 @@ const CanvasSelect = ({ windowId }) => {
|
||||||
(!canvases[canvasId].hid || showHiddenCanvases)
|
(!canvases[canvasId].hid || showHiddenCanvases)
|
||||||
&& (
|
&& (
|
||||||
<CanvasItem
|
<CanvasItem
|
||||||
|
key={canvasId}
|
||||||
|
online={online[canvasId]}
|
||||||
canvasId={canvasId}
|
canvasId={canvasId}
|
||||||
canvas={canvases[canvasId]}
|
canvas={canvases[canvasId]}
|
||||||
selCanvas={selCanvas}
|
selCanvas={selCanvas}
|
||||||
|
|
|
@ -7,7 +7,11 @@ const initialState = {
|
||||||
isLightGrid: false,
|
isLightGrid: false,
|
||||||
compactPalette: false,
|
compactPalette: false,
|
||||||
paletteOpen: true,
|
paletteOpen: true,
|
||||||
|
// top-left button menu
|
||||||
menuOpen: false,
|
menuOpen: false,
|
||||||
|
// show online users per canvas instead of total
|
||||||
|
onlineCanvas: false,
|
||||||
|
// selected theme
|
||||||
style: 'default',
|
style: 'default',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -38,6 +42,13 @@ export default function gui(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'TOGGLE_ONLINE_CANVAS': {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
onlineCanvas: !state.onlineCanvas,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
case 'TOGGLE_POTATO_MODE': {
|
case 'TOGGLE_POTATO_MODE': {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
|
|
@ -5,7 +5,15 @@ const initialState = {
|
||||||
ranking: 1488,
|
ranking: 1488,
|
||||||
dailyRanking: 1488,
|
dailyRanking: 1488,
|
||||||
// global stats
|
// global stats
|
||||||
online: 1,
|
/*
|
||||||
|
* {
|
||||||
|
* total: totalUsersOnline,
|
||||||
|
* canvasId: onlineAtCanvas,
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
online: {
|
||||||
|
total: 0,
|
||||||
|
},
|
||||||
totalRanking: {},
|
totalRanking: {},
|
||||||
totalDailyRanking: {},
|
totalDailyRanking: {},
|
||||||
};
|
};
|
||||||
|
|
|
@ -11,7 +11,13 @@ import PixelUpdate from './packets/PixelUpdateServer';
|
||||||
class SocketEvents extends EventEmitter {
|
class SocketEvents extends EventEmitter {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.onlineCounter = 0;
|
/*
|
||||||
|
* {
|
||||||
|
* canvasId: onlineUsers,
|
||||||
|
* ...
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
this.onlineCounter = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -144,7 +150,7 @@ class SocketEvents extends EventEmitter {
|
||||||
*/
|
*/
|
||||||
broadcastOnlineCounter(online: number) {
|
broadcastOnlineCounter(online: number) {
|
||||||
this.onlineCounter = online;
|
this.onlineCounter = online;
|
||||||
const buffer = OnlineCounter.dehydrate({ online });
|
const buffer = OnlineCounter.dehydrate(online);
|
||||||
this.emit('broadcast', buffer);
|
this.emit('broadcast', buffer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
import WebSocket from 'ws';
|
import WebSocket from 'ws';
|
||||||
|
|
||||||
import logger from '../core/logger';
|
import logger from '../core/logger';
|
||||||
|
import canvases from '../canvases.json';
|
||||||
import Counter from '../utils/Counter';
|
import Counter from '../utils/Counter';
|
||||||
import { getIPFromRequest } from '../utils/ip';
|
import { getIPFromRequest } from '../utils/ip';
|
||||||
|
|
||||||
|
@ -82,11 +83,7 @@ class SocketServer {
|
||||||
ws.name = user.getName();
|
ws.name = user.getName();
|
||||||
cheapDetector(user.ip);
|
cheapDetector(user.ip);
|
||||||
|
|
||||||
ws.send(OnlineCounter.dehydrate({
|
ws.send(OnlineCounter.dehydrate(socketEvents.onlineCounter));
|
||||||
online: ipCounter.amount() || 0,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const ip = getIPFromRequest(req);
|
|
||||||
|
|
||||||
ws.on('error', (e) => {
|
ws.on('error', (e) => {
|
||||||
logger.error(`WebSocket Client Error for ${ws.name}: ${e.message}`);
|
logger.error(`WebSocket Client Error for ${ws.name}: ${e.message}`);
|
||||||
|
@ -95,9 +92,7 @@ class SocketServer {
|
||||||
ws.on('pong', heartbeat);
|
ws.on('pong', heartbeat);
|
||||||
|
|
||||||
ws.on('close', () => {
|
ws.on('close', () => {
|
||||||
// is close called on terminate?
|
ipCounter.delete(getIPFromRequest(req));
|
||||||
// possible memory leak?
|
|
||||||
ipCounter.delete(ip);
|
|
||||||
this.deleteAllChunks(ws);
|
this.deleteAllChunks(ws);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -114,6 +109,7 @@ class SocketServer {
|
||||||
this.broadcastPixelBuffer = this.broadcastPixelBuffer.bind(this);
|
this.broadcastPixelBuffer = this.broadcastPixelBuffer.bind(this);
|
||||||
this.reloadUser = this.reloadUser.bind(this);
|
this.reloadUser = this.reloadUser.bind(this);
|
||||||
this.ping = this.ping.bind(this);
|
this.ping = this.ping.bind(this);
|
||||||
|
this.onlineCounterBroadcast = this.onlineCounterBroadcast.bind(this);
|
||||||
|
|
||||||
socketEvents.on('broadcast', this.broadcast);
|
socketEvents.on('broadcast', this.broadcast);
|
||||||
socketEvents.on('pixelUpdate', this.broadcastPixelBuffer);
|
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);
|
setInterval(this.ping, 45 * 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -294,8 +290,36 @@ class SocketServer {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static onlineCounterBroadcast() {
|
onlineCounterBroadcast() {
|
||||||
const online = ipCounter.amount() || 0;
|
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);
|
socketEvents.broadcastOnlineCounter(online);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -406,6 +430,7 @@ class SocketServer {
|
||||||
}
|
}
|
||||||
case RegisterCanvas.OP_CODE: {
|
case RegisterCanvas.OP_CODE: {
|
||||||
const canvasId = RegisterCanvas.hydrate(buffer);
|
const canvasId = RegisterCanvas.hydrate(buffer);
|
||||||
|
if (!canvases[canvasId]) return;
|
||||||
if (ws.canvasId !== null && ws.canvasId !== canvasId) {
|
if (ws.canvasId !== null && ws.canvasId !== canvasId) {
|
||||||
this.deleteAllChunks(ws);
|
this.deleteAllChunks(ws);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,48 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Numbers of online players per canvas
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
const OP_CODE = 0xA7;
|
const OP_CODE = 0xA7;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
OP_CODE,
|
OP_CODE,
|
||||||
hydrate(data) {
|
|
||||||
// CLIENT (receiver)
|
// CLIENT (receiver)
|
||||||
const online = data.getInt16(1);
|
/*
|
||||||
return { online };
|
* {
|
||||||
|
* total: totalOnline,
|
||||||
|
* canvasId: online,
|
||||||
|
* ....
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
hydrate(data) {
|
||||||
|
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)
|
// SERVER (sender)
|
||||||
if (!process.env.BROWSER) {
|
if (!process.env.BROWSER) {
|
||||||
const buffer = Buffer.allocUnsafe(1 + 2);
|
const canvasIds = Object.keys(online);
|
||||||
buffer.writeUInt8(OP_CODE, 0);
|
|
||||||
|
|
||||||
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;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
|
@ -330,6 +330,7 @@ tr:nth-child(even) {
|
||||||
.onlinebox {
|
.onlinebox {
|
||||||
left: 16px;
|
left: 16px;
|
||||||
bottom: 57px;
|
bottom: 57px;
|
||||||
|
cursor: pointer;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -800,7 +801,7 @@ tr:nth-child(even) {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.actionbuttons:hover, .coorbox:hover, .menu > div:hover {
|
.actionbuttons:hover, .coorbox:hover, .menu > div:hover, .onlinebox:hover {
|
||||||
background-color: #d2d2d2cc;
|
background-color: #d2d2d2cc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -107,8 +107,8 @@ export default ({
|
||||||
externals: [
|
externals: [
|
||||||
/\/proxies\.json$/,
|
/\/proxies\.json$/,
|
||||||
/\/canvases\.json$/,
|
/\/canvases\.json$/,
|
||||||
/^\.\/styleassets\.json$/,
|
/\/styleassets\.json$/,
|
||||||
/^\.\/assets\.json$/,
|
/\/assets\.json$/,
|
||||||
nodeExternals(),
|
nodeExternals(),
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user