@casual-simulation/aux-runtime
Version:
Runtime for AUX projects
1,209 lines (1,208 loc) • 125 kB
JavaScript
/* CasualOS is a set of web-based tools designed to facilitate the creation of real-time, multi-user, context-aware interactive experiences.
*
* Copyright (c) 2019-2025 Casual Simulation, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { hasValue, tagsOnBot, isFormula, isScript, isNumber, BOT_SPACE_TAG, botUpdated, isBot, ORIGINAL_OBJECT, DEFAULT_ENERGY, getBotSpace, ON_ACTION_ACTION_NAME, breakIntoIndividualEvents, ON_BOT_ADDED_ACTION_NAME, ON_ANY_BOTS_ADDED_ACTION_NAME, ON_ANY_BOTS_REMOVED_ACTION_NAME, ON_BOT_CHANGED_ACTION_NAME, ON_ANY_BOTS_CHANGED_ACTION_NAME, updatedBot, TAG_MASK_SPACE_PRIORITIES, CLEAR_CHANGES_SYMBOL, DNA_TAG_PREFIX, isRuntimeBot, createBot, ON_ERROR, action, isBotInDimension, asyncResult, registerBuiltinPortal, defineGlobalBot, isBotLink, parseBotLink, isBotDate, parseBotDate, formatBotDate, isTaggedString, parseTaggedString, parseNumber, isTaggedNumber, isBotVector, parseBotVector, formatBotVector, isBotRotation, parseBotRotation, formatBotRotation, parseTaggedNumber, REPLACE_BOT_SYMBOL, isModule, calculateStringTagValue, ON_RESOLVE_MODULE, } from '@casual-simulation/aux-common/bots';
import { Subject, Subscription } from 'rxjs';
import { AuxCompiler, getInterpretableFunction, isInterpretableFunction, IMPORT_META_FACTORY, IMPORT_FACTORY, EXPORT_FACTORY, } from './AuxCompiler';
import { addToContext, MemoryGlobalContext, removeFromContext, isInContext, } from './AuxGlobalContext';
import { createDefaultLibrary, GET_RUNTIME } from './AuxLibrary';
import { createRuntimeBot, RealtimeEditMode } from './RuntimeBot';
import { RanOutOfEnergyError } from './AuxResults';
import { isPromise, isRuntimePromise, isUrl, markAsRuntimePromise, } from './Utils';
import { DefaultRealtimeEditModeProvider } from './AuxRealtimeEditModeProvider';
import { sortBy, forOwn, merge, union } from 'es-toolkit/compat';
import { applyTagEdit, isTagEdit } from '@casual-simulation/aux-common/bots';
import { updateRuntimeVersion } from './RuntimeStateVersion';
import { replaceMacros } from './Transpiler';
import { DateTime } from 'luxon';
import { Rotation, Vector2, Vector3 } from '@casual-simulation/aux-common/math';
import { isGenerator, markAsUncopiableObject, UNCOPIABLE, } from '@casual-simulation/js-interpreter/InterpreterUtils';
import { v4 as uuid } from 'uuid';
import { importInterpreter as _dynamicImportInterpreter } from './AuxRuntimeDynamicImports';
import { UNMAPPABLE } from '@casual-simulation/aux-common/bots/BotEvents';
import { DeepObjectError, convertToCopiableValue, } from '@casual-simulation/aux-common/partitions/PartitionUtils';
let Interpreter;
let DeclarativeEnvironmentRecord;
let DefinePropertyOrThrow;
let Descriptor;
let Value;
let hasModule = false;
let interpreterImportPromise;
export function registerInterpreterModule(module) {
hasModule = true;
Interpreter = module.Interpreter;
DeclarativeEnvironmentRecord = module.DeclarativeEnvironmentRecord;
DefinePropertyOrThrow = module.DefinePropertyOrThrow;
Descriptor = module.Descriptor;
Value = module.Value;
}
function importInterpreter() {
if (hasModule) {
return Promise.resolve();
}
if (interpreterImportPromise) {
return interpreterImportPromise;
}
else {
return (interpreterImportPromise = _importInterpreterCore());
}
}
async function _importInterpreterCore() {
const module = await _dynamicImportInterpreter();
registerInterpreterModule(module);
}
/**
* Defines an class that is able to manage the runtime state of an AUX.
*
* Being a runtime means providing and managing the execution state that an AUX is in.
* This means taking state updates events, shouts and whispers, and emitting additional events to affect the future state.
*/
export class AuxRuntime {
get library() {
return this._library;
}
get context() {
return this._globalContext;
}
get currentVersion() {
return this._currentVersion;
}
get globalObject() {
return this._globalObject;
}
get canTriggerBreakpoint() {
return !!this._interpreter && this._interpreter.debugging;
}
get systemMap() {
return this._systemMap;
}
/**
* Creates a new AuxRuntime using the given library factory.
* @param libraryFactory
* @param forceSignedScripts Whether to force the runtime to only allow scripts that are signed.
* @param exemptSpaces The spaces that are exempt from requiring signed scripts.
* @param interpreter The interpreter that should be used for the runtime.
*/
constructor(version, device, libraryFactory = createDefaultLibrary, editModeProvider = new DefaultRealtimeEditModeProvider(), exemptSpaces = ['local', 'tempLocal'], forceSyncScripts = false, interpreter = null) {
this._compiledState = {};
this._existingMasks = {};
this._compiler = new AuxCompiler();
this._stopState = null;
this._breakpoints = new Map();
this._currentStopCount = 0;
this._currentPromise = null;
this._actionBatch = [];
this._errorBatch = [];
this._currentVersion = {
localSites: {},
vector: {},
};
this._updatedBots = new Map();
this._newBots = new Map();
this._batchPending = false;
this._jobQueueCheckPending = false;
this._jobQueueCheckCount = 0;
this._processingErrors = false;
this._portalBots = new Map();
this._builtinPortalBots = [];
this._globalChanges = {};
this._beforeActionListeners = [];
this._scriptActionEnqueuedListeners = [];
this._scriptUpdatedTagListeners = [];
this._scriptUpdatedTagMaskListeners = [];
// private _beforeScriptEnterListeners: ((
// trace: DebuggerScriptEnterTrace
// ) => void)[] = [];
// private _afterScriptExitListeners: ((
// trace: DebuggerScriptExitTrace
// ) => void)[] = [];
/**
* The counter that is used to generate function names.
*/
this._functionNameCounter = 0;
/**
* A map of function names to their respective functions.
*/
this._functionMap = new Map();
/**
* A map of bot IDs to a list of function names.
*/
this._botFunctionMap = new Map();
/**
* Whether changes should be automatically batched.
*/
this._autoBatch = true;
this._forceSyncScripts = false;
this._currentDebugger = null;
/**
* The map of module IDs to their exports.
* Only used for global modules, which are modules that are not attached to a bot (e.g. source modules).
*/
this._cachedGlobalModules = new Map();
/**
* The map of system IDs to their respective bot IDs.
*/
this._systemMap = new Map();
/**
* The number of times that the runtime can call onError for an error from the same script.
*/
this.repeatedErrorLimit = 1000;
this._libraryFactory = libraryFactory;
this._interpreter = interpreter;
this._globalContext = new MemoryGlobalContext(version, device, this, this, this);
this._forceSyncScripts = forceSyncScripts;
this._globalContext.mockAsyncActions = forceSyncScripts;
this._library = merge(libraryFactory(this._globalContext), {
api: {
os: {
createDebugger: this._createDebugger.bind(this),
getExecutingDebugger: this._getExecutingDebugger.bind(this),
},
},
});
this._editModeProvider = editModeProvider;
this._exemptSpaces = exemptSpaces;
this._onActions = new Subject();
this._onErrors = new Subject();
this._onRuntimeStop = new Subject();
let sub = (this._sub = new Subscription(() => {
this._globalContext.cancelAllBotTimers();
}));
sub.add(this._globalContext.startAnimationLoop());
if (globalThis.addEventListener) {
const unhandledRejectionListener = (event) => {
const data = this._handleError(event.reason, null, null);
this._globalContext.enqueueError(data);
event.preventDefault();
};
globalThis.addEventListener('unhandledrejection', unhandledRejectionListener);
sub.add(() => {
globalThis.removeEventListener('unhandledrejection', unhandledRejectionListener);
});
}
this._globalObject = new Proxy(globalThis, {
get: (target, key, receiver) => {
if (key in this._globalChanges) {
return Reflect.get(this._globalChanges, key);
}
else if (key in target) {
return Reflect.get(target, key);
}
return Reflect.get(target, key, receiver);
},
set: (target, key, value, receiver) => {
return Reflect.set(this._globalChanges, key, value);
},
deleteProperty: (target, key) => {
return Reflect.deleteProperty(this._globalChanges, key);
},
defineProperty: (target, key, options) => {
return Reflect.defineProperty(this._globalChanges, key, options);
},
ownKeys: (target) => {
const addedKeys = Reflect.ownKeys(this._globalChanges);
const otherKeys = Reflect.ownKeys(target);
return union(addedKeys, otherKeys);
},
has: (target, key) => {
return (Reflect.has(this._globalChanges, key) ||
Reflect.has(target, key));
},
getOwnPropertyDescriptor: (target, key) => {
if (key in this._globalChanges) {
return Reflect.getOwnPropertyDescriptor(this._globalChanges, key);
}
return Reflect.getOwnPropertyDescriptor(target, key);
},
});
this._globalContext.global = this._globalObject;
if (this._interpreter) {
// Use the interpreted versions of APIs
this._interpretedApi = { ...this._library.api };
this._interpretedTagSpecificApi = {
...this._library.tagSpecificApi,
};
for (let key in this._interpretedApi) {
const val = this._interpretedApi[key];
if (isInterpretableFunction(val)) {
this._interpretedApi[key] = getInterpretableFunction(val);
}
}
for (let key in this._interpretedTagSpecificApi) {
const val = this._interpretedTagSpecificApi[key];
if (isInterpretableFunction(val)) {
this._interpretedTagSpecificApi[key] =
getInterpretableFunction(val);
}
}
}
}
async _importModule(module, meta, dependencyChain = [], allowCustomResolution = true) {
try {
let m;
let bot;
const allowResolution = meta.tag !== ON_RESOLVE_MODULE && allowCustomResolution;
if (typeof module !== 'string') {
m = module;
if (!m) {
throw new Error('Module not found: ' + module);
}
}
else {
const globalModule = this._cachedGlobalModules.get(module);
if (globalModule) {
return await globalModule;
}
m = await this.resolveModule(module, meta, allowResolution);
if (!m) {
throw new Error('Module not found: ' + module);
}
if (dependencyChain.length > 1) {
const index = dependencyChain.indexOf(m.id);
if (index >= 0) {
throw new Error(`Circular dependency detected: ${dependencyChain
.slice(index)
.join(' -> ')} -> ${m.id}`);
}
}
if ('botId' in m) {
bot = this._compiledState[m.botId];
if (bot) {
const exports = bot.exports[m.tag];
if (exports) {
return await exports;
}
}
}
}
const promise = this._importModuleCore(m, [...dependencyChain, m.id], allowResolution);
if (bot) {
bot.exports[m.tag] = promise;
}
else {
this._cachedGlobalModules.set(m.id, promise);
}
return await promise;
}
finally {
this._scheduleJobQueueCheck();
}
}
async _importModuleCore(m, dependencyChain, allowCustomResolution) {
try {
const exports = {};
const importFunc = (id, meta) => this._importModule(id, meta, dependencyChain, allowCustomResolution);
const exportFunc = async (valueOrSource, e, meta) => {
const result = await this._resolveExports(valueOrSource, e, meta, dependencyChain, allowCustomResolution);
this._scheduleJobQueueCheck();
Object.assign(exports, result);
};
if ('botId' in m) {
const bot = this._compiledState[m.botId];
const module = bot === null || bot === void 0 ? void 0 : bot.modules[m.tag];
if (module) {
await module.moduleFunc(importFunc, exportFunc);
}
}
else if ('source' in m) {
const source = m.source;
const mod = this._compile(null, null, source, {});
if (mod.moduleFunc) {
await mod.moduleFunc(importFunc, exportFunc);
}
}
else if ('exports' in m) {
Object.assign(exports, m.exports);
}
else if ('url' in m) {
return await this.dynamicImport(m.url);
}
return exports;
}
finally {
this._scheduleJobQueueCheck();
}
}
async _resolveExports(valueOrSource, exports, meta, dependencyChain, allowCustomResolution) {
if (typeof valueOrSource === 'string') {
const sourceModule = await this._importModule(valueOrSource, meta, dependencyChain, allowCustomResolution);
if (exports) {
const result = {};
for (let val of exports) {
if (typeof val === 'string') {
result[val] = sourceModule[val];
}
else {
const [source, target] = val;
const key = target !== null && target !== void 0 ? target : source;
result[key] = sourceModule[source];
}
}
return result;
}
else {
return sourceModule;
}
}
else {
return valueOrSource;
}
}
/**
* Performs a dynamic import() of the given module.
* Uses the JS Engine's native import() functionality.
* @param module The module that should be imported.
* @returns Returns a promise that resolves with the module's exports.
*/
async dynamicImport(module) {
return await import(/* @vite-ignore */ module);
}
/**
* Attempts to resolve the module with the given name.
* @param moduleName The name of the module to resolve.
* @param meta The metadata that should be used to resolve the module.
*/
async resolveModule(moduleName, meta, allowCustomResolution = true) {
if ((meta === null || meta === void 0 ? void 0 : meta.tag) === ON_RESOLVE_MODULE) {
allowCustomResolution = false;
}
if (moduleName === 'casualos') {
let exports = {
...this._library.api,
};
const bot = (meta === null || meta === void 0 ? void 0 : meta.botId) ? this._compiledState[meta.botId] : null;
const ctx = {
bot,
tag: meta === null || meta === void 0 ? void 0 : meta.tag,
creator: bot
? this._getRuntimeBot(bot.script.tags.creator)
: null,
config: null,
};
for (let key in this._library.tagSpecificApi) {
if (!Object.prototype.hasOwnProperty.call(this._library.tagSpecificApi, key)) {
continue;
}
const result = this._library.tagSpecificApi[key](ctx);
exports[key] = result;
}
return {
id: 'casualos',
exports,
};
}
if (allowCustomResolution) {
const shoutResult = this.shout(ON_RESOLVE_MODULE, undefined, {
module: moduleName,
meta,
});
const actionResult = isRuntimePromise(shoutResult)
? await shoutResult
: shoutResult;
for (let scriptResult of actionResult.results) {
const result = await scriptResult;
if (result) {
if (typeof result === 'object') {
if (typeof result.botId === 'string' &&
typeof result.tag === 'string') {
const bot = this._compiledState[result.botId];
const mod = bot === null || bot === void 0 ? void 0 : bot.modules[result.tag];
if (mod) {
return {
botId: result.botId,
id: moduleName,
tag: result.tag,
};
}
}
else if (typeof result.exports === 'object' &&
result.exports) {
return {
id: moduleName,
exports: result.exports,
};
}
}
else if (typeof result === 'string') {
if (isUrl(result)) {
return {
id: moduleName,
url: result,
};
}
else {
return {
id: moduleName,
source: result,
};
}
}
}
}
}
const isRelativeImport = moduleName.startsWith('.') || moduleName.startsWith(':');
if (isRelativeImport) {
if (!meta) {
throw new Error('Cannot resolve relative import without metadata');
}
const bot = this._compiledState[meta.botId];
if (!bot) {
throw new Error('Cannot resolve relative import without bot');
}
const system = calculateStringTagValue(null, bot, 'system', `🔗${bot.id}`);
const split = system.split('.');
for (let i = 0; i < moduleName.length; i++) {
if (moduleName[i] === ':') {
split.pop();
}
else if (moduleName[i] === '.') {
/* empty */
}
else {
moduleName =
split.join('.') + '.' + moduleName.substring(i);
break;
}
}
}
if (moduleName.startsWith('🔗')) {
const [id, tag] = moduleName.substring('🔗'.length).split('.');
const bot = this._compiledState[id];
if (bot && tag) {
return {
id: moduleName,
botId: bot.id,
tag: tag,
};
}
}
const lastIndex = moduleName.lastIndexOf('.');
if (lastIndex >= 0) {
const system = moduleName.substring(0, lastIndex);
const tag = moduleName.substring(lastIndex + 1);
const botIds = this._systemMap.get(system);
if (botIds) {
for (let id of botIds) {
const bot = this._compiledState[id];
if (bot && bot.modules[tag]) {
return {
botId: id,
id: moduleName,
tag: tag,
};
}
}
}
}
return {
id: moduleName,
url: moduleName,
};
}
getShoutTimers() {
return {};
}
get closed() {
return this._sub.closed;
}
unsubscribe() {
return this._sub.unsubscribe();
}
/**
* Gets the current state that the runtime is operating on.
*/
get currentState() {
return this._compiledState;
}
get userId() {
return this._userId;
}
set userId(id) {
this._userId = id;
this._globalContext.playerBot = this.userBot;
}
get userBot() {
if (!this._userId) {
return;
}
const bot = this._compiledState[this._userId];
if (bot) {
return bot.script;
}
else {
return null;
}
}
/**
* An observable that resolves whenever the runtime issues an action.
*/
get onActions() {
return this._onActions;
}
/**
* An observable that resolves whenever the runtime issues an error.
*/
get onErrors() {
return this._onErrors;
}
/**
* An observable that resolves whenever the runtime pauses in a script.
*/
get onRuntimeStop() {
return this._onRuntimeStop;
}
/**
* Processes the given bot actions and dispatches the resulting actions in the future.
* @param actions The actions to process.
*/
process(actions) {
if (this._beforeActionListeners.length > 0) {
for (let func of this._beforeActionListeners) {
for (let action of actions) {
try {
func(action);
}
catch (err) {
console.error(err);
}
}
}
}
this._processBatch();
const result = this._processCore(actions);
this._processBatch();
return result;
}
_getExecutingDebugger() {
return this._currentDebugger;
}
async _createDebugger(options) {
const forceSyncScripts = typeof (options === null || options === void 0 ? void 0 : options.allowAsynchronousScripts) === 'boolean'
? !options.allowAsynchronousScripts
: false;
await importInterpreter();
const interpreter = (options === null || options === void 0 ? void 0 : options.pausable) ? new Interpreter() : null;
const runtime = new AuxRuntime(this._globalContext.version, this._globalContext.device, this._libraryFactory, this._editModeProvider, this._exemptSpaces, forceSyncScripts, interpreter);
runtime._autoBatch = true;
let idCount = 0;
if (!(options === null || options === void 0 ? void 0 : options.useRealUUIDs)) {
runtime._globalContext.uuid = () => {
idCount += 1;
return `uuid-${idCount}`;
};
}
let allActions = [];
let allErrors = [];
let create;
if (interpreter &&
isInterpretableFunction(runtime._library.tagSpecificApi.create)) {
const func = getInterpretableFunction(runtime._library.tagSpecificApi.create)({
bot: null,
config: null,
creator: null,
tag: null,
});
create = (...args) => {
const result = func(...args);
if (isGenerator(result)) {
return runtime._processGenerator(result);
}
return result;
};
}
else {
create = runtime._library.tagSpecificApi.create({
bot: null,
config: null,
creator: null,
tag: null,
});
}
const isCommonAction = (action) => {
return !(action.type === 'add_bot' ||
action.type === 'remove_bot' ||
action.type === 'update_bot' ||
action.type === 'apply_state');
};
const getAllActions = () => {
const actions = runtime._processUnbatchedActions();
allActions.push(...actions);
return allActions;
};
// The config bot is always ID 0 in debuggers
const configBotId = (options === null || options === void 0 ? void 0 : options.useRealUUIDs)
? runtime.context.uuid()
: 'uuid-0';
const configBotTags = (options === null || options === void 0 ? void 0 : options.configBot)
? isBot(options === null || options === void 0 ? void 0 : options.configBot)
? options.configBot.tags
: options.configBot
: {};
runtime.context.createBot(createBot(configBotId, configBotTags, 'tempLocal'));
runtime.process(this._builtinPortalBots.map((b) => registerBuiltinPortal(b)));
runtime.userId = configBotId;
const api = {
...runtime._library.api,
};
if (interpreter) {
for (let key in runtime._library.api) {
const val = runtime._library.api[key];
if (isInterpretableFunction(val)) {
const func = getInterpretableFunction(val);
api[key] = (...args) => {
const result = func(...args);
if (isGenerator(result)) {
return runtime._processGenerator(result);
}
return result;
};
}
}
}
if (interpreter && (options === null || options === void 0 ? void 0 : options.pausable)) {
interpreter.debugging = true;
}
const debug = {
[UNCOPIABLE]: true,
...api,
getAllActions,
getCommonActions: () => {
return getAllActions().filter(isCommonAction);
},
getBotActions: () => {
return getAllActions().filter((a) => !isCommonAction(a));
},
getErrors: () => {
const errors = runtime._processUnbatchedErrors();
allErrors.push(...errors);
return allErrors;
},
onBeforeUserAction: (listener) => {
runtime._beforeActionListeners.push(listener);
},
onScriptActionEnqueued: (listener) => {
runtime._scriptActionEnqueuedListeners.push(listener);
},
onAfterScriptUpdatedTag: (listener) => {
runtime._scriptUpdatedTagListeners.push(listener);
},
onAfterScriptUpdatedTagMask: (listener) => {
runtime._scriptUpdatedTagMaskListeners.push(listener);
},
getCallStack() {
if (!interpreter) {
throw new Error('getCallStack() is only supported on pausable debuggers.');
}
return runtime._mapCallStack(interpreter.agent.executionContextStack);
},
async performUserAction(...actions) {
const result = await runtime.process(actions);
return result.map((r) => (r ? r.results : null));
},
// TODO: Determine whether to support this
// onBeforeScriptEnter: (
// listener: (trace: DebuggerScriptEnterTrace) => void
// ) => {
// runtime._beforeScriptEnterListeners.push(listener);
// },
// onAfterScriptExit: (
// listener: (trace: DebuggerScriptExitTrace) => void
// ) => {
// runtime._afterScriptExitListeners.push(listener);
// },
setPauseTrigger(b, tag, options) {
var _a, _b, _c;
if (typeof b === 'object' && 'triggerId' in b) {
runtime.setBreakpoint({
id: b.triggerId,
botId: b.botId,
tag: b.tag,
lineNumber: b.lineNumber,
columnNumber: b.columnNumber,
states: (_a = b.states) !== null && _a !== void 0 ? _a : ['before'],
disabled: !((_b = b.enabled) !== null && _b !== void 0 ? _b : true),
});
return b;
}
else {
const id = isBot(b) ? b.id : b;
const trigger = {
triggerId: uuid(),
botId: id,
tag: tag,
...options,
};
runtime.setBreakpoint({
id: trigger.triggerId,
botId: id,
tag: tag,
lineNumber: trigger.lineNumber,
columnNumber: trigger.columnNumber,
states: (_c = trigger.states) !== null && _c !== void 0 ? _c : ['before'],
disabled: false,
});
return trigger;
}
},
removePauseTrigger(triggerOrId) {
let id = typeof triggerOrId === 'string'
? triggerOrId
: triggerOrId.triggerId;
runtime.removeBreakpoint(id);
},
disablePauseTrigger(triggerOrId) {
var _a;
if (typeof triggerOrId === 'string') {
let trigger = runtime._breakpoints.get(triggerOrId);
if (trigger) {
trigger.disabled = true;
}
}
else {
runtime.setBreakpoint({
id: triggerOrId.triggerId,
botId: triggerOrId.botId,
tag: triggerOrId.tag,
lineNumber: triggerOrId.lineNumber,
columnNumber: triggerOrId.columnNumber,
states: (_a = triggerOrId.states) !== null && _a !== void 0 ? _a : ['before'],
disabled: true,
});
}
},
enablePauseTrigger(triggerOrId) {
var _a;
if (typeof triggerOrId === 'string') {
let trigger = runtime._breakpoints.get(triggerOrId);
if (trigger) {
trigger.disabled = false;
}
}
else {
runtime.setBreakpoint({
id: triggerOrId.triggerId,
botId: triggerOrId.botId,
tag: triggerOrId.tag,
lineNumber: triggerOrId.lineNumber,
columnNumber: triggerOrId.columnNumber,
states: (_a = triggerOrId.states) !== null && _a !== void 0 ? _a : ['before'],
disabled: false,
});
}
},
listPauseTriggers() {
let triggers = [];
for (let breakpoint of runtime._breakpoints.values()) {
triggers.push({
triggerId: breakpoint.id,
botId: breakpoint.botId,
tag: breakpoint.tag,
columnNumber: breakpoint.columnNumber,
lineNumber: breakpoint.lineNumber,
states: breakpoint.states.slice(),
enabled: !breakpoint.disabled,
});
}
return triggers;
},
listCommonPauseTriggers(botOrId, tag) {
const id = typeof botOrId === 'string' ? botOrId : botOrId.id;
const bot = runtime.currentState[id];
const func = bot.listeners[tag];
if (!func) {
return [];
}
return runtime._compiler.listPossibleBreakpoints(func, runtime._interpreter);
},
onPause(callback) {
runtime.onRuntimeStop.subscribe((stop) => {
const pause = {
pauseId: stop.stopId,
state: stop.state,
callStack: runtime._mapCallStack(stop.stack),
trigger: {
triggerId: stop.breakpoint.id,
botId: stop.breakpoint.botId,
tag: stop.breakpoint.tag,
lineNumber: stop.breakpoint.lineNumber,
columnNumber: stop.breakpoint.columnNumber,
states: stop.breakpoint.states,
},
};
callback(pause);
});
},
resume(pause) {
runtime.continueAfterStop(pause.pauseId);
},
[GET_RUNTIME]() {
return runtime;
},
get configBot() {
return runtime.userBot;
},
getPortalBots() {
var _a;
let portalBots = new Map();
for (let [portal, id] of runtime._portalBots) {
portalBots.set(portal, (_a = runtime.currentState[id]) === null || _a === void 0 ? void 0 : _a.script);
}
return portalBots;
},
create,
};
runtime._currentDebugger = debug;
this._scheduleJobQueueCheck();
return debug;
}
_mapCallStack(stack) {
const interpreter = this._interpreter;
return stack.map((s) => {
const callSite = s.callSite;
const funcName = callSite.getFunctionName();
let funcLocation = {};
if (funcName) {
const f = this._functionMap.get(funcName);
if (f) {
funcLocation.name = f.metadata.diagnosticFunctionName;
const location = this._compiler.calculateOriginalLineLocation(f, {
lineNumber: callSite.lineNumber,
column: callSite.columnNumber,
});
funcLocation.lineNumber = location.lineNumber + 1;
funcLocation.columnNumber = location.column + 1;
const tagName = f.metadata.context.tag;
const bot = f.metadata.context.bot;
if (bot) {
funcLocation.botId = bot.id;
}
if (tagName) {
funcLocation.tag = tagName;
}
}
else {
funcLocation.name = funcName;
}
}
if (!hasValue(funcLocation.lineNumber) &&
!hasValue(funcLocation.columnNumber) &&
hasValue(callSite.lineNumber) &&
hasValue(callSite.columnNumber)) {
funcLocation.lineNumber = callSite.lineNumber;
funcLocation.columnNumber = callSite.columnNumber;
}
if (!hasValue(funcLocation.lineNumber) &&
!hasValue(funcLocation.columnNumber) &&
!hasValue(funcLocation.name)) {
funcLocation = null;
}
const ret = {
location: funcLocation,
listVariables() {
let variables = [];
if (s.LexicalEnvironment instanceof
DeclarativeEnvironmentRecord) {
addBindingsFromEnvironment(s.LexicalEnvironment, 'block');
}
if (s.VariableEnvironment instanceof
DeclarativeEnvironmentRecord) {
addBindingsFromEnvironment(s.VariableEnvironment, 'frame');
let parent = s.VariableEnvironment.OuterEnv;
while (parent) {
if (parent instanceof DeclarativeEnvironmentRecord) {
addBindingsFromEnvironment(parent, 'closure');
}
parent = parent.OuterEnv;
}
}
return variables;
function addBindingsFromEnvironment(env, scope) {
for (let [nameValue, binding,] of env.bindings.entries()) {
const name = interpreter.copyFromValue(nameValue);
const initialized = !!binding.initialized;
const mutable = !!binding.mutable;
const value = initialized
? interpreter.reverseProxyObject(binding.value, false)
: undefined;
const variable = {
name,
value,
writable: mutable,
scope,
};
if (!initialized) {
variable.initialized = false;
}
variables.push(variable);
}
}
},
setVariableValue(name, value) {
if (s.LexicalEnvironment instanceof
DeclarativeEnvironmentRecord) {
const nameValue = interpreter.copyToValue(name);
const proxiedValue = interpreter.proxyObject(value);
if (nameValue.Type !== 'normal') {
throw interpreter.copyFromValue(nameValue.Value);
}
if (proxiedValue.Type !== 'normal') {
throw interpreter.copyFromValue(proxiedValue.Value);
}
const result = s.LexicalEnvironment.SetMutableBinding(nameValue.Value, proxiedValue.Value, Value.true);
if (result.Type !== 'normal') {
throw interpreter.copyFromValue(result.Value);
}
return interpreter.copyFromValue(result.Value);
}
},
};
return ret;
});
}
_processCore(actions) {
const _this = this;
const results = [];
function processAction(action, addToResults) {
let promise = _this._processAction(action);
if (addToResults) {
if (isRuntimePromise(promise)) {
return markAsRuntimePromise(promise.then((result) => {
results.push(result);
}));
}
else {
results.push(promise);
}
return;
}
return promise;
}
function handleRejection(action, rejection) {
let promise = processListOfMaybePromises(null, rejection.newActions, (action) => {
return processAction(action, false);
});
if (rejection.rejected) {
return;
}
if (promise) {
return markAsRuntimePromise(promise.then((p) => processAction(action, true)));
}
else {
return processAction(action, true);
}
}
let promise = processListOfMaybePromises(null, actions, (action) => {
let rejection = this._rejectAction(action);
let result;
if (isRuntimePromise(rejection)) {
result = markAsRuntimePromise(rejection.then((result) => handleRejection(action, result)));
}
else {
result = handleRejection(action, rejection);
}
return result;
});
if (isRuntimePromise(promise)) {
return markAsRuntimePromise(promise.then(() => results));
}
else {
return results;
}
}
_processAction(action) {
if (action.type === 'action') {
const result = this._shout(action.eventName, action.botIds, action.argument, false);
if (isRuntimePromise(result)) {
return markAsRuntimePromise(result
.then((result) => this._processCore(result.actions))
.then(() => result));
}
else {
let promise = this._processCore(result.actions);
if (isRuntimePromise(promise)) {
return markAsRuntimePromise(promise.then(() => result));
}
else {
return result;
}
}
}
else if (action.type === 'run_script') {
const result = this._execute(action.script, false, false);
if (isRuntimePromise(result)) {
return markAsRuntimePromise(result.then((result) => {
const p = this._processCore(result.actions);
if (isPromise(p)) {
return p.then(() => {
if (hasValue(action.taskId)) {
this._globalContext.resolveTask(action.taskId, result.result, false);
}
return null;
});
}
else {
if (hasValue(action.taskId)) {
if (this._globalContext.resolveTask(action.taskId, result.result, false)) {
this._scheduleJobQueueCheck();
}
}
}
return null;
}));
}
else {
const p = this._processCore(result.actions);
if (isRuntimePromise(p)) {
return markAsRuntimePromise(p.then(() => {
if (hasValue(action.taskId)) {
if (this._globalContext.resolveTask(action.taskId, result.result, false)) {
this._scheduleJobQueueCheck();
}
}
return null;
}));
}
if (hasValue(action.taskId)) {
if (this._globalContext.resolveTask(action.taskId, result.result, false)) {
this._scheduleJobQueueCheck();
}
}
}
}
else if (action.type === 'apply_state') {
const events = breakIntoIndividualEvents(this.currentState, action);
const promise = this._processCore(events);
if (isRuntimePromise(promise)) {
return markAsRuntimePromise(promise.then(() => null));
}
else {
return null;
}
}
else if (action.type === 'async_result') {
const value = action.mapBotsInResult === true
? this._mapBotsToRuntimeBots(action.result)
: action.result;
if (!this._globalContext.resolveTask(action.taskId, value, false)) {
this._actionBatch.push(action);
}
else {
this._scheduleJobQueueCheck();
}
}
else if (action.type === 'async_error') {
if (!this._globalContext.rejectTask(action.taskId, action.error, false)) {
this._actionBatch.push(action);
}
else {
this._scheduleJobQueueCheck();
}
}
else if (action.type === 'device_result') {
if (!this._globalContext.resolveTask(action.taskId, action.result, true)) {
this._actionBatch.push(action);
}
else {
this._scheduleJobQueueCheck();
}
}
else if (action.type === 'device_error') {
if (!this._globalContext.rejectTask(action.taskId, action.error, true)) {
this._actionBatch.push(action);
}
else {
this._scheduleJobQueueCheck();
}
}
else if (action.type === 'iterable_next') {
if (!this._globalContext.iterableNext(action.taskId, action.value, false)) {
this._actionBatch.push(action);
}
else {
this._scheduleJobQueueCheck();
}
}
else if (action.type === 'iterable_complete') {
if (!this._globalContext.iterableComplete(action.taskId, false)) {
this._actionBatch.push(action);
}
else {
this._scheduleJobQueueCheck();
}
}
else if (action.type === 'iterable_throw') {
if (!this._globalContext.iterableThrow(action.taskId, action.error, false)) {
this._actionBatch.push(action);
}
else {
this._scheduleJobQueueCheck();
}
}
else if (action.type === 'register_custom_app') {
this._registerPortalBot(action.appId, action.botId);
this._actionBatch.push(action);
}
else if (action.type === 'register_builtin_portal') {
if (!this._portalBots.has(action.portalId)) {
const newBot = this.context.createBot(createBot(this.context.uuid(), undefined, 'tempLocal'));
this._builtinPortalBots.push(action.portalId);
this._registerPortalBot(action.portalId, newBot.id);
this._actionBatch.push(defineGlobalBot(action.portalId, newBot.id));
}
else {
const botId = this._portalBots.get(action.portalId);
this._actionBatch.push(defineGlobalBot(action.portalId, botId));
}
}
else if (action.type === 'define_global_bot') {
if (this._portalBots.get(action.name) !== action.botId) {
this._registerPortalBot(action.name, action.botId);
this._actionBatch.push(action);
}
if (hasValue(action.taskId)) {
const promise = this._processCore([
asyncResult(action.taskId, null),
]);
if (isRuntimePromise(promise)) {
return markAsRuntimePromise(promise.then(() => null));
}
else {
return null;
}
}
}
else {
this._actionBatch.push(action);
}
return null;
}
_registerPortalBot(portalId, botId) {
const hadPortalBot = this._portalBots.has(portalId);
this._portalBots.set(portalId, botId);
if (!hadPortalBot) {
const variableName = `${portalId}Bot`;
const getValue = () => {
const botId = this._portalBots.get(portalId);
if (hasValue(botId)) {
return this.context.state[botId];
}
else {
return undefined;
}
};
Object.defineProperty(this._globalObject, variableName, {
get: getValue,