Compare commits

...

7 Commits

Author SHA1 Message Date
d1f9104aba makefile: deps for hx and js 2023-06-30 15:34:51 +02:00
07ad65d5cf added ID bin 2023-06-26 20:16:31 +02:00
3a2613d916 improved terminal with history 2023-06-26 20:04:26 +02:00
dbd8038851 removed unused import 2023-06-26 20:04:00 +02:00
7bfe594b4b improved exporter 2023-06-26 19:06:24 +02:00
4dcc060e9a added type to IPerph 2023-06-26 19:05:48 +02:00
f124525d2d added computer perph 2023-06-26 19:05:10 +02:00
20 changed files with 325 additions and 123 deletions

View File

@@ -29,8 +29,15 @@ $(MIN_PATH): $(POLYFILL_PATH)
node minify.js $(POLYFILL_PATH) $@
.PHONY: deps
deps:
haxelib install all --always && yarn install
deps: deps-hx deps-node
.PHONY: deps-hx
deps-hx:
haxelib install all --always
.PHONY: deps-node
deps-node:
yarn install
.PHONY: clean
clean:

13
src/bin/ID.hx Normal file
View File

@@ -0,0 +1,13 @@
package bin;
import kernel.ps.ProcessHandle;
import kernel.ps.Process;
class ID implements Process {
public function new() {}
public function run(handle:ProcessHandle) {
handle.writeLine("ID: " + kernel.net.Net.instance.networkID);
handle.close();
}
}

View File

@@ -1,67 +0,0 @@
package bin;
import lib.exporter.Request;
import kernel.ps.ProcessHandle;
import lib.RessourceNames;
import lib.exporter.Export;
import kernel.ps.Process;
import kernel.log.Log;
import kernel.net.Package;
import kernel.net.Net;
import kernel.net.Package.GenericPackage;
using tink.CoreApi;
class ResManager implements Process {
private var handle:ProcessHandle;
private final exports:Map<String,Export> = [];
public function new() {}
public function run(handle:ProcessHandle) {
this.handle = handle;
Net.instance.registerProto("res",handlePackage);
}
public function register(id: String, export: Export): Future<Bool>{
if (exports.exists(id)){
handle.writeLine("Ressource already exists: " + id);
return Future.sync(false);
}
return registerName(id).map((success)->{
if (success){
exports.set(id,export);
}
return success;
});
}
private function handlePackage(pack: GenericPackage){
Log.debug("Handling ressource request" + pack);
var requestPack: Package<Request> = cast pack;
var id = requestPack.data.id;
if (!exports.exists(id)){
requestPack.respond(lib.exporter.Response.NotFound);
return;
}
var export = exports.get(id);
var response = export.handleRequest(requestPack.data);
requestPack.respond(response);
}
private function registerName(id: String){
return RessourceNames.register(id, Net.instance.networkID).map((res)->{
switch (res) {
case Success(data):
return data;
case Failure(err):
Log.error("Failed to register ressource: " + id + " " + err);
return false;
}
});
}
}

View File

@@ -1,6 +1,5 @@
package bin;
import kernel.log.Log;
import kernel.binstore.BinStore;
import kernel.ps.ProcessHandle;
import kernel.ps.Process;
@@ -11,11 +10,18 @@ import kernel.ui.WindowContext;
using tink.CoreApi;
class Terminal implements Process {
private var context:WindowContext;
private static inline final MAX_BACKLOG:Int = 100;
private var handle:ProcessHandle;
private var ctx:WindowContext;
private var requestRender: () -> Void;
private var input:String = "";
private var backlog:Array<String> = [];
private var handle:ProcessHandle;
private var requestRender: () -> Void;
private var history:Array<String> = [];
private var historyIndex:Int = 0;
private var runningPID:PID = -1;
public function new() {}
@@ -25,18 +31,20 @@ class Terminal implements Process {
var statelessContext = handle.createStatelessWindowContext();
this.context = statelessContext.ctx;
this.ctx = statelessContext.ctx;
this.requestRender = statelessContext.requestRender;
statelessContext.setRenderFunc(this.render);
handle.addCallbackLink(this.context.onChar.handle(char -> {
// Add input event handlers
handle.addCallbackLink(this.ctx.onChar.handle(char -> {
if (this.runningPID > 0) return;
this.input += char;
this.requestRender();
}));
handle.addCallbackLink(this.context.onKey.handle(e -> {
// Add key event handlers
handle.addCallbackLink(this.ctx.onKey.handle(e -> {
switch (e.keyCode) {
case 259: // Backspace
if (this.runningPID > 0) return;
@@ -48,9 +56,16 @@ class Terminal implements Process {
var command = this.input;
this.input = "";
this.requestRender();
this.historyIndex = 0;
this.invokeCommand(command);
case 269: // END
this.stopCurrentPS();
case 265: // UP
if (this.historyIndex < this.history.length) {
this.historyIndex++;
this.input = this.history[this.history.length - this.historyIndex];
this.requestRender();
}
}
}));
@@ -66,12 +81,7 @@ class Terminal implements Process {
}
private function render() {
redrawBacklog();
redrawInput();
}
private function redrawBacklog() {
var size = this.context.getSize();
var size = this.ctx.getSize();
var linesAvailable = size.y - 1;
var start:Int = this.backlog.length - linesAvailable;
@@ -79,29 +89,28 @@ class Terminal implements Process {
for (i in 0...linesAvailable) {
var line = this.backlog[start + i];
this.context.setCursorPos(0, i);
this.context.clearLine();
this.ctx.setCursorPos(0, i);
this.ctx.clearLine();
if (line != null) {
this.context.write(line);
this.ctx.write(line);
}
}
this.moveCursorToInput();
this.ctx.setCursorPos(0, size.y - 1);
this.ctx.clearLine();
this.ctx.setTextColor(Color.Blue);
this.ctx.write("> ");
this.ctx.setTextColor(Color.White);
this.ctx.write(this.input);
if (this.runningPID < 0) {
this.ctx.setCursorBlink(true);
} else {
this.ctx.setCursorBlink(false);
}
private function redrawInput() {
var size = this.context.getSize();
this.context.setCursorPos(0, size.y - 1);
this.context.clearLine();
this.context.setTextColor(Color.Blue);
this.context.write("> ");
this.context.setTextColor(Color.White);
this.context.write(this.input);
this.context.setCursorBlink(true);
}
private function invokeCommand(command:String):Void {
@@ -110,6 +119,13 @@ class Terminal implements Process {
if (args.length == 0) {
return;
}
this.history.push(command);
if (this.history.length > MAX_BACKLOG) {
this.history.shift();
}
var commandName = args[0];
// Handle built-in commands
@@ -125,8 +141,7 @@ class Terminal implements Process {
var ps = getProgByName(commandName);
if (ps == null) {
this.backlog.push("Unknown command: " + commandName);
this.redrawBacklog();
this.redrawInput();
this.requestRender();
return;
}
@@ -149,6 +164,11 @@ class Terminal implements Process {
} else {
this.backlog[this.backlog.length - 1] += s;
}
// Trim the backlog if it's too long
if (this.backlog.length > MAX_BACKLOG) {
this.backlog.shift();
}
}
this.requestRender();
@@ -163,9 +183,12 @@ class Terminal implements Process {
}
});
this.context.setCursorBlink(false);
this.ctx.setCursorBlink(false);
}
/**
Convter a command string into an array of arguments where the first element is the command name
**/
private function parseArgs(command:String):Array<String> {
// TODO: tim and quote handling
return command.split(" ");
@@ -173,7 +196,7 @@ class Terminal implements Process {
private function clear() {
this.backlog = [];
this.redrawBacklog();
this.requestRender();
}
private function getProgByName(name:String):Process {
@@ -186,7 +209,7 @@ class Terminal implements Process {
}
private function moveCursorToInput() {
var size = this.context.getSize();
this.context.setCursorPos(this.input.length + 2, size.y - 1);
var size = this.ctx.getSize();
this.ctx.setCursorPos(this.input.length + 2, size.y - 1);
}
}

View File

@@ -1,4 +1,4 @@
package bin;
package bin.exporter;
import lib.exporter.Export;
import lib.exporter.IExportable;
@@ -35,7 +35,7 @@ class Res extends CLIAppBase {
return false;
}
var perf: IExportable = Peripheral.instance.getRedstone(addr);
var perf: kernel.peripherals.Redstone = Peripheral.instance.getRedstone(addr);
if (perf == null) {
handle.writeLine("Error: peripheral not found");
@@ -43,13 +43,15 @@ class Res extends CLIAppBase {
}
return srv.register(name,new Export(perf)).map((res)->{
if (res) {
switch (res){
case Success(_):
handle.writeLine("Success");
} else {
handle.writeLine("Error");
return true;
case Failure(err):
handle.writeLine("Error: ");
handle.writeLine(Std.string(err));
return false;
}
return res;
});
},"<addr> <name>");
}

View File

@@ -0,0 +1,91 @@
package bin.exporter;
import kernel.peripherals.Peripherals.Peripheral;
import lib.KVStore;
import lib.exporter.Request;
import kernel.ps.ProcessHandle;
import lib.RessourceNames;
import lib.exporter.Export;
import kernel.ps.Process;
import kernel.net.Package;
import kernel.net.Net;
import kernel.net.Package.GenericPackage;
using tink.CoreApi;
class ResManager implements Process {
private var handle:ProcessHandle;
private var exports:Map<String,Export> = [];
public function new() {}
public function run(handle:ProcessHandle) {
this.handle = handle;
Net.instance.registerProto("res",handlePackage);
load();
}
public function register(id: String, export: Export): Promise<Noise>{
if (exports.exists(id)){
return Promise.reject(new Error("Ressource already exists: " + id));
}
return registerName(id).next((success)->{
exports.set(id,export);
persist();
return null;
});
}
private function handlePackage(pack: GenericPackage){
var requestPack: Package<Request> = cast pack;
var id = requestPack.data.id;
if (!exports.exists(id)){
requestPack.respond(lib.exporter.Response.NotFound);
return;
}
var export = exports.get(id);
var response = export.handleRequest(requestPack.data);
requestPack.respond(response);
}
private function registerName(id: String){
return RessourceNames.register(id, Net.instance.networkID);
}
private function persist(){
var store = new KVStore("export");
var saveExports: Array<{name: String, addr: String, type: String}> =
[for (k => v in this.exports) {name: k, addr: v.getAddr(), type: v.getType()}];
store.set("exports",saveExports);
store.save();
}
private function load(){
var store = new KVStore("export");
var savedExports: Array<{name: String, addr: String, type: String}> = store.get("exports",[]);
for (export in savedExports){
var perph = Peripheral.instance.getFromType(export.addr,export.type);
if (perph == null){
handle.writeLine('Could not load export: ${export.name} on ${export.addr}');
continue;
}
// I dont know if cast is the best way to do this
// But since we know that this is a IExportable we can do this (I think)
exports.set(export.name, new Export(cast perph));
handle.writeLine('Loaded export: ${export.name} on ${export.addr}');
}
}
}

View File

@@ -1,7 +1,5 @@
package bin.srsc;
import lib.TypeField;
import kernel.log.Log;
import lib.KVStore;
import bin.srsc.PackageTypes;
import kernel.net.Package;

View File

@@ -20,8 +20,9 @@ class DCEHack {
new bin.srsc.CLI(),
new bin.Perf(),
new bin.KSettings(),
new bin.ResManager(),
new bin.Res(),
new bin.exporter.ResManager(),
new bin.exporter.Res(),
new bin.ID(),
];
}
}

View File

@@ -1,7 +1,8 @@
package kernel.binstore;
import bin.Res;
import bin.ResManager;
import bin.ID;
import bin.exporter.Res;
import bin.exporter.ResManager;
import bin.KSettings;
import bin.Perf;
import bin.srsc.CLI;
@@ -39,7 +40,8 @@ class BinStore {
{c: Perf, name: "Perf", aliases: ["perf"]},
{c: KSettings, name: "KSettings", aliases: ["ksettings","ks"]},
{c: ResManager, name: "ResManager", aliases: ["resmanager","resmgr"]},
{c: Res, name: "Res", aliases: ["res"]}
{c: Res, name: "Res", aliases: ["res"]},
{c: ID , name: "ID", aliases: ["id"]}
];
@:allow(kernel.Init)

View File

@@ -0,0 +1,50 @@
package kernel.peripherals;
import cc.Peripheral;
import kernel.net.Package.NetworkID;
class Computer implements IPeripheral {
public static inline final TYPE_NAME:String = "computer";
private final addr:String;
@:allow(kernel.peripherals)
private function new(addr: String) {
this.addr = addr;
}
public function getAddr():String {
return addr;
}
public function getType():String {
return Computer.TYPE_NAME;
}
public function isOn():Bool {
return Peripheral.call(addr, "isOn");
}
public function getLabel():Null<String> {
return Peripheral.call(addr, "getLabel");
}
/**
Return -1 if no ID set yet
**/
public function getID():NetworkID{
return Peripheral.call(addr, "getID");
}
public function reboot() {
Peripheral.call(addr, "reboot");
}
public function shutdown() {
Peripheral.call(addr, "shutdown");
}
public function turnOn() {
Peripheral.call(addr, "turnOn");
}
}

View File

@@ -39,6 +39,10 @@ class Drive implements IPeripheral {
return this.addr;
}
public function getType():String {
return TYPE_NAME;
}
public inline function isDiskPresent(): Bool {
return this.native.isDiskPresent();
}

View File

@@ -1,6 +1,9 @@
package kernel.peripherals;
class EnergyStorage implements IPeripheral{
import lib.exporter.ExportConfig;
import lib.exporter.IExportable;
class EnergyStorage implements IPeripheral implements IExportable{
public static inline final TYPE_NAME:String = "energyCell";
private final addr:String;
@@ -22,4 +25,17 @@ class EnergyStorage implements IPeripheral{
public function getAddr():String {
return this.addr;
}
public function getType():String {
return TYPE_NAME;
}
public function export():ExportConfig {
return {
getDelegates: [
"energy" => _ -> Number(this.getEnergy()),
"capacity" => _ -> Number(this.getEnergyCapacity()),
],
}
}
}

View File

@@ -2,4 +2,5 @@ package kernel.peripherals;
interface IPeripheral {
public function getAddr(): String;
public function getType(): String;
}

View File

@@ -74,6 +74,10 @@ class Modem implements INetworkInterface implements IPeripheral {
return this.addr;
}
public function getType():String {
return TYPE_NAME;
}
public function getBaseRoutingCost():Int {
if (this.native.isWireless()){
return 2; // Prefere messages over cable

View File

@@ -72,6 +72,31 @@ class Peripheral {
};
}
/**
Cast peripheral to a specific type.
This is a temporary solution, maybe forever.
**/
public function getFromType(addr: String, type: String): Null<IPeripheral> {
switch (type){
case Computer.TYPE_NAME:
return getComputer(addr);
case Screen.TYPE_NAME:
return getScreen(addr);
case Drive.TYPE_NAME:
return getDrive(addr);
case EnergyStorage.TYPE_NAME:
return getEnergyStorage(addr);
case Modem.TYPE_NAME:
return getModem(addr);
case Printer.TYPE_NAME:
return getPrinter(addr);
case "redstone":
return getRedstone(addr);
}
return null;
}
public function getScreen(addr: String): Null<Screen> {
var addr = safeGetAddr(addr, Screen.TYPE_NAME);
if (addr == null) return null;
@@ -127,4 +152,14 @@ class Peripheral {
public function getAllEnergyStorages(): Array<EnergyStorage> {
return [ for (addr in findAddrByType(EnergyStorage.TYPE_NAME)) new EnergyStorage(addr)];
}
public function getComputer(addr: String): Null<Computer> {
var addr = safeGetAddr(addr, Computer.TYPE_NAME);
if (addr == null) return null;
return new Computer(addr);
}
public function getAllComputers(): Array<Computer> {
return [ for (addr in findAddrByType(Computer.TYPE_NAME)) new Computer(addr)];
}
}

View File

@@ -19,6 +19,10 @@ class Printer implements IPeripheral {
return addr;
}
public function getType():String {
return TYPE_NAME;
}
public function write(text: String){
}

View File

@@ -44,6 +44,7 @@ abstract BundleMask(Int) from cc.Colors.Color to cc.Colors.Color {
}
class Redstone implements IPeripheral implements IExportable {
public static inline final TYPE_NAME:String = "redstone"; // TODO: there is technically not a type for redstone.
public final onChange:Signal<Noise>;
@@ -74,6 +75,10 @@ class Redstone implements IPeripheral implements IExportable {
return this.addr;
}
public function getType():String {
return TYPE_NAME;
}
private function updateState() {
this.analogInputState = cc.Redstone.getAnalogInput(this.addr);
this.bundleInputState = cc.Redstone.getBundledInput(this.addr);
@@ -125,7 +130,6 @@ class Redstone implements IPeripheral implements IExportable {
public function export():ExportConfig {
return {
type: "redstone",
getDelegates: [
"input" => (_) -> {return Bool(this.getInput());},
"analog" => (_) -> {return Number(this.getAnalogInput());}

View File

@@ -39,6 +39,10 @@ class Screen implements TermWriteable implements IPeripheral {
return this.addr;
}
public function getType():String {
return TYPE_NAME;
}
public function getTextScale():Float {
return nativ.getTextScale();
}

View File

@@ -1,14 +1,17 @@
package lib.exporter;
import kernel.peripherals.IPeripheral;
import kernel.log.Log;
using tink.CoreApi;
class Export {
private final exportConfig: ExportConfig;
private final peripheral: IPeripheral;
public function new(exportConfig: IExportable) {
this.exportConfig = exportConfig.export();
public function new<T: IExportable & IPeripheral>(exportPerph: T) {
this.peripheral = exportPerph;
this.exportConfig = exportPerph.export();
}
public function handleRequest(req: Request): Response {
@@ -32,4 +35,12 @@ class Export {
return Get(value);
}
public function getType(): String {
return this.peripheral.getType();
}
public function getAddr(): String {
return this.peripheral.getAddr();
}
}

View File

@@ -3,7 +3,6 @@ package lib.exporter;
import lib.exporter.Response;
typedef ExportConfig = {
type: String,
getDelegates: Map<String, Null<Int>->ValueType>,
// setDelegates: Map<String, (ValueType, Null<Int>)->ValueType>,
}