communicate via JSON RPC

pull/43/head
Alexander Bentkamp 4 years ago
parent 2f465b37c7
commit 054e28c1ec

@ -22,21 +22,17 @@ function App() {
const [nbLevels, setNbLevels] = useState(0) const [nbLevels, setNbLevels] = useState(0)
const [curLevel, setCurLevel] = useState(0) const [curLevel, setCurLevel] = useState(0)
const [finished, setFinished] = useState(false) const [finished, setFinished] = useState(false)
const [rpcConnection, setRpcConnection] = useState(null) const [rpcConnection, setRpcConnection] = useState<null|rpc.MessageConnection>(null)
const socketUrl = 'ws://' + window.location.host + '/websocket/' const socketUrl = 'ws://' + window.location.host + '/websocket/'
const { sendJsonMessage, getWebSocket } = useWebSocket(socketUrl, { useWebSocket(socketUrl, {
onOpen: function (ev) { onOpen: function (ev) {
const logger = new rpc.ConsoleLogger(); const logger = new rpc.ConsoleLogger();
const socket = rpc.toSocket(ev.target as WebSocket); const socket = rpc.toSocket(ev.target as WebSocket);
let rpcConnection = rpc.createWebSocketConnection(socket, logger) let rpcConnection = rpc.createWebSocketConnection(socket, logger)
setRpcConnection(rpcConnection); setRpcConnection(rpcConnection);
rpcConnection.listen(); rpcConnection.listen();
rpcConnection.onNotification("Hello!", () => {
console.log("Yes"); // This prints Hello World
});
rpcConnection.sendNotification("Hello!", 'Hello World');
} }
}) })
@ -56,14 +52,14 @@ function App() {
setCurLevel(1) setCurLevel(1)
} }
// let mainComponent; let mainComponent;
// if (finished) { if (finished) {
// mainComponent = <GoodBye message={conclusion} /> mainComponent = <GoodBye message={conclusion} />
// } else if (curLevel > 0) { } else if (curLevel > 0) {
// mainComponent = <Level sendJsonMessage={sendJsonMessage} lastMessage={lastMessage} lastJsonMessage={lastJsonMessage} nbLevels={nbLevels} level={curLevel} setCurLevel={setCurLevel} setLevelTitle={setLevelTitle} setFinished={setFinished}/> mainComponent = <Level rpcConnection={rpcConnection} nbLevels={nbLevels} level={curLevel} setCurLevel={setCurLevel} setLevelTitle={setLevelTitle} setFinished={setFinished}/>
// } else { } else {
// mainComponent = <Welcome sendJsonMessage={sendJsonMessage} lastMessage={lastMessage} lastJsonMessage={lastJsonMessage} setNbLevels={setNbLevels} setTitle={setTitle} startGame={startGame} setConclusion={setConclusion}/> mainComponent = <Welcome rpcConnection={rpcConnection} setNbLevels={setNbLevels} setTitle={setTitle} startGame={startGame} setConclusion={setConclusion}/>
// } }
return ( return (
<div className="App"> <div className="App">
@ -79,7 +75,7 @@ function App() {
</Typography> </Typography>
</Toolbar> </Toolbar>
</AppBar> </AppBar>
{/* {mainComponent} */} {mainComponent}
</MathJaxContext> </MathJaxContext>
</div> </div>
) )

@ -11,10 +11,18 @@ import LeftPanel from './LeftPanel';
import InputZone from './InputZone'; import InputZone from './InputZone';
import Message from './Message'; import Message from './Message';
import TacticState from './TacticState'; import TacticState from './TacticState';
import * as rpc from 'vscode-ws-jsonrpc';
interface LevelProps {
rpcConnection: null|rpc.MessageConnection;
nbLevels: any;
level: any;
setCurLevel: any;
setLevelTitle: any;
setFinished: any
}
function Level({ rpcConnection, nbLevels, level, setCurLevel, setLevelTitle, setFinished }: LevelProps) {
function Level({ sendJsonMessage, lastMessage, lastJsonMessage, nbLevels, level, setCurLevel, setLevelTitle, setFinished }) {
const [index, setIndex] = useState(level) // Level number const [index, setIndex] = useState(level) // Level number
const [tacticDocs, setTacticDocs] = useState([]) const [tacticDocs, setTacticDocs] = useState([])
const [lemmaDocs, setLemmaDocs] = useState([]) const [lemmaDocs, setLemmaDocs] = useState([])
@ -30,46 +38,40 @@ function Level({ sendJsonMessage, lastMessage, lastJsonMessage, nbLevels, level,
const [completed, setCompleted] = useState(false) const [completed, setCompleted] = useState(false)
// The next function will be called when the level changes const processResponse = (res:any) => {
useEffect(() => { setLeanData(res);
sendJsonMessage({ "loadLevel": level }); setErrors(res.errors);
}, [level, sendJsonMessage]) if (res.message !== "" && res.errors?.length === 0) {
setMessage(res.message)
// The next function will be called when a message arrives or the level title changes
useEffect(() => {
console.log(lastMessage)
console.log(lastJsonMessage)
if ("nb_levels" in lastJsonMessage) { return } // this is an old message from starting the game
const data = lastJsonMessage;
if ("title" in data) { // This is the level metadata coming in
setLevelTitle("Level " + data["index"] + ": " + data["title"])
setIndex(parseInt(data["index"]))
setTacticDocs(data["tactics"])
setLemmaDocs(data["lemmas"])
}
if (data["message"] !== "" && data.errors.length === 0) {
setMessage(data["message"])
setMessageOpen(true) setMessageOpen(true)
} }
setLeanData(data); if (res.goals?.length === 0 && res.errors?.length === 0) {
setErrors(data.errors);
if (data.goals.length === 0 && data.errors.length === 0) {
setCompleted(true) setCompleted(true)
} }
}
}, [lastJsonMessage, lastMessage, setLevelTitle]) // The next function will be called when the level changes
useEffect(() => {
if (rpcConnection) {
rpcConnection.sendRequest("loadLevel", {number: level}).then((res) => {
setLevelTitle("Level " + res["index"] + ": " + res["title"])
setIndex(parseInt(res["index"]))
setTacticDocs(res["tactics"])
setLemmaDocs(res["lemmas"])
processResponse(res)
});
}
}, [level, rpcConnection])
function sendTactic(input) { function sendTactic(input) {
sendJsonMessage({ "runTactic": input }); rpcConnection.sendRequest("runTactic", {tactic: input}).then(processResponse);
setLastTactic(input); setLastTactic(input);
setHistory(history.concat([input])); setHistory(history.concat([input]));
} }
function undo() { function undo() {
if (errors.length === 0) { if (errors.length === 0) {
sendJsonMessage('undo'); rpcConnection.sendRequest('undo').then(processResponse);
} }
if (history.length > 1) { if (history.length > 1) {
setLastTactic(history[history.length - 1]); setLastTactic(history[history.length - 1]);

@ -6,32 +6,44 @@ import '@fontsource/roboto/300.css';
import '@fontsource/roboto/400.css'; import '@fontsource/roboto/400.css';
import '@fontsource/roboto/500.css'; import '@fontsource/roboto/500.css';
import '@fontsource/roboto/700.css'; import '@fontsource/roboto/700.css';
import * as rpc from 'vscode-ws-jsonrpc';
import { Box, Typography, Button, CircularProgress, Grid } from '@mui/material'; import { Box, Typography, Button, CircularProgress, Grid } from '@mui/material';
interface WelcomeProps {
rpcConnection: null|rpc.MessageConnection;
setNbLevels: any;
setTitle: any;
startGame: any;
setConclusion: any;
}
type infoResultType = {
title: string,
nb_levels: any[],
conclusion: string
}
function Welcome({ sendJsonMessage, lastJsonMessage, lastMessage, setNbLevels, setTitle, startGame, setConclusion }) { const infoRequestType = new rpc.RequestType0<infoResultType, void>("info")
const [leanData, setLeanData] = useState({}) function Welcome({ rpcConnection, setNbLevels, setTitle, startGame, setConclusion }: WelcomeProps) {
// Will run at the very beginning const [leanData, setLeanData] = useState<null|infoResultType>(null)
useEffect(() => {
sendJsonMessage('info')
}, [sendJsonMessage])
// Will run when a Json message arrives
useEffect(() => { useEffect(() => {
if (lastJsonMessage != null && lastJsonMessage.hasOwnProperty("nb_levels")) { if (rpcConnection) { // Will run in the beginning as soon as RPC connection is established
setLeanData(lastJsonMessage) rpcConnection.sendRequest(infoRequestType).then((res: infoResultType) =>{
setNbLevels(lastJsonMessage.nb_levels) setLeanData(res)
setTitle(lastJsonMessage.title) setNbLevels(res.nb_levels)
document.title = lastJsonMessage.title setTitle(res.title)
setConclusion(lastJsonMessage.conclusion) document.title = res.title
setConclusion(res.conclusion)
});
} }
}, [lastJsonMessage, setNbLevels, setTitle, setConclusion]) }, [rpcConnection, setLeanData, setNbLevels, setTitle, setConclusion])
let content let content
if ("introduction" in leanData) { if (leanData) {
content = (<Box sx={{ m: 3 }}> content = (<Box sx={{ m: 3 }}>
<Typography variant="body1" component="div"> <Typography variant="body1" component="div">
<MathJax> <MathJax>

@ -33,6 +33,7 @@ class ClientConnection {
this.ws = ws this.ws = ws
this.ws.on("message", (msg) => { this.ws.on("message", (msg) => {
console.log(msg.toString("utf8"));
this.send(JSON.parse(msg.toString("utf8"))); this.send(JSON.parse(msg.toString("utf8")));
}) })

@ -76,8 +76,8 @@ section
variable [ToJson α] variable [ToJson α]
def writeGspMessage (h : FS.Stream) (m : Message) : IO Unit := do def writeGspMessage (h : FS.Stream) (m : Message) : IO Unit := do
h.writeMessage m h.putStr ((toJson m).compress ++ "\n")
h.putStr "\n" h.flush
def writeGspRequest (h : FS.Stream) (r : Request α) : IO Unit := def writeGspRequest (h : FS.Stream) (r : Request α) : IO Unit :=
h.writeGspMessage r h.writeGspMessage r

@ -142,7 +142,15 @@ def coreCtx : Core.Context := {
fileMap := { source := "", positions := #[0], lines := #[1] } } fileMap := { source := "", positions := #[0], lines := #[1] } }
partial def runLevel (env : Environment) (GameName : Name) (levels : HashMap Nat GameLevel) (idx : Nat) : IO Unit := do structure RunTacticParams where
tactic : String
deriving FromJson
structure LoadLevelParams where
number : Nat
deriving FromJson
partial def runLevel (requestId : JsonRpc.RequestID) (env : Environment) (GameName : Name) (levels : HashMap Nat GameLevel) (idx : Nat) : IO Unit := do
let levelName : Name := s!"Level{toString idx}" let levelName : Name := s!"Level{toString idx}"
let termElabM : TermElabM Unit := do let termElabM : TermElabM Unit := do
let some lvl := levels.find? idx | throwError s!"Cannot find level {idx}" let some lvl := levels.find? idx | throwError s!"Cannot find level {idx}"
@ -160,7 +168,8 @@ partial def runLevel (env : Environment) (GameName : Name) (levels : HashMap Nat
errors := resp.errors, errors := resp.errors,
goals := resp.goals, goals := resp.goals,
message := resp.message } message := resp.message }
output (← levelInfo.toJson) let hOut ← IO.getStdout
hOut.writeGspResponse ⟨requestId, ← levelInfo.toJson⟩
mainLoop mainLoop
levelM.run lvl |>.run' state levelM.run lvl |>.run' state
let metaM : MetaM Unit := termElabM.run' (ctx := {}) let metaM : MetaM Unit := termElabM.run' (ctx := {})
@ -215,43 +224,66 @@ where
catch ex => mkResponse #[← ex.toMessageData.toString] catch ex => mkResponse #[← ex.toMessageData.toString]
mainLoop : LevelM Unit := do mainLoop : LevelM Unit := do
match ← Action.get with let hIn ← IO.getStdin
| Action.runTactic tac => do let resp ← runTactic tac; output s!"{← resp.toJson}" let hOut ← IO.getStdout
| Action.loadLevel n => runLevel env GameName levels n let hLog ← IO.getStderr
| Action.undo => do modify fun s => s.pop; output s!"{← (← mkResponse).toJson}" let m ← hIn.readGspMessage
| Action.restartLevel => runLevel env GameName levels idx match m with
| Action.prev => runLevel env GameName levels idx.pred | .request id "runTactic" params => do
| Action.next => runLevel env GameName levels idx.succ match fromJson? (toJson (params)) with
| Action.quit => IO.Process.exit 0 | Except.ok (v : RunTacticParams) =>
| Action.restartGame => output "Can't restart game now" let resp ← runTactic v.tactic
| Action.info => output "Can't get info now" hOut.writeGspResponse ⟨id, ← resp.toJson⟩
| Action.invalid s => output s!"{← { ← mkResponse with errors := #[s!"Invalid action: {s}"] : Response}.toJson}" | Except.error inner =>
hLog.putStr s!"Invalid params: {inner}"
hLog.flush
| .request id "loadLevel" (some params) =>
match fromJson? (toJson (params)) with
| Except.ok (v : LoadLevelParams) =>
runLevel id env GameName levels v.number
| Except.error inner =>
hLog.putStr s!"Invalid params: {inner}"
hLog.flush
| .request id "undo" _ => do
modify fun s => s.pop
hOut.writeGspResponse ⟨id, ← (← mkResponse).toJson⟩
| _ =>
hLog.putStr s!"Invalid action: {toJson m}"
hLog.flush
mainLoop mainLoop
#check (toJson "").compress
open System Lean Std in open System Lean Std in
partial def runGame (GameName : Name) : IO Unit := do partial def runGame (GameName : Name) : IO Unit := do
let hIn ← IO.getStdin let env ← importModules [{ module := `Init : Import }, { module := GameName : Import }] {} 0
let hOut ← IO.getStdout let termElabM : TermElabM Unit := do
let hLog ← IO.getStderr let levels := levelsExt.getState env
hLog.putStr s!"{toJson $ ← hIn.readGspMessage}" let game := {← gameExt.get with nb_levels := levels.size }
hOut.writeGspNotification ⟨"Hello!", "s"⟩ mainLoop env game levels
-- let env ← importModules [{ module := `Init : Import }, { module := GameName : Import }] {} 0 let metaM : MetaM Unit := termElabM.run' (ctx := {})
-- let termElabM : TermElabM Unit := do discard <| metaM.run'.toIO coreCtx { env := env }
-- let levels := levelsExt.getState env where
-- let game := {← gameExt.get with nb_levels := levels.size } mainLoop (env : Environment) (game : Game) (levels : HashMap Nat GameLevel): IO Unit := do
-- mainLoop env game levels let hIn ← IO.getStdin
-- let metaM : MetaM Unit := termElabM.run' (ctx := {}) let hOut ← IO.getStdout
-- discard <| metaM.run'.toIO coreCtx { env := env } let hLog ← IO.getStderr
-- where let m ← hIn.readGspMessage
-- mainLoop (env : Environment) (game : Game) (levels : HashMap Nat GameLevel): IO Unit := do match m with
-- match ← Action.get with | .request id "info" params =>
-- | Action.info => output (toJson game) hOut.writeGspResponse ⟨id, game⟩
-- | Action.loadLevel n => runLevel env GameName levels n | .request id "loadLevel" (some params) =>
-- | Action.quit => IO.Process.exit 0 match fromJson? (toJson (params)) with
-- | Action.invalid s => output s!"Invalid action: {s}" | Except.ok (v : LoadLevelParams) =>
-- | _ => output "Invalid action" runLevel id env GameName levels v.number
-- mainLoop env game levels | Except.error inner =>
hLog.putStr s!"Invalid params: {inner}"
hLog.flush
| .request id m _ =>
hOut.writeGspResponseError
⟨id, JsonRpc.ErrorCode.methodNotFound, s!"Method not found: {m}", none⟩
| _ =>
hLog.putStr s!"Invalid action: {toJson m}"
hLog.flush
mainLoop env game levels
end Server end Server

@ -44,7 +44,6 @@ module.exports = env => {
output: { output: {
path: path.resolve(__dirname, "client/dist/"), path: path.resolve(__dirname, "client/dist/"),
filename: "bundle.js", filename: "bundle.js",
sourceMapFilename: "[name].js.map"
}, },
devServer: { devServer: {
proxy: { proxy: {

Loading…
Cancel
Save