nesbox / TIC-80

TIC-80 is a fantasy computer for making, playing and sharing tiny games.
https://tic80.com
MIT License
4.91k stars 474 forks source link

Compiled Languages support / WASM language bindings #1784

Open joshgoebel opened 2 years ago

joshgoebel commented 2 years ago

Creating a master topic to link to from our wiki/documentation for those wishing to contribute language templates/libraries.

We should be able to support all the same languages as the WASM-4 project. The list:

Essentially any language that compiles to WASM and allows you to configure the memory layout is a potential.

What is needed? Port over the build/template scripts from the WASM-4 project templates and update for TIC-80. These templates are typically well thought out and well maintained so they are a great starting point.

What is the core API?

Reference:

The ZIG API definition is pretty easy to read:

It follows VERY closely to the native C API which you can find in:

It's often quite a bit lower level than the API docs on the wiki, so don't rely on those TOO much.

ukrustacean commented 1 year ago

Oh yeah, forgot the memory related functions. In that case I'll just finish off the Rust bindings using opt-level = 0. The size penalty isn't just a one time cost, but it's also not too bad as far as I can tell. Every bit of code does end up larger than it would when compiled with opt-level = "s", but wasm-opt can shrink the build a decent amount.

Relying on this to access 0x00000 is probably not right, since that's still undefined behaviour, but I imagine it's highly unlikely to ever change. Plus, it really does seem to be the only remaining option, since even opt-level = 1 can optimise away null pointers dereferences, and that seems to be far too deeply in ingrained in Rust to change.

@soxfox42 I have found how to deal with this issue. Since Rust 1.66 we have stable std::hint::black_box which is used to prevent optimization of certain code block (mostly used for benchmarking). This code works for me even with opt-level = "z":

#[export_name = "TIC"]
pub fn tic() {
    unsafe {
        let nullptr = std::hint::black_box(0 as *mut u8);
        // Same as `tic80::poke4(0, 12)`
        *nullptr = 12;
    };
}

Although it is stated in Rust doc that this function should not be relied upon to control critical program behavior, I think it is still better than having no optimizations at all. I would make a PR but I do not know if this kind of hack is acceptable.

joshgoebel commented 1 year ago

Which parts of the API are we talking about using this hack, exactly?

soxfox42 commented 1 year ago

I think I found another way a while ago and never got around to updating the docs. I believe std::ptr::write_volatile (which is basically designed for MMIO), while still technically undefined for address 0, is actually able to successfully write there even with optimisations on. It's been a while since I tested that though, so I might do some tests and open a PR to mention it in the Rust README.

soxfox42 commented 1 year ago

Which parts of the API are we talking about using this hack, exactly?

@joshgoebel This is just for directly writing to memory from the Rust code, like *((char *) 0) = 42 in C. It doesn't affect the API.

RuiNtD commented 1 year ago

Any chance we can get AssemblyScript bindings, or at least have it added to the list in the first post?

https://www.assemblyscript.org/ https://github.com/aduros/wasm4/tree/main/cli/assets/templates/assemblyscript

joshgoebel commented 1 year ago

Done. These bindings are all community provided... if you want to contribute...

nefrace commented 6 months ago

I've recently made one game with Odin lang and TIC-80 for game jam and have made some kind of bindings for that purpose. It's not very clean and does not cover 100% of TIC-80 API and have some extra methods that I've found convenient but if I'll have enough time I can try to make a fully covered bindings and project template from that.

That language really deserves some attention in gamedev space in my opinion.