UNPKG

@blockv/sdk

Version:

Allows web apps to display and interact with vatoms.

136 lines (92 loc) 3.83 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; /** * Singleton. Responsible for storing and exeucting changes to objects over time. */ var _default = new class DataObjectAnimator { /** Constructor */ constructor() { // Store regions this.regions = []; // List of changes this.changes = []; // Update timer this.updateTimer = null; // Time skew, for syncing server time to client time. This time is added to the device's "current time". this.timeSkew = 0; // Add listeners for the WebSocket this.onWebSocketMessage = this.onWebSocketMessage.bind(this); // window.blockv.WebSockets.addEventListener('websocket.raw', this.onWebSocketMessage) } /** Called when a new message comes down the WebSocket */ onWebSocketMessage(msg) { // We only handle state update messages here. if (msg.msg_type != 'state_update') return; // Only handle brain updates if (msg.payload.action_name != 'brain-update') return; // Check if the brain has given us a set of next positions let nextPositions = msg.payload.new_object.next_positions; if (nextPositions) { // Map coordinates to sparse object updates let updates = nextPositions.map(p => { return { id: msg.payload.id, time: p.time, new_data: { 'vAtom::vAtomType': { 'geo_pos': { 'coordinates': p.geo_pos } } } }; }); // Fetch earliest time let earliestTime = updates[0].time; for (let update of updates) if (earliestTime > update.time) earliestTime = update.time; // Clear old data from animator this.clearUpdatesFor(msg.payload.id, earliestTime); // Hand off to the animator this.add(updates); } } /** Add a region */ addRegion(region) { this.regions.push(region); } /** Remove a region */ removeRegion(region) { this.regions = this.regions.filter(r => r != region); } /** Check if any of our regions has this data object */ isMonitoringID(id) { for (let region of this.regions) if (region.objects.get(id)) return true; // No, we don't care about this object return false; } /** Add updates to be executed */ add(updates) { // Add updates to the array let now = Date.now() + this.timeSkew; for (let u of updates) { // Ensure we care about this object if (!this.isMonitoringID(u.id)) continue; // Ensure this time entry has not passed already if (!u.time || u.time < now) continue; // Add it this.changes.push(u); } // Sort changes oldest to newest this.changes.sort((a, b) => a.time - b.time); // Start update timer if needed if (!this.updateTimer) this.updateTimer = setInterval(this.doNextUpdate.bind(this), 50); } /** Remove pending updates for the specified object ID */ clearUpdatesFor(id, afterTime = 0) { // Remove items for (let i = 0; i < this.changes.length; i++) if (this.changes[i].id == id && this.changes[i].time > afterTime) this.changes.splice(i--, 1); } /** @private Run the next update */ doNextUpdate() { // Stop if no more entries if (this.changes.length == 0) { clearInterval(this.updateTimer); this.updateTimer = null; return; } // Check if the first entry has passed yet let now = Date.now() + this.timeSkew; if (this.changes[0].time > now) return; // Get change to execute let change = this.changes.shift(); // Do it on all regions for (let region of this.regions) region.updateObjects([change]); // If next entry time has also passed already, don't wait, just execute if (this.changes.length > 0 && this.changes[0].time < now) this.doNextUpdate(); } }(); exports.default = _default;