diff --git a/plugos/plug.ts b/plugos/plug.ts index e519a4e0..968870fb 100644 --- a/plugos/plug.ts +++ b/plugos/plug.ts @@ -5,9 +5,10 @@ import { AssetBundle, AssetJson } from "./asset_bundle/bundle.ts"; export class Plug { system: System; - sandbox: Sandbox; + sandbox?: Sandbox; public manifest?: Manifest; public assets?: AssetBundle; + private sandboxFactory: (plug: Plug) => Sandbox; readonly runtimeEnv: RuntimeEnvironment; grantedPermissions: string[] = []; name: string; @@ -20,21 +21,35 @@ export class Plug { ) { this.system = system; this.name = name; - this.sandbox = sandboxFactory(this); + this.sandboxFactory = sandboxFactory; + // this.sandbox = sandboxFactory(this); this.runtimeEnv = system.runtimeEnv; this.version = new Date().getTime(); } - async load(manifest: Manifest) { + // Lazy load sandbox, guarantees that the sandbox is loaded + async ensureSandbox() { + if (!this.sandbox) { + console.log("Now starting sandbox for", this.name); + // Kick off worker + this.sandbox = this.sandboxFactory(this); + // Push in any dependencies + for ( + const [dep, code] of Object.entries(this.manifest!.dependencies || {}) + ) { + await this.sandbox.loadDependency(dep, code); + } + await this.system.emit("sandboxInitialized", this.sandbox, this); + } + } + + load(manifest: Manifest) { this.manifest = manifest; this.assets = new AssetBundle( manifest.assets ? manifest.assets as AssetJson : {}, ); // TODO: These need to be explicitly granted, not just taken this.grantedPermissions = manifest.requiredPermissions || []; - for (const [dep, code] of Object.entries(manifest.dependencies || {})) { - await this.sandbox.loadDependency(dep, code); - } } syscall(name: string, args: any[]): Promise { @@ -57,6 +72,8 @@ export class Plug { if (!funDef) { throw new Error(`Function ${name} not found in manifest`); } + await this.ensureSandbox(); + const sandbox = this.sandbox!; if (funDef.redirect) { // Function redirect, look up // deno-lint-ignore no-this-alias @@ -73,18 +90,20 @@ export class Plug { } return plug.invoke(name, args); } - if (!this.sandbox.isLoaded(name)) { + if (!sandbox.isLoaded(name)) { if (!this.canInvoke(name)) { throw new Error( `Function ${name} is not available in ${this.runtimeEnv}`, ); } - await this.sandbox.load(name, funDef.code!); + await sandbox.load(name, funDef.code!); } - return await this.sandbox.invoke(name, args); + return await sandbox.invoke(name, args); } stop() { - this.sandbox.stop(); + if (this.sandbox) { + this.sandbox.stop(); + } } } diff --git a/plugos/runtime.test.ts b/plugos/runtime.test.ts index d64e59d5..5ea028f6 100644 --- a/plugos/runtime.test.ts +++ b/plugos/runtime.test.ts @@ -147,11 +147,11 @@ Deno.test("Preload dependencies", async () => { const system = new System("server"); system.on({ - plugLoaded: async (plug) => { + sandboxInitialized: async (sandbox) => { for ( const [modName, code] of Object.entries(globalModules.dependencies!) ) { - await plug.sandbox.loadDependency(modName, code as string); + await sandbox.loadDependency(modName, code as string); } }, }); diff --git a/plugos/syscalls/sandbox.ts b/plugos/syscalls/sandbox.ts index faf0ccc1..ab1e1b1b 100644 --- a/plugos/syscalls/sandbox.ts +++ b/plugos/syscalls/sandbox.ts @@ -6,7 +6,9 @@ export default function sandboxSyscalls(system: System): SysCallMapping { "sandbox.getLogs": (): LogEntry[] => { let allLogs: LogEntry[] = []; for (const plug of system.loadedPlugs.values()) { - allLogs = allLogs.concat(plug.sandbox.logBuffer); + if (plug.sandbox) { + allLogs = allLogs.concat(plug.sandbox.logBuffer); + } } allLogs = allLogs.sort((a, b) => a.date - b.date); return allLogs; diff --git a/plugos/system.ts b/plugos/system.ts index 7d03a219..126f48c1 100644 --- a/plugos/system.ts +++ b/plugos/system.ts @@ -1,6 +1,6 @@ import { Hook, Manifest, RuntimeEnvironment } from "./types.ts"; import { EventEmitter } from "./event.ts"; -import { SandboxFactory } from "./sandbox.ts"; +import { Sandbox, SandboxFactory } from "./sandbox.ts"; import { Plug } from "./plug.ts"; export interface SysCallMapping { @@ -11,6 +11,7 @@ export type SystemJSON = Manifest[]; export type SystemEvents = { plugLoaded: (plug: Plug) => void | Promise; + sandboxInitialized(sandbox: Sandbox, plug: Plug): void | Promise; plugUnloaded: (name: string) => void | Promise; }; diff --git a/server/http_server.ts b/server/http_server.ts index a5a21ab6..67066dd7 100644 --- a/server/http_server.ts +++ b/server/http_server.ts @@ -138,13 +138,13 @@ export class HttpServer { this.system.addHook(new EndpointHook(this.app, "/_")); this.system.on({ - plugLoaded: async (plug) => { + sandboxInitialized: async (sandbox) => { for ( const [modName, code] of Object.entries( this.globalModules.dependencies!, ) ) { - await plug.sandbox.loadDependency(modName, code as string); + await sandbox.loadDependency(modName, code as string); } }, }); diff --git a/web/editor.tsx b/web/editor.tsx index 6ed73f3e..5f37ae1f 100644 --- a/web/editor.tsx +++ b/web/editor.tsx @@ -245,13 +245,13 @@ export class Editor { ).json(); this.system.on({ - plugLoaded: async (plug) => { + sandboxInitialized: async (sandbox) => { for ( const [modName, code] of Object.entries( globalModules.dependencies, ) ) { - await plug.sandbox.loadDependency(modName, code as string); + await sandbox.loadDependency(modName, code as string); } }, });