Compare commits

...

65 Commits

Author SHA1 Message Date
c665401fd8 use .call instead of native in Inventory peripheral 2025-05-23 15:39:44 +02:00
2799a0be3d added GeoScanner peripheral 2024-10-27 16:08:38 +01:00
e527dd5b6a added simple 3D navigation 2024-10-27 16:07:23 +01:00
fe17b4fd67 added inspect command to turtleCtl 2024-10-20 23:23:58 +02:00
90b4015ba1 get chunk from position 2024-10-20 22:20:33 +02:00
2dd85c2b26 added Tags class for list of classes 2024-10-20 22:18:23 +02:00
1d60e13792 idfk 2024-10-18 16:48:38 +02:00
9d6e8a366b fixed SinglePromise reset order 2024-10-18 16:37:46 +02:00
5167533c6d Ignore GPS messages from other dimension
refs #2
2024-10-17 00:14:39 +02:00
aac527ae89 added a timeout and improvements to GPS and INS
closes #6
2024-10-16 23:45:53 +02:00
af6a4c840b added SinglePromise and SingleTimeoutPromise 2024-10-16 23:42:21 +02:00
afbd1dfd68 improved Vector stuff 2024-10-16 23:39:07 +02:00
b305594ea4 improved networking 2024-10-15 01:19:36 +02:00
df7991763d added PaN 2024-10-14 21:42:52 +02:00
e0f8d274e7 renamed Pos and Pos3 2024-10-14 21:40:26 +02:00
08f41ccb0b constrained vec to only accept numbers 2024-10-14 12:11:14 +02:00
28ec48cc85 added PriorityQueue for later use 2024-10-12 20:58:10 +02:00
4f2b6e7c53 fixed uninitialized children array in RootElement 2024-08-17 01:11:38 +02:00
2e0bda7a6e renamed Perf to Peri 2024-08-17 00:58:01 +02:00
87c93f3ae0 TurtleCtl times parameter optional 2024-08-17 00:57:03 +02:00
628aef06e3 oops 2024-08-17 00:55:52 +02:00
2603527b67 pretty error reporting 2024-08-17 00:48:53 +02:00
39a7da716c improved logging system 2024-08-17 00:47:15 +02:00
afc0309222 switched from yarn to npm 2024-08-13 00:13:24 +02:00
0d9ce5d6b1 start terminal on boot when in debug 2024-08-13 00:11:37 +02:00
be58ed1c05 removed unused files 2024-08-13 00:10:47 +02:00
fe16799bbb added inventory peripheral 2024-08-12 15:16:32 +02:00
5f5e899f6e rewrote circle mine to use plans 2024-08-11 20:18:25 +02:00
8546659ae0 first implementation of turtle plans 2024-08-11 20:17:37 +02:00
249a48807a use digEmpty function in tunnel command 2024-08-05 17:09:23 +02:00
be405887e2 added digEmpty function 2024-08-05 17:09:02 +02:00
73e2ccdcb8 added turtle helper: combine 2024-08-05 17:01:41 +02:00
b05dae958d tunnel command to turtle ctl 2024-08-05 16:58:46 +02:00
3e993b84eb added turtle ctl dig command 2024-08-05 15:41:03 +02:00
09fef78f9a added doc for applications 2024-08-05 14:39:16 +02:00
7d30d9d971 added build doc 2024-08-05 13:59:23 +02:00
29285641a4 removed unused import 2024-08-05 13:54:31 +02:00
37eab6f8e2 debug guard for build info 2024-08-05 13:53:33 +02:00
f95c262c57 put new cli parser to use in CLIAppBase 2024-05-08 16:08:35 +02:00
5f42941d76 added cli arg parser 2024-05-08 16:07:11 +02:00
1108eab403 fixed line printing in Terminal 2024-05-08 15:33:40 +02:00
e5b990ae61 added CircleMine bin 2024-05-03 22:45:26 +02:00
5925f851c4 trace can now be used in debug mode 2024-05-02 15:09:21 +02:00
d13831f213 added DroneInterface peripheral stub #12 2024-05-02 15:07:40 +02:00
a9f6adcd9d added refuel functions to TurtleCtl 2024-05-02 15:04:15 +02:00
757d7098cf changed Item to be an enum abstract 2024-05-02 15:03:51 +02:00
4e69bda3c8 added fuel related functions to InvManager 2024-05-02 15:03:19 +02:00
755e5ff6b4 added getSlotsByCount 2024-05-02 15:02:51 +02:00
9353ccba8a refactored InvManager and State 2024-04-25 00:36:48 +02:00
5c71ab1c30 removed unused turtle stuff 2024-04-22 22:41:38 +02:00
36f97f09d5 redone turtle cli 2024-04-22 22:40:55 +02:00
e04021425a added native serialization for kv store
available as a flag: kv_use_native
2024-04-19 13:00:13 +02:00
d89956a626 added scroll back to terminal 2024-04-19 11:18:37 +02:00
dd146d1caf fixed inspect for Peripherals 2024-04-18 23:11:16 +02:00
adb758bd53 added unpack & moved build stuff 2024-04-16 11:05:28 +02:00
baae1428bd added exporter service 2024-04-13 12:08:02 +02:00
7bd18c1a4c added exporter and interface for redstone 2024-04-13 12:07:46 +02:00
99489e37df fixed hardcoded redstone type name 2024-04-13 12:06:55 +02:00
86f6dbf302 added Pocket 2024-04-10 14:40:45 +02:00
4836cae3fa added speaker peripheral 2024-04-09 20:07:36 +02:00
69ca3f3282 added RPC to Concepts 2024-03-21 00:22:53 +01:00
3484503ba1 added NameSystem 2024-03-21 00:03:15 +01:00
1efa8fa212 renamed to nameserver in kernel settings 2024-03-21 00:02:57 +01:00
9541b653b5 fixed ServiceManager not loading kvstore 2024-03-21 00:01:37 +01:00
4ab3d868c1 interface name consistency 2024-03-13 10:39:22 +01:00
121 changed files with 3810 additions and 1426 deletions

View File

@@ -1,32 +1,57 @@
BUNDLE_NAME = bundle.lua
HAXE_NAME = haxe.lua
MINIFYD_NAME = bundle.min.lua
HAXE_ZIP_NAME = "haxe.zip"
UNPACK_NAME = unpack.lua
UNPACK_POLYFILLED_NAME = unpack.polyfill.lua
UNPACK_MINIFYD_NAME = unpack.min.lua
BUILD_DIR = build
HAXE_FLAGS =
HAXE_FLAGS = -D message.reporting=pretty
POLYFILLED_NAME = bundle.polyfill.lua
POLYFILL_SRC = src/polyfill.lua
CREAFTOS_PATH = craftos
BUILD_HXML = build.hxml
HAXE_PATH := $(BUILD_DIR)/$(HAXE_NAME)
MIN_PATH := $(BUILD_DIR)/$(MINIFYD_NAME)
POLYFILL_PATH := $(BUILD_DIR)/$(POLYFILLED_NAME)
UNPACK_PATH := $(BUILD_DIR)/$(UNPACK_NAME)
UNPACK_MINIFYD_PATH := $(BUILD_DIR)/$(UNPACK_MINIFYD_NAME)
HAXE_ZIP_PATH := $(BUILD_DIR)/$(HAXE_ZIP_NAME)
UNPACK_POLYFILLED_PATH := $(BUILD_DIR)/$(UNPACK_POLYFILLED_NAME)
all: clean build
all: clean build unpack
build: HAXE_FLAGS += --main kernel.Entrypoint -D analyzer-optimize
build: HAXE_FLAGS += -D analyzer-optimize -D no-traces
build: $(MIN_PATH)
debug: HAXE_FLAGS += -D webconsole -D error_stack --debug
debug: build
debug: $(MIN_PATH)
unpack: $(UNPACK_MINIFYD_PATH) $(HAXE_ZIP_PATH)
$(HAXE_PATH): HAXE_FLAGS += --main kernel.Entrypoint --lua $(HAXE_PATH)
$(HAXE_PATH): $(shell find src -name '*.hx')
haxe build.hxml $(HAXE_FLAGS)
haxe $(BUILD_HXML) $(HAXE_FLAGS)
$(POLYFILL_PATH): $(POLYFILL_SRC) $(HAXE_PATH)
cat $(POLYFILL_SRC) $(HAXE_PATH) > $@
$(MIN_PATH): $(POLYFILL_PATH)
node minify.js $(POLYFILL_PATH) $@
node tools/minify.js < $(POLYFILL_PATH) > $@
$(HAXE_ZIP_PATH): $(MIN_PATH)
node tools/zlibDeflate.js < $(MIN_PATH) > $@
$(UNPACK_PATH): HAXE_FLAGS += --main Unpack -D analyzer-optimize --lua $(UNPACK_PATH)
$(UNPACK_PATH): $(shell find src -name '*.hx')
haxe $(BUILD_HXML) $(HAXE_FLAGS)
$(UNPACK_POLYFILLED_PATH): $(UNPACK_PATH)
cat $(POLYFILL_SRC) $(UNPACK_PATH) > $@
$(UNPACK_MINIFYD_PATH): $(UNPACK_POLYFILLED_PATH)
node tools/minify.js < $(UNPACK_POLYFILLED_PATH) > $@
.PHONY: deps
deps: deps-hx deps-node
@@ -37,7 +62,7 @@ deps-hx:
.PHONY: deps-node
deps-node:
yarn install
npm install
.PHONY: clean
clean:
@@ -54,7 +79,7 @@ emulator:
.PHONY: webconsole
webconsole:
node console.js
node tools/console.js
.PHONY: format
format:

View File

@@ -10,5 +10,3 @@
-D lua-ver 5.1
--macro include("bin")
--lua build/haxe.lua

View File

@@ -1,32 +0,0 @@
const http = require('http');
function time() {
let now = new Date();
return `${now.getHours().toString().padStart(2, "0")}:${now.getMinutes().toString().padStart(2, 0)}:${now.getSeconds().toString().padStart(2, 0)}`;
}
const server = http.createServer((req, res) => {
if (req.method != "POST") {
res.writeHead(400);
return;
}
var id = req.url.substring(1);
let data = "";
req.on('data', chunk => {
data += chunk;
})
req.on('end', () => {
console.log(`[${time()}][${id}]${data}`);
res.writeHead(200);
res.end();
})
});
console.log("Listening on port 8080")
server.listen(8080);

View File

@@ -57,7 +57,7 @@ Also peripherals can be made accessible via the network. More on that later.
var back = Peripheral.getRedstone("back");
back.setOutput(true);
var drive Peripheral.getDrive("drive_0");
var drive = Peripheral.getDrive("drive_0");
drive.eject();
```
@@ -111,10 +111,9 @@ A collection of `Pixel` is called a `Canvas` which is nothing more than a 2D arr
The concept of processes tryes to encapsulate programs. A process is basically an interface with the `run(handle: ProcessHandle)` method.
The idea is that you can register all you disposable resources in the handle and they will be disposed when the process is killed or crashes.
A process can be used as a command on the terminal or as a service. See [bin/HelloWorld.hx](../src/bin/HelloWorld.hx) for an example.
Basically everything that runs and is not part of the kernel is a process.
A process can be used as a command on the terminal or as a service. Basically everything that runs and is not part of the kernel is a process.
In order for you program to be used it needs to be registered in the `BinStore` and the `DCEHack` manually.
More on that at [Applications](#applications).
# EndOfLoop
@@ -156,3 +155,99 @@ TurtleMutex.runInTThread(() -> {
}
});
```
# RPC
With the help of dark and badly documented magic also known as "macros", we can create quickly create remote procedure call Classes to call functions on other
computers. A problem that arises is that since all data gets send over the network that we kinda lose the type safty. We cloud trust ourself to cast
the result of the request to the right type or we cloud just make use of macros. The RPC macro will create a RPC class that implements all functions of an
interface just that the return type is wrapped in a Promise. Now if we call a function of that RPC class it fires a request to the other computer and waits
for a response. On the other side the service makes use of that generated package handle function for the RPC class.
A simple example:
```haxe
interface IExampleRPC {
function addNumber(a:Int, b:Int):Int;
}
@:build(macros.rpc.RPC.buildRPC(IExampleRPC))
class ExampleRPC extends RPCBase {}
@:build(macros.Binstore.includeBin("Example SRV", ["example-srv"]))
class ExampleService implements IProcess implements IExampleRPC {
private var handle:ProcessHandle;
public function new() {}
public function run(handle:ProcessHandle) {
this.handle = handle;
kernel.net.Net.registerProto("example", (pack) -> {
ExampleRPC.handlePackage(this, pack);
});
}
public function addNumber(a:Int, b:Int):Int {
return a + b;
}
}
// ...
var rpc = new ExampleRPC(12,"example");
rpc.addNumber(3,7).handle((p)->{
switch p {
case Success(r):
Log.info('3+7=$r');
case Failure(err):
Log.error('Error: $err');
}
});
```
# Build system and flags
We use `make` to build the project. If you want a prod build just run `make` and if you want the debug build run `make debug`.
The `build` directory should contain all needed files.
- `bundle.min.lua`: The minified final file
- `bundle.polyfill.lua`: The same as above just not minified. use this when debugging.
- `haxe.lua`: Intermediate file. Polyfill not yet added.
- `haxe.zip`: The compressed `bundle.min.lua`.
- `unpack.*`: Same as the bundle files. Used to unpack and run the `haxe.zip` file.
The follwing haxe flags can be used in the code or can be set in the makefile.
- `debug`: Is set when debug
- `kv_use_native`: If set use the native CC serialize and unserialize functions instead of the Haxe ones. See [KVStore.hx](../src/lib/KVStore.hx) for more.
# Applications
If you want to make an application you just need to create a class that implements IProcess and and has no arguments in the constructor.
In order to have the programm available from the terminal you need to add the `Binstore.includeBin` macro for the class. Here is an example:
```haxe
@:build(macros.Binstore.includeBin("HelloWorld", ["hello", "helloworld"]))
class HelloWorld implements IProcess {
public function new() {}
public function run(handle:ProcessHandle) {
handle.writeLine("Hello World");
handle.close(true);
}
}
```
Let's break this down:
- The `includeBin` macro is required to automaticly inlcude the app and to defeat the DCE optimisation. In early versions refections were used but DCE did not like that
- The first argument on `includeBin` is the name of the app. Only ever used in displaying.
- The second argument are the aliases. These are used to call the app from the terminal and other places
- The constructor can't have any arguments. It's also recommended to not do any major setup stuff in there.
- The `run` method is the entry point in the app.
- The handle is used to interact with the outside world. (theoretically at least)
- The `handle.close(true)` call is required to end a process. The app does not end when the run function is finished. The first arguments represents if the app was successfull.

View File

@@ -1,10 +0,0 @@
const fs = require("fs");
const luamin = require("luamin");
const haxeOutput = fs.readFileSync(process.argv[2] ?? "build/Haxe.min.lua",{encoding:"utf8"});
const minified = luamin.minify(haxeOutput);
fs.writeFileSync(process.argv[3] ?? "build/Haxe.min.lua",minified);
console.log("minified lua");

35
package-lock.json generated Normal file
View File

@@ -0,0 +1,35 @@
{
"name": "haxe",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "haxe",
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"luamin": "^1.0.4"
}
},
"node_modules/luamin": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/luamin/-/luamin-1.0.4.tgz",
"integrity": "sha1-lEUptY/G+k0x6s4uA1PUEhDw49M= sha512-z1h0bclRD/QGsS/Dz4Skp9z0qPTmmm+IKcrCapGmdTczPWVdN9f9jk6WqkNrcifQr8+n9Pzsm9WkwpOH1JBUAQ==",
"dependencies": {
"luaparse": "^0.2.1"
},
"bin": {
"luamin": "bin/luamin"
}
},
"node_modules/luaparse": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/luaparse/-/luaparse-0.2.1.tgz",
"integrity": "sha1-qo9WEysN6X0388mRqd9C4OF/ZWw= sha512-VKBcryd5nJte4ZNR29NOk8F/UkMipjeb4yoxcSS51z6QAzg9DXUC2WsfLniS0J1eh3pr/ZL3e9ha6V8fhoLbBQ==",
"bin": {
"luaparse": "bin/luaparse"
}
}
}
}

View File

@@ -6,6 +6,14 @@ import lib.HomeContext;
class Startup {
public static function main() {
#if debug
var term = BinStore.instantiate("terminal");
var pid = ProcessManager.run(term, {
// args: ["debug"]
});
var ctx = WindowManager.getContextByPID(pid);
WindowManager.focusContextToOutput(ctx[0], "main");
#else
if (MainTerm.instance.isColor()) {
var main = new HomeContext();
@@ -16,5 +24,6 @@ class Startup {
var ctx = WindowManager.getContextByPID(pid);
WindowManager.focusContextToOutput(ctx[0], "main");
}
#end
}
}

29
src/Unpack.hx Normal file
View File

@@ -0,0 +1,29 @@
import lua.Lua;
import haxe.io.Bytes;
import kernel.fs.FS;
import haxe.zip.Uncompress;
class Unpack {
public static function main() {
var filename = "/haxe.zip";
var handle = FS.openReadBinary(filename);
var size = FS.attributes(filename).size;
var data = Bytes.alloc(size);
for (i in 0...size) {
data.set(i, handle.readByte());
}
var uncompressed = Uncompress.run(data);
var res = Lua.load(uncompressed.toString()); // FIXME: Haxe is missing some parameters. This does not work.
var f = res.func; // Required for silly haxe bug.
if (res.message == null) {
f();
} else {
trace('Failed: ${res.message}');
}
}
}

View File

@@ -1,5 +1,6 @@
package bin;
import kernel.peripherals.Drive;
import lib.CLIAppBase;
import kernel.peripherals.Peripherals.Peripheral;
@@ -30,37 +31,17 @@ class Disk extends CLIAppBase {
});
registerSyncSubcommand("play", (args) -> {
if (args.length < 1) {
handle.writeLine("Missing drive address");
return false;
}
return audioDiskPlayPause(args[0], true);
}, "<drive>");
return audioDiskPlayPause(args.getString("addr"), true);
}, [Peripheral("addr", Drive.TYPE_NAME)]);
registerSyncSubcommand("stop", (args) -> {
if (args.length < 1) {
handle.writeLine("Missing drive address");
return false;
}
return audioDiskPlayPause(args[0], false);
}, "<drive>");
return audioDiskPlayPause(args.getString("addr"), false);
}, [Peripheral("addr", Drive.TYPE_NAME)]);
registerSyncSubcommand("eject", (args) -> {
if (args.length < 1) {
handle.writeLine("Missing drive address");
return false;
}
var driveAddr = args[0];
var driveAddr = args.getString("addr");
var drive = Peripheral.getDrive(driveAddr);
if (drive == null) {
handle.writeLine("Drive not found: " + driveAddr);
return false;
}
if (!drive.isDiskPresent()) {
handle.writeLine("No disk in drive: " + driveAddr);
return false;
@@ -68,21 +49,12 @@ class Disk extends CLIAppBase {
drive.ejectDisk();
return true;
}, "<drive>");
}, [Peripheral("addr", Drive.TYPE_NAME)]);
registerSyncSubcommand("lable", (args) -> {
if (args.length < 1) {
handle.writeLine("Missing drive address");
return false;
}
var driveAddr = args[0];
registerSyncSubcommand("label", (args) -> {
var driveAddr = args.getString("addr");
var drive = Peripheral.getDrive(driveAddr);
var label:String = args[1];
if (drive == null) {
handle.writeLine("Drive not found: " + driveAddr);
}
var label:Null<String> = args.getString("label");
if (!drive.isDiskPresent()) {
handle.writeLine("No disk in drive: " + driveAddr);
@@ -99,7 +71,7 @@ class Disk extends CLIAppBase {
}
return true;
}, "<drive> [label]");
}, [Peripheral("addr", Drive.TYPE_NAME), Optional(String("label"))]);
}
private function audioDiskPlayPause(driveAddr:String, play:Bool):Bool {

View File

@@ -2,7 +2,7 @@ package bin;
import lib.CLIAppBase;
import kernel.gps.INS;
import lib.Pos3;
import lib.WorldPos;
import lib.Vec.Vec3;
using tink.CoreApi;
@@ -11,16 +11,16 @@ using tink.CoreApi;
class GPS extends CLIAppBase {
public function new() {
registerSyncSubcommand("set", (args) -> {
var x:Float = Std.parseFloat(args[0]);
var y:Float = Std.parseFloat(args[1]);
var z:Float = Std.parseFloat(args[2]);
var x:Float = args.getFloat("x");
var y:Float = args.getFloat("y");
var z:Float = args.getFloat("z");
var pos:Pos3 = new Vec3<Float>(x, y, z);
var pos:WorldPos = new Vec3<Float>(x, y, z);
kernel.gps.GPS.setManualPosition(pos);
return true;
}, "<x> <y> <z>");
}, [Float("x"), Float("y"), Float("z")]);
registerSyncSubcommand("status", (args) -> {
var pos = kernel.gps.GPS.getPosition();
@@ -51,11 +51,12 @@ class GPS extends CLIAppBase {
});
registerAsyncSubcommand("locate", (args) -> {
return kernel.gps.GPS.locate().map((pos) -> {
if (pos != null) {
handle.writeLine('Position x:${pos.x} y:${pos.y} z:${pos.z}');
} else {
handle.writeLine("Position not available");
return kernel.gps.GPS.locate().map((result) -> {
switch result {
case Success(pos):
handle.writeLine('Position x:${pos.x} y:${pos.y} z:${pos.z}');
case Failure(err):
handle.writeLine("Position not available: " + err);
}
return true;
});

View File

@@ -1,10 +1,10 @@
package bin;
import kernel.ps.ProcessHandle;
import kernel.ps.Process;
import kernel.ps.IProcess;
@:build(macros.Binstore.includeBin("ID", ["id"]))
class ID implements Process {
class ID implements IProcess {
public function new() {}
public function run(handle:ProcessHandle) {

View File

@@ -7,18 +7,13 @@ import lib.CLIAppBase;
class KSettings extends CLIAppBase {
public function new() {
registerSyncSubcommand("get", (args) -> {
var key = args[0];
if (key == null) {
handle.writeLine("Key not specified");
return false;
}
var key = args.getString("key");
var value = switch (key) {
case "hostname":
KernelSettings.hostname;
case "sitecontroller":
Std.string(KernelSettings.siteController);
case "nameServer":
Std.string(KernelSettings.nameServer);
default:
null;
}
@@ -30,17 +25,17 @@ class KSettings extends CLIAppBase {
handle.writeLine(value);
return true;
}, " <key>");
}, [String("key")]);
registerSyncSubcommand("set", (args) -> {
var key = args[0];
var key = args.getString("key");
if (key == null) {
handle.writeLine("Key not specified");
return false;
}
var value = args[1];
var value = args.getString("value");
if (value == null) {
handle.writeLine("Value not specified");
@@ -50,18 +45,18 @@ class KSettings extends CLIAppBase {
switch (key) {
case "hostname":
KernelSettings.hostname = value;
case "sitecontroller":
KernelSettings.siteController = Std.parseInt(value);
case "nameServer":
KernelSettings.nameServer = Std.parseInt(value);
default:
handle.writeLine("Key not found");
return false;
}
return true;
}, " <key> <value>");
}, [String("key"), String("value")]);
registerSyncSubcommand("list", (args) -> {
handle.writeLine("hostname");
handle.writeLine("sitecontroller");
handle.writeLine("nameServer");
return true;
});
}

View File

@@ -1,17 +1,16 @@
package bin;
import kernel.ps.ProcessHandle;
import kernel.ps.Process;
import kernel.ps.IProcess;
import lib.Color;
import lib.MathI;
import kernel.log.Log;
import kernel.ui.WindowContext;
import lib.ui.UIApp;
using tink.CoreApi;
@:build(macros.Binstore.includeBin("Log", ["log"]))
class KernelLog implements Process {
class KernelLog implements IProcess {
private var handle:ProcessHandle;
private var ctx:WindowContext;

View File

@@ -2,10 +2,10 @@ package bin;
import kernel.ps.ProcessManager;
import kernel.ps.ProcessHandle;
import kernel.ps.Process;
import kernel.ps.IProcess;
@:build(macros.Binstore.includeBin("LSPS", ["lsps"]))
class LSPS implements Process {
class LSPS implements IProcess {
public function new() {}
public function run(handle:ProcessHandle) {

View File

@@ -40,16 +40,7 @@ class Net extends CLIAppBase {
});
registerAsyncSubcommand("ping", (args) -> {
if (args.length < 1) {
return Future.sync(false);
}
var toID:Null<Int> = Std.parseInt(args[0]);
if (toID == null) {
handle.write("Invalid ID");
return Future.sync(false);
}
var toID:Int = args.getInt("id");
return kernel.net.Net.ping(toID).map(result -> {
switch (result) {
@@ -61,6 +52,6 @@ class Net extends CLIAppBase {
return true;
});
}, "<id>");
}, [Int("id")]);
}
}

View File

@@ -4,18 +4,11 @@ import kernel.peripherals.Peripherals.Peripheral;
import lib.CLIAppBase;
@:build(macros.Binstore.includeBin("Peripheral", ["ph", "peripheral"]))
class Perf extends CLIAppBase {
class Peri extends CLIAppBase {
public function new() {
registerSyncSubcommand("inspect", (args) -> {
if (args.length < 1)
return false;
var result = Peripheral.inspect(args[0]);
if (result == null) {
handle.writeLine("No peripheral found on side " + args[0]);
return true;
}
var addr = args.getString("addr");
var result = Peripheral.inspect(addr);
handle.writeLine("Types:");
for (type in result.types) {
@@ -28,7 +21,7 @@ class Perf extends CLIAppBase {
}
return true;
}, "<side>");
}, [Addr("addr")]);
registerSyncSubcommand("list", (args) -> {
for (addr in Peripheral.getAllAddresses()) {

View File

@@ -9,19 +9,19 @@ using tink.CoreApi;
class Redstone extends CLIAppBase {
public function new() {
registerSyncSubcommand("on", (args) -> {
Peripheral.getRedstone(args[0]).setOutput(true);
Peripheral.getRedstone(args.getString("side")).setOutput(true);
return true;
}, "<side>");
}, [Side("side")]);
registerSyncSubcommand("off", (args) -> {
Peripheral.getRedstone(args[0]).setOutput(false);
Peripheral.getRedstone(args.getString("side")).setOutput(false);
return true;
}, "<side>");
}, [Side("side")]);
registerSyncSubcommand("get", (args) -> {
var value = Peripheral.getRedstone(args[0]).getAnalogInput();
var value = Peripheral.getRedstone(args.getString("side")).getAnalogInput();
handle.write("Analog input: " + value);
return true;
}, "<side>");
}, [Side("side")]);
}
}

View File

@@ -9,50 +9,28 @@ using tink.CoreApi;
class Service extends CLIAppBase {
public function new() {
registerSyncSubcommand("start", (args) -> {
if (args.length < 1) {
return false;
}
var name = args[0];
var result = ServiceManager.start(name);
var result = ServiceManager.start(args.getString("name"));
return handleResult(result);
}, "<name>");
}, [String("name")]);
registerSyncSubcommand("stop", (args) -> {
if (args.length < 1) {
return false;
}
var name = args[0];
var result = ServiceManager.stop(name);
var result = ServiceManager.stop(args.getString("name"));
return handleResult(result);
}, "<name>");
}, [String("name")]);
registerSyncSubcommand("register", (args) -> {
if (args.length < 2) {
return false;
}
var name = args[0];
var binName = args[1];
var rest = args.slice(2);
var name = args.getString("name");
var binName = args.getString("binary");
var rest = args.getRest();
var result = ServiceManager.register(name, binName, rest);
return handleResult(result);
}, "<name> <binary> [args...]");
}, [String("name"), String("binary"), Rest("args")]);
registerSyncSubcommand("unregister", (args) -> {
if (args.length < 2) {
return false;
}
var name = args[0];
var result = ServiceManager.unregister(name);
var result = ServiceManager.unregister(args.getString("name"));
return handleResult(result);
}, "<name>");
}, [String("name")]);
registerSyncSubcommand("list", (args) -> {
var list = ServiceManager.listRunning();
@@ -65,13 +43,9 @@ class Service extends CLIAppBase {
});
registerSyncSubcommand("enable", (args) -> {
if (args.length < 1) {
return false;
}
ServiceManager.enable(args[0]);
ServiceManager.enable(args.getString("name"));
return true;
}, "<name>");
}, [String("name")]);
}
private function handleResult(res:Outcome<Noise, String>):Bool {

71
src/bin/Speaker.hx Normal file
View File

@@ -0,0 +1,71 @@
package bin;
import kernel.peripherals.Peripherals.Peripheral;
import lib.CLIAppBase;
using tink.CoreApi;
@:build(macros.Binstore.includeBin("Speaker", ["speaker", "sp"]))
class Speaker extends CLIAppBase {
public function new() {
registerSyncSubcommand("note", (args) -> {
var sp = Peripheral.getSpeaker(args.getString("addr"));
var note = args.getString("note");
var r;
if (args.hasArg("pitch")) {
if (args.hasArg("volume")) {
r = sp.playNote(note, args.getFloat("volume"), args.getInt("pitch"));
} else {
r = sp.playNote(note, args.getFloat("volume"));
}
} else {
r = sp.playNote(note);
}
switch r {
case Failure(failure):
handle.writeLine(failure);
return false;
case Success(_):
return true;
}
}, [
Peripheral("addr", kernel.peripherals.Speaker.TYPE_NAME),
String("note"),
Optional(Int("pitch")),
Optional(Float("volume"))
]);
registerSyncSubcommand("sound", (args) -> {
var sp = Peripheral.getSpeaker(args.getString("addr"));
var sound = args.getString("sound");
var r;
if (args.hasArg("volume")) {
if (args.hasArg("pitch")) {
r = sp.playSound(sound, args.getFloat("volume"), args.getFloat("pitch"));
} else {
r = sp.playSound(sound, args.getFloat("volume"));
}
} else {
r = sp.playSound(sound);
}
switch r {
case Failure(failure):
handle.writeLine(failure);
return false;
case Success(_):
return true;
}
}, [
Peripheral("addr", kernel.peripherals.Speaker.TYPE_NAME),
String("sound"),
Optional(Float("pitch")),
Optional(Float("volume"))
]);
}
}

View File

@@ -1,10 +1,11 @@
package bin;
import lib.MathI;
import kernel.EndOfLoop;
import lua.NativeStringTools;
import kernel.binstore.BinStore;
import kernel.ps.ProcessHandle;
import kernel.ps.Process;
import kernel.ps.IProcess;
import kernel.ps.ProcessManager;
import lib.Color;
import kernel.ui.WindowContext;
@@ -12,7 +13,7 @@ import kernel.ui.WindowContext;
using tink.CoreApi;
@:build(macros.Binstore.includeBin("Terminal", ["terminal"]))
class Terminal implements Process {
class Terminal implements IProcess {
private static inline final MAX_BACKLOG:Int = 100;
private var handle:ProcessHandle;
@@ -25,6 +26,8 @@ class Terminal implements Process {
private var history:Array<String> = [];
private var historyIndex:Int = 0;
private var scrollBack = 0;
private var runningPID:PID = -1;
public function new() {}
@@ -59,8 +62,11 @@ class Terminal implements Process {
if (this.runningPID > 0)
return;
this.backlog.push("> " + this.input);
this.backlog.push("");
var command = this.input;
this.input = "";
this.scrollBack = 0;
this.requestRender();
this.historyIndex = 0;
this.invokeCommand(command);
@@ -72,6 +78,12 @@ class Terminal implements Process {
this.input = this.history[this.history.length - this.historyIndex];
this.requestRender();
}
case 266: // PAGE UP
this.scrollBack = MathI.min(scrollBack + 1, this.backlog.length - 1);
this.requestRender();
case 267: // PAGE DOWN
this.scrollBack = MathI.max(scrollBack - 1, 0);
this.requestRender();
}
}));
@@ -81,6 +93,8 @@ class Terminal implements Process {
var arg1 = handle.args[0];
this.backlog.push("> " + arg1);
this.backlog.push("");
EndOfLoop.endOfLoop(() -> {
this.invokeCommand(arg1);
});
@@ -99,7 +113,8 @@ class Terminal implements Process {
var size = this.ctx.getSize();
var linesAvailable = size.y - 1;
var start:Int = this.backlog.length - linesAvailable;
var withoutScrollBack = (this.backlog.length - linesAvailable);
var start:Int = withoutScrollBack - scrollBack;
for (i in 0...linesAvailable) {
var line = this.backlog[start + i];
@@ -166,24 +181,13 @@ class Terminal implements Process {
return;
}
var mfunc = NativeStringTools.gmatch(s, "(.-)(\n)");
var splits = s.split("\n");
while (true) {
var found = mfunc();
if (found == null) {
break;
}
if (found == "\n") {
this.backlog.push("");
for (i => split in splits) {
if (i == 0) {
this.backlog[this.backlog.length - 1] += split;
} else {
this.backlog.push(found);
}
// Trim the backlog if it's too long
if (this.backlog.length > MAX_BACKLOG) {
this.backlog.shift();
this.backlog.push(split);
}
}

View File

@@ -1,62 +0,0 @@
package bin;
import lib.turtle.InvManager;
import lib.CLIAppBase;
using tink.CoreApi;
@:build(macros.Binstore.includeBin("Turtle", ["turtle", "t"]))
class Turtle extends CLIAppBase {
public function new() {
registerSyncSubcommand("forward", (args) -> {
return checkAvailable() && perform(kernel.turtle.Turtle.forward());
});
registerSyncSubcommand("back", (args) -> {
return checkAvailable() && perform(kernel.turtle.Turtle.back());
});
registerSyncSubcommand("left", (args) -> {
return checkAvailable() && perform(kernel.turtle.Turtle.turnLeft());
});
registerSyncSubcommand("right", (args) -> {
return checkAvailable() && perform(kernel.turtle.Turtle.turnRight());
});
registerSyncSubcommand("up", (args) -> {
return checkAvailable() && perform(kernel.turtle.Turtle.up());
});
registerSyncSubcommand("down", (args) -> {
return checkAvailable() && perform(kernel.turtle.Turtle.down());
});
registerSyncSubcommand("defrag", (args) -> {
if (!checkAvailable()) {
return false;
}
InvManager.defrag();
return true;
});
}
private function checkAvailable():Bool {
if (!kernel.turtle.Turtle.isTurtle()) {
handle.write("This is not a turtle!");
return false;
}
return true;
}
private function perform(outcome:Outcome<Noise, String>):Bool {
switch outcome {
case Success(_):
return true;
case Failure(error):
handle.write(error);
return false;
}
}
}

180
src/bin/TurtleCtl.hx Normal file
View File

@@ -0,0 +1,180 @@
package bin;
import kernel.turtle.Types.ToolSide;
import lib.turtle.Helper;
import kernel.turtle.TurtleMutex;
import kernel.turtle.Turtle;
import lib.turtle.InvManager;
import lib.CLIAppBase;
using tink.CoreApi;
@:build(macros.Binstore.includeBin("Turtle", ["turtle", "t"]))
class TurtleCtl extends CLIAppBase {
public function new() {
registerAsyncSubcommand("f", (args) -> {
return asynPerform(Turtle.forward, args.getInt("times") ?? 1);
}, [Optional(Int("times"))]);
registerAsyncSubcommand("b", (args) -> {
return asynPerform(Turtle.back, args.getInt("times") ?? 1);
}, [Optional(Int("times"))]);
registerAsyncSubcommand("l", (args) -> {
return asynPerform(Turtle.turnLeft, args.getInt("times") ?? 1);
}, [Optional(Int("times"))]);
registerAsyncSubcommand("r", (args) -> {
return asynPerform(Turtle.turnRight, args.getInt("times") ?? 1);
}, [Optional(Int("times"))]);
registerAsyncSubcommand("u", (args) -> {
return asynPerform(Turtle.up, args.getInt("times") ?? 1);
}, [Optional(Int("times"))]);
registerAsyncSubcommand("d", (args) -> {
return asynPerform(Turtle.down, args.getInt("times") ?? 1);
}, [Optional(Int("times"))]);
registerAsyncSubcommand("defrag", (args) -> {
return asynPerform(() -> {
// TODO: when defrag can fail return that error
InvManager.defrag();
return Success(null);
}, 1);
});
registerSyncSubcommand("fuel", (args) -> {
var lvl = Turtle.getFuelLevel();
var limit = Turtle.getFuelLimit();
handle.writeLine('${lvl}/${limit} (${(lvl / limit) * 100}%)');
return true;
});
registerAsyncSubcommand("fuel-sources", (args) -> {
return asynPerform(() -> {
var items = InvManager.getCombustableItems();
for (i in items) {
handle.writeLine(i);
}
return Success(null);
}, 1);
});
registerAsyncSubcommand("refuel", (args) -> {
var refuelTo = Turtle.getFuelLimit();
var arg = args.getString("to");
if (arg != null) {
var split = arg.split("%");
if (split.length > 1) {
// Is percentage
var parsed = Std.parseFloat(split[0]);
if (parsed == null) {
handle.writeLine("Failed to parse ammount");
return Future.sync(false);
}
refuelTo = Math.round(refuelTo * (parsed / 100));
} else {
// Is absolute
var parsed = Std.parseInt(arg);
if (parsed == null) {
handle.writeLine("Failed to parse ammount");
return Future.sync(false);
}
refuelTo = parsed;
}
}
return asynPerform(() -> {
InvManager.refuel(refuelTo, []);
return Success(null);
}, 1);
}, [Optional(String("to"))]);
registerAsyncSubcommand("dig", (args) -> {
var direction = args.getString("direction");
switch (direction) {
case "front" | "f":
return asynPerform(() -> Turtle.dig(Front), 1);
case "top" | "t" | "up" | "u":
return asynPerform(() -> Turtle.dig(Up), 1);
case "bot" | "bottom" | "b" | "down" | "d":
return asynPerform(() -> Turtle.dig(Down), 1);
default:
return Future.sync(false);
}
}, [String("direction")]);
registerAsyncSubcommand("tunnel", (args) -> {
var len = args.getInt("length");
return asynPerform(Helper.combine.bind([Turtle.digEmpty.bind(Front), Turtle.forward, Turtle.digEmpty.bind(Up)]), len);
}, [Int("length")]);
registerSyncSubcommand("inspect", (args) -> {
var res = Turtle.inspect(Front);
switch res {
case Failure(err):
handle.writeLine("Failed: " + err);
return false;
case Success(data):
handle.writeLine('Name: ${data.name}');
handle.writeLine("Tags:");
for (tag in data.tags.sortByName()) {
handle.writeLine(' ${tag}');
}
handle.writeLine("State:");
handle.writeLine(data.state);
}
return true;
}, []);
}
private function asynPerform(op:Void->Outcome<Noise, String>, times:Int):Future<Bool> {
return new Future<Bool>((wakeup) -> {
if (times == 0) {
wakeup(false);
return null;
}
if (!Turtle.isTurtle()) {
handle.write("This is not a turtle!");
wakeup(false);
return null;
}
if (!handle.claimTurtleMutex()) {
handle.writeLine("Failed to claim mutex");
wakeup(false);
return null;
}
TurtleMutex.runInTThread(() -> {
for (i in 0...times) {
switch op() {
case Success(_):
case Failure(failure):
handle.writeLine('Failed: $failure');
wakeup(false);
return;
}
}
wakeup(true);
});
return null;
});
}
}

View File

@@ -3,7 +3,7 @@ package bin.debug;
import kernel.log.Log;
import lib.turtle.InvManager;
import kernel.ps.ProcessHandle;
import kernel.ps.Process;
import kernel.ps.IProcess;
/**
Use this to test whatever you are working on. It will also print debug statements.
@@ -12,7 +12,7 @@ import kernel.ps.Process;
#if debug
@:build(macros.Binstore.includeBin("Debug", ["dbg", "debug"]))
#end
class Debug implements Process {
class Debug implements IProcess {
public function new() {}
public function run(handle:ProcessHandle) {

View File

@@ -3,12 +3,12 @@ package bin.debug;
import bin.debug.DebugRPC.DebugRPCImpl;
import macros.rpc.RPC;
import kernel.ps.ProcessHandle;
import kernel.ps.Process;
import kernel.ps.IProcess;
#if debug
@:build(macros.Binstore.includeBin("Debug SRV", ["dbg-srv", "debug-srv"]))
#end
class DebugService implements Process implements DebugRPC {
class DebugService implements IProcess implements DebugRPC {
private var handle:ProcessHandle;
public function new() {}

View File

@@ -0,0 +1,67 @@
package bin.exporter;
import kernel.net.Package.GenericPackage;
import kernel.peripherals.exports.RedstoneExport;
import kernel.peripherals.Peripherals.Peripheral;
import kernel.ps.ProcessHandle;
import kernel.ps.IProcess;
/**
A service to expose local peripherals to the network.
**/
@:build(macros.Binstore.includeBin("Exporter", ["exporter"]))
class Exporter implements IProcess {
public function new() {}
public function run(handle:ProcessHandle) {
if (handle.args.length < 2) {
handle.writeLine("Not enough args: <addr> <type>");
handle.close(false);
}
var addr = handle.args[0];
var expectedType = handle.args[1];
// Check if peripheral is present. Not the case for redstone.
if (expectedType != kernel.peripherals.Redstone.TYPE_NAME && !Peripheral.isPresent(addr)) {
handle.writeLine('Address: $addr not present');
handle.close(false);
}
var types = Peripheral.getTypes(addr);
// Check if the correct type is present. Not the case for redstone.
if (expectedType != kernel.peripherals.Redstone.TYPE_NAME && !types.contains(expectedType)) {
handle.writeLine('Expected type not machted on address: $addr. Wanted $expectedType got $types');
handle.close(false);
}
var peri = Peripheral.getFromType(addr, expectedType);
if (peri == null) {
handle.writeLine('Failed to create peripheral for address: $addr');
handle.close(false);
}
var handePackFunc:(GenericPackage) -> Bool = null;
// Cast to RPC class
// TODO: There must be a smarter way to do this.
switch expectedType {
case kernel.peripherals.Redstone.TYPE_NAME:
handePackFunc = (p) -> {
return RedstoneExport.handlePackage(cast peri, p);
};
// TODO: More peripherals
}
handle.writeLine('Listening on export:$addr with type $expectedType');
// Listen for packages
kernel.net.Net.registerProto('export:$addr', (p) -> {
if (!handePackFunc(p)) {
handle.writeLine("Failed handle package on export");
}
});
}
}

71
src/bin/ns/NameSystem.hx Normal file
View File

@@ -0,0 +1,71 @@
package bin.ns;
import lib.KVStore;
import haxe.ds.StringMap;
import kernel.net.Package.NetworkID;
import bin.ns.NameSystemRPC.INameSystemRPC;
import kernel.ps.ProcessHandle;
import kernel.ps.IProcess;
using Lambda;
using tink.CoreApi;
@:build(macros.Binstore.includeBin("NameSystem", ["ns", "namesystem"]))
class NameSystem implements IProcess implements INameSystemRPC {
private var handle:ProcessHandle;
private var idRecords:StringMap<NetworkID> = new StringMap();
public function new() {}
public function run(handle:ProcessHandle) {
this.handle = handle;
this.load();
kernel.net.Net.registerProto("ns", (p) -> {
NameSystemRPC.handlePackage(this, p);
});
}
private function load() {
var kv = new KVStore("NameSystem");
kv.load();
this.idRecords = kv.get("idRecords", new StringMap());
}
private function save() {
var kv = new KVStore("NameSystem");
kv.set("idRecords", this.idRecords);
kv.save();
}
public function setIDRecord(name:String, id:NetworkID):Noise {
this.idRecords.set(name, id);
this.save();
return Noise;
}
public function getIDRecord(name:String):Null<NetworkID> {
return this.idRecords.get(name);
}
public function removeIDRecord(name:String):Noise {
this.idRecords.remove(name);
this.save();
return Noise;
}
public function listIDRecords():Array<{name:String, id:NetworkID}> {
var rtn = [];
for (k => v in this.idRecords) {
rtn.push({name: k, id: v});
}
return rtn;
}
}

View File

@@ -0,0 +1,71 @@
package bin.ns;
import lib.CLIAppBase;
using tink.CoreApi;
@:build(macros.Binstore.includeBin("NameSystemCLI", ["ns-cli"]))
class NameSystemCLI extends CLIAppBase {
public function new() {
registerAsyncSubcommand("get", (args) -> {
var ns = NameSystemRPC.getDefault();
return ns.getIDRecord(args.getString("name")).map((r) -> {
switch r {
case Success(data):
handle.writeLine('Resolved: $data');
case Failure(failure):
handle.writeLine('Failed: $failure');
}
return true;
});
}, [String("name")]);
registerAsyncSubcommand("register", (args) -> {
var id = args.getInt("id");
var ns = NameSystemRPC.getDefault();
return ns.setIDRecord(args.getString("name"), id).map((r) -> {
switch r {
case Success(_):
handle.writeLine('Set');
case Failure(failure):
handle.writeLine('Failed: $failure');
}
return true;
});
}, [String("name"), Int("id")]);
registerAsyncSubcommand("list", (args) -> {
var ns = NameSystemRPC.getDefault();
return ns.listIDRecords().map((r) -> {
switch r {
case Success(data):
handle.writeLine('List: $data');
case Failure(failure):
handle.writeLine('Failed: $failure');
}
return true;
});
});
registerAsyncSubcommand("unregister", (args) -> {
var ns = NameSystemRPC.getDefault();
return ns.removeIDRecord(args.getString("name")).map((r) -> {
switch r {
case Success(_):
handle.writeLine('Unregisterd');
case Failure(failure):
handle.writeLine('Failed: $failure');
}
return true;
});
}, [String("name")]);
}
}

View File

@@ -0,0 +1,31 @@
package bin.ns;
import kernel.KernelSettings;
import macros.rpc.RPCBase;
import kernel.net.Package.NetworkID;
using tink.CoreApi;
interface INameSystemRPC {
function setIDRecord(name:String, id:NetworkID):Noise;
function getIDRecord(name:String):Null<NetworkID>;
function removeIDRecord(name:String):Noise;
function listIDRecords():Array<{name:String, id:NetworkID}>;
}
@:build(macros.rpc.RPC.buildRPC(INameSystemRPC))
class NameSystemRPC extends RPCBase {
public static function getDefault():NameSystemRPC {
return new NameSystemRPC(KernelSettings.nameServer, "ns");
}
public static function resolve(input:String):Promise<Null<NetworkID>> {
if (input == "12345") {
return Promise.resolve(Std.parseInt(input));
}
var ns = getDefault();
return ns.getIDRecord(input);
}
}

View File

@@ -1,14 +1,14 @@
package bin.pathfinder;
import lib.Pos3;
import lib.ui.elements.UIElement;
import lib.WorldPos;
import lib.ui.elements.IUIElement;
import lib.ui.elements.TextElement;
import lib.ui.elements.RootElement;
import kernel.ui.WindowContext;
import kernel.ps.ProcessHandle;
import kernel.ps.Process;
import kernel.ps.IProcess;
class PFClient implements Process {
class PFClient implements IProcess {
private var handle:ProcessHandle;
private var ctx:WindowContext;
@@ -36,9 +36,9 @@ class PFClient implements Process {
private function render() {
var acc = kernel.gps.GPS.getAccuracy();
var pos:Pos3 = kernel.gps.GPS.getPosition() ?? {x: 0, y: 0, z: 0};
var pos:WorldPos = kernel.gps.GPS.getPosition() ?? {x: 0, y: 0, z: 0};
var childre:Array<UIElement> = [
var childre:Array<IUIElement> = [
new TextElement('Acc: ${acc}'),
new TextElement('Pos: X:${pos.x} Y:${pos.y} Z:${pos.z}'),
new TextElement('UPDATE', {

View File

@@ -0,0 +1,158 @@
package bin.turtle;
import kernel.turtle.Turtle;
import lib.turtle.TurtleAppBase;
@:build(macros.Binstore.includeBin("Circle Mine", ["circle-mine", "cm"]))
class CircleMine extends TurtleAppBase {
private var rings:Int;
private var skip:Int = 0;
private var depth:Int = 0;
public function new() {
super(initFunc, turtleFunc);
}
private function initFunc() {
if (handle.args.length < 1) {
handle.writeLine("Not enough args");
handle.close(false);
return;
}
this.rings = Std.parseInt(handle.args[0]);
if (this.rings == null) {
handle.writeLine("Failed to parse args");
handle.close(false);
return;
}
if (handle.args.length > 1) {
this.skip = Std.parseInt(handle.args[1]);
if (this.skip == null) {
handle.writeLine("Failed to parse args");
handle.close(false);
return;
}
}
}
private function turtleFunc() {
// Go down 1 layer
if (skip == 0) {
Turtle.dig(Down);
Turtle.down();
Turtle.dig(Down);
} else {
// Move to outer ring
for (i in 0...(skip * 3) - 1) {
Turtle.forward();
}
}
// Start mining
for (i in skip...(rings + skip)) {
if (i == 0) {
center();
handle.writeLine("Center done");
} else {
circle(i * 3 + 1);
handle.writeLine('Ring $i done');
}
}
// Go back to starting pos.
for (i in 0...((rings + skip) * 3 - 1)) {
Turtle.back();
}
handle.close();
}
private function circle(radius:Int) {
step();
step();
frontDig();
// Do a 1/2 of a side of a ring
Turtle.turnRight();
for (i in 0...radius) {
step();
}
// Do the other 2 sides
for (s in 0...3) {
Turtle.turnRight();
for (i in 0...radius) {
step();
}
middle();
for (i in 0...radius) {
step();
}
}
// Complete the 1/2 of the first side
Turtle.turnRight();
for (i in 0...radius) {
step();
}
Turtle.turnLeft();
Turtle.forward();
}
private function middle() {
Turtle.turnRight();
Turtle.dig(Front);
Turtle.down();
Turtle.dig(Front);
Turtle.turnLeft();
Turtle.turnLeft();
Turtle.dig(Front);
Turtle.up();
Turtle.dig(Front);
Turtle.turnRight();
}
private function step() {
Turtle.dig(Front);
Turtle.forward();
Turtle.dig(Down);
}
private function frontDig() {
Turtle.dig(Front);
Turtle.down();
Turtle.dig(Front);
Turtle.up();
}
private function center() {
Turtle.dig(Down);
step();
frontDig();
Turtle.turnRight();
for (i in 0...3) {
step();
Turtle.turnRight();
step();
Turtle.turnLeft();
frontDig();
Turtle.turnRight();
}
step();
Turtle.turnRight();
Turtle.forward();
Turtle.turnLeft();
Turtle.forward();
}
}

View File

@@ -0,0 +1,56 @@
package bin.turtle;
import lib.turtle.planner.Plan;
import kernel.ps.ProcessHandle;
import kernel.ps.IProcess;
@:build(macros.Binstore.includeBin("Circle Mine Plan", ["cmp"]))
class CircleMinePlan implements IProcess {
public function new() {}
public function run(handle:ProcessHandle) {
var rings:Int = 3;
var skip:Int = 0;
var p = Plan.newPlan();
p = p.repeat([Forward], (skip * 3) - 1); // Move to outer ring or stay if none skiped
for (i in skip...(rings + skip)) {
if (i == 0) {
p = p.act([Clear(Down), Down, Clear(Down)]) // Move down a layer
.subplan(center());
} else {
p = p.subplan(circle(i * 3 + 1));
}
}
p.repeat([Back], (rings + skip) * 3 - 1);
p.begin(handle);
}
private function circle(radius:Int):Plan {
return Plan.newPlan()
.act([FullTunnel, FullTunnel, HalfTunnel])
.act([TurnRight])
.repeat([FullTunnel], radius)
.repateSubplan(Plan.newPlan().act([TurnRight]).repeat([FullTunnel], radius).act([
TurnRight, Clear(Front), Down, Clear(Front), TurnLeft, TurnLeft, Clear(Front), Up, Clear(Front), TurnRight
]).repeat([FullTunnel], radius), 3)
.act([TurnRight])
.repeat([FullTunnel], radius)
.act([TurnLeft, Forward]);
}
private function center():Plan {
return Plan.newPlan()
.act([Clear(Down)])
.act([FullTunnel])
.act([Clear(Front), Down, Clear(Front), Up])
.act([TurnRight])
.repeat([FullTunnel, TurnRight, FullTunnel, TurnLeft, HalfTunnel, TurnRight], 3)
.act([FullTunnel])
.act([TurnRight, Forward, TurnLeft, Forward]);
}
}

View File

@@ -2,9 +2,9 @@ package bin.turtle;
import kernel.turtle.TurtleMutex;
import kernel.ps.ProcessHandle;
import kernel.ps.Process;
import kernel.ps.IProcess;
class Patrol implements Process {
class Patrol implements IProcess {
private var handle:ProcessHandle;
public function new() {}

View File

@@ -1,11 +1,13 @@
package kernel;
#if debug
import lib.Debug;
#end
import kernel.service.ServiceManager;
import kernel.fs.FS;
import kernel.gps.GPS;
import kernel.log.Log;
import kernel.net.Routing;
import lib.Debug;
import kernel.ui.WindowManager;
import kernel.net.Net;
@@ -29,7 +31,9 @@ class Init {
KernelEvents.shutdown();
});
#if debug
Debug.printBuildInfo();
#end
if (!FS.exists("/var/ns")) {
FS.makeDir("/var/ns");

View File

@@ -4,7 +4,7 @@ import kernel.turtle.TurtleMutex;
import kernel.turtle.Turtle;
import kernel.peripherals.Peripherals.Peripheral;
import kernel.log.Log;
import lib.Pos;
import lib.ScreenPos;
import cc.HTTP.HTTPResponse;
import lua.TableTools;
import lua.Coroutine;
@@ -38,11 +38,11 @@ class KernelEvents {
distance:Null<Float>
}>;
public static var onMonitorResize(default, null):Signal<String>;
public static var onMonitorTouch(default, null):Signal<{addr:String, pos:Pos}>;
public static var onMouseClick(default, null):Signal<{button:ButtonType, pos:Pos}>;
public static var onMouseDrag(default, null):Signal<{button:ButtonType, pos:Pos}>;
public static var onMouseScroll(default, null):Signal<{dir:Int, pos:Pos}>;
public static var onMouseUp(default, null):Signal<{button:ButtonType, pos:Pos}>;
public static var onMonitorTouch(default, null):Signal<{addr:String, pos:ScreenPos}>;
public static var onMouseClick(default, null):Signal<{button:ButtonType, pos:ScreenPos}>;
public static var onMouseDrag(default, null):Signal<{button:ButtonType, pos:ScreenPos}>;
public static var onMouseScroll(default, null):Signal<{dir:Int, pos:ScreenPos}>;
public static var onMouseUp(default, null):Signal<{button:ButtonType, pos:ScreenPos}>;
public static var onPaste(default, null):Signal<String>;
public static var onPeripheral(default, null):Signal<String>;
public static var onPeripheralDetach(default, null):Signal<String>;
@@ -75,11 +75,11 @@ class KernelEvents {
distance:Null<Float>
}> = Signal.trigger();
private static final onMonitorResizeTrigger:SignalTrigger<String> = Signal.trigger();
private static final onMonitorTouchTrigger:SignalTrigger<{addr:String, pos:Pos}> = Signal.trigger();
private static final onMouseClickTrigger:SignalTrigger<{button:ButtonType, pos:Pos}> = Signal.trigger();
private static final onMouseDragTrigger:SignalTrigger<{button:ButtonType, pos:Pos}> = Signal.trigger();
private static final onMouseScrollTrigger:SignalTrigger<{dir:Int, pos:Pos}> = Signal.trigger();
private static final onMouseUpTrigger:SignalTrigger<{button:ButtonType, pos:Pos}> = Signal.trigger();
private static final onMonitorTouchTrigger:SignalTrigger<{addr:String, pos:ScreenPos}> = Signal.trigger();
private static final onMouseClickTrigger:SignalTrigger<{button:ButtonType, pos:ScreenPos}> = Signal.trigger();
private static final onMouseDragTrigger:SignalTrigger<{button:ButtonType, pos:ScreenPos}> = Signal.trigger();
private static final onMouseScrollTrigger:SignalTrigger<{dir:Int, pos:ScreenPos}> = Signal.trigger();
private static final onMouseUpTrigger:SignalTrigger<{button:ButtonType, pos:ScreenPos}> = Signal.trigger();
private static final onPasteTrigger:SignalTrigger<String> = Signal.trigger();
private static final onPeripheralTrigger:SignalTrigger<String> = Signal.trigger();
private static final onPeripheralDetachTrigger:SignalTrigger<String> = Signal.trigger();

View File

@@ -39,20 +39,20 @@ class KernelSettings {
return value;
}
public static var siteController(get, set):NetworkID;
private static var _siteController:NetworkID = get("siteController");
public static var nameServer(get, set):NetworkID;
private static var _nameServer:NetworkID = get("nameServer");
private static function get_siteController():NetworkID {
return _siteController;
private static function get_nameServer():NetworkID {
return _nameServer;
}
private static function set_siteController(value:NetworkID):NetworkID {
private static function set_nameServer(value:NetworkID):NetworkID {
if (value == null) {
return get_siteController();
return get_nameServer();
}
set("siteController", value);
_siteController = value;
set("nameServer", value);
_nameServer = value;
return value;
}
}

View File

@@ -1,7 +1,7 @@
package kernel;
import lib.Pos;
import kernel.ui.TermWriteable;
import lib.ScreenPos;
import kernel.ui.ITermWriteable;
import cc.Term;
import lib.Vec.Vec2;
import lib.Color;
@@ -11,7 +11,7 @@ using tink.CoreApi;
/**
Represents the main computer screen.
**/
class MainTerm implements TermWriteable {
class MainTerm implements ITermWriteable {
/**
Depends on: KernelEvents,
**/
@@ -39,7 +39,7 @@ class MainTerm implements TermWriteable {
Term.scroll(y);
}
public function getCursorPos():Pos {
public function getCursorPos():ScreenPos {
var rtn = Term.getCursorPos();
return {
x: rtn.x - 1,
@@ -60,7 +60,7 @@ class MainTerm implements TermWriteable {
Term.setCursorBlink(blink);
}
public function getSize():Pos {
public function getSize():ScreenPos {
var rtn = Term.getSize();
return {
x: rtn.width,

View File

@@ -1,12 +1,12 @@
package kernel.binstore;
import kernel.ps.Process;
import kernel.ps.IProcess;
/**
Represents a callable program.
**/
typedef Bin = {
c:Void->Process,
c:Void->IProcess,
name:String,
aliases:Array<String>,
}

View File

@@ -1,6 +1,6 @@
package kernel.binstore;
import kernel.ps.Process;
import kernel.ps.IProcess;
import macros.Binstore;
class BinStore {
@@ -13,7 +13,7 @@ class BinStore {
return bins;
}
public static function instantiate(alias:String):Null<Process> {
public static function instantiate(alias:String):Null<IProcess> {
for (bin in bins) {
for (a in bin.aliases) {
if (a == alias) {

View File

@@ -1,11 +1,12 @@
package kernel.gps;
import lib.SingleTimeoutPromise;
import kernel.log.Log;
import lib.KVStore;
import kernel.net.Net;
import kernel.net.INetworkInterface;
import kernel.net.Package;
import lib.Pos3;
import lib.WorldPos;
using tink.CoreApi;
@@ -15,20 +16,22 @@ using tink.CoreApi;
You need at least 3 computers that know their position to determine the position of the computer.
**/
class GPS {
private static inline final TIMEOUT:Int = 1;
private static var shouldRespond = true;
private static var shouldDoWholeNumberCheck = true;
private static var posAccuracy = 0; // 0 = unkown, 1 = (ins,best guess), 2 = (stored/manual,should be right), 3 = (gps,confirmed)
private static var cachedPosition:Pos3;
private static var lastPositionResponse:Array<{pos:Pos3, dist:Float}> = [];
private static var cachedPosition:WorldPos;
private static var lastPositionResponse:Array<{pos:WorldPos, dist:Float}> = [];
private static var futureResolve:(pos:Null<Pos3>) -> Void = null;
private static final locatePromise:SingleTimeoutPromise<WorldPos> = new SingleTimeoutPromise(TIMEOUT, brodcastPositionRequest);
@:allow(kernel.Init)
private static function init() {
loadCachedPosition();
}
public static function setManualPosition(pos:Pos3) {
public static function setManualPosition(pos:WorldPos) {
var kvstore = new KVStore("gps");
kvstore.set("mpos", pos);
kvstore.save();
@@ -40,12 +43,12 @@ class GPS {
}
@:allow(kernel.gps.INS)
private static function setINSPosition(pos:Pos3) {
private static function setINSPosition(pos:WorldPos) {
cachedPosition = pos;
posAccuracy = 1;
}
public static function getPosition():Null<Pos3> {
public static function getPosition():Null<WorldPos> {
return cachedPosition;
}
@@ -58,18 +61,8 @@ class GPS {
posAccuracy = 0;
}
public static function locate():Future<Null<Pos3>> {
// TODO: implenet a timeout
// TODO: dont send a request twice if the last one is still pending or we moved
return new Future<Null<Pos3>>((resolve) -> {
futureResolve = resolve;
sendPositionRequest();
return null;
});
}
private static function resolveFuture(pos:Null<Pos3>) {
futureResolve(pos);
public static function locate():Promise<WorldPos> {
return locatePromise.request();
}
private static function persistCachedPositon() {
@@ -85,8 +78,8 @@ class GPS {
var kvstore = new KVStore("gps");
kvstore.load();
var mPos:Null<Pos3> = kvstore.get("mpos"); // Manual set position
var cPos:Null<Pos3> = kvstore.get("cpos"); // Cached position
var mPos:Null<WorldPos> = kvstore.get("mpos"); // Manual set position
var cPos:Null<WorldPos> = kvstore.get("cpos"); // Cached position
if (mPos != null && cPos != null && mPos == cPos) {
// Both are the same, so we can use the cached position
@@ -117,12 +110,17 @@ class GPS {
}
}
private static function sendPositionRequest() {
private static function brodcastPositionRequest() {
Net.brodcastGPSRequest();
}
@:allow(kernel.net.Net)
private static function handlePackage(pack:Package<Noise>, dist:Float, iface:INetworkInterface) {
private static function handlePackage(pack:Package<Noise>, dist:Null<Float>, iface:INetworkInterface) {
if (dist == null) {
// Message comes from another dimension and has no distance.
return;
}
switch (pack.type) {
case GPSRequest:
if (!shouldRespond)
@@ -132,13 +130,13 @@ class GPS {
if (cachedPosition == null)
return;
var response = new Package(Net.networkID, pack.fromID, pack.msgID, GPSResponse(cachedPosition), null, 0);
var response = new Package(Net.networkID, pack.fromID, pack.msgID, GPSResponse(cachedPosition.x, cachedPosition.y, cachedPosition.z,), null, 0);
iface.send(pack.fromID, Net.networkID, response);
case GPSResponse(pos):
if (lastPositionResponse.contains({pos: pos, dist: dist}))
case GPSResponse(x, y, z):
if (lastPositionResponse.contains({pos: {x: x, y: y, z: z}, dist: dist}))
return; // Ignore duplicate responses
lastPositionResponse.push({pos: pos, dist: dist});
lastPositionResponse.push({pos: {x: x, y: y, z: z}, dist: dist});
// TODO: wait for a few seconds before calculating the position, so we can get more responses
@@ -158,12 +156,12 @@ class GPS {
cachedPosition = calculatedPosition;
posAccuracy = 3;
resolveFuture(calculatedPosition);
locatePromise.resolve(calculatedPosition);
default:
}
}
private static function calculatePosition():Null<Pos3> {
private static function calculatePosition():Null<WorldPos> {
if (lastPositionResponse.length < 3)
return null;
@@ -219,7 +217,7 @@ class GPS {
/**
Determines the position(s) of a point given 3 other points and the distance to each of them.
**/
private static function trilateration(p1:Pos3, p2:Pos3, p3:Pos3, r1:Float, r2:Float, r3:Float):Pair<Pos3, Pos3> {
private static function trilateration(p1:WorldPos, p2:WorldPos, p3:WorldPos, r1:Float, r2:Float, r3:Float):Pair<WorldPos, WorldPos> {
var a2b = p2 - p1;
var a2c = p3 - p1;
@@ -228,7 +226,7 @@ class GPS {
var i = ex.dot(a2c);
var ey = (a2c - ex * i).normalize();
var j = ey.dot(a2c);
var ez = ex.cross(ey);
var ez:WorldPos = ex.cross(ey);
var x = (r1 * r1 - r2 * r2 + d * d) / (2 * d);
var y = (r1 * r1 - r3 * r3 - x * x + (x - i) * (x - i) + j * j) / (2 * j);

View File

@@ -1,14 +1,15 @@
package kernel.gps;
import kernel.log.Log;
import lib.SinglePromise;
import kernel.turtle.Turtle;
import lib.Pos3;
import lib.WorldPos;
using tink.CoreApi;
class INS {
private static var heading:Null<Pos3> = null;
private static var heading:Null<WorldPos> = null;
private static var alingment:Int = 1; // 0 = degraded, 1 = not aligned, 2 = aligned
private static final alignPromise:SinglePromise<Noise> = new SinglePromise(startAlign);
@:allow(kernel.turtle.Turtle)
private static function moveForward() {
@@ -68,7 +69,10 @@ class INS {
}
}
private static function move(dir:Null<Pos3>) {
private static function move(dir:Null<WorldPos>) {
if (alignPromise.isRunning()) {
return;
}
var pos = GPS.getPosition();
if (pos == null || dir == null)
return;
@@ -76,58 +80,53 @@ class INS {
GPS.setINSPosition(newPos);
}
public static function getHeading():Null<Pos3> {
public static function getHeading():Null<WorldPos> {
return heading;
}
public static function align():Promise<Noise> {
Log.info("Aligning INS");
return new Promise<Noise>((resolve, reject) -> {
if (Turtle.getFuelLevel() < 2) {
Log.warn("Not enough fuel to align");
reject(new Error("Not enough fuel to align"));
return null;
return alignPromise.request();
}
public static function startAlign():Void {
if (Turtle.getFuelLevel() < 2) {
alignPromise.reject(new Error("Not enough fuel to align"));
return;
}
GPS.locate().handle((result) -> {
switch result {
case Failure(err):
alignPromise.reject(new Error("Failed to locate 1st positon: " + err));
return;
case Success(pos1):
var moved = tryMoving();
if (moved == -1) {
alignPromise.reject(new Error("Can't move"));
return;
}
GPS.locate().handle((result2) -> {
switch result2 {
case Failure(err):
alignPromise.reject(new Error("GPS not available for 2nd position: " + err));
return;
case Success(pos2):
var cHeading = calcHeading(pos1, pos2, moved);
if (cHeading == null) {
alignPromise.reject(new Error("Can't calculate heading"));
return;
}
heading = cHeading;
moveBack(moved);
GPS.setINSPosition(pos1);
alignPromise.resolve(Noise);
}
});
}
GPS.locate().handle((pos1) -> {
Log.debug('pos1: $pos1');
if (pos1 == null) {
Log.warn("GPS not available for 1st position");
reject(new Error("GPS not available"));
return;
}
var moved = tryMoving();
if (moved == -1) {
Log.warn("Can't move");
reject(new Error("Can't move"));
return;
}
GPS.locate().handle((pos2) -> {
Log.debug('pos2: $pos2');
if (pos2 == null) {
Log.warn("GPS not available for 2nd position");
reject(new Error("GPS not available for 2nd position"));
return;
}
var cHeading = calcHeading(pos1, pos2, moved);
if (cHeading == null) {
Log.error("Can't calculate heading");
reject(new Error("Can't calculate heading"));
return;
}
heading = cHeading;
moveBack(moved);
GPS.setINSPosition(pos1);
resolve(Noise);
});
});
return null;
});
}
@@ -150,7 +149,7 @@ class INS {
}
}
private static function calcHeading(pos1:Pos3, pos2:Pos3, moved:Int):Null<Pos3> {
private static function calcHeading(pos1:WorldPos, pos2:WorldPos, moved:Int):Null<WorldPos> {
if (moved == 0) {
return pos1 - pos2;
} else if (moved == 1) {
@@ -167,24 +166,18 @@ class INS {
private static function moveBack(moved:Int) {
if (moved == 0) {
Turtle.forward();
// cc.Turtle.forward();
} else if (moved == 1) {
Turtle.back();
// cc.Turtle.back();
} else if (moved == 2) {
Turtle.back();
// cc.Turtle.back();
Turtle.turnRight();
// cc.Turtle.turnRight();
} else if (moved == 3) {
Turtle.forward();
// cc.Turtle.forward();
Turtle.turnRight();
// cc.Turtle.turnRight();
}
}
private static function rotatePos3ToRight(pos:Pos3):Pos3 {
private static function rotatePos3ToRight(pos:WorldPos):WorldPos {
if (pos.x == 0 && pos.z == -1) {
return {x: 1, y: 0, z: 0};
} else if (pos.x == -1 && pos.z == 0) {
@@ -198,7 +191,7 @@ class INS {
}
}
private static function rotatePos3ToLeft(pos3:Pos3):Pos3 {
private static function rotatePos3ToLeft(pos3:WorldPos):WorldPos {
return rotatePos3ToRight(rotatePos3ToRight(rotatePos3ToRight(pos3)));
}
}

View File

@@ -1,7 +1,8 @@
package kernel.log;
import haxe.ds.ReadOnlyArray;
#if webconsole
import haxe.macro.Context;
#if (webconsole && !macro)
import lib.Debug;
#end
@@ -21,32 +22,62 @@ class Log {
@:allow(kernel.Init)
private static function init() {
onLog = onLogTrigger.asSignal();
}
public static function info(msg:Dynamic, ?pos:haxe.PosInfos) {
log({level: Info, message: Std.string(msg), time: 0}, pos);
}
public static function warn(msg:Dynamic, ?pos:haxe.PosInfos) {
log({level: Warn, message: Std.string(msg), time: 0}, pos);
}
public static function error(msg:Dynamic, ?pos:haxe.PosInfos) {
log({level: Error, message: Std.string(msg), time: 0}, pos);
}
public static function debug(msg:Dynamic, ?pos:haxe.PosInfos) {
#if debug
log({level: Debug, message: Std.string(msg), time: 0}, pos);
haxe.Log.trace = function(v:Dynamic, ?infos:haxe.PosInfos) {
Log._debug(v, infos.className);
}
#end
}
public static function silly(msg:Dynamic, ?pos:haxe.PosInfos) {
log({level: Silly, message: Std.string(msg), time: 0}, pos);
public static function _info(msg:Dynamic, ?origin:String) {
log({
level: Info,
message: Std.string(msg),
time: 0,
origin: origin
});
}
private static function log(line:LogLine, ?pos:haxe.PosInfos) {
line.origin = pos.className;
public static function _warn(msg:Dynamic, ?origin:String) {
log({
level: Warn,
message: Std.string(msg),
time: 0,
origin: origin
});
}
public static function _error(msg:Dynamic, ?origin:String) {
log({
level: Error,
message: Std.string(msg),
time: 0,
origin: origin
});
}
public static function _debug(msg:Dynamic, ?origin:String) {
#if debug
log({
level: Debug,
message: Std.string(msg),
time: 0,
origin: origin
});
#end
}
public static function _silly(msg:Dynamic, ?origin:String) {
log({
level: Silly,
message: Std.string(msg),
time: 0,
origin: origin
});
}
private static function log(line:LogLine) {
logLines.push(line);
if (logLines.length > MAX_LINES) {
@@ -54,12 +85,49 @@ class Log {
}
onLogTrigger.trigger(line);
#if webconsole
Debug.printWeb('[${Std.string(line.level)}][${line.origin}] ${line.message}');
#if (webconsole && !macro)
Debug.logToWebconsole(line);
#end
}
public static function getLines():ReadOnlyArray<LogLine> {
return logLines;
}
#if macro
private static function getOrigin():String {
var localClass = Context.getLocalClass().get();
return localClass.name;
}
#end
public macro static function info(msg:ExprOf<Dynamic>) {
return macro {
Log._info(${msg}, $v{getOrigin()});
}
}
public macro static function warn(msg:ExprOf<Dynamic>) {
return macro {
Log._warn(${msg}, $v{getOrigin()});
}
}
public macro static function error(msg:ExprOf<Dynamic>) {
return macro {
Log._error(${msg}, $v{getOrigin()});
}
}
public macro static function debug(msg:ExprOf<Dynamic>) {
return macro {
Log._debug(${msg}, $v{getOrigin()});
}
}
public macro static function silly(msg:ExprOf<Dynamic>) {
return macro {
Log._silly(${msg}, $v{getOrigin()});
}
}
}

View File

@@ -39,6 +39,35 @@ class Net {
setupInterf(interf);
}
// Handle when a modem has been removed
KernelEvents.onPeripheralDetach.handle((addr) -> {
var idx = interfaces.findIndex((i) -> {
return i.name() == addr;
});
if (idx == -1) {
return;
}
Log.debug('Removed modem on addr: $addr');
interfaces.splice(idx, 1);
Routing.removeInterface(addr);
});
// Handle when a new modem is connected
KernelEvents.onPeripheral.handle((addr) -> {
if (!Peripheral.getTypes(addr).contains("modem")) {
return;
}
Log.debug('Added modem on addr: $addr');
var modem = Peripheral.getModem(addr);
interfaces.push(modem);
setupInterf(modem);
Routing.addInterface(modem);
});
setupPingHandle();
Routing.setup();
@@ -62,6 +91,9 @@ class Net {
});
}
/**
Enables the interface to receive messages.
**/
private static function setupInterf(interf:INetworkInterface) {
interf.onMessage.handle(e -> handle(e.pack, interf, e.dist));
interf.listen(networkID);
@@ -135,12 +167,13 @@ class Net {
if (!sendRaw(pack)) {
// Cant forward
Log.silly('Received a message that could not be forwarded to ${pack.toID}');
}
}
public static function respondTo(pack:GenericPackage, data:Dynamic) {
if (pack.type.match(DataNoResponse(_))) {
Log.warn("Responed to a no response package. Ignoring");
if (!pack.type.match(Data(_))) {
Log.warn("Responding to a package that does require responding. Ignoring.");
return;
}
@@ -212,6 +245,8 @@ class Net {
if (timeout != null) {
timeout.cancle();
}
responseBus.remove(pack.msgID);
});
timeout = new Timer(MESSAGE_TIMEOUT, () -> {

View File

@@ -1,7 +1,5 @@
package kernel.net;
import lib.Pos3;
typedef NetworkID = Int;
typedef GenericPackage = Package<Dynamic>;
@@ -12,7 +10,7 @@ enum PackageTypes {
RouteDiscover(reachableIDs:Array<{id:NetworkID, cost:Int}>);
RouteDiscoverResponse(reachableIDs:Array<{id:NetworkID, cost:Int}>);
RouteDiscoverUpdate(reachableIDs:Array<{id:NetworkID, cost:Int}>);
GPSResponse(pos:Pos3);
GPSResponse(x:Float, y:Float, z:Float);
GPSRequest();
}

View File

@@ -74,42 +74,37 @@ class Routing {
private static function handleRoutePackage(pack:Package<Noise>, interf:INetworkInterface):Void {
addPossibleRoute(pack.fromID, interf, 0, pack.fromID);
var shouldRespond:Bool = switch pack.type {
switch pack.type {
case RouteDiscoverResponse(routes):
// A request for routes has been answerd
for (route in routes) {
addPossibleRoute(route.id, interf, route.cost, pack.fromID);
}
false;
case RouteDiscover(routes):
// Received a request for available routes
for (route in routes) {
addPossibleRoute(route.id, interf, route.cost, pack.fromID);
}
true;
// Respond to peer
var response:Package<Noise> = {
toID: pack.fromID,
fromID: Net.networkID,
msgID: null,
type: RouteDiscoverResponse(genRouteList()),
data: null,
ttl: 0, // Prevent forwarding
}
interf.send(response.toID, Net.networkID, response);
case RouteDiscoverUpdate(routes):
// Received an update of routes
for (route in routes) {
addPossibleRoute(route.id, interf, route.cost, pack.fromID);
}
false;
default:
Log.error("Expected package to be a Route discover package");
false;
Log.silly("Expected package to be a Route discover package");
};
if (!shouldRespond) {
return;
}
// Respond to peer
var response:Package<Noise> = {
toID: pack.fromID,
fromID: Net.networkID,
msgID: null,
type: RouteDiscoverResponse(genRouteList()),
data: null,
ttl: 0, // Prevent forwarding
}
interf.send(response.toID, Net.networkID, response);
}
/**
@@ -126,6 +121,9 @@ class Routing {
return routes;
}
/**
Create a new packate to brodcast our routes.
**/
private static function newRoutDiscoverPackage():Package<Noise> {
var pack:Package<Noise> = {
type: RouteDiscover(genRouteList()),
@@ -179,4 +177,28 @@ class Routing {
public static function getRouteTable():Map<NetworkID, Route> {
return routingTable;
}
/**
Call when an interface is no longer available.
Will rebrodcast routing requests.
**/
@:allow(kernel.net.Net)
private static function removeInterface(addr:String) {
// Remove the interface from the routing table
for (to => modem in routingTable) {
if (modem.interf.name() == addr) {
routingTable.remove(to);
}
}
// Since some routes to some host could be lost
// we rebrodcast on all interfaces again
brodcastRoutingTable();
}
@:allow(kernel.net.Net)
private static function addInterface(iface:INetworkInterface) {
var pack = newRoutDiscoverPackage();
iface.send(Net.BRODCAST_PORT, Net.networkID, pack);
}
}

View File

@@ -0,0 +1,491 @@
package kernel.peripherals;
import lib.Item;
import haxe.exceptions.NotImplementedException;
/**
See https://github.com/MineMaarten/PneumaticCraft/blob/master/resources/assets/pneumaticcraft/wiki/en_US/block/droneInterface.txt
**/
class DroneInterface implements IPeripheral {
public static inline final TYPE_NAME:String = "drone_interface";
private final addr:String;
@:allow(kernel.peripherals)
private function new(addr:String) {
this.addr = addr;
}
public function getAddr():String {
return this.addr;
}
public function getType():String {
return DroneInterface.TYPE_NAME;
}
/**
Returns true if a Drone has connected with this Drone Interface
(when the Drone's program has arrived at the ComputerCraft piece and made a connection).
**/
public function isConnectedToDrone():Bool {
throw new NotImplementedException();
}
/**
Returns the pressure of the connected Drone.
**/
public function getDronePressure() {
throw new NotImplementedException();
}
/**
Returns a table of 3 double values containing the x,y and z position respectively of the Drone.
**/
public function getDronePosition() {
throw new NotImplementedException();
}
/**
Stops the ComputerCraft piece in the Drone, and allows the Drone's program to proceed to the next puzzle piece.
**/
public function exitPiece() {
throw new NotImplementedException();
}
/**
Returns a table of all the current selectable actions (like 'dig' or 'place').
**/
public function getAllActions() {
/*
pneumaticcraft:entity_attack
pneumaticcraft:dig
pneumaticcraft:harvest
pneumaticcraft:place
pneumaticcraft:block_right_click
pneumaticcraft:entity_right_click
pneumaticcraft:pickup_item
pneumaticcraft:drop_item
pneumaticcraft:void_item
pneumaticcraft:void_liquid
pneumaticcraft:inventory_export
pneumaticcraft:inventory_import
pneumaticcraft:liquid_export
pneumaticcraft:liquid_import
pneumaticcraft:entity_export
pneumaticcraft:entity_import
pneumaticcraft:rf_import
pneumaticcraft:rf_export
pneumaticcraft:goto
pneumaticcraft:teleport
pneumaticcraft:emit_redstone
pneumaticcraft:rename
pneumaticcraft:suicide
pneumaticcraft:crafting
pneumaticcraft:standby
pneumaticcraft:logistics
pneumaticcraft:edit_sign
pneumaticcraft:condition_redstone
pneumaticcraft:condition_light
pneumaticcraft:condition_item_inventory
pneumaticcraft:condition_block
pneumaticcraft:condition_liquid_inventory
pneumaticcraft:condition_pressure
pneumaticcraft:drone_condition_item
pneumaticcraft:drone_condition_liquid
pneumaticcraft:drone_condition_entity
pneumaticcraft:drone_condition_pressure
pneumaticcraft:drone_condition_upgrades
pneumaticcraft:condition_rf
pneumaticcraft:drone_condition_rf
pneumaticcraft:computer_control
*/
throw new NotImplementedException();
}
/**
Sets the place/dig order of the Drone.
**/
public function setBlockOrder(order:String) {
throw new NotImplementedException();
}
/**
Returns a table of all the possible area types (filled, frame, sphere, ..).
**/
public function getAreaTypes() {
throw new NotImplementedException();
}
/**
Adds an area to the current stored area of the Drone. When using the latter method, x1, y1, and z1, represent
the first point (which would be the first GPS Tool normally), and x2, y2, and z2 represent the second point.
**/
public function addArea(x:Int, y:Int, z:Int) {
throw new NotImplementedException();
}
/**
Removes an area from the current stored area (like blacklisting).
**/
public function removeArea(x:Int, y:Int, z:Int) {
throw new NotImplementedException();
}
/**
Clears the current stored area.
**/
public function clearArea() {
throw new NotImplementedException();
}
/**
Will show the current stored area using the area renderer you are used to.
**/
public function showArea() {
throw new NotImplementedException();
}
/**
Will stop showing the area stored in the Drone.
**/
public function hideArea() {
throw new NotImplementedException();
}
/**
Acts as you've put a Item Filter piece on the right of a piece (making it whitelisting).
The item/block name is in Minecraft format, i.e. "minecraft:stone", or "pneumaticcraft:pressureTube".
The 'useXXX' are all booleans that determine what filters will be used (same functionality as the check boxes in the Item Filter puzzle piece).
**/
public function addWhitelistItemFilter(item:Item, metadata:String, useMetadata:Bool, useNBT:Bool, useOreDictionary:Bool, useModSimilarity:Bool) {
throw new NotImplementedException();
}
/**
Like the addWhitelistItemFilter(...), but to blacklist items.
**/
public function addBlacklistItemFilter(item:Item, metadata:String, useMetadata:Bool, useNBT:Bool, useOreDictionary:Bool, useModSimilarity:Bool) {
throw new NotImplementedException();
}
/**
Clears all the whitelisted item filters stored.
**/
public function clearWhitelistItemFilter() {
throw new NotImplementedException();
}
/**
Clears all the blacklisted item filters stored.
**/
public function clearBlacklistItemFilter() {
throw new NotImplementedException();
}
/**
Adds a text to the whitelisted texts. Used to specify a filter for the Entity Attack action for example.
**/
public function addWhitelistText(text:String) {
throw new NotImplementedException();
}
/**
Adds a text to the blacklisted texts. Used to specify a filter for the Entity Attack action for example.
**/
public function addBlacklistText(text:String) {
throw new NotImplementedException();
}
/**
Clears the stored whitelisted texts.
**/
public function clearWhitelistText() {
throw new NotImplementedException();
}
/**
Clears the stored blacklisted texts.
**/
public function clearBlacklistText() {
throw new NotImplementedException();
}
/**
Acts as you've put a Liquid Filter piece on the right of a piece (making it whitelisting).
You can either give it the 'registry name', or the localized name.
**/
public function addWhitelistLiquidFilter(liquidName:String) {
throw new NotImplementedException();
}
/**
Like the addWhitelistLiquidFilter(...), but to blacklist liquids.
**/
public function addBlacklistLiquidFilter(liquidName:String) {
throw new NotImplementedException();
}
/**
Clears all the whitelisted liquid filters stored.
**/
public function clearWhitelistLiquidFilter() {
throw new NotImplementedException();
}
/**
Clears all the blacklisted liquid filters stored.
**/
public function clearBlacklistLiquidFilter() {
throw new NotImplementedException();
}
/**
Sets the strength the redstone will be emitting when the "emitRedstone" action would be set.
**/
public function setEmittingRedstone(strength:Int) {
throw new NotImplementedException();
}
/**
Sets the name the Drone will be named to when the "rename" action would be set.
**/
public function setRenameString(name:String) {
throw new NotImplementedException();
}
/**
When the "dropItem" action would be set, this determines whether or not
the item will be dropped with a random velocity, or straight down.
**/
public function setDropStraight(to:Bool) {
throw new NotImplementedException();
}
/**
This says whether or not the Drone has a maximum of imported/exported/dropped pieces.
If true, also use setCount().
**/
public function setUseCount(count:Int) {
throw new NotImplementedException();
}
/**
This configures the maximum amount of imported/exported/dropped items, and also is the amount that's checked when doing a condition check.
**/
public function setCount(count:Int) {
throw new NotImplementedException();
}
/**
Used in conditions only. When true, all checked blocks need to satisfy the condition requirements (>=10 etcetera).
**/
public function setIsAndFunction(to:Bool) {
throw new NotImplementedException();
}
/**
Used in conditions only.
Says whether or not the condition is checking for an equal amount (=) or equal and higher than amount (>=).
The amount can be set using setCount().
**/
public function setOperator(op:String) {
throw new NotImplementedException();
}
/**
Returns true/false. Used in conditions only.
Will return true/false depending on whether or not the condition is satisfied.
Drone Conditions can be checked right after setting 'setAction()'.
Note that you need to wait until 'isActionDone' if you're dealing with a non Drone Condition.
**/
public function evaluateCondition():Bool {
throw new NotImplementedException();
}
/**
Sets the specific side to be accessible or not.
Used in the Inventory Im- and Export actions to set which side of the inventory the Drone can access.
It is also used for the Place action to determine how to place the block.
**/
public function setSide(side:String, accessible:Bool) {
throw new NotImplementedException();
}
/**
Same as setSide(side: String, accessible: Bool), now in one function to set all sides at once (6x boolean).
**/
public function setSides(down:Bool, up:Bool, north:Bool, south:Bool, east:Bool, west:Bool) {
throw new NotImplementedException();
}
/**
This says whether or not the Drone has a maximum actions performed on a block at a time before the command is considered 'done'.
Applies to the Place, Dig and Right-Click block program. If true, also use setMaxActions().
**/
public function setUseMaxActions(to:Bool) {
throw new NotImplementedException();
}
/**
This configures the maximum amount of actions performed on blocks before the command is considered 'done'.
This applies to the Place, Dig an Right-Click block program. Be sure to also call setUseMaxActions(true) to enable usage of this.
**/
public function setMaxActions(max:Int) {
throw new NotImplementedException();
}
/**
Sets up the crafting grid so when the Crafting instruction is called, this recipe will be used.
You need to specify all 9 items making up the recipe.
For empty spaces supply nil. The naming format is the same as for supplying item filters.
TODO: args: <item/block name>, <item/block name>, ...(9x)
**/
public function setCraftingGrid() {
throw new NotImplementedException();
}
/**
Only used in the right click command, this will set whether or not the fake player is sneaking while right clicking.
**/
public function setSneaking(sneaking:Bool) {
throw new NotImplementedException();
}
/**
Only used in the Liquid Export command, when set to true the Drone will be allowed to place down fluid blocks.
**/
public function setPlaceFluidBlocks(to:Bool) {
throw new NotImplementedException();
}
/**
Sets the[link{pneumaticcraft:progwidget/coordinateOperator}] variable[link{}] of this Drone.
When passing 'true', the coordinate will be set to (1,0,0).
Alternatively, false will be setting it to (0,0,0).
It is possible to set global variables (#) as well.
TODO: args
setVariable(<variable name>, <true/false>)
setVariable(<variable name>, <x>)
setVariable(<variable name>, <x>, <y>, <z>)
**/
public function setVariable(name:String, to:Dynamic) {
throw new NotImplementedException();
}
/**
Returns the value of the variable from this Drone (x, y and z).
It is possible to get global (#) and special variables ($) as well.
**/
public function getVariable(name:String):Dynamic {
throw new NotImplementedException();
}
/**
Sets the text that's going to be set to Signs using the Edit Sign command.
TODO: args <line1>, <line2>, ..., <lineN>
**/
public function setSignText(lines:Array<String>) {
throw new NotImplementedException();
}
/**
String that represents the action. This should be one of the actions returned by getAllActions().
**/
public function setAction(action:String) {
throw new NotImplementedException();
}
/**
Returns a string that represents the last action set by 'setAction'.
Will return nil when no action is set.
Can be used to make sure to only call 'isActionDone()' when this method does not return nil.
**/
public function getAction():String {
throw new NotImplementedException();
}
/**
Stops the current running action.
**/
public function abortAction() {
throw new NotImplementedException();
}
/**
Returns true if the current action is done (goto has arrived at the target location,
inventory import can't import anymore, dig has dug every possible block, ..).
**/
public function isActionDone():Bool {
throw new NotImplementedException();
}
/**
When the Drone was targeting an Entity (in the Entity Attack program), this will stop attacking that target.
**/
public function forgetTarget() {
throw new NotImplementedException();
}
/**
Will get the amount of inserted upgrades of the given type.
This type is the index of the upgrade (in creative, or NEI), starting with 0.
Or the metadata value when you use NEI.
A Speed upgrade for example has an index of 5.
**/
public function getUpgrades(upgrade:String) {
throw new NotImplementedException();
}
/**
Unkown. Showed up in the inspect.
**/
public function setCanSteal(canSteal:Bool) {
throw new NotImplementedException();
}
/**
Unkown. Showed up in the inspect.
**/
public function setRequiresTool(requiresTool:Bool) {
throw new NotImplementedException();
}
/**
Unkown. Showed up in the inspect.
**/
public function setCheckLineOfSight(check:Bool) {
throw new NotImplementedException();
}
/**
Unkown. Showed up in the inspect.
**/
public function setRightClickType(to:String) {
throw new NotImplementedException();
}
/**
Unkown. Showed up in the inspect.
**/
public function getDronePositionVec() {
throw new NotImplementedException();
}
/**
Unkown. Showed up in the inspect.
**/
public function getOwnerID() {
throw new NotImplementedException();
}
/**
Unkown. Showed up in the inspect.
**/
public function getOwnerName():String {
throw new NotImplementedException();
}
}

View File

@@ -0,0 +1,76 @@
package kernel.peripherals;
import lib.BlockPos;
import lib.Tags;
import cc.Peripheral;
using tink.CoreApi;
using lua.Table;
@:structInit typedef ScanBlock = {
name:String,
tags:Tags,
pos:BlockPos
}
class GeoScanner implements IPeripheral {
public static inline final TYPE_NAME:String = "geoScanner";
private final addr:String;
public function new(addr:String) {
this.addr = addr;
}
public function getAddr():String {
return this.addr;
}
public function getType():String {
return TYPE_NAME;
}
/**
Returns the current time remaining until then next scan() can be ran.
**/
public function getScanCooldown():Int {
return Peripheral.call(this.addr, "getScanCooldown");
}
/**
Returns the cost in FE for a scan with the given radius.
Returns null of scan of this radius is not posible.
**/
public function cost(radius:Int):Null<Int> {
return Peripheral.call(this.addr, "cost", radius);
}
/**
Returns a list of data about all blocks in the radius.
X,Y,Z are relative to the geoscanner block.
Air blocks are not included.
List is unsorted.
**/
public function scan(radius:Int):Outcome<Array<ScanBlock>, String> {
// TODO: Handel fail state
var result:lua.Table<Int, {
x:Int,
y:Int,
z:Int,
name:String,
tags:lua.Table<Int, String>
}> = Peripheral.call(this.addr, "scan", radius);
return Success(result.toArray().map((e) -> {
name: e.name,
tags: Tags.fromIntTable(e.tags),
pos: new BlockPos(e.x, e.y, e.z)
}));
}
public function chunkAnalyze():Outcome<Map<String, Int>, String> {
var result:lua.Table<String, Int> = Peripheral.call(this.addr, "chunkAnalyze");
return Success(result.toMap());
}
}

View File

@@ -0,0 +1,123 @@
package kernel.peripherals;
import kernel.log.Log;
import lib.Debug;
import cc.Peripheral;
import lib.Tags;
import lib.Item;
import cc.periphs.ItemStorage.ReducedItemInfo;
import lua.Table;
using Lambda;
class ItemInfo {
public final count:Int;
public final name:Item;
public final nbt:Null<String>;
@:allow(kernel.peripherals.Inventory)
private function new(from:ReducedItemInfo) {
this.count = from.count;
this.name = from.name;
this.nbt = from.nbt;
}
}
class DetailedItemInfo extends ItemInfo {
public final displayName:String;
public final maxCount:Int;
public final tags:Tags;
public final durability:Null<Float>;
public final damage:Null<Int>;
public final maxDamage:Null<Int>;
public final enchantments:Array<{displayName:String, level:Int, name:String}>;
@:allow(kernel.peripherals.Inventory)
private function new(from:cc.periphs.ItemStorage.DetailedItemInfo) {
super(from);
this.tags = Tags.fromBoolTable(from.tags);
this.displayName = from.displayName;
this.maxCount = from.maxCount;
this.durability = from.durability;
this.damage = from.damage;
this.maxDamage = from.maxDamage;
if (from.enchantments != null) {
this.enchantments = from.enchantments;
} else {
this.enchantments = [];
}
}
}
class Inventory implements IPeripheral {
public static inline final TYPE_NAME:String = "inventory";
private final addr:String;
public function new(addr:String) {
this.addr = addr;
}
public function getAddr():String {
return this.addr;
}
public function getType():String {
return TYPE_NAME;
}
/**
Get the size of this inventory.
**/
public function size():Int {
return Peripheral.call(this.addr, "size");
}
/**
List all items in this inventory.
**/
public function list():Map<Int, ItemInfo> {
var list:Map<Int, ReducedItemInfo> = Table.toMap(Peripheral.call(this.addr, "list"));
var rtn:Map<Int, ItemInfo> = new Map();
for (k => v in list) {
rtn.set(k - 1, new ItemInfo(v));
}
return rtn;
}
/**
Get detailed information about an item.
**/
public function getItemDetail(slot:Int):DetailedItemInfo {
return new DetailedItemInfo(Peripheral.call(this.addr, "getItemDetail", slot + 1));
}
/**
Get the maximum number of items which can be stored in this slot.
Typically this will be limited to 64 items.
However, some inventorwies (such as barrels or caches) can store hundreds or thousands of items in one slot.
Keep in mind that this does not depend on that item stored in this slot.
**/
public function getItemLimit(slot:Int):Int {
return Peripheral.call(this.addr, "getItemLimit", slot + 1);
}
/**
Push items from one inventory to another connected one. Needs to be on the same wired network.
**/
public function pushItems(to:String, fromSlot:Int, ?limit:Int, ?toSlot:Int):Int {
return Peripheral.call(this.addr, "pushItems", to, fromSlot + 1, limit, toSlot);
}
/**
Pull items from a connected inventory into this one. Needs to be on the same wired network.
**/
public function pullItems(from:String, fromSlot:Int, ?limit:Int, ?toSlot:Int):Int {
return Peripheral.call(this.addr, "pullItems", from, fromSlot + 1, limit, toSlot);
}
}

View File

@@ -50,12 +50,7 @@ class Peripheral {
}
var types = getTypes(addr);
var methodsMap = cc.Peripheral.getMethods(addr).toMap();
var methods:Array<String> = [];
for (k => v in methodsMap) {
methods.push(k);
}
var methods = cc.Peripheral.getMethods(addr).toArray();
return {
types: types,
@@ -81,8 +76,14 @@ class Peripheral {
return getModem(addr);
case Printer.TYPE_NAME:
return getPrinter(addr);
case "redstone":
case Speaker.TYPE_NAME:
return getSpeaker(addr);
case Redstone.TYPE_NAME:
return getRedstone(addr);
case Inventory.TYPE_NAME:
return getInventory(addr);
case GeoScanner.TYPE_NAME:
return getGeoScanner(addr);
}
return null;
@@ -158,4 +159,37 @@ class Peripheral {
public static function getAllComputers():Array<Computer> {
return [for (addr in findAddrByType(Computer.TYPE_NAME)) new Computer(addr)];
}
public static function getSpeaker(addr:String):Null<Speaker> {
var addr = safeGetAddr(addr, Speaker.TYPE_NAME);
if (addr == null)
return null;
return new Speaker(addr);
}
public static function getAllSpeakers():Array<Speaker> {
return [for (addr in findAddrByType(Speaker.TYPE_NAME)) new Speaker(addr)];
}
public static function getInventory(addr:String):Null<Inventory> {
var addr = safeGetAddr(addr, Inventory.TYPE_NAME);
if (addr == null)
return null;
return new Inventory(addr);
}
public static function getAllInventorys():Array<Inventory> {
return [for (addr in findAddrByType(Inventory.TYPE_NAME)) new Inventory(addr)];
}
public static function getGeoScanner(addr:String):Null<GeoScanner> {
var addr = safeGetAddr(addr, GeoScanner.TYPE_NAME);
if (addr == null)
return null;
return new GeoScanner(addr);
}
public static function getAllGeoScanners():Array<GeoScanner> {
return [for (addr in findAddrByType(GeoScanner.TYPE_NAME)) new GeoScanner(addr)];
}
}

View File

@@ -2,7 +2,7 @@ package kernel.peripherals;
import cc.Peripheral;
import lib.Rect;
import lib.Pos;
import lib.ScreenPos;
class Printer implements IPeripheral {
public static inline final TYPE_NAME:String = "printer";
@@ -25,11 +25,11 @@ class Printer implements IPeripheral {
public function write(text:String) {}
public function getCurserPos():Pos {
return new Pos({x: 0, y: 0});
public function getCurserPos():ScreenPos {
return new ScreenPos({x: 0, y: 0});
}
public function setCurserPos(pos:Pos) {
public function setCurserPos(pos:ScreenPos) {
this.native.setCursorPos(pos.x, pos.y);
}

View File

@@ -1,5 +1,6 @@
package kernel.peripherals;
import kernel.peripherals.interfaces.IRedstone;
import haxe.ds.ReadOnlyArray;
import lib.Color;
@@ -41,7 +42,7 @@ abstract BundleMask(Int) from cc.Colors.Color to cc.Colors.Color {
}
}
class Redstone implements IPeripheral {
class Redstone implements IPeripheral implements IRedstone {
public static inline final TYPE_NAME:String = "redstone"; // TODO: there is technically not a type for redstone.
public final onChange:Signal<Noise>;

View File

@@ -1,15 +1,15 @@
package kernel.peripherals;
import cc.Peripheral;
import lib.Pos;
import lib.ScreenPos;
import cc.Term.TerminalSize;
import kernel.ui.TermWriteable;
import kernel.ui.ITermWriteable;
import lib.Vec.Vec2;
import lib.Color;
using tink.CoreApi;
class Screen implements TermWriteable implements IPeripheral {
class Screen implements ITermWriteable implements IPeripheral {
public static inline final TYPE_NAME:String = "monitor";
private final nativ:cc.periphs.Monitor.Monitor;
@@ -59,7 +59,7 @@ class Screen implements TermWriteable implements IPeripheral {
nativ.scroll(y);
}
public function getCursorPos():Pos {
public function getCursorPos():ScreenPos {
var rtn = nativ.getCursorPos();
return {
x: rtn.x - 1,

View File

@@ -0,0 +1,61 @@
package kernel.peripherals;
import cc.Peripheral;
using tink.CoreApi;
class Speaker implements IPeripheral {
public static inline final TYPE_NAME:String = "speaker";
private final addr:String;
public function new(addr:String) {
this.addr = addr;
}
public function getType():String {
return Speaker.TYPE_NAME;
}
public function getAddr():String {
return this.addr;
}
/**
Plays a note block note through the speaker.
The pitch argument uses semitones as the unit. This directly maps to the number of clicks on a note block.
For reference, 0, 12, and 24 map to F#, and 6 and 18 map to C.
A maximum of 8 notes can be played in a single tick. If this limit is hit, this function will return an error.
**/
public function playNote(instrument:String, ?volume:Float = 1.0, ?pitch:Int = 12):Outcome<Noise, String> {
if (Peripheral.call(addr, "playNote", instrument, volume, pitch)) {
return Success(null);
} else {
return Failure("maximum reached");
}
}
public function playSound(sound:String, ?volume:Float = 1.0, ?pitch:Float = 1.0):Outcome<Noise, String> {
try {
if (Peripheral.call(addr, "playSound", sound, volume, pitch)) {
return Success(null);
} else {
return Failure("Sound still playing");
}
} catch (e) {
return Failure("Sound does not exist");
}
}
public function playAudio(buffer:Array<Int>, ?volume:Float = 1.0):Outcome<Noise, String> {
try {
if (Peripheral.call(addr, "playAudio", buffer, volume)) {
return Success(null);
} else {
return Failure("Buffer full");
}
} catch (e) {
return Failure("Buffer malformed");
}
}
}

View File

@@ -0,0 +1,7 @@
package kernel.peripherals.exports;
import macros.rpc.RPCBase;
import kernel.peripherals.interfaces.IRedstone;
@:build(macros.rpc.RPC.buildRPC(IRedstone))
class RedstoneExport extends RPCBase {}

View File

@@ -0,0 +1,17 @@
package kernel.peripherals.interfaces;
import cc.Colors.Color;
import kernel.peripherals.Redstone.BundleMask;
interface IRedstone {
function setOutput(on:Bool):Void;
function getOutput():Bool;
function getInput():Bool;
function setAnalogOutput(strength:Int):Void;
function getAnalogOutput():Int;
function getAnalogInput():Int;
function setBundledOutput(output:BundleMask):Void;
function getBundledOutput():BundleMask;
function getBundledInput():BundleMask;
function testBundledInput(mask:Color):Bool;
}

View File

@@ -0,0 +1,34 @@
package kernel.pocket;
using tink.CoreApi;
class Pocket {
public static function isPocket():Bool {
return cc.Pocket != null;
}
/**
Search the player's inventory for another upgrade, replacing the existing one with that item if found.
This inventory search starts from the player's currently selected slot, allowing you to prioritise upgrades.
**/
public static function equipBack():Outcome<Noise, String> {
var r = cc.Pocket.equipBack();
if (r.successful) {
return Success(Noise);
}
return Failure(r.error);
}
/**
Remove the pocket computer's current upgrade.
**/
public static function unequipBack():Outcome<Noise, String> {
var r = cc.Pocket.unequipBack();
if (r.successful) {
return Success(Noise);
}
return Failure(r.error);
}
}

View File

@@ -3,6 +3,6 @@ package kernel.ps;
/**
Defines an independent process that can be run by the kernel.
**/
interface Process {
interface IProcess {
public function run(handle:ProcessHandle):Void;
}

View File

@@ -10,7 +10,7 @@ typedef PID = Int;
class ProcessManager {
private static final processList = new Map<PID, ProcessHandle>();
public static function run(process:Process, config:HandleConfig):PID {
public static function run(process:IProcess, config:HandleConfig):PID {
var pid = createPID();
var handle = new ProcessHandle(config, pid);

View File

@@ -1,6 +1,6 @@
package kernel.service;
import kernel.ps.Process;
import kernel.ps.IProcess;
import kernel.ps.ProcessManager;
import kernel.binstore.BinStore;
@@ -11,7 +11,7 @@ class Service {
public final name:String;
public final args:Array<String>;
public var pid:PID;
public var ps:Process;
public var ps:IProcess;
@:allow(kernel.service.ServiceManager)
private function new(binName:String, name:String, ?args:Array<String>) {

View File

@@ -17,37 +17,46 @@ class ServiceManager {
/**
Add a service to be automatically started.
**/
public static function enable(name:String) {
public static function enable(name:String):Outcome<Noise, String> {
if (!services.exists(name)) {
return; // Service must be started
return Failure("Service must be started before enable");
}
var store = KVStore.getStoreForClass();
store.load();
var enabled = store.get("enabled", []);
enabled.push(name);
store.set("enabled", enabled);
store.save();
return Success(Noise);
}
/**
Remove a service from being automatically started.
**/
private static function disable(name:String) {
private static function disable(name:String):Outcome<Noise, String> {
var store = KVStore.getStoreForClass();
store.load();
var enabled:Array<String> = store.get("enabled");
var index = enabled.indexOf(name);
if (index == -1) {
return;
return Failure("Service not found");
}
enabled.splice(index, 1);
store.save();
return Success(Noise);
}
private static function startAllEnabled() {
var store = KVStore.getStoreForClass();
store.load();
var enabled:Array<String> = store.get("enabled", []);
for (name in enabled) {
start(name);

View File

@@ -72,6 +72,11 @@ class Turtle {
return r2;
}
/**
Dig in the provided direction.
Keep in mind that digging on a empty space is considerd a failure.
Also see `digEmpty`.
**/
public static function dig(dir:InteractDirections, ?toolSide:ToolSide):Outcome<Noise, String> {
var r:cc.Turtle.TurtleActionResult;
@@ -87,6 +92,36 @@ class Turtle {
return conterToOutcome(r);
}
/**
Dig in the provided direction.
Does not fail if there is nothing to dig.
Also see `dig`.
**/
public static function digEmpty(dir:InteractDirections, ?toolSide:ToolSide):Outcome<Noise, String> {
var r:cc.Turtle.TurtleActionResult;
// FIXME: upstream needs to be fixed to accept ToolSide
switch dir {
case Front:
r = cc.Turtle.dig();
case Up:
r = cc.Turtle.digUp();
case Down:
r = cc.Turtle.digDown();
}
var result = conterToOutcome(r);
switch (result) {
case Success(_):
return result;
case Failure(failure):
if (failure == "Nothing to dig here") {
return Success(null);
}
return result;
}
}
public static function place(dir:InteractDirections):Outcome<Noise, String> {
var r:cc.Turtle.TurtleActionResult;
switch dir {

View File

@@ -1,9 +1,9 @@
package kernel.ui;
import lib.Pos;
import lib.ScreenPos;
import lib.Vec.Vec2;
import lib.Color;
import kernel.ui.TermWriteable;
import kernel.ui.ITermWriteable;
using tink.CoreApi;
@@ -11,15 +11,15 @@ using tink.CoreApi;
A term writer that can switch beetween its internal buffer and another termwriter.
The other target is most of the time a real screen
**/
class BufferedVirtualTermWriter implements VirtualTermWriter extends TermBuffer {
class BufferedVirtualTermWriter implements IVirtualTermWriter extends TermBuffer {
private static final defaultSize:Vec2<Int> = {x: 50, y: 50};
private var target:TermWriteable;
private var target:ITermWriteable;
private var enabled:Bool = false;
private var onResizeLink:CallbackLink;
@:allow(kernel.ui)
private function new(?target:TermWriteable) {
private function new(?target:ITermWriteable) {
setTarget(target);
if (enabled) {
@@ -44,7 +44,7 @@ class BufferedVirtualTermWriter implements VirtualTermWriter extends TermBuffer
return enabled;
}
public function setTarget(newTarget:TermWriteable) {
public function setTarget(newTarget:ITermWriteable) {
if (newTarget != null) {
super.setSize(newTarget.getSize());
@@ -86,7 +86,7 @@ class BufferedVirtualTermWriter implements VirtualTermWriter extends TermBuffer
super.scroll(y);
}
public override function getCursorPos():Pos {
public override function getCursorPos():ScreenPos {
if (isEnabled()) {
return target.getCursorPos();
} else {

View File

@@ -1,6 +1,6 @@
package kernel.ui;
import lib.Pos;
import lib.ScreenPos;
import lib.Color;
import lib.Vec.Vec2;
@@ -9,7 +9,7 @@ using tink.CoreApi;
/**
Interface describing a terminal. E.g. the main computer screen or a external screen.
**/
interface TermWriteable {
interface ITermWriteable {
public var onResize(default, null):Signal<Vec2<Int>>;
public function write(text:String):Void;
@@ -18,7 +18,7 @@ interface TermWriteable {
/**
Even though CC is 1 based we use a 0 based index.
**/
public function getCursorPos():Pos;
public function getCursorPos():ScreenPos;
/**
Even though CC is 1 based we use a 0 based index.

View File

@@ -4,9 +4,9 @@ package kernel.ui;
A VirtualTermWriter is a TermWriteable that can be enabled or disabled.
When disabled, it will not write to its target. When enabled, it will.
**/
interface VirtualTermWriter extends TermWriteable {
interface IVirtualTermWriter extends ITermWriteable {
public function enable():Void;
public function disable():Void;
public function isEnabled():Bool;
public function setTarget(newTarget:TermWriteable):Void;
public function setTarget(newTarget:ITermWriteable):Void;
}

View File

@@ -1,7 +1,7 @@
package kernel.ui;
import kernel.log.Log;
import lib.Pos;
import lib.ScreenPos;
import lib.Vec.Vec2;
import lib.Color;
@@ -13,18 +13,18 @@ using tink.CoreApi;
The render function is only called when needed.
You can also request a re-render by calling `requestRender`.
**/
class StatelessVirtualTermWriter implements VirtualTermWriter {
class StatelessVirtualTermWriter implements IVirtualTermWriter {
public var onResize(default, null):Signal<Vec2<Int>>;
private var onResizeTrigger:SignalTrigger<Vec2<Int>> = Signal.trigger();
private var target:TermWriteable;
private var target:ITermWriteable;
private var enabled:Bool = false;
private var renderFunc:Null<Void->Void> = null;
private var renderRequested:Bool = false;
private var onResizeLink:CallbackLink;
@:allow(kernel.ui)
private function new(?target:TermWriteable) {
private function new(?target:ITermWriteable) {
onResize = onResizeTrigger.asSignal();
setTarget(target);
}
@@ -70,7 +70,7 @@ class StatelessVirtualTermWriter implements VirtualTermWriter {
enabled = false;
}
public function setTarget(newTarget:TermWriteable) {
public function setTarget(newTarget:ITermWriteable) {
if (newTarget == null) {
return;
}
@@ -110,8 +110,8 @@ class StatelessVirtualTermWriter implements VirtualTermWriter {
target.scroll(y);
}
public inline function getCursorPos():Pos {
return enabled ? target.getCursorPos() : new Pos({x: 0, y: 0});
public inline function getCursorPos():ScreenPos {
return enabled ? target.getCursorPos() : new ScreenPos({x: 0, y: 0});
}
public inline function setCursorPos(x:Int, y:Int) {

View File

@@ -1,9 +1,9 @@
package kernel.ui;
import lib.Pos;
import lib.ScreenPos;
import lib.Vec.Vec2;
import lib.Color;
import kernel.ui.TermWriteable;
import kernel.ui.ITermWriteable;
using tink.CoreApi;
@@ -12,13 +12,13 @@ using tink.CoreApi;
even if its not displayed right now. When the GUI gets displayed again
this buffer will be written to the screen.
**/
class TermBuffer implements TermWriteable {
class TermBuffer implements ITermWriteable {
/**
format [y][x]. First index is the line. Second index the char in the line.
**/
private var screenBuffer:Array<Array<Pixel>>;
private var cursorPos:Pos = {x: 0, y: 0};
private var cursorPos:ScreenPos = {x: 0, y: 0};
private var currentTextColor:Color = White;
private var currentBgColor:Color = Black;
private var cursorBlink:Bool = false;
@@ -60,7 +60,7 @@ class TermBuffer implements TermWriteable {
}
}
private function copyBufferToTarget(target:TermWriteable) {
private function copyBufferToTarget(target:ITermWriteable) {
target.setCursorPos(0, 0);
target.setBackgroundColor(Black);
target.setTextColor(White);
@@ -91,7 +91,7 @@ class TermBuffer implements TermWriteable {
target.setCursorBlink(cursorBlink);
}
private function safeWriteScreenBuffer(pos:Pos, char:String) {
private function safeWriteScreenBuffer(pos:ScreenPos, char:String) {
if (screenBuffer.length > pos.y && screenBuffer[pos.y].length > pos.x) {
screenBuffer[pos.y][pos.x].char = char;
screenBuffer[pos.y][pos.x].bg = currentBgColor;
@@ -121,7 +121,7 @@ class TermBuffer implements TermWriteable {
]);
}
public function getCursorPos():Pos {
public function getCursorPos():ScreenPos {
return cursorPos;
}

View File

@@ -1,42 +1,42 @@
package kernel.ui;
import lib.ui.rendere.UIEventDelegate;
import lib.Pos;
import lib.ui.rendere.IUIEventDelegate;
import lib.ScreenPos;
import lib.Color;
import kernel.ButtonType;
import lib.Vec.Vec2;
import kernel.ui.TermWriteable;
import kernel.ui.ITermWriteable;
using tink.CoreApi;
/**
The main object you interact with when writing anything to the screen.
**/
class WindowContext implements TermWriteable {
private final writer:VirtualTermWriter;
class WindowContext implements ITermWriteable {
private final writer:IVirtualTermWriter;
@:allow(kernel.ui.WindowManager) private var eventDelegate:Null<UIEventDelegate>;
@:allow(kernel.ui.WindowManager) private var eventDelegate:Null<IUIEventDelegate>;
public var onClick(default, null):Signal<{button:ButtonType, pos:Pos}>;
public var onClick(default, null):Signal<{button:ButtonType, pos:ScreenPos}>;
public var onKey(default, null):Signal<{keyCode:Int, isHeld:Bool}>;
public var onKeyUp(default, null):Signal<Int>;
public var onMouseDrag(default, null):Signal<{button:ButtonType, pos:Pos}>;
public var onMouseScroll(default, null):Signal<{dir:Int, pos:Pos}>;
public var onMouseUp(default, null):Signal<{button:ButtonType, pos:Pos}>;
public var onMouseDrag(default, null):Signal<{button:ButtonType, pos:ScreenPos}>;
public var onMouseScroll(default, null):Signal<{dir:Int, pos:ScreenPos}>;
public var onMouseUp(default, null):Signal<{button:ButtonType, pos:ScreenPos}>;
public var onPaste(default, null):Signal<String>;
public var onChar(default, null):Signal<String>;
@:allow(kernel.ui.WindowManager) private final onClickTrigger:SignalTrigger<{button:ButtonType, pos:Pos}>;
@:allow(kernel.ui.WindowManager) private final onClickTrigger:SignalTrigger<{button:ButtonType, pos:ScreenPos}>;
@:allow(kernel.ui.WindowManager) private final onKeyTrigger:SignalTrigger<{keyCode:Int, isHeld:Bool}>;
@:allow(kernel.ui.WindowManager) private final onKeyUpTrigger:SignalTrigger<Int>;
@:allow(kernel.ui.WindowManager) private final onMouseDragTrigger:SignalTrigger<{button:ButtonType, pos:Pos}>;
@:allow(kernel.ui.WindowManager) private final onMouseScrollTrigger:SignalTrigger<{dir:Int, pos:Pos}>;
@:allow(kernel.ui.WindowManager) private final onMouseUpTrigger:SignalTrigger<{button:ButtonType, pos:Pos}>;
@:allow(kernel.ui.WindowManager) private final onMouseDragTrigger:SignalTrigger<{button:ButtonType, pos:ScreenPos}>;
@:allow(kernel.ui.WindowManager) private final onMouseScrollTrigger:SignalTrigger<{dir:Int, pos:ScreenPos}>;
@:allow(kernel.ui.WindowManager) private final onMouseUpTrigger:SignalTrigger<{button:ButtonType, pos:ScreenPos}>;
@:allow(kernel.ui.WindowManager) private final onPasteTrigger:SignalTrigger<String>;
@:allow(kernel.ui.WindowManager) private final onCharTrigger:SignalTrigger<String>;
@:allow(kernel.ui.WindowManager)
private function new(writer:VirtualTermWriter) {
private function new(writer:IVirtualTermWriter) {
this.writer = writer;
this.onResize = writer.onResize;
@@ -62,7 +62,7 @@ class WindowContext implements TermWriteable {
public var onResize(default, null):Signal<Vec2<Int>>;
@:allow(kernel.ui)
private inline function setTarget(target:TermWriteable) {
private inline function setTarget(target:ITermWriteable) {
writer.setTarget(target);
}
@@ -89,7 +89,7 @@ class WindowContext implements TermWriteable {
writer.scroll(y);
}
public inline function getCursorPos():Pos {
public inline function getCursorPos():ScreenPos {
return writer.getCursorPos();
}
@@ -145,7 +145,7 @@ class WindowContext implements TermWriteable {
Delegate events to an UIEventDelegate.
Set to null to stop delegating events.
**/
public inline function delegateEvents(delegate:Null<UIEventDelegate>) {
public inline function delegateEvents(delegate:Null<IUIEventDelegate>) {
this.eventDelegate = delegate;
}
}

View File

@@ -3,7 +3,7 @@ package kernel.ui;
import kernel.ps.ProcessManager;
import kernel.ps.ProcessManager.PID;
import haxe.ds.ReadOnlyArray;
import kernel.ui.TermWriteable;
import kernel.ui.ITermWriteable;
import kernel.peripherals.Peripherals.Peripheral;
/**
@@ -143,7 +143,7 @@ class WindowManager {
Move context to output. If output is "main", context will be moved to main screen.
**/
public static function focusContextToOutput(context:WindowContext, output:String) {
var target:TermWriteable;
var target:ITermWriteable;
if (output == "main") {
target = MainTerm.instance;
currentMainContext = context;

12
src/lib/Block.hx Normal file
View File

@@ -0,0 +1,12 @@
package lib;
@:structInit
class Block {
public final name:PaN;
public final tags:Tags;
public function new(name:String, tags:Tags) {
this.name = name;
this.tags = tags;
}
}

114
src/lib/BlockBlockMap.hx Normal file
View File

@@ -0,0 +1,114 @@
package lib;
import kernel.peripherals.GeoScanner.ScanBlock;
import lib.BlockMap;
@:forward
abstract BlockBlockMap(BlockMap<Block>) {
public inline function new() {
this = new BlockMap<Block>();
}
public static function fromScan(scan:Array<ScanBlock>):BlockBlockMap {
var map:BlockBlockMap = new BlockBlockMap();
for (block in scan) {
map.set(block.pos, new Block(block.name, block.tags));
}
return map;
}
/**
Navigate from one point into another.
Returns the path to take.
Based on A*
**/
public function nav(from:BlockPos, to:BlockPos):Array<BlockPos> {
if (!canPass(to)) {
trace("End is blocked");
return [];
}
var openSet = new PriorityQueue();
openSet.insert(from, 0);
var cameFrom:BlockMap<BlockPos> = new BlockMap();
var gCost:BlockMap<Int> = new BlockMap(); // G cost is the distance from start
gCost.set(from, 0);
var fCost:BlockMap<Float> = new BlockMap(); // F cost is the combines cost of moving to this pos (G cost) + the distnace to the end
fCost.set(from, from.distance(to));
while (!openSet.isEmpty()) {
var current = openSet.extractMin(); // Check the pos with the lowest F cost
if (current.equals(to)) {
var totalPath = [];
var currentPathPos = to;
while (cameFrom.exists(currentPathPos)) {
totalPath.unshift(currentPathPos);
currentPathPos = cameFrom.get(currentPathPos);
}
totalPath.unshift(from);
return totalPath;
}
for (neighbor in current.neighbors()) {
if (!canPass(neighbor)) {
continue;
}
var newGCost = gCost.get(current) + 1; // Movment cost to neighbor is always 1 as we can't move diagonally
if (newGCost < (gCost.get(neighbor) ?? 9999)) {
cameFrom.set(neighbor, current);
gCost.set(neighbor, newGCost);
fCost.set(neighbor, newGCost + neighbor.distance(to));
if (!openSet.containsElement(neighbor)) {
openSet.insert(neighbor, fCost.get(neighbor));
}
}
}
}
return [];
}
/**
Some block can be passed by the turtle.
Some blocks get destroyed by it.
**/
public function canPass(k:BlockPos):Bool {
var block = this.get(k);
if (block == null) {
return true;
}
switch block.name {
case "minecraft:water":
return true;
case "minecaft:lava":
case "mimecraft:grass":
case "minecraft:hanging_roots":
case "minecraft:warped_roots":
case "minecraft:dead_bush":
case "minecraft:fern":
case "minecraft:vine":
case "minecraft:glow_lichen":
case "minecraft:seagrass":
case "minecraft:crimson_roots":
case "minecraft:nether_sprouts":
case "minecraft:snow":
case "minecraft:rose_bush":
case "minecraft:fire":
return true;
}
return false;
}
}

48
src/lib/BlockMap.hx Normal file
View File

@@ -0,0 +1,48 @@
package lib;
import haxe.ds.IntMap;
/**
Map values to positions in 3D space. For representing blocks from a scan see `BlockBlockMap`.
The only reason this exsists is that the abstract class for BlockPos does not
satisfy the constraint of HashMap. The hashCode function needs to be on a real class and not on
an abstract class. Thanks Obama.
**/
@:forward(iterator, clear)
abstract BlockMap<T>(IntMap<T>) {
/**
Creates a new HashMap.
**/
public inline function new() {
this = new IntMap();
}
/**
See `Map.set`
**/
@:arrayAccess public inline function set(k:BlockPos, v:T) {
this.set(k.hashCode(), v);
}
/**
See `Map.get`
**/
@:arrayAccess public inline function get(k:BlockPos) {
return this.get(k.hashCode());
}
/**
See `Map.exists`
**/
public inline function exists(k:BlockPos) {
return this.exists(k.hashCode());
}
/**
See `Map.remove`
**/
public inline function remove(k:BlockPos) {
return this.remove(k.hashCode());
}
}

78
src/lib/BlockPos.hx Normal file
View File

@@ -0,0 +1,78 @@
package lib;
import lib.Vec.Vec2;
import lib.Vec.Vec3;
/**
Represents a Point in a 3D `Int` System.
Basicly a wrapper for Vec3<Int> with some extra functions.
`Y` represents the height of the point.
**/
@:forward
abstract BlockPos(Vec3<Int>) from Vec3<Int> to Vec3<Int> {
public inline function new(x:Int, y:Int, z:Int) {
this = new Vec3(x, y, z);
}
@:op(A + B)
public inline function add(rhs:BlockPos):BlockPos {
return this.add(rhs);
}
@:op(A - B)
public inline function sub(rhs:BlockPos):BlockPos {
return this.sub(rhs);
}
@:op(A * B)
public inline function multiplyScalar(rhs:Int):BlockPos {
return this.multiplyScalar(rhs);
}
@:op(-A)
public inline function negate():BlockPos {
return this.negate();
}
@:op(A == B)
public function equals(rhs:BlockPos):Bool {
return this.x == rhs.x && this.y == rhs.y && this.z == rhs.z;
}
@:op(A != B)
public function notEquals(rhs:BlockPos):Bool {
return !equals(rhs);
}
public function hashCode():Int {
return (this.x * 73856093) ^ (this.y * 19349663) ^ (this.z * 83492791);
}
public function toString():String {
return 'BlockPos(${this.x}, ${this.y}, ${this.z})';
}
/**
Returns the chunk the position is in.
**/
public function chunk():Vec2<Int> {
var x = Math.floor(this.x / 16);
var z = Math.floor(this.z / 16);
return new Vec2(x, z);
}
/**
Returns a list of positions neighboring the block. No Diagonal.
**/
public function neighbors():Array<BlockPos> {
return [
new BlockPos(this.x + 1, this.y + 0, this.z + 0), // Front
new BlockPos(this.x - 1, this.y + 0, this.z + 0), // Back
new BlockPos(this.x + 0, this.y + 0, this.z - 1), // Left
new BlockPos(this.x + 0, this.y + 0, this.z + 1), // Right
new BlockPos(this.x + 0, this.y - 1, this.z + 0), // Bot
new BlockPos(this.x + 0, this.y + 1, this.z + 0), // Top
];
}
}

View File

@@ -1,16 +1,18 @@
package lib;
import kernel.ps.Process;
import lib.args.CLIArgs;
import lib.args.ArgType;
import kernel.ps.IProcess;
import kernel.ps.ProcessHandle;
using tink.CoreApi;
abstract class CLIAppBase implements Process {
abstract class CLIAppBase implements IProcess {
private var handle:ProcessHandle;
private final _subcommandsSync:Map<String, (Array<String>) -> Bool> = [];
private final _subcommandsAsync:Map<String, (Array<String>) -> Future<Bool>> = [];
private final _subcommandsSynopsis:Array<String> = [];
private final _subcommandsSync:Map<String, (CLIArgs) -> Bool> = [];
private final _subcommandsAsync:Map<String, (CLIArgs) -> Future<Bool>> = [];
private final _subcommandsArgs:Map<String, Array<ArgType>> = [];
public final function run(handle:ProcessHandle) {
this.handle = handle;
@@ -26,10 +28,19 @@ abstract class CLIAppBase implements Process {
var args = handle.args.slice(1);
if (_subcommandsSync.exists(subcommand)) {
var result = _subcommandsSync[subcommand](args);
return handle.close(result);
var argParser = new CLIArgs(_subcommandsArgs.get(subcommand));
if (!argParser.parse(args)) {
handle.writeLine(argParser.getError());
return handle.close(false);
}
return handle.close(_subcommandsSync[subcommand](argParser));
} else if (_subcommandsAsync.exists(subcommand)) {
_subcommandsAsync[subcommand](args).handle(handle.close);
var argParser = new CLIArgs(_subcommandsArgs.get(subcommand));
if (!argParser.parse(args)) {
handle.writeLine(argParser.getError());
return handle.close(false);
}
_subcommandsAsync[subcommand](argParser).handle(handle.close);
} else {
handle.writeLine("Unknown subcommand: " + subcommand);
printHelp();
@@ -37,21 +48,21 @@ abstract class CLIAppBase implements Process {
}
}
private function registerSyncSubcommand(command:String, callback:(Array<String>) -> Bool, synopsis:String = null) {
private function registerSyncSubcommand(command:String, callback:(CLIArgs) -> Bool, args:Array<ArgType> = null) {
_subcommandsSync.set(command, callback);
_subcommandsSynopsis.push(command + " " + (synopsis ?? ""));
_subcommandsArgs.set(command, args ?? []);
}
private function registerAsyncSubcommand(command:String, callback:(Array<String>) -> Future<Bool>, synopsis:String = null) {
private function registerAsyncSubcommand(command:String, callback:(CLIArgs) -> Future<Bool>, args:Array<ArgType> = null) {
_subcommandsAsync.set(command, callback);
_subcommandsSynopsis.push(command + " " + (synopsis ?? ""));
_subcommandsArgs.set(command, args ?? []);
}
private function printHelp() {
handle.writeLine("Usage: <subcommand> [args]");
handle.writeLine("Subcommands:");
for (subcommand in _subcommandsSynopsis) {
handle.writeLine(" " + subcommand);
for (k => v in this._subcommandsArgs) {
handle.writeLine(' $k ${CLIArgs.getSynopsis(v)}');
}
}
}

View File

@@ -1,7 +1,7 @@
package lib;
import kernel.http.HTTPRequest.Http;
import lua.TableTools;
import kernel.KernelEvents;
import kernel.log.Log;
import lua.NativeStringTools;
import lib.ui.Canvas;
@@ -9,6 +9,7 @@ import cc.ComputerCraft;
#if webconsole
import cc.HTTP;
import kernel.net.Net;
import kernel.log.LogLine;
#end
class Debug {
@@ -66,8 +67,8 @@ class Debug {
#end
#if webconsole
public static function printWeb(msg:String) {
HTTP.request("http://127.0.0.1:8080/" + Net.networkID, msg);
public static function logToWebconsole(line:LogLine) {
Http.request('http://127.0.0.1:8080/log/${Net.networkID}/${line.level.getIndex()}', '[${line.origin}] ${line.message}').eager();
}
#end
}

View File

@@ -1,69 +0,0 @@
package lib;
import haxe.Exception;
class LambdaExtender {
/**
Returns the first element if there are exectly one element present.
Throws exception if not.
**/
static public function single<T>(it:Iterable<T>):T {
var elem:T = null;
for (t in it) {
if (elem != null) {
throw new Exception("Multiple elements found");
}
elem = t;
}
if (elem == null) {
throw new Exception("No element found");
}
return elem;
}
/**
Like `single` but when no element was found return the default value.
**/
static public function singleOrDefault<T>(it:Iterable<T>, defaultValue:T):T {
var elem:T = null;
for (t in it) {
if (elem != null) {
throw new Exception("Multiple elements found");
}
elem = t;
}
if (elem == null) {
return defaultValue;
}
return elem;
}
/**
Returns the first element.
Throws execption if no first element found.
**/
static public function first<T>(it:Iterable<T>):T {
for (t in it) {
return t;
}
throw new Exception("No element found");
}
/**
Like `first` only if no first element was found it returns the defalt value.
**/
static public function firstOrDefault<T>(it:Iterable<T>, defaultValue:T):T {
var iter = it.iterator();
if (iter.hasNext()) {
return iter.next();
}
return defaultValue;
}
}

View File

@@ -4,10 +4,10 @@ import kernel.log.Log;
import kernel.binstore.BinStore;
import kernel.peripherals.Screen;
import kernel.peripherals.Peripherals.Peripheral;
import kernel.ps.Process;
import kernel.ps.IProcess;
import kernel.ps.ProcessManager;
import bin.KernelLog;
import lib.ui.elements.UIElement;
import lib.ui.elements.IUIElement;
import lib.ui.elements.TextElement;
import lib.ui.elements.RootElement;
import kernel.KernelEvents;
@@ -148,7 +148,7 @@ class HomeContext {
var workspaceIDs:Array<Int> = [for (k => v in workspaces) k];
workspaceIDs.sort((a, b) -> a - b);
var children:Array<UIElement> = [
var children:Array<IUIElement> = [
for (i in workspaceIDs)
new TextElement('Switch to ${i + 1}', {uiEvents: {onClick: this.handleSelectContext.bind(i)}})
];

View File

@@ -3,7 +3,8 @@ package lib;
/**
Represents an item in the game.
**/
abstract Item(String) to String {
@:forward
enum abstract Item(PaN) to String {
public inline function new(name:String) {
// Check if the name is valid. in the format `mod:item_name` e.g. `minecraft:apple`
// TODO: implement
@@ -16,7 +17,6 @@ abstract Item(String) to String {
return new Item(s);
}
function getBase():String {
return this.split(":")[0];
}
var Coal = "minecraft:coal";
var Charcoal = "minecraft:charcoal";
}

View File

@@ -1,12 +1,17 @@
package lib;
import haxe.Serializer;
import haxe.Unserializer;
import kernel.fs.FS;
import haxe.ds.StringMap;
#if kv_use_native
import cc.TextUtils;
#else
import haxe.Unserializer;
import haxe.Serializer;
#end
/**
Key value store with persistence.
Use flag kv_use_native to use the cc serialization.
**/
class KVStore {
private var kvStore:StringMap<Dynamic> = new StringMap();
@@ -43,14 +48,21 @@ class KVStore {
public function save() {
var handle = FS.openWrite(getNamespaceFile(this.namespace));
#if kv_use_native
handle.write(TextUtils.serialize(this.kvStore));
#else
handle.write(Serializer.run(this.kvStore));
#end
handle.close();
}
private function parseFile(content:String) {
#if kv_use_native
this.kvStore = TextUtils.unserialize(content);
#else
var unserializer = new Unserializer(content);
this.kvStore = unserializer.unserialize();
#end
}
public inline function set(key:String, value:Dynamic) {

View File

@@ -1,24 +0,0 @@
package lib;
class ObjMerge {
public static function merge<T>(obj1:T, obj2:T):T {
if (obj1 == null) {
return obj2;
}
if (obj2 == null) {
return obj1;
}
var rtn:T = Reflect.copy(obj1);
var fields = Reflect.fields(obj2);
for (field in fields) {
if (Reflect.getProperty(obj1, field) == null) {
Reflect.setProperty(rtn, field, Reflect.getProperty(obj2, field));
}
}
return (rtn : T);
}
}

18
src/lib/PaN.hx Normal file
View File

@@ -0,0 +1,18 @@
package lib;
/**
Provider and name. e.g. "minecraft:dirt", "mod:item", "minecraft:overworld"
**/
abstract PaN(String) to String from String {
public inline function new(pan:String) {
this = pan;
}
public function getProvider():String {
return this.split(":")[0];
}
public function getName():String {
return this.split(":")[1];
}
}

View File

@@ -1,46 +0,0 @@
package lib;
import lib.Vec.Vec2;
/**
Reporesents a Point in a 2D `Int` System.
Basicly a wrapper for Vec2<Int> with some extra functions.
**/
@:forward(x, y)
abstract Pos(Vec2<Int>) from Vec2<Int> to Vec2<Int> {
inline public function new(i:Vec2<Int>) {
this = i;
}
@:op(A + B)
public function add(rhs:Vec2<Int>):Pos {
return new Pos({
y: this.y + rhs.y,
x: this.x + rhs.x,
});
}
@:op(A - B)
public function sub(rhs:Vec2<Int>):Pos {
return new Pos({
y: this.y - rhs.y,
x: this.x - rhs.x,
});
}
@:op(A * B)
public function multiply(rhs:Vec2<Int>):Pos {
return new Pos({
y: this.y * rhs.y,
x: this.x * rhs.x,
});
}
@:op(-A)
public function negate():Pos {
return new Pos({
y: -this.y,
x: -this.x,
});
}
}

View File

@@ -1,111 +0,0 @@
package lib;
import lib.Vec.Vec3;
/**
Reporesents a Point in a 3D `Float` System.
Basicly a wrapper for Vec3<Float> with some extra functions.
`Y` represents the height of the point.
**/
@:forward(x, y, z)
abstract Pos3(Vec3<Float>) from Vec3<Float> to Vec3<Float> {
inline public function new(i:Vec3<Float>) {
this = i;
}
@:op(A + B)
public function add(rhs:Vec3<Float>):Pos3 {
return new Pos3({
y: this.y + rhs.y,
x: this.x + rhs.x,
z: this.z + rhs.z
});
}
@:op(A - B)
public function sub(rhs:Vec3<Float>):Pos3 {
return new Pos3({
y: this.y - rhs.y,
x: this.x - rhs.x,
z: this.z - rhs.z
});
}
@:op(A * B)
public function multiplyScalar(rhs:Float):Pos3 {
return new Pos3({
y: this.y * rhs,
x: this.x * rhs,
z: this.z * rhs
});
}
@:op(A / B)
public function divideScalar(rhs:Float):Pos3 {
return new Pos3({
y: this.y / rhs,
x: this.x / rhs,
z: this.z / rhs
});
}
@:op(-A)
public function negate():Pos3 {
return new Pos3({
y: -this.y,
x: -this.x,
z: -this.z
});
}
public function dot(rhs:Vec3<Float>):Float {
return this.x * rhs.x + this.y * rhs.y + this.z * rhs.z;
}
public function cross(rhs:Vec3<Float>):Pos3 {
return new Pos3({
x: this.y * rhs.z - this.z * rhs.y,
y: this.z * rhs.x - this.x * rhs.z,
z: this.x * rhs.y - this.y * rhs.x
});
}
public function normalize():Pos3 {
var l = length();
return multiplyScalar(1 / l);
}
public function length():Float {
return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
}
@:op(A == B)
public function equals(rhs:Pos3):Bool {
return close(rhs);
}
@:op(A != B)
public function notEquals(rhs:Pos3):Bool {
return !close(rhs);
}
public function close(rhs:Pos3, epsilon:Float = 0.001):Bool {
return Math.abs(this.x - rhs.x) < epsilon && Math.abs(this.y - rhs.y) < epsilon && Math.abs(this.z - rhs.z) < epsilon;
}
public function toString():String {
return 'Pos3(${this.x}, ${this.y}, ${this.z})';
}
public function round():Pos3 {
return new Pos3({
x: Math.fround(this.x),
y: Math.fround(this.y),
z: Math.fround(this.z)
});
}
public function distance(rhs:Pos3):Float {
return Math.sqrt(Math.pow(this.x - rhs.x, 2) + Math.pow(this.y - rhs.y, 2) + Math.pow(this.z - rhs.z, 2));
}
}

69
src/lib/PriorityQueue.hx Normal file
View File

@@ -0,0 +1,69 @@
package lib;
using Lambda;
abstract PriorityQueue<T>(Array<{prio:Float, val:T}>) {
public inline function new() {
this = [];
}
public function containsElement(element:T):Bool {
return this.exists((e) -> e.val == element);
}
public function isEmpty():Bool {
return this.length == 0;
}
public function insert(e:T, prio:Float) {
this.push({prio: prio, val: e});
bubbleUp(this.length - 1);
}
public function extractMin():Null<T> {
if (this.length == 0) {
return null;
}
swap(0, this.length - 1);
var minElement = this.pop();
bubbleDown(0);
return minElement.val;
}
private function bubbleUp(index:Int) {
var parentIndex:Int = Math.floor((index - 1) / 2);
if (index > 0 && this[index].prio < this[parentIndex].prio) {
swap(index, parentIndex);
bubbleUp(parentIndex);
}
}
private function bubbleDown(index:Int) {
var leftIndex = 2 * index + 1;
var rightIndex = 2 * index + 2;
var smallest = index;
if (leftIndex < this.length && this[leftIndex].prio < this[smallest].prio) {
smallest = leftIndex;
}
if (rightIndex < this.length && this[rightIndex].prio < this[smallest].prio) {
smallest = rightIndex;
}
if (smallest != index) {
swap(index, smallest);
bubbleDown(smallest);
}
}
private function swap(i:Int, j:Int) {
var tmp = this[i];
this[i] = this[j];
this[j] = tmp;
}
}

View File

@@ -1,10 +1,12 @@
package lib;
class Rect {
private final tl:Pos;
private final br:Pos;
import kernel.peripherals.Screen;
public function new(p1:Pos, p2:Pos) {
class Rect {
public final tl:ScreenPos;
public final br:ScreenPos;
public function new(p1:ScreenPos, p2:ScreenPos) {
this.tl = {
x: MathI.min(p1.x, p2.x),
y: MathI.min(p1.y, p2.y)
@@ -20,11 +22,11 @@ class Rect {
return getWidth() * getHight();
}
public function isInside(p:Pos):Bool {
public function isInside(p:ScreenPos):Bool {
return (p.x >= tl.x && p.x <= br.x) && (p.y >= tl.y && p.y <= br.y);
}
public function isOutside(p:Pos):Bool {
public function isOutside(p:ScreenPos):Bool {
return !this.isInside(p);
}
@@ -36,7 +38,7 @@ class Rect {
return br.x - tl.x;
}
public function offset(pos:Pos) {
public function offset(pos:ScreenPos) {
tl.x += pos.x;
tl.y += pos.y;
br.x += pos.x;

34
src/lib/ScreenPos.hx Normal file
View File

@@ -0,0 +1,34 @@
package lib;
import lib.Vec.Vec2;
/**
Reporesents a Point in a 2D `Int` System.
Basicly a wrapper for Vec2<Int> with some extra functions.
**/
@:forward(x, y)
abstract ScreenPos(Vec2<Int>) from Vec2<Int> to Vec2<Int> {
inline public function new(i:Vec2<Int>) {
this = i;
}
@:op(A + B)
public inline function add(rhs:Vec2<Int>):ScreenPos {
return this.add(rhs);
}
@:op(A - B)
public inline function sub(rhs:Vec2<Int>):ScreenPos {
return this.sub(rhs);
}
@:op(A * B)
public inline function multiply(rhs:Vec2<Int>):ScreenPos {
return this.multiply(rhs);
}
@:op(-A)
public inline function negate():ScreenPos {
return this.negate();
}
}

55
src/lib/SinglePromise.hx Normal file
View File

@@ -0,0 +1,55 @@
package lib;
import kernel.EndOfLoop;
using tink.CoreApi;
class SinglePromise<T> {
private var inProgress:Bool = false;
private var interalPromise:Promise<T> = null;
private var interalResolve:T->Void = null;
private var interalReject:(Error->Void) = null;
private final activate:Void->Void;
public function new(activate:Void->Void) {
this.activate = activate;
}
public function request():Promise<T> {
if (this.inProgress) {
trace("Is progress");
return this.interalPromise;
}
this.inProgress = true;
this.interalPromise = new Promise((resolve, reject) -> {
this.interalResolve = resolve;
this.interalReject = reject;
this.activate();
return null;
});
return this.interalPromise;
}
public function isRunning():Bool {
return this.inProgress;
}
public function resolve(val:T) {
if (this.inProgress) {
this.inProgress = false;
this.interalResolve(val);
}
}
public function reject(err:Error) {
if (this.inProgress) {
this.inProgress = false;
this.interalReject(err);
}
}
}

View File

@@ -0,0 +1,64 @@
package lib;
import kernel.Timer;
using tink.CoreApi;
class SingleTimeoutPromise<T> {
private var inProgress:Bool = false;
private var interalPromise:Promise<T> = null;
private var interalResolve:T->Void = null;
private var interalReject:(Error->Void) = null;
private var timer:Timer = null;
private final activate:Void->Void;
private final timeout:Int;
public function new(timeout:Int, activate:Void->Void) {
this.activate = activate;
this.timeout = timeout;
}
public function request():Promise<T> {
if (this.inProgress) {
return this.interalPromise;
}
this.inProgress = true;
this.interalPromise = new Promise((resolve, reject) -> {
this.interalResolve = resolve;
this.interalReject = reject;
this.activate();
this.timer = new Timer(this.timeout, () -> {
this.reject(new Error("Timeout"));
});
return null;
});
return this.interalPromise;
}
public function isRunning():Bool {
return this.inProgress;
}
public function resolve(val:T) {
if (this.inProgress) {
this.inProgress = false;
this.timer.cancle();
this.interalResolve(val);
}
}
public function reject(err:Error) {
if (this.inProgress) {
this.inProgress = false;
this.timer.cancle();
this.interalReject(err);
}
}
}

49
src/lib/Tags.hx Normal file
View File

@@ -0,0 +1,49 @@
package lib;
import lib.PaN;
using Lambda;
using lua.Table;
/**
Represents a list a minecraft tags.
Needed because tags are SOMETIMES retuned not as arrays.
**/
@:forward
enum abstract Tags(Array<PaN>) from Array<PaN> to Array<PaN> {
inline public function new(arr:Array<String>) {
this = arr;
}
/**
From values that are object with the tag as the filed and the value set to true.
**/
@:from
public static function fromBoolTable(from:Table<String, Bool>) {
var rtn = [];
for (k => _ in Table.toMap(from)) {
rtn.push(k);
}
return new Tags(rtn);
}
/**
From values that are lua arrays.
**/
@:from
public static function fromIntTable(from:Table<Int, String>) {
return new Tags(from.toArray());
}
public function sortByName():Tags {
var copy = this.copy();
copy.sort((a:String, b:String) -> {
return if (a < b) -1 else 1;
});
return copy;
}
}

View File

@@ -1,6 +1,6 @@
package lib;
@:structInit class Vec2<T> {
@:structInit class Vec2<T:Float> {
public var x:T;
public var y:T;
@@ -8,16 +8,96 @@ package lib;
this.x = x;
this.y = y;
}
public function add(rhs:Vec2<T>):Vec2<T> {
return {
y: this.y + rhs.y,
x: this.x + rhs.x,
};
}
public function sub(rhs:Vec2<T>):Vec2<T> {
return {
y: this.y - rhs.y,
x: this.x - rhs.x,
};
}
public function multiply(rhs:Vec2<T>):Vec2<T> {
return {
y: this.y * rhs.y,
x: this.x * rhs.x,
};
}
public function negate():Vec2<T> {
return {
y: -this.y,
x: -this.x,
};
}
}
@:structInit class Vec3<T> {
public var x:T;
public var y:T;
public var z:T;
@:structInit class Vec3<T:Float> {
public final x:T;
public final y:T;
public final z:T;
public function new(x:T, y:T, z:T) {
this.x = x;
this.y = y;
this.z = z;
}
public function add(rhs:Vec3<T>):Vec3<T> {
return {
x: this.x + rhs.x,
y: this.y + rhs.y,
z: this.z + rhs.z
};
}
public function sub(rhs:Vec3<T>):Vec3<T> {
return {
x: this.x - rhs.x,
y: this.y - rhs.y,
z: this.z - rhs.z
};
}
public function multiplyScalar(rhs:T):Vec3<T> {
return {
x: this.x * rhs,
y: this.y * rhs,
z: this.z * rhs
};
}
public function negate():Vec3<T> {
return {
x: -this.x,
y: -this.y,
z: -this.z
};
}
public function dot(rhs:Vec3<T>):Float {
return this.x * rhs.x + this.y * rhs.y + this.z * rhs.z;
}
public function cross(rhs:Vec3<T>):Vec3<T> {
return {
x: this.y * rhs.z - this.z * rhs.y,
y: this.z * rhs.x - this.x * rhs.z,
z: this.x * rhs.y - this.y * rhs.x
};
}
public function length():Float {
return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
}
public function distance(rhs:Vec3<T>):Float {
return Math.sqrt(Math.pow(this.x - rhs.x, 2) + Math.pow(this.y - rhs.y, 2) + Math.pow(this.z - rhs.z, 2));
}
}

86
src/lib/WorldPos.hx Normal file
View File

@@ -0,0 +1,86 @@
package lib;
import lib.Vec.Vec2;
import lib.Vec.Vec3;
/**
Reporesents a Point in a 3D `Float` System.
Basicly a wrapper for Vec3<Float> with some extra functions.
`Y` represents the height of the point.
**/
@:forward
abstract WorldPos(Vec3<Float>) from Vec3<Float> to Vec3<Float> {
public inline function new(v:Vec3<Float>) {
this = v;
}
@:op(A + B)
public inline function add(rhs:WorldPos):WorldPos {
return this.add(rhs);
}
@:op(A - B)
public inline function sub(rhs:Vec3<Float>):WorldPos {
return this.sub(rhs);
}
@:op(A * B)
public inline function multiplyScalar(rhs:Float):WorldPos {
return this.multiplyScalar(rhs);
}
@:op(A / B)
public function divideScalar(rhs:Float):WorldPos {
return {
y: this.y / rhs,
x: this.x / rhs,
z: this.z / rhs
};
}
@:op(-A)
public function negate():WorldPos {
return this.negate();
}
public function normalize():WorldPos {
var l = this.length();
return multiplyScalar(1 / l);
}
@:op(A == B)
public function equals(rhs:WorldPos):Bool {
return close(rhs);
}
@:op(A != B)
public function notEquals(rhs:WorldPos):Bool {
return !close(rhs);
}
public function close(rhs:WorldPos, epsilon:Float = 0.001):Bool {
return Math.abs(this.x - rhs.x) < epsilon && Math.abs(this.y - rhs.y) < epsilon && Math.abs(this.z - rhs.z) < epsilon;
}
public function toString():String {
return 'WorldPos(${this.x}, ${this.y}, ${this.z})';
}
/**
Returns the chunk the position is in.
**/
public function chunk():Vec2<Int> {
var x = Math.floor(this.x / 16);
var z = Math.floor(this.z / 16);
return new Vec2(x, z);
}
public function round():WorldPos {
return new WorldPos({
x: Math.fround(this.x),
y: Math.fround(this.y),
z: Math.fround(this.z)
});
}
}

27
src/lib/args/ArgType.hx Normal file
View File

@@ -0,0 +1,27 @@
package lib.args;
enum ArgType {
/** Any Int **/
Int(name:String);
/** Any Float **/
Float(name:String);
/** Any String **/
String(name:String);
/** A side like front, back, top, ... **/
Side(name:String);
/** Any peripheral address that exists **/
Addr(name:String);
/** Address of peripheral with given type **/
Peripheral(name:String, type:String);
/** An optional argument **/
Optional(type:ArgType);
/** The rest of the arguments as a string array **/
Rest(name:String);
}

181
src/lib/args/CLIArgs.hx Normal file
View File

@@ -0,0 +1,181 @@
package lib.args;
import kernel.peripherals.Peripherals.Peripheral;
import haxe.ds.ReadOnlyArray;
import haxe.ds.StringMap;
class CLIArgs {
private final argTypes:Array<ArgType>;
private final parsedArgs:StringMap<Dynamic> = new StringMap();
private var rest:Array<String>;
private var error:Null<String>;
private var errorPos:Int = 0;
public function new(args:Array<ArgType>) {
this.argTypes = args;
}
public function parse(args:ReadOnlyArray<String>):Bool {
for (i => type in this.argTypes) {
if (args.length < (i + 1)) {
if (type.match(Optional(_))) {
return true;
} else {
this.errorPos = i;
return false;
}
}
switch type {
case Rest(_):
this.rest = args.slice(i);
default:
if (!parseArg(args[i], type)) {
this.errorPos = i;
return false;
}
}
}
return true;
}
public inline function getError():String {
return 'Error at pos ${this.errorPos}: ${this.error}';
}
/**
Returns synopsis.
**/
public static function getSynopsis(argTypes:Array<ArgType>):String {
var synopsis = "";
for (arg in argTypes) {
var name = getName(arg);
synopsis += switch (arg) {
case Optional(_): '[$name]';
case Rest(_): '[$name...]'; // TODO: is rest always optional?
default: '<$name>';
}
}
return synopsis;
}
private static function getName(arg:ArgType):String {
return switch (arg) {
case Int(name): name;
case Float(name): name;
case String(name): name;
case Side(name): name;
case Addr(name): name;
case Peripheral(name, type): name;
case Rest(name): name;
case Optional(type): getName(type);
};
}
private function parseArg(arg:String, type:ArgType):Bool {
switch type {
case Int(name):
var parsed = Std.parseInt(arg);
if (parsed == null) {
this.error = 'Need to be an integer';
return false;
}
this.parsedArgs.set(name, parsed);
case Float(name):
var parsed = Std.parseFloat(arg);
if (parsed == null) {
return false;
}
this.parsedArgs.set(name, parsed);
case String(name):
this.parsedArgs.set(name, arg);
case Side(name):
if (!["front", "back", "top", "right", "left", "bottom"].contains(arg)) {
this.error = "must be a side";
return false;
}
this.parsedArgs.set(name, arg);
case Addr(name):
if (!Peripheral.isPresent(arg)) {
this.error = "address not present";
return false;
}
this.parsedArgs.set(name, arg);
case Peripheral(name, type):
if (!Peripheral.getTypes(arg).contains(type)) {
this.error = "address has invalid type";
return false;
}
this.parsedArgs.set(name, arg);
case Optional(innerType):
return parseArg(arg, innerType);
case Rest(name):
return true; // Should never happen
}
return true;
}
/**
Get the arg with `name`.
When in debug mode will throw execption on wrong type.
Returns null when not existing.
**/
public function getInt(name:String):Null<Int> {
var v = this.parsedArgs.get(name);
if (v == null)
return null;
#if debug
return cast(v, Int);
#else
return cast v;
#end
}
/**
Get the arg with `name`.
When in debug mode will throw execption on wrong type.
Returns null when not existing.
**/
public function getFloat(name:String):Null<Float> {
var v = this.parsedArgs.get(name);
if (v == null)
return null;
#if debug
return cast(v, Float);
#else
return cast v;
#end
}
/**
Get the arg with `name`.
When in debug mode will throw execption on wrong type.
Returns null when not existing.
**/
public function getString(name:String):Null<String> {
var v = this.parsedArgs.get(name);
if (v == null)
return null;
#if debug
return cast(v, String);
#else
return cast v;
#end
}
/**
Returns true if arg is present. Only makes sense on Optional args.
**/
public function hasArg(name:String):Bool {
return this.parsedArgs.exists(name);
}
public function getRest():Array<String> {
return this.rest;
}
}

View File

@@ -1,27 +0,0 @@
package lib.observable;
using tink.CoreApi;
class DummyObservable<T> implements Observable<T> {
private var value:T;
private function new(value:T) {
this.value = value;
}
public function set(value:T) {
throw new haxe.exceptions.NotImplementedException();
}
public function get():T {
return this.value;
}
public function subscribe(callback:Callback<T>):CallbackLink {
return null;
}
public static function dummy<T>(value:T):Observable<T> {
return new DummyObservable<T>(value);
}
}

View File

@@ -1,9 +0,0 @@
package lib.observable;
using tink.CoreApi;
interface Observable<T> {
public function set(value:T):Void;
public function get():T;
public function subscribe(callback:Callback<T>):CallbackLink;
}

View File

@@ -1,56 +0,0 @@
package lib.observable;
class ObservableArray<T> extends ObservableValue<Array<T>> {
public function new(value:Array<T>) {
super(value);
}
public function insert(pos:Int, x:T):Void {
this.value.insert(pos, x);
this.callbacks.invoke(this.value);
}
public function pop():Null<T> {
var poped = this.pop();
this.callbacks.invoke(this.value);
return poped;
}
public function push(x:T):Int {
var i = this.value.push(x);
this.callbacks.invoke(this.value);
return i;
}
public function remove(x:T):Bool {
var b = this.value.remove(x);
this.callbacks.invoke(this.value);
return b;
}
public function resize(len:Int) {
this.value.resize(len);
this.callbacks.invoke(this.value);
}
public function reverse() {
this.value.reverse();
this.callbacks.invoke(this.value);
}
public function shift():Null<T> {
var e = this.value.shift();
this.callbacks.invoke(this.value);
return e;
}
public function sort(f:(T, T) -> Int):Void {
this.value.sort(f);
this.callbacks.invoke(this.value);
}
public function unshift(x:T):Void {
this.value.unshift(x);
this.callbacks.invoke(this.value);
}
}

View File

@@ -1,28 +0,0 @@
package lib.observable;
using tink.CoreApi;
class ObservableValue<T> implements Observable<T> {
private var value:T;
private var callbacks:CallbackList<T> = new CallbackList();
public function new(value:T) {
this.value = value;
}
public function set(value:T) {
if (value != this.value) {
this.value = value;
callbacks.invoke(value);
}
}
public function get():T {
return value;
}
public function subscribe(callback:Callback<T>):CallbackLink {
callback.invoke(value);
return callbacks.add(callback);
}
}

View File

@@ -14,4 +14,16 @@ class Helper {
return Success(null);
}
public static function combine(steps:Array<Void->Outcome<Noise, String>>):Outcome<Noise, String> {
for (step in steps) {
var res = step();
if (!res.isSuccess()) {
return res;
}
}
return Success(null);
}
}

View File

@@ -9,123 +9,28 @@ import kernel.turtle.Turtle;
using Lambda;
using tink.CoreApi;
typedef InvState = Map<TurtleSlot, {count:Int, name:Item, max:Int}>;
/**
A wrapper for the native turtle inventory functions. Adds usefullt helper functions.
**/
class InvManager {
public function new() {}
private static function getInvState() {
var invState:InvState = new Map();
for (i in 0...Turtle.MAX_SLOTS) {
var detail = Turtle.getItemDetail(i);
var spaceLeft = Turtle.getItemSpace(i);
switch detail {
case Some(v):
invState.set(i, {
count: v.count,
name: v.name,
max: spaceLeft + v.count
});
case None:
invState.remove(i);
}
}
return invState;
}
public static function getItemCountInfo():Map<Item, Int> {
var invState = getInvState();
var rtn:Map<Item, Int> = new Map();
for (slot in invState) {
if (!rtn.exists(slot.name)) {
rtn.set(slot.name, slot.count);
continue;
}
var count = rtn.get(slot.name);
rtn.set(slot.name, count + slot.count);
}
return rtn;
}
private static function getSlotWithMinCountForItem(item:Item, invState:InvState):Null<TurtleSlot> {
var min:Int = 99; // TODO: is there something like MAX_INT ???
var minSlot:TurtleSlot = -1;
for (k => slot in invState) {
if (slot.name != item) {
continue;
}
if (slot.count < min) {
min = slot.count;
minSlot = k;
}
};
return minSlot == -1 ? null : minSlot;
}
public static function place(item:Item, dir:InteractDirections):Outcome<Noise, String> {
var invState = getInvState();
var slot = getSlotWithMinCountForItem(item, invState);
var invState = new InvState();
var slot = invState.getSlotWithMinCountForItem(item);
if (slot == null) {
return Failure("Item not in inventory");
}
return placeSlot(slot, dir);
}
private static function placeSlot(slot:TurtleSlot, dir:InteractDirections):Outcome<Noise, String> {
Turtle.selectSlot(slot); // TODO: handle error
Turtle.selectSlot(slot);
return Turtle.place(dir);
}
private static function getSlotsForItems(invState:InvState):Map<Item, Array<TurtleSlot>> {
var rtn:Map<Item, Array<TurtleSlot>> = new Map();
for (k => slot in invState) {
if (!rtn.exists(slot.name)) {
rtn.set(slot.name, [k]);
continue;
}
var value = rtn.get(slot.name);
value.push(k);
rtn.set(slot.name, value);
}
return rtn;
}
/**
Returns the first slot that contains this item.
**/
public static function findItemSlot(item:Item):Null<TurtleSlot> {
var invState = getInvState();
for (k => slot in invState) {
if (slot.name == item) {
return k;
}
}
return null;
}
/**
Cleans up turtle inventory. Moves items together.
This should be run in a turtle thread.
**/
public static function defrag() {
var invState = getInvState();
var items = getSlotsForItems(invState);
var invState = new InvState();
var items = invState.getSlotsForItems();
for (item in items) {
defragItem(invState, item);
@@ -198,7 +103,8 @@ class InvManager {
}
public static function equipTool(item:Item, side:ToolSide):Outcome<TurtleSlot, String> {
var toolSlot = findItemSlot(item);
var invState = new InvState();
var toolSlot = invState.findItemSlot(item);
if (toolSlot == null) {
return Failure("Item not found");
@@ -214,4 +120,117 @@ class InvManager {
return Failure(failure);
}
}
/**
Check what items in the inventory can be used as fuel.
**/
public static function getCombustableItems():Array<Item> {
var invState = new InvState();
var rtn = new Map<Item, Noise>();
for (slot => v in invState) {
if (rtn.exists(v.name)) {
continue;
}
Turtle.selectSlot(slot);
var result = Turtle.canRefultWithSlot();
if (result) {
rtn.set(v.name, true); // How do i set a Noise type???
}
}
return [for (k => _ in rtn) k];
}
/**
Refule to a specific value.
Use array items first (in order).
Set `useAll` to false to don't use items not listes in priority.
**/
public static function refuel(to:Int, priority:Array<Item>, useAll:Bool = true) {
var invState = new InvState();
var fuelTo = MathI.min(to, Turtle.getFuelLimit());
if (Turtle.getFuelLevel() >= fuelTo) {
return;
}
var items = invState.getSlotsForItems();
for (item in priority) {
if (!items.exists(item)) {
continue;
}
var allSlotsWithItemOrderd = items.get(item);
allSlotsWithItemOrderd.sort((a, b) -> {
return invState.get(a).count - invState.get(b).count;
});
var fuelPerItem = 0;
for (slot in allSlotsWithItemOrderd) {
var slotInfo = invState.get(slot);
Turtle.selectSlot(slot);
if (fuelPerItem == 0) {
fuelPerItem = refuelFromSelectedSlot(slotInfo.count, fuelTo);
if (fuelPerItem == 0) {
Log.warn('Item ${slotInfo.name} is not combustable');
continue;
}
} else {
var itemsToUse = MathI.min(Math.ceil((fuelTo - (Turtle.getFuelLevel())) / fuelPerItem), slotInfo.count);
Turtle.refuel(itemsToUse);
}
if (Turtle.getFuelLevel() >= fuelTo) {
return;
}
}
}
// If not reached our goal yet, check all other items to use as fuel.
if (!useAll) {
return;
}
for (slot => slotInfo in invState) {
// Skip items that we already checked
// Also the invstate might be invalid for these slots
if (priority.contains(slotInfo.name)) {
continue;
}
Turtle.selectSlot(slot);
refuelFromSelectedSlot(slotInfo.count, fuelTo);
if (Turtle.getFuelLevel() >= fuelTo) {
return;
}
}
}
/**
Refuel from selected slot. The ammount of items in the slot must be given.
The `to` arg should be the desired fuel level.
Returns the ammount of fuel on item of this kind refuels.
Returns 0 of item is not combustable.
**/
private static function refuelFromSelectedSlot(count:Int, to:Int):Int {
var fuelLevel = Turtle.getFuelLevel();
if (!Turtle.refuel(1).isSuccess()) {
return 0;
}
var fuelPerItem = Turtle.getFuelLevel() - fuelLevel;
var itemsToUse = MathI.min(Math.ceil((to - (fuelLevel + fuelPerItem)) / fuelPerItem), count - 1);
Turtle.refuel(itemsToUse);
return fuelPerItem;
}
}

111
src/lib/turtle/InvState.hx Normal file
View File

@@ -0,0 +1,111 @@
package lib.turtle;
import kernel.turtle.Turtle;
import kernel.turtle.TurtleSlot;
@:forward(get, set, iterator, keyValueIterator)
abstract InvState(Map<TurtleSlot, {count:Int, name:Item, max:Int}>) {
public function new() {
this = new Map();
for (i in 0...Turtle.MAX_SLOTS) {
var detail = Turtle.getItemDetail(i);
var spaceLeft = Turtle.getItemSpace(i);
switch detail {
case Some(v):
this.set(i, {
count: v.count,
name: v.name,
max: spaceLeft + v.count
});
case None:
this.remove(i);
}
}
}
/**
Count items.
**/
public function getItemCountInfo():Map<Item, Int> {
var rtn:Map<Item, Int> = new Map();
for (slot in this) {
if (!rtn.exists(slot.name)) {
rtn.set(slot.name, slot.count);
continue;
}
var count = rtn.get(slot.name);
rtn.set(slot.name, count + slot.count);
}
return rtn;
}
/**
Return the slot with with the minimal count for an item.
**/
public function getSlotWithMinCountForItem(item:Item):Null<TurtleSlot> {
var min:Int = 99; // TODO: is there something like MAX_INT ???
var minSlot:TurtleSlot = -1;
for (k => slot in this) {
if (slot.name != item) {
continue;
}
if (slot.count < min) {
min = slot.count;
minSlot = k;
}
};
return minSlot == -1 ? null : minSlot;
}
/**
Get all slots for an Item.
**/
public function getSlotsForItems():Map<Item, Array<TurtleSlot>> {
var rtn:Map<Item, Array<TurtleSlot>> = new Map();
for (k => slot in this) {
if (!rtn.exists(slot.name)) {
rtn.set(slot.name, [k]);
continue;
}
var value = rtn.get(slot.name);
value.push(k);
rtn.set(slot.name, value);
}
return rtn;
}
/**
Returns the first slot that contains this item.
**/
public function findItemSlot(item:Item):Null<TurtleSlot> {
for (k => slot in this) {
if (slot.name == item) {
return k;
}
}
return null;
}
/**
Get all slots with items sorted from low to high.
**/
public function getSlotsSortedByCount():Array<TurtleSlot> {
var keys = [for (k in this.keys()) k];
keys.sort((a, b) -> {
return this.get(a).count - this.get(b).count;
});
return keys;
}
}

View File

@@ -2,9 +2,9 @@ package lib.turtle;
import kernel.turtle.TurtleMutex;
import kernel.ps.ProcessHandle;
import kernel.ps.Process;
import kernel.ps.IProcess;
abstract class TurtleAppBase implements Process {
abstract class TurtleAppBase implements IProcess {
private var handle:ProcessHandle;
private var _initFunc:Void->Void;

View File

@@ -1,102 +0,0 @@
package lib.turtle;
import lib.Pos;
import lib.Pos3;
import kernel.turtle.Turtle;
using tink.CoreApi;
class TurtleExecuter {
private var instructions:Array<TurtleInstruction>;
public function new(instructions:Array<TurtleInstruction>) {
this.instructions = instructions;
}
public function getRequiredFuel():Int {
var fuel = 0;
for (inst in instructions) {
if (inst == Forward || inst == Back || inst == Up || inst == Down) {
fuel++;
}
}
return fuel;
}
public function getRequiredBlocks():Int {
var blocks = 0;
for (inst in instructions) {
switch inst {
case Place(_):
blocks++;
default:
}
}
return blocks;
}
/**
Return the offset of the turtle after executing the instructions.
The origin is the starting position of the turtle.
**/
public function getFinalOffset():{offset:Pos3, faceing:Pos} {
var pos:Pos3 = {x: 0, y: 0, z: 0};
var forwardVec:Pos3 = {x: 1, y: 0, z: 0};
for (inst in instructions) {
switch inst {
case Forward:
pos = pos + forwardVec;
case Back:
pos = pos - forwardVec;
case TurnRight:
forwardVec = {x: -forwardVec.z, z: forwardVec.x, y: forwardVec.y};
case TurnLeft:
forwardVec = {x: forwardVec.z, z: -forwardVec.x, y: forwardVec.y};
default:
}
}
return {offset: pos, faceing: new Pos({x: forwardVec.x, y: forwardVec.z})};
}
public function execute() {
for (inst in instructions) {
executeInst(inst);
}
}
private function executeInst(instruction:TurtleInstruction):Outcome<Noise, String> {
switch instruction {
case Forward:
return Turtle.forward();
case Back:
return Turtle.back();
case Up:
return Turtle.up();
case Down:
return Turtle.down();
case TurnLeft:
return Turtle.turnLeft();
case TurnRight:
return Turtle.turnRight();
case Dig(dir):
return Turtle.dig(dir);
case Place(dir):
return Turtle.place(dir);
case PlacseSign(dir, text):
return Turtle.placeSign(dir, text);
case Select(slot):
var r = Turtle.selectSlot(slot);
if (r.isSuccess()) {
return Outcome.Success(null);
} else {
return Outcome.Failure("Failed to select slot");
}
}
return Outcome.Failure("Unknown instruction: " + instruction);
}
}

Some files were not shown because too many files have changed in this diff Show More