puter/experiments/x86emu/www/js/InstanceManager.mjs
2024-07-10 16:25:14 -04:00

159 lines
5.6 KiB
JavaScript

/*
* Copyright (C) 2024 Puter Technologies Inc.
*
* This file is part of Puter.
*
* Puter 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 Instance from "./Instance.mjs"
/**
* Class representing the basic interface for managing instances of emulated machines.
*/
class InstanceManager {
/**
* Create an Instance Manager.
* @param {Object} [options] - Options for configuring the instance manager.
* @param {boolean} [options.screen=true] - Spawn screen option.
* @param {boolean} [options.term=false] - Spawn terminal option.
* @param {string} [options.instanceName="Host"] - Name of the instance.
* @param {number} [options.memory=1024] - Memory size for the instance; must be power of two.
* @param {HTMLElement} [options.spawnRoot=undefined] - Htlm element where instance should be spawned.
* @param {boolean} [options.autoStart=true] - Whether to automatically start the instance.
* @param {string} [options.remote="./"] - Remote URL, defaults to origin.
* @param {string} [options.wsUrl=""] - Websocket URL option.
*/
constructor(options) {
const defaultOptions = {
term: false,
screen: false,
instanceName: "Host",
memory: 1024,
spawnRoot: undefined,
autoStart: true,
remote: "./",
wsUrl: "",
};
const instanceOptions = { ...defaultOptions, ...options };
this.instances = {};
this.instanceNames = [];
this.curr_inst = 0;
this.instanceNames.push(instanceOptions.instanceName);
this.instances[instanceOptions.instanceName] = new Instance(instanceOptions);
}
/**
* Create an instance with given options and adds it to the pool of instances.
* @param {Object} options - Options for configuring the instance.
* @returns {Promise<Object>} - Resolves with the initialized instance.
*/
async createInstance(options) {
const instance = new Instance(options);
this.instanceNames.push(instance.instanceName);
this.instances[instance.instanceName] = instance;
return instance;
}
/**
* Continue running a suspended instance.
* @param {string} instName - instName of the instance to continue.
*/
async continueInstance(instName) {
var instance = await this.getInstanceByinstName(instName);
if (!instance.vm.cpu_is_running)
await instance.vm.run();
}
/**
* Suspend a running instance.
* @param {string} instName - instName of the instance to suspend.
*/
async suspendInstance(instName) {
var instance = await this.getInstanceByinstName(instName);
if (instance.vm.cpu_is_running)
await instance.vm.stop();
}
/**
* Save the state of a running instance.
* @param {string} instName - instName of the instance to save state.
* @returns {Promise} - Promise resolving once state is saved.
*/
async saveState(instName) {
const instance = this.getInstanceByinstName(instName);
if (instance.vm.cpu_is_running)
return instance.vm.save_state();
}
/**
* Load the state of a previously saved instance.
* @param {string} instName - instName of the instance to load state.
* @param {any} state - State to load.
* @returns {Promise} - Promise resolving once state is loaded.
*/
async loadState(instName, state) {
const instance = this.getInstanceByinstName(instName);
if (instance.vm.cpu_is_running)
await instance.vm.save_state(state);
}
/**
* Connect two instances for communication through NIC's.
* @param {string} destinationinstName - instName of the destination instance.
* @param {string} sourceinstName - instName of the source instance.
*/
async connectInstances(destinationinstName, sourceinstName) {
const destinationInstance = this.getInstanceByinstName(destinationinstName);
const sourceInstance = this.getInstanceByinstName(sourceinstName);
destinationInstance.add_listener("net0-send", (data) => {
source.bus.send("net0-receive", data);
});
sourceInstance.add_listener("net0-send", (data) => {
destination.bus.send("net0-receive", data);
});
}
/**
* Execute a command within an instance.
* @param {Object} inst - Instance object.
* @param {string} cmd - Command to execute.
* @param {Object} env - Environment variables.
* @param {number} [timeout=60] - Timeout for the command execution.
*/
async exec(inst, cmd, env, timeout = 60) {
// TODO: instNamed pipes on the instance would make this super nice so multiple terminals can be had
}
/**
* Destroy a specific instance.
* @param {string} instName - instName of the instance to destroy.
*/
async destroyInstance(instName) {
await this.getInstanceByinstName(instName).destroy();
}
/**
* Destroy all instances.
*/
async destroyInstances() {
for (const instance in this.instances)
destroyInstance(instance)
}
/**
* Get an instance by its instName.
* @param {string} instName - instName of the instance.
* @returns {Object} - The instance object.
* @throws Will throw an error if the instance instName is not found.
*/
async getInstanceByinstName(instName) {
if (!(instName in this.instances))
throw Error("getInstance: instName not found in instances object");
return this.instances[instName];
}
}
export default InstanceManager