UNPKG

@casual-simulation/aux-common

Version:
329 lines 13.6 kB
import { hasValue, getActiveObjects, breakIntoIndividualEvents, stateUpdatedEvent, } from '../bots'; import { BehaviorSubject, Subject } from 'rxjs'; import { startWith } from 'rxjs/operators'; import { union } from 'es-toolkit/compat'; import { merge } from '../utils'; import { applyTagEdit, edits, isTagEdit } from '../bots'; import { v4 as uuid } from 'uuid'; import { ensureBotIsSerializable, ensureTagIsSerializable, } from './PartitionUtils'; /** * Attempts to create a MemoryPartition from the given config. * @param config The config. */ export function createMemoryPartition(config) { if (config.type === 'memory') { if ('initialState' in config) { return new MemoryPartitionImpl(config); } else { return config.partition; } } return undefined; } export class MemoryPartitionImpl { get realtimeStrategy() { return 'immediate'; } get onBotsAdded() { return this._onBotsAdded.pipe(startWith(getActiveObjects(this.state))); } get onBotsRemoved() { return this._onBotsRemoved; } get onBotsUpdated() { return this._onBotsUpdated; } get onStateUpdated() { return this._onStateUpdated.pipe(startWith(stateUpdatedEvent(this.state, this._getCurrentVersion(null)))); } get onVersionUpdated() { return this._onVersionUpdated; } get onError() { return this._onError; } get onEvents() { return this._onEvents; } get onStatusUpdated() { return this._onStatusUpdated; } constructor(config) { var _a, _b; this._onBotsAdded = new Subject(); this._onBotsRemoved = new Subject(); this._onBotsUpdated = new Subject(); this._onStateUpdated = new Subject(); this._onError = new Subject(); this._onEvents = new Subject(); this._onStatusUpdated = new Subject(); this._updateCounter = 0; this.type = 'memory'; this._siteId = (_a = config.localSiteId) !== null && _a !== void 0 ? _a : uuid(); this._remoteSite = (_b = config.remoteSiteId) !== null && _b !== void 0 ? _b : uuid(); this.private = config.private || false; this.state = config.initialState; this._onVersionUpdated = new BehaviorSubject({ currentSite: this._siteId, remoteSite: this._remoteSite, vector: {}, }); } async applyEvents(events) { let finalEvents = events.flatMap((e) => { if (e.type === 'apply_state') { return breakIntoIndividualEvents(this.state, e); } else if (e.type === 'add_bot' || e.type === 'remove_bot' || e.type === 'update_bot') { return [e]; } else { return []; } }); this._applyEvents(finalEvents); return []; } connect() { if (this.space) { for (let id in this.state) { const bot = this.state[id]; bot.space = this.space; } } this._onStatusUpdated.next({ type: 'connection', connected: true, }); this._onStatusUpdated.next({ type: 'authentication', authenticated: true, }); this._onStatusUpdated.next({ type: 'authorization', authorized: true, }); this._onStatusUpdated.next({ type: 'sync', synced: true, }); } unsubscribe() { this.closed = true; } _applyEvents(events) { var _a, _b; let added = new Map(); let removed = []; let updated = new Map(); let updatedState = {}; let nextVersion; // Flag to record if we have already created a new state object // during the update. let createdNewState = false; for (let event of events) { if (event.type === 'add_bot') { // console.log('[MemoryPartition] Add bot', event.bot); let bot = { ...ensureBotIsSerializable(event.bot), space: this.space, }; if (createdNewState) { this.state[event.bot.id] = bot; } else { this.state = Object.assign({}, this.state, { [event.bot.id]: bot, }); createdNewState = true; } updatedState[event.bot.id] = bot; added.set(event.bot.id, bot); } else if (event.type === 'remove_bot') { if (createdNewState) { delete this.state[event.id]; } else { let { [event.id]: removedBot, ...state } = this.state; this.state = state; createdNewState = true; } if (!added.delete(event.id)) { removed.push(event.id); } updatedState[event.id] = null; } else if (event.type === 'update_bot') { if (event.update.tags && this.state[event.id]) { let newBot = Object.assign({}, this.state[event.id]); let changedTags = []; let lastBot = updatedState[event.id]; const updatedBot = (updatedState[event.id] = merge(updatedState[event.id] || {}, event.update)); for (let tag of Object.keys(event.update.tags)) { const newVal = ensureTagIsSerializable(event.update.tags[tag]); const oldVal = newBot.tags[tag]; if ((newVal !== oldVal && (hasValue(newVal) || hasValue(oldVal))) || Array.isArray(newVal)) { changedTags.push(tag); } if (hasValue(newVal)) { if (isTagEdit(newVal)) { newBot.tags[tag] = applyTagEdit(newBot.tags[tag], newVal); nextVersion = this.getNextVersion(newVal); let combinedEdits = []; if (lastBot) { const lastVal = lastBot.tags[tag]; if (lastVal !== oldVal && isTagEdit(lastVal)) { combinedEdits = lastVal.operations; } } updatedBot.tags[tag] = edits(nextVersion.vector, ...combinedEdits, ...newVal.operations); } else { newBot.tags[tag] = newVal; updatedBot.tags[tag] = newVal; } if (!hasValue(newBot.tags[tag])) { delete newBot.tags[tag]; } } else if (hasValue(oldVal)) { delete newBot.tags[tag]; updatedBot.tags[tag] = null; } else { delete newBot.tags[tag]; delete updatedBot.tags[tag]; } } this.state[event.id] = newBot; let update = updated.get(event.id); if (update) { update.bot = newBot; update.tags = union(update.tags, changedTags); } else if (changedTags.length > 0) { updated.set(event.id, { bot: newBot, tags: changedTags, }); } else if (!added.has(event.id)) { delete updatedState[event.id]; } } if (event.update.masks && event.update.masks[this.space]) { const tags = event.update.masks[this.space]; let newBot = Object.assign({}, this.state[event.id]); if (!newBot.masks) { newBot.masks = {}; } if (!newBot.masks[this.space]) { newBot.masks[this.space] = {}; } let lastMasks = (_b = (_a = updatedState[event.id]) === null || _a === void 0 ? void 0 : _a.masks) === null || _b === void 0 ? void 0 : _b[this.space]; const masks = newBot.masks[this.space]; const updatedBot = (updatedState[event.id] = merge(updatedState[event.id] || {}, event.update)); let changedTags = []; for (let tag in tags) { const newVal = ensureTagIsSerializable(tags[tag]); const oldVal = masks[tag]; if (newVal !== oldVal) { changedTags.push(tag); } if (hasValue(newVal)) { if (isTagEdit(newVal)) { masks[tag] = applyTagEdit(masks[tag], newVal); nextVersion = this.getNextVersion(newVal); let combinedEdits = []; if (lastMasks) { const lastVal = lastMasks[tag]; if (lastVal !== oldVal && isTagEdit(lastVal)) { combinedEdits = lastVal.operations; } } updatedBot.masks[this.space][tag] = edits(nextVersion.vector, ...combinedEdits, ...newVal.operations); } else { masks[tag] = newVal; if (newVal !== tags[tag]) { updatedBot.masks[this.space][tag] = newVal; } } } else { delete masks[tag]; updatedBot.masks[this.space][tag] = null; } } if (newBot.masks) { for (let space in event.update.masks) { for (let tag in event.update.masks[this.space]) { if (newBot.masks[space][tag] === null) { delete newBot.masks[space][tag]; } } if (Object.keys(newBot.masks[space]).length <= 0) { delete newBot.masks[space]; } } if (!!newBot.masks && Object.keys(newBot.masks).length <= 0) { delete newBot.masks; } } this.state[event.id] = newBot; } const updatedBot = updatedState[event.id]; if ((updatedBot === null || updatedBot === void 0 ? void 0 : updatedBot.tags) && Object.keys(updatedBot.tags).length <= 0) { delete updatedBot.tags; } } } if (added.size > 0) { this._onBotsAdded.next([...added.values()]); } if (removed.length > 0) { this._onBotsRemoved.next(removed); } if (updated.size > 0) { this._onBotsUpdated.next([...updated.values()]); } const updateEvent = stateUpdatedEvent(updatedState, this._getCurrentVersion(nextVersion)); if (updateEvent.addedBots.length > 0 || updateEvent.removedBots.length > 0 || updateEvent.updatedBots.length > 0) { this._onStateUpdated.next(updateEvent); } if (nextVersion) { this._onVersionUpdated.next(nextVersion); } } _getCurrentVersion(nextVersion) { return this.getCurrentVersion ? this.getCurrentVersion() : nextVersion !== null && nextVersion !== void 0 ? nextVersion : this._onVersionUpdated.value; } /** * Gets the version that should be used for the resulting edit when the given text edit is applied. */ getNextVersion(textEdit) { return { currentSite: this._onVersionUpdated.value.currentSite, remoteSite: this._onVersionUpdated.value.remoteSite, vector: { ...this._onVersionUpdated.value.vector, [textEdit.isRemote ? this._remoteSite : this._siteId]: (this._updateCounter += 1), }, }; } } //# sourceMappingURL=MemoryPartition.js.map