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() {
|
||||
return {
|
||||
type: 'TOGGLE_MUTE',
|
||||
|
|
|
@ -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' }
|
||||
|
|
|
@ -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', (
|
||||
|
|
|
@ -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,
|
||||
}) => (
|
||||
<div
|
||||
className="cvbtn"
|
||||
onClick={() => selCanvas(canvasId)}
|
||||
|
@ -23,6 +25,12 @@ const CanvasItem = ({ canvasId, canvas, selCanvas }) => (
|
|||
/>
|
||||
<p className="modalcvtext">
|
||||
<span className="modaltitle">{canvas.title}</span><br />
|
||||
{(online) && (
|
||||
<>
|
||||
{t`Online Users`}:
|
||||
<span className="modalinfo">{online}</span><br />
|
||||
</>
|
||||
)}
|
||||
<span className="modalinfo">{canvas.desc}</span><br />
|
||||
{t`Cooldown`}:
|
||||
<span className="modalinfo">
|
||||
|
@ -33,7 +41,7 @@ const CanvasItem = ({ canvasId, canvas, selCanvas }) => (
|
|||
{t`Stacking till`}:
|
||||
<span className="modalinfo"> {canvas.cds / 1000}s</span><br />
|
||||
{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}
|
||||
<span className="modalinfo">
|
||||
{(canvas.req !== -1) ? <span>{t`User Account`} </span> : null}
|
||||
|
|
|
@ -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 (
|
||||
<div>
|
||||
{(online || name)
|
||||
{(onlineUsers || name)
|
||||
? (
|
||||
<div className="onlinebox">
|
||||
{(online)
|
||||
&& <span title={t`User online`}>{online} <FaUser /> </span>}
|
||||
<div
|
||||
className="onlinebox"
|
||||
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)
|
||||
&& (
|
||||
<span title={t`Pixels placed`}>
|
||||
|
|
|
@ -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)
|
||||
&& (
|
||||
<CanvasItem
|
||||
key={canvasId}
|
||||
online={online[canvasId]}
|
||||
canvasId={canvasId}
|
||||
canvas={canvases[canvasId]}
|
||||
selCanvas={selCanvas}
|
||||
|
|
|
@ -7,7 +7,11 @@ const initialState = {
|
|||
isLightGrid: false,
|
||||
compactPalette: false,
|
||||
paletteOpen: true,
|
||||
// top-left button menu
|
||||
menuOpen: false,
|
||||
// show online users per canvas instead of total
|
||||
onlineCanvas: false,
|
||||
// selected theme
|
||||
style: 'default',
|
||||
};
|
||||
|
||||
|
@ -38,6 +42,13 @@ export default function gui(
|
|||
};
|
||||
}
|
||||
|
||||
case 'TOGGLE_ONLINE_CANVAS': {
|
||||
return {
|
||||
...state,
|
||||
onlineCanvas: !state.onlineCanvas,
|
||||
};
|
||||
}
|
||||
|
||||
case 'TOGGLE_POTATO_MODE': {
|
||||
return {
|
||||
...state,
|
||||
|
|
|
@ -5,7 +5,15 @@ const initialState = {
|
|||
ranking: 1488,
|
||||
dailyRanking: 1488,
|
||||
// global stats
|
||||
online: 1,
|
||||
/*
|
||||
* {
|
||||
* total: totalUsersOnline,
|
||||
* canvasId: onlineAtCanvas,
|
||||
* }
|
||||
*/
|
||||
online: {
|
||||
total: 0,
|
||||
},
|
||||
totalRanking: {},
|
||||
totalDailyRanking: {},
|
||||
};
|
||||
|
|
|
@ -11,7 +11,13 @@ import PixelUpdate from './packets/PixelUpdateServer';
|
|||
class SocketEvents extends EventEmitter {
|
||||
constructor() {
|
||||
super();
|
||||
this.onlineCounter = 0;
|
||||
/*
|
||||
* {
|
||||
* canvasId: onlineUsers,
|
||||
* ...
|
||||
* }
|
||||
*/
|
||||
this.onlineCounter = {};
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -144,7 +150,7 @@ class SocketEvents extends EventEmitter {
|
|||
*/
|
||||
broadcastOnlineCounter(online: number) {
|
||||
this.onlineCounter = online;
|
||||
const buffer = OnlineCounter.dehydrate({ online });
|
||||
const buffer = OnlineCounter.dehydrate(online);
|
||||
this.emit('broadcast', buffer);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
import WebSocket from 'ws';
|
||||
|
||||
import logger from '../core/logger';
|
||||
import canvases from '../canvases.json';
|
||||
import Counter from '../utils/Counter';
|
||||
import { getIPFromRequest } from '../utils/ip';
|
||||
|
||||
|
@ -82,11 +83,7 @@ class SocketServer {
|
|||
ws.name = user.getName();
|
||||
cheapDetector(user.ip);
|
||||
|
||||
ws.send(OnlineCounter.dehydrate({
|
||||
online: ipCounter.amount() || 0,
|
||||
}));
|
||||
|
||||
const ip = getIPFromRequest(req);
|
||||
ws.send(OnlineCounter.dehydrate(socketEvents.onlineCounter));
|
||||
|
||||
ws.on('error', (e) => {
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -107,8 +107,8 @@ export default ({
|
|||
externals: [
|
||||
/\/proxies\.json$/,
|
||||
/\/canvases\.json$/,
|
||||
/^\.\/styleassets\.json$/,
|
||||
/^\.\/assets\.json$/,
|
||||
/\/styleassets\.json$/,
|
||||
/\/assets\.json$/,
|
||||
nodeExternals(),
|
||||
],
|
||||
|
||||
|
|
Loading…
Reference in New Issue