Edit TLDR: Nope, Module & Package info types do not exist.
So... I'm building a code generator, that takes Godot Api information produces Roc code as the output.
import Godot
GodotToRocMapper := {}.{
to_roc : Godot.ExtensionApi -> Module
to_roc = |bar| {
bar
}
roc_module_to_str : Module -> Str
roc_module_to_str = |module| {
Str.inspect(module)
}
to_roc_str : Godot.ExtensionApi -> Str
to_roc_str = |geapi| {
roc_module_to_roc_str(geapi)
}
}
── ✗ undeclared type ───────── GodotToRocMapper.roc:6:36
The type Module is not declared in this scope.
to_roc : Godot.ExtensionApi -> Module
^^^^^^
What's the most high level ergonomic builtin that i can use here?
I could define a custom Module type... but I would prefer not to reinvent the wheel, and use the builtin types, if such a type exists.
CustomRocModule : {
name: Str,
imports: List(Str),
type_defs: Dict(Str, CustomRocType),
expressions: Dict(Str, CustomRocExpression)
}
I feel like you write the Roc code as a template using a multiline string and then fill in the gaps by generating that from the godot API.
I'm not sure what you mean by a builtin type here? This is putting out a Utf-8 string right, so Str would be all you need.
Do you have an example of the godot API and what the generated roc code might look like?
Oh I think I see what you are doing ... if I'm right you have Godot godot --dump-extension-api to dump the actual API for the specific engine you have, and then you want to interface with that?
I asked grok...
I’ll check the current Roc language and standard library for any built-in high-level type that represents a module or AST.There is no high-level builtin Module (or equivalent AST / module-representation type) available to user code.Roc’s builtins are limited to the usual core types (Str, List, Dict, Set, Result, Bool, numeric types, etc.). Anything that looks like a “module” or full AST lives only inside the compiler (the internal AST, ModuleEnv, CIR, etc.). Those are not exposed as a public, importable type you can use in ordinary Roc programs.
Most ergonomic practical options (ordered from highest-level to lowest)
- Just emit a Str (recommended for most generators)
Keep the intermediate representation as simple records / tags of your own, then turn the whole thing into source text at the end:
- This is by far the most common and ergonomic approach for code generators in Roc today.
- Define a small custom intermediate type (exactly what you already sketched)
Your CustomRocModule (or a slightly richer version of it) is the right level of abstraction. You only need the parts that matter for your generation:
I guess, perhaps just using a string template is the right approach.
I was hoping that i could do something like
module : Module
module = godot_api.to_roc()
str = module.to_str()
inspect_str = Str.inspect(module)
My intuition is that you would want to handle the godot bindings side with Zig not Roc
Luke Boswell said:
Oh I think I see what you are doing ... if I'm right you have Godot
godot --dump-extension-apito dump the actual API for the specific engine you have, and then you want to interface with that?
yup... I'll need zig host bindings, but then I'll also want a matching Roc api.
The plan is to generate everything, except for some glue functions, and godot version independent host implementation.
Do you want to support any godot engine version? or is the platform going to be pinned to one version?
For now, i've got it pinned to a specific version.
And then add additional versions, as the generator matures.
I feel like the Roc platform <-> host (effects) side would be pretty stable and you shouldn't need to codegen that.
Would you mind sharing a extension_api.json in a gist or something, I'm interested to see the shape of that
I don't have godot installed
Let's say...
godot 4.6.2 & 4.7.1
Could be a new feature... "CharacterBody3D class now has 2 more methods, and we added 4D class"
Luke Boswell said:
Would you mind sharing a
extension_api.jsonin a gist or something, I'm interested to see the shape of that
350,000 file.
I'm not saying you can't generate it... but if you want an idiomatic Roc API you might not want to match the hosted effects 1-1 in Roc, you might want the host to handle some things and provide a nicer experience for app authors.
Inside the godot editor, a comparable gdscript doesn't reference a pinned version...
class_name MyPlayerCharacter extends CharacterBody3D
I'd assume it'll just throw errors at runtime, for api missmatches.
So long term, would want to mimic the exact same thing, but perhaps we can give compile time errors/warnings for typemismatches.
Was thinking along the lines of:
Godot.CharacterBody3D # <-- this is what most app devs will use
Godot_4_6_2.CharacterBody3D # <-- if you want an explicit pinned version.
Godot_4_7_1.CharacterBody3D
Godot_4_7_1.NewCoolNode_v2
Is your goal here to learn and build something from scratch, or would you like to get to a working demo thing as fast as possible and happy to skip all the bindings part?
Another option you could consider is sitting on top of something mature like https://github.com/godot-rust/gdext (using their core crate and not the macros or anything) and then the bindings are already handled for you
Then you could focus on the Roc app facing API and the host facing API sides of the platform
Both... I've already implemented bootstrapping, a working prototype, a minimal 3d platformer game...
Now I'm looking at API completeness.
Yeah, perhaps leaning on existing gdexts is the way to go... or at least in the short term, to get something closer to production ready faster.
I assume people are going to want to use it, and they're going to want it to be robust & mature as possible etc.
Does godot have a "stable" version everyone uses? could you pin to that and it'd be ok
The rust gdext, does something similar, they only support versioned releases, and not the nightly releases.
https://godot-rust.github.io/book/toolchain/compatibility.html#rust-api-stability
Can go as granular as desired.
Anyway, for now... I'm using a specific pinned version
nix develop
# roc: Roc compiler version debug-no-git (pinned in flake.lock)
# zig: 0.16.0
# godot: 4.7.2.stable.nixpkgs.ed1daf0bf
# python: Python 3.14.7
# gh: gh version 2.100.0 (nixpkgs)
and for now... i'll just use string templates as the code / type generator, don't need a fancy Module / Package typeinfo.
And someone already made one for zig:
https://github.com/godot-zig/godot-zig/tree/master/binding_generator
Scott Campbell has marked this topic as resolved.
How is memory managed in a GDExtension?
Does the host call into roc once each frame?
The way i've implemented it... there are a few globals.
The roc stuff is initialised immediately as the first line of code in the extension when it's loaded, it's called once, and the I just keep it in memory as a global indefinately.
https://github.com/scottc/godot-roc-platform/blob/main/src/host.zig#L42
Next we give godot a "scene" init and deinit handlers...
And that's when I register classes from the roc app... still playing around with the optimal timings.
So tldr, nope, it doesn't init roc every frame... just the stuff that's needed for when it's needed... appropriate lifetimes.
We just call the _process game loop tick handler, and then from there we route it to each class type.
Simple single class example:
https://github.com/scottc/godot-roc-platform/blob/main/examples/hello_godot/main.roc
Multi class example:
https://github.com/scottc/godot-roc-platform/blob/main/examples/hello_godot_complex/main.roc
One trick I have been using is like a typed "host-owned" resource, basically a HostThing :: Box(U64).{ ... and then when I allocate that in the host I can keep track of the memory address, and when roc deallocates the Box calling roc_dealloc I can catch that and know to then release the resources the host is holding onto.
That might be helpful for making resources or opaque handles for things.
Then I can make the API such that you must have a live handle to call a particular effect because that is a method (or associated function) on that handle.
It looks like GDExtension is very OO shaped... so this might be helpful
https://github.com/lukewilliamboswell/roc-ray/blob/main/src/host_resource.zig
I'm new to manual memory management & related types, ownership and lifetimes etc.
I've always coded in high level garbage collected languages... typescript and such.
Thanks for the suggestions, I'll read into it and familiarise myself with the concepts & implementation details.
Lol, I went to fork your repo so I could try it out and I cant because it's a fork of my repo :smile:
It's fork inception
I can try unlink the fork... It's useful for pulling upstream changes, or sending pull requests.
The project is a bit awkward, because i made the project, and then dumped the platform template inside a subdirectory... so it's a git module that has independent git history.
https://github.com/scottc/godot-roc
https://github.com/scottc/godot-roc-platform
So I need to git add * && git commit -m 'foo' && git push twice, I probably need to flatten them at some point.
I was just wanting to try and setup a single handle as a demo for you
I'm actually here experimenting with some new hardware things and NixOS and your project is a convenient excuse to play with Roc at the same time :sweat_smile:
I was thinking about a nix package manager platform.
And then instead of nix expressions, one could use roc expressions instead.
Or just yeet the entire platform and start from scratch, and keep just the good parts.
flake.lock, 100% reproduceable as default, and now with amazing type inferance.
One only needs to bootstrap from bare metal... just a thin UEFI layer, as a bootloader... and 1 function call...
InstallUrl("hash", "remote-url"), or something like that.
ConvertFlakeToRocPlatform("path/flake.nix")
Start with fetching a linux kernel... or redox os microkernal.
And then can use the kernal syscalls, but won't have network device drivers yet...
So maybe need to start with minimal kernal + network driver, as the platform.
Have you seen @blu's Kai project?
Nope, ah nice... someone's already working on it.
This is delightful...
Roc gen:
roc check ./main.roc && cat extension_api.json | roc run ./main.roc > generated.roc && roc check ./generated.roc
Zig gen:
roc check ./main.roc && cat extension_api.json | roc run ./main.roc > generated.zig && zig test ./generated.zig
And then an editor open with 3 panes:
![]()
I should probably start with the zig host first.
I doubt the roc api, is going to need these details, maybe some...
But will need to see what zig host will need to expose?
Yeah just doing... GodotThingy -> Str
is simple enough, and probably better for performance.
No intermediate mapping, which is another memory allocation.
So probably twice as fast as GodotThingy -> RocThingy -> Str
Although not quite as composable... f1(f2())
Another layer of abstraction & indirection... is actually counter productive for readability... when trying to understand the entire process from start to finish it's better if it's just direct from A -> B then all concerns are immediately apparent, instead of having yet another mental model and map in back of mind.
Last updated: Sep 24 2026 at 15:59 UTC