Compare commits

...

4 Commits

Author SHA1 Message Date
e3875f76f6 removed hello world examples 2024-03-12 21:48:10 +01:00
fdcb81b565 removed export system 2024-03-12 21:47:49 +01:00
72654b1036 added formatter install in makefile 2024-03-12 21:44:51 +01:00
fe85c33d64 remade RPC system 2024-03-12 21:44:08 +01:00
20 changed files with 203 additions and 636 deletions

View File

@@ -58,4 +58,8 @@ webconsole:
.PHONY: format
format:
haxelib run formatter -s src
haxelib run formatter -s src
.PHONY: format-deps
format-deps:
haxelib install formatter

View File

@@ -1,18 +0,0 @@
package bin;
import kernel.log.Log;
import kernel.ps.ProcessHandle;
import kernel.ps.Process;
using tink.CoreApi;
@:build(macros.Binstore.includeBin("Hello world", ["hello"]))
class HelloWorld implements Process {
public function new() {}
public function run(handle:ProcessHandle) {
handle.write("Hello World!");
handle.close();
}
}

View File

@@ -1,74 +0,0 @@
package bin;
import kernel.log.Log;
import lib.ui.elements.TextElement;
import lib.Pos;
import lib.ui.elements.UIElement;
import lib.ui.elements.LayerdRootElement;
import kernel.ui.WindowContext;
import kernel.ps.ProcessHandle;
import kernel.ps.Process;
@:build(macros.Binstore.includeBin("HelloWorld-GUI", ["hello-gui"]))
class HelloWorldGUI implements Process {
private var handle:ProcessHandle;
private var ctx:WindowContext;
private var requestRender:Void->Void;
private var root:LayerdRootElement;
public function new() {}
public function run(handle:ProcessHandle) {
this.handle = handle;
var stateless = handle.createStatelessWindowContext();
this.ctx = stateless.ctx;
this.requestRender = stateless.requestRender;
stateless.setRenderFunc(this.render);
this.root = new LayerdRootElement();
this.ctx.delegateEvents(this.root);
this.requestRender();
}
private function render() {
var children:Array<{element:UIElement, offset:Pos}> = [
{
element: new TextElement("Hello World", {
uiEvents: {
onClick: () -> {
Log.debug("Hello World");
}
}
}),
offset: new Pos({x: 0, y: 0})
},
{
element: new TextElement("Holla Mundo", {
uiEvents: {
onClick: () -> {
Log.debug("Holla Mundo");
}
}
}),
offset: new Pos({x: 0, y: 1})
},
{
element: new TextElement("Ayyy", {
uiEvents: {
onClick: () -> {
Log.debug("Ayy");
}
}
}),
offset: new Pos({x: 4, y: 1})
}
];
this.root.setChildren(children);
this.root.render(ctx.getSize()).renderToContext(ctx);
}
}

View File

@@ -1,28 +0,0 @@
package bin;
import kernel.log.Log;
import macros.rpc.RPC;
import kernel.ps.ProcessHandle;
import kernel.ps.Process;
using tink.CoreApi;
@:build(macros.rpc.RPC.buildRPC())
class HelloWorldService implements Process {
private var handle:ProcessHandle;
public function new() {}
public function run(handle:ProcessHandle) {
this.handle = handle;
RPC.generateRPCPackageHandle();
}
@rpc
public function getNumber(arg1:Int, arg2:Int):Int {
Log.debug(arg1);
Log.debug(arg2);
return 42;
}
}

41
src/bin/debug/Debug.hx Normal file
View File

@@ -0,0 +1,41 @@
package bin.debug;
import kernel.log.Log;
import lib.turtle.InvManager;
import kernel.ps.ProcessHandle;
import kernel.ps.Process;
/**
Use this to test whatever you are working on. It will also print debug statements.
IDK if you commit changes in this file. It will not be included in non debug build.
**/
#if debug
@:build(macros.Binstore.includeBin("Debug", ["dbg", "debug"]))
#end
class Debug implements Process {
public function new() {}
public function run(handle:ProcessHandle) {
var link = Log.onLog.handle((line) -> {
handle.writeLine('[${line.level}] ${line.message}');
});
handle.addDeferFunc(() -> {
link.cancel();
});
// Add your stuff here
// -----
var rpc = new bin.debug.DebugRPC.DebugRPCImpl(1, "debug");
var a = rpc.addNumber(1, 2);
// rpc.addNumber(1,2).handle((e)->{
// Log.debug(e);
// handle.close();
// });
// -----
}
}

10
src/bin/debug/DebugRPC.hx Normal file
View File

@@ -0,0 +1,10 @@
package bin.debug;
import macros.rpc.RPCBase;
interface DebugRPC {
function addNumber(a:Int, b:Int):Int;
}
@:build(macros.rpc.RPC.buildRPC(DebugRPC))
class DebugRPCImpl extends RPCBase {}

View File

@@ -0,0 +1,27 @@
package bin.debug;
import bin.debug.DebugRPC.DebugRPCImpl;
import macros.rpc.RPC;
import kernel.ps.ProcessHandle;
import kernel.ps.Process;
#if debug
@:build(macros.Binstore.includeBin("Debug SRV", ["dbg-srv", "debug-srv"]))
#end
class DebugService implements Process implements DebugRPC {
private var handle:ProcessHandle;
public function new() {}
public function run(handle:ProcessHandle) {
this.handle = handle;
kernel.net.Net.registerProto("debug", (pack) -> {
DebugRPCImpl.handlePackage(this, pack);
});
}
public function addNumber(a:Int, b:Int):Int {
return a + b;
}
}

View File

@@ -1,58 +0,0 @@
package bin.exporter;
import lib.exporter.Export;
import lib.exporter.IExportable;
import kernel.peripherals.Peripherals.Peripheral;
import kernel.service.ServiceManager;
import lib.exporter.Import;
import lib.CLIAppBase;
class Res extends CLIAppBase {
public function new() {
registerAsyncSubcommand("get", (args) -> {
var url = args[0];
return Import.get(url).map((res) -> {
switch (res) {
case Success(data):
handle.writeLine(Std.string(data));
case Failure(err):
handle.writeLine("Error: ");
handle.writeLine(Std.string(err));
}
return true;
});
}, "<url>");
registerAsyncSubcommand("register", (args) -> {
var srv:Null<ResManager> = ServiceManager.get("resmgr");
var addr = args[0];
var name = args[1];
if (srv == null) {
handle.writeLine("Error: resmgr not found");
return false;
}
var perf:kernel.peripherals.Redstone = Peripheral.getRedstone(addr);
if (perf == null) {
handle.writeLine("Error: peripheral not found");
return false;
}
return srv.register(name, new Export(perf)).map((res) -> {
switch (res) {
case Success(_):
handle.writeLine("Success");
return true;
case Failure(err):
handle.writeLine("Error: ");
handle.writeLine(Std.string(err));
return false;
}
});
}, "<addr> <name>");
}
}

View File

@@ -1,90 +0,0 @@
package bin.exporter;
import kernel.KernelSettings;
import kernel.peripherals.Peripherals.Peripheral;
import lib.KVStore;
import lib.exporter.Request;
import kernel.ps.ProcessHandle;
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.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) {
var rpc = new SiteRessourceControllerRPC(KernelSettings.siteController);
return rpc.register(id, Net.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.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,9 +1,6 @@
package kernel.peripherals;
import lib.exporter.ExportConfig;
import lib.exporter.IExportable;
class EnergyStorage implements IPeripheral implements IExportable {
class EnergyStorage implements IPeripheral {
public static inline final TYPE_NAME:String = "energyCell";
private final addr:String;
@@ -29,13 +26,4 @@ class EnergyStorage implements IPeripheral implements IExportable {
public function getType():String {
return TYPE_NAME;
}
public function export():ExportConfig {
return {
getDelegates: [
"energy" => _ -> Number(this.getEnergy()),
"capacity" => _ -> Number(this.getEnergyCapacity()),
],
}
}
}

View File

@@ -1,7 +1,5 @@
package kernel.peripherals;
import lib.exporter.ExportConfig;
import lib.exporter.IExportable;
import haxe.ds.ReadOnlyArray;
import lib.Color;
@@ -43,8 +41,7 @@ abstract BundleMask(Int) from cc.Colors.Color to cc.Colors.Color {
}
}
@:build(macros.Exporter.buildExport())
class Redstone implements IPeripheral implements IExportable {
class Redstone implements IPeripheral {
public static inline final TYPE_NAME:String = "redstone"; // TODO: there is technically not a type for redstone.
public final onChange:Signal<Noise>;
@@ -89,12 +86,10 @@ class Redstone implements IPeripheral implements IExportable {
cc.Redstone.setOutput(this.addr, on);
}
@export("output")
public inline function getOutput():Bool {
return cc.Redstone.getOutput(this.addr);
}
@export("input")
public inline function getInput():Bool {
return cc.Redstone.getInput(this.addr);
}

View File

@@ -1,46 +0,0 @@
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<T:IExportable & IPeripheral>(exportPerph:T) {
this.peripheral = exportPerph;
this.exportConfig = exportPerph.export();
}
public function handleRequest(req:Request):Response {
switch (req.operation) {
case Get:
return handleGet(req);
case Set(value):
// TODO: implement
return NotFound;
}
}
private function handleGet(request:Request):Response {
if (!this.exportConfig.getDelegates.exists(request.field)) {
Log.warn('Requested get field ${request.field} does not exist in ??');
return NotFound;
}
var delegate = this.exportConfig.getDelegates.get(request.field);
var value = delegate(request.index);
return Get(value);
}
public function getType():String {
return this.peripheral.getType();
}
public function getAddr():String {
return this.peripheral.getAddr();
}
}

View File

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

View File

@@ -1,5 +0,0 @@
package lib.exporter;
interface IExportable {
public function export():ExportConfig;
}

View File

@@ -1,29 +0,0 @@
package lib.exporter;
import kernel.KernelSettings;
import kernel.net.Net;
import kernel.net.Package.NetworkID;
using tink.CoreApi;
class Import {
public static function get(ressourceLocator:String):Promise<Response> {
var request = Request.fromString(ressourceLocator);
var rpc = new SiteRessourceControllerRPC(KernelSettings.siteController);
return rpc.get(request.id).next((response) -> {
return performRequest(response, request);
});
}
private static function performRequest(netID:NetworkID, request:Request):Promise<Response> {
return Net.sendAndAwait(netID, "res", request).map((response) -> {
switch (response) {
case Success(data):
return Success(cast(data.data, Response));
case Failure(error):
return Failure(error);
}
});
}
}

View File

@@ -1,6 +0,0 @@
package lib.exporter;
enum Operation {
Get;
Set(value:Dynamic);
}

View File

@@ -1,50 +0,0 @@
package lib.exporter;
import lua.TableTools;
import lua.NativeStringTools;
class Request {
public final id:String;
public final field:String;
public final index:Null<Int>;
public final operation:Operation;
public function new(id:String, field:String, index:Null<Int>, operation:Operation) {
this.id = id;
this.field = field;
this.index = index;
this.operation = operation;
}
/**
Example:
"myfield[2]@myid"
"myfield@myid"
**/
public static function fromString(locator:String):Request {
if (StringTools.contains(locator, "[")) {
var f = TableTools.pack(NativeStringTools.gmatch(locator, "(%a+)%[([%d]+)%]@(%a+)")());
var field = f[1];
var index = Std.parseInt(f[2]);
var id = f[3];
return new Request(id, field, index, Get);
} else {
var f = TableTools.pack(NativeStringTools.gmatch(locator, "(%a+)@(%a+)")());
var field = f[1];
var id = f[2];
return new Request(id, field, null, Get);
}
}
public function toString() {
if (index == null) {
return field + "@" + id;
} else {
return field + "[" + index + "]@" + id;
}
}
}

View File

@@ -1,14 +0,0 @@
package lib.exporter;
enum Response {
NotFound;
Set;
NotSet;
Get(value:ValueType);
}
enum ValueType {
Number(value:Int);
String(value:String);
Bool(value:Bool);
}

View File

@@ -1,79 +0,0 @@
package macros;
import haxe.macro.Context;
import haxe.macro.Expr;
using Lambda;
class Exporter {
macro public static function buildExport():Array<haxe.macro.Expr.Field> {
var fields = Context.getBuildFields();
var getExp = [];
for (field in fields) {
if (field.meta == null)
continue;
var s = "";
for (meta in field.meta) {
if (meta.name == "export") {
switch (meta.params[0].expr) {
case EConst(CString(s1)):
s = s1;
default:
Context.error("Invalid export name", meta.pos);
}
}
}
if (s == "")
continue;
switch (field.kind) {
case FFun(f):
var funName = field.name;
switch (f.ret) {
case TPath(p):
switch (p.name) {
case "Int":
getExp.push(macro $v{s} => (i:Int) -> lib.exporter.Response.ValueType.Int(this.$funName()));
case "Float":
getExp.push(macro $v{s} => (i:Int) -> lib.exporter.Response.ValueType.Float(this.$funName()));
case "Bool":
getExp.push(macro $v{s} => (i:Int) -> lib.exporter.Response.ValueType.Bool(this.$funName()));
case "String":
getExp.push(macro $v{s} => (i:Int) -> lib.exporter.Response.ValueType.String(this.$funName()));
default:
Context.error("Only Int, Float, Bool and String can be exported", field.pos);
}
default:
Context.error("Only functions returning a type can be exported", field.pos);
}
default:
Context.error("Only functions can be exported", field.pos);
}
}
var exportField:Field = {
name: "export",
pos: Context.currentPos(),
kind: FFun({
args: [],
ret: TPath({name: "ExportConfig", pack: []}),
expr: macro {
return {
getDelegates: $a{getExp},
};
}
}),
access: [APublic],
doc: null,
meta: [],
};
fields.push(exportField);
return fields;
}
}

View File

@@ -1,129 +1,136 @@
package macros.rpc;
import haxe.macro.ComplexTypeTools;
import haxe.macro.TypeTools;
import haxe.macro.Context;
import haxe.macro.Expr;
using Lambda;
class RPC {
macro static public function buildRPC():Array<Field> {
var fields = Context.getBuildFields();
macro static public function buildRPC(iface:Expr):Array<Field> {
var buildFields = Context.getBuildFields();
var localClass = Context.getLocalClass().get();
var handleExprs:Array<Expr> = [];
var className = Context.getLocalClass().get().name + "RPC";
var ifaceType = null;
var c = macro class $className extends macros.rpc.RPCBase {
public function new(id:kernel.net.Package.NetworkID) {
super(id, $v{className});
}
}
switch (iface.expr) {
case EConst(CIdent(i)):
var t = Context.getType(i);
ifaceType = TypeTools.toComplexType(t);
switch (t) {
case TInst(t2, params):
var fields = t2.get().fields.get();
for (field in fields) {
switch (field.type) {
case TFun(args, ret):
var argsExprs:Array<Expr> = [for (a in args) macro $i{a.name}];
for (field in fields) {
if (field.meta == null)
continue;
if (field.meta.exists((i) -> i.name == "rpc") == false)
continue;
switch (field.kind) {
case FFun(f):
var argsExprs:Array<Expr> = [for (a in f.args) macro $i{a.name}];
var convertedArgs = [];
for (a in f.args) {
a.type = Helper.resolveType(a.type, field.pos);
convertedArgs.push(a);
}
var rtn = if (Helper.isPromise(ComplexTypeTools.toType(f.ret))) {
TypeTools.toComplexType(Helper.getPromiseType(f.ret));
} else {
Helper.resolveType(f.ret, field.pos);
}
c.fields.push({
name: field.name,
pos: field.pos,
kind: FFun({
args: convertedArgs,
expr: macro {
return cast this._performRequest($v{field.name}, $a{argsExprs});
},
ret: Helper.newPromise(rtn),
}),
access: [APublic],
doc: null,
meta: [],
});
default:
Context.error("Only functions can be used for rpc", field.pos);
}
}
haxe.macro.Context.defineType(c);
return fields;
}
macro static public function generateRPCPackageHandle() {
#if display
return macro {};
#end
var proto = Context.getLocalClass().get().name + "RPC";
var exprs:Array<Expr> = [];
var fields = Context.getLocalClass().get().fields.get();
for (field in fields) {
if (field.meta == null)
continue;
if (!field.meta.has("rpc"))
continue;
switch (field.kind) {
case FMethod(k):
var funName = field.name;
var exprsType = field.expr().t;
switch (exprsType) {
case TFun(args, ret):
var callArgs = [for (k => v in args) macro pack.data.args[$v{k}]];
if (Helper.isVoid(ret)) {
exprs.push(macro {
if (pack.data.func == $v{funName}) {
this.$funName($a{callArgs});
pack.respond(null);
}
});
} else if (Helper.isPromise(ret)) {
exprs.push(macro {
if (pack.data.func == $v{funName}) {
this.$funName($a{callArgs}).handle((r) -> {
pack.respond(r);
var convertedArgs:Array<FunctionArg> = [];
for (a in args) {
convertedArgs.push({
name: a.name,
opt: a.opt,
type: TypeTools.toComplexType(a.t)
});
}
});
} else {
exprs.push(macro {
if (pack.data.func == $v{funName}) {
pack.respond(this.$funName($a{callArgs}));
var retComplexType = TypeTools.toComplexType(ret);
var retType:ComplexType = if (Helper.isPromise(ret)) {
TypeTools.toComplexType(Helper.getPromiseType(retComplexType));
} else {
retComplexType;
}
});
buildFields.push({
name: field.name,
pos: localClass.pos,
access: [APublic, AInline],
kind: FFun({
args: convertedArgs,
expr: macro {
return cast this._performRequest($v{field.name}, $a{argsExprs});
},
ret: Helper.newPromise(retType),
}),
});
// Add expr for package handle
// TODO: this needs to be more efficient. Maybe use a switch case.
var callArgs = [for (k => v in args) macro p.data.args[$v{k}]];
var funName = field.name;
if (Helper.isVoid(ret)) {
handleExprs.push(macro {
if (p.data.func == $v{funName}) {
d.$funName($a{callArgs});
p.respond(null);
return true;
}
true;
});
} else if (Helper.isPromise(ret)) {
handleExprs.push(macro {
if (p.data.func == $v{funName}) {
d.$funName($a{callArgs}).handle((r) -> {
p.respond(r);
return true;
});
}
true;
});
} else {
handleExprs.push(macro {
if (p.data.func == $v{funName}) {
p.respond(d.$funName($a{callArgs}));
return true;
}
// HACK: I not a 100% sure why this need to be here but it does not work without it.
// My guess is that a macro has to be able to be evaluated to a value and a simple if
// statement is of type Void. I don't think this last true statement will have an impact
// on the resulting Expr.
true;
});
}
default:
Context.warning("IDFK man", field.pos);
}
default:
Context.error("Only functions can be used for rpc", field.pos);
}
default:
Context.error("Only functions can be used for rpc", field.pos);
}
}
default:
Context.error("Only Interfaces are supported", iface.pos);
}
default:
Context.error("Only Interfaces are supported", iface.pos);
}
return macro {
kernel.net.Net.registerProto($v{proto}, (pack) -> {
$a{exprs}
});
};
// Add handle package static method
buildFields.push({
name: "handlePackage",
access: [APublic, AStatic],
pos: localClass.pos,
kind: FFun({
args: [
{
name: "d",
type: ifaceType,
opt: false,
},
{
name: "p",
type: macro :kernel.net.Package.GenericPackage,
opt: false,
},
],
expr: macro {
$a{handleExprs} return false;
}
})
});
return buildFields;
}
}