feat: throttling configuration
This commit is contained in:
parent
409681b217
commit
ca47c41c69
@ -4,7 +4,7 @@ backend:
|
|||||||
# TODO
|
# TODO
|
||||||
webdav: false
|
webdav: false
|
||||||
performance:
|
performance:
|
||||||
# Whiteboard information broadcasting frequency (in /s)
|
# Whiteboard information broadcasting frequency (in Hz i.e. /s)
|
||||||
# => diminishing this will result in more latency
|
# => diminishing this will result in more latency
|
||||||
whiteboardInfoBroadcastFreq: 1
|
whiteboardInfoBroadcastFreq: 1
|
||||||
|
|
||||||
@ -15,7 +15,8 @@ frontend:
|
|||||||
# Show smallest screen indicator
|
# Show smallest screen indicator
|
||||||
showSmallestScreenIndicator: true
|
showSmallestScreenIndicator: true
|
||||||
performance:
|
performance:
|
||||||
pointerEventsThreshold:
|
pointerEventsThrottling:
|
||||||
minDistDelta: 1
|
- fromNbUser: 0
|
||||||
minTimeDelta: 33
|
minDistDelta: 1
|
||||||
|
maxFreq: 30
|
||||||
refreshInfoFreq: 5
|
refreshInfoFreq: 5
|
||||||
|
@ -41,20 +41,28 @@
|
|||||||
"performance": {
|
"performance": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"required": ["pointerEventsThreshold", "refreshInfoFreq"],
|
"required": ["pointerEventsThrottling", "refreshInfoFreq"],
|
||||||
"properties": {
|
"properties": {
|
||||||
"pointerEventsThreshold": {
|
"pointerEventsThrottling": {
|
||||||
"type": "object",
|
"type": "array",
|
||||||
"additionalProperties": false,
|
"minItems": 1,
|
||||||
"required": ["minDistDelta", "minTimeDelta"],
|
"items": {
|
||||||
"properties": {
|
"type": "object",
|
||||||
"minDistDelta": {
|
"additionalProperties": false,
|
||||||
"type": "number",
|
"required": ["fromNbUser", "minDistDelta", "maxFreq"],
|
||||||
"minimum": 0
|
"properties": {
|
||||||
},
|
"fromNbUser": {
|
||||||
"minTimeDelta": {
|
"type": "number",
|
||||||
"type": "number",
|
"minimum": 0
|
||||||
"minimum": 0
|
},
|
||||||
|
"minDistDelta": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0
|
||||||
|
},
|
||||||
|
"maxFreq": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -44,11 +44,28 @@ function getConfig(path) {
|
|||||||
*/
|
*/
|
||||||
function isConfigValid(config, warn = true) {
|
function isConfigValid(config, warn = true) {
|
||||||
const validate = ajv.compile(configSchema);
|
const validate = ajv.compile(configSchema);
|
||||||
const isValid = validate(config);
|
const isValidAgainstSchema = validate(config);
|
||||||
|
|
||||||
if (!isValid && warn) console.warn(validate.errors);
|
if (!isValidAgainstSchema && warn) console.warn(validate.errors);
|
||||||
|
|
||||||
return isValid;
|
let structureIsValid = false;
|
||||||
|
try {
|
||||||
|
structureIsValid = config.frontend.performance.pointerEventsThrottling.some(
|
||||||
|
(item) => item.fromNbUser === 0
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
if (!e instanceof TypeError) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!structureIsValid && warn)
|
||||||
|
console.warn(
|
||||||
|
"At least one item under frontend.performance.pointerEventsThrottling" +
|
||||||
|
"must have fromNbUser set to 0"
|
||||||
|
);
|
||||||
|
|
||||||
|
return isValidAgainstSchema && structureIsValid;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,4 +1,12 @@
|
|||||||
|
import { getThrottling } from "./ConfigService.utils";
|
||||||
|
|
||||||
class ConfigService {
|
class ConfigService {
|
||||||
|
/**
|
||||||
|
* @type {object}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_configFromServer = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @readonly
|
* @readonly
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
@ -13,15 +21,9 @@ class ConfigService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @readonly
|
* @readonly
|
||||||
* @type {number}
|
* @type {{minDistDelta: number, minTimeDelta: number}}
|
||||||
*/
|
*/
|
||||||
pointerEventsThresholdMinDistDelta = 0;
|
pointerEventsThrottling = { minDistDelta: 0, minTimeDelta: 0 };
|
||||||
|
|
||||||
/**
|
|
||||||
* @readonly
|
|
||||||
* @type {number}
|
|
||||||
*/
|
|
||||||
pointerEventsThresholdMinTimeDelta = 0;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @readonly
|
* @readonly
|
||||||
@ -32,19 +34,31 @@ class ConfigService {
|
|||||||
/**
|
/**
|
||||||
* Init the service from the config sent by the server
|
* Init the service from the config sent by the server
|
||||||
*
|
*
|
||||||
* @param {object} serverResponse
|
* @param {object} configFromServer
|
||||||
*/
|
*/
|
||||||
initFromServer(serverResponse) {
|
initFromServer(configFromServer) {
|
||||||
const { common } = serverResponse;
|
this._configFromServer = configFromServer;
|
||||||
|
|
||||||
|
const { common } = configFromServer;
|
||||||
const { readOnlyOnWhiteboardLoad, showSmallestScreenIndicator, performance } = common;
|
const { readOnlyOnWhiteboardLoad, showSmallestScreenIndicator, performance } = common;
|
||||||
|
|
||||||
this.readOnlyOnWhiteboardLoad = readOnlyOnWhiteboardLoad;
|
this.readOnlyOnWhiteboardLoad = readOnlyOnWhiteboardLoad;
|
||||||
this.showSmallestScreenIndicator = showSmallestScreenIndicator;
|
this.showSmallestScreenIndicator = showSmallestScreenIndicator;
|
||||||
this.pointerEventsThresholdMinDistDelta = performance.pointerEventsThreshold.minDistDelta;
|
|
||||||
this.pointerEventsThresholdMinTimeDelta = performance.pointerEventsThreshold.minTimeDelta;
|
|
||||||
this.refreshInfoInterval = 1000 / performance.refreshInfoFreq;
|
this.refreshInfoInterval = 1000 / performance.refreshInfoFreq;
|
||||||
|
|
||||||
console.log("Whiteboard config from server:", serverResponse, "parsed:", this);
|
console.log("Whiteboard config from server:", configFromServer, "parsed:", this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO
|
||||||
|
*/
|
||||||
|
refreshNbUserDependant(nbUser) {
|
||||||
|
const { _configFromServer } = this;
|
||||||
|
const { common } = _configFromServer;
|
||||||
|
const { performance } = common;
|
||||||
|
const { pointerEventsThrottling } = performance;
|
||||||
|
|
||||||
|
this.pointerEventsThrottling = getThrottling(pointerEventsThrottling, nbUser);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
21
src/js/services/ConfigService.utils.js
Normal file
21
src/js/services/ConfigService.utils.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* Helper to extract the correct correct throttling values based on the config and the number of user
|
||||||
|
*
|
||||||
|
* @param {Array.<{fromNbUser: number, minDistDelta: number, maxFreq: number}>} pointerEventsThrottling
|
||||||
|
* @param {number} nbUser
|
||||||
|
* @return {{minDistDelta: number, minTimeDelta: number}}
|
||||||
|
*/
|
||||||
|
export function getThrottling(pointerEventsThrottling, nbUser) {
|
||||||
|
let tmpOut = pointerEventsThrottling[0];
|
||||||
|
let lastDistToNbUser = nbUser - tmpOut.fromNbUser;
|
||||||
|
if (lastDistToNbUser < 0) lastDistToNbUser = Number.MAX_VALUE;
|
||||||
|
for (const el of pointerEventsThrottling) {
|
||||||
|
const distToNbUser = nbUser - el.fromNbUser;
|
||||||
|
if (el.fromNbUser <= nbUser && distToNbUser <= lastDistToNbUser) {
|
||||||
|
tmpOut = el;
|
||||||
|
lastDistToNbUser = distToNbUser;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { minDistDelta: tmpOut.minDistDelta, minTimeDelta: 1000 * (1 / tmpOut.maxFreq) };
|
||||||
|
}
|
29
src/js/services/ConfigService.utils.test.js
Normal file
29
src/js/services/ConfigService.utils.test.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { getThrottling } from "./ConfigService.utils";
|
||||||
|
|
||||||
|
test("Simple throttling config", () => {
|
||||||
|
const throttling = [{ fromNbUser: 0, minDistDelta: 1, maxFreq: 1 }];
|
||||||
|
|
||||||
|
const target0 = { minDistDelta: 1, minTimeDelta: 1000 };
|
||||||
|
expect(getThrottling(throttling, 0)).toEqual(target0);
|
||||||
|
|
||||||
|
const target100 = { minDistDelta: 1, minTimeDelta: 1000 };
|
||||||
|
expect(getThrottling(throttling, 100)).toEqual(target100);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Complex throttling config", () => {
|
||||||
|
// mix ordering
|
||||||
|
const throttling = [
|
||||||
|
{ fromNbUser: 100, minDistDelta: 100, maxFreq: 1 },
|
||||||
|
{ fromNbUser: 0, minDistDelta: 1, maxFreq: 1 },
|
||||||
|
{ fromNbUser: 50, minDistDelta: 50, maxFreq: 1 },
|
||||||
|
];
|
||||||
|
|
||||||
|
const target0 = { minDistDelta: 1, minTimeDelta: 1000 };
|
||||||
|
expect(getThrottling(throttling, 0)).toEqual(target0);
|
||||||
|
|
||||||
|
const target50 = { minDistDelta: 50, minTimeDelta: 1000 };
|
||||||
|
expect(getThrottling(throttling, 50)).toEqual(target50);
|
||||||
|
|
||||||
|
const target100 = { minDistDelta: 100, minTimeDelta: 1000 };
|
||||||
|
expect(getThrottling(throttling, 100)).toEqual(target100);
|
||||||
|
});
|
@ -14,9 +14,9 @@ class InfoService {
|
|||||||
* Holds the number of user connected to the server
|
* Holds the number of user connected to the server
|
||||||
*
|
*
|
||||||
* @type {number}
|
* @type {number}
|
||||||
* @private
|
* @readonly
|
||||||
*/
|
*/
|
||||||
_nbConnectedUsers = 0;
|
nbConnectedUsers = -1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -49,7 +49,11 @@ class InfoService {
|
|||||||
* @param {{w: number, h: number}} smallestScreenResolution
|
* @param {{w: number, h: number}} smallestScreenResolution
|
||||||
*/
|
*/
|
||||||
updateInfoFromServer({ nbConnectedUsers, smallestScreenResolution = undefined }) {
|
updateInfoFromServer({ nbConnectedUsers, smallestScreenResolution = undefined }) {
|
||||||
this._nbConnectedUsers = nbConnectedUsers;
|
if (this.nbConnectedUsers !== nbConnectedUsers) {
|
||||||
|
// Refresh config service parameters on nb connected user change
|
||||||
|
ConfigService.refreshNbUserDependant(nbConnectedUsers);
|
||||||
|
}
|
||||||
|
this.nbConnectedUsers = nbConnectedUsers;
|
||||||
if (smallestScreenResolution) {
|
if (smallestScreenResolution) {
|
||||||
this._smallestScreenResolution = smallestScreenResolution;
|
this._smallestScreenResolution = smallestScreenResolution;
|
||||||
}
|
}
|
||||||
@ -73,7 +77,7 @@ class InfoService {
|
|||||||
refreshDisplayedInfo() {
|
refreshDisplayedInfo() {
|
||||||
$("#messageReceivedCount")[0].innerText = String(this._nbMessagesReceived);
|
$("#messageReceivedCount")[0].innerText = String(this._nbMessagesReceived);
|
||||||
$("#messageSentCount")[0].innerText = String(this._nbMessagesSent);
|
$("#messageSentCount")[0].innerText = String(this._nbMessagesSent);
|
||||||
$("#connectedUsersCount")[0].innerText = String(this._nbConnectedUsers);
|
$("#connectedUsersCount")[0].innerText = String(this.nbConnectedUsers);
|
||||||
const { _smallestScreenResolution: ssr } = this;
|
const { _smallestScreenResolution: ssr } = this;
|
||||||
$("#smallestScreenResolution")[0].innerText = ssr ? `(${ssr.w}, ${ssr.h})` : "Unknown";
|
$("#smallestScreenResolution")[0].innerText = ssr ? `(${ssr.w}, ${ssr.h})` : "Unknown";
|
||||||
}
|
}
|
||||||
|
@ -25,10 +25,10 @@ class ThrottlingService {
|
|||||||
throttle(newPosition, onSuccess) {
|
throttle(newPosition, onSuccess) {
|
||||||
const newTime = getCurrentTimeMs();
|
const newTime = getCurrentTimeMs();
|
||||||
const { _lastPointPosition, _lastSuccessTime } = this;
|
const { _lastPointPosition, _lastSuccessTime } = this;
|
||||||
if (newTime - _lastSuccessTime > ConfigService.pointerEventsThresholdMinTimeDelta) {
|
if (newTime - _lastSuccessTime > ConfigService.pointerEventsThrottling.minTimeDelta) {
|
||||||
if (
|
if (
|
||||||
_lastPointPosition.distTo(newPosition) >
|
_lastPointPosition.distTo(newPosition) >
|
||||||
ConfigService.pointerEventsThresholdMinDistDelta
|
ConfigService.pointerEventsThrottling.minDistDelta
|
||||||
) {
|
) {
|
||||||
onSuccess();
|
onSuccess();
|
||||||
this._lastPointPosition = newPosition;
|
this._lastPointPosition = newPosition;
|
||||||
|
Loading…
Reference in New Issue
Block a user