-
Notifications
You must be signed in to change notification settings - Fork 6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Higher-order state support #117
Comments
Do you need to completely change the logic, or just change the number of elements ? If it is the latter you can try to use resampleListPar . {- | Create as many cells as the input list is long and execute them in parallel
(in the sense that each one has a separate state). At each tick the list with
the different states grows or shrinks depending on the size of the input list.
Similar to Yampa's [parC](https://hackage.haskell.org/package/Yampa-0.13.3/docs/FRP-Yampa-Switches.html#v:parC).
-}
resampleListPar :: Monad m => Cell m a b -> Cell m [a] [b] |
See also #89 . |
Thank you for the reply! That's interesting, I remember having issues with using With Also, I see that the |
I think what I need in order to create, destroy, move, etc actors in my game are hot-reloadable lists of I suspect that the only way to do that is to make Thoughts? |
Maybe The idea is roughly that when you have data Pair a = Pair a a
runPair :: Pair (Cell m a b) -> Cell m a (Pair b)
runPair (Pair (Cell state1 step1) (Cell state2 step2)) = Cell
{ cellState = Pair state1 state2
, cellStep = \(Pair s1 s2) a -> Pair <$> step1 s1 <*> step2 s2
} Similarly, I'm sure you can write a class Functor col => PSwitch col where
pSwitch :: (forall sf . (a -> col sf -> col (b, sf)))
-> col (Cell m b c)
-> Cell m (a, col c) (Event d)
-> (col (Cell m b c) -> d -> Cell m a (col c))
-> Cell m a (col c) And we can write down generic instances for GHC Generics, or SoP. Then we can derive instances for many functors.
True, but you might be able to get away with a "finite traversable". If you know at compile time that you only need 1000 entities, you could use Template Haskell to create a 1000-tuple and derive |
Yes, that is also a possibility. You will not quite be able to write down data DefunCell m step a b = forall s . (Data s, Data (step m a b)) => DefunCell
{ dcState :: s
, dcStep :: step m (s, a) (s, b)
}
class Fun step where
run :: step m a b -> a -> m b
runDefun :: Fun step => DefunCell m step a b -> Cell m a b You would need to build up a lot of infrastructure like |
Yet another idea might apply to your special case of many similar entities. If you know the state types of them, and they are all the same, then the situation is in principle much easier. I haven't thought about this in detail, but if you make the state type explicit, you might be able to define data ExplicitCell s m a b = ExplicitCell
{ ecState :: s
, ecStep :: s -> a -> m (b, s)
}
implicit :: Data s => ExplicitCell s m a b -> Cell m a b For your |
I believe so, the state is separate for each element in the array.
I belive the state will be carried over with hot-reload. [] is only used when initializing, on reloads the previous state is saved and reloaded. |
I was able to make a GUI with dynamic number of buttons, where the number of buttons could be increased or decreased by another set of buttons, using forever, throw and resampleListPar . The code is not particularly beautiful but it works... This is indeed a limitation of eolc. |
Thank you so much for the detailed reply, I appreciate it! I will have a look soon. |
So looking at my approach again, it was essentialy the following. I used as switching function this one: -- | foreverE uses ReaderT e which in most cases requires lifting cells into the reader monad.
-- If the e value doesn't need to be sent deeply down into other cells, then it is easier to
-- get the e value as an input instead of via ask.
foreverE' ::
(Monad m, Data e) =>
e ->
Cell (ExceptT e m) (e, a) b ->
Cell m a b
foreverE' e cell = foreverE e (readerToInput cell)
where
readerToInput cell = proc a -> do
e <- constM ask -< ()
liftCell cell -< (e, a) It is just a convenience to not have to do call ask explicitely. The value -- | Throw an exception on an event.
throwEC :: (Monad m, Traversable f) => Cell (ExceptT e m) (f e) ()
throwEC = traverse' throwC >>> constantly ()
-- | Throw an exception on an event, but only on the next tick.
-- Usefull when used with 'foreverE'.
dthrowEC :: (Monad m, Data e, Data (f e), Traversable f, Monoid (f e)) => Cell (ExceptT e m) (f e) ()
dthrowEC = delay mempty >>> traverse' throwC >>> constantly () The functions above require the stuff that is not in eolc yet, but in this PR which adds a Also, I can confirm the hot-reloading works with this approach. I can change the code, reload and keep the current state of the rows of buttons. |
Actually, after studying my code again, and looking at it with fresh eyes, I refactored it and realized that I don't event need any exceptions at all. So I don't need demoDyn3 :: Cell (HandlingStateT IO) () ()
demoDyn3 = loopPrintExceptions $
withMainLoopC $ feedback
(([],[]) :: ([Maybe ()],[Maybe ()]))
( proc ((), (prevIncEvents, previousRemoveEvents)) -> do
-- widgets
buttonAdd <- newC Button -< [#label := "Add item"]
labelNum <- newC Label -< []
-- logic
addItemE <- onEC #clicked -< buttonAdd
entryModelLabel <- newC Label -< [#label := "This simulates a change of the model from outside the GUI"]
entryModel <- newC Entry -< [#text := tshow ([1, 2, 3]::[Int])]
activateE <- onEC #activate -< entryModel
textB <- getC #text -< entryModel
simulateChangeModel <- arr (\x -> x >>= (readMaybe . T.unpack :: Text -> Maybe [Int])) -< textB <$ activateE
-- resampleListPar creates as many 'singleLine' cells as needed according to the list 'counters'
let
deleteAt :: [Int] -> [Int] -> [Int]
deleteAt (idx : idxs) xs = case splitAt idx xs of
(lft, _ : rgt) -> deleteAt idxs (lft ++ rgt)
_ -> error "splitAt demoDyn1"
deleteAt [] xs = xs
getList :: [a] -> Maybe [a]
getList [] = Nothing
getList xs = Just xs
counters <- accumHold [1,2,3] -<
((\counters -> counters ++ [0]) <$ addItemE) `lMerge`
(deleteAt <$> getList (catMaybes (zipWith (<$) [(0 :: Int) ..] previousRemoveEvents))) `lMerge`
((\counters -> zipWith (\n e -> if isJust e then n + 1 else n) counters prevIncEvents) <$ getList (catMaybes prevIncEvents)) `lMerge`
(const <$> simulateChangeModel)
(boxes, incEvents, removeEvents) <- arr unzip3 <<< resampleListPar singleLine -< counters
labelList <- newC Label -< [#label := ("Current model value: " <> tshow counters)]
-- layout
box <-
boxCreatePackC
-<
( [#orientation := OrientationVertical, #margin := 10],
(,False,False,0) <$> [toW buttonAdd, toW labelNum, toW entryModelLabel, toW entryModel] ++ (toW <$> boxes) ++ [toW labelList]
)
newWindowC -< ([#title := "Dynamic GUI"], box)
returnA -< ((), (incEvents, removeEvents))
)
where
singleLine :: Cell (ExceptT EOLCGtkError (HandlingStateT IO)) Int (Box, Maybe (), Maybe ())
singleLine = proc num -> do
buttonRemove <- newC Button -< [#label := "Remove"]
buttonCount <- newC Button -< [#label := "Increment "]
countE <- onEC #clicked -< buttonCount
removeE <- onEC #clicked -< buttonRemove
entry <- newC Entry -< [#halign := AlignFill, #expand := True, #text := tshow num]
box <-
boxCreatePackC
-<
( [#orientation := OrientationHorizontal],
(,False,False,0) <$> [toW buttonRemove, toW buttonCount, toW entry]
)
returnA -< (box, countE, removeE) |
Wow thank you! That looks very interesting! I have yet to read everything here, but do you have a runnable project using that snippet that's publicly available? |
The essence-of-live-coding-gi-gtk library is not public yet, I'm still cleaning it up, but hope to make it available soon. |
Hello!
Essence-of-live-coding doesn't support higher order state, as stated in:
essence-of-live-coding/essence-of-live-coding/src/LiveCoding/Coalgebra.lhs
Line 88 in 279f42a
This means that higher order arrow constructs such as
pSwitch
aren't possible to implement with state remaining intact between reloads.Unfortunately, for most games, one needs dynamically many entities, which I believe requires use of higher order arrows.
Using essence-of-live-coding to develop games would be amazing. Currently, my engine architecture makes use of
essence-of-live-coding
so that GPU initialization and windows can be kept between reloads, but the state simulation of my game has to rely on a library supporting higher order state, such as Dunai. At the moment, I record all inputs and then replay them in sequence to get a pseudo live coding environment, but the process of replaying past state quickly starts to take too long.Is there any way around this limitation at all?
Thank you!
Sebastian
The text was updated successfully, but these errors were encountered: