libmach: Compile to shared library? #391
Labels
No labels
CI
all
basisu
blog
bug
build
contributor-friendly
core
correctness
deferred
dev
direct3d-headers
docs
driver-os-issue
duplicate
dxcompiler
editor
examples
experiment
feature-idea
feedback
flac
freetype
gamemode
gkurve
glfw
gpu
gpu-dawn
harfbuzz
help welcome
in-progress
infrastructure
invalid
libmach
linux-audio-headers
long-term
mach
mach.gfx
mach.math
mach.physics
mach.testing
model3d
needs-triage
object
opengl-headers
opus
os/linux
os/macos
os/wasm
os/windows
package-manager
priority
proposal
proposal-accepted
question
roadmap
slipped
stability
sysaudio
sysgpu
sysjs
validating-fix
vulkan-zig-generated
wayland-headers
website
wontfix
wrench
www
x11-headers
xcode-frameworks
zig-update
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
hexops/mach#391
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Firstly, I want to say that this is a great project, and I really appreciate the effort being put into a truly cross platform graphics toolkit. Are there any plans (or is it even possible) to compile mach into a shared library? I think this would allow for easy integration with any programming language that supports a C FFI.
I've thought about this a bit, I think it would be good to have something like this, yes.
Supporting a broad range of bindings in other languages would be an explicit non-goal (i.e., you'd be on your own, because we just don't have time to support that) but I think that having a well-defined C ABI could be a good way to enable adoption of Mach from other languages.
I also think that having an option to script Mach using WASM modules (so, whichever languages can compile to WASM and use the Mach API through that) is compelling to me.
How this would look exactly? That's a bit up in the air. Since Mach is composed of a bunch of smaller projects, and isn't one big API, I think what we would need to do is create a subproject (let's call it
libmach) which defines a C ABI, and imports all of the standard Mach libraries in order to implement it.How far we go with it, what gets supported vs. what does not.. that gets trickier. I don't know. I think ultimately, the thought here would be: is someone interested in contributing/maintaining such a library?
I'd be willing to contribute.
I made a hacky proof of concept here. If you run
zig build,cdintolibmach, then runmake run, it should run the boids example. All it took was addingsrc/exports.zigand then generating a shared library inbuild.zig. I think that exporting mach internals should be relatively easy.However, I'm not exactly sure how exactly the mach API should be exposed. From what I can tell, mach currently requires a Zig package called "app" to be linked at compile time, but I don't think this is desirable in the context of dynamic linking. Exposing the high-level API would probably require
Appto be more decoupled from mach internals. Otherwise, I don't think runtime linking would really make any sense (unless only low-level API functions are exposed?).Also, Zig currently can't automatically emit C header files (but it should be supported by stage 2), so those would have to be written by hand for the moment.
edit: oops, for any future readers, the above link is no longer accurate (thanks, Mr.
git push --force)cool!
The reason Mach requires an App is because we aim to support browser and android/ios. In these, you don't really get a main function - so the standard idea of "I will call into the library" doesn't really work. Instead, you get callbacks (basically) where your app can do things, render, etc. App reflects this constraint, I don't think we can change it.
So there are a few ways we can do this for
libmach..We could copy the ECS app, like this, and define an app that has no ECS modules at all (they'll all have to be added at runtime via
libmachAPI). In this model, when you link against libmach it would have it's ownpub fn init(engine: *ecs.World(modules)) !void {which calls your exportedmach_initcallback. Therefor, your C code must export such a function so it can call it.We could define an app that exposes
init,deinitandupdatefunctions similar to the triangle example but again, just have them directly call a C functionmach_init,mach_deinit,mach_updateand expect that those are defined by the user linkinglibmachinto their program.We could say "Truly? I don't care about iOS/Android/WebAssembly." - In this model, we could define a custom platform type for "library" mode, in which
mainis not defined for you but rather it is exported asmach_mainor similar.My advice would be that we go with both #1 and #2, naming them
libmachengineandlibmachcore. Depending on which one you link against, you choose whether it expects to find just a singleinitfunction (Mach engine apps), orinit,deinitandupdate(Mach core apps.)Then the rest of this is just sorting out how to write/expose C ABIs for Mach APIs. Shouldn't be too hard, but definitely some work to do there. Stage2 will help with headers in the future as you mentioned.
Ok, thanks for the explanation.
However, I'm a little confused. When you say "C code exporting a function", what exactly do you mean? Do you mean that a C file can define something like
extern void mach_init()and thenlibmachwill figure out the right function(s) to call through some linker magic? (I haven't been able to get this to work yet). Or do you mean having C code explicitly pass function pointer(s) tolibmachat runtime to use as callbacks?I was thinking the second approach makes more sense, since then other languages with a C FFI could load
libmachand then provide their own callback function(s) forlibmachto call in its internal core loop. That's the sort of use case I was envisioning (similar to libraries like Raylib, but you use mach's core loop instead of your own). But would this satisfy the constraints of iOS/Android/WASM?If we go with the first approach instead, then I just don't really see how C bindings would be all that useful - I thought the whole point was so that Mach could sort of be used like a library from multiple languages. Is there something that I'm missing?
Ooh, sorry, I guess my brain kind of glazed over on that part. For some reason I was thinking of languages that can export C functions as part of their FFI approach (e.g. Go can do this) but not languages that use e.g. dlopen-based FFI.
OK, so I think what we'll need to do in order to support this then is something like this:
libmach(single library)native_common.zigand renamemainin there torun_main.platform/native.zigwhich exposesmainand just callsrun_main.platform/libmach.zigwhich will expose our main C ABI. It can be used in two ways:mach_engine_set_init(takes a single argument, a function pointer like this). If you use this, you're writing a Mach engine app.mach_run- takes control from here on out, and callsnative_common.zig'srun_mainfunction.mach_core_set_init(function pointer like this)mach_core_set_deinit(like this)mach_core_set_update(like this)mach_run- takes control from here on out, and callsnative_common.zig'srun_mainfunction.In theory, I think that
libmach.zigcould maybe look something like this, except it would also need to expose thosemach_*functions above.Then inside of the
mach_runimplementation, it would look at whether you usedmach_engine_set_initormach_core_set_initand decide what to do from there:mach_core_set_init, it can just delegate to those function pointers you've set.mach_engine_set_init, then it just needs to delegate those 3 function calls to these 3 functions and call the user'sinitLet me know if any of that doesn't make sense, there might be other things I've not thought about here. We can start small, maybe with just a PR to do the file renaming + expose
mach_runwhen building a shared library?Feel free to join the Matrix chat BTW, we do a lot of collaboration/discussion like this in there: https://matrix.to/#/#hexops:matrix.org
Just to track progress:
#406
#420
#423
Why is this necessary? The name
maindoesn't mean anything in Zig, it's just whatstd/start.ziglooks for in@import("root")when you compile an executable.new approach hexops/mach#858