mirror of
https://github.com/giongto35/cloud-game.git
synced 2026-07-25 02:57:24 +00:00
These modules should be supported by all contemporary browsers, and this transition should resolve most issues related to the explicit import order of the .js files.
440 lines
10 KiB
JavaScript
440 lines
10 KiB
JavaScript
import {env} from 'env';
|
|
import {
|
|
pub,
|
|
sub,
|
|
STATS_TOGGLE,
|
|
HELP_OVERLAY_TOGGLED,
|
|
PING_REQUEST,
|
|
PING_RESPONSE
|
|
} from 'event';
|
|
import {log} from 'log';
|
|
import {webrtc} from 'network';
|
|
|
|
const _modules = [];
|
|
let tempHide = false;
|
|
|
|
// internal rendering stuff
|
|
const fps = 30;
|
|
let time = 0;
|
|
let active = false;
|
|
|
|
// !to add connection drop notice
|
|
|
|
const statsOverlayEl = document.getElementById('stats-overlay');
|
|
|
|
/**
|
|
* The graph element.
|
|
*/
|
|
const graph = (parent, opts = {
|
|
historySize: 60,
|
|
width: 60 * 2 + 2,
|
|
height: 20,
|
|
pad: 4,
|
|
scale: 1,
|
|
style: {
|
|
barColor: '#9bd914',
|
|
barFallColor: '#c12604'
|
|
}
|
|
}) => {
|
|
const _canvas = document.createElement('canvas');
|
|
const _context = _canvas.getContext('2d');
|
|
|
|
let data = [];
|
|
|
|
_canvas.setAttribute('class', 'graph');
|
|
|
|
_canvas.width = opts.width * opts.scale;
|
|
_canvas.height = opts.height * opts.scale;
|
|
|
|
_context.scale(opts.scale, opts.scale);
|
|
_context.imageSmoothingEnabled = false;
|
|
_context.fillStyle = opts.fillStyle;
|
|
|
|
if (parent) parent.append(_canvas);
|
|
|
|
// bar size
|
|
const barWidth = Math.round(_canvas.width / opts.scale / opts.historySize);
|
|
const barHeight = Math.round(_canvas.height / opts.scale);
|
|
|
|
let maxN = 0,
|
|
minN = 0;
|
|
|
|
const max = () => maxN
|
|
|
|
const get = () => _canvas
|
|
|
|
const add = (value) => {
|
|
if (data.length > opts.historySize) data.shift();
|
|
data.push(value);
|
|
render();
|
|
}
|
|
|
|
/**
|
|
* Draws a bar graph on the canvas.
|
|
*/
|
|
const render = () => {
|
|
// 0,0 w,0 0,0 w,0 0,0 w,0
|
|
// +-------+ +-------+ +---------+
|
|
// | | |+---+ | |+---+ |
|
|
// | | |||||| | ||||||+---+
|
|
// | | |||||| | |||||||||||
|
|
// +-------+ +----+--+ +---------+
|
|
// 0,h w,h 0,h w,h 0,h w,h
|
|
// [] [3] [3, 2]
|
|
//
|
|
|
|
_context.clearRect(0, 0, _canvas.width, _canvas.height);
|
|
|
|
maxN = data[0] || 1;
|
|
minN = 0;
|
|
for (let k = 1; k < data.length; k++) {
|
|
if (data[k] > maxN) maxN = data[k];
|
|
if (data[k] < minN) minN = data[k];
|
|
}
|
|
|
|
for (let j = 0; j < data.length; j++) {
|
|
let x = j * barWidth,
|
|
y = (barHeight - opts.pad * 2) * (data[j] - minN) / (maxN - minN) + opts.pad;
|
|
|
|
const color = j > 0 && data[j] > data[j - 1] ? opts.style.barFallColor : opts.style.barColor;
|
|
|
|
drawRect(x, barHeight - Math.round(y), barWidth, barHeight, color);
|
|
}
|
|
}
|
|
|
|
const drawRect = (x, y, w, h, color = opts.style.barColor) => {
|
|
_context.fillStyle = color;
|
|
_context.fillRect(x, y, w, h);
|
|
}
|
|
|
|
return {add, get, max, render}
|
|
}
|
|
|
|
/**
|
|
* Get cached module UI.
|
|
*
|
|
* HTML:
|
|
* <div><div>LABEL</div><span>VALUE</span>[<span><canvas/><span>]</div>
|
|
*
|
|
* @param label The name of the stat to show.
|
|
* @param withGraph True if to draw a graph.
|
|
* @param postfix Supposed to be the name of the stat passed as a function.
|
|
* @returns {{el: HTMLDivElement, update: function}}
|
|
*/
|
|
const moduleUi = (label = '', withGraph = false, postfix = () => 'ms') => {
|
|
const ui = document.createElement('div'),
|
|
_label = document.createElement('div'),
|
|
_value = document.createElement('span');
|
|
ui.append(_label, _value);
|
|
|
|
let postfix_ = postfix;
|
|
|
|
let _graph;
|
|
if (withGraph) {
|
|
const _container = document.createElement('span');
|
|
ui.append(_container);
|
|
_graph = graph(_container);
|
|
}
|
|
|
|
_label.innerHTML = label;
|
|
|
|
const withPostfix = (value) => postfix_ = value;
|
|
|
|
const update = (value) => {
|
|
if (_graph) _graph.add(value);
|
|
// 203 (333) ms
|
|
_value.textContent = `${value < 1 ? '<1' : value} ${_graph ? `(${_graph.max()}) ` : ''}${postfix_(value)}`;
|
|
}
|
|
|
|
return {el: ui, update, withPostfix}
|
|
}
|
|
|
|
/**
|
|
* Latency stats submodule.
|
|
*
|
|
* Accumulates the simple rolling mean value
|
|
* between the next server request and following server response values.
|
|
*
|
|
* window
|
|
* _____________
|
|
* | |
|
|
* [1, 1, 3, 4, 1, 4, 3, 1, 2, 1, 1, 1, 2, ... n]
|
|
* |
|
|
* stats_snapshot_period
|
|
* mean = round(next - mean / length % window)
|
|
*
|
|
* Events:
|
|
* <- PING_RESPONSE
|
|
* <- PING_REQUEST
|
|
*
|
|
* ?Interface:
|
|
* HTMLElement get()
|
|
* void enable()
|
|
* void disable()
|
|
* void render()
|
|
*
|
|
* @version 1
|
|
*/
|
|
const latency = (() => {
|
|
let listeners = [];
|
|
|
|
let mean = 0;
|
|
let length = 0;
|
|
let previous = 0;
|
|
const window = 5;
|
|
|
|
const ui = moduleUi('Ping(c)', true);
|
|
|
|
const onPingRequest = (data) => previous = data.time;
|
|
|
|
const onPingResponse = () => {
|
|
length++;
|
|
const delta = Date.now() - previous;
|
|
mean += Math.round((delta - mean) / length);
|
|
|
|
if (length % window === 0) {
|
|
length = 1;
|
|
mean = delta;
|
|
}
|
|
}
|
|
|
|
const enable = () => {
|
|
listeners.push(
|
|
sub(PING_RESPONSE, onPingResponse),
|
|
sub(PING_REQUEST, onPingRequest)
|
|
);
|
|
}
|
|
|
|
const disable = () => {
|
|
while (listeners.length) listeners.shift().unsub();
|
|
}
|
|
|
|
const render = () => ui.update(mean);
|
|
|
|
const get = () => ui.el;
|
|
|
|
return {get, enable, disable, render}
|
|
})(event, moduleUi);
|
|
|
|
/**
|
|
* User agent memory stats.
|
|
*
|
|
* ?Interface:
|
|
* HTMLElement get()
|
|
* void enable()
|
|
* void disable()
|
|
* void render()
|
|
*
|
|
* @version 1
|
|
*/
|
|
const clientMemory = (() => {
|
|
let active = false;
|
|
|
|
const measures = ['B', 'KB', 'MB', 'GB'];
|
|
const precision = 1;
|
|
let mLog = 0;
|
|
|
|
const ui = moduleUi('Memory', false, (x) => (x > 0) ? measures[mLog] : '');
|
|
|
|
const get = () => ui.el;
|
|
|
|
const enable = () => {
|
|
active = true;
|
|
render();
|
|
}
|
|
|
|
const disable = () => active = false;
|
|
|
|
const render = () => {
|
|
if (!active) return;
|
|
|
|
const m = performance.memory.usedJSHeapSize;
|
|
let newValue = 'N/A';
|
|
|
|
if (m > 0) {
|
|
mLog = Math.floor(Math.log(m) / Math.log(1000));
|
|
newValue = Math.round(m * precision / Math.pow(1000, mLog)) / precision;
|
|
}
|
|
|
|
ui.update(newValue);
|
|
}
|
|
|
|
if (window.performance && !performance.memory) performance.memory = {usedJSHeapSize: 0, totalJSHeapSize: 0};
|
|
|
|
return {get, enable, disable, render}
|
|
})(moduleUi, performance, window);
|
|
|
|
|
|
const webRTCStats_ = (() => {
|
|
let interval = null
|
|
|
|
function getStats() {
|
|
if (!webrtc.isConnected()) return;
|
|
webrtc.getConnection().getStats(null).then(stats => {
|
|
let frameStatValue = '?';
|
|
stats.forEach(report => {
|
|
if (report["framesReceived"] !== undefined && report["framesDecoded"] !== undefined && report["framesDropped"] !== undefined) {
|
|
frameStatValue = report["framesReceived"] - report["framesDecoded"] - report["framesDropped"];
|
|
pub('STATS_WEBRTC_FRAME_STATS', frameStatValue)
|
|
} else if (report["framerateMean"] !== undefined) {
|
|
frameStatValue = Math.round(report["framerateMean"] * 100) / 100;
|
|
pub('STATS_WEBRTC_FRAME_STATS', frameStatValue)
|
|
}
|
|
|
|
if (report["nominated"] && report["currentRoundTripTime"] !== undefined) {
|
|
pub('STATS_WEBRTC_ICE_RTT', report["currentRoundTripTime"] * 1000);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
const enable = () => {
|
|
interval = window.setInterval(getStats, 1000);
|
|
}
|
|
|
|
const disable = () => window.clearInterval(interval);
|
|
|
|
return {enable, disable, internal: true}
|
|
})(event, webrtc, window);
|
|
|
|
/**
|
|
* User agent frame stats.
|
|
*
|
|
* ?Interface:
|
|
* HTMLElement get()
|
|
* void enable()
|
|
* void disable()
|
|
* void render()
|
|
*
|
|
* @version 1
|
|
*/
|
|
const webRTCFrameStats = (() => {
|
|
let value = 0;
|
|
let listener;
|
|
|
|
const label = env.getBrowser() === 'firefox' ? 'FramerateMean' : 'FrameDelay';
|
|
const ui = moduleUi(label, false, () => '');
|
|
|
|
const get = () => ui.el;
|
|
|
|
const enable = () => {
|
|
listener = sub('STATS_WEBRTC_FRAME_STATS', onStats);
|
|
}
|
|
|
|
const disable = () => {
|
|
value = 0;
|
|
if (listener) listener.unsub();
|
|
}
|
|
|
|
const render = () => ui.update(value);
|
|
|
|
function onStats(val) {
|
|
value = val;
|
|
}
|
|
|
|
return {get, enable, disable, render}
|
|
})(env, event, moduleUi);
|
|
|
|
const webRTCRttStats = (() => {
|
|
let value = 0;
|
|
let listener;
|
|
|
|
const ui = moduleUi('RTT', true, () => 'ms');
|
|
|
|
const get = () => ui.el;
|
|
|
|
const enable = () => {
|
|
listener = sub('STATS_WEBRTC_ICE_RTT', onStats);
|
|
}
|
|
|
|
const disable = () => {
|
|
value = 0;
|
|
if (listener) listener.unsub();
|
|
}
|
|
|
|
const render = () => ui.update(value);
|
|
|
|
function onStats(val) {
|
|
value = val;
|
|
}
|
|
|
|
return {get, enable, disable, render}
|
|
})(event, moduleUi);
|
|
|
|
const modules = (fn, force = true) => _modules.forEach(m => (force || !m.internal) && fn(m))
|
|
|
|
const enable = () => {
|
|
active = true;
|
|
modules(m => m.enable())
|
|
render();
|
|
draw();
|
|
_show();
|
|
};
|
|
|
|
function draw(timestamp) {
|
|
if (!active) return;
|
|
|
|
const time_ = time + 1000 / fps;
|
|
|
|
if (timestamp > time_) {
|
|
time = timestamp;
|
|
render();
|
|
}
|
|
|
|
requestAnimationFrame(draw);
|
|
}
|
|
|
|
const disable = () => {
|
|
active = false;
|
|
modules(m => m.disable());
|
|
_hide();
|
|
}
|
|
|
|
const _show = () => statsOverlayEl.style.visibility = 'visible';
|
|
const _hide = () => statsOverlayEl.style.visibility = 'hidden';
|
|
|
|
const onToggle = () => active ? disable() : enable();
|
|
|
|
/**
|
|
* Handles help overlay toggle event.
|
|
* Workaround for a not normal app layout layering.
|
|
*
|
|
* !to remove when app layering is fixed
|
|
*
|
|
* @param {Object} overlay Overlay data.
|
|
* @param {boolean} overlay.shown A flag if the overlay is being currently showed.
|
|
*/
|
|
const onHelpOverlayToggle = (overlay) => {
|
|
if (statsOverlayEl.style.visibility === 'visible' && overlay.shown && !tempHide) {
|
|
_hide();
|
|
tempHide = true;
|
|
} else {
|
|
if (tempHide) {
|
|
_show();
|
|
tempHide = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
const render = () => modules(m => m.render(), false);
|
|
|
|
// add submodules
|
|
_modules.push(
|
|
webRTCRttStats,
|
|
// latency,
|
|
clientMemory,
|
|
webRTCStats_,
|
|
webRTCFrameStats
|
|
);
|
|
modules(m => statsOverlayEl.append(m.get()), false);
|
|
|
|
sub(STATS_TOGGLE, onToggle);
|
|
sub(HELP_OVERLAY_TOGGLED, onHelpOverlayToggle)
|
|
|
|
/**
|
|
* App statistics module.
|
|
*/
|
|
export const stats = {
|
|
enable,
|
|
disable
|
|
}
|