diff --git a/.gitignore b/.gitignore index 407cbdf4..d9d65e88 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,6 @@ node_modules client/dist server/build server/lakefile.olean +server32bit **/lake-packages/ **/.DS_Store diff --git a/client/public/worker.js b/client/public/worker.js index 86f0c579..6bcb1f4b 100644 --- a/client/public/worker.js +++ b/client/public/worker.js @@ -75,7 +75,7 @@ onmessage = (ev) => { setInterval(() => { if (stderrBuffer !== "") { - console.error(stderrBuffer); + console.log(stderrBuffer); stderrBuffer = "" } }, 1000) diff --git a/server/GameServer.lean b/server/GameServer.lean new file mode 100644 index 00000000..4858662d --- /dev/null +++ b/server/GameServer.lean @@ -0,0 +1,6 @@ +import GameServer.Commands + +Game "TestGame" +Title "Hello Test" + +MakeGame diff --git a/server/GameServer/WasmServer.lean b/server/GameServer/WasmServer.lean index 1599477a..31d2e25e 100644 --- a/server/GameServer/WasmServer.lean +++ b/server/GameServer/WasmServer.lean @@ -10,58 +10,52 @@ open Lsp open JsonRpc open System.Uri -def counter := IO.mkRef 0 +def ServerState := GameServerState +@[export game_make_state] +def makeState : IO (Ref ServerState) := do + let e ← IO.getStderr + try + searchPathRef.set ["/lib", "/gamelib"] + let state : GameServerState := { + env := ← importModules #[ + { module := `Init : Import } + -- { module := `GameServer : Import } + ] {} 0 --← createEnv gameDir module, + game := "TEST", + gameDir := "test", + inventory := #[] + difficulty := 0 + } + return ← IO.mkRef state + catch err => + e.putStrLn s!"Import error: {err}" + throw err -def readLspRequestAs (s : String) (expectedMethod : String) (α : Type) [FromJson α] : IO (Request α) := do +def readMessage (s : String) : IO JsonRpc.Message := do let j ← ofExcept (Json.parse s) let m ← match fromJson? j with | Except.ok (m : JsonRpc.Message) => pure m | Except.error inner => throw $ userError s!"JSON '{j.compress}' did not have the format of a JSON-RPC message.\n{inner}" - let initRequest ← match m with - | Message.request id method params? => - if method = expectedMethod then - let j := toJson params? - match fromJson? j with - | Except.ok v => pure $ JsonRpc.Request.mk id expectedMethod (v : α) - | Except.error inner => throw $ userError s!"Unexpected param '{j.compress}' for method '{expectedMethod}'\n{inner}" - else - throw $ userError s!"Expected method '{expectedMethod}', got method '{method}'" - | _ => throw $ userError s!"Expected JSON-RPC request, got: '{(toJson m).compress}'" + return m +def readLspRequestAs (s : String) (expectedMethod : String) (α : Type) [FromJson α] : IO (Request α) := do + let m ← readMessage s + match m with + | Message.request id method params? => + if method = expectedMethod then + let j := toJson params? + match fromJson? j with + | Except.ok v => pure $ JsonRpc.Request.mk id expectedMethod (v : α) + | Except.error inner => throw $ userError s!"Unexpected param '{j.compress}' for method '{expectedMethod}'\n{inner}" + else + throw $ userError s!"Expected method '{expectedMethod}', got method '{method}'" + | _ => throw $ userError s!"Expected JSON-RPC request, got: '{(toJson m).compress}'" -@[export game_send_message] -def sendMessage (s : String) : IO Unit := do - -- IO.println s!"received {s}" - -- if args.length < 2 then - -- throwServerError s!"Expected 1-3 command line arguments in addition to `--server`: - -- game directory, the name of the main module (optional), and the name of the game (optional)." - -- let gameDir := args[1]! - -- let module := if args.length < 3 then defaultGameModule else args[2]! - -- let gameName := if args.length < 4 then defaultGameName else args[3]! - -- let workerPath := "./gameserver" - -- -- TODO: Do the following commands slow us down? - -- let srcSearchPath ← initSrcSearchPath (← getBuildDir) - -- let references ← IO.mkRef (← loadReferences) - -- let fileWorkersRef ← IO.mkRef (RBMap.empty : FileWorkerMap) - -- -- let i ← maybeTee "wdIn.txt" false i - -- -- let o ← maybeTee "wdOut.txt" true o - -- -- let e ← maybeTee "wdErr.txt" true e - -- let state : GameServerState := { - -- env := ← importModules #[] {} 0 --← createEnv gameDir module, - -- game := "TEST", - -- gameDir := "test", - -- inventory := #[] - -- difficulty := 0 - -- } - let initRequest ← readLspRequestAs s "initialize" InitializeParams - - -- We misuse the `rootUri` field to the gameName - let rootUri? := "TEST" - let initRequest := {initRequest with param := {initRequest.param with rootUri?}} +def initializeServer (id : RequestID) : IO Unit := do let o ← IO.getStdout o.writeLspResponse { - id := initRequest.id + id := id result := { capabilities := mkLeanServerCapabilities serverInfo? := some { @@ -71,17 +65,27 @@ def sendMessage (s : String) : IO Unit := do : InitializeResult } } - -- let context : ServerContext := { - -- hIn := i - -- hOut := o - -- hLog := e - -- args := args - -- fileWorkersRef := fileWorkersRef - -- initParams := initRequest.param - -- workerPath - -- srcSearchPath - -- references - -- } - -- discard $ ReaderT.run (StateT.run initAndRunWatchdogAux state) context - return () + +@[export game_send_message] +def sendMessage (s : String) (state : Ref ServerState) : IO Unit := do + let o ← IO.getStdout + let e ← IO.getStderr + try + let m ← readMessage s + match m with + | Message.request id "initialize" params? => + initializeServer id + | Message.request id "info" _ => + let some game := (gameExt.getState (← state.get).env).find? `TestGame + | throwServerError "Game not found" + let gameJson : Json := toJson game + -- Add world sizes to Json object + let worldSize := game.worlds.nodes.toList.map (fun (n, w) => (n.toString, w.levels.size)) + let gameJson := gameJson.mergeObj (Json.mkObj [("worldSize", Json.mkObj worldSize)]) + o.writeLspResponse ⟨id, gameJson⟩ + | _ => throw $ userError s!"Expected JSON-RPC request, got: '{(toJson m).compress}'" + + catch err => + e.putStrLn s!"Server error: {err}" + return () diff --git a/server/lakefile.lean b/server/lakefile.lean index 4ffee6f6..70e66802 100644 --- a/server/lakefile.lean +++ b/server/lakefile.lean @@ -3,6 +3,7 @@ open Lake DSL package GameServer +@[default_target] lean_lib GameServer @[default_target] diff --git a/server/main.c b/server/main.c index 8c2d6a6c..9437804a 100644 --- a/server/main.c +++ b/server/main.c @@ -1,7 +1,8 @@ #include #include -extern lean_object* game_send_message(lean_object*, lean_object*); +extern lean_object* game_send_message(lean_object*, lean_object*, lean_object*); +extern lean_object* game_make_state(lean_object*); // see https://leanprover.github.io/lean4/doc/dev/ffi.html#initialization extern void lean_initialize_runtime_module(); @@ -9,13 +10,16 @@ extern void lean_initialize(); extern void lean_io_mark_end_initialization(); extern lean_object * initialize_GameServer_WasmServer(uint8_t builtin, lean_object *); +lean_object * state; +lean_object * io_world; + int main() { lean_initialize(); lean_initialize_runtime_module(); lean_object * res; // use same default as for Lean executables uint8_t builtin = 1; - lean_object * io_world = lean_io_mk_world(); + io_world = lean_io_mk_world(); res = initialize_GameServer_WasmServer(builtin, io_world); if (lean_io_result_is_ok(res)) { lean_dec_ref(res); @@ -25,9 +29,10 @@ int main() { return 1; // do not access Lean declarations if initialization failed } lean_io_mark_end_initialization(); + state = lean_io_result_get_value(game_make_state(io_world)); } void send_message(char* msg){ lean_object * s = lean_mk_string(msg); - game_send_message(s, lean_io_mk_world()); + game_send_message(s, state, io_world); } diff --git a/wasm.sh b/wasm.sh index b03ce899..4f4aac7a 100755 --- a/wasm.sh +++ b/wasm.sh @@ -1,7 +1,13 @@ #!/bin/bash +rm -rf server32bit +cp -r server server32bit -cd server +cd server32bit +/home/alex/Projects/lean4/build/test/stage1/bin/lake update -R +/home/alex/Projects/lean4/build/test/stage1/bin/lake build + +cd ../server lake build @@ -11,4 +17,11 @@ LEAN_SYSROOT=/home/alex/Projects/lean4/build/release/stage1 LEAN_LIBDIR=$LEAN_SYSROOT/lib/lean emcc -o $OUT_DIR/server.js main.c -I $LEAN_SYSROOT/include -L $LEAN_LIBDIR build/ir/GameServer/*.c -lInit -lLean -lleancpp -lleanrt \ - -sFORCE_FILESYSTEM -lnodefs.js -s EXIT_RUNTIME=0 -s MAIN_MODULE=1 -s LINKABLE=1 -s EXPORT_ALL=1 -s ALLOW_MEMORY_GROWTH=1 -fwasm-exceptions -pthread -flto + -sFORCE_FILESYSTEM -lnodefs.js -s EXIT_RUNTIME=0 -s MAIN_MODULE=1 -s LINKABLE=1 -s EXPORT_ALL=1 -s ALLOW_MEMORY_GROWTH=1 -fwasm-exceptions -pthread -flto \ + --preload-file "${LEAN_SYSROOT}/lib/lean/Init"@/lib/Init \ + --preload-file "${LEAN_SYSROOT}/lib/lean/Init.olean"@/lib/Init.olean \ + --preload-file "${LEAN_SYSROOT}/lib/lean/Init.ilean"@/lib/Init.ilean \ + # --preload-file "${LEAN_SYSROOT}/lib/lean/Lean"@/lib/Lean \ + # --preload-file "${LEAN_SYSROOT}/lib/lean/Lean.olean"@/lib/Lean.olean \ + # --preload-file "${LEAN_SYSROOT}/lib/lean/Lean.ilean"@/lib/Lean.ilean \ + # --preload-file "../server32bit/build/lib"@/gamelib