@viewar/call
Version:
ViewAR Call
188 lines (157 loc) • 4.76 kB
JavaScript
import cloneDeep from 'lodash/cloneDeep';
import isArray from 'lodash/isArray';
import isEqual from 'lodash/isEqual';
import isObject from 'lodash/isObject';
import transform from 'lodash/transform';
import { log } from '../utils';
const LOG_SCENE_STATES = false;
function cloneWithRoundedFloats(obj) {
return transform(
obj,
(r, v, k) => {
if (isArray(v)) {
r[k] = v.map(cloneWithRoundedFloats);
} else if (isObject(v)) {
r[k] = cloneWithRoundedFloats(v);
} else {
r[k] = roundFloats(v);
}
},
{}
);
}
function roundFloats(value, decimals = 4) {
// necessary until the backend floating point precision is fixed
if (typeof value === 'number' && !Number.isInteger(value)) {
const multiplicator = Math.pow(10, decimals);
return Math.round(value * multiplicator) / multiplicator;
} else {
return value;
}
}
/**
* Syncs the scene state.
* Save timestamp for each update and send to remote client.
* Drop incoming states if own timestamp is newer.
*/
const createSyncManager = (viewarApi, sendData, getData) => {
let interval;
let started = false;
let updateInProgress = false;
let mySceneState = null;
let myTimestamp = null;
let syncSubscription;
let unprocessedSceneState = null;
let unprocessedTimestamp = null;
const start = async () => {
mySceneState = await viewarApi.sceneManager.getSceneStateSafe();
myTimestamp = getTimestamp();
syncSubscription = getData('sceneState').subscribe(updateSceneState);
started = true;
interval = setInterval(tick, 250);
};
const stop = () => {
if (started) {
syncSubscription.unsubscribe();
}
started = false;
clearInterval(interval);
};
/**
* Gets the server timestamp.
* See https://www.nodeguy.com/serverdate/.
*/
const getTimestamp = () => {
return Date.now();
};
const tick = async () => {
if (updateInProgress || !started) {
return;
}
if (unprocessedSceneState) {
// Set (last) queued scene state.
updateInProgress = true;
myTimestamp = unprocessedTimestamp;
mySceneState = cloneDeep(unprocessedSceneState);
log(
'setting previously queued scene state... ' + myTimestamp,
mySceneState
);
logSceneState(mySceneState, myTimestamp);
try {
await viewarApi.sceneManager.setSceneState(mySceneState);
} catch (e) {
console.error(`Could not update scene state: ${e.message}`, e);
}
unprocessedSceneState = null;
// log('setting is done', myTimestamp);
updateInProgress = false;
return;
}
const timestamp = getTimestamp();
const newSceneState = cloneWithRoundedFloats(
viewarApi.sceneManager.getSceneState()
);
if (timestamp > myTimestamp && !isEqual(mySceneState, newSceneState)) {
mySceneState = newSceneState;
myTimestamp = timestamp;
logSceneState(mySceneState, myTimestamp);
log('send scene state ' + myTimestamp, mySceneState);
sendData('sceneState', {
sceneState: mySceneState,
timestamp: myTimestamp,
});
}
};
const updateSceneState = async ({ timestamp, sceneState }) => {
if (sceneState) {
if (updateInProgress) {
if (timestamp > unprocessedTimestamp) {
unprocessedSceneState = sceneState;
unprocessedTimestamp = timestamp;
log('update already in progress, queue incoming.');
} else {
log(
'update already in progress, dropping incoming scene state since own state is newer.'
);
}
return;
}
if (isEqual(mySceneState, sceneState)) {
// TODO: Check if necessary.
log('dropping incoming scene state, already equal.');
return;
}
if (timestamp < myTimestamp) {
log('dropping incoming scene state, timestamp is older.');
return;
}
updateInProgress = true;
logSceneState(sceneState, timestamp);
log('setting new scene state... ' + timestamp, sceneState);
try {
await viewarApi.sceneManager.setSceneState(sceneState);
} catch (e) {
console.error(`Could not update scene state: ${e.message}`, e);
}
// log('set scene state is done.', timestamp);
myTimestamp = timestamp;
mySceneState = sceneState;
updateInProgress = false;
}
};
const logSceneState = (sceneState, timestamp) => {
if (LOG_SCENE_STATES) {
window.states = window.states || {};
window.states[timestamp] = cloneDeep(sceneState);
}
};
return {
start,
stop,
get started() {
return started;
},
};
};
export default createSyncManager;