turion / rhine

Haskell Functional Reactive Programming framework with type-level clocks
http://hackage.haskell.org/package/rhine
121 stars 21 forks source link

Rhine and Terminal #161

Closed jmatsushita closed 9 months ago

jmatsushita commented 2 years ago

Hi @turion,

I'm finally spending some time poking at Rhine which I wanted to do for a while. I really love the concept and I'm bumping into some questions and issues that you probably will be able to help me with!

I'm trying to integrate the haskell-terminal library with Rhine, trying to replace the StdinClock with a clock that returns terminal events (and a subclock that filters on key events). As you'll see I'm also trying to compose a periodic clock with an IO event driven clock, as well as printing to screen. I have tried to think about the display/actuator side as a Clock, but I couldn't wrap my head around how to do this (maybe by passing state to the clock and flushing to the terminal periodically?). Anyway that's not really the problem I wanted to ask help for, but I thought I'd also mention this in passing.

The issue I'm running into when running this toy program https://gist.github.com/jmatsushita/d94f04479666979b57d99558fcf3f40d/49ac895e92fa620953086cd734cff21af371df68 is that I get the message

thread blocked indefinitely in an STM transaction

I'm a bit confused about how I should go about hoisting the TerminalT monad transformer into IO. I probably have the wrong idea running withTerminal $ runTerminalT twice, in initClock (which installs the awaitEvent handler) and in liftTerminal which tries to hoist the ClSF on the display side, but I've wrestled with it for a while now and I can't wrap my head around how to make this work.

Hoping this could be useful as an example of how to use clocks that have effects in transformers, and I realise that hoisting Rhines is a more difficult work in progress, but it doesn't seem like I should need that, but I might be wrong.

Anyway, thanks a ton for the awesome paper and library, really looking forward to get unstuck :)

Jun

jmatsushita commented 2 years ago

I should probably study this https://github.com/turion/rhine/blob/master/rhine-gloss/src/FRP/Rhine/Gloss/IO.hs :)

turion commented 2 years ago

Thanks for your kind words :) this is a great idea!

I still don't understand the terminal library well enough to understand the error exactly. What I don't understand at all: withTerminal :: (MonadIO m, MonadMask m) => (System.Terminal.Platform.LocalTerminal -> m a) -> m a expects a function, but you give it the output of runTerminalT. So you never use the value of type System.Terminal.Platform.LocalTerminal. So how do you ensure that the clock and the ClSF are on the same terminal?

Here is an idea you could try: Don't use TerminalT (and runTerminalT) at all in the definition of the Clock and the ClSF. Only use the type classes MonadTerminal and so on. Then use withTerminal and runTerminalT at the very end, i.e.:

main = withTerminal $ runTerminalT $ flow mainRhine

If this works then at least we know where the trouble lies. There might be even better solutions which we can think about afterwards.

If you want, you can also submit your code as a PR to this repo here, as a separate library rhine-terminal. Then we can iterate on it together. (I just have to warn you that I'm a meticulous reviewer ;) )

jmatsushita commented 2 years ago

If you want, you can also submit your code as a PR to this repo here, as a separate library rhine-terminal. Then we can iterate on it together. (I just have to warn you that I'm a meticulous reviewer ;) )

That sounds awesome! I look forward to it and the learning opportunity. Doing as you suggested in the latest revision of the gist works a bit better, with the periodic clock properly displaying, but I can't seem to get terminal events to flow through.

I've managed to make a simple example work with vty (in this gist) and I suspect that terminal is less compliant maybe because of threading issues?

I'll take some time to submit both if that sounds like a good idea as rhine-vty and rhine-terminal?

Thanks a lot your help :D

turion commented 2 years ago

I'll take some time to submit both if that sounds like a good idea as rhine-vty and rhine-terminal?

Yes, that's great! If vty is easier to handle, then it's probably best to start with that. They should be separate libraries then.

jmatsushita commented 2 years ago

Yes, that's great! If vty is easier to handle, then it's probably best to start with that. They should be separate libraries then.

Sounds good!

So how do you ensure that the clock and the ClSF are on the same terminal?

I think my latest revision suffers from the same problem you pointed out above. The terminalSchedule function also creates it's own terminal instead of reusing the existing one, probably leading to the events being swallowed.

I'll try using glossConcurrently as a template for a terminalConcurrently that properly passes the terminal (at first explicitly, then maybe through a ReaderT).

jmatsushita commented 2 years ago

I've got a new version kinda sorta doing what I was looking for (basically the basics of a REPL), except it's excruciatingly slow, probably because my terminalConcurrently is broken? I think it's using runTerminal at each tick, which AFAICT should just be runReaderT but maybe there's something else going on I don't understand. withTerminal seems to be where the actual work is done, and I'm running this only once at the top-level.

Not sure if I'm missing something, I wonder if the terminal API is missing some way to pass an existing terminal state (the way haskeline has queryInput), or maybe if the TerminalT constructor was exported then it would be possible to rewrap the TerminalT value and use readerSchedule concurrently? Or maybe there's something about how async and forkIO interact? Possibly something @lpeterse could help clarify?

Now that I have something that looks like what I want, however broken at this point, I'll start preparing a PR with a rhine-terminal backend, and a rhine-example that uses it, so that it can be ran from a repo instead of a gist.

turion commented 2 years ago

Feel free to open a PR :) I don't understand what the performance problem is.