mirror of
https://github.com/urbit/shrub.git
synced 2024-11-28 05:22:27 +03:00
External terminals working well now (Lots more janky cherry picking from king-daemon branch)
This commit is contained in:
parent
c579335288
commit
a920e71aca
@ -4,15 +4,17 @@
|
|||||||
ships. Do it or strip it out.
|
ships. Do it or strip it out.
|
||||||
-}
|
-}
|
||||||
|
|
||||||
module King.API (kingAPI, readPortsFile) where
|
module King.API (King(..), kingAPI, readPortsFile) where
|
||||||
|
|
||||||
import UrbitPrelude
|
import UrbitPrelude
|
||||||
import Data.Aeson
|
-- ort Data.Aeson
|
||||||
import RIO.Directory
|
import RIO.Directory
|
||||||
|
|
||||||
|
import Arvo (Belt)
|
||||||
import King.App (HasConfigDir(..))
|
import King.App (HasConfigDir(..))
|
||||||
import Network.Socket (Socket)
|
import Network.Socket (Socket)
|
||||||
import Prelude (read)
|
import Prelude (read)
|
||||||
|
|
||||||
-- rt Vere.LockFile (lockFile)
|
-- rt Vere.LockFile (lockFile)
|
||||||
|
|
||||||
import qualified Network.HTTP.Types as H
|
import qualified Network.HTTP.Types as H
|
||||||
@ -20,30 +22,24 @@ import qualified Network.Wai as W
|
|||||||
import qualified Network.Wai.Handler.Warp as W
|
import qualified Network.Wai.Handler.Warp as W
|
||||||
import qualified Network.Wai.Handler.WebSockets as WS
|
import qualified Network.Wai.Handler.WebSockets as WS
|
||||||
import qualified Network.WebSockets as WS
|
import qualified Network.WebSockets as WS
|
||||||
import qualified Urbit.Ob as Ob
|
import qualified Vere.NounServ as NounServ
|
||||||
|
import qualified Vere.Term.API as Term
|
||||||
|
|
||||||
|
|
||||||
-- Types -----------------------------------------------------------------------
|
-- Types -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
type TermConn = NounServ.Conn Belt [Term.Ev]
|
||||||
|
|
||||||
|
type TermConnAPI = TVar (Maybe (TermConn -> STM ()))
|
||||||
|
|
||||||
{-
|
{-
|
||||||
Daemon state.
|
Daemon state.
|
||||||
-}
|
-}
|
||||||
data King = King
|
data King = King
|
||||||
{ kServer :: Async ()
|
{ kServer :: Async ()
|
||||||
|
, kTermConn :: TermConnAPI
|
||||||
}
|
}
|
||||||
|
|
||||||
data ShipStatus = Halted | Booting | Booted | Running | LandscapeUp
|
|
||||||
deriving (Generic, ToJSON, FromJSON)
|
|
||||||
|
|
||||||
data KingStatus = Starting | Started
|
|
||||||
deriving (Generic, ToJSON, FromJSON)
|
|
||||||
|
|
||||||
data StatusResp = StatusResp
|
|
||||||
{ king :: KingStatus
|
|
||||||
, ships :: Map Text ShipStatus
|
|
||||||
}
|
|
||||||
deriving (Generic, ToJSON, FromJSON)
|
|
||||||
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
@ -84,9 +80,11 @@ kingServer is =
|
|||||||
where
|
where
|
||||||
startKing :: HasLogFunc e => (Int, Socket) -> RIO e King
|
startKing :: HasLogFunc e => (Int, Socket) -> RIO e King
|
||||||
startKing (port, sock) = do
|
startKing (port, sock) = do
|
||||||
|
api <- newTVarIO Nothing
|
||||||
let opts = W.defaultSettings & W.setPort port
|
let opts = W.defaultSettings & W.setPort port
|
||||||
tid <- async $ io $ W.runSettingsSocket opts sock $ app
|
env <- ask
|
||||||
pure (King tid)
|
tid <- async $ io $ W.runSettingsSocket opts sock $ app env api
|
||||||
|
pure (King tid api)
|
||||||
|
|
||||||
{-
|
{-
|
||||||
Start the HTTP server and write to the ports file.
|
Start the HTTP server and write to the ports file.
|
||||||
@ -99,36 +97,35 @@ kingAPI = do
|
|||||||
-- lockFile dir
|
-- lockFile dir
|
||||||
kingServer (port, sock)
|
kingServer (port, sock)
|
||||||
|
|
||||||
stubStatus :: StatusResp
|
serveTerminal :: HasLogFunc e => e -> TermConnAPI -> Word -> W.Application
|
||||||
stubStatus = StatusResp Started $ mapFromList [("zod", Running)]
|
serveTerminal env api word =
|
||||||
|
WS.websocketsOr WS.defaultConnectionOptions wsApp fallback
|
||||||
serveTerminal :: Ship -> Word -> W.Application
|
|
||||||
serveTerminal ship word =
|
|
||||||
WS.websocketsOr WS.defaultConnectionOptions placeholderWSApp fallback
|
|
||||||
where
|
where
|
||||||
fallback req respond =
|
fallback req respond =
|
||||||
respond $ W.responseLBS H.status500 []
|
respond $ W.responseLBS H.status500 []
|
||||||
$ "This endpoint uses websockets"
|
$ "This endpoint uses websockets"
|
||||||
|
|
||||||
placeholderWSApp :: WS.ServerApp
|
wsApp pen =
|
||||||
placeholderWSApp _ = pure ()
|
atomically (readTVar api) >>= \case
|
||||||
|
Nothing -> WS.rejectRequest pen "Ship not running"
|
||||||
|
Just sp -> do
|
||||||
|
wsc <- io $ WS.acceptRequest pen
|
||||||
|
inp <- io $ newTBMChanIO 5
|
||||||
|
out <- io $ newTBMChanIO 5
|
||||||
|
atomically $ sp $ NounServ.mkConn inp out
|
||||||
|
runRIO env $
|
||||||
|
NounServ.wsConn "NOUNSERV (wsServ) " inp out wsc
|
||||||
|
|
||||||
data BadShip = BadShip Text
|
data BadShip = BadShip Text
|
||||||
deriving (Show, Exception)
|
deriving (Show, Exception)
|
||||||
|
|
||||||
readShip :: Text -> IO Ship
|
app :: HasLogFunc e => e -> TermConnAPI -> W.Application
|
||||||
readShip t = Ob.parsePatp t & \case
|
app env api req respond =
|
||||||
Left err -> throwIO (BadShip t)
|
|
||||||
Right pp -> pure $ Ship $ fromIntegral $ Ob.fromPatp pp
|
|
||||||
|
|
||||||
app :: W.Application
|
|
||||||
app req respond =
|
|
||||||
case W.pathInfo req of
|
case W.pathInfo req of
|
||||||
["terminal", ship, session] -> do
|
["terminal", session] -> do
|
||||||
session :: Word <- evaluate $ read $ unpack session
|
session :: Word <- evaluate $ read $ unpack session
|
||||||
ship <- readShip ship
|
serveTerminal env api session req respond
|
||||||
serveTerminal ship session req respond
|
|
||||||
["status"] ->
|
["status"] ->
|
||||||
respond $ W.responseLBS H.status200 [] $ encode stubStatus
|
respond $ W.responseLBS H.status200 [] "{}"
|
||||||
_ ->
|
_ ->
|
||||||
respond $ W.responseLBS H.status404 [] "No implemented"
|
respond $ W.responseLBS H.status404 [] "No implemented"
|
||||||
|
@ -5,6 +5,9 @@ module Vere.NounServ
|
|||||||
, wsServer
|
, wsServer
|
||||||
, wsClient
|
, wsClient
|
||||||
, testIt
|
, testIt
|
||||||
|
, wsServApp
|
||||||
|
, mkConn
|
||||||
|
, wsConn
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import UrbitPrelude
|
import UrbitPrelude
|
||||||
@ -37,7 +40,7 @@ data Server i o a = Server
|
|||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
wsConn :: (FromNoun i, ToNoun o, Show o, HasLogFunc e)
|
wsConn :: (FromNoun i, ToNoun o) -- , HasLogFunc e)
|
||||||
=> Utf8Builder
|
=> Utf8Builder
|
||||||
-> TBMChan i -> TBMChan o
|
-> TBMChan i -> TBMChan o
|
||||||
-> WS.Connection
|
-> WS.Connection
|
||||||
@ -45,21 +48,21 @@ wsConn :: (FromNoun i, ToNoun o, Show o, HasLogFunc e)
|
|||||||
wsConn pre inp out wsc = do
|
wsConn pre inp out wsc = do
|
||||||
env <- ask
|
env <- ask
|
||||||
|
|
||||||
-- logWarn (pre <> "(wcConn) Connected!")
|
-- logWarn (pre <> "(wcConn) Connected!")
|
||||||
|
|
||||||
writer <- io $ async $ runRIO env $ forever $ do
|
writer <- io $ async $ runRIO env $ forever $ do
|
||||||
-- logWarn (pre <> "(wsConn) Waiting for data.")
|
-- logWarn (pre <> "(wsConn) Waiting for data.")
|
||||||
byt <- io $ toStrict <$> WS.receiveData wsc
|
byt <- io $ toStrict <$> WS.receiveData wsc
|
||||||
-- logWarn (pre <> "Got data")
|
-- logWarn (pre <> "Got data")
|
||||||
dat <- cueBSExn byt >>= fromNounExn
|
dat <- cueBSExn byt >>= fromNounExn
|
||||||
-- logWarn (pre <> "(wsConn) Decoded data, writing to chan")
|
-- logWarn (pre <> "(wsConn) Decoded data, writing to chan")
|
||||||
atomically $ writeTBMChan inp dat
|
atomically $ writeTBMChan inp dat
|
||||||
|
|
||||||
reader <- io $ async $ runRIO env $ forever $ do
|
reader <- io $ async $ runRIO env $ forever $ do
|
||||||
-- logWarn (pre <> "Waiting for data from chan")
|
-- logWarn (pre <> "Waiting for data from chan")
|
||||||
atomically (readTBMChan out) >>= \case
|
atomically (readTBMChan out) >>= \case
|
||||||
Nothing -> do
|
Nothing -> do
|
||||||
-- logWarn (pre <> "(wsConn) Connection closed")
|
-- logWarn (pre <> "(wsConn) Connection closed")
|
||||||
error "dead-conn"
|
error "dead-conn"
|
||||||
Just msg -> do
|
Just msg -> do
|
||||||
-- logWarn (pre <> "(wsConn) Got message! " <> displayShow msg)
|
-- logWarn (pre <> "(wsConn) Got message! " <> displayShow msg)
|
||||||
@ -67,7 +70,7 @@ wsConn pre inp out wsc = do
|
|||||||
|
|
||||||
res <- atomically (waitCatchSTM writer <|> waitCatchSTM reader)
|
res <- atomically (waitCatchSTM writer <|> waitCatchSTM reader)
|
||||||
|
|
||||||
-- logWarn $ displayShow (res :: Either SomeException ())
|
-- logWarn $ displayShow (res :: Either SomeException ())
|
||||||
|
|
||||||
atomically (closeTBMChan inp >> closeTBMChan out)
|
atomically (closeTBMChan inp >> closeTBMChan out)
|
||||||
|
|
||||||
@ -76,9 +79,9 @@ wsConn pre inp out wsc = do
|
|||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
wsClient :: ∀i o e. (Show o, Show i, ToNoun o, FromNoun i, HasLogFunc e)
|
wsClient :: ∀i o e. (ToNoun o, FromNoun i, HasLogFunc e)
|
||||||
=> W.Port -> RIO e (Client i o)
|
=> Text -> W.Port -> RIO e (Client i o)
|
||||||
wsClient por = do
|
wsClient pax por = do
|
||||||
env <- ask
|
env <- ask
|
||||||
inp <- io $ newTBMChanIO 5
|
inp <- io $ newTBMChanIO 5
|
||||||
out <- io $ newTBMChanIO 5
|
out <- io $ newTBMChanIO 5
|
||||||
@ -87,31 +90,36 @@ wsClient por = do
|
|||||||
-- logDebug "NOUNSERV (wsClie) Trying to connect"
|
-- logDebug "NOUNSERV (wsClie) Trying to connect"
|
||||||
|
|
||||||
tid <- io $ async
|
tid <- io $ async
|
||||||
$ WS.runClient "127.0.0.1" por "/terminal/~zod/1"
|
$ WS.runClient "127.0.0.1" por (unpack pax)
|
||||||
$ runRIO env . wsConn "NOUNSERV (wsClie) " inp out
|
$ runRIO env . wsConn "NOUNSERV (wsClie) " inp out
|
||||||
|
|
||||||
pure $ Client con tid
|
pure $ Client con tid
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
wsServer :: ∀i o e. (Show o, Show i, ToNoun o, FromNoun i, HasLogFunc e)
|
wsServApp :: (HasLogFunc e, ToNoun o, FromNoun i)
|
||||||
|
=> (Conn i o -> STM ())
|
||||||
|
-> WS.PendingConnection
|
||||||
|
-> RIO e ()
|
||||||
|
wsServApp cb pen = do
|
||||||
|
logError "NOUNSERV (wsServer) Got connection!"
|
||||||
|
wsc <- io $ WS.acceptRequest pen
|
||||||
|
inp <- io $ newTBMChanIO 5
|
||||||
|
out <- io $ newTBMChanIO 5
|
||||||
|
atomically $ cb (mkConn inp out)
|
||||||
|
wsConn "NOUNSERV (wsServ) " inp out wsc
|
||||||
|
|
||||||
|
wsServer :: ∀i o e. (ToNoun o, FromNoun i, HasLogFunc e)
|
||||||
=> RIO e (Server i o W.Port)
|
=> RIO e (Server i o W.Port)
|
||||||
wsServer = do
|
wsServer = do
|
||||||
con <- io $ newTBMChanIO 5
|
con <- io $ newTBMChanIO 5
|
||||||
|
|
||||||
let app pen = do
|
|
||||||
logTrace "NOUNSERV (wsServer) Got connection! Accepting"
|
|
||||||
wsc <- io $ WS.acceptRequest pen
|
|
||||||
inp <- io $ newTBMChanIO 5
|
|
||||||
out <- io $ newTBMChanIO 5
|
|
||||||
atomically $ writeTBMChan con (mkConn inp out)
|
|
||||||
wsConn "NOUNSERV (wsServ) " inp out wsc
|
|
||||||
|
|
||||||
tid <- async $ do
|
tid <- async $ do
|
||||||
env <- ask
|
env <- ask
|
||||||
logTrace "NOUNSERV (wsServer) Starting server"
|
logError "NOUNSERV (wsServer) Starting server"
|
||||||
io $ WS.runServer "127.0.0.1" 9999 (runRIO env . app)
|
io $ WS.runServer "127.0.0.1" 9999
|
||||||
logWarn "NOUNSERV (wsServer) Server died"
|
$ runRIO env . wsServApp (writeTBMChan con)
|
||||||
|
logError "NOUNSERV (wsServer) Server died"
|
||||||
atomically $ closeTBMChan con
|
atomically $ closeTBMChan con
|
||||||
|
|
||||||
pure $ Server (readTBMChan con) tid 9999
|
pure $ Server (readTBMChan con) tid 9999
|
||||||
@ -133,7 +141,7 @@ testIt = do
|
|||||||
logTrace "(testIt) Starting Server"
|
logTrace "(testIt) Starting Server"
|
||||||
Server{..} <- wsServer @Example @Example
|
Server{..} <- wsServer @Example @Example
|
||||||
logTrace "(testIt) Connecting"
|
logTrace "(testIt) Connecting"
|
||||||
Client{..} <- wsClient @Example @Example sData
|
Client{..} <- wsClient @Example @Example "/" sData
|
||||||
|
|
||||||
logTrace "(testIt) Accepting connection"
|
logTrace "(testIt) Accepting connection"
|
||||||
sConn <- fromJust "accept" =<< atomically sAccept
|
sConn <- fromJust "accept" =<< atomically sAccept
|
||||||
|
@ -152,7 +152,12 @@ pier (serf, log, ss) = do
|
|||||||
saveM <- newEmptyTMVarIO
|
saveM <- newEmptyTMVarIO
|
||||||
shutdownM <- newEmptyTMVarIO
|
shutdownM <- newEmptyTMVarIO
|
||||||
|
|
||||||
_api ← King.kingAPI
|
kapi ← King.kingAPI
|
||||||
|
|
||||||
|
termApiQ <- atomically $ do
|
||||||
|
q <- newTQueue
|
||||||
|
writeTVar (King.kTermConn kapi) (Just $ writeTQueue q)
|
||||||
|
pure q
|
||||||
|
|
||||||
let shutdownEvent = putTMVar shutdownM ()
|
let shutdownEvent = putTMVar shutdownM ()
|
||||||
|
|
||||||
@ -160,28 +165,22 @@ pier (serf, log, ss) = do
|
|||||||
|
|
||||||
-- (sz, local) <- Term.localClient
|
-- (sz, local) <- Term.localClient
|
||||||
|
|
||||||
(waitExternalTerm, termServPort) <- Term.termServer
|
-- (waitExternalTerm, termServPort) <- Term.termServer
|
||||||
|
|
||||||
(demux, muxed) <- atomically $ do
|
(demux, muxed) <- atomically $ do
|
||||||
res <- Term.mkDemux
|
res <- Term.mkDemux
|
||||||
-- Term.addDemux local res
|
-- Term.addDemux local res
|
||||||
pure (res, Term.useDemux res)
|
pure (res, Term.useDemux res)
|
||||||
|
|
||||||
rio $ logInfo $ display $
|
-- rio $ logInfo $ display $
|
||||||
"TERMSERV Terminal Server running on port: " <> tshow termServPort
|
-- "TERMSERV Terminal Server running on port: " <> tshow termServPort
|
||||||
|
|
||||||
let listenLoop = do
|
acquireWorker $ forever $ do
|
||||||
logTrace "TERMSERV Waiting for external terminal."
|
logTrace "TERMSERV Waiting for external terminal."
|
||||||
ok <- atomically $ do
|
atomically $ do
|
||||||
waitExternalTerm >>= \case
|
ext <- Term.connClient <$> readTQueue termApiQ
|
||||||
Nothing -> pure False
|
Term.addDemux ext demux
|
||||||
Just ext -> Term.addDemux ext demux >> pure True
|
logTrace "TERMSERV External terminal connected."
|
||||||
if ok
|
|
||||||
then do logTrace "TERMSERV External terminal connected"
|
|
||||||
listenLoop
|
|
||||||
else logTrace "TERMSERV Termainal server is dead"
|
|
||||||
|
|
||||||
acquireWorker listenLoop
|
|
||||||
|
|
||||||
swapMVar (sStderr serf) (atomically . Term.trace muxed)
|
swapMVar (sStderr serf) (atomically . Term.trace muxed)
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ module Vere.Term
|
|||||||
, localClient
|
, localClient
|
||||||
, connectToRemote
|
, connectToRemote
|
||||||
, runTerminalClient
|
, runTerminalClient
|
||||||
, termServer
|
, connClient
|
||||||
, term
|
, term
|
||||||
) where
|
) where
|
||||||
|
|
||||||
@ -113,31 +113,11 @@ isTerminalBlit _ = True
|
|||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
{-
|
connClient :: Serv.Conn Belt [Term.Ev] -> Client
|
||||||
TODO XX HACK: We don't have any good way of handling client
|
connClient c = Client
|
||||||
disconnect, so we just retry. This will probably waste CPU.
|
{ give = Serv.cSend c
|
||||||
-}
|
, take = Serv.cRecv c
|
||||||
termServer :: ∀e. HasLogFunc e
|
}
|
||||||
=> RAcquire e (STM (Maybe Client), Port)
|
|
||||||
termServer = fst <$> mkRAcquire start stop
|
|
||||||
where
|
|
||||||
stop = cancel . snd
|
|
||||||
start = do
|
|
||||||
serv <- Serv.wsServer @Belt @[Term.Ev]
|
|
||||||
|
|
||||||
let getClient = do
|
|
||||||
Serv.sAccept serv <&> \case
|
|
||||||
Nothing -> Nothing
|
|
||||||
Just c -> Just $ Client
|
|
||||||
{ give = Serv.cSend c
|
|
||||||
, take = Serv.cRecv c >>= \case
|
|
||||||
Nothing -> empty
|
|
||||||
Just ev -> pure ev
|
|
||||||
}
|
|
||||||
|
|
||||||
pure ( (getClient, Port $ fromIntegral $ Serv.sData serv)
|
|
||||||
, Serv.sAsync serv
|
|
||||||
)
|
|
||||||
|
|
||||||
connectToRemote :: ∀e. HasLogFunc e
|
connectToRemote :: ∀e. HasLogFunc e
|
||||||
=> Port
|
=> Port
|
||||||
@ -147,10 +127,13 @@ connectToRemote port local = mkRAcquire start stop
|
|||||||
where
|
where
|
||||||
stop (x, y) = cancel x >> cancel y
|
stop (x, y) = cancel x >> cancel y
|
||||||
start = do
|
start = do
|
||||||
Serv.Client{..} <- Serv.wsClient (fromIntegral port)
|
Serv.Client{..} <- Serv.wsClient "/terminal/0" (fromIntegral port)
|
||||||
|
|
||||||
|
-- TODO XX Handle disconnect more cleanly.
|
||||||
ferry <- async $ forever $ atomically $ asum
|
ferry <- async $ forever $ atomically $ asum
|
||||||
[ Term.take local >>= Serv.cSend cConn
|
[ Term.take local >>= \case
|
||||||
|
Nothing -> empty
|
||||||
|
Just ev -> Serv.cSend cConn ev
|
||||||
, Serv.cRecv cConn >>= \case
|
, Serv.cRecv cConn >>= \case
|
||||||
Nothing -> empty
|
Nothing -> empty
|
||||||
Just ev -> Term.give local ev
|
Just ev -> Term.give local ev
|
||||||
@ -164,11 +147,13 @@ instance HasConfigDir HackConfigDir where configDirL = hcdPax
|
|||||||
|
|
||||||
runTerminalClient :: ∀e. HasLogFunc e => FilePath -> RIO e ()
|
runTerminalClient :: ∀e. HasLogFunc e => FilePath -> RIO e ()
|
||||||
runTerminalClient pier = runRAcquire $ do
|
runTerminalClient pier = runRAcquire $ do
|
||||||
mPort <- runRIO (HCD pier) readPortsFile
|
mPort <- runRIO (HCD pier) readPortsFile
|
||||||
port <- maybe (error "Can't connect") pure mPort
|
port <- maybe (error "Can't connect") pure mPort
|
||||||
(tsize, local) <- localClient
|
mExit <- io newEmptyTMVarIO
|
||||||
(tid1, tid2) <- connectToRemote (Port $ fromIntegral port) local
|
(siz, cli) <- localClient (putTMVar mExit ())
|
||||||
atomically $ waitSTM tid1 <|> waitSTM tid2
|
(tid, sid) <- connectToRemote (Port $ fromIntegral port) cli
|
||||||
|
atomically $ waitSTM tid <|> waitSTM sid <|> takeTMVar mExit
|
||||||
|
|
||||||
where
|
where
|
||||||
runRAcquire :: RAcquire e () -> RIO e ()
|
runRAcquire :: RAcquire e () -> RIO e ()
|
||||||
runRAcquire act = rwith act $ const $ pure ()
|
runRAcquire act = rwith act $ const $ pure ()
|
||||||
@ -176,8 +161,10 @@ runTerminalClient pier = runRAcquire $ do
|
|||||||
{-
|
{-
|
||||||
Initializes the generalized input/output parts of the terminal.
|
Initializes the generalized input/output parts of the terminal.
|
||||||
-}
|
-}
|
||||||
localClient :: ∀e. HasLogFunc e => RAcquire e (TSize.Window Word, Client)
|
localClient :: ∀e. HasLogFunc e
|
||||||
localClient = fst <$> mkRAcquire start stop
|
=> STM ()
|
||||||
|
-> RAcquire e (TSize.Window Word, Client)
|
||||||
|
localClient doneSignal = fst <$> mkRAcquire start stop
|
||||||
where
|
where
|
||||||
start :: HasLogFunc e => RIO e ((TSize.Window Word, Client), Private)
|
start :: HasLogFunc e => RIO e ((TSize.Window Word, Client), Private)
|
||||||
start = do
|
start = do
|
||||||
@ -202,7 +189,7 @@ localClient = fst <$> mkRAcquire start stop
|
|||||||
pReaderThread <- asyncBound
|
pReaderThread <- asyncBound
|
||||||
(readTerminal tsReadQueue tsWriteQueue (bell tsWriteQueue))
|
(readTerminal tsReadQueue tsWriteQueue (bell tsWriteQueue))
|
||||||
|
|
||||||
let client = Client { take = readTQueue tsReadQueue
|
let client = Client { take = Just <$> readTQueue tsReadQueue
|
||||||
, give = writeTQueue tsWriteQueue
|
, give = writeTQueue tsWriteQueue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -504,8 +491,10 @@ localClient = fst <$> mkRAcquire start stop
|
|||||||
writeTQueue rq $ Ctl $ Cord "c"
|
writeTQueue rq $ Ctl $ Cord "c"
|
||||||
loop rd
|
loop rd
|
||||||
else if w <= 26 then do
|
else if w <= 26 then do
|
||||||
sendBelt $ Ctl $ Cord $ pack [BS.w2c (w + 97 - 1)]
|
case pack [BS.w2c (w + 97 - 1)] of
|
||||||
loop rd
|
"d" -> atomically doneSignal
|
||||||
|
c -> do sendBelt $ Ctl $ Cord c
|
||||||
|
loop rd
|
||||||
else if w == 27 then do
|
else if w == 27 then do
|
||||||
loop rd { rdEscape = True }
|
loop rd { rdEscape = True }
|
||||||
else do
|
else do
|
||||||
@ -537,28 +526,31 @@ term (tsize, Client{..}) shutdownSTM king enqueueEv =
|
|||||||
|
|
||||||
runTerm :: RAcquire e (EffCb e TermEf)
|
runTerm :: RAcquire e (EffCb e TermEf)
|
||||||
runTerm = do
|
runTerm = do
|
||||||
tim <- mkRAcquire start cancel
|
tim <- mkRAcquire (async readLoop) cancel
|
||||||
pure handleEffect
|
pure handleEffect
|
||||||
|
|
||||||
start :: RIO e (Async ())
|
{-
|
||||||
start = async readBelt
|
Because our terminals are always `Demux`ed, we don't have to
|
||||||
|
care about disconnections.
|
||||||
readBelt :: RIO e ()
|
-}
|
||||||
readBelt = forever $ do
|
readLoop :: RIO e ()
|
||||||
b <- atomically take
|
readLoop = forever $ do
|
||||||
let blip = EvBlip $ BlipEvTerm $ TermEvBelt (UD 1, ()) $ b
|
atomically take >>= \case
|
||||||
atomically $ enqueueEv $ blip
|
Nothing -> pure ()
|
||||||
|
Just b -> do
|
||||||
|
let blip = EvBlip $ BlipEvTerm $ TermEvBelt (UD 1, ()) $ b
|
||||||
|
atomically $ enqueueEv $ blip
|
||||||
|
|
||||||
handleEffect :: TermEf -> RIO e ()
|
handleEffect :: TermEf -> RIO e ()
|
||||||
handleEffect = \case
|
handleEffect = \case
|
||||||
TermEfBlit _ blits -> do
|
TermEfInit _ _ -> pure ()
|
||||||
let (termBlits, fsWrites) = partition isTerminalBlit blits
|
TermEfMass _ _ -> pure ()
|
||||||
atomically $ give [Term.Blits termBlits]
|
TermEfLogo _ _ -> atomically shutdownSTM
|
||||||
for_ fsWrites handleFsWrite
|
TermEfBlit _ blits -> do
|
||||||
TermEfInit _ _ -> pure ()
|
let (termBlits, fsWrites) = partition isTerminalBlit blits
|
||||||
TermEfLogo path _ -> do
|
atomically $ give [Term.Blits termBlits]
|
||||||
atomically $ shutdownSTM
|
for_ fsWrites handleFsWrite
|
||||||
TermEfMass _ _ -> pure ()
|
|
||||||
|
|
||||||
handleFsWrite :: Blit -> RIO e ()
|
handleFsWrite :: Blit -> RIO e ()
|
||||||
handleFsWrite (Sag path noun) = performPut path (jamBS noun)
|
handleFsWrite (Sag path noun) = performPut path (jamBS noun)
|
||||||
|
@ -22,7 +22,7 @@ data Ev = Blits [Blit]
|
|||||||
deriving (Show)
|
deriving (Show)
|
||||||
|
|
||||||
data Client = Client
|
data Client = Client
|
||||||
{ take :: STM Belt
|
{ take :: STM (Maybe Belt)
|
||||||
, give :: [Ev] -> STM ()
|
, give :: [Ev] -> STM ()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,17 +17,40 @@ import qualified Vere.Term.Logic as Logic
|
|||||||
|
|
||||||
-- External --------------------------------------------------------------------
|
-- External --------------------------------------------------------------------
|
||||||
|
|
||||||
|
data KeyedSet a = KeyedSet
|
||||||
|
{ _ksTable :: IntMap a
|
||||||
|
, _nextKey :: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
instance Semigroup (KeyedSet a) where
|
||||||
|
KeyedSet t1 k1 <> KeyedSet t2 k2 = KeyedSet (t1 <> t2) (max k1 k2)
|
||||||
|
|
||||||
|
instance Monoid (KeyedSet a) where
|
||||||
|
mempty = KeyedSet mempty 0
|
||||||
|
|
||||||
|
ksInsertKey :: a -> KeyedSet a -> (Int, KeyedSet a)
|
||||||
|
ksInsertKey x (KeyedSet tbl nex) =
|
||||||
|
(nex, KeyedSet (insertMap nex x tbl) (succ nex))
|
||||||
|
|
||||||
|
ksInsert :: a -> KeyedSet a -> KeyedSet a
|
||||||
|
ksInsert x s = snd $ ksInsertKey x s
|
||||||
|
|
||||||
|
ksDelete :: Int -> KeyedSet a -> KeyedSet a
|
||||||
|
ksDelete k (KeyedSet t n) = KeyedSet (deleteMap k t) n
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
data Demux = Demux
|
data Demux = Demux
|
||||||
{ dConns :: TVar [Client]
|
{ dConns :: TVar (KeyedSet Client)
|
||||||
, dStash :: TVar Logic.St
|
, dStash :: TVar Logic.St
|
||||||
}
|
}
|
||||||
|
|
||||||
mkDemux :: STM Demux
|
mkDemux :: STM Demux
|
||||||
mkDemux = Demux <$> newTVar [] <*> newTVar Logic.init
|
mkDemux = Demux <$> newTVar mempty <*> newTVar Logic.init
|
||||||
|
|
||||||
addDemux :: Client -> Demux -> STM ()
|
addDemux :: Client -> Demux -> STM ()
|
||||||
addDemux conn Demux{..} = do
|
addDemux conn Demux{..} = do
|
||||||
modifyTVar' dConns (conn:)
|
modifyTVar' dConns (ksInsert conn)
|
||||||
stash <- readTVar dStash
|
stash <- readTVar dStash
|
||||||
Term.give conn (Logic.toTermEv <$> Logic.drawState stash)
|
Term.give conn (Logic.toTermEv <$> Logic.drawState stash)
|
||||||
|
|
||||||
@ -44,9 +67,26 @@ dGive :: Demux -> [Term.Ev] -> STM ()
|
|||||||
dGive Demux{..} evs = do
|
dGive Demux{..} evs = do
|
||||||
modifyTVar' dStash (force $ steps evs)
|
modifyTVar' dStash (force $ steps evs)
|
||||||
conns <- readTVar dConns
|
conns <- readTVar dConns
|
||||||
for_ conns $ \c -> Term.give c evs
|
for_ (_ksTable conns) $ \c -> Term.give c evs
|
||||||
|
|
||||||
dTake :: Demux -> STM Belt
|
{-
|
||||||
|
Returns Nothing if any connected client disconnected. A `Demux`
|
||||||
|
terminal lives forever, so you can continue to call this after it
|
||||||
|
returns `Nothing`.
|
||||||
|
|
||||||
|
If there are no attached clients, this will not return until one
|
||||||
|
is attached.
|
||||||
|
-}
|
||||||
|
dTake :: Demux -> STM (Maybe Belt)
|
||||||
dTake Demux{..} = do
|
dTake Demux{..} = do
|
||||||
conns <- readTVar dConns
|
conns <- readTVar dConns
|
||||||
asum (Term.take <$> conns)
|
waitForBelt conns >>= \case
|
||||||
|
(_, Just b ) -> pure (Just b)
|
||||||
|
(k, Nothing) -> do writeTVar dConns (ksDelete k conns)
|
||||||
|
pure Nothing
|
||||||
|
where
|
||||||
|
waitForBelt :: KeyedSet Client -> STM (Int, Maybe Belt)
|
||||||
|
waitForBelt ks = asum
|
||||||
|
$ fmap (\(k,c) -> (k,) <$> Term.take c)
|
||||||
|
$ mapToList
|
||||||
|
$ _ksTable ks
|
||||||
|
Loading…
Reference in New Issue
Block a user