Compare commits

..

321 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
2ab5a38894 removed SRSC 2024-03-13 10:27:31 +01:00
e3875f76f6 removed hello world examples 2024-03-12 21:48:10 +01:00
fdcb81b565 removed export system 2024-03-12 21:47:49 +01:00
72654b1036 added formatter install in makefile 2024-03-12 21:44:51 +01:00
fe85c33d64 remade RPC system 2024-03-12 21:44:08 +01:00
1d9e08641e added defrag to turtle command 2024-01-29 01:25:15 +01:00
638d1acbe0 TurtleSlot is now guaranteed to be in range 2024-01-29 01:24:49 +01:00
815ccdaab9 added equipTool to InvManager 2024-01-29 01:05:40 +01:00
c0741c48aa fixed null for args in process handle 2024-01-29 00:46:51 +01:00
e2ab296e79 added terminal args for runnig command on start 2024-01-29 00:10:21 +01:00
64f4304626 fixed invManager wrong invState update
thanks chatGPT
2024-01-29 00:08:13 +01:00
8cd105b84c start termianl if not advanced computer 2024-01-28 16:46:57 +01:00
ce0e89f5b3 improved turtle defrag 2024-01-28 16:13:09 +01:00
c9e0ceef32 fixed turtle defrag 2024-01-28 15:46:37 +01:00
6170fdd0d3 added turtle defrag 2024-01-28 15:40:28 +01:00
3fce8d515a added InvManager.hx 2024-01-26 21:24:43 +01:00
fe88e065ab minor turtle improvements 2024-01-26 21:23:56 +01:00
e2916a213a added turtles to concepts 2024-01-22 23:37:02 +01:00
22509932c3 fixed pre-commit hook for deleted files 2024-01-22 23:36:41 +01:00
f109305f48 added bin info to other bins 2024-01-22 23:36:00 +01:00
295d284bc1 ALL NEW improved Binstore
also automaticly inlcude everything in src/bin
2024-01-22 03:34:19 +01:00
92deb7177f added ui events to LayerdRootElement 2024-01-21 20:50:08 +01:00
3e09bcfad2 also stop turtle main loop on exit 2024-01-21 20:20:59 +01:00
762f72c160 added LayerdRootElement 2024-01-21 16:32:55 +01:00
1d2a43155a added mirror function to structure 2023-11-19 01:43:58 +01:00
9bb6b8bf2b newline support for TextElement 2023-11-16 02:29:41 +01:00
7f8c0d154c added rotate to structure 2023-11-16 01:38:40 +01:00
f9c10cfc0b improved turtle Printer 2023-11-15 20:39:08 +01:00
be89ca12bd added rotate to Layer 2023-11-15 20:38:26 +01:00
ca79714e8d added textRepresentation to Layer 2023-11-15 20:35:23 +01:00
e0d4844890 fixes terminal newline handling 2023-11-15 17:42:22 +01:00
12eb9d05de added Structure Printer helper 2023-11-15 01:05:05 +01:00
e39720b63d upadted CC luca code url 2023-11-07 18:14:56 +01:00
e593fc52c4 added promise support for RPC macro 2023-08-10 21:52:02 +02:00
ad0c62d6e5 added buildserver 2023-08-09 15:22:12 +02:00
0cac0053e3 fixed native cc call to getMethods 2023-08-06 22:04:44 +02:00
a568c9cdd8 added example excavate app 2023-08-05 11:42:38 +02:00
44772557ba fixed INS when no GPS fix 2023-08-05 11:42:15 +02:00
0540cc465a added example turtle program 2023-08-03 18:01:12 +02:00
21387cd8e7 you can stop the turtle thread now 2023-08-02 17:14:54 +02:00
a46760e587 added seperate turtle thread 2023-08-02 16:32:52 +02:00
c83f457968 clipAppBase run final 2023-08-02 16:31:40 +02:00
b8f5ffb93a made Turtle a static class 2023-08-01 13:19:01 +02:00
3c59e045de precommit add formatted files 2023-08-01 13:16:18 +02:00
4b8bc87db9 added printArgs debug function 2023-08-01 13:15:56 +02:00
0c7280a2f5 fixed turtle output text 2023-07-31 16:42:17 +02:00
98b8465436 isTurtle check 2023-07-31 16:40:11 +02:00
016063e3be formatting 2023-07-30 23:34:43 +02:00
0fc2d4d397 made SRSC a rpc thing 2023-07-30 23:34:35 +02:00
da3f00acb6 added precommit hook 2023-07-30 23:33:51 +02:00
db50a93512 improved RPC macro 2023-07-30 23:18:51 +02:00
f3a13e4247 rpc now can handle args 2023-07-30 19:02:59 +02:00
91972107eb BIG FORMATING COMMIT 2023-07-30 15:55:22 +02:00
088fce0aaa made ServiceManager a static class 2023-07-30 15:48:22 +02:00
3b3c69ee56 made GPS and INS a static class 2023-07-30 15:45:30 +02:00
5a9d463192 made GPS a static class 2023-07-30 15:42:02 +02:00
97c906e013 made Net a static class 2023-07-30 15:38:46 +02:00
505d318ffb made Routing a static class 2023-07-30 15:35:43 +02:00
89f209130e made BinStore a static class 2023-07-27 20:45:37 +02:00
74d0232160 made WindowManager a static class 2023-07-27 20:41:23 +02:00
9deea0ee98 made peripherals a static class 2023-07-27 20:38:23 +02:00
a93ee1cddf made Log static class 2023-07-27 20:35:36 +02:00
e1e0180502 changed kernel events to static class 2023-07-27 20:30:39 +02:00
adc5ab1849 makefile watch all files 2023-07-27 20:30:11 +02:00
1a523cb3ce added startup file 2023-07-27 20:15:41 +02:00
dfaabea00d added styles to TextElement 2023-07-11 13:17:33 +02:00
8683eaf17a added very simple GPS GUI 2023-07-10 23:29:22 +02:00
5fa6c3ecbf added RPC macro 2023-07-10 22:56:07 +02:00
10a061c41b automatic add Process to DCEHack 2023-07-09 15:29:26 +02:00
c390519393 some ui stuff 2023-07-08 14:01:38 +02:00
4f881117cf added macro to export perph 2023-07-08 12:51:08 +02:00
4084659a4a added more doc to GUI stuff 2023-06-30 23:35:57 +02:00
63e279f879 added BigReactor Perph 2023-06-30 23:02:52 +02:00
f68ae00098 dynamic app selection on home context 2023-06-30 19:06:46 +02:00
2937de86d6 select output on home context 2023-06-30 18:42:23 +02:00
788c839937 made a func public 2023-06-30 18:42:06 +02:00
90e76c8cd9 clear screen on shutdown 2023-06-30 18:15:02 +02:00
d2873d6353 added list for srsc 2023-06-30 16:19:00 +02:00
d1f9104aba makefile: deps for hx and js 2023-06-30 15:34:51 +02:00
07ad65d5cf added ID bin 2023-06-26 20:16:31 +02:00
3a2613d916 improved terminal with history 2023-06-26 20:04:26 +02:00
dbd8038851 removed unused import 2023-06-26 20:04:00 +02:00
7bfe594b4b improved exporter 2023-06-26 19:06:24 +02:00
4dcc060e9a added type to IPerph 2023-06-26 19:05:48 +02:00
f124525d2d added computer perph 2023-06-26 19:05:10 +02:00
8333cabdc8 added some docs 2023-06-25 21:23:13 +02:00
7b55241e00 fixed Synopsys in srv 2023-06-25 21:21:33 +02:00
b60c9774e1 fixed missing Synopsys in Redstone 2023-06-25 21:21:15 +02:00
7861135c05 fixed missing synopsis in Drive 2023-06-25 21:20:52 +02:00
909aac941c added export to Redstone 2023-06-25 19:06:48 +02:00
e686afb298 implmented basic ressource exporting 2023-06-25 19:05:57 +02:00
7996dd062d changed synaopsis to srv 2023-06-25 19:02:45 +02:00
9d6979c8e8 added a type to Package and added GenericPackage
This forces you to use cast to if you want to force the package to have
a specific type
2023-06-07 23:42:23 +02:00
7c7529ae39 use new kernel settings 2023-06-07 21:29:29 +02:00
6fcbcfce8d added kernel settings 2023-06-07 21:29:10 +02:00
86793bfdc1 terminal kill runnig apps 2023-06-07 20:10:51 +02:00
644ebe1c05 improved HelloWord service 2023-06-07 20:09:54 +02:00
7aa5698486 improved Processhandle 2023-06-07 20:09:34 +02:00
9a9041bd98 killing non existing process does not throw error 2023-06-07 20:07:37 +02:00
f92d777ee1 added EndOfLoop 2023-06-07 20:07:03 +02:00
f85ff77728 added energyStorage perf 2023-06-07 15:15:50 +02:00
13fe7fbab1 added Perf bin 2023-06-04 21:47:26 +02:00
d0c01800a6 added inspect to perf 2023-06-04 21:29:02 +02:00
fd2b53167e big Peripheal refactor 2023-06-04 20:52:24 +02:00
2051f486d4 get local services 2023-06-04 16:36:29 +02:00
f8b226bae5 added SRSC 2023-06-04 16:28:26 +02:00
cd6808e62c enable services via cli 2023-06-04 15:44:11 +02:00
30304ec5e6 autoload KVStore 2023-06-04 15:43:24 +02:00
00a9c8dc4f fixed typo in Service 2023-06-04 14:32:20 +02:00
c86d0e1d8b refactored Redstone bin 2023-05-31 19:19:17 +02:00
8c041766c9 refactored Net bin 2023-05-31 19:12:44 +02:00
779933be32 refactored GPS bin 2023-05-31 18:57:10 +02:00
06b3e37138 refactored Disk bin to use CLIBaseApp 2023-05-31 18:40:14 +02:00
2a7f02c5e3 fixed null synaopsis 2023-05-31 18:39:36 +02:00
e11a9383ee added callback dispose in ps handle 2023-05-31 15:17:19 +02:00
182a2bce7d fixed termial newline handle 2023-05-29 22:10:25 +02:00
05a220110d added a hopfully temp hack for DCE 2023-05-29 21:50:01 +02:00
9b10ff1d14 added example service 2023-05-29 21:49:41 +02:00
0c9f186d8a added service bin 2023-05-29 21:49:22 +02:00
560c85ea61 added listRunning to service 2023-05-29 21:46:05 +02:00
cb7e284313 added safeguard to ps run 2023-05-29 21:10:22 +02:00
563211af6f fixed missing null check in ps handle 2023-05-29 21:09:39 +02:00
5985b0c8be added service bin 2023-05-29 12:50:11 +02:00
f35c98f912 added SerciceManager 2023-05-29 12:50:00 +02:00
a343db133e improved KVStore 2023-05-29 12:49:42 +02:00
dd2e9a4993 added kill to ProcessManager 2023-05-29 12:49:17 +02:00
eb39131056 added removeNamespace to KVStore 2023-05-25 23:44:08 +02:00
c4260aa719 added bin: lsps 2023-05-23 14:51:56 +02:00
e73b4c4d14 use BinStore in Terminal 2023-05-23 14:36:51 +02:00
94d02d60af added BinStore 2023-05-23 14:35:53 +02:00
16b7db779f use new lib: compiletime 2023-05-22 22:36:11 +02:00
ec63a65ba3 use new Terminal Process in Terminal 2023-05-22 22:26:00 +02:00
ec89107262 convert Terminal to Process 2023-05-22 22:25:37 +02:00
8dca828cf3 use process on KernelLog 2023-04-18 19:57:12 +02:00
2da337b8a9 added windowContext to ProcessHandle 2023-04-18 19:56:59 +02:00
17be4149db added turtle program 2023-04-14 14:36:09 +02:00
f9aadbcbe9 added CLI helper base 2023-04-14 14:35:54 +02:00
bd3402fc39 migrated the apps to processes 2023-04-14 00:19:49 +02:00
747bde4aa6 added the concept of a process 2023-04-14 00:18:21 +02:00
655439461a updated GPS bin with INS 2023-04-13 22:26:37 +02:00
b9452cd598 implemented INS 2023-04-13 22:26:26 +02:00
0c5775560c improved makefile 2023-04-08 03:43:23 +02:00
a1fdca689f updated README 2023-04-08 03:42:10 +02:00
b9fcbd9040 fixed GPS 2023-04-08 03:41:52 +02:00
ba05ff7645 added distance to Pos3 2023-04-08 03:41:43 +02:00
14f7b6c6df added shutdown 2023-04-08 00:39:54 +02:00
f8f5f5e5c7 try catch in entrypoint for kernel stuff 2023-04-01 03:27:52 +02:00
bf378deea2 moved log trigger before printWeb 2023-04-01 03:27:17 +02:00
df39865c3d added round to Pos3 2023-04-01 03:26:42 +02:00
4a7b57c47f added GPS cli app 2023-03-28 00:57:04 +02:00
4f8db600dc added GPS 2023-03-28 00:56:49 +02:00
7fe52b1a8a improved Pos3 with math functions 2023-03-28 00:56:06 +02:00
da78641aec added Constructor to Vec 2023-03-28 00:55:37 +02:00
7480afc5a1 fixed KVStore loading 2023-03-28 00:55:23 +02:00
4fbd064439 constructor for Package class 2023-03-28 00:54:47 +02:00
f7c320c123 added distance to network message 2023-03-28 00:54:18 +02:00
409c4fb411 added log location 2023-03-28 00:52:21 +02:00
82827ed921 added isWireless function to Modem 2023-03-25 00:23:40 +01:00
793639c69d improved home context 2023-03-22 01:32:46 +01:00
4ae6fb4bc7 don't drink and code 2023-03-22 00:57:41 +01:00
9f6bdf6851 removed unused TermIO class 2023-03-21 01:10:58 +01:00
347e210f5f added statelessVirtualTermWriter
- changed how WindowManager handles new contexts
2023-03-21 00:53:07 +01:00
d0670325f2 windowContext inlined the TermWriteable functions 2023-03-20 19:09:07 +01:00
8991a99585 added debug kernel event count 2023-03-20 16:24:25 +01:00
05fa6203d1 removed unused kernel events 2023-03-20 01:19:52 +01:00
204e464b1b Added kernel log ui app 2023-03-20 01:15:22 +01:00
84d437104d Improved Log system 2023-03-20 01:15:07 +01:00
b0be0ba8b4 HomeContext new ui system 2023-02-05 02:56:18 +01:00
a4e4e103bd something something ui 2023-02-05 02:56:04 +01:00
3e7d993662 printer 2023-02-04 23:24:57 +01:00
dabc42ea25 print help in rs command 2023-02-04 20:44:02 +01:00
af8241505f added getHight and getWidth to Rect 2023-02-04 20:43:39 +01:00
d56b871554 improved canvas render to context 2023-02-04 20:41:10 +01:00
8934d40c0b use new event delegate in HomeContext 2023-01-30 16:44:52 +01:00
8f7739c26a implemented UIEvents delegate in WindowContext 2023-01-30 16:44:17 +01:00
a1ce5957d1 use simple list renderer in HomeContext 2023-01-30 16:06:46 +01:00
9e128eaad2 added simple list renderer 2023-01-30 16:06:27 +01:00
881aad743d added renderToContext to Canvas 2023-01-30 16:06:13 +01:00
df86bae738 removed unused import 2023-01-30 03:15:13 +01:00
c731dcef36 i fogor again 2023-01-30 03:14:54 +01:00
e14138c7a0 i forgor 2023-01-30 03:04:53 +01:00
d684360547 renamed MainTerm to HomeContext to avoid confusion 2023-01-30 03:04:20 +01:00
a9eb569a02 moved log to own package 2023-01-30 02:59:55 +01:00
77d330a71d removed unused allContexts 2023-01-30 02:49:43 +01:00
391c6b19fd refactored Logging 2023-01-30 02:47:54 +01:00
632173d122 README feature update 2023-01-28 03:58:26 +01:00
7aa1306077 documentation 2023-01-28 03:56:32 +01:00
15b7112348 changed startup 2023-01-28 03:56:21 +01:00
2cbcc86dce added main term (may rename later) 2023-01-28 03:56:07 +01:00
f71505ce28 removed unused file 2023-01-28 03:55:46 +01:00
cb1f892e6d added UIApp and renamed CLIBase 2023-01-28 03:55:32 +01:00
b54a25eec6 added stacktrace to debug flags 2023-01-28 02:33:38 +01:00
a87f50eec7 added disk cli program 2023-01-27 14:34:25 +01:00
95a9ab63d0 added side fromString 2023-01-27 14:34:06 +01:00
f6c0feda4b catch setDiskLabel error 2023-01-27 13:50:51 +01:00
916831743e removed get side in redstone 2023-01-27 13:39:20 +01:00
72e71c8e36 added Restone cli 2022-12-19 23:28:46 +01:00
8180599c04 Net cli update on new icmp 2022-12-19 22:57:41 +01:00
0b965d32b6 added more icmp stuff 2022-12-19 22:33:17 +01:00
f988c79e2f improved Routing 2022-12-19 21:25:39 +01:00
45a8851e2a private routing method 2022-12-19 21:11:01 +01:00
a6ed7818da another big refactor 2022-12-19 21:06:23 +01:00
3cb1811dcb improved terminal stuff 2022-12-19 17:35:52 +01:00
c6e5a836ea Net improvments 2022-12-19 17:35:14 +01:00
a7dbdff535 i don't even know anymore 2022-12-17 15:08:07 +01:00
2b8aa06117 added terminal app with example hello world 2022-12-15 14:21:26 +01:00
1863462b44 fixed missing char event in windowContext 2022-12-15 14:20:46 +01:00
209e40d0d5 added entrypoint 2022-12-15 14:19:57 +01:00
621dfb71ca fixed missing import in Debug 2022-05-06 13:42:08 +02:00
8d279b31a5 fixed typo 2022-05-06 13:39:45 +02:00
6a933768d9 canvas added from array 2022-05-06 13:37:18 +02:00
879e68d70d fixed CanvasKeyValueIterator loop 2022-05-06 12:51:24 +02:00
ec7535ccb6 added canvas debug print 2022-05-01 01:02:03 +02:00
f21dd48106 fixed typo 2022-05-01 00:06:08 +02:00
f7e7059641 improved canvas 2022-04-30 23:57:43 +02:00
3219738799 added TurtleExt 2022-04-28 22:54:54 +02:00
5ab1a46840 changed getFinalOffset to include facing 2022-04-28 22:54:33 +02:00
1ad78c23bb extented TurtleExcuter 2022-04-26 19:43:34 +02:00
ceb55e86dc added Pos3 2022-04-26 19:42:58 +02:00
e1bb9c3ac3 Pos2 multiply and negate 2022-04-26 19:42:50 +02:00
aca39ea46f added turtleInstructionParser 2022-04-13 21:40:57 +02:00
b40e293d6e added turtle executer 2022-04-13 21:40:45 +02:00
dffb493d4f added turtle instruction set 2022-04-13 21:40:33 +02:00
a5c023c78a added item class 2022-04-13 21:40:19 +02:00
82939e53cc improved turtle kernel 2022-04-13 21:39:59 +02:00
96c6595846 make turtle slot 0 based in kernel 2022-04-12 01:02:04 +02:00
8339c69ecc turtle init stuff 2022-04-11 01:00:23 +02:00
943391dddc added turtle kernel stuff 2022-04-11 00:54:58 +02:00
fa62e3a2eb removed old metatag 2022-03-13 22:29:37 +01:00
a1bb41f8a2 redstone bundle mask improvement 2022-03-13 22:29:25 +01:00
57bf49bea9 added lshift polyfill 2022-03-13 22:28:48 +01:00
24d9268b4a redstone change listner 2022-03-12 22:23:17 +01:00
a75f86cbc1 added redstone interface 2022-03-12 22:03:32 +01:00
35be02dbe2 changed how we handle colors 2022-03-12 21:48:09 +01:00
aee8c0fd3c polyfill exit fix 2022-03-12 21:45:31 +01:00
f3141a0f99 use ReadOnlyArray where it makes sense 2022-03-12 17:23:09 +01:00
72dfcb1aa1 added KVStore 2022-03-12 17:18:12 +01:00
7f6e8bb273 refined haxe build step 2022-03-12 17:18:00 +01:00
7ff19b3ed5 added fs abstrction 2022-03-11 17:44:54 +01:00
e777736e6e added fs abstrction 2022-03-11 17:44:39 +01:00
de35f34173 use Pos instead of Vec2<Int> 2022-03-08 13:33:40 +01:00
edc3195192 added ui elements with children 2022-03-08 13:25:27 +01:00
47c38ac731 log debug only with debug flag 2022-03-08 13:24:01 +01:00
60b369fe27 use HAXE_PATH in makefile 2022-03-08 01:45:48 +01:00
1b9009d8e3 added Pos 2022-03-08 01:45:25 +01:00
3304d10da5 added ListElement 2022-03-07 20:21:24 +01:00
7c4fc95584 added observable array 2022-03-07 20:21:17 +01:00
672826c326 screen access 2022-03-05 03:06:56 +01:00
335992a338 improved disk 2022-03-05 03:06:41 +01:00
6b3f0760ca added drives 2022-03-05 03:01:26 +01:00
df77704d1c a LOT of minor stuff 2022-03-05 02:41:30 +01:00
ce45bf2726 implmented http abstrction 2022-03-04 20:00:41 +01:00
1aa93574c8 fixed element map 2022-03-04 17:46:44 +01:00
33c9e3ab6c startup update 2022-03-04 13:28:59 +01:00
46f03fbfe4 improved reactive ui and events 2022-03-04 13:28:52 +01:00
043d431e94 improved some log messages 2022-03-04 13:28:25 +01:00
169 changed files with 10797 additions and 1269 deletions

3
.gitignore vendored
View File

@@ -1,2 +1,3 @@
/build /build
/node_modules node_modules
/dump

View File

@@ -1,42 +1,90 @@
BUNDLE_NAME = bundle.lua BUNDLE_NAME = bundle.lua
HAXE_NAME = haxe.lua HAXE_NAME = haxe.lua
MINIFYD_NAME = bundle.min.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 BUILD_DIR = build
HAXE_FLAGS = HAXE_FLAGS = -D message.reporting=pretty
POLYFILLED_NAME = bundle.polyfill.lua POLYFILLED_NAME = bundle.polyfill.lua
POLYFILL_SRC = src/polyfill.lua POLYFILL_SRC = src/polyfill.lua
CREAFTOS_PATH = craftos
BUILD_HXML = build.hxml
HAXE_PATH := $(BUILD_DIR)/$(HAXE_NAME) HAXE_PATH := $(BUILD_DIR)/$(HAXE_NAME)
MIN_PATH := $(BUILD_DIR)/$(MINIFYD_NAME) MIN_PATH := $(BUILD_DIR)/$(MINIFYD_NAME)
POLYFILL_PATH := $(BUILD_DIR)/$(POLYFILLED_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 += -D analyzer-optimize -D no-traces
build: $(MIN_PATH) build: $(MIN_PATH)
debug: HAXE_FLAGS += -D webconsole --debug 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_PATH): $(shell find src -name '*.hx')
haxe build.hxml $(HAXE_FLAGS) haxe $(BUILD_HXML) $(HAXE_FLAGS)
$(MIN_PATH): $(POLYFILL_PATH)
node minify.js $(POLYFILL_PATH) $@
$(POLYFILL_PATH): $(POLYFILL_SRC) $(HAXE_PATH) $(POLYFILL_PATH): $(POLYFILL_SRC) $(HAXE_PATH)
cat $(POLYFILL_SRC) $(HAXE_PATH) > $@ cat $(POLYFILL_SRC) $(HAXE_PATH) > $@
deps: package.json build.hxml $(MIN_PATH): $(POLYFILL_PATH)
haxelib install all --always && yarn install 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
.PHONY: deps-hx
deps-hx:
haxelib install all --always
.PHONY: deps-node
deps-node:
npm install
.PHONY: clean
clean: clean:
rm -rf $(BUILD_DIR) rm -rf $(BUILD_DIR)
mkdir $(BUILD_DIR)
.PHONY: watch
watch: watch:
find src -name "*.hx" | entr make debug find src | entr make debug
.PHONY: emulator
emulator: emulator:
craftos --mount-ro /=build $(CREAFTOS_PATH) --mount-ro /=$(shell pwd)/$(BUILD_DIR)
.PHONY: webconsole
webconsole: webconsole:
node console.js node tools/console.js
.PHONY: format
format:
haxelib run formatter -s src
.PHONY: format-deps
format-deps:
haxelib install formatter

View File

@@ -7,6 +7,7 @@ General purpose "operation system" for [ComputerCraft](https://tweaked.cc/) buil
- Hardware abstraction - Hardware abstraction
- Virtual screens to switch between multiple GUI apps - Virtual screens to switch between multiple GUI apps
- Reactive UI framework - Reactive UI framework
- Solid base to easily add applications
# Building # Building
@@ -20,7 +21,7 @@ run `make deps && make`. The `bundle.min.lua` inside the `build` dir is the fina
# Useful links # Useful links
[CC lua code](https://github.com/cc-tweaked/CC-Tweaked/tree/mc-1.16.x/src/main/resources/data/computercraft/lua) [CC lua code](https://github.com/cc-tweaked/CC-Tweaked/tree/mc-1.20.x/projects/core/src/main/resources/data/computercraft/lua)
[CC wiki](https://tweaked.cc/) [CC wiki](https://tweaked.cc/)
@@ -37,6 +38,7 @@ Run `make watch` to recompile when a file changed.
## Emulator ## Emulator
You could use Minecraft to run the program or you could use [craftos pc](https://www.craftos-pc.cc/) as an emulator. Just install it and run `make emulator`. You could use Minecraft to run the program or you could use [craftos pc](https://www.craftos-pc.cc/) as an emulator. Just install it and run `make emulator`.
There is an AppImage available [here](https://github.com/MCJack123/craftos2/releases).
## Websconsole ## Websconsole

View File

@@ -1,11 +1,12 @@
-p src -p src
--main Startup
--library cctweaked:git:https://git.kapelle.org/niklas/cctweaked-haxelib.git --library cctweaked:git:https://git.kapelle.org/niklas/cctweaked-haxelib.git
--library tink_core --library tink_core
--library compiletime
--dce full --dce full
--lua build/haxe.lua
-D lua-vanilla -D lua-vanilla
-D lua-ver 5.1 -D lua-ver 5.1
--macro include("bin")

24
buildServer/Dockerfile Normal file
View File

@@ -0,0 +1,24 @@
FROM haxe:4.3-bullseye
RUN apt-get update && apt-get install -y \
git \
make \
&& rm -rf /var/lib/apt/lists/*
RUN curl -fsSL https://deb.nodesource.com/setup_current.x | bash - && \
apt-get install -y nodejs
RUN mkdir -p /app
RUN mkdir -p /repo
WORKDIR /app
COPY index.js /app/index.js
COPY package.json /app/package.json
RUN npm install
ENV REPO_DIR=/repo
EXPOSE 3000
CMD ["node", "index.js"]

105
buildServer/index.js Normal file
View File

@@ -0,0 +1,105 @@
const express = require('express');
const childProcess = require('child_process');
const fs = require('fs').promises;
const path = require('path');
const async = require('async');
const bodyParser = require('body-parser');
const workingDir = process.env.REPO_DIR ?? "repo";
const buildQueue = async.queue(async (task) => {
return build(task);
});
const app = express();
app.use(bodyParser.json());
app.post('/build', async (req, res) => {
try{
let output = await buildQueue.push(req.body);
res.status(200);
res.send(output);
}catch(e){
res.status(500);
res.send(e);
}
});
app.post('/pull', async (req, res) => {
try{
await pull();
res.status(200);
res.send("Pulled");
}catch(e){
res.status(500);
res.send(e);
}
});
main();
async function build(body){
return await make();
}
async function main() {
await setup();
app.listen(3000, () => console.log('Server running on port 3000'));
process.on('SIGINT', async () => {
console.log("Shutting down");
process.exit(0);
});
}
async function pull(){
await runCommand("git pull");
}
async function make(makeCommand = "make build"){
try{
await runCommand(makeCommand);
let build = await fs.readFile(path.join(workingDir,"build/bundle.min.lua"), "utf8");
return build;
}catch(e){
console.log("Build failed");
}finally{
await runCommand("make clean");
}
}
async function setup() {
try {
await fs.mkdir(workingDir);
} catch (e) {
if (e.code !== "EEXIST") {
throw e;
}
}
if ((await fs.readdir(workingDir)).length !== 0) {
return;
}
console.log("Setting up repo");
await runCommand("git clone https://git.kapelle.org/niklas/cc-haxe.git .");
await runCommand("make deps");
}
function runCommand(cmd) {
return new Promise((resolve, reject) => {
childProcess.exec(cmd,{
cwd: workingDir,
}, (err, stdout, stderr) => {
if (err) {
reject(err);
}
console.log(stdout);
console.log(stderr);
resolve();
});
});
}

701
buildServer/package-lock.json generated Normal file
View File

@@ -0,0 +1,701 @@
{
"name": "buildServer",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "buildServer",
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"async": "^3.2.4",
"body-parser": "^1.20.2",
"express": "^4.18.2"
}
},
"node_modules/accepts": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
"license": "MIT",
"dependencies": {
"mime-types": "~2.1.34",
"negotiator": "0.6.3"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/array-flatten": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
"license": "MIT"
},
"node_modules/async": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz",
"integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ=="
},
"node_modules/body-parser": {
"version": "1.20.2",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
"integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
"dependencies": {
"bytes": "3.1.2",
"content-type": "~1.0.5",
"debug": "2.6.9",
"depd": "2.0.0",
"destroy": "1.2.0",
"http-errors": "2.0.0",
"iconv-lite": "0.4.24",
"on-finished": "2.4.1",
"qs": "6.11.0",
"raw-body": "2.5.2",
"type-is": "~1.6.18",
"unpipe": "1.0.0"
},
"engines": {
"node": ">= 0.8",
"npm": "1.2.8000 || >= 1.4.16"
}
},
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/call-bind": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.1",
"get-intrinsic": "^1.0.2"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/content-disposition": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
"integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
"license": "MIT",
"dependencies": {
"safe-buffer": "5.2.1"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/content-type": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cookie": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
"integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cookie-signature": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
"license": "MIT"
},
"node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"license": "MIT",
"dependencies": {
"ms": "2.0.0"
}
},
"node_modules/depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/destroy": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
"integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
"license": "MIT",
"engines": {
"node": ">= 0.8",
"npm": "1.2.8000 || >= 1.4.16"
}
},
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
"license": "MIT"
},
"node_modules/encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
"license": "MIT"
},
"node_modules/etag": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/express": {
"version": "4.18.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
"integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==",
"license": "MIT",
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
"body-parser": "1.20.1",
"content-disposition": "0.5.4",
"content-type": "~1.0.4",
"cookie": "0.5.0",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "2.0.0",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"finalhandler": "1.2.0",
"fresh": "0.5.2",
"http-errors": "2.0.0",
"merge-descriptors": "1.0.1",
"methods": "~1.1.2",
"on-finished": "2.4.1",
"parseurl": "~1.3.3",
"path-to-regexp": "0.1.7",
"proxy-addr": "~2.0.7",
"qs": "6.11.0",
"range-parser": "~1.2.1",
"safe-buffer": "5.2.1",
"send": "0.18.0",
"serve-static": "1.15.0",
"setprototypeof": "1.2.0",
"statuses": "2.0.1",
"type-is": "~1.6.18",
"utils-merge": "1.0.1",
"vary": "~1.1.2"
},
"engines": {
"node": ">= 0.10.0"
}
},
"node_modules/express/node_modules/body-parser": {
"version": "1.20.1",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
"integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==",
"dependencies": {
"bytes": "3.1.2",
"content-type": "~1.0.4",
"debug": "2.6.9",
"depd": "2.0.0",
"destroy": "1.2.0",
"http-errors": "2.0.0",
"iconv-lite": "0.4.24",
"on-finished": "2.4.1",
"qs": "6.11.0",
"raw-body": "2.5.1",
"type-is": "~1.6.18",
"unpipe": "1.0.0"
},
"engines": {
"node": ">= 0.8",
"npm": "1.2.8000 || >= 1.4.16"
}
},
"node_modules/express/node_modules/raw-body": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
"integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==",
"dependencies": {
"bytes": "3.1.2",
"http-errors": "2.0.0",
"iconv-lite": "0.4.24",
"unpipe": "1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/finalhandler": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
"integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
"license": "MIT",
"dependencies": {
"debug": "2.6.9",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"on-finished": "2.4.1",
"parseurl": "~1.3.3",
"statuses": "2.0.1",
"unpipe": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/forwarded": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/fresh": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
"integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
"license": "MIT"
},
"node_modules/get-intrinsic": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz",
"integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==",
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.1",
"has": "^1.0.3",
"has-proto": "^1.0.1",
"has-symbols": "^1.0.3"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.1"
},
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/has-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz",
"integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-symbols": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/http-errors": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
"integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
"license": "MIT",
"dependencies": {
"depd": "2.0.0",
"inherits": "2.0.4",
"setprototypeof": "1.2.0",
"statuses": "2.0.1",
"toidentifier": "1.0.1"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC"
},
"node_modules/ipaddr.js": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
"license": "MIT",
"engines": {
"node": ">= 0.10"
}
},
"node_modules/media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/merge-descriptors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
"integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==",
"license": "MIT"
},
"node_modules/methods": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
"license": "MIT",
"bin": {
"mime": "cli.js"
},
"engines": {
"node": ">=4"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT"
},
"node_modules/negotiator": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/object-inspect": {
"version": "1.12.3",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
"integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/on-finished": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
"license": "MIT",
"dependencies": {
"ee-first": "1.1.1"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/path-to-regexp": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==",
"license": "MIT"
},
"node_modules/proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
"integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
"license": "MIT",
"dependencies": {
"forwarded": "0.2.0",
"ipaddr.js": "1.9.1"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/qs": {
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
"integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
"license": "BSD-3-Clause",
"dependencies": {
"side-channel": "^1.0.4"
},
"engines": {
"node": ">=0.6"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/raw-body": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
"integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
"dependencies": {
"bytes": "3.1.2",
"http-errors": "2.0.0",
"iconv-lite": "0.4.24",
"unpipe": "1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"node_modules/send": {
"version": "0.18.0",
"resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
"integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
"license": "MIT",
"dependencies": {
"debug": "2.6.9",
"depd": "2.0.0",
"destroy": "1.2.0",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"fresh": "0.5.2",
"http-errors": "2.0.0",
"mime": "1.6.0",
"ms": "2.1.3",
"on-finished": "2.4.1",
"range-parser": "~1.2.1",
"statuses": "2.0.1"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/send/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/serve-static": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
"integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
"license": "MIT",
"dependencies": {
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"parseurl": "~1.3.3",
"send": "0.18.0"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/setprototypeof": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
"license": "ISC"
},
"node_modules/side-channel": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
"license": "MIT",
"dependencies": {
"call-bind": "^1.0.0",
"get-intrinsic": "^1.0.2",
"object-inspect": "^1.9.0"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/statuses": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/toidentifier": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
"license": "MIT",
"engines": {
"node": ">=0.6"
}
},
"node_modules/type-is": {
"version": "1.6.18",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
"license": "MIT",
"dependencies": {
"media-typer": "0.3.0",
"mime-types": "~2.1.24"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
"license": "MIT",
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
}
}
}

11
buildServer/package.json Normal file
View File

@@ -0,0 +1,11 @@
{
"name": "buildServer",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"async": "^3.2.4",
"body-parser": "^1.20.2",
"express": "^4.18.2"
}
}

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);

253
doc/Concepts.md Normal file
View File

@@ -0,0 +1,253 @@
Use this if you want to know how the OS works.
# Network
Every computer has a id assigned by the mod. It is a unique number that identifies the computer and cannot be changed.
The native implementation of the network stack works with channels. If you listen on a channel you will receive all messages sent to that channel.
Every computer listens to the channel with its id and a brodcast channel.
The broadcast channel is used for Routing Tables and GPS packets.
Network messages can be forwarded by other computers. This is done by the Routing Table. The routing algorithm prefers wire over wireless connections.
Packages also have a time-to-live (TTL) value. This is used to prevent packages from being forwarded forever. You will get a response if a package is dropped because of TTL.
There is also a concept of protocols. A protocol is used to distinguish between different types of packages.
A protocol is basically a string and that is used to forward packages to the correct handler. You can use `registerProto` to listen for packages with a specific protocol.
There are 2 ways of sending messages to other computers: `sendAndAwait` and `sendAndForget`.
`sendAndAwait` will wait for a response from the remote computer and return it.
`sendAndForget` will send the message and return immediately and does not care about the response.
You can compare it to UDP and TCP.
## Usage
```haxe
import net.Net;
var data = {"foo": "bar"};
Net.sendAndAwait(netID,"protoname",data).map((response)->{
switch (response){
case Success(data):
trace(data);
case Failure(error):
trace(error);
}
});
Net.registerProto("res",(pack: GenericPackage)->{
var requestPack: Package<MyType> = cast pack; // Try not to use Dynamic
requestPack.respond("Hello Back");
});
```
# Peripherals
Peripherals are devices that are connected to the computer. They can be used to interact with the world.
Every peripheral has an address and a type. The address can be "back" or "right" to refer to the peripheral on the back or right side of the computer or
something like "energyCell_0" to refer to something connected via cable. Peripherals can be accessed via the Peripheral class.
Also peripherals can be made accessible via the network. More on that later.
## Usage
```haxe
var back = Peripheral.getRedstone("back");
back.setOutput(true);
var drive = Peripheral.getDrive("drive_0");
drive.eject();
```
# GUI
If you want to write something to the screen you have to create a `WindowContext` via the `WindowManager`. This allows programs to write to the screen without interfering with each other.
There are currently 2 types of `WindowContext`: the `BufferedVirtualTermWriter` that stores the state of the screen in a buffer and prints it to the screen
when it is activeted and the `StatelessVirtualTermWriter` which calls a render method when it is activated. Currently i prefer the `StatelessVirtualTermWriter` because its not so heavy on the RAM but both work.
They both can be used just like the [nativ implmentation](https://tweaked.cc/module/term.html).
## Usage
```haxe
var ctx = WindowManager.createNewBufferedContext();
ctx.setCursorPos(0, 0);
ctx.setCursorBlink(false);
ctx.setBackgroundColor(Blue);
ctx.setForegroundColor(White);
ctx.write("Hello world!");
```
## Under the hood
There are a number of interfaces and classes that needs to be explained to understand how the GUI works.
`TermWriteable` is an interface that allows the usage of the normal CC terminal write methods. Stuff like `write`, `setCursorPos` and `setCursorBlink` are defined here. This is of course implemented by the physical screens and the main terminal.
Most of the time you will not write directory to a real screen but to a `VirtualTermWriter` which extends `TermWriteable` with some more methods like `enable`
and `setTarget`. The `setTarget` is used as the proxy target of a `VirtualTermWriter` and with `enable` and `disable` you can enable and disable the forwarding of the write methods to the target.
The `StatelessVirtualTermWriter` and `BufferedVirtualTermWriter` are both `VirtualTermWriter`. They can have a real output as a target. Or they can have another `VirtualTermWriter` as target like the `BufferedVirtualTermWriter` which uses a `TermBuffer` as an intermediate target.
All of that is just for printing to the screen. If you want to read input you have to use the `WindowContext` which is a `TermWriteable`.
`WindowContext` also handles events like `onClick` or `onKey`. This is need so that the right program gets the input depending on the active window on the
screen or terminal.
All of the `WindowContext` are managed by the `WindowManager`. The `WindowManager` also delegates the events to the right `WindowContext`.
## GUI helper classes
Because we want a more abstract way of writing to the screen we have some "helper" classes. I call them "helper" but they a very essential to the GUI.
First there is the `Pixel` class which is nothing more that a char and a foreground and background color.
A collection of `Pixel` is called a `Canvas` which is nothing more than a 2D array of `Pixel` with some functions strapped to it.
# Proceses
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. Basically everything that runs and is not part of the kernel is a process.
More on that at [Applications](#applications).
# EndOfLoop
You can imagine the whole runtime like the event loop is JS. The `EndOfLoop` class is used to register callbacks that are called at the end of the loop.
This is like the `setTimeout(0, callback)` in JS.
# Turtle Thread
Computercraft is mostly event-based. Listening to `Coroutine.yield()` waits for events with an option parameter for filtering events.
The problem with turtle arises when we call turtle functions like `move` or `place`. These functions wait internally for
the operation to finish with a `Coroutine.yield("turtle_response")` call. There is no way to prevent this. So in order to keep the main loop from
being blocked, we run all the turtle code in a separate thread.
There are no real threads in Lua, but we can create a coroutine for the turtles. Lua now switches between the main thread and the turtle thread.
Switching occurs when a `turtle_response` events is fired. This will be forwarded to the turtle thread. Once the turtle thread has run its code
and waits for the turtle operation to finish, we switch back to the main thread. The main thread is running normally until a `turtle_response` fires
and the circle repeats.
Be aware that no one is stopping you from running turtle commands in the main thread.
See `KernelEvents.hx` for more.
## Using Turtle threads
Example:
```haxe
// In the context of a process
if (!handle.claimTurtleMutex()) {
Log.warn("Failed to claim turtle thread");
return;
}
// Btw. no one is stopping you from calling this without claiming the mutex.
TurtleMutex.runInTThread(() -> {
while(true){
Turtle.turnLeft();
}
});
```
# 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.

112
doc/Manual.md Normal file
View File

@@ -0,0 +1,112 @@
Use this if you want to know how to use the OS.
# Commands
## Disk
Interact with a disk drive.
Synopsys:
- `disk ls` list all disk drives and its content
- `disk play <drive>` play an audio disk inside a disk drive
- `disk stop <drive>` stops playing audio
- `disk eject <drive>` ejects the disk
- `disk lable <dive> [lable]` Reads or sets a lable on a disk.
## GPS
Interact with the buildin GPS and INS (Inertial Navigation System) system. The INS is used to update a turtle's position when it is moving.
The GPS has 4 states of accuracy:
- `0` Unknown position
- `1` Position from INS or its best guess
- `2` Position from manual input or the last known position
- `3` Position from GPS
In case of a turtle the GPS accuracy will be set to `1`.
Synopsys:
- `gps set <x> <y> <z>` set the position manually. This will get saved.
- `gps status` prints the current GPS accuracy, position and INS status.
- `gps locate` try to locate the current position. Accuracy will be set to `3` if successful.
- `gps ins` perform an INS alignment. Used to get the current heading.
## KSettings
Get and set Kernel settings.
Current settings are:
- `hostname` the hostname of the computer
- `sitecontroller` the id of the site controller
Synopsys:
- `ks get <setting>` print the value of a setting
- `ks set <setting> <value>` set the value of a setting
- `ks list` list all settings
## LSPS
List all processes running on the computer. Very simple and not very useful other than for debugging.
## Net
Interact with the network stack.
Synopsys:
- `net route` print the routing table
- `net iface` list all network interfaces
- `net proto` list all network currently active protocols
- `net ping <id>` ping a remote computer by id
## Peripherals
Interact with the peripherals connected to the computer.
Synopsys:
- `perf inspect <peripheral>` print all types and methods of a peripheral
- `perf list` list all peripherals
## Redstone
Interact with the redstone. Support for Project Red's Bundle Cable is partialy implemented.
Synopsys:
- `rs on <side> ` turn on redstone on a side
- `rs off <side> ` turn off redstone on a side
- `rs get <side> ` get the redstone state on a side
## Service
Interact with the service manager. A service is a program that runs in the background and can be automatically started when the computer boots.
A service is a normal program. In order to create a new service you must register a command with arguments under a name.
After that you can start the service by its name. If it is running you can enable it so it will be started when the computer boots.
Synopsys:
- `srv start <name> ` start a service
- `srv stop <name> ` stop a service
- `srv register <name> <binary> [args...]` register a service
- `srv unregister <name> ` unregister a service
- `srv list` list all services
- `srv enable <name> ` enable a service. Must be started before it can be enabled.
## Turtle
Interact with a turtle. Very simple. More to come maybe.
Synopsys:
- `turtle forward` move the turtle forward
- `turtle back` move the turtle back
- `turtle up` move the turtle up
- `turtle down` move the turtle down
- `turtle left` turn the turtle left
- `turtle right` turn the turtle right

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"
}
}
}
}

15
pre-commit.sh Executable file
View File

@@ -0,0 +1,15 @@
#!/usr/bin/env sh
# Run:
# ln -s ../../pre-commit.sh .git/hooks/pre-commit
set -e
# format
make format
git diff --name-only --cached --diff-filter=d | xargs -l git add
# build
make build

View File

@@ -1,11 +1,29 @@
import kernel.Log; import kernel.ui.WindowManager;
import kernel.Init; import kernel.ps.ProcessManager;
import kernel.binstore.BinStore;
using util.Extender.LambdaExtender; import kernel.MainTerm;
import lib.HomeContext;
class Startup { class Startup {
public static function main() { public static function main() {
Init.initKernel(); #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();
main.run();
} else {
var term = BinStore.instantiate("terminal");
var pid = ProcessManager.run(term, {});
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}');
}
}
}

103
src/bin/Disk.hx Normal file
View File

@@ -0,0 +1,103 @@
package bin;
import kernel.peripherals.Drive;
import lib.CLIAppBase;
import kernel.peripherals.Peripherals.Peripheral;
using tink.CoreApi;
using Lambda;
@:build(macros.Binstore.includeBin("Disk", ["disk"]))
class Disk extends CLIAppBase {
public function new() {
registerSyncSubcommand("ls", (args) -> {
Peripheral.getAllDrives().foreach(drive -> {
var addr = drive.getAddr();
var label = drive.getDiskLabel();
var id = drive.getDiskID();
if (drive.isDiskPresent()) {
if (drive.hasAudio()) {
handle.writeLine('${addr} => ${label} [AUDIO]');
} else {
handle.writeLine('${addr} => ${label} (${id})');
}
} else {
handle.writeLine('${addr} => [NO DISK]');
}
return true;
});
});
registerSyncSubcommand("play", (args) -> {
return audioDiskPlayPause(args.getString("addr"), true);
}, [Peripheral("addr", Drive.TYPE_NAME)]);
registerSyncSubcommand("stop", (args) -> {
return audioDiskPlayPause(args.getString("addr"), false);
}, [Peripheral("addr", Drive.TYPE_NAME)]);
registerSyncSubcommand("eject", (args) -> {
var driveAddr = args.getString("addr");
var drive = Peripheral.getDrive(driveAddr);
if (!drive.isDiskPresent()) {
handle.writeLine("No disk in drive: " + driveAddr);
return false;
}
drive.ejectDisk();
return true;
}, [Peripheral("addr", Drive.TYPE_NAME)]);
registerSyncSubcommand("label", (args) -> {
var driveAddr = args.getString("addr");
var drive = Peripheral.getDrive(driveAddr);
var label:Null<String> = args.getString("label");
if (!drive.isDiskPresent()) {
handle.writeLine("No disk in drive: " + driveAddr);
}
if (label == null || label == "") {
handle.writeLine(drive.getDiskLabel());
} else {
var err = drive.setDiskLabel(label);
if (err != null) {
handle.writeLine("Failed to set lable");
return false;
}
}
return true;
}, [Peripheral("addr", Drive.TYPE_NAME), Optional(String("label"))]);
}
private function audioDiskPlayPause(driveAddr:String, play:Bool):Bool {
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;
}
if (!drive.hasAudio()) {
handle.writeLine("Disk in drive: " + driveAddr + " does not have audio");
return false;
}
if (play) {
drive.playAudio();
} else {
drive.stopAudio();
}
return true;
}
}

72
src/bin/GPS.hx Normal file
View File

@@ -0,0 +1,72 @@
package bin;
import lib.CLIAppBase;
import kernel.gps.INS;
import lib.WorldPos;
import lib.Vec.Vec3;
using tink.CoreApi;
@:build(macros.Binstore.includeBin("GPS", ["gps"]))
class GPS extends CLIAppBase {
public function new() {
registerSyncSubcommand("set", (args) -> {
var x:Float = args.getFloat("x");
var y:Float = args.getFloat("y");
var z:Float = args.getFloat("z");
var pos:WorldPos = new Vec3<Float>(x, y, z);
kernel.gps.GPS.setManualPosition(pos);
return true;
}, [Float("x"), Float("y"), Float("z")]);
registerSyncSubcommand("status", (args) -> {
var pos = kernel.gps.GPS.getPosition();
if (pos != null) {
handle.writeLine('Position x:${pos.x} y:${pos.y} z:${pos.z}');
} else {
handle.writeLine("Position not available");
return true;
}
var acc = kernel.gps.GPS.getAccuracy();
if (acc == 1) {
handle.writeLine("Accuracy: Low");
} else if (acc == 2) {
handle.writeLine("Accuracy: Medium");
} else if (acc == 3) {
handle.writeLine("Accuracy: High");
}
var ins = INS.getHeading();
if (ins != null) {
handle.writeLine('INS heading: ${ins.x} y:${ins.y} z:${ins.z}');
} else {
handle.writeLine("INS heading not available");
}
return true;
});
registerAsyncSubcommand("locate", (args) -> {
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;
});
});
registerAsyncSubcommand("ins", (args) -> {
return INS.align().map((_) -> {
handle.writeLine("INS aligned");
return true;
});
});
}
}

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

@@ -0,0 +1,14 @@
package bin;
import kernel.ps.ProcessHandle;
import kernel.ps.IProcess;
@:build(macros.Binstore.includeBin("ID", ["id"]))
class ID implements IProcess {
public function new() {}
public function run(handle:ProcessHandle) {
handle.writeLine("ID: " + kernel.net.Net.networkID);
handle.close();
}
}

63
src/bin/KSettings.hx Normal file
View File

@@ -0,0 +1,63 @@
package bin;
import kernel.KernelSettings;
import lib.CLIAppBase;
@:build(macros.Binstore.includeBin("Kernel settings", ["ksettings", "kset"]))
class KSettings extends CLIAppBase {
public function new() {
registerSyncSubcommand("get", (args) -> {
var key = args.getString("key");
var value = switch (key) {
case "hostname":
KernelSettings.hostname;
case "nameServer":
Std.string(KernelSettings.nameServer);
default:
null;
}
if (value == null) {
handle.writeLine("Key not found or not set");
return false;
}
handle.writeLine(value);
return true;
}, [String("key")]);
registerSyncSubcommand("set", (args) -> {
var key = args.getString("key");
if (key == null) {
handle.writeLine("Key not specified");
return false;
}
var value = args.getString("value");
if (value == null) {
handle.writeLine("Value not specified");
return false;
}
switch (key) {
case "hostname":
KernelSettings.hostname = value;
case "nameServer":
KernelSettings.nameServer = Std.parseInt(value);
default:
handle.writeLine("Key not found");
return false;
}
return true;
}, [String("key"), String("value")]);
registerSyncSubcommand("list", (args) -> {
handle.writeLine("hostname");
handle.writeLine("nameServer");
return true;
});
}
}

65
src/bin/KernelLog.hx Normal file
View File

@@ -0,0 +1,65 @@
package bin;
import kernel.ps.ProcessHandle;
import kernel.ps.IProcess;
import lib.Color;
import lib.MathI;
import kernel.log.Log;
import kernel.ui.WindowContext;
using tink.CoreApi;
@:build(macros.Binstore.includeBin("Log", ["log"]))
class KernelLog implements IProcess {
private var handle:ProcessHandle;
private var ctx:WindowContext;
public function new() {}
public function run(handle:ProcessHandle):Void {
this.handle = handle;
var statelessCtx = handle.createStatelessWindowContext();
this.ctx = statelessCtx.ctx;
statelessCtx.setRenderFunc(this.render);
Log.onLog.handle(() -> {
statelessCtx.requestRender();
});
}
private function render() {
ctx.clear();
ctx.setCursorPos(0, 0);
var lines = Log.getLines();
var height = ctx.getSize().y;
var start = MathI.max(lines.length - height, 0);
for (i in start...lines.length) {
var line = lines[i];
switch (line.level) {
case Info:
ctx.setTextColor(Color.White);
ctx.write("[INFO] ");
case Warn:
ctx.setTextColor(Color.Yellow);
ctx.write("[WARN] ");
case Error:
ctx.setTextColor(Color.Red);
ctx.write("[ERRO] ");
case Debug:
ctx.setTextColor(Color.Gray);
ctx.write("[DEBG] ");
case Silly:
ctx.setTextColor(Color.Gray);
ctx.write("[SILL] ");
}
ctx.write(line.message);
ctx.setCursorPos(0, ctx.getCursorPos().y + 1);
}
}
}

22
src/bin/LSPS.hx Normal file
View File

@@ -0,0 +1,22 @@
package bin;
import kernel.ps.ProcessManager;
import kernel.ps.ProcessHandle;
import kernel.ps.IProcess;
@:build(macros.Binstore.includeBin("LSPS", ["lsps"]))
class LSPS implements IProcess {
public function new() {}
public function run(handle:ProcessHandle) {
var pids = ProcessManager.listProcesses();
handle.writeLine('Count: ${pids.length}');
for (pid in pids) {
handle.writeLine('${pid}');
}
handle.close();
}
}

57
src/bin/Net.hx Normal file
View File

@@ -0,0 +1,57 @@
package bin;
import lib.CLIAppBase;
import kernel.peripherals.Peripherals.Peripheral;
import kernel.net.Routing;
using tink.CoreApi;
@:build(macros.Binstore.includeBin("Net", ["net"]))
class Net extends CLIAppBase {
public function new() {
registerSyncSubcommand("route", (args) -> {
var routes = Routing.getRouteTable();
for (k => v in routes) {
handle.writeLine('${k} => ${v.interf.name()}(${v.cost})');
}
return true;
});
registerSyncSubcommand("iface", (args) -> {
var modems = Peripheral.getAllModems();
for (modem in modems) {
handle.writeLine(modem.name());
}
return true;
});
registerSyncSubcommand("proto", (args) -> {
var protos = kernel.net.Net.getActiveProtocols();
for (proto in protos) {
handle.writeLine(proto);
}
return true;
});
registerAsyncSubcommand("ping", (args) -> {
var toID:Int = args.getInt("id");
return kernel.net.Net.ping(toID).map(result -> {
switch (result) {
case Success(_):
handle.write("Ping succeeded");
case Failure(failure):
handle.write("Ping failed: " + failure);
}
return true;
});
}, [Int("id")]);
}
}

33
src/bin/Peri.hx Normal file
View File

@@ -0,0 +1,33 @@
package bin;
import kernel.peripherals.Peripherals.Peripheral;
import lib.CLIAppBase;
@:build(macros.Binstore.includeBin("Peripheral", ["ph", "peripheral"]))
class Peri extends CLIAppBase {
public function new() {
registerSyncSubcommand("inspect", (args) -> {
var addr = args.getString("addr");
var result = Peripheral.inspect(addr);
handle.writeLine("Types:");
for (type in result.types) {
handle.writeLine(" " + type);
}
handle.writeLine("Methods:");
for (method in result.methods) {
handle.writeLine(" " + method);
}
return true;
}, [Addr("addr")]);
registerSyncSubcommand("list", (args) -> {
for (addr in Peripheral.getAllAddresses()) {
handle.writeLine('$addr => ${Peripheral.getTypes(addr).join(", ")}');
}
return true;
});
}
}

27
src/bin/Redstone.hx Normal file
View File

@@ -0,0 +1,27 @@
package bin;
import lib.CLIAppBase;
import kernel.peripherals.Peripherals.Peripheral;
using tink.CoreApi;
@:build(macros.Binstore.includeBin("Redstone", ["redstone", "rs"]))
class Redstone extends CLIAppBase {
public function new() {
registerSyncSubcommand("on", (args) -> {
Peripheral.getRedstone(args.getString("side")).setOutput(true);
return true;
}, [Side("side")]);
registerSyncSubcommand("off", (args) -> {
Peripheral.getRedstone(args.getString("side")).setOutput(false);
return true;
}, [Side("side")]);
registerSyncSubcommand("get", (args) -> {
var value = Peripheral.getRedstone(args.getString("side")).getAnalogInput();
handle.write("Analog input: " + value);
return true;
}, [Side("side")]);
}
}

60
src/bin/Service.hx Normal file
View File

@@ -0,0 +1,60 @@
package bin;
import kernel.service.ServiceManager;
import lib.CLIAppBase;
using tink.CoreApi;
@:build(macros.Binstore.includeBin("Service", ["srv"]))
class Service extends CLIAppBase {
public function new() {
registerSyncSubcommand("start", (args) -> {
var result = ServiceManager.start(args.getString("name"));
return handleResult(result);
}, [String("name")]);
registerSyncSubcommand("stop", (args) -> {
var result = ServiceManager.stop(args.getString("name"));
return handleResult(result);
}, [String("name")]);
registerSyncSubcommand("register", (args) -> {
var name = args.getString("name");
var binName = args.getString("binary");
var rest = args.getRest();
var result = ServiceManager.register(name, binName, rest);
return handleResult(result);
}, [String("name"), String("binary"), Rest("args")]);
registerSyncSubcommand("unregister", (args) -> {
var result = ServiceManager.unregister(args.getString("name"));
return handleResult(result);
}, [String("name")]);
registerSyncSubcommand("list", (args) -> {
var list = ServiceManager.listRunning();
for (name in list) {
this.handle.writeLine(name);
}
return true;
});
registerSyncSubcommand("enable", (args) -> {
ServiceManager.enable(args.getString("name"));
return true;
}, [String("name")]);
}
private function handleResult(res:Outcome<Noise, String>):Bool {
switch (res) {
case Success(_):
return true;
case Failure(e):
this.handle.write(e);
return false;
}
}
}

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"))
]);
}
}

226
src/bin/Terminal.hx Normal file
View File

@@ -0,0 +1,226 @@
package bin;
import lib.MathI;
import kernel.EndOfLoop;
import lua.NativeStringTools;
import kernel.binstore.BinStore;
import kernel.ps.ProcessHandle;
import kernel.ps.IProcess;
import kernel.ps.ProcessManager;
import lib.Color;
import kernel.ui.WindowContext;
using tink.CoreApi;
@:build(macros.Binstore.includeBin("Terminal", ["terminal"]))
class Terminal implements IProcess {
private static inline final MAX_BACKLOG:Int = 100;
private var handle:ProcessHandle;
private var ctx:WindowContext;
private var requestRender:() -> Void;
private var input:String = "";
private var backlog:Array<String> = [];
private var history:Array<String> = [];
private var historyIndex:Int = 0;
private var scrollBack = 0;
private var runningPID:PID = -1;
public function new() {}
public function run(handle:ProcessHandle):Void {
this.handle = handle;
var statelessContext = handle.createStatelessWindowContext();
this.ctx = statelessContext.ctx;
this.requestRender = statelessContext.requestRender;
statelessContext.setRenderFunc(this.render);
// Add input event handlers
handle.addCallbackLink(this.ctx.onChar.handle(char -> {
if (this.runningPID > 0)
return;
this.input += char;
this.requestRender();
}));
// Add key event handlers
handle.addCallbackLink(this.ctx.onKey.handle(e -> {
switch (e.keyCode) {
case 259: // Backspace
if (this.runningPID > 0)
return;
this.input = this.input.substr(0, this.input.length - 1);
this.requestRender();
case 257: // Enter
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);
case 269: // END
this.stopCurrentPS();
case 265: // UP
if (this.historyIndex < this.history.length) {
this.historyIndex++;
this.input = this.history[this.history.length - this.historyIndex];
this.requestRender();
}
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();
}
}));
this.requestRender();
if (handle.args.length > 0) {
var arg1 = handle.args[0];
this.backlog.push("> " + arg1);
this.backlog.push("");
EndOfLoop.endOfLoop(() -> {
this.invokeCommand(arg1);
});
}
}
private function stopCurrentPS() {
if (this.runningPID < 0) {
return;
}
ProcessManager.kill(this.runningPID);
}
private function render() {
var size = this.ctx.getSize();
var linesAvailable = size.y - 1;
var withoutScrollBack = (this.backlog.length - linesAvailable);
var start:Int = withoutScrollBack - scrollBack;
for (i in 0...linesAvailable) {
var line = this.backlog[start + i];
this.ctx.setCursorPos(0, i);
this.ctx.clearLine();
if (line != null) {
this.ctx.write(line);
}
}
this.ctx.setCursorPos(0, size.y - 1);
this.ctx.clearLine();
this.ctx.setTextColor(Color.Blue);
this.ctx.write("> ");
this.ctx.setTextColor(Color.White);
this.ctx.write(this.input);
if (this.runningPID < 0) {
this.ctx.setCursorBlink(true);
} else {
this.ctx.setCursorBlink(false);
}
}
private function invokeCommand(command:String):Void {
var args = this.parseArgs(command);
if (args.length == 0) {
return;
}
this.history.push(command);
if (this.history.length > MAX_BACKLOG) {
this.history.shift();
}
var commandName = args[0];
// Handle built-in commands
switch (commandName) {
case "clear":
this.clear();
return;
}
var commandArgs:Array<String> = args.slice(1);
var ps = BinStore.instantiate(commandName);
if (ps == null) {
this.backlog.push("Unknown command: " + commandName);
this.requestRender();
return;
}
this.runningPID = ProcessManager.run(ps, {
args: commandArgs,
onWrite: (s:String) -> {
if (s == "") {
return;
}
var splits = s.split("\n");
for (i => split in splits) {
if (i == 0) {
this.backlog[this.backlog.length - 1] += split;
} else {
this.backlog.push(split);
}
}
this.requestRender();
},
onExit: (success:Bool) -> {
this.runningPID = -1;
if (this.backlog[this.backlog.length - 1] == "") {
this.backlog.pop();
}
this.requestRender();
}
});
this.ctx.setCursorBlink(false);
}
/**
Convter a command string into an array of arguments where the first element is the command name
**/
private function parseArgs(command:String):Array<String> {
// TODO: tim and quote handling
return command.split(" ");
}
private function clear() {
this.backlog = [];
this.requestRender();
}
private function moveCursorToInput() {
var size = this.ctx.getSize();
this.ctx.setCursorPos(this.input.length + 2, size.y - 1);
}
}

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;
});
}
}

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

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

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

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

View File

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

View File

@@ -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

@@ -0,0 +1,59 @@
package bin.pathfinder;
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.IProcess;
class PFClient implements IProcess {
private var handle:ProcessHandle;
private var ctx:WindowContext;
private var requestRender:Void->Void;
private var root:RootElement;
public function new() {}
public function run(handle:ProcessHandle) {
this.handle = handle;
var stateless = handle.createStatelessWindowContext();
this.ctx = stateless.ctx;
this.requestRender = stateless.requestRender;
stateless.setRenderFunc(this.render);
this.root = new RootElement();
this.root.setTitle("Pathfinder");
this.ctx.delegateEvents(this.root);
this.requestRender();
}
private function render() {
var acc = kernel.gps.GPS.getAccuracy();
var pos:WorldPos = kernel.gps.GPS.getPosition() ?? {x: 0, y: 0, z: 0};
var childre:Array<IUIElement> = [
new TextElement('Acc: ${acc}'),
new TextElement('Pos: X:${pos.x} Y:${pos.y} Z:${pos.z}'),
new TextElement('UPDATE', {
style: {bgColor: Gray},
uiEvents: {
onClick: () -> {
kernel.gps.GPS.locate().handle((pos) -> {
this.requestRender();
});
}
}
}),
];
this.root.setChildren(childre);
this.root.render(ctx.getSize()).renderToContext(ctx);
}
}

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

@@ -0,0 +1,67 @@
package bin.turtle;
import lib.turtle.Helper;
import lib.turtle.TurtleAppBase;
class Excavate extends TurtleAppBase {
private var x:Int = 0;
private var y:Int = 0;
private var z:Int = 0;
public function new() {
super(init, turtleFunction);
}
private function init() {
if (handle.args.length < 2) {
handle.writeLine("Usage: excavate <front> <right> <down>");
handle.close(false);
}
var parsedX = Std.parseInt(handle.args[0]);
var parsedY = Std.parseInt(handle.args[1]);
var parsedZ = Std.parseInt(handle.args[2]);
if (parsedX == null || parsedY == null || parsedZ == null) {
handle.writeLine("Invalid arguments");
handle.close(false);
}
this.x = parsedX;
this.y = parsedY;
this.z = parsedZ;
}
private function turtleFunction() {
for (z in 0...this.z) {
for (y in 0...this.y) {
for (x in 0...this.x) {
kernel.turtle.Turtle.dig(Down);
if (x < this.x - 1) {
kernel.turtle.Turtle.forward();
}
}
if (y < this.y - 1) {
if (y % 2 == 0) {
kernel.turtle.Turtle.turnRight();
kernel.turtle.Turtle.forward();
kernel.turtle.Turtle.turnRight();
} else {
kernel.turtle.Turtle.turnLeft();
kernel.turtle.Turtle.forward();
kernel.turtle.Turtle.turnLeft();
}
}
}
kernel.turtle.Turtle.dig(Down);
kernel.turtle.Turtle.down();
Helper.times(kernel.turtle.Turtle.turnLeft, 2);
}
handle.close(true);
}
}

31
src/bin/turtle/Patrol.hx Normal file
View File

@@ -0,0 +1,31 @@
package bin.turtle;
import kernel.turtle.TurtleMutex;
import kernel.ps.ProcessHandle;
import kernel.ps.IProcess;
class Patrol implements IProcess {
private var handle:ProcessHandle;
public function new() {}
public function run(handle:ProcessHandle) {
this.handle = handle;
if (!handle.claimTurtleMutex()) {
handle.writeLine("Failed to claim turtle mutex");
handle.close();
}
handle.writeLine("Patroling");
TurtleMutex.runInTThread(() -> {
while (true) {
kernel.turtle.Turtle.forward();
kernel.turtle.Turtle.forward();
kernel.turtle.Turtle.forward();
kernel.turtle.Turtle.turnLeft();
}
});
}
}

28
src/kernel/EndOfLoop.hx Normal file
View File

@@ -0,0 +1,28 @@
package kernel;
import cc.OS;
/**
Make sure that a function is called at the end of the current event loop.
Like setTimeout(func, 0) in JavaScript.
**/
class EndOfLoop {
private static var backlog:Array<Void->Void> = [];
private static var isQueued = false;
public static function endOfLoop(func:Void->Void) {
backlog.push(func);
if (!isQueued) {
OS.queueEvent("endofloop", null);
}
}
@:allow(kernel.KernelEvents)
private static function run() {
for (func in backlog) {
func();
}
backlog = [];
isQueued = false;
}
}

22
src/kernel/Entrypoint.hx Normal file
View File

@@ -0,0 +1,22 @@
package kernel;
import kernel.log.Log;
class Entrypoint {
public static function main() {
try {
Init.initKernel();
} catch (e) {
Log.error('Error in init: ${e.toString()}');
return;
}
try {
Startup.main();
} catch (e) {
Log.error('Error in startup: ${e.toString()}');
}
KernelEvents.startEventLoop();
}
}

View File

@@ -1,38 +1,44 @@
package kernel; package kernel;
import haxe.MainLoop; #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 kernel.net.Routing;
import cc.OS;
import util.Debug;
import kernel.ui.WindowManager; import kernel.ui.WindowManager;
import kernel.peripherals.Peripherals.Peripheral;
import kernel.net.Net; import kernel.net.Net;
class Init { class Init {
@:allow(kernel.KernelEvents)
public static function initKernel() { public static function initKernel() {
// Init singeltons here because haxe is confused about the order to create them. // Init singeltons here because haxe is confused about the order to create them.
KernelEvents.instance = new KernelEvents();
Peripheral.instance = new Peripheral();
WindowManager.instance = new WindowManager();
MainTerm.instance = new MainTerm();
Log.init(); Log.init();
KernelEvents.init();
Routing.instance = new Routing(); WindowManager.init();
Net.instance = new Net(); MainTerm.instance = new MainTerm();
Routing.init();
Net.init();
GPS.init();
// Register default terminate handler // Register default terminate handler
KernelEvents.instance.onTerminate.handle(_->{ KernelEvents.onTerminate.handle(_ -> {
OS.reboot(); KernelEvents.shutdown();
}); });
#if debug
Debug.printBuildInfo(); Debug.printBuildInfo();
#end
Routing.instance.init(); if (!FS.exists("/var/ns")) {
FS.makeDir("/var/ns");
MainLoop.add(()->{ }
KernelEvents.instance.startEventLoop();
});
ServiceManager.init();
} }
} }

View File

@@ -1,8 +1,13 @@
package kernel; package kernel;
import kernel.turtle.TurtleMutex;
import kernel.turtle.Turtle;
import kernel.peripherals.Peripherals.Peripheral;
import kernel.log.Log;
import lib.ScreenPos;
import cc.HTTP.HTTPResponse;
import lua.TableTools; import lua.TableTools;
import lua.Coroutine; import lua.Coroutine;
import util.Vec.Vec2;
import haxe.Exception; import haxe.Exception;
using tink.CoreApi; using tink.CoreApi;
@@ -15,158 +20,220 @@ class KernelEvents {
/** /**
Depends on: (Nothing) Depends on: (Nothing)
**/ **/
public static var instance:KernelEvents; public static var onAlarm(default, null):Signal<Int>;
public final onAlarm:Signal<Int>; public static var onChar(default, null):Signal<String>;
public final onChar:Signal<String>; public static var onDisk(default, null):Signal<String>;
public final onDisk:Signal<String>; public static var onDiskEject(default, null):Signal<String>;
public final onDiskEject:Signal<String>; public static var onHttpCheck(default, null):Signal<{url:String, success:Bool, failReason:Any}>;
public final onHttpCheck:Signal<{url:String, success:Bool, failReason:Any}>; public static var onHttpFailure(default, null):Signal<{url:String, failReason:String, handle:HTTPResponse}>;
public final onHttpFailure:Signal<{url:String, failReason:String, handle:Any}>; public static var onHttpSuccess(default, null):Signal<{url:String, handle:HTTPResponse}>;
public final onHttpSuccess:Signal<{url:String, handle:Any}>; public static var onKey(default, null):Signal<{keyCode:Int, isHeld:Bool}>;
public final onKey:Signal<{keyCode:Int, isHeld:Bool}>; public static var onKeyUp(default, null):Signal<Int>;
public final onKeyUp:Signal<Int>; public static var onModemMessage(default, null):Signal<{
public final onModemMessage:Signal<{
addr:String, addr:String,
channel:Int, channel:Int,
replyChannel:Int, replyChannel:Int,
message:Dynamic, message:Dynamic,
distance:Int distance:Null<Float>
}>; }>;
public final onMonitorResize:Signal<String>; public static var onMonitorResize(default, null):Signal<String>;
public final onMonitorTouch:Signal<{addr:String, pos:Vec2<Int>}>; public static var onMonitorTouch(default, null):Signal<{addr:String, pos:ScreenPos}>;
public final onMouseClick:Signal<{button:ButtonType, pos:Vec2<Int>}>; public static var onMouseClick(default, null):Signal<{button:ButtonType, pos:ScreenPos}>;
public final onMouseDrag:Signal<{button:ButtonType, pos:Vec2<Int>}>; public static var onMouseDrag(default, null):Signal<{button:ButtonType, pos:ScreenPos}>;
public final onMouseScroll:Signal<{dir:Int, pos:Vec2<Int>}>; public static var onMouseScroll(default, null):Signal<{dir:Int, pos:ScreenPos}>;
public final onMouseUp:Signal<{button:ButtonType, pos:Vec2<Int>}>; public static var onMouseUp(default, null):Signal<{button:ButtonType, pos:ScreenPos}>;
public final onPaste:Signal<String>; public static var onPaste(default, null):Signal<String>;
public final onPeripheral:Signal<String>; public static var onPeripheral(default, null):Signal<String>;
public final onPeripheralDetach:Signal<String>; public static var onPeripheralDetach(default, null):Signal<String>;
public final onRednetMessage:Signal<{sender:Int, message:Any, protocol:Any}>; public static var onRedstone(default, null):Signal<Noise>;
public final onRedstone:Signal<Noise>; public static var onSpeakerAudioEmpty(default, null):Signal<String>;
public final onSpeakerAudioEmpty:Signal<String>; public static var onTaskComplete(default, null):Signal<{id:Int, success:Bool, failedReason:String}>;
public final onTaskComplete:Signal<{id:Int, success:Bool, failedReason:String}>; public static var onTermResize(default, null):Signal<Noise>;
public final onTermResize:Signal<Noise>; public static var onTerminate(default, null):Signal<Noise>;
public final onTerminate:Signal<Noise>; public static var onTimer(default, null):Signal<Int>;
public final onTimer:Signal<Int>; public static var onTurtleInventory(default, null):Signal<Noise>;
public final onTurtleInventory:Signal<Noise>; public static var onWebsocketClose(default, null):Signal<String>;
public final onWebsocketClose:Signal<String>; public static var onWebsocketFailure(default, null):Signal<{url:String, failReason:String}>;
public final onWebsocketFailure:Signal<{url:String, failReason:String}>; public static var onWebsocketMessage(default, null):Signal<{url:String, message:String, isBinary:Bool}>;
public final onWebsocketMessage:Signal<{url:String, message:String, isBinary:Bool}>; public static var onWebsocketSuccess(default, null):Signal<{url:String, handle:Any}>;
public final onWebsocketSuccess:Signal<{url:String, handle:Any}>;
private final onAlarmTrigger:SignalTrigger<Int> = Signal.trigger(); private static final onAlarmTrigger:SignalTrigger<Int> = Signal.trigger();
private final onCharTrigger:SignalTrigger<String> = Signal.trigger(); private static final onCharTrigger:SignalTrigger<String> = Signal.trigger();
private final onDiskTrigger:SignalTrigger<String> = Signal.trigger(); private static final onDiskTrigger:SignalTrigger<String> = Signal.trigger();
private final onDiskEjectTrigger:SignalTrigger<String> = Signal.trigger(); private static final onDiskEjectTrigger:SignalTrigger<String> = Signal.trigger();
private final onHttpCheckTrigger:SignalTrigger<{url:String, success:Bool, failReason:Any}> = Signal.trigger(); private static final onHttpCheckTrigger:SignalTrigger<{url:String, success:Bool, failReason:Any}> = Signal.trigger();
private final onHttpFailureTrigger:SignalTrigger<{url:String, failReason:String, handle:Any}> = Signal.trigger(); private static final onHttpFailureTrigger:SignalTrigger<{url:String, failReason:String, handle:HTTPResponse}> = Signal.trigger();
private final onHttpSuccessTrigger:SignalTrigger<{url:String, handle:Any}> = Signal.trigger(); private static final onHttpSuccessTrigger:SignalTrigger<{url:String, handle:HTTPResponse}> = Signal.trigger();
private final onKeyTrigger:SignalTrigger<{keyCode:Int, isHeld:Bool}> = Signal.trigger(); private static final onKeyTrigger:SignalTrigger<{keyCode:Int, isHeld:Bool}> = Signal.trigger();
private final onKeyUpTrigger:SignalTrigger<Int> = Signal.trigger(); private static final onKeyUpTrigger:SignalTrigger<Int> = Signal.trigger();
private final onModemMessageTrigger:SignalTrigger<{ private static final onModemMessageTrigger:SignalTrigger<{
addr:String, addr:String,
channel:Int, channel:Int,
replyChannel:Int, replyChannel:Int,
message:Dynamic, message:Dynamic,
distance:Int distance:Null<Float>
}> = Signal.trigger(); }> = Signal.trigger();
private final onMonitorResizeTrigger:SignalTrigger<String> = Signal.trigger(); private static final onMonitorResizeTrigger:SignalTrigger<String> = Signal.trigger();
private final onMonitorTouchTrigger:SignalTrigger<{addr:String, pos:Vec2<Int>}> = Signal.trigger(); private static final onMonitorTouchTrigger:SignalTrigger<{addr:String, pos:ScreenPos}> = Signal.trigger();
private final onMouseClickTrigger:SignalTrigger<{button:ButtonType, pos:Vec2<Int>}> = Signal.trigger(); private static final onMouseClickTrigger:SignalTrigger<{button:ButtonType, pos:ScreenPos}> = Signal.trigger();
private final onMouseDragTrigger:SignalTrigger<{button:ButtonType, pos:Vec2<Int>}> = Signal.trigger(); private static final onMouseDragTrigger:SignalTrigger<{button:ButtonType, pos:ScreenPos}> = Signal.trigger();
private final onMouseScrollTrigger:SignalTrigger<{dir:Int, pos:Vec2<Int>}> = Signal.trigger(); private static final onMouseScrollTrigger:SignalTrigger<{dir:Int, pos:ScreenPos}> = Signal.trigger();
private final onMouseUpTrigger:SignalTrigger<{button:ButtonType, pos:Vec2<Int>}> = Signal.trigger(); private static final onMouseUpTrigger:SignalTrigger<{button:ButtonType, pos:ScreenPos}> = Signal.trigger();
private final onPasteTrigger:SignalTrigger<String> = Signal.trigger(); private static final onPasteTrigger:SignalTrigger<String> = Signal.trigger();
private final onPeripheralTrigger:SignalTrigger<String> = Signal.trigger(); private static final onPeripheralTrigger:SignalTrigger<String> = Signal.trigger();
private final onPeripheralDetachTrigger:SignalTrigger<String> = Signal.trigger(); private static final onPeripheralDetachTrigger:SignalTrigger<String> = Signal.trigger();
private final onRednetMessageTrigger:SignalTrigger<{sender:Int, message:Any, protocol:Any}> = Signal.trigger(); private static final onRednetMessageTrigger:SignalTrigger<{sender:Int, message:Any, protocol:Any}> = Signal.trigger();
private final onRedstoneTrigger:SignalTrigger<Noise> = Signal.trigger(); private static final onRedstoneTrigger:SignalTrigger<Noise> = Signal.trigger();
private final onSpeakerAudioEmptyTrigger:SignalTrigger<String> = Signal.trigger(); private static final onSpeakerAudioEmptyTrigger:SignalTrigger<String> = Signal.trigger();
private final onTaskCompleteTrigger:SignalTrigger<{id:Int, success:Bool, failedReason:String}> = Signal.trigger(); private static final onTaskCompleteTrigger:SignalTrigger<{id:Int, success:Bool, failedReason:String}> = Signal.trigger();
private final onTermResizeTrigger:SignalTrigger<Noise> = Signal.trigger(); private static final onTermResizeTrigger:SignalTrigger<Noise> = Signal.trigger();
private final onTerminateTrigger:SignalTrigger<Noise> = Signal.trigger(); private static final onTerminateTrigger:SignalTrigger<Noise> = Signal.trigger();
private final onTimerTrigger:SignalTrigger<Int> = Signal.trigger(); private static final onTimerTrigger:SignalTrigger<Int> = Signal.trigger();
private final onTurtleInventoryTrigger:SignalTrigger<Noise> = Signal.trigger(); private static final onTurtleInventoryTrigger:SignalTrigger<Noise> = Signal.trigger();
private final onWebsocketCloseTrigger:SignalTrigger<String> = Signal.trigger(); private static final onWebsocketCloseTrigger:SignalTrigger<String> = Signal.trigger();
private final onWebsocketFailureTrigger:SignalTrigger<{url:String, failReason:String}> = Signal.trigger(); private static final onWebsocketFailureTrigger:SignalTrigger<{url:String, failReason:String}> = Signal.trigger();
private final onWebsocketMessageTrigger:SignalTrigger<{url:String, message:String, isBinary:Bool}> = Signal.trigger(); private static final onWebsocketMessageTrigger:SignalTrigger<{url:String, message:String, isBinary:Bool}> = Signal.trigger();
private final onWebsocketSuccessTrigger:SignalTrigger<{url:String, handle:Any}> = Signal.trigger(); private static final onWebsocketSuccessTrigger:SignalTrigger<{url:String, handle:Any}> = Signal.trigger();
private static var stopLoop:Bool = false;
private static var turtleCoroutine:Coroutine<Dynamic>;
@:allow(kernel.Init) @:allow(kernel.Init)
private function new() { private static function init() {
this.onAlarm = onAlarmTrigger.asSignal(); onAlarm = onAlarmTrigger.asSignal();
this.onChar = onCharTrigger.asSignal(); onChar = onCharTrigger.asSignal();
this.onDisk = onDiskTrigger.asSignal(); onDisk = onDiskTrigger.asSignal();
this.onDiskEject = onDiskEjectTrigger.asSignal(); onDiskEject = onDiskEjectTrigger.asSignal();
this.onHttpCheck = onHttpCheckTrigger.asSignal(); onHttpCheck = onHttpCheckTrigger.asSignal();
this.onHttpFailure = onHttpFailureTrigger.asSignal(); onHttpFailure = onHttpFailureTrigger.asSignal();
this.onHttpSuccess = onHttpSuccessTrigger.asSignal(); onHttpSuccess = onHttpSuccessTrigger.asSignal();
this.onKey = onKeyTrigger.asSignal(); onKey = onKeyTrigger.asSignal();
this.onKeyUp = onKeyUpTrigger.asSignal(); onKeyUp = onKeyUpTrigger.asSignal();
this.onModemMessage = onModemMessageTrigger.asSignal(); onModemMessage = onModemMessageTrigger.asSignal();
this.onMonitorResize = onMonitorResizeTrigger.asSignal(); onMonitorResize = onMonitorResizeTrigger.asSignal();
this.onMonitorTouch = onMonitorTouchTrigger.asSignal(); onMonitorTouch = onMonitorTouchTrigger.asSignal();
this.onMouseClick = onMouseClickTrigger.asSignal(); onMouseClick = onMouseClickTrigger.asSignal();
this.onMouseDrag = onMouseDragTrigger.asSignal(); onMouseDrag = onMouseDragTrigger.asSignal();
this.onMouseScroll = onMouseScrollTrigger.asSignal(); onMouseScroll = onMouseScrollTrigger.asSignal();
this.onMouseUp = onMouseUpTrigger.asSignal(); onMouseUp = onMouseUpTrigger.asSignal();
this.onPaste = onPasteTrigger.asSignal(); onPaste = onPasteTrigger.asSignal();
this.onPeripheral = onPeripheralTrigger.asSignal(); onPeripheral = onPeripheralTrigger.asSignal();
this.onPeripheralDetach = onPeripheralDetachTrigger.asSignal(); onPeripheralDetach = onPeripheralDetachTrigger.asSignal();
this.onRednetMessage = onRednetMessageTrigger.asSignal(); onRedstone = onRedstoneTrigger.asSignal();
this.onRedstone = onRedstoneTrigger.asSignal(); onSpeakerAudioEmpty = onSpeakerAudioEmptyTrigger.asSignal();
this.onSpeakerAudioEmpty = onSpeakerAudioEmptyTrigger.asSignal(); onTaskComplete = onTaskCompleteTrigger.asSignal();
this.onTaskComplete = onTaskCompleteTrigger.asSignal(); onTermResize = onTermResizeTrigger.asSignal();
this.onTermResize = onTermResizeTrigger.asSignal(); onTerminate = onTerminateTrigger.asSignal();
this.onTerminate = onTerminateTrigger.asSignal(); onTimer = onTimerTrigger.asSignal();
this.onTimer = onTimerTrigger.asSignal(); onTurtleInventory = onTurtleInventoryTrigger.asSignal();
this.onTurtleInventory = onTurtleInventoryTrigger.asSignal(); onWebsocketClose = onWebsocketCloseTrigger.asSignal();
this.onWebsocketClose = onWebsocketCloseTrigger.asSignal(); onWebsocketFailure = onWebsocketFailureTrigger.asSignal();
this.onWebsocketFailure = onWebsocketFailureTrigger.asSignal(); onWebsocketMessage = onWebsocketMessageTrigger.asSignal();
this.onWebsocketMessage = onWebsocketMessageTrigger.asSignal(); onWebsocketSuccess = onWebsocketSuccessTrigger.asSignal();
this.onWebsocketSuccess = onWebsocketSuccessTrigger.asSignal();
} }
/** /**
Start pulling events. Blocking. Start pulling events. Blocking.
**/ **/
public function startEventLoop() { @:allow(kernel.Entrypoint)
while (true) { private static function startEventLoop() {
if (Turtle.isTurtle()) {
turtleLoop();
} else {
runMainLoop();
}
}
@:allow(kernel.turtle.TurtleMutex)
private static function startTurtleCoroutine() {
turtleCoroutine = Coroutine.create(runTurtleLoop);
Coroutine.resume(turtleCoroutine);
}
private static function turtleLoop() {
startTurtleCoroutine();
while (!stopLoop) {
var eventData = pullEvents();
if (eventData[1] == "turtle_response" || eventData[1] == "tthread") {
var result = Coroutine.resume(turtleCoroutine, TableTools.unpack(eventData));
if (!result.success) {
Log.error('Error while running turtle thread: ${result.result}');
}
if (Coroutine.status(turtleCoroutine) == Dead) {
Log.error('Turtle thread died');
}
} else {
fireSignal(eventData[1], eventData);
}
}
}
private static function runTurtleLoop() {
while (!stopLoop) {
if ((TableTools.pack(Coroutine.yield()))[1] != "tthread")
continue;
if (stopLoop)
continue;
TurtleMutex.runThreadFunc();
}
}
private static function runMainLoop() {
while (!stopLoop) {
var event:Table<Int, Dynamic> = pullEvents(); var event:Table<Int, Dynamic> = pullEvents();
var eventName:String = event[1]; var eventName:String = event[1];
try {
fireSignal(eventName, event); fireSignal(eventName, event);
} catch (e:Dynamic) {
Log.error('Error while handling event: $eventName: ${e}');
}
} }
} }
private function pullEvents():Table<Int, Dynamic> { public static function shutdown() {
// clearing screens
for (screen in Peripheral.getAllScreens()) {
screen.reset();
}
Log.info('Shutting down event loop');
stopLoop = true;
MainTerm.instance.reset();
}
private static function pullEvents():Table<Int, Dynamic> {
return cast TableTools.pack(Coroutine.yield(null)); return cast TableTools.pack(Coroutine.yield(null));
} }
private function fireSignal(eventName: String,event:Table<Int, Dynamic> ) { private static function fireSignal(eventName:String, event:Table<Int, Dynamic>) {
switch eventName { switch eventName {
case "alarm": case "alarm":
this.onAlarmTrigger.trigger(event[2]); onAlarmTrigger.trigger(event[2]);
case "char": case "char":
this.onCharTrigger.trigger(event[2]); onCharTrigger.trigger(event[2]);
case "disk": case "disk":
this.onDiskTrigger.trigger(event[2]); onDiskTrigger.trigger(event[2]);
case "disk_eject": case "disk_eject":
this.onDiskEjectTrigger.trigger(event[2]); onDiskEjectTrigger.trigger(event[2]);
case "http_check": case "http_check":
this.onHttpCheckTrigger.trigger({url: event[2], success: event[3], failReason: event[4]}); onHttpCheckTrigger.trigger({url: event[2], success: event[3], failReason: event[4]});
case "http_failure": case "http_failure":
this.onHttpFailureTrigger.trigger({url: event[2], failReason: event[3], handle: event[4]}); onHttpFailureTrigger.trigger({url: event[2], failReason: event[3], handle: event[4]});
case "http_success": case "http_success":
this.onHttpSuccessTrigger.trigger({url: event[2], handle: event[3]}); onHttpSuccessTrigger.trigger({url: event[2], handle: event[3]});
case "key": case "key":
this.onKeyTrigger.trigger({keyCode: event[2], isHeld: event[3]}); onKeyTrigger.trigger({keyCode: event[2], isHeld: event[3]});
case "key_up": case "key_up":
this.onKeyUpTrigger.trigger(event[2]); onKeyUpTrigger.trigger(event[2]);
case "modem_message": case "modem_message":
this.onModemMessageTrigger.trigger({ onModemMessageTrigger.trigger({
addr: event[2], addr: event[2],
channel: event[3], channel: event[3],
replyChannel: event[4], replyChannel: event[4],
@@ -174,49 +241,49 @@ class KernelEvents {
distance: event[6] distance: event[6]
}); });
case "monitor_resize": case "monitor_resize":
this.onMonitorResizeTrigger.trigger(event[2]); onMonitorResizeTrigger.trigger(event[2]);
case "monitor_touch": case "monitor_touch":
this.onMonitorTouchTrigger.trigger({addr: event[2], pos: {x: (event[3] : Int) - 1, y: (event[4] : Int) - 1}}); onMonitorTouchTrigger.trigger({addr: event[2], pos: {x: (event[3] : Int) - 1, y: (event[4] : Int) - 1}});
case "mouse_click": case "mouse_click":
this.onMouseClickTrigger.trigger({button: ccButtonToEnum(event[2]), pos: {x: (event[3] : Int) - 1, y: (event[4] : Int) - 1}}); onMouseClickTrigger.trigger({button: ccButtonToEnum(event[2]), pos: {x: (event[3] : Int) - 1, y: (event[4] : Int) - 1}});
case "mouse_drag": case "mouse_drag":
this.onMouseDragTrigger.trigger({button: ccButtonToEnum(event[2]), pos: {x: (event[3] : Int) - 1, y: (event[4] : Int) - 1}}); onMouseDragTrigger.trigger({button: ccButtonToEnum(event[2]), pos: {x: (event[3] : Int) - 1, y: (event[4] : Int) - 1}});
case "mouse_scroll": case "mouse_scroll":
this.onMouseScrollTrigger.trigger({dir: event[2], pos: {x: (event[3] : Int) - 1, y: (event[4] : Int) - 1}}); onMouseScrollTrigger.trigger({dir: event[2], pos: {x: (event[3] : Int) - 1, y: (event[4] : Int) - 1}});
case "mouse_up": case "mouse_up":
this.onMouseUpTrigger.trigger({button: ccButtonToEnum(event[2]), pos: {x: (event[3] : Int) - 1, y: (event[4] : Int) - 1}}); onMouseUpTrigger.trigger({button: ccButtonToEnum(event[2]), pos: {x: (event[3] : Int) - 1, y: (event[4] : Int) - 1}});
case "paste": case "paste":
this.onPasteTrigger.trigger(event[2]); onPasteTrigger.trigger(event[2]);
case "peripheral": case "peripheral":
this.onPeripheralTrigger.trigger(event[2]); onPeripheralTrigger.trigger(event[2]);
case "peripheral_detach": case "peripheral_detach":
this.onPeripheralDetachTrigger.trigger(event[2]); onPeripheralDetachTrigger.trigger(event[2]);
case "rednet_message":
this.onRednetMessageTrigger.trigger({sender: event[2], message: event[3], protocol: event[4]});
case "redstone": case "redstone":
this.onRedstoneTrigger.trigger(null); onRedstoneTrigger.trigger(null);
case "speaker_audio_empty": case "speaker_audio_empty":
this.onSpeakerAudioEmptyTrigger.trigger(event[2]); onSpeakerAudioEmptyTrigger.trigger(event[2]);
case "task_complete": case "task_complete":
this.onTaskCompleteTrigger.trigger({id: event[2], success: event[3], failedReason: event[4]}); onTaskCompleteTrigger.trigger({id: event[2], success: event[3], failedReason: event[4]});
case "term_resize": case "term_resize":
this.onTermResizeTrigger.trigger(null); onTermResizeTrigger.trigger(null);
case "terminate": case "terminate":
this.onTerminateTrigger.trigger(null); onTerminateTrigger.trigger(null);
case "timer": case "timer":
this.onTimerTrigger.trigger(event[2]); onTimerTrigger.trigger(event[2]);
case "turtle_inventory": case "turtle_inventory":
this.onTurtleInventoryTrigger.trigger(null); onTurtleInventoryTrigger.trigger(null);
case "websocket_closed": case "websocket_closed":
this.onWebsocketCloseTrigger.trigger(event[2]); onWebsocketCloseTrigger.trigger(event[2]);
case "websocket_failure": case "websocket_failure":
this.onWebsocketFailureTrigger.trigger({url: event[2], failReason: event[3]}); onWebsocketFailureTrigger.trigger({url: event[2], failReason: event[3]});
case "websocket_message": case "websocket_message":
this.onWebsocketMessageTrigger.trigger({url: event[2], message: event[3], isBinary: event[4]}); onWebsocketMessageTrigger.trigger({url: event[2], message: event[3], isBinary: event[4]});
case "websocket_success": case "websocket_success":
this.onWebsocketSuccessTrigger.trigger({url: event[2], handle: event[3]}); onWebsocketSuccessTrigger.trigger({url: event[2], handle: event[3]});
case "endofloop":
EndOfLoop.run();
default: default:
Log.error("Unknown cc event: " + eventName); Log.error('Unknown event: $eventName');
} }
} }
@@ -232,4 +299,68 @@ class KernelEvents {
throw new Exception("Invalid input"); throw new Exception("Invalid input");
} }
} }
@:allow(lib.Debug)
private static function printListenerCount() {
if (onAlarmTrigger.getLength() > 0)
Log.debug("onAlarm: " + onAlarmTrigger.getLength());
if (onCharTrigger.getLength() > 0)
Log.debug("onChar: " + onCharTrigger.getLength());
if (onDiskTrigger.getLength() > 0)
Log.debug("onDisk: " + onDiskTrigger.getLength());
if (onDiskEjectTrigger.getLength() > 0)
Log.debug("onDiskEject: " + onDiskEjectTrigger.getLength());
if (onHttpCheckTrigger.getLength() > 0)
Log.debug("onHttpCheck: " + onHttpCheckTrigger.getLength());
if (onHttpFailureTrigger.getLength() > 0)
Log.debug("onHttpFailure: " + onHttpFailureTrigger.getLength());
if (onHttpSuccessTrigger.getLength() > 0)
Log.debug("onHttpSuccess: " + onHttpSuccessTrigger.getLength());
if (onKeyTrigger.getLength() > 0)
Log.debug("onKey: " + onKeyTrigger.getLength());
if (onKeyUpTrigger.getLength() > 0)
Log.debug("onKeyUp: " + onKeyUpTrigger.getLength());
if (onModemMessageTrigger.getLength() > 0)
Log.debug("onModemMessage: " + onModemMessageTrigger.getLength());
if (onMonitorResizeTrigger.getLength() > 0)
Log.debug("onMonitorResize: " + onMonitorResizeTrigger.getLength());
if (onMonitorTouchTrigger.getLength() > 0)
Log.debug("onMonitorTouch: " + onMonitorTouchTrigger.getLength());
if (onMouseClickTrigger.getLength() > 0)
Log.debug("onMouseClick: " + onMouseClickTrigger.getLength());
if (onMouseDragTrigger.getLength() > 0)
Log.debug("onMouseDrag: " + onMouseDragTrigger.getLength());
if (onMouseScrollTrigger.getLength() > 0)
Log.debug("onMouseScroll: " + onMouseScrollTrigger.getLength());
if (onMouseUpTrigger.getLength() > 0)
Log.debug("onMouseUp: " + onMouseUpTrigger.getLength());
if (onPasteTrigger.getLength() > 0)
Log.debug("onPaste: " + onPasteTrigger.getLength());
if (onPeripheralTrigger.getLength() > 0)
Log.debug("onPeripheral: " + onPeripheralTrigger.getLength());
if (onPeripheralDetachTrigger.getLength() > 0)
Log.debug("onPeripheralDetach: " + onPeripheralDetachTrigger.getLength());
if (onRedstoneTrigger.getLength() > 0)
Log.debug("onRedstone: " + onRedstoneTrigger.getLength());
if (onSpeakerAudioEmptyTrigger.getLength() > 0)
Log.debug("onSpeakerAudioEmpty: " + onSpeakerAudioEmptyTrigger.getLength());
if (onTaskCompleteTrigger.getLength() > 0)
Log.debug("onTaskComplete: " + onTaskCompleteTrigger.getLength());
if (onTermResizeTrigger.getLength() > 0)
Log.debug("onTermResize: " + onTermResizeTrigger.getLength());
if (onTerminateTrigger.getLength() > 0)
Log.debug("onTerminate: " + onTerminateTrigger.getLength());
if (onTimerTrigger.getLength() > 0)
Log.debug("onTimer: " + onTimerTrigger.getLength());
if (onTurtleInventoryTrigger.getLength() > 0)
Log.debug("onTurtleInventory: " + onTurtleInventoryTrigger.getLength());
if (onWebsocketCloseTrigger.getLength() > 0)
Log.debug("onWebsocketClose: " + onWebsocketCloseTrigger.getLength());
if (onWebsocketFailureTrigger.getLength() > 0)
Log.debug("onWebsocketFailure: " + onWebsocketFailureTrigger.getLength());
if (onWebsocketMessageTrigger.getLength() > 0)
Log.debug("onWebsocketMessage: " + onWebsocketMessageTrigger.getLength());
if (onWebsocketSuccessTrigger.getLength() > 0)
Log.debug("onWebsocketSuccess: " + onWebsocketSuccessTrigger.getLength());
}
} }

View File

@@ -0,0 +1,58 @@
package kernel;
import cc.OS;
import kernel.net.Package.NetworkID;
import lib.KVStore;
import cc.Settings;
class KernelSettings {
private static inline function setAllowStartup(value:Bool) {
Settings.set("shell.allow_startup", value);
}
private static inline function setAllowStartupFromFloppy(value:Bool) {
Settings.set("shell.allow_disk_startup", value);
}
private static function set(name:String, value:Dynamic) {
var kvstore = new KVStore("kernel");
kvstore.set(name, value);
kvstore.save();
}
private static function get(name:String):Null<Dynamic> {
var kvstore = new KVStore("kernel");
return kvstore.get(name);
}
public static var hostname(get, set):String;
private static var _hostname:String = get("hostname");
private static inline function get_hostname():String {
return _hostname;
}
private static inline function set_hostname(value:String):String {
OS.setComputerLabel(value);
set("hostname", value);
_hostname = value;
return value;
}
public static var nameServer(get, set):NetworkID;
private static var _nameServer:NetworkID = get("nameServer");
private static function get_nameServer():NetworkID {
return _nameServer;
}
private static function set_nameServer(value:NetworkID):NetworkID {
if (value == null) {
return get_nameServer();
}
set("nameServer", value);
_nameServer = value;
return value;
}
}

View File

@@ -1,62 +0,0 @@
package kernel;
import kernel.ui.WindowContext;
import kernel.ui.WindowManager;
import lib.TermWriteable;
import lib.TermIO;
#if webconsole
import kernel.net.Net;
import util.Debug;
#end
/**
Log messages to specified output.
**/
class Log {
private static var context:WindowContext;
private static var writer:TermIO;
/**
Depends on: WindowManager
**/
@:allow(kernel.Init)
private static function init() {
Log.context = WindowManager.instance.createNewContext();
Log.writer = new TermIO(Log.context);
}
private static function setMainoutout(newOutput:TermWriteable) {
writer = new TermIO(newOutput);
}
public static function info(msg:Dynamic, ?pos:haxe.PosInfos) {
writer.writeLn("[INFO][" + pos.className + "]: " + Std.string(msg));
#if webconsole
Debug.printWeb("[INFO][" + pos.className + "]: " + Std.string(msg));
#end
}
public static function warn(msg:Dynamic, ?pos:haxe.PosInfos) {
writer.writeLn("[WARN][" + pos.className + "]: " + Std.string(msg), Yellow);
#if webconsole
Debug.printWeb("[WARN][" + pos.className + "]: " + Std.string(msg));
#end
}
public static function error(msg:Dynamic, ?pos:haxe.PosInfos) {
writer.writeLn("[ERRO][" + pos.className + "]: " + Std.string(msg), Red);
#if webconsole
Debug.printWeb("[ERRO][" + pos.className + "]: " + Std.string(msg));
#end
}
public static function debug(msg:Dynamic, ?pos:haxe.PosInfos) {
writer.writeLn("[DEBG][" + pos.className + "]: " + Std.string(msg), Gray);
#if webconsole
Debug.printWeb("[DEBG][" + pos.className + "]: " + Std.string(msg));
#end
}
public static function moveToOutput(addr:String) {
WindowManager.instance.focusContextToOutput(context, addr);
}
}

View File

@@ -1,16 +1,17 @@
package kernel; package kernel;
using tink.CoreApi; import lib.ScreenPos;
import kernel.ui.ITermWriteable;
import lib.TermWriteable;
import cc.Term; import cc.Term;
import util.Vec.Vec2; import lib.Vec.Vec2;
import util.Color; import lib.Color;
using tink.CoreApi;
/** /**
Represents the main computer screen. Represents the main computer screen.
**/ **/
class MainTerm implements TermWriteable { class MainTerm implements ITermWriteable {
/** /**
Depends on: KernelEvents, Depends on: KernelEvents,
**/ **/
@@ -25,7 +26,7 @@ class MainTerm implements TermWriteable {
this.onResizeTrigger = Signal.trigger(); this.onResizeTrigger = Signal.trigger();
this.onResize = this.onResizeTrigger.asSignal(); this.onResize = this.onResizeTrigger.asSignal();
KernelEvents.instance.onTermResize.handle(_ -> { KernelEvents.onTermResize.handle(_ -> {
onResizeTrigger.trigger(getSize()); onResizeTrigger.trigger(getSize());
}); });
} }
@@ -38,7 +39,7 @@ class MainTerm implements TermWriteable {
Term.scroll(y); Term.scroll(y);
} }
public function getCursorPos():Vec2<Int> { public function getCursorPos():ScreenPos {
var rtn = Term.getCursorPos(); var rtn = Term.getCursorPos();
return { return {
x: rtn.x - 1, x: rtn.x - 1,
@@ -59,7 +60,7 @@ class MainTerm implements TermWriteable {
Term.setCursorBlink(blink); Term.setCursorBlink(blink);
} }
public function getSize():Vec2<Int> { public function getSize():ScreenPos {
var rtn = Term.getSize(); var rtn = Term.getSize();
return { return {
x: rtn.width, x: rtn.width,
@@ -76,19 +77,19 @@ class MainTerm implements TermWriteable {
} }
public function getTextColor():Color { public function getTextColor():Color {
return ColorConvert.ccToColor(Term.getTextColor()); return Term.getTextColor();
} }
public function setTextColor(colour:Color) { public function setTextColor(color:Color) {
Term.setTextColor(ColorConvert.colorToCC(colour)); Term.setTextColor(color);
} }
public function getBackgroundColor():Color { public function getBackgroundColor():Color {
return ColorConvert.ccToColor(Term.getBackgroundColor()); return Term.getBackgroundColor();
} }
public function setBackgroundColor(color:Color) { public function setBackgroundColor(color:Color) {
Term.setBackgroundColor(ColorConvert.colorToCC(color)); Term.setBackgroundColor(color);
} }
public function isColor():Bool { public function isColor():Bool {

View File

@@ -1,11 +1,11 @@
package kernel; package kernel;
using tink.CoreApi;
import cc.OS; import cc.OS;
using tink.CoreApi;
/** /**
Wrapper class for using timer. Wrapper class for using timers.
**/ **/
class Timer { class Timer {
private final timerID:Int; private final timerID:Int;
@@ -19,7 +19,7 @@ class Timer {
timerID = OS.startTimer(timeout); timerID = OS.startTimer(timeout);
this.callback = callback; this.callback = callback;
timerLink = KernelEvents.instance.onTimer.handle(timerID -> { timerLink = KernelEvents.onTimer.handle(timerID -> {
if (this.timerID == timerID) { if (this.timerID == timerID) {
callback.invoke(null); callback.invoke(null);
timerLink.cancel(); timerLink.cancel();

View File

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

View File

@@ -0,0 +1,45 @@
package kernel.binstore;
import kernel.ps.IProcess;
import macros.Binstore;
class BinStore {
private static final bins:Array<Bin> = populateStore();
private static function populateStore()
return {
var bins:Array<Bin> = [];
Binstore.generateBinStore();
return bins;
}
public static function instantiate(alias:String):Null<IProcess> {
for (bin in bins) {
for (a in bin.aliases) {
if (a == alias) {
return bin.c();
}
}
}
return null;
}
private static function getBinByAlias(alias:String):Null<Bin> {
for (bin in bins) {
for (a in bin.aliases) {
if (a == alias) {
return bin;
}
}
}
return null;
}
public static function getNameByAlias(alias:String):Null<String> {
var bin = getBinByAlias(alias);
if (bin == null) {
return null;
}
return bin.name;
}
}

103
src/kernel/fs/FS.hx Normal file
View File

@@ -0,0 +1,103 @@
package kernel.fs;
import haxe.ds.ReadOnlyArray;
import kernel.fs.FileHandler.WriteBinaryHandle;
import kernel.fs.FileHandler.ReadBinaryHandle;
import kernel.fs.FileHandler.WriteHandle;
import kernel.fs.FileHandler.ReadHandle;
import cc.FileSystem;
using lua.Table;
/**
Wrapper to interact with the filesystem.
**/
class FS {
public static inline function list(path:String):ReadOnlyArray<String> {
return FileSystem.list(path).toArray();
}
public static inline function combine(base:String, part:String):String {
return FileSystem.combine(base, part);
}
public static inline function getName(path:String):String {
return FileSystem.getName(path);
}
public static inline function getDir(path:String):String {
return FileSystem.getDir(path);
}
public static inline function getSize(path:String):Int {
return FileSystem.getSize(path);
}
public static inline function exists(path:String):Bool {
return FileSystem.exists(path);
}
public static inline function isDir(path:String):Bool {
return FileSystem.isDir(path);
}
public static inline function isReadOnly(path:String):Bool {
return FileSystem.isReadOnly(path);
}
public static inline function makeDir(path:String):Void {
FileSystem.makeDir(path);
}
public static inline function move(src:String, dest:String):Void {
FileSystem.move(src, dest);
}
public static inline function copy(src:String, dest:String):Void {
FileSystem.copy(src, dest);
}
public static inline function delete(path:String):Void {
FileSystem.delete(path);
}
public static inline function openRead(path:String):ReadHandle {
return FileSystem.open(path, Read);
}
public static inline function openWrite(path:String):WriteHandle {
return FileSystem.open(path, Write);
}
public static inline function openAppend(path:String):WriteHandle {
return FileSystem.open(path, Append);
}
public static inline function openReadBinary(path:String):ReadBinaryHandle {
return FileSystem.open(path, BinaryRead);
}
public static inline function openWriteBinary(path:String):WriteBinaryHandle {
return FileSystem.open(path, BinaryWrite);
}
public static inline function openAppendBinary(path:String):WriteBinaryHandle {
return FileSystem.open(path, BinaryAppend);
}
public static inline function find(pattern:String):ReadOnlyArray<String> {
return FileSystem.find(pattern).toArray();
}
public static inline function getFreeSpace(path:String):Int {
return FileSystem.getFreeSpace(path);
}
public static inline function getCapacity(path:String):Int {
return FileSystem.getCapacity(path);
}
public static inline function attributes(path:String):FileAttributes {
return FileSystem.attributes(path);
}
}

View File

@@ -0,0 +1,112 @@
package kernel.fs;
import cc.FileSystem.FileHandle;
using tink.CoreApi;
abstract ReadHandle(FileHandle) from FileHandle {
public inline function new(handle:FileHandle) {
this = handle;
}
public inline function readLine(?withTrailing:Bool = false):Null<String> {
return this.readLine(withTrailing);
}
public inline function readAll():Null<String> {
return this.readAll();
}
public inline function read(?count:Int = 1):Null<String> {
return this.read(count);
}
public inline function close():Void {
this.close();
}
}
abstract WriteHandle(FileHandle) from FileHandle {
public inline function new(handle:FileHandle) {
this = handle;
}
public inline function write(data:String):Void {
this.write(data);
}
public inline function writeLine(data:String):Void {
this.writeLine(data);
}
public inline function flush():Void {
this.flush();
}
public inline function close():Void {
this.close();
}
}
abstract ReadBinaryHandle(FileHandle) from FileHandle {
public inline function new(handle:FileHandle) {
this = handle;
}
public inline function readLine(?withTrailing:Bool = false):Null<String> {
return this.readLine(withTrailing);
}
public inline function readAll():Null<String> {
return this.readAll();
}
public inline function read(count:Int):Null<String> {
return this.read(count);
}
public inline function readByte():Null<Int> {
return this.read();
}
public inline function seek(?whence:BinarySeekWhence = Current, ?offset:Int):Void {
this.seek(whence, offset);
}
public inline function close():Void {
this.close();
}
}
abstract WriteBinaryHandle(FileHandle) from FileHandle {
public inline function new(handle:FileHandle) {
this = handle;
}
public inline function write(data:String):Void {
this.write(data);
}
// TODO
public inline function writeByte(data:Int):Void {
// this.write(data);
}
public inline function flush():Void {
this.flush();
}
public inline function close():Void {
this.close();
}
public inline function seek(?whence:BinarySeekWhence = Current, ?offset:Int):Void {
this.seek(whence, offset);
}
}
enum abstract BinarySeekWhence(String) to String {
var Set = "set"; // Relative to the beginning of the file.
var Current = "cur"; // Relative to the current position. This is the default.
var End = "end"; // Relative to the end of the file.
}

244
src/kernel/gps/GPS.hx Normal file
View File

@@ -0,0 +1,244 @@
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.WorldPos;
using tink.CoreApi;
/**
Determines the position of the computer based on the distance to other computers.
When receiving a message from another computer via wireless, the distance is also received.
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:WorldPos;
private static var lastPositionResponse:Array<{pos:WorldPos, dist:Float}> = [];
private static final locatePromise:SingleTimeoutPromise<WorldPos> = new SingleTimeoutPromise(TIMEOUT, brodcastPositionRequest);
@:allow(kernel.Init)
private static function init() {
loadCachedPosition();
}
public static function setManualPosition(pos:WorldPos) {
var kvstore = new KVStore("gps");
kvstore.set("mpos", pos);
kvstore.save();
if (cachedPosition == null) {
cachedPosition = pos;
posAccuracy = 2;
}
}
@:allow(kernel.gps.INS)
private static function setINSPosition(pos:WorldPos) {
cachedPosition = pos;
posAccuracy = 1;
}
public static function getPosition():Null<WorldPos> {
return cachedPosition;
}
public static function getAccuracy():Int {
return posAccuracy;
}
public static function invalidatePosition() {
cachedPosition = null;
posAccuracy = 0;
}
public static function locate():Promise<WorldPos> {
return locatePromise.request();
}
private static function persistCachedPositon() {
if (cachedPosition == null)
return;
var kvstore = new KVStore("gps");
kvstore.set("cpos", cachedPosition);
kvstore.save();
}
private static function loadCachedPosition() {
var kvstore = new KVStore("gps");
kvstore.load();
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
cachedPosition = mPos;
posAccuracy = 3;
return;
}
if (mPos != null && cPos != null && mPos != cPos) {
// Both are different, so we can use the manual position
cachedPosition = mPos;
posAccuracy = 1;
return;
}
if (mPos == null && cPos != null) {
// No manual position set, so we can use the cached position
cachedPosition = cPos;
posAccuracy = 2;
return;
}
if (mPos != null && cPos == null) {
// No cached position, so we can use the manual position
cachedPosition = mPos;
posAccuracy = 2;
return;
}
}
private static function brodcastPositionRequest() {
Net.brodcastGPSRequest();
}
@:allow(kernel.net.Net)
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)
return;
if (posAccuracy < 2)
return;
if (cachedPosition == null)
return;
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(x, y, z):
if (lastPositionResponse.contains({pos: {x: x, y: y, z: z}, dist: dist}))
return; // Ignore duplicate responses
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
if (lastPositionResponse.length < 4)
return; // We need at least 3 responses to calculate the position
var calculatedPosition = calculatePosition();
if (calculatedPosition != null) {
calculatedPosition = calculatedPosition.round();
}
lastPositionResponse = []; // Reset the response array
if (calculatedPosition == null)
return;
cachedPosition = calculatedPosition;
posAccuracy = 3;
locatePromise.resolve(calculatedPosition);
default:
}
}
private static function calculatePosition():Null<WorldPos> {
if (lastPositionResponse.length < 3)
return null;
// do a simple trilateration with the last 3 responses for now
var p1 = lastPositionResponse[0].pos;
var p2 = lastPositionResponse[1].pos;
var p3 = lastPositionResponse[2].pos;
var r1 = lastPositionResponse[0].dist;
var r2 = lastPositionResponse[1].dist;
var r3 = lastPositionResponse[2].dist;
var result = trilateration(p1, p2, p3, r1, r2, r3);
if (result.a.close(result.b))
return result.a;
// If we have more than 3 responses, we can use the 4th response to determine which of the two positions is correct
// TODO: if this is a pocket computer we cant use this since it can move freely
if (lastPositionResponse.length > 3) {
var err1 = Math.abs(result.a.distance(lastPositionResponse[3].pos) - lastPositionResponse[3].dist);
var err2 = Math.abs(result.b.distance(lastPositionResponse[3].pos) - lastPositionResponse[3].dist);
if (Math.abs(err1 - err2) < 0.0001) {
Log.warn("Failed to determine GPS position via 4th fix");
return null; // The two positions are essentially the same, so we cant determine which one is correct
}
if (err1 < err2)
return result.a;
if (err2 < err1)
return result.b;
}
// last resort, just use the position that is closest to a whole number (whithin a resonable margin of error)
if (!shouldDoWholeNumberCheck)
return null;
// TODO: mark the position as not so accurate if we use this method
var err1 = (result.a - result.a.round()).length();
var err2 = (result.b - result.b.round()).length();
if (err1 < err2 && err1 < 0.0001) {
return result.a;
} else if (err2 < 0.0001) {
return result.b;
}
return null;
}
/**
Determines the position(s) of a point given 3 other points and the distance to each of them.
**/
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;
var d = a2b.length();
var ex = a2b.normalize();
var i = ex.dot(a2c);
var ey = (a2c - ex * i).normalize();
var j = ey.dot(a2c);
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);
var result = p1 + ex * x + ey * y;
var zSquared = r1 * r1 - x * x - y * y;
if (zSquared > 0) {
var z = Math.sqrt(zSquared);
return new Pair(result + (ez * z), result - (ez * z));
}
return new Pair(result, result);
}
}

197
src/kernel/gps/INS.hx Normal file
View File

@@ -0,0 +1,197 @@
package kernel.gps;
import lib.SinglePromise;
import kernel.turtle.Turtle;
import lib.WorldPos;
using tink.CoreApi;
class INS {
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() {
if (heading == null) {
alingment = 0;
return;
}
move(heading);
}
@:allow(kernel.turtle.Turtle)
private static function moveBackward() {
if (heading == null) {
alingment = 0;
return;
}
move(heading.negate());
}
@:allow(kernel.turtle.Turtle)
private static function moveUp() {
move({x: 0, y: 1, z: 0});
}
@:allow(kernel.turtle.Turtle)
private static function moveDown() {
move({x: 0, y: -1, z: 0});
}
@:allow(kernel.turtle.Turtle)
private static function turnLeft() {
if (heading == null)
return;
if (heading.x == 0 && heading.z == -1) {
heading = {x: -1, y: 0, z: 0};
} else if (heading.x == -1 && heading.z == 0) {
heading = {x: 0, y: 0, z: 1};
} else if (heading.x == 0 && heading.z == 1) {
heading = {x: 1, y: 0, z: 0};
} else if (heading.x == 1 && heading.z == 0) {
heading = {x: 0, y: 0, z: -1};
}
}
@:allow(kernel.turtle.Turtle)
private static function turnRight() {
if (heading == null)
return;
if (heading.x == 0 && heading.z == -1) {
heading = {x: 1, y: 0, z: 0};
} else if (heading.x == -1 && heading.z == 0) {
heading = {x: 0, y: 0, z: -1};
} else if (heading.x == 0 && heading.z == 1) {
heading = {x: -1, y: 0, z: 0};
} else if (heading.x == 1 && heading.z == 0) {
heading = {x: 0, y: 0, z: 1};
}
}
private static function move(dir:Null<WorldPos>) {
if (alignPromise.isRunning()) {
return;
}
var pos = GPS.getPosition();
if (pos == null || dir == null)
return;
var newPos = pos + dir;
GPS.setINSPosition(newPos);
}
public static function getHeading():Null<WorldPos> {
return heading;
}
public static function align():Promise<Noise> {
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);
}
});
}
});
}
// -1 = not moved, 0 = back, 1 = forward, 2 = left, 3 = right
private static function tryMoving():Int {
if (Turtle.back().isSuccess()) {
return 0;
} else if (Turtle.forward().isSuccess()) {
return 1;
} else {
Turtle.turnLeft(); // TODO: Check if successfull
if (Turtle.forward().isSuccess()) {
return 2;
} else if (Turtle.back().isSuccess()) {
return 3;
} else {
// Can't move
return -1;
}
}
}
private static function calcHeading(pos1:WorldPos, pos2:WorldPos, moved:Int):Null<WorldPos> {
if (moved == 0) {
return pos1 - pos2;
} else if (moved == 1) {
return pos2 - pos1;
} else if (moved == 2) {
return rotatePos3ToRight(pos2 - pos1);
} else if (moved == 3) {
return rotatePos3ToLeft(pos2 - pos1);
} else {
return null;
}
}
private static function moveBack(moved:Int) {
if (moved == 0) {
Turtle.forward();
} else if (moved == 1) {
Turtle.back();
} else if (moved == 2) {
Turtle.back();
Turtle.turnRight();
} else if (moved == 3) {
Turtle.forward();
Turtle.turnRight();
}
}
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) {
return {x: 0, y: 0, z: -1};
} else if (pos.x == 0 && pos.z == 1) {
return {x: -1, y: 0, z: 0};
} else if (pos.x == 1 && pos.z == 0) {
return {x: 0, y: 0, z: 1};
} else {
return pos;
}
}
private static function rotatePos3ToLeft(pos3:WorldPos):WorldPos {
return rotatePos3ToRight(rotatePos3ToRight(rotatePos3ToRight(pos3)));
}
}

View File

@@ -0,0 +1,21 @@
package kernel.http;
using lua.Table;
class HTTPFailure {
public final reason:String;
public final statusCode:Null<StatusCode>;
public final headers:Map<String, String>;
public final body:String;
@:allow(kernel.http)
private function new(failReason:String, ?handle:cc.HTTP.HTTPResponse) {
this.reason = failReason;
if (handle != null) {
this.statusCode = handle.getResponseCode();
this.headers = handle.getResponseHeaders().toMap();
this.body = handle.readAll();
handle.close();
}
}
}

View File

@@ -0,0 +1,28 @@
package kernel.http;
using tink.CoreApi;
/**
Wrapper for the native HTTP request function.
**/
class Http {
public static function request(url:String, ?body:String, ?options:String):Future<Outcome<HTTPResponse, HTTPFailure>> {
return new Future<Outcome<HTTPResponse, HTTPFailure>>((resolve) -> {
KernelEvents.onHttpFailure.handle((params) -> {
if (params.url == url) {
resolve(Failure(new HTTPFailure(params.failReason, params.handle)));
}
});
KernelEvents.onHttpSuccess.handle((params) -> {
if (params.url == url) {
resolve(Success(new HTTPResponse(params.handle)));
}
});
cc.HTTP.request(url, body);
return null;
});
}
}

View File

@@ -0,0 +1,16 @@
package kernel.http;
using lua.Table;
class HTTPResponse {
public final statusCode:StatusCode;
public final headers:Map<String, String>;
public final body:String;
@:allow(kernel.http)
private function new(handle:cc.HTTP.HTTPResponse) {
this.statusCode = handle.getResponseCode();
this.headers = handle.getResponseHeaders().toMap();
this.body = handle.readAll();
}
}

View File

@@ -0,0 +1,65 @@
package kernel.http;
enum abstract StatusCode(Int) from Int {
var Continue = 100;
var SwitchingProtocols = 101;
var Processing = 102;
var OK = 200;
var Created = 201;
var Accepted = 202;
var NonAuthoritativeInformation = 203;
var NoContent = 204;
var ResetContent = 205;
var PartialContent = 206;
var MultiStatus = 207;
var AlreadyReported = 208;
var IMUsed = 226;
var MultipleChoices = 300;
var MovedPermanently = 301;
var Found = 302;
var SeeOther = 303;
var NotModified = 304;
var UseProxy = 305;
var SwitchProxy = 306;
var TemporaryRedirect = 307;
var PermanentRedirect = 308;
var BadRequest = 400;
var Unauthorized = 401;
var PaymentRequired = 402;
var Forbidden = 403;
var NotFound = 404;
var MethodNotAllowed = 405;
var NotAcceptable = 406;
var ProxyAuthenticationRequired = 407;
var RequestTimeout = 408;
var Conflict = 409;
var Gone = 410;
var LengthRequired = 411;
var PreconditionFailed = 412;
var PayloadTooLarge = 413;
var URITooLong = 414;
var UnsupportedMediaType = 415;
var RangeNotSatisfiable = 416;
var ExpectationFailed = 417;
var ImATeapot = 418;
var MisdirectedRequest = 421;
var UnprocessableEntity = 422;
var Locked = 423;
var FailedDependency = 424;
var UpgradeRequired = 426;
var PreconditionRequired = 428;
var TooManyRequests = 429;
var RequestHeaderFieldsTooLarge = 431;
var UnavailableForLegalReasons = 451;
var InternalServerError = 500;
var NotImplemented = 501;
var BadGateway = 502;
var ServiceUnavailable = 503;
var GatewayTimeout = 504;
var HTTPVersionNotSupported = 505;
var VariantAlsoNegotiates = 506;
var InsufficientStorage = 507;
var LoopDetected = 508;
var NotExtended = 510;
var NetworkAuthenticationRequired = 511;
}

133
src/kernel/log/Log.hx Normal file
View File

@@ -0,0 +1,133 @@
package kernel.log;
import haxe.ds.ReadOnlyArray;
import haxe.macro.Context;
#if (webconsole && !macro)
import lib.Debug;
#end
using tink.CoreApi;
/**
Central logging system.
**/
class Log {
private static inline final MAX_LINES:Int = 100;
public static var onLog(default, null):Signal<LogLine>;
private static final onLogTrigger:SignalTrigger<LogLine> = new SignalTrigger();
private static final logLines:Array<LogLine> = [];
@:allow(kernel.Init)
private static function init() {
onLog = onLogTrigger.asSignal();
#if debug
haxe.Log.trace = function(v:Dynamic, ?infos:haxe.PosInfos) {
Log._debug(v, infos.className);
}
#end
}
public static function _info(msg:Dynamic, ?origin:String) {
log({
level: Info,
message: Std.string(msg),
time: 0,
origin: origin
});
}
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) {
logLines.shift();
}
onLogTrigger.trigger(line);
#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

@@ -0,0 +1,9 @@
package kernel.log;
enum LogLevel {
Info;
Warn;
Error;
Debug;
Silly;
}

View File

@@ -0,0 +1,8 @@
package kernel.log;
typedef LogLine = {
level:LogLevel,
message:String,
time:Int,
?origin:String,
}

View File

@@ -1,5 +1,7 @@
package kernel.net; package kernel.net;
import kernel.net.Package.GenericPackage;
using tink.CoreApi; using tink.CoreApi;
/** /**
@@ -13,5 +15,5 @@ interface INetworkInterface {
public function send(chan:Int, replyChan:Int, payload:Any):Void; public function send(chan:Int, replyChan:Int, payload:Any):Void;
public function name():String; public function name():String;
public function getBaseRoutingCost():Int; public function getBaseRoutingCost():Int;
public var onMessage (default, null): Signal<Package>; public var onMessage(default, null):Signal<{pack:GenericPackage, ?dist:Float}>;
} }

View File

@@ -1,16 +1,19 @@
package kernel.net; package kernel.net;
import kernel.net.Package.GenericPackage;
import kernel.log.Log;
using tink.CoreApi; using tink.CoreApi;
/** /**
Virtual network interface that handle Virtual network interface that handles packages to the same id as the sender.
**/ **/
class Loopback implements INetworkInterface { class Loopback implements INetworkInterface {
public static final instance:Loopback = new Loopback(); public static final instance:Loopback = new Loopback();
public var onMessage(default, null):Signal<Package>; public var onMessage(default, null):Signal<{pack:GenericPackage, dist:Null<Float>}>;
private final onMessageTrigger: SignalTrigger<Package> = Signal.trigger(); private final onMessageTrigger:SignalTrigger<{pack:GenericPackage, dist:Null<Float>}> = Signal.trigger();
private var openChans:Array<Int> = []; private var openChans:Array<Int> = [];
private function new() { private function new() {
@@ -18,11 +21,13 @@ class Loopback implements INetworkInterface {
} }
public function listen(chan:Int) { public function listen(chan:Int) {
// TODO if (!this.openChans.contains(chan)) {
this.openChans.push(chan);
}
} }
public function close(chan:Int) { public function close(chan:Int) {
// TODO this.openChans.remove(chan);
} }
public function isListening(chan:Int):Bool { public function isListening(chan:Int):Bool {
@@ -34,7 +39,11 @@ class Loopback implements INetworkInterface {
} }
public function send(chan:Int, replyChan:Int, payload:Any) { public function send(chan:Int, replyChan:Int, payload:Any) {
this.onMessageTrigger.trigger(payload); if (this.openChans.contains(chan)) {
this.onMessageTrigger.trigger({pack: payload, dist: null});
} else {
Log.silly("Loopback got package on non open channel");
}
} }
public function name():String { public function name():String {

View File

@@ -1,15 +1,16 @@
package kernel.net; package kernel.net;
using tink.CoreApi; import kernel.net.Package.GenericPackage;
import kernel.gps.GPS;
import haxe.ds.ReadOnlyArray;
import kernel.net.Package.NetworkID; import kernel.net.Package.NetworkID;
import kernel.peripherals.Peripherals.Peripheral; import kernel.peripherals.Peripherals.Peripheral;
import kernel.Log; import kernel.log.Log;
import kernel.Timer; import kernel.Timer;
import cc.OS; import cc.OS;
using tink.CoreApi;
using Lambda; using Lambda;
using util.Extender.LambdaExtender;
/** /**
Class responsible for everything network related. Class responsible for everything network related.
@@ -19,28 +20,82 @@ class Net {
/** /**
Depends on: KernelEvents Depends on: KernelEvents
**/ **/
public static var instance:Net;
public static inline final BRODCAST_PORT:Int = 65533; public static inline final BRODCAST_PORT:Int = 65533;
public static inline final MESSAGE_TIMEOUT:Int = 3; public static inline final MESSAGE_TIMEOUT:Int = 3;
public static inline final DEFAULT_TTL:Int = 10; public static inline final DEFAULT_TTL:Int = 10;
public final networkID:NetworkID = OS.getComputerID(); public static final networkID:NetworkID = OS.getComputerID();
private final responseBus:Map<Int, Callback<Package>> = new Map(); private static final responseBus:Map<Int, Callback<Outcome<GenericPackage, Error>>> = new Map();
private final protoHandlers:Map<String, Callback<Package>> = new Map(); private static final protoHandlers:Map<String, Callback<GenericPackage>> = new Map();
private var interfaces:Array<INetworkInterface>; private static var interfaces:Array<INetworkInterface>;
@:allow(kernel.Init) @:allow(kernel.Init)
private function new() { private static function init() {
this.interfaces = [for (e in Peripheral.instance.getModems()) e ]; // TODO: is this the way to do it? interfaces = [for (e in Peripheral.getAllModems()) e]; // TODO: is this the way to do it?
this.interfaces.push(Loopback.instance); interfaces.push(Loopback.instance);
for (interf in interfaces) { for (interf in interfaces) {
setupInterf(interf); 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;
} }
private function setupInterf(interf: INetworkInterface) { Log.debug('Removed modem on addr: $addr');
interf.onMessage.handle(pack -> handle(pack,interf));
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();
}
private static function setupPingHandle() {
registerProto("icmp", pack -> {
switch pack.data.type {
case "ping":
respondTo(pack, "pong");
case "died":
// If we get a "died" message from a node when one of our packages ttl hits 0
// the `data.msgId` prop is the message id
var msgID:Int = pack.data.msgID;
if (responseBus.exists(msgID)) {
responseBus[msgID].invoke(Outcome.Failure(new Error("TTL reached 0")));
}
default:
Log.silly('Unknown icmp message: ${pack.data}');
}
});
}
/**
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); interf.listen(networkID);
interf.listen(BRODCAST_PORT); interf.listen(BRODCAST_PORT);
} }
@@ -48,8 +103,8 @@ class Net {
/** /**
Called when a new package comes in. Called when a new package comes in.
**/ **/
private function handle(pack:Package,interf: INetworkInterface) { private static function handle(pack:GenericPackage, interf:INetworkInterface, ?dist:Float) {
if (pack.toID == this.networkID || pack.toID == Net.BRODCAST_PORT){ if (pack.toID == networkID || pack.toID == Net.BRODCAST_PORT) {
switch pack.type { switch pack.type {
case Data(_) | DataNoResponse(_): case Data(_) | DataNoResponse(_):
// Let a local proccess handle it // Let a local proccess handle it
@@ -57,11 +112,18 @@ class Net {
case Response: case Response:
// Got a response to a send message. Invoke the callback // Got a response to a send message. Invoke the callback
if (responseBus.exists(pack.msgID)) { if (responseBus.exists(pack.msgID)) {
responseBus[pack.msgID].invoke(pack); responseBus[pack.msgID].invoke(Outcome.Success(pack));
} }
case RouteDiscover(_) | RouteDiscoverResponse(_) | RouteDiscoverUpdate(_): case RouteDiscover(_) | RouteDiscoverResponse(_) | RouteDiscoverUpdate(_):
// Delegate to Routing // Delegate to Routing
Routing.instance.handleRoutePackage(pack,interf); Routing.handleRoutePackage(cast pack, interf);
case GPSRequest | GPSResponse(_):
if (dist == null) {
Log.silly("Got a GPS package but no distance was provided");
return;
}
// Delegate to GPS
GPS.handlePackage(cast pack, dist, interf);
} }
} else { } else {
// New message received but its not ment for us. Forward if possible. // New message received but its not ment for us. Forward if possible.
@@ -69,15 +131,15 @@ class Net {
} }
} }
private function generateMessageID():Int { private static function generateMessageID():Int {
return Std.random(2147483647); // TODO: better uniqe number return Std.random(2147483647); // TODO: better uniqe number
} }
/** /**
Send a message. Dont care if its reaches its destination nor it has a response. Send a message. Dont care if its reaches its destination nor it has a response.
**/ **/
public function sendAndForget(dest:Int, proto:String, data:Dynamic) { public static function sendAndForget(dest:Int, proto:String, data:Dynamic) {
var pack:Package = { var pack:GenericPackage = {
toID: dest, toID: dest,
fromID: networkID, fromID: networkID,
msgID: generateMessageID(), msgID: generateMessageID(),
@@ -89,8 +151,14 @@ class Net {
sendRaw(pack); sendRaw(pack);
} }
private function forwardPackage(pack: Package) { private static function forwardPackage(pack:GenericPackage) {
if (pack.ttl == 0) { if (pack.ttl == 0) {
if (pack.type.match(Data(_))) {
// If the package is a data package and the ttl hits 0
// we send a "died" message to the sender
sendAndForget(pack.fromID, "icmp", {type: "died", msgID: pack.msgID});
}
// Drop package // Drop package
return; return;
} }
@@ -99,12 +167,13 @@ class Net {
if (!sendRaw(pack)) { if (!sendRaw(pack)) {
// Cant forward // Cant forward
Log.silly('Received a message that could not be forwarded to ${pack.toID}');
} }
} }
public function respondTo(pack:Package, data:Dynamic) { public static function respondTo(pack:GenericPackage, data:Dynamic) {
if (pack.type.match(DataNoResponse(_))) { if (!pack.type.match(Data(_))) {
Log.warn("Responed to a no response package. Ignoring"); Log.warn("Responding to a package that does require responding. Ignoring.");
return; return;
} }
@@ -116,7 +185,7 @@ class Net {
/** /**
Send to package to the localy register handler based on the proto Send to package to the localy register handler based on the proto
**/ **/
private function routeToProto(pack:Package) { private static function routeToProto(pack:GenericPackage) {
var proto = switch pack.type { var proto = switch pack.type {
case Data(proto): case Data(proto):
proto; proto;
@@ -127,7 +196,7 @@ class Net {
} }
if (!protoHandlers.exists(proto)) { if (!protoHandlers.exists(proto)) {
Log.warn("Trying to route package to proto: \"" + proto + "\" but nothing was register"); Log.warn('Trying to route package to proto: $proto but nothing was register');
return; return;
} }
@@ -138,22 +207,22 @@ class Net {
Just send the package to the right modem. Just send the package to the right modem.
Returns true if message was send Returns true if message was send
**/ **/
private function sendRaw(pack:Package): Bool { private static function sendRaw(pack:GenericPackage):Bool {
var route = Routing.instance.getRouteToID(pack.toID); var route = Routing.getRouteToID(pack.toID);
if (route == null) { if (route == null) {
return false; return false;
} }
route.interf.send(route.rep,this.networkID,pack); route.interf.send(route.rep, networkID, pack);
return true; return true;
} }
/** /**
Send a message and wait for a response. Send a message and wait for a response.
**/ **/
public function sendAndAwait(dest:NetworkID, proto:String, data:Dynamic):Promise<Package> { public static function sendAndAwait<T>(dest:NetworkID, proto:String, data:T):Promise<GenericPackage> {
return new Promise<Package>((resolve, reject) -> { return new Promise<GenericPackage>((resolve, reject) -> {
var pack:Package = { var pack:GenericPackage = {
toID: dest, toID: dest,
fromID: networkID, fromID: networkID,
msgID: generateMessageID(), msgID: generateMessageID(),
@@ -164,11 +233,20 @@ class Net {
var timeout:Timer = null; var timeout:Timer = null;
responseBus[pack.msgID] = ((reponse:Package) -> { responseBus[pack.msgID] = ((reponse:Outcome<GenericPackage, Error>) -> {
resolve(reponse); switch reponse {
case Success(pack):
resolve(pack);
case Failure(err):
reject(err);
}
// Always remove the timeout
if (timeout != null) { if (timeout != null) {
timeout.cancle(); timeout.cancle();
} }
responseBus.remove(pack.msgID);
}); });
timeout = new Timer(MESSAGE_TIMEOUT, () -> { timeout = new Timer(MESSAGE_TIMEOUT, () -> {
@@ -185,7 +263,7 @@ class Net {
}); });
} }
public function registerProto(proto:String, callback:Callback<Package>) { public static function registerProto(proto:String, callback:Callback<GenericPackage>) {
if (protoHandlers.exists(proto)) { if (protoHandlers.exists(proto)) {
// Failed. Handler already exist. // Failed. Handler already exist.
// TODO: return error // TODO: return error
@@ -195,7 +273,52 @@ class Net {
protoHandlers[proto] = callback; protoHandlers[proto] = callback;
} }
public function removeProto(proto:String) { public static function removeProto(proto:String) {
protoHandlers.remove(proto); protoHandlers.remove(proto);
} }
/**
Sends a ping package to the given id. Returns true if there was a response.
**/
public static function ping(toID:NetworkID):Promise<Noise> {
return new Promise<Noise>((resolve, reject) -> {
sendAndAwait(toID, "icmp", {type: "ping"}).handle(pack -> {
switch pack {
case Success(_):
resolve(Noise);
case Failure(err):
reject(err);
}
});
return null;
});
}
public static function getActiveProtocols():ReadOnlyArray<String> {
var arr = new Array<String>();
for (proto in protoHandlers.keys()) {
arr.push(proto);
}
return arr;
}
@:allow(kernel.gps.GPS)
private static function brodcastGPSRequest() {
var pack:Package<Noise> = {
fromID: networkID,
toID: Net.BRODCAST_PORT,
ttl: 0, // Prevent forwarding
msgID: generateMessageID(),
type: GPSRequest,
data: null,
};
for (modem in Peripheral.getAllModems()) {
if (!modem.isWireless())
continue;
modem.send(Net.BRODCAST_PORT, networkID, pack);
}
}
} }

View File

@@ -1,6 +1,7 @@
package kernel.net; package kernel.net;
typedef NetworkID = Int; typedef NetworkID = Int;
typedef GenericPackage = Package<Dynamic>;
enum PackageTypes { enum PackageTypes {
Data(proto:String); Data(proto:String);
@@ -9,39 +10,34 @@ enum PackageTypes {
RouteDiscover(reachableIDs:Array<{id:NetworkID, cost:Int}>); RouteDiscover(reachableIDs:Array<{id:NetworkID, cost:Int}>);
RouteDiscoverResponse(reachableIDs:Array<{id:NetworkID, cost:Int}>); RouteDiscoverResponse(reachableIDs:Array<{id:NetworkID, cost:Int}>);
RouteDiscoverUpdate(reachableIDs:Array<{id:NetworkID, cost:Int}>); RouteDiscoverUpdate(reachableIDs:Array<{id:NetworkID, cost:Int}>);
GPSResponse(x:Float, y:Float, z:Float);
GPSRequest();
} }
/** /**
Representing a network package. Representing a network package.
**/ **/
@:structInit class Package { @:structInit class Package<T> {
public final fromID:NetworkID; public final fromID:NetworkID;
public final toID:NetworkID; public final toID:NetworkID;
public final msgID:Int; public final msgID:Int;
public final type:PackageTypes; public final type:PackageTypes;
public final data:Dynamic; public final data:T;
public var ttl:Int; public var ttl:Int;
/** public function new(fromID:NetworkID, toID:NetworkID, msgID:Int, type:PackageTypes, data:T, ttl:Int) {
Parse package from an `modem_message` event. this.fromID = fromID;
**/ this.toID = toID;
public static function fromEvent(params:Array<Dynamic>):Package { this.msgID = msgID;
var payload = params[4]; this.type = type;
this.data = data;
return { this.ttl = ttl;
fromID: params[3],
toID: params[2],
msgID: payload.msgID,
type: payload.type,
data: payload.data,
ttl: payload.ttl,
};
} }
/** /**
Create package that can be used as a response. Create package that can be used as a response.
**/ **/
public function createResponse(newData:Dynamic):Package { public function createResponse<T2>(newData:T2):Package<T2> {
return { return {
toID: fromID, toID: fromID,
fromID: toID, fromID: toID,
@@ -53,9 +49,9 @@ enum PackageTypes {
} }
/** /**
Wrapper for `Net.instance.respondTo`. Wrapper for `Net.respondTo`.
**/ **/
public function respond(data:Dynamic) { public function respond(data:Dynamic) {
Net.instance.respondTo(this, data); Net.respondTo(this, data);
} }
} }

View File

@@ -1,12 +1,12 @@
package kernel.net; package kernel.net;
import kernel.log.Log;
import kernel.peripherals.Peripherals.Peripheral; import kernel.peripherals.Peripherals.Peripheral;
import kernel.net.INetworkInterface; import kernel.net.INetworkInterface;
import kernel.net.Package.NetworkID;
using tink.CoreApi; using tink.CoreApi;
import kernel.net.Package.NetworkID;
@:structInit typedef Route = { @:structInit typedef Route = {
interf:INetworkInterface, interf:INetworkInterface,
rep:NetworkID, rep:NetworkID,
@@ -20,105 +20,99 @@ class Routing {
/** /**
Depends on: Peripheral Depends on: Peripheral
**/ **/
public static var instance:Routing; public static inline final UPDATE_WAIT_TIME:Float = 1;
public static inline final UPDATE_WAIT_TIME:Float = 3; public static var onNewNeigbor(default, null):Signal<Int>;
public final onNewNeigbor:Signal<Int>; private static final onNewNeigborTrigger:SignalTrigger<NetworkID> = Signal.trigger();
private static final routingTable:Map<NetworkID, Route> = new Map();
private final onNewNeigborTrigger:SignalTrigger<NetworkID> = Signal.trigger(); private static var routeUpdateInQueue:Bool = false;
private final routingTable:Map<NetworkID, Route> = new Map();
private var routeUpdateInQueue: Bool = false;
@:allow(kernel.Init) @:allow(kernel.Init)
private function new() { private static function init() {
this.onNewNeigbor = this.onNewNeigborTrigger.asSignal(); onNewNeigbor = onNewNeigborTrigger.asSignal();
} }
@:allow(kernel.Init) @:allow(kernel.net.Net)
private function init() { private static function setup() {
routingTable.set(Net.instance.networkID,{interf: Loopback.instance,cost:0,rep:Net.instance.networkID}); routingTable.set(Net.networkID, {interf: Loopback.instance, cost: 0, rep: Net.networkID});
for (modem in Peripheral.instance.getModems()) { brodcastRoutingTable();
modem.send(Net.BRODCAST_PORT, Net.instance.networkID, newRoutDiscoverPackage());
}
} }
private function prepareRouteUpdate() { /**
if (this.routeUpdateInQueue){ Prepares an brodcast of the current routing table. If a brodcast is already in queue it will be ignored.
After UPDATE_WAIT_TIME seconds the routing table will be brodcasted. This is done to prevent spamming the network.
**/
private static function prepareRouteUpdate() {
if (routeUpdateInQueue) {
return; return;
} }
this.routeUpdateInQueue = true; routeUpdateInQueue = true;
new Timer(UPDATE_WAIT_TIME, () -> { new Timer(UPDATE_WAIT_TIME, () -> {
brodcastUpdatedRoutes(); brodcastRoutingTable();
this.routeUpdateInQueue = false; routeUpdateInQueue = false;
}); });
} }
private function brodcastUpdatedRoutes() { /**
var pack: Package = { Brodcast the current routing table to all peers.
type: RouteDiscoverUpdate(genRouteList()), **/
toID: Net.BRODCAST_PORT, private static function brodcastRoutingTable() {
msgID: null, var pack = newRoutDiscoverPackage();
fromID: Net.instance.networkID,
data: null,
ttl: 0, // Prevent forwarding
};
for (modem in Peripheral.instance.getModems()) { for (modem in Peripheral.getAllModems()) {
modem.send(Net.BRODCAST_PORT, Net.networkID, pack);
modem.send(Net.BRODCAST_PORT, Net.instance.networkID,pack);
} }
} }
/** /**
Handle incomming packages that involve route discovery. Handle incomming packages that involve route discovery.
**/ **/
public function handleRoutePackage(pack:Package, interf:INetworkInterface):Void { @:allow(kernel.net.Net)
private static function handleRoutePackage(pack:Package<Noise>, interf:INetworkInterface):Void {
addPossibleRoute(pack.fromID, interf, 0, pack.fromID); addPossibleRoute(pack.fromID, interf, 0, pack.fromID);
var shouldRespond: Bool = switch pack.type { switch pack.type {
case RouteDiscoverResponse(routes): case RouteDiscoverResponse(routes):
// A request for routes has been answerd
for (route in routes) { for (route in routes) {
addPossibleRoute(route.id, interf, route.cost, pack.fromID); addPossibleRoute(route.id, interf, route.cost, pack.fromID);
} }
false;
case RouteDiscover(routes): case RouteDiscover(routes):
// Received a request for available routes
for (route in routes) { for (route in routes) {
addPossibleRoute(route.id, interf, route.cost, pack.fromID); addPossibleRoute(route.id, interf, route.cost, pack.fromID);
} }
true;
case RouteDiscoverUpdate(routes):
for (route in routes) {
addPossibleRoute(route.id,interf,route.cost,pack.fromID);
}
false;
default:
Log.error("Expected RouteDiscover or RouteDiscoverResponse type");
false;
};
if (!shouldRespond){
return;
}
// Respond to peer // Respond to peer
var response:Package = { var response:Package<Noise> = {
toID: pack.fromID, toID: pack.fromID,
fromID: Net.instance.networkID, fromID: Net.networkID,
msgID: null, msgID: null,
type: RouteDiscoverResponse(genRouteList()), type: RouteDiscoverResponse(genRouteList()),
data: null, data: null,
ttl: 0, // Prevent forwarding ttl: 0, // Prevent forwarding
} }
interf.send(response.toID,Net.instance.networkID,response); 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);
}
default:
Log.silly("Expected package to be a Route discover package");
};
} }
private function genRouteList(): Array<{id:NetworkID,cost:Int}> { /**
Generate a list of routes to send to a peer based on the current routing table.
**/
private static function genRouteList():Array<{id:NetworkID, cost:Int}> {
var routes:Array<{id:NetworkID, cost:Int}> = []; var routes:Array<{id:NetworkID, cost:Int}> = [];
for (k => v in this.routingTable){ for (k => v in routingTable) {
routes.push({ routes.push({
id: k, id: k,
cost: v.cost cost: v.cost
@@ -127,12 +121,15 @@ class Routing {
return routes; return routes;
} }
private function newRoutDiscoverPackage():Package { /**
var pack:Package = { Create a new packate to brodcast our routes.
**/
private static function newRoutDiscoverPackage():Package<Noise> {
var pack:Package<Noise> = {
type: RouteDiscover(genRouteList()), type: RouteDiscover(genRouteList()),
toID: Net.BRODCAST_PORT, toID: Net.BRODCAST_PORT,
msgID: null, msgID: null,
fromID: Net.instance.networkID, fromID: Net.networkID,
data: null, data: null,
ttl: 0, // Prevent forwarding ttl: 0, // Prevent forwarding
} }
@@ -144,33 +141,31 @@ class Routing {
Called when a route to a client has been disoverd. Called when a route to a client has been disoverd.
Its possible to be called multiple times with the same id but different addr. Its possible to be called multiple times with the same id but different addr.
**/ **/
private function addPossibleRoute(toID:Int, interf:INetworkInterface, cost:Int, rep: NetworkID) { private static function addPossibleRoute(toID:Int, interf:INetworkInterface, cost:Int, rep:NetworkID) {
if (toID == Net.instance.networkID){ if (toID == Net.networkID) {
return; return;
} }
var fullCost = cost + interf.getBaseRoutingCost(); var fullCost = cost + interf.getBaseRoutingCost();
if (this.routingTable.exists(toID)){ if (routingTable.exists(toID)) {
if (this.routingTable[toID].cost > fullCost){ if (routingTable[toID].cost > fullCost) {
Log.info("Better route: " + toID + " -> " + interf.name() + ":$"+fullCost); routingTable[toID] = {interf: interf, cost: cost + interf.getBaseRoutingCost(), rep: rep};
this.routingTable[toID] = {interf:interf,cost:cost + interf.getBaseRoutingCost(),rep: rep}; prepareRouteUpdate();
this.prepareRouteUpdate();
} }
} else { } else {
this.routingTable[toID] = {interf:interf,cost:cost + interf.getBaseRoutingCost(),rep: rep}; routingTable[toID] = {interf: interf, cost: cost + interf.getBaseRoutingCost(), rep: rep};
Log.info("New route: " + toID + " -> " + interf.name() + ":$"+fullCost); onNewNeigborTrigger.trigger(toID);
this.onNewNeigborTrigger.trigger(toID); prepareRouteUpdate();
this.prepareRouteUpdate();
} }
} }
public function getRouteToID(networkID:NetworkID):{interf: INetworkInterface, rep: NetworkID} { public static function getRouteToID(networkID:NetworkID):{interf:INetworkInterface, rep:NetworkID} {
if (networkID == Net.instance.networkID) { if (networkID == Net.networkID) {
return {interf: Loopback.instance,rep: Net.instance.networkID}; return {interf: Loopback.instance, rep: Net.networkID};
} }
var route = this.routingTable[networkID]; var route:Null<Route> = routingTable[networkID];
if (route == null) { if (route == null) {
return null; return null;
@@ -178,4 +173,32 @@ class Routing {
return {interf: route.interf, rep: route.rep}; return {interf: route.interf, rep: route.rep};
} }
} }
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,181 @@
package kernel.peripherals;
import cc.Peripheral;
using tink.CoreApi;
class BigReactor implements IPeripheral {
public static inline final TYPE_NAME:String = "BigReactors-Reactor";
private final addr:String;
public function new(addr:String) {
this.addr = addr;
}
public function getAddr():String {
return addr;
}
public function getType():String {
return TYPE_NAME;
}
public inline function getActive():Bool {
return Peripheral.call(addr, "getActive");
}
public inline function getNumberOfControlRods():Int {
return Peripheral.call(addr, "getNumberOfControlRods");
}
public inline function getEnergyStored():Float {
return Peripheral.call(addr, "getEnergyStored");
}
public inline function getFuelTemperature():Float {
return Peripheral.call(addr, "getFuelTemperature");
}
public inline function getCasingTemperature():Float {
return Peripheral.call(addr, "getCasingTemperature");
}
public inline function getFuelAmount():Int {
return Peripheral.call(addr, "getFuelAmount");
}
public inline function getWasteAmount():Int {
return Peripheral.call(addr, "getWasteAmount");
}
public inline function getFuelAmountMax():Int {
return Peripheral.call(addr, "getFuelAmountMax");
}
public function getControlRodName(controlRodIndex:Int):String {
// TODO: check in bounds
return Peripheral.call(addr, "getControlRodName", controlRodIndex);
}
public function getControlRodLevel(controlRodIndex:Int):Int {
// TODO: check in bounds
return Peripheral.call(addr, "getControlRodLevel", controlRodIndex);
}
/**
If the reactor is actively cooled, returns the amount of hot fluid produced in the past tick, in milli-Buckets (mB).
**/
public inline function getEnergyProducedLastTick():Float {
return Peripheral.call(addr, "getEnergyProducedLastTick");
}
/**
If the reactor is passively cooled, always returns 0.
**/
public inline function getHotFluidProducedLastTick():Float {
return Peripheral.call(addr, "getHotFluidProducedLastTick");
}
public inline function getCoolantType():Null<String> {
return Peripheral.call(addr, "getCoolantType");
}
/**
in milli-buckets (mB)
**/
public inline function getCoolantAmount():Int {
return Peripheral.call(addr, "getCoolantAmount");
}
/**
in milli-buckets (mB)
**/
public inline function getCoolantAmountMax():Int {
return Peripheral.call(addr, "getCoolantAmountMax");
}
public inline function getHotFluidType():Null<String> {
return Peripheral.call(addr, "getHotFluidType");
}
/**
in milli-buckets (mB)
**/
public inline function getHotFluidAmount():Int {
return Peripheral.call(addr, "getHotFluidAmount");
}
/**
Returns the reactivity level of the reactor's fuel. 100 = 100 percent
**/
public inline function getFuelReactivity():Int {
return Peripheral.call(addr, "getFuelReactivity");
}
public inline function getFuelConsumedLastTick():Float {
return Peripheral.call(addr, "getFuelConsumedLastTick");
}
public inline function isActivelyCooled():Bool {
return Peripheral.call(addr, "isActivelyCooled");
}
public inline function setActive(active:Bool):Void {
Peripheral.call(addr, "setActive", active);
}
/**
0 (not inserted) to 100 (fully inserted)
**/
public inline function setAllControlRodLevels(level:Int) {
Peripheral.call(addr, "setAllControlRodLevels", level);
}
/**
0 (not inserted) to 100 (fully inserted)
**/
public function setControlRodLevel(index:Int, level:Int):Void {
// TODO: check in bounds
Peripheral.call(addr, "setControlRodLevel", index, level);
}
public inline function doEjectWaste():Void {
throw new Error("Not implemented");
}
public inline function doEjectFuel():Void {
throw new Error("Not implemented");
}
public inline function getControlRodLocation(index:Int):Void {
throw new Error("Not implemented");
}
public inline function getEnergyStoredAsText():String {
return Peripheral.call(addr, "getEnergyStoredAsText");
}
public inline function getVariant():String {
return Peripheral.call(addr, "getVariant");
}
public inline function setControlRodName(index:Int, name:String):Void {
Peripheral.call(addr, "setControlRodName", index, name);
}
// Useless methods
// getFuelStats
// getEnergyStats
// getCoolantFluidStats
// getHotFluidStats
// TODO: need research
// isMethodAvailable(method: String): Bool
// mbGetMaximumCoordinate(): Pos3
// mbGetMinimumCoordinate(): Pos3
// mbGetMultiblockControllerTypeName(): String
// mbIsAssembled(): Bool
// mbIsConnected(): Bool
// mbIsDisassembled(): Bool
// mbIsPaused(): Bool
}

View File

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

View File

@@ -0,0 +1,102 @@
package kernel.peripherals;
import cc.Peripheral;
using tink.CoreApi;
class Drive implements IPeripheral {
public static inline final TYPE_NAME:String = "drive";
public final onDiskInsert:Signal<Noise>;
public final onDiskEject:Signal<Noise>;
private final addr:String;
private final native:cc.periphs.Disk;
private final onDiskInsertTrigger:SignalTrigger<Noise> = Signal.trigger();
private final onDiskEjectTrigger:SignalTrigger<Noise> = Signal.trigger();
@:allow(kernel.peripherals)
private function new(addr:String) {
this.addr = addr;
this.native = Peripheral.wrap(addr);
this.onDiskInsert = this.onDiskInsertTrigger.asSignal();
this.onDiskEject = this.onDiskEjectTrigger.asSignal();
KernelEvents.onDisk.handle((addr) -> {
if (addr == this.addr) {
this.onDiskInsertTrigger.trigger(null);
}
});
KernelEvents.onDiskEject.handle((addr) -> {
if (addr == this.addr) {
this.onDiskEjectTrigger.trigger(null);
}
});
}
public function getAddr():String {
return this.addr;
}
public function getType():String {
return TYPE_NAME;
}
public inline function isDiskPresent():Bool {
return this.native.isDiskPresent();
}
/**
The label of the disk, or `null` if either no disk is inserted or the disk doesn't have a label.
**/
public inline function getDiskLabel():Null<String> {
return this.native.getDiskLabel();
}
public inline function clearDiskLabel() {
this.native.setDiskLabel();
}
public inline function setDiskLabel(label:String):Null<Error> {
try {
this.native.setDiskLabel(label);
return null;
} catch (e:Dynamic) {
return new Error("Invalid label");
}
}
public inline function hasData():Bool {
return this.native.hasData();
}
public inline function hasAudio():Bool {
return this.native.hasAudio();
}
public inline function getMountPath():Null<String> {
return this.getMountPath();
}
public inline function getAudioTitle():Null<String> {
return this.native.getAudioTitle();
}
public inline function playAudio() {
this.native.playAudio();
}
public inline function stopAudio() {
this.native.stopAudio();
}
public inline function ejectDisk() {
this.native.ejectDisk();
}
public inline function getDiskID():Int {
return this.native.getDiskID();
}
}

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,29 @@
package kernel.peripherals;
class EnergyStorage implements IPeripheral {
public static inline final TYPE_NAME:String = "energyCell";
private final addr:String;
private final native:cc.periphs.EnergyStorage;
public function new(addr:String) {
this.addr = addr;
this.native = cc.Peripheral.wrap(addr);
}
public function getEnergy():Int {
return this.native.getEnergy();
}
public function getEnergyCapacity():Int {
return this.native.getEnergyCapacity();
}
public function getAddr():String {
return this.addr;
}
public function getType():String {
return TYPE_NAME;
}
}

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

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

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

@@ -1,26 +1,31 @@
package kernel.peripherals; package kernel.peripherals;
using tink.CoreApi; import cc.Peripheral;
import kernel.log.Log;
import kernel.net.Package; import kernel.net.Package;
import kernel.net.INetworkInterface; import kernel.net.INetworkInterface;
class Modem implements INetworkInterface implements IPeripheral { using tink.CoreApi;
public final addr:String;
public var onMessage(default, null):Signal<Package>;
private final onMessageTrigger:SignalTrigger<Package> = Signal.trigger(); class Modem implements INetworkInterface implements IPeripheral {
public static inline final TYPE_NAME:String = "modem";
public final addr:String;
public var onMessage(default, null):Signal<{pack:GenericPackage, dist:Null<Float>}>;
private final onMessageTrigger:SignalTrigger<{pack:GenericPackage, dist:Null<Float>}> = Signal.trigger();
private final native:cc.periphs.Modem.Modem; private final native:cc.periphs.Modem.Modem;
@:allow(kernel.peripherals) @:allow(kernel.peripherals)
private function new(nativePeripherals:cc.periphs.Modem.Modem, addr:String) { private function new(addr:String) {
this.onMessage = onMessageTrigger.asSignal(); this.onMessage = onMessageTrigger.asSignal();
this.native = nativePeripherals; this.native = Peripheral.wrap(addr);
this.addr = addr; this.addr = addr;
KernelEvents.instance.onModemMessage.handle(params ->{ KernelEvents.onModemMessage.handle(params -> {
try {
if (params.addr == this.addr) { if (params.addr == this.addr) {
var pack:Package = { var pack:GenericPackage = {
fromID: params.message.fromID, fromID: params.message.fromID,
toID: params.message.toID, toID: params.message.toID,
msgID: params.message.msgID, msgID: params.message.msgID,
@@ -29,11 +34,18 @@ class Modem implements INetworkInterface implements IPeripheral {
ttl: params.message.ttl, ttl: params.message.ttl,
}; };
this.onMessageTrigger.trigger(pack); this.onMessageTrigger.trigger({pack: pack, dist: params.distance});
}
} catch (e:Dynamic) {
Log.error("Error while parsing modem message");
} }
}); });
} }
public function isWireless():Bool {
return native.isWireless();
}
public function listen(chan:Int) { public function listen(chan:Int) {
native.open(chan); native.open(chan);
} }
@@ -62,6 +74,10 @@ class Modem implements INetworkInterface implements IPeripheral {
return this.addr; return this.addr;
} }
public function getType():String {
return TYPE_NAME;
}
public function getBaseRoutingCost():Int { public function getBaseRoutingCost():Int {
if (this.native.isWireless()) { if (this.native.isWireless()) {
return 2; // Prefere messages over cable return 2; // Prefere messages over cable

View File

@@ -1,61 +1,195 @@
package kernel.peripherals; package kernel.peripherals;
import kernel.net.INetworkInterface;
import kernel.peripherals.Modem; import kernel.peripherals.Modem;
import kernel.peripherals.Screen; import kernel.peripherals.Screen;
using lua.Table; using lua.Table;
using Lambda; using Lambda;
using tink.CoreApi;
/** /**
Class responseable for retrieving peripherals. Class responseable for retrieving peripherals.
**/ **/
class Peripheral { class Peripheral {
/** public static function getAllAddresses():Array<String> {
Depends on: KernelEvents return cc.Peripheral.getNames().toArray();
**/
public static var instance:Peripheral;
private var screens: Array<Screen> = [];
private var modes: Array<Modem> = [];
@:allow(kernel.Init)
private function new() {
KernelEvents.instance.onPeripheral.handle(this.updatePeripherals);
KernelEvents.instance.onPeripheralDetach.handle(this.updatePeripherals);
updatePeripherals();
} }
private function updatePeripherals() { public static function isPresent(addr:String):Bool {
findScreens(); return cc.Peripheral.isPresent(addr);
findModems(); }
public static function getTypes(addr:String):Array<String> {
if (!cc.Peripheral.isPresent(addr)) {
return [];
}
return cc.Peripheral.getType(addr).toArray();
}
public static function findAddrByType(type:String):Array<String> {
return getAllAddresses().filter(addr -> getTypes(addr).contains(type));
}
private static function safeGetAddr(addr:String, type:String):Null<String> {
if (!isPresent(addr)) {
return null;
}
var types = getTypes(addr);
if (!types.contains(type)) {
return null;
}
return addr;
}
public static function inspect(addr:String):Null<{types:Array<String>, methods:Array<String>}> {
if (!isPresent(addr)) {
return null;
}
var types = getTypes(addr);
var methods = cc.Peripheral.getMethods(addr).toArray();
return {
types: types,
methods: methods
};
} }
/** /**
Get all connected screens. Cast peripheral to a specific type.
This is a temporary solution, maybe forever.
**/ **/
public function getScreens():Array<Screen> { public static function getFromType(addr:String, type:String):Null<IPeripheral> {
return this.screens; switch (type) {
case Computer.TYPE_NAME:
return getComputer(addr);
case Screen.TYPE_NAME:
return getScreen(addr);
case Drive.TYPE_NAME:
return getDrive(addr);
case EnergyStorage.TYPE_NAME:
return getEnergyStorage(addr);
case Modem.TYPE_NAME:
return getModem(addr);
case Printer.TYPE_NAME:
return getPrinter(addr);
case 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);
} }
private function findScreens():Void { return null;
var allScreens = cc.Peripheral.getNames().toArray().filter(s -> cc.Peripheral.getType(s) == "monitor");
this.screens = allScreens.map(s -> return new Screen((cc.Peripheral.wrap(s) : Dynamic), s));
} }
public function getScreen(addr: String): Screen { public static function getScreen(addr:String):Null<Screen> {
return this.screens.find(item -> item.getAddr() == addr); var addr = safeGetAddr(addr, Screen.TYPE_NAME);
if (addr == null)
return null;
return new Screen(addr);
} }
/** public static function getAllScreens():Array<Screen> {
Get all connected modems. return [for (addr in findAddrByType(Screen.TYPE_NAME)) new Screen(addr)];
**/
public function getModems():Array<Modem> {
return this.modes;
} }
private function findModems():Void { public static function getModem(addr:String):Null<Modem> {
var allModems = cc.Peripheral.getNames().toArray().filter(s -> cc.Peripheral.getType(s) == "modem"); var addr = safeGetAddr(addr, Modem.TYPE_NAME);
this.modes = allModems.map(s -> return new Modem((cc.Peripheral.wrap(s) : Dynamic), s)); if (addr == null)
return null;
return new Modem(addr);
}
public static function getAllModems():Array<Modem> {
return [for (addr in findAddrByType(Modem.TYPE_NAME)) new Modem(addr)];
}
public static function getDrive(addr:String):Null<Drive> {
var addr = safeGetAddr(addr, Drive.TYPE_NAME);
if (addr == null)
return null;
return new Drive(addr);
}
public static function getAllDrives():Array<Drive> {
return [for (addr in findAddrByType(Drive.TYPE_NAME)) new Drive(addr)];
}
public static function getRedstone(side:String):Redstone {
// TODO: maybe handle restone differently to not duplicate event listeners
return new Redstone(side);
}
public static function getPrinter(addr:String):Null<Printer> {
var addr = safeGetAddr(addr, Printer.TYPE_NAME);
if (addr == null)
return null;
return new Printer(addr);
}
public static function getAllPrinters():Array<Printer> {
return [for (addr in findAddrByType(Printer.TYPE_NAME)) new Printer(addr)];
}
public static function getEnergyStorage(addr:String):Null<EnergyStorage> {
var addr = safeGetAddr(addr, EnergyStorage.TYPE_NAME);
if (addr == null)
return null;
return new EnergyStorage(addr);
}
public static function getAllEnergyStorages():Array<EnergyStorage> {
return [for (addr in findAddrByType(EnergyStorage.TYPE_NAME)) new EnergyStorage(addr)];
}
public static function getComputer(addr:String):Null<Computer> {
var addr = safeGetAddr(addr, Computer.TYPE_NAME);
if (addr == null)
return null;
return new Computer(addr);
}
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

@@ -0,0 +1,60 @@
package kernel.peripherals;
import cc.Peripheral;
import lib.Rect;
import lib.ScreenPos;
class Printer implements IPeripheral {
public static inline final TYPE_NAME:String = "printer";
private final native:cc.periphs.Printer.Printer;
private final addr:String;
public function new(addr:String) {
this.native = Peripheral.wrap(addr);
this.addr = addr;
}
public function getAddr():String {
return addr;
}
public function getType():String {
return TYPE_NAME;
}
public function write(text:String) {}
public function getCurserPos():ScreenPos {
return new ScreenPos({x: 0, y: 0});
}
public function setCurserPos(pos:ScreenPos) {
this.native.setCursorPos(pos.x, pos.y);
}
public function getPageSize():Rect {
var pos = this.native.getPageSize();
return new Rect({x: 0, y: 0}, {x: pos.x, y: pos.y});
}
public function newPage():Bool {
return this.native.newPage();
}
public function endPage():Bool {
return this.native.endPage();
}
public function setPageTitle(title:String) {
this.native.setPageTitle(title);
}
public function getInkLevel():Float {
return this.native.getInkLevel();
}
public function getPaperLevel():Int {
return this.native.getPaperLevel();
}
}

View File

@@ -0,0 +1,127 @@
package kernel.peripherals;
import kernel.peripherals.interfaces.IRedstone;
import haxe.ds.ReadOnlyArray;
import lib.Color;
using tink.CoreApi;
abstract BundleMask(Int) from cc.Colors.Color to cc.Colors.Color {
public inline function new(i:Int) {
this = i;
}
@:from
public static function fromColor(c:Color) {
return new BundleMask(c);
}
@:op(A + B)
@:op(A | B)
public inline function combine(rhs:BundleMask):BundleMask {
return this | rhs;
}
@:op(A + B)
@:op(A | B)
public inline function combineWithColor(rhs:Color):BundleMask {
return this | rhs;
}
public function getComponents():ReadOnlyArray<Color> {
var components:Array<Color> = [];
var mask = 1;
for (i in 0...16) {
if ((this & mask) > 0) {
components.push(mask);
}
mask = mask << 1;
}
return components;
}
}
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>;
private final onChangeTrigger:SignalTrigger<Noise> = Signal.trigger();
private final addr:Side;
private var analogInputState:Int;
private var bundleInputState:BundleMask;
@:allow(kernel.peripherals)
private function new(side:Side) {
this.addr = side;
this.onChange = this.onChangeTrigger.asSignal();
updateState();
KernelEvents.onRedstone.handle(() -> {
if ((this.getAnalogInput() != this.analogInputState) || (this.bundleInputState != this.getBundledInput())) {
updateState();
this.onChangeTrigger.trigger(null);
}
});
}
public function getAddr():String {
return this.addr;
}
public function getType():String {
return TYPE_NAME;
}
private function updateState() {
this.analogInputState = cc.Redstone.getAnalogInput(this.addr);
this.bundleInputState = cc.Redstone.getBundledInput(this.addr);
}
public inline function setOutput(on:Bool):Void {
this.analogInputState = 15;
cc.Redstone.setOutput(this.addr, on);
}
public inline function getOutput():Bool {
return cc.Redstone.getOutput(this.addr);
}
public inline function getInput():Bool {
return cc.Redstone.getInput(this.addr);
}
public inline function setAnalogOutput(strength:Int):Void {
this.analogInputState = strength;
cc.Redstone.setAnalogOutput(this.addr, strength);
}
public inline function getAnalogOutput():Int {
return cc.Redstone.getAnalogOutput(this.addr);
}
public inline function getAnalogInput():Int {
return cc.Redstone.getAnalogInput(this.addr);
}
public inline function setBundledOutput(output:BundleMask) {
this.bundleInputState = output;
cc.Redstone.setBundledOutput(this.addr, output);
}
public inline function getBundledOutput():BundleMask {
return cc.Redstone.getBundledOutput(this.addr);
}
public inline function getBundledInput():BundleMask {
return cc.Redstone.getBundledInput(this.addr);
}
public inline function testBundledInput(mask:Color):Bool {
return cc.Redstone.testBundledInput(this.addr, mask);
}
}

View File

@@ -1,29 +1,32 @@
package kernel.peripherals; package kernel.peripherals;
import cc.Peripheral;
import lib.ScreenPos;
import cc.Term.TerminalSize;
import kernel.ui.ITermWriteable;
import lib.Vec.Vec2;
import lib.Color;
using tink.CoreApi; using tink.CoreApi;
import cc.Term.TerminalSize; class Screen implements ITermWriteable implements IPeripheral {
import lib.TermWriteable; public static inline final TYPE_NAME:String = "monitor";
import util.Vec.Vec2;
import util.Color;
class Screen implements TermWriteable implements IPeripheral {
private final nativ:cc.periphs.Monitor.Monitor; private final nativ:cc.periphs.Monitor.Monitor;
private final addr:String; private final addr:String;
private final onResizeTrigger:SignalTrigger<Vec2<Int>>; private final onResizeTrigger:SignalTrigger<Vec2<Int>> = Signal.trigger();
public var onResize(default, null):Signal<Vec2<Int>>; public final onResize:Signal<Vec2<Int>>;
@:allow(kernel.peripherals) @:allow(kernel.peripherals)
public function new(nativePeripherals:cc.periphs.Monitor.Monitor, addr:String) { private function new(addr:String) {
this.onResizeTrigger = Signal.trigger();
this.onResize = onResizeTrigger.asSignal(); this.onResize = onResizeTrigger.asSignal();
this.nativ = nativePeripherals; this.nativ = Peripheral.wrap(addr);
this.addr = addr; this.addr = addr;
KernelEvents.instance.onMonitorResize.handle(addr -> { KernelEvents.onMonitorResize.handle(addr -> {
if (addr == this.addr) { if (addr == this.addr) {
onResizeTrigger.trigger(getSize()); onResizeTrigger.trigger(getSize());
} }
@@ -36,6 +39,10 @@ class Screen implements TermWriteable implements IPeripheral {
return this.addr; return this.addr;
} }
public function getType():String {
return TYPE_NAME;
}
public function getTextScale():Float { public function getTextScale():Float {
return nativ.getTextScale(); return nativ.getTextScale();
} }
@@ -52,7 +59,7 @@ class Screen implements TermWriteable implements IPeripheral {
nativ.scroll(y); nativ.scroll(y);
} }
public function getCursorPos():Vec2<Int> { public function getCursorPos():ScreenPos {
var rtn = nativ.getCursorPos(); var rtn = nativ.getCursorPos();
return { return {
x: rtn.x - 1, x: rtn.x - 1,
@@ -73,7 +80,6 @@ class Screen implements TermWriteable implements IPeripheral {
} }
public function getSize():Vec2<Int> { public function getSize():Vec2<Int> {
// FIXME: this will not compile. Has to be changes upstream
var size:TerminalSize = nativ.getSize(); var size:TerminalSize = nativ.getSize();
return { return {
x: size.width, x: size.width,
@@ -90,19 +96,19 @@ class Screen implements TermWriteable implements IPeripheral {
} }
public function getTextColor():Color { public function getTextColor():Color {
return ColorConvert.ccToColor(nativ.getTextColor()); return nativ.getTextColor();
} }
public function setTextColor(colour:Color) { public function setTextColor(color:Color) {
nativ.setTextColor(ColorConvert.colorToCC(colour)); nativ.setTextColor(color);
} }
public function getBackgroundColor():Color { public function getBackgroundColor():Color {
return ColorConvert.ccToColor(nativ.getBackgroundColor()); return nativ.getBackgroundColor();
} }
public function setBackgroundColor(color:Color) { public function setBackgroundColor(color:Color) {
nativ.setBackgroundColor(ColorConvert.colorToCC(color)); nativ.setBackgroundColor(color);
} }
public function isColor():Bool { public function isColor():Bool {

View File

@@ -0,0 +1,30 @@
package kernel.peripherals;
enum abstract Side(String) to String {
var Top = "top";
var Bottom = "bottom";
var Left = "left";
var Right = "right";
var Front = "front";
var Back = "back";
@:from
static public function fromString(s:String) {
switch (s) {
case "top":
return Top;
case "bottom":
return Bottom;
case "left":
return Left;
case "right":
return Right;
case "front":
return Front;
case "back":
return Back;
default:
return null;
}
}
}

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

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

View File

@@ -0,0 +1,123 @@
package kernel.ps;
import kernel.turtle.TurtleMutex;
import kernel.ps.ProcessManager.PID;
import kernel.ui.WindowContext;
import kernel.ui.WindowManager;
import haxe.ds.ReadOnlyArray;
using tink.CoreApi;
typedef HandleConfig = {
?args:Array<String>,
?onWrite:Callback<String>,
?onExit:Callback<Bool>,
}
class ProcessHandle {
public var args(get, null):ReadOnlyArray<String>;
private final pid:PID;
private final config:HandleConfig;
private final closeFuture:Future<Bool>;
private var closeFutureResolev:Bool->Void;
private final windowContexts:Array<WindowContext> = [];
private final cbLinks:Array<CallbackLink> = [];
private final deferFuncs:Array<Void->Void> = [];
private var hasExited:Bool = false;
@:allow(kernel.ps.ProcessManager)
private function new(config:HandleConfig, pid:PID) {
this.config = config;
this.pid = pid;
this.closeFuture = new Future<Bool>((trigger) -> {
this.closeFutureResolev = trigger;
return null;
});
if (this.config.onExit != null) {
this.closeFuture.handle(this.config.onExit);
}
}
public function onExit():Future<Bool> {
return this.closeFuture;
}
public function close(success:Bool = true):Void {
this.hasExited = true;
this.dispose();
EndOfLoop.endOfLoop(() -> {
this.closeFutureResolev(success);
});
ProcessManager.removeProcess(this.pid);
}
public function write(message:String):Void {
if (this.hasExited)
return;
if (this.config.onWrite != null) {
this.config.onWrite.invoke(message);
}
}
public function writeLine(message:String):Void {
this.write(message + "\n");
}
public function createBufferdWindowContext():WindowContext {
var ctx = WindowManager.createNewContext();
this.windowContexts.push(ctx);
return ctx;
}
public function createStatelessWindowContext():{ctx:WindowContext, setRenderFunc:(() -> Void)->Void, requestRender:() -> Void} {
var ctx = WindowManager.createNewStatelessContext();
this.windowContexts.push(ctx.ctx);
return ctx;
}
public function getWindowContexts():ReadOnlyArray<WindowContext> {
return this.windowContexts;
}
private function dispose() {
for (link in this.cbLinks) {
link.cancel();
}
for (func in this.deferFuncs) {
func();
}
}
public function getPid():PID {
return this.pid;
}
public function addCallbackLink(link:CallbackLink) {
this.cbLinks.push(link);
}
public function addDeferFunc(func:Void->Void) {
this.deferFuncs.push(func);
}
public function get_args():ReadOnlyArray<String> {
return this.config.args != null ? this.config.args : [];
}
public function claimTurtleMutex():Bool {
if (TurtleMutex.claim(this.pid)) {
this.addDeferFunc(() -> {
TurtleMutex.release(this.pid);
});
return true;
}
return false;
}
}

View File

@@ -0,0 +1,60 @@
package kernel.ps;
import kernel.log.Log;
import kernel.ps.ProcessHandle.HandleConfig;
using tink.CoreApi;
typedef PID = Int;
class ProcessManager {
private static final processList = new Map<PID, ProcessHandle>();
public static function run(process:IProcess, config:HandleConfig):PID {
var pid = createPID();
var handle = new ProcessHandle(config, pid);
processList.set(pid, handle);
try {
process.run(handle);
} catch (e:Dynamic) {
Log.error("Error while running process: " + e);
handle.close(false);
}
return pid;
}
public static function kill(pid:PID) {
if (!processList.exists(pid)) {
Log.warn("Trying to kill non-existing process: " + pid);
return;
}
var handle = processList.get(pid);
handle.close();
}
private static function createPID():PID {
// TODO: better PID generation
// generate a random PID
return Math.ceil(Math.random() * 1000000);
}
@:allow(kernel.ui.WindowManager)
private static function getProcess(pid:PID):Null<ProcessHandle> {
return processList.get(pid);
}
@:allow(kernel.ps.ProcessHandle)
private static function removeProcess(pid:PID):Void {
processList.remove(pid);
}
public static function listProcesses():Array<PID> {
return [for (pid in processList.keys()) pid];
}
}

View File

@@ -0,0 +1,38 @@
package kernel.service;
import kernel.ps.IProcess;
import kernel.ps.ProcessManager;
import kernel.binstore.BinStore;
using tink.CoreApi;
class Service {
public final binName:String;
public final name:String;
public final args:Array<String>;
public var pid:PID;
public var ps:IProcess;
@:allow(kernel.service.ServiceManager)
private function new(binName:String, name:String, ?args:Array<String>) {
this.binName = binName;
this.name = name;
this.args = args ?? [];
}
public function start() {
var ps = BinStore.instantiate(this.binName);
if (ps == null) {
throw new Error('Bin ${this.binName} not found');
}
this.ps = ps;
this.pid = ProcessManager.run(this.ps, {});
}
public function stop() {
ProcessManager.kill(this.pid);
}
}

View File

@@ -0,0 +1,149 @@
package kernel.service;
import kernel.log.Log;
import kernel.binstore.BinStore;
import lib.KVStore;
using tink.CoreApi;
class ServiceManager {
private static final services:Map<String, Service> = new Map();
@:allow(kernel.Init)
private static function init() {
startAllEnabled();
}
/**
Add a service to be automatically started.
**/
public static function enable(name:String):Outcome<Noise, String> {
if (!services.exists(name)) {
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):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 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);
}
}
private static function load(name:String):Null<Service> {
var store = new KVStore('service/${name}');
store.load();
if (!store.exists("service")) {
return null;
}
return store.get("service");
}
public static function register(name:String, binName:String, args:Array<String>):Outcome<Noise, String> {
if (BinStore.getNameByAlias(binName) == null) {
return Failure("bin not found");
}
if (load(name) != null) {
return Failure("service already exists");
}
var service = new Service(binName, name, args);
var store = new KVStore('service/${name}');
store.set("service", service);
store.save();
Log.info('Service ${name} registered');
return Success(Noise);
}
public static function unregister(name:String):Outcome<Noise, String> {
if (services.exists(name)) {
return Failure("service is running");
}
KVStore.removeNamespace('service/${name}');
Log.info('Service ${name} unregistered');
return Success(Noise);
}
public static function start(name:String):Outcome<Noise, String> {
var service = load(name);
if (service == null) {
return Failure("service not found");
}
service.start();
services.set(name, service);
Log.info('Service ${name} started');
return Success(Noise);
}
public static function stop(name:String):Outcome<Noise, String> {
if (!services.exists(name)) {
return Failure("service not found");
}
var service = services.get(name);
service.stop();
services.remove(name);
Log.info('Service ${name} stopped');
return Success(Noise);
}
public static function listRunning():Array<String> {
var running = [];
for (name in services.keys()) {
running.push(name);
}
return running;
}
public static function get(name:String):Null<Dynamic> {
if (!services.exists(name)) {
return null;
}
// TODO: Maybe there is a way to check types here?
var srv = services.get(name);
return srv.ps;
}
}

312
src/kernel/turtle/Turtle.hx Normal file
View File

@@ -0,0 +1,312 @@
package kernel.turtle;
import kernel.turtle.Types;
import kernel.gps.INS;
using tink.CoreApi;
class Turtle {
public static final MAX_SLOTS:Int = 16;
public static function isTurtle():Bool {
return cc.Turtle != null;
}
private static function conterToOutcome(r:cc.Turtle.TurtleActionResult):Outcome<Noise, String> {
if (r.successful) {
return Outcome.Success(null);
} else {
if (r.error != null) {
return Outcome.Failure(r.error);
} else {
return Outcome.Failure("Unknown error");
}
}
}
public static function forward():Outcome<Noise, String> {
var r = cc.Turtle.forward();
var r2 = conterToOutcome(r);
if (r2.isSuccess())
INS.moveForward();
return r2;
}
public static function back():Outcome<Noise, String> {
var r = cc.Turtle.back();
var r2 = conterToOutcome(r);
if (r2.isSuccess())
INS.moveBackward();
return r2;
}
public static function up():Outcome<Noise, String> {
var r = cc.Turtle.up();
var r2 = conterToOutcome(r);
if (r2.isSuccess())
INS.moveUp();
return r2;
}
public static function down():Outcome<Noise, String> {
var r = cc.Turtle.down();
var r2 = conterToOutcome(r);
if (r2.isSuccess())
INS.moveDown();
return r2;
}
public static function turnLeft():Outcome<Noise, String> {
var r = cc.Turtle.turnLeft();
var r2 = conterToOutcome(r);
if (r2.isSuccess())
INS.turnRight();
return r2;
}
public static function turnRight():Outcome<Noise, String> {
var r = cc.Turtle.turnRight();
var r2 = conterToOutcome(r);
if (r2.isSuccess())
INS.turnRight();
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;
// 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();
}
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 {
case Front:
r = cc.Turtle.place();
case Up:
r = cc.Turtle.placeUp();
case Down:
r = cc.Turtle.placeDown();
}
return conterToOutcome(r);
}
public static function placeSign(dir:InteractDirections, text:String):Outcome<Noise, String> {
var r:cc.Turtle.TurtleActionResult;
switch dir {
case Front:
r = cc.Turtle.place(text);
case Up:
r = cc.Turtle.placeUp(); // FIXME: fix upstream to accept text
case Down:
r = cc.Turtle.placeDown();
}
return conterToOutcome(r);
}
public static function drop(dir:InteractDirections, ?count:Int):Outcome<Noise, String> {
var r = cc.Turtle.drop(count);
return conterToOutcome(r);
}
public static function selectSlot(slot:TurtleSlot):Outcome<Noise, Noise> {
var r = cc.Turtle.select(slot.toCCSlot());
return (r) ? Outcome.Success(null) : Outcome.Failure("Slot out of bounds");
}
public static function getItemCount(?slot:TurtleSlot):Int {
return cc.Turtle.getItemCount(slot.toCCSlot());
}
public static function getItemSpace(?slot:TurtleSlot):Int {
return cc.Turtle.getItemSpace(slot.toCCSlot());
}
public static function detect(dir:InteractDirections):Bool {
switch dir {
case Front:
return cc.Turtle.detect();
case Up:
return cc.Turtle.detectUp();
case Down:
return cc.Turtle.detectDown();
}
}
public static function compareToSlot(dir:InteractDirections):Bool {
switch dir {
case Front:
return cc.Turtle.compare();
case Up:
return cc.Turtle.compareUp();
case Down:
return cc.Turtle.compareDown();
}
}
public static function attack(dir:InteractDirections, ?toolSide:ToolSide):Outcome<Noise, String> {
var r:cc.Turtle.TurtleActionResult;
// FIXEM: upstream needs to be fixed to accept ToolSide
switch dir {
case Front:
r = cc.Turtle.attack();
case Up:
r = cc.Turtle.attackUp();
case Down:
r = cc.Turtle.attackDown();
}
return conterToOutcome(r);
}
public static function suckItem(dir:InteractDirections, ?ammount:Int):Outcome<Noise, String> {
// TODO: ammount in bounds?
var r:cc.Turtle.TurtleActionResult;
switch dir {
case Front:
r = cc.Turtle.suck(ammount);
case Up:
r = cc.Turtle.suckUp(ammount);
case Down:
r = cc.Turtle.suckDown(ammount);
}
return conterToOutcome(r);
}
public static function getFuelLevel():Int {
var r = cc.Turtle.getFuelLevel(); // FIXME: can be a string. Has to be fixed upstream
return r;
}
public static function refuel(?ammount:Int):Outcome<Noise, String> {
var r = cc.Turtle.refuel(ammount);
return conterToOutcome(r);
}
public static function canRefultWithSlot():Bool {
var r = cc.Turtle.refuel(0);
return r.successful;
}
public static function compareSlot(otherSlot:TurtleSlot):Bool {
return cc.Turtle.compareTo(otherSlot.toCCSlot());
}
public static function transfer(from:TurtleSlot, to:TurtleSlot, ?count:Int):Outcome<Noise, Noise> {
selectSlot(from);
var r = cc.Turtle.transferTo(to.toCCSlot(), count);
return r ? Outcome.Success(null) : Outcome.Failure(null);
}
public static function getSelectedSlot():TurtleSlot {
return cc.Turtle.getSelectedSlot() - 1;
}
public static function getFuelLimit():Int {
return cc.Turtle.getFuelLimit(); // FIXME: can be a string. Has to be fixed upstream
}
public static function equip(side:ToolSide):Outcome<Noise, String> {
switch side {
case Left:
return conterToOutcome(cc.Turtle.equipLeft());
case Right:
return conterToOutcome(cc.Turtle.equipRight());
}
}
public static function inspect(dir:InteractDirections):Outcome<BlockInspect, String> {
var r:cc.Turtle.TurtleInspectResult;
switch dir {
case Front:
r = cc.Turtle.inspect();
case Up:
r = cc.Turtle.inspectUp();
case Down:
r = cc.Turtle.inspectDown();
}
if (!r.successful) {
return Outcome.Failure(r.result);
}
// TODO: check if this is correct
return Outcome.Success({
name: (r.result : cc.Turtle.TurtleBlockDetail).name,
state: (r.result : cc.Turtle.TurtleBlockDetail).state,
metadata: (r.result : cc.Turtle.TurtleBlockDetail).metadata,
});
}
public static function getItemDetail(slot:TurtleSlot, ?detailed:Bool = false):Option<ItemInspect> {
var r = cc.Turtle.getItemDetail(slot.toCCSlot()); // FIXME: can take detailed as flag. Has to be fixed upstream
if (r == null) {
return None;
}
// TODO: check if this is correct
return Some({
name: r.name,
count: r.count,
damage: r.damage,
});
}
public static function craft(?limit:Int = 64):Outcome<Noise, String> {
if (limit < 1 || limit > 64) {
return Outcome.Failure("Crafting limit out of bounds");
}
var r = cc.Turtle.craft(limit);
return conterToOutcome(r);
}
}

View File

@@ -0,0 +1,52 @@
package kernel.turtle;
import kernel.log.Log;
import cc.OS;
import kernel.ps.ProcessManager.PID;
/**
Make sure only one process can access the turtle at a time.
**/
class TurtleMutex {
private static var claimedPid:PID = -1;
public static var threadFunc:() -> Void;
@:allow(kernel.ps.ProcessHandle)
private static function claim(pid:PID):Bool {
if (claimedPid == -1) {
claimedPid = pid;
return true;
}
return false;
}
@:allow(kernel.ps.ProcessHandle)
private static function release(pid:PID) {
if (claimedPid == pid) {
claimedPid = -1;
stopTurtleThread();
}
}
@:allow(kernel.KernelEvents)
private static function runThreadFunc() {
if (threadFunc != null) {
try {
threadFunc();
} catch (e) {
Log.error("Error in tthread: " + e);
}
threadFunc = null;
}
}
public static function runInTThread(func:() -> Void) {
threadFunc = func;
OS.queueEvent("tthread");
}
public static function stopTurtleThread() {
threadFunc = null;
KernelEvents.startTurtleCoroutine();
}
}

View File

@@ -0,0 +1,26 @@
package kernel.turtle;
using tink.CoreApi;
/**
The slot nummber for turtle inventory. 0 based. Assured to be in range.
**/
abstract TurtleSlot(Int) to Int {
public function new(i:Int) {
if (i >= 0 && i < Turtle.MAX_SLOTS) {
this = i;
} else {
throw new Error("Slot not in range: " + i);
}
}
@:from
public static function fromInt(i:Int) {
return new TurtleSlot(i);
}
@:allow(kernel.turtle)
private inline function toCCSlot():Int {
return this + 1;
}
}

View File

@@ -0,0 +1,24 @@
package kernel.turtle;
enum ToolSide {
Left;
Right;
}
enum InteractDirections {
Front;
Up;
Down;
}
typedef BlockInspect = {
public var name:String;
public var metadata:Int;
public var state:Dynamic;
}
typedef ItemInspect = {
public var name:String;
public var damage:Int;
public var count:Int;
}

View File

@@ -1,19 +1,25 @@
package kernel.ui; package kernel.ui;
import lib.ScreenPos;
import lib.Vec.Vec2;
import lib.Color;
import kernel.ui.ITermWriteable;
using tink.CoreApi; using tink.CoreApi;
import util.Vec.Vec2; /**
import util.Color; A term writer that can switch beetween its internal buffer and another termwriter.
import lib.TermWriteable; The other target is most of the time a real screen
**/
class VirtualTermWriter implements TermWriteable extends TermBuffer { class BufferedVirtualTermWriter implements IVirtualTermWriter extends TermBuffer {
private static final defaultSize:Vec2<Int> = {x: 50, y: 50}; 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 enabled:Bool = false;
private var onResizeLink:CallbackLink; private var onResizeLink:CallbackLink;
public function new(?target:TermWriteable) { @:allow(kernel.ui)
private function new(?target:ITermWriteable) {
setTarget(target); setTarget(target);
if (enabled) { if (enabled) {
@@ -38,7 +44,7 @@ class VirtualTermWriter implements TermWriteable extends TermBuffer {
return enabled; return enabled;
} }
public function setTarget(newTarget:TermWriteable) { public function setTarget(newTarget:ITermWriteable) {
if (newTarget != null) { if (newTarget != null) {
super.setSize(newTarget.getSize()); super.setSize(newTarget.getSize());
@@ -52,6 +58,8 @@ class VirtualTermWriter implements TermWriteable extends TermBuffer {
setSuperSize(newSize); setSuperSize(newSize);
}); });
newTarget.reset();
target = newTarget; target = newTarget;
} }
} }
@@ -78,7 +86,7 @@ class VirtualTermWriter implements TermWriteable extends TermBuffer {
super.scroll(y); super.scroll(y);
} }
public override function getCursorPos():Vec2<Int> { public override function getCursorPos():ScreenPos {
if (isEnabled()) { if (isEnabled()) {
return target.getCursorPos(); return target.getCursorPos();
} else { } else {
@@ -95,11 +103,18 @@ class VirtualTermWriter implements TermWriteable extends TermBuffer {
} }
public override function getCursorBlink():Bool { public override function getCursorBlink():Bool {
throw new haxe.exceptions.NotImplementedException(); if (isEnabled()) {
return target.getCursorBlink();
} else {
return super.getCursorBlink();
}
} }
public override function setCursorBlink(blink:Bool) { public override function setCursorBlink(blink:Bool) {
// TODO if (isEnabled()) {
target.setCursorBlink(blink);
}
super.setCursorBlink(blink);
} }
public override function getSize():Vec2<Int> { public override function getSize():Vec2<Int> {
@@ -135,12 +150,12 @@ class VirtualTermWriter implements TermWriteable extends TermBuffer {
return super.getTextColor(); return super.getTextColor();
} }
public override function setTextColor(colour:Color) { public override function setTextColor(color:Color) {
if (isEnabled()) { if (isEnabled()) {
target.setTextColor(colour); target.setTextColor(color);
} }
super.setTextColor(colour); super.setTextColor(color);
} }
public override function getBackgroundColor():Color { public override function getBackgroundColor():Color {

View File

@@ -1,14 +1,15 @@
package lib; package kernel.ui;
import lib.ScreenPos;
import lib.Color;
import lib.Vec.Vec2;
using tink.CoreApi; using tink.CoreApi;
import util.Color;
import util.Vec.Vec2;
/** /**
Interface describing a terminal. E.g. the main computer screen or a external screen. 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 var onResize(default, null):Signal<Vec2<Int>>;
public function write(text:String):Void; public function write(text:String):Void;
@@ -17,7 +18,7 @@ interface TermWriteable {
/** /**
Even though CC is 1 based we use a 0 based index. Even though CC is 1 based we use a 0 based index.
**/ **/
public function getCursorPos():Vec2<Int>; public function getCursorPos():ScreenPos;
/** /**
Even though CC is 1 based we use a 0 based index. Even though CC is 1 based we use a 0 based index.
@@ -30,12 +31,13 @@ interface TermWriteable {
public function clear():Void; public function clear():Void;
public function clearLine():Void; public function clearLine():Void;
public function getTextColor():Color; public function getTextColor():Color;
public function setTextColor(colour:Color):Void; public function setTextColor(color:Color):Void;
public function getBackgroundColor():Color; public function getBackgroundColor():Color;
public function setBackgroundColor(color:Color):Void; public function setBackgroundColor(color:Color):Void;
public function isColor():Bool; public function isColor():Bool;
// setPaletteColor(...)
// getPaletteColor(colour) // public function setPaletteColor(...);
// public function getPaletteColor(color);
/** /**
Clears the screen, resetes the courser to (0,0) and resetes the color to Black and White. Clears the screen, resetes the courser to (0,0) and resetes the color to Black and White.

View File

@@ -0,0 +1,12 @@
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 IVirtualTermWriter extends ITermWriteable {
public function enable():Void;
public function disable():Void;
public function isEnabled():Bool;
public function setTarget(newTarget:ITermWriteable):Void;
}

View File

@@ -1,6 +1,6 @@
package kernel.ui; package kernel.ui;
import util.Color; import lib.Color;
@:structInit class Pixel { @:structInit class Pixel {
public var char:String; public var char:String;

View File

@@ -0,0 +1,171 @@
package kernel.ui;
import kernel.log.Log;
import lib.ScreenPos;
import lib.Vec.Vec2;
import lib.Color;
using tink.CoreApi;
/**
Normal TermWriteable but without storing the the state in a buffer wehen disabled.
When enabled or the screen size changes the render function is called.
The render function is only called when needed.
You can also request a re-render by calling `requestRender`.
**/
class StatelessVirtualTermWriter implements IVirtualTermWriter {
public var onResize(default, null):Signal<Vec2<Int>>;
private var onResizeTrigger:SignalTrigger<Vec2<Int>> = Signal.trigger();
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:ITermWriteable) {
onResize = onResizeTrigger.asSignal();
setTarget(target);
}
public function setRenderFunc(func:(Void->Void)) {
this.renderFunc = func;
}
public function requestRender() {
if (renderFunc == null) {
Log.warn("Render requested, but no render function set");
return;
}
if (enabled) {
target.reset();
renderFunc();
renderRequested = false;
} else {
renderRequested = true;
}
}
//
// VirtualTermWriter implementation
//
public function enable() {
if (target == null) {
return;
}
enabled = true;
if (renderFunc != null) {
renderFunc();
}
renderRequested = false;
}
public function disable() {
enabled = false;
}
public function setTarget(newTarget:ITermWriteable) {
if (newTarget == null) {
return;
}
if (target != null) {
onResizeLink.cancel();
}
onResizeLink = newTarget.onResize.handle((size) -> {
requestRender();
onResizeTrigger.trigger(size);
});
target = newTarget;
target.reset();
}
public function isEnabled():Bool {
return enabled;
}
//
// TermWriteable implementation
//
// Some functions return defualt values if the target is not set. This should not be an issue since
// the render func is only called when the target is set.
//
public inline function write(text:String) {
if (enabled)
target.write(text);
}
public inline function scroll(y:Int) {
if (enabled)
target.scroll(y);
}
public inline function getCursorPos():ScreenPos {
return enabled ? target.getCursorPos() : new ScreenPos({x: 0, y: 0});
}
public inline function setCursorPos(x:Int, y:Int) {
if (enabled)
target.setCursorPos(x, y);
}
public inline function getCursorBlink():Bool {
return enabled ? target.getCursorBlink() : false;
}
public inline function setCursorBlink(blink:Bool) {
if (enabled)
target.setCursorBlink(blink);
}
public inline function getSize():Vec2<Int> {
return enabled ? target.getSize() : {x: 0, y: 0};
}
public inline function clear() {
if (enabled)
target.clear();
}
public inline function clearLine() {
if (enabled)
target.clearLine();
}
public inline function getTextColor():Color {
return enabled ? target.getTextColor() : Color.White;
}
public inline function setTextColor(color:Color) {
if (enabled)
target.setTextColor(color);
}
public inline function getBackgroundColor():Color {
return enabled ? target.getBackgroundColor() : Color.Black;
}
public inline function setBackgroundColor(color:Color) {
if (enabled)
target.setBackgroundColor(color);
}
public inline function isColor():Bool {
return enabled ? target.isColor() : false;
}
public inline function reset() {
if (enabled)
target.reset();
}
}

View File

@@ -1,28 +1,34 @@
package kernel.ui; package kernel.ui;
import lib.ScreenPos;
import lib.Vec.Vec2;
import lib.Color;
import kernel.ui.ITermWriteable;
using tink.CoreApi; using tink.CoreApi;
import util.Vec.Vec2; /**
import util.Color; A virtual terminal. With this a GUI program can still write to its "screen"
import lib.TermWriteable; 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. format [y][x]. First index is the line. Second index the char in the line.
**/ **/
private var screenBuffer:Array<Array<Pixel>>; private var screenBuffer:Array<Array<Pixel>>;
private var cursorPos:Vec2<Int> = {x: 0, y: 0}; private var cursorPos:ScreenPos = {x: 0, y: 0};
private var currentTextColor:Color = White; private var currentTextColor:Color = White;
private var currentBgColor:Color = Black; private var currentBgColor:Color = Black;
private var cursorBlink:Bool = false;
private var size:Vec2<Int> = {x: 51, y: 19}; // Default size set to default size of the main terminal private var size:Vec2<Int> = {x: 51, y: 19}; // Default size set to default size of the main terminal
public var onResize(default, null):Signal<Vec2<Int>>; public final onResize:Signal<Vec2<Int>>;
private final onResizeTrigger:SignalTrigger<Vec2<Int>>; private final onResizeTrigger:SignalTrigger<Vec2<Int>> = Signal.trigger();
public function new() { private function new() {
this.onResizeTrigger = Signal.trigger();
this.onResize = onResizeTrigger.asSignal(); this.onResize = onResizeTrigger.asSignal();
initScreenBuffer(size); initScreenBuffer(size);
} }
@@ -54,7 +60,7 @@ class TermBuffer implements TermWriteable {
} }
} }
private function copyBufferToTarget(target:TermWriteable) { private function copyBufferToTarget(target:ITermWriteable) {
target.setCursorPos(0, 0); target.setCursorPos(0, 0);
target.setBackgroundColor(Black); target.setBackgroundColor(Black);
target.setTextColor(White); target.setTextColor(White);
@@ -82,9 +88,10 @@ class TermBuffer implements TermWriteable {
target.setCursorPos(cursorPos.x, cursorPos.y); target.setCursorPos(cursorPos.x, cursorPos.y);
target.setTextColor(currentTextColor); target.setTextColor(currentTextColor);
target.setBackgroundColor(currentBgColor); target.setBackgroundColor(currentBgColor);
target.setCursorBlink(cursorBlink);
} }
private function safeWriteScreenBuffer(pos:Vec2<Int>, char:String) { private function safeWriteScreenBuffer(pos:ScreenPos, char:String) {
if (screenBuffer.length > pos.y && screenBuffer[pos.y].length > pos.x) { if (screenBuffer.length > pos.y && screenBuffer[pos.y].length > pos.x) {
screenBuffer[pos.y][pos.x].char = char; screenBuffer[pos.y][pos.x].char = char;
screenBuffer[pos.y][pos.x].bg = currentBgColor; screenBuffer[pos.y][pos.x].bg = currentBgColor;
@@ -114,7 +121,7 @@ class TermBuffer implements TermWriteable {
]); ]);
} }
public function getCursorPos():Vec2<Int> { public function getCursorPos():ScreenPos {
return cursorPos; return cursorPos;
} }
@@ -126,11 +133,11 @@ class TermBuffer implements TermWriteable {
} }
public function getCursorBlink():Bool { public function getCursorBlink():Bool {
throw new haxe.exceptions.NotImplementedException(); return this.cursorBlink;
} }
public function setCursorBlink(blink:Bool) { public function setCursorBlink(blink:Bool) {
// TODO this.cursorBlink = blink;
} }
public function getSize():Vec2<Int> { public function getSize():Vec2<Int> {
@@ -151,8 +158,8 @@ class TermBuffer implements TermWriteable {
return currentTextColor; return currentTextColor;
} }
public function setTextColor(colour:Color) { public function setTextColor(color:Color) {
currentTextColor = colour; currentTextColor = color;
} }
public function getBackgroundColor():Color { public function getBackgroundColor():Color {
@@ -164,7 +171,7 @@ class TermBuffer implements TermWriteable {
} }
public function isColor():Bool { public function isColor():Bool {
throw new haxe.exceptions.NotImplementedException(); return true; // Lets return true for now.
} }
public function reset() { public function reset() {

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