Luke Boswell and I are excited to announce a new platform for making games with Roc!
The platform creates games targeting the WASM-4 fantasy console (Thanks @Hannes for the idea).
roc-wasm4 wraps all the pieces necessary to fully utilize wasm4 from roc.
Current examples include:
Please try it out and let us know what you think!
All information needed to get started can be found in the README
Awesome!!
Thanks Brendan and Luke for doing this! I was making such slow progress on my version of this platform because I was learning Zig and manual memory management at the same time as trying to build the platform :sweat_smile:
Very excited to try making some games in Roc! :tada:
Wow :star_struck:
Amazing
This is so cool!
is it cool if I tweet about this from the roc_lang account? I'd like more people to know about it! :smiley:
We were planning to publish it more broadly after some people here play with it and we confirm there aren't any major issues, but probably fine to just share it.
either way - I'm also ok waiting!
@Luke Boswell I'm good either way, what's your pick?
I think sharing is good, I'm hoping we dont have any major issues preventing people from using it... :sweat_smile: but I guess we can just fix those.
Cool note for anyone interested:
You can get hot reloading for any changes to the update function by setting up two terminals:
find . -name "*.roc" | entr -cc zig build -Dapp=examples/snake.roc
w4 run zig-out/lib/cart.wasm --hot
Note, if you change the Model type, it will probably just crash due to trying to read the memory in an invalid way. But otherwise, it should mostly work. If it crashes, just press R
for a fresh restart.
I've also posted this to reddit on the roc_lang and functionalprogramming communities :smiley:
Extra note for anyone who tries roc-wasm4. Make sure to update your version of Roc. I had to add a few patches to Roc for this platform.
So awesome! Great example to learn wasm platform. Roc as the ultimate functional embedded DSL :joy:
This is really cool!
However, I wonder if this approach is leaving a lot on the table performance-wise?
I looked at the code and it seems that the platform calls the update function each frame, passing in the previous model and getting the next. I guess this is like The Elm Architecture running a cmd every frame.
One of the things that excites me about Roc is that (in theory) it can optimize this pattern away , taking the frame update code from "return a new state" to "update the state in-place".
This would give us the classic imperative game loop, but written functionally:
while(true) {
update();
draw();
}
But this optimization cannot exist across the platform boundary?
Very new to Roc so curious what people think.
@Brendan Hansknecht and I discussed briefly a few different ways to do this, he will be much more capable of answering this question than I. But I think we decided to implement the simple and easy thing first to have something working, and now that we have that we can explore further ways to improve performance and developer experience.
taking the frame update code from "return a new state" to "update the state in-place".
Roc can't have any state. So this is not possible in Roc. Of course you could use an effect to save state to the host, but that is just this model with extra steps.
In the future, if roc adds something like #ideas > Stored
ability, it would then become possible to keep all of the state on the roc side. Though it would be saved and loaded through tasks. That said, roc understands the types fully, so probably could optimize that use case a bit more (and should avoid all boxing hopefully).
passing in the previous model and getting the next
This is just a single pointer being passed back and forth. So theoretically no real cost.
However, I wonder if this approach is leaving a lot on the table performance-wise?
For wasm4 were models are small, and allocations are a mostly linear array, probably not that much performance lost. For more complex use cases, probably a lot of performance lost.
That said, all of the real loss is in our current handling of Box
. As it stands today, each iteration will take the boxed model, copy it to the stack, free the box, build a new model on the stack, allocate a new box, copy the new model into the box.
This has 2 core inefficiencies:
What we really want is:
This would be the same flow as if we had just passed around a reference to a large struct and then updated it at the end. If we had a smarter compiler and a proper Box.update
function, most of this loss should be mitigated.
All that said, I think it should be possible to remove the boxing and allocate a single storage space for the model (actually may take 2 to be safe). Storage would still live on the host, but it could be in an alloca on the stack or allocated on the heap just once. I'll look into doing that to safe from a bit of the extra cost of box. nvm, that isn't doable cause we don't know how the struct will be passed in. That depends on the exact struct implementation details. So have to box currently.
Brendan Hansknecht said:
taking the frame update code from "return a new state" to "update the state in-place".
Roc can't have any state. So this is not possible in Roc. Of course you could use an effect to save state to the host, but that is just this model with extra steps.
hm, but we could pass the state from the host to/from Roc (in update
) and then, if the refcount is 1, Roc could update it in place, right?
yeah, that is already what is being done.
Except that Box
never updates in place currently
Could the Roc platform have two effects available:
Then you would build a game-loop Task out of those and the Model would never cross the platform boundary?
gameLoop =
\state ->
deltaTime <- getTime
nextState = update deltaTime state
_ <- sendRenderInstructions (render nextState)
gameLoop nextState
main =
gameLoop initialState
I don't know what do-notation looks like in Roc (if any?) so please forgive the syntax!
You would also need an effect to get the player input, right?
Hannes said:
You would also need an effect to get the player input, right?
Yes, and also for things like sound, but I was trying to simplify. getTime
could be replaced by getTimeAndInputs
for example, but the principle is the same.
So make roc an infinite closure.
I think that could theoretically work with other platforms, but probably not wasm4
Wasm4 games don't even control their own execution in this manner
They are split into an init and update function called by the wasm4 runtime
And it is all single threaded, so no way for something else to run while roc is running
Though maybe there is some way to split up tasks to always capture the model. Probably would require switching to an effect interpreter model.
Oh, also, wasm4 has no sense of time except a manually added frame count
I’m having so much fun playing with this platform! Bravo :clap: :big_smile:
Thanks for letting us know, please let me know if you run into any issues :smiley: I would love it to be a really smooth experience, particularly for newcomers to the language
I just opened an issue; unused defs and imports are causing builds to fail for me
@Brendan Hansknecht and I talked about that briefly, without running roc check
first zig build will gobble any warnings and errors and fail without explanation. Unfortunately roc check
will return non-zero exit code for a warning. I think we might need to raise an idea thread to discuss changing that.
It is worse than that.
roc build
will emit a non-zero exit code on any warning
So zig will see that and assume it can't continue building
Also, I don't think zig build gobbles the roc build
errors anymore. So we probably could remove roc check
now if we wanted, but given it catches extra errors, I left it in.
we need something like roc build --ignoreWarnings
for this to work
Ah gotcha
I started a related thread here https://roc.zulipchat.com/#narrow/stream/304641-ideas/topic/Changing.20exit.20codes.20for.20roc.20build.20and.20check/near/412777236
This isn't fully done, but since it is functional and I am hitting a compiler bug for the last part, thought I would post it:
@Brendan Hansknecht dope animations!
I can't claim any of them. I have a friend who loves to do pixel art and he made everything.
this is so rad!!! :star_struck::star_struck::star_struck::star_struck::star_struck:
Awesome! :bird:
Haha, there goes my first idea for a Roc game :sweat_smile: I'll have to be more original when i actually start making games. Any chance you could release the Roc sprite for others to use, @Brendan Hansknecht?
ok my high score is 2^3, I'm satisfied with that :laughing:
Screenshot-2024-01-15-at-6.50.46-PM.png
Any chance you could release the Roc sprite for others to use
For sure. They technically are already on github, but only in the code form. https://github.com/lukewilliamboswell/roc-wasm4/blob/8459de33dcf479b056432a24dd2dee0337c7dd5d/examples/rocci-bird.roc#L605-L639
Thanks, I'll extract them for whatever game I end up making :) Does your friend have a name and/or website that I could use to credit them?
If you play rocci bird, when your bird crashes it shows the arists name, Art by Luke DeVault
Full version of the game with plants, better end screen, credits, more randomness, and memory corruption fixes is uploaded now. game - source
Feel free to use any of the source code including the sprite code.
Hopefully the game is stable now and won't run out of memory or hit other perf issues. Went through a bit of a process fixing a major platform bug and cleaning up some of the code.
Oh also, if you are using the roc-wasm4
platform, please pull the new code. Otherwise your game dev experience may get exceptionally frustrating as you deal with memory corruption.
This is very cool. It looks super great, Luke did a great job with that art! :heart:
Two notes anyone interested:
Thanks for the warning part, it was a bit distracting!
I made a drum machine using roc-wasm4 and it was a lot of fun! You can try it here or see the source.
It can make grooves like this :drum:
roc-drum-machine.mov
I have not been able to get the sound to play on a phone but it should work in a normal browser
Works on my Samsung S22 :grinning_face_with_smiling_eyes: - the sound that is
Great!
Do you try to balance channels at all to avoid overwriting sounds?
No I don't balance them; I've found that in practice it works well as is. If you try to play all five sounds at once they don't all come through but that doesn't really matter for typical grooves
You can also kind of use the overwriting to your advantage sometimes to get new sounds
Fair
From my wife
Love it!
this is awesome!!! is it cool if I share this from the roc_lang twitter account?
Nice, I'm not sure how the guys in my office feel about this game :sweat_smile::musical_notes:
this is awesome!!! is it cool if I share this from the roc_lang twitter account?
Yes sounds great!
And thanks :big_smile:
Ok, I made a maze with infinite levels (2 ^ 32 if to be more accurate) with simple generative melodies per level (beware of horrible sounds!)
It's not the final version (I have a couple of ideas where to move), but you can check the current state here:
https://kirdzi.itch.io/just-a-maze?secret=H82wQhk3TOiFbu94vs46WaVS6ww
I think next week I'll clean up the source code and publish it on github
This looks nice, love the simplicity :grinning:
Are the levels seeded or are they the same on every single run?
All the same. It doesn’t touch w4 random at all but uses the level id as the seed for xorshift
That's awesome!
@Kiryl Dziamura once you have the source code published, I'd be happy to share it via the roc_lang twitter account if you're cool with that! :smiley:
sure! let me just figure out how to refactor all my mess with the help of the state monad :grinning_face_with_smiling_eyes:
A new release for roc-wasm4 with a few fixes and cleanup so that the examples work with latest main, and also using the new !
chaining syntax.
Last updated: Jul 06 2025 at 12:14 UTC