Compare commits
12 Commits
ec63a65ba3
...
560c85ea61
| Author | SHA1 | Date | |
|---|---|---|---|
| 560c85ea61 | |||
| cb7e284313 | |||
| 563211af6f | |||
| 5985b0c8be | |||
| f35c98f912 | |||
| a343db133e | |||
| dd2e9a4993 | |||
| eb39131056 | |||
| c4260aa719 | |||
| e73b4c4d14 | |||
| 94d02d60af | |||
| 16b7db779f |
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
--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
|
||||||
|
|
||||||
|
|||||||
21
src/bin/LSPS.hx
Normal file
21
src/bin/LSPS.hx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package bin;
|
||||||
|
|
||||||
|
import kernel.ps.ProcessManager;
|
||||||
|
import kernel.ps.ProcessHandle;
|
||||||
|
import kernel.ps.Process;
|
||||||
|
|
||||||
|
class LSPS implements Process {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
63
src/bin/Service.hx
Normal file
63
src/bin/Service.hx
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
package bin;
|
||||||
|
|
||||||
|
import kernel.service.ServiceManager;
|
||||||
|
import lib.CLIAppBase;
|
||||||
|
|
||||||
|
using tink.CoreApi;
|
||||||
|
|
||||||
|
class Service extends CLIAppBase {
|
||||||
|
public function new() {
|
||||||
|
registerSyncSubcommand("start", (args) ->{
|
||||||
|
if (args.length < 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var name = args[0];
|
||||||
|
|
||||||
|
var result = ServiceManager.instace.start(name);
|
||||||
|
return handleResult(result);
|
||||||
|
},"Start a service");
|
||||||
|
|
||||||
|
registerSyncSubcommand("stop", (args) ->{
|
||||||
|
if (args.length < 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var name = args[0];
|
||||||
|
|
||||||
|
var result = ServiceManager.instace.stop(name);
|
||||||
|
return handleResult(result);
|
||||||
|
},"Stop a service");
|
||||||
|
|
||||||
|
registerSyncSubcommand("register", (args) ->{
|
||||||
|
if (args.length < 2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var name = args[0];
|
||||||
|
var binName = args[1];
|
||||||
|
var rest = args.slice(2);
|
||||||
|
|
||||||
|
var result = ServiceManager.instace.register(name, binName, rest);
|
||||||
|
return handleResult(result);
|
||||||
|
},"Register a new service");
|
||||||
|
|
||||||
|
registerSyncSubcommand("unregister", (args) ->{
|
||||||
|
return true;
|
||||||
|
},"Unregister a service");
|
||||||
|
|
||||||
|
registerSyncSubcommand("list", (args) ->{
|
||||||
|
return true;
|
||||||
|
},"List all services");
|
||||||
|
}
|
||||||
|
|
||||||
|
private function handleResult(res: Outcome<Noise,String>): Bool {
|
||||||
|
switch (res) {
|
||||||
|
case Success(_):
|
||||||
|
return true;
|
||||||
|
case Failure(e):
|
||||||
|
this.handle.write(e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package bin;
|
package bin;
|
||||||
|
|
||||||
|
import kernel.binstore.BinStore;
|
||||||
import kernel.ps.ProcessHandle;
|
import kernel.ps.ProcessHandle;
|
||||||
import kernel.ps.Process;
|
import kernel.ps.Process;
|
||||||
import kernel.ps.ProcessManager;
|
import kernel.ps.ProcessManager;
|
||||||
@@ -146,22 +147,12 @@ class Terminal implements Process {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private function getProgByName(name:String):Process {
|
private function getProgByName(name:String):Process {
|
||||||
switch (name) {
|
var bin = BinStore.instance.getBinByAlias(name);
|
||||||
case "hello":
|
if (bin == null) {
|
||||||
return new HelloWorld();
|
|
||||||
case "net":
|
|
||||||
return new Net();
|
|
||||||
case "rs":
|
|
||||||
return new Redstone();
|
|
||||||
case "disk":
|
|
||||||
return new Disk();
|
|
||||||
case "gps":
|
|
||||||
return new GPS();
|
|
||||||
case "turtle":
|
|
||||||
return new Turtle();
|
|
||||||
default:
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Type.createInstance(bin.c,[]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function moveCursorToInput() {
|
private function moveCursorToInput() {
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package kernel;
|
package kernel;
|
||||||
|
|
||||||
|
import kernel.service.ServiceManager;
|
||||||
|
import kernel.binstore.BinStore;
|
||||||
import kernel.gps.INS;
|
import kernel.gps.INS;
|
||||||
import kernel.fs.FS;
|
import kernel.fs.FS;
|
||||||
import kernel.gps.GPS;
|
import kernel.gps.GPS;
|
||||||
@@ -27,6 +29,8 @@ class Init {
|
|||||||
WindowManager.instance = new WindowManager();
|
WindowManager.instance = new WindowManager();
|
||||||
MainTerm.instance = new MainTerm();
|
MainTerm.instance = new MainTerm();
|
||||||
|
|
||||||
|
BinStore.instance = new BinStore();
|
||||||
|
|
||||||
if (Turtle.isTurtle()){
|
if (Turtle.isTurtle()){
|
||||||
Turtle.instance = new Turtle();
|
Turtle.instance = new Turtle();
|
||||||
}
|
}
|
||||||
@@ -53,5 +57,7 @@ class Init {
|
|||||||
Init.mainEvent = MainLoop.add(()->{
|
Init.mainEvent = MainLoop.add(()->{
|
||||||
KernelEvents.instance.startEventLoop();
|
KernelEvents.instance.startEventLoop();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ServiceManager.instace = new ServiceManager();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
12
src/kernel/binstore/Bin.hx
Normal file
12
src/kernel/binstore/Bin.hx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package kernel.binstore;
|
||||||
|
|
||||||
|
import kernel.ps.Process;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Represents a callable program.
|
||||||
|
**/
|
||||||
|
typedef Bin = {
|
||||||
|
c: Class<Process>,
|
||||||
|
name: String,
|
||||||
|
aliases: Array<String>,
|
||||||
|
}
|
||||||
55
src/kernel/binstore/BinStore.hx
Normal file
55
src/kernel/binstore/BinStore.hx
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package kernel.binstore;
|
||||||
|
|
||||||
|
import bin.Service;
|
||||||
|
import bin.LSPS;
|
||||||
|
import bin.Turtle;
|
||||||
|
import bin.Terminal;
|
||||||
|
import bin.Redstone;
|
||||||
|
import bin.Net;
|
||||||
|
import bin.KernelLog;
|
||||||
|
import bin.HelloWorld;
|
||||||
|
import bin.GPS;
|
||||||
|
import bin.Disk;
|
||||||
|
import haxe.ds.ReadOnlyArray;
|
||||||
|
|
||||||
|
class BinStore {
|
||||||
|
public static var instance: BinStore;
|
||||||
|
|
||||||
|
private final store:ReadOnlyArray<Bin> = [
|
||||||
|
{c: Disk, name: "Disk", aliases: ["disk"]},
|
||||||
|
{c: GPS, name: "GPS", aliases: ["gps"]},
|
||||||
|
{c: HelloWorld, name: "HelloWorld", aliases: ["hello"]},
|
||||||
|
{c: KernelLog, name: "KernelLog", aliases: ["log"]},
|
||||||
|
{c: Net, name: "Net", aliases: ["net"]},
|
||||||
|
{c: Redstone, name: "Redstone", aliases: ["redstone","rs"]},
|
||||||
|
{c: Terminal, name: "Terminal", aliases: ["terminal","term"]},
|
||||||
|
{c: Turtle, name: "Turtle", aliases: ["turtle"]},
|
||||||
|
{c: LSPS, name: "PM", aliases: ["lsps"]},
|
||||||
|
{c: Service, name: "Service", aliases: ["service","srv"]}
|
||||||
|
];
|
||||||
|
|
||||||
|
@:allow(kernel.Init)
|
||||||
|
private function new() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getBinByName(name:String):Null<Bin> {
|
||||||
|
for (bin in store) {
|
||||||
|
if (bin.name == name) {
|
||||||
|
return bin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getBinByAlias(alias:String):Null<Bin> {
|
||||||
|
for (bin in store) {
|
||||||
|
for (a in bin.aliases) {
|
||||||
|
if (a == alias) {
|
||||||
|
return bin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -49,8 +49,10 @@ class ProcessHandle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function write(message: String): Void {
|
public function write(message: String): Void {
|
||||||
|
if (this.config.onWrite != null){
|
||||||
this.config.onWrite.invoke(message);
|
this.config.onWrite.invoke(message);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function writeLine(message: String): Void {
|
public function writeLine(message: String): Void {
|
||||||
this.write(message + "\n");
|
this.write(message + "\n");
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
package kernel.ps;
|
package kernel.ps;
|
||||||
|
|
||||||
|
import kernel.log.Log;
|
||||||
import kernel.ps.ProcessHandle.HandleConfig;
|
import kernel.ps.ProcessHandle.HandleConfig;
|
||||||
|
|
||||||
|
using tink.CoreApi;
|
||||||
|
|
||||||
typedef PID = Int;
|
typedef PID = Int;
|
||||||
|
|
||||||
class ProcessManager {
|
class ProcessManager {
|
||||||
@@ -13,11 +16,26 @@ class ProcessManager {
|
|||||||
|
|
||||||
processList.set(pid, handle);
|
processList.set(pid, handle);
|
||||||
|
|
||||||
|
try{
|
||||||
process.run(handle);
|
process.run(handle);
|
||||||
|
}catch(e:Dynamic){
|
||||||
|
Log.error("Error while running process: " + e);
|
||||||
|
handle.close(false);
|
||||||
|
}
|
||||||
|
|
||||||
return pid;
|
return pid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function kill(pid: PID) {
|
||||||
|
if (!processList.exists(pid)){
|
||||||
|
throw new Error("Process with PID " + pid + " does not exist");
|
||||||
|
}
|
||||||
|
|
||||||
|
var handle = processList.get(pid);
|
||||||
|
|
||||||
|
handle.close();
|
||||||
|
}
|
||||||
|
|
||||||
private static function createPID(): PID {
|
private static function createPID(): PID {
|
||||||
// TODO: better PID generation
|
// TODO: better PID generation
|
||||||
|
|
||||||
@@ -34,4 +52,8 @@ class ProcessManager {
|
|||||||
private static function removeProcess(pid:PID):Void {
|
private static function removeProcess(pid:PID):Void {
|
||||||
processList.remove(pid);
|
processList.remove(pid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function listProcesses():Array<PID> {
|
||||||
|
return [for (pid in processList.keys()) pid];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
38
src/kernel/service/Service.hx
Normal file
38
src/kernel/service/Service.hx
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package kernel.service;
|
||||||
|
|
||||||
|
import kernel.ps.ProcessManager;
|
||||||
|
import kernel.ps.ProcessHandle;
|
||||||
|
import kernel.binstore.BinStore;
|
||||||
|
|
||||||
|
using tink.CoreApi;
|
||||||
|
|
||||||
|
class Service {
|
||||||
|
private final binName:String;
|
||||||
|
private final name:String;
|
||||||
|
private final args:Array<String>;
|
||||||
|
private var pid:PID;
|
||||||
|
|
||||||
|
@: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 bin = BinStore.instance.getBinByAlias(this.binName);
|
||||||
|
|
||||||
|
if (bin == null){
|
||||||
|
throw new Error('Bin ${this.binName} not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
var ps = Type.createInstance(bin.c,this.args);
|
||||||
|
|
||||||
|
this.pid = ProcessManager.run(ps,{});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stop() {
|
||||||
|
ProcessManager.kill(this.pid);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
131
src/kernel/service/ServiceManager.hx
Normal file
131
src/kernel/service/ServiceManager.hx
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
package kernel.service;
|
||||||
|
|
||||||
|
import kernel.log.Log;
|
||||||
|
import kernel.binstore.BinStore;
|
||||||
|
import lib.KVStore;
|
||||||
|
|
||||||
|
using tink.CoreApi;
|
||||||
|
|
||||||
|
class ServiceManager {
|
||||||
|
public static var instace: ServiceManager;
|
||||||
|
|
||||||
|
private final services:Map<String,Service> = new Map();
|
||||||
|
|
||||||
|
@:allow(kernel.Init)
|
||||||
|
private function new() {
|
||||||
|
this.startAllEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Add a service to be automatically started.
|
||||||
|
**/
|
||||||
|
private function enable(name: String) {
|
||||||
|
if (!this.services.exists(name)){
|
||||||
|
return; // Service must be started
|
||||||
|
}
|
||||||
|
|
||||||
|
var store = KVStore.getStoreForClass();
|
||||||
|
|
||||||
|
var enabled = store.get("enabled",[]);
|
||||||
|
enabled.push(name);
|
||||||
|
store.set("enabled",enabled);
|
||||||
|
|
||||||
|
store.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Remove a service from being automatically started.
|
||||||
|
**/
|
||||||
|
private function disable(name: String) {
|
||||||
|
var store = KVStore.getStoreForClass();
|
||||||
|
var enabled: Array<String> = store.get("enabled");
|
||||||
|
var index = enabled.indexOf(name);
|
||||||
|
if (index == -1){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
enabled.splice(index,1);
|
||||||
|
store.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function startAllEnabled() {
|
||||||
|
var store = KVStore.getStoreForClass();
|
||||||
|
var enabled: Array<String> = store.get("enabled",[]);
|
||||||
|
for (name in enabled){
|
||||||
|
this.start(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private 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 function register(name: String, binName: String,args: Array<String>): Outcome<Noise,String> {
|
||||||
|
if (BinStore.instance.getBinByAlias(binName) == null){
|
||||||
|
return Failure("bin not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.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 function unregister(name: String): Outcome<Noise,String> {
|
||||||
|
if (this.services.exists(name)){
|
||||||
|
return Failure("service is running");
|
||||||
|
}
|
||||||
|
|
||||||
|
KVStore.removeNamespace('service/${name}');
|
||||||
|
Log.info('Service ${name} unregistered');
|
||||||
|
return Success(Noise);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function start(name: String): Outcome<Noise,String> {
|
||||||
|
var service = this.load(name);
|
||||||
|
if (service == null){
|
||||||
|
return Failure("service not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
service.start();
|
||||||
|
this.services.set(name,service);
|
||||||
|
|
||||||
|
Log.info('Service ${name} started');
|
||||||
|
return Success(Noise);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stop(name: String): Outcome<Noise,String> {
|
||||||
|
if (!this.services.exists(name)){
|
||||||
|
return Failure("service not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
var service = this.services.get(name);
|
||||||
|
service.stop();
|
||||||
|
this.services.remove(name);
|
||||||
|
|
||||||
|
Log.info('Service ${name} stopped');
|
||||||
|
return Success(Noise);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function listRunning(): Array<String> {
|
||||||
|
var running = [];
|
||||||
|
for (name in this.services.keys()){
|
||||||
|
running.push(name);
|
||||||
|
}
|
||||||
|
return running;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,36 +7,21 @@ class BuildInfo {
|
|||||||
/**
|
/**
|
||||||
Get the latest git commit.
|
Get the latest git commit.
|
||||||
**/
|
**/
|
||||||
public static macro function getGitCommitHash():haxe.macro.Expr.ExprOf<String> {
|
public static function getGitCommitHash():String {
|
||||||
#if !display
|
#if !display
|
||||||
var process = new sys.io.Process('git', ['rev-parse', 'HEAD']);
|
return CompileTime.buildGitCommitSha();
|
||||||
if (process.exitCode() != 0) {
|
|
||||||
var message = process.stderr.readAll().toString();
|
|
||||||
var pos = haxe.macro.Context.currentPos();
|
|
||||||
haxe.macro.Context.error("Cannot execute `git rev-parse HEAD`. " + message, pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
// read the output of the process
|
|
||||||
var commitHash:String = process.stdout.readLine();
|
|
||||||
|
|
||||||
// Generates a string expression
|
|
||||||
return macro $v{commitHash};
|
|
||||||
#else
|
|
||||||
// `#if display` is used for code completion. In this case returning an
|
|
||||||
// empty string is good enough; We don't want to call git on every hint.
|
|
||||||
var commitHash:String = "";
|
|
||||||
return macro $v{commitHash};
|
|
||||||
#end
|
#end
|
||||||
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Get the time the file was build.
|
Get the time the file was build.
|
||||||
**/
|
**/
|
||||||
public static macro function buildTime():haxe.macro.Expr.ExprOf<Int> {
|
public static function buildTime():Date {
|
||||||
#if !display
|
#if !display
|
||||||
return macro $v{Math.floor(Date.now().getTime())};
|
return CompileTime.buildDate();
|
||||||
#else
|
#else
|
||||||
return macro $v{0};
|
return 0;
|
||||||
#end
|
#end
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,9 +14,7 @@ class Debug {
|
|||||||
public static function printBuildInfo() {
|
public static function printBuildInfo() {
|
||||||
Log.debug("Commit: " + BuildInfo.getGitCommitHash());
|
Log.debug("Commit: " + BuildInfo.getGitCommitHash());
|
||||||
|
|
||||||
var time:Date = Date.fromTime(BuildInfo.buildTime());
|
Log.debug("Build time: " + BuildInfo.buildTime().toString());
|
||||||
|
|
||||||
Log.debug("Build time: " + time.toString());
|
|
||||||
|
|
||||||
Log.debug("CC/MC version:" + ComputerCraft._HOST);
|
Log.debug("CC/MC version:" + ComputerCraft._HOST);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,12 +10,22 @@ import haxe.ds.StringMap;
|
|||||||
**/
|
**/
|
||||||
class KVStore {
|
class KVStore {
|
||||||
private var kvStore: StringMap<Dynamic> = new StringMap();
|
private var kvStore: StringMap<Dynamic> = new StringMap();
|
||||||
private final namespace:String;
|
public final namespace:String;
|
||||||
|
|
||||||
public function new(namespace: String) {
|
public function new(namespace: String) {
|
||||||
this.namespace = namespace;
|
this.namespace = namespace;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function removeNamespace(namespace: String): Void {
|
||||||
|
var nsFile = getNamespaceFile(namespace);
|
||||||
|
FS.delete(nsFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getStoreForClass(?pos:haxe.PosInfos) {
|
||||||
|
var className = pos.className;
|
||||||
|
return new KVStore(className);
|
||||||
|
}
|
||||||
|
|
||||||
private static function getNamespaceFile(namespace: String): String {
|
private static function getNamespaceFile(namespace: String): String {
|
||||||
return '/var/ns/$namespace';
|
return '/var/ns/$namespace';
|
||||||
}
|
}
|
||||||
@@ -45,8 +55,8 @@ class KVStore {
|
|||||||
this.kvStore.set(key,value);
|
this.kvStore.set(key,value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public inline function get<T>(key: String): Null<T> {
|
public inline function get<T>(key: String,?orElse:T = null): Null<T> {
|
||||||
return this.kvStore.get(key);
|
return this.kvStore.get(key) ?? orElse;
|
||||||
}
|
}
|
||||||
|
|
||||||
public inline function exists(key: String): Bool {
|
public inline function exists(key: String): Bool {
|
||||||
|
|||||||
Reference in New Issue
Block a user