UNPKG

@viewar/call

Version:

ViewAR Call

188 lines (157 loc) 4.76 kB
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;