@casual-simulation/aux-common
Version:
Common library for AUX projects
329 lines • 13.6 kB
JavaScript
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