module FRP.Sodium.IO where import FRP.Sodium.Context import FRP.Sodium.Internal import Control.Concurrent (forkIO) -- | Execute the specified IO operation asynchronously on a separate thread, and -- signal the output event in a new transaction upon its completion. -- -- Caveat: Where 'switch' or 'switchE' is used, when some reactive logic has been -- switched away, we rely on garbage collection to actually disconnect this logic -- from any input it may be listening to. With normal Sodium code, everything is -- pure, so before garbage collection happens, the worst we will get is some wasted -- CPU cycles. If you are using 'executeAsyncIO'/'executeSyncIO' inside a 'switch' -- or 'switchE', however, it is possible that logic that has been switched away -- hasn't been garbage collected yet. This logic /could/ still run, and if it has -- observable effects, you could see it running after it is supposed to have been -- switched out. One way to avoid this is to pipe the source event for IO out of the -- switch, run the 'executeAsyncIO'/'executeSyncIO' outside the switch, and pipe its -- output back into the switch contents. executeAsyncIO :: Event Plain (IO a) -> Event Plain a executeAsyncIO ev = Event gl cacheRef (dep ev) where cacheRef = unsafeNewIORef Nothing ev gl = do (l, push, nodeRef) <- ioReactive newEventImpl unlistener <- later $ linkedListen ev (Just nodeRef) False $ \action -> do ioReactive $ do _ <- forkIO $ sync . push =<< action return () addCleanup_Listen unlistener l -- | Execute the specified IO operation synchronously and fire the output event -- in the same transaction. -- -- Caveat: See 'executeAsyncIO'. executeSyncIO :: Event Plain (IO a) -> Event Plain a executeSyncIO ev = Event gl cacheRef (dep ev) where cacheRef = unsafeNewIORef Nothing ev gl = do (l, push, nodeRef) <- ioReactive newEventImpl unlistener <- later $ linkedListen ev (Just nodeRef) False $ \action -> do push =<< ioReactive action addCleanup_Listen unlistener l