YarnSpinnerTool / YarnSpinner-Rust

The friendly tool for writing game dialogue in Rust
https://janhohenheim.itch.io/yarnspinner-rust-demo
Apache License 2.0
177 stars 15 forks source link

web assembly module target (wasm interface) to replace bondage.js #213

Open blurymind opened 2 months ago

blurymind commented 2 months ago

The current (classic) editor for yarn files (https://github.com/blurymind/YarnClassic) uses bondagejs (https://github.com/hylyh/bondage.js) which is an alternative to yarn spinner. It is unfortunately not a very active project, which doesnt support yarn spec version 2.

I wonder if I can replace it with the rust port of yarn spinner - running as a web module. But in order for that to work it needs an interface to be used as a web module. It needs to take input data (string - the actual file reading will be done by the web browser), it needs to receive commands to advance dialog or select options, it needs to trigger browser events or callback functions with the output - so that is then displayed on the website or web app. The goal here is to be able to compile to a minimal size target no dependencies web module that can be used with any html5 game engine.

I wonder how much would it increase the bundle size of the app and if an alternative is to just create a bevy player instead of a full on wasm interface and just iframe it in the app. Thats not ideal of course

I also created the yarn wrapper in https://github.com/4ian/GDevelop https://wiki.gdevelop.io/gdevelop5/all-features/dialogue-tree/ that also currently uses bondagejs and as a result yarn syntax support in gdevelop is not great. It's another project that can benefit from this and I can make a PR to if its doable

janhohenheim commented 2 months ago

Sounds like a great idea! I'd welcome any PRs that would help with this :)

blurymind commented 2 months ago

should it be a PR to this repository or should it be another repository which uses this module as a dependency?

I've been thinking of giving it a try by exposing things via wasm_bindgen, with the output going to a pkg folder.

Guess it will be a lib.rs file and a new output folder? It would add a bunch of new dependencies for sure

janhohenheim commented 2 months ago

Since this would be useful to anyone using Yarn Spinner in the web, I'm fine with adding a new crate right here :)

blurymind commented 2 months ago

Ok I will give this a stab in the weekend. Do you have an example where using its api, you initialise it from the raw string data of the yarn file? Since its a web module, we have no file access, that needs to be done by the browser instead.

Would be helpful to also get the variable state and write to it - exposing that is needed for game persistence (load/save states)

guessing I can just do this? YarnSpinnerPlugin::with_yarn_source("yarn data here"),

janhohenheim commented 2 months ago

@blurymind I thought a bit about it. Since Bevy can run completely headless, you don't need an iframe at all to run the Bevy app. Simply communicate with it over wasm-bindgen exposed functions that e.g. trigger an observe on Bevy's side. I think that's the simplest solution. I have not used wasm-bindgen in ages, so you know more about that than I do. But let me know if you need help with any Bevy-related things :)

Do you have an example where using its api, you initialise it from the raw string data of the yarn file?

No, but it should be as you guessed:

let content = "
title: Foo
---
Narrator: Hello world!
===
"
let file = YarnFile::new("my_file.yarn", content); 
let plugin = YarnSpinnerPlugin::with_yarn_sources([
   YarnFileSource::InMemory(file)
]);

Note that you can also initialize the plugin with deferred to start the app without knowing your yarn files yet. You can then send a LoadYarnProjectEvent at runtime when the content is ready.

Would be helpful to also get the variable state and write to it - exposing that is needed for game persistence (load/save states)

DialogueRunner::variable_storage and its _mut variant do exactly that :)

blurymind commented 2 months ago

thank you for the guidance. To be fair I am still learning rust, so it might take me some time to get something going, but i really want this feature for my other projects.

One thing that is a concern is pulling bevy stuff with the library. In my case I want the tiniest possible web module size - so if it pulls the bevy stuff and end up being 70+ mb, it probably wont be accepted in gdevelop.

The ideal output really is to bundle only whats needed to parse the yarn file and get an instance that outputs text, options or commands depending on how you interact with it. Basically the tiniest size possible :)

I am guessing this is what i want https://github.com/YarnSpinnerTool/YarnSpinner-Rust/blob/main/crates/yarnspinner/src/lib.rs the comment says that it can be used standalone, so should be my target to expose it?

I sort of wonder if I still need some sort of a bridge class on the rust side or the js side . Surely wont be as simple as slapping a bunch of annotations

janhohenheim commented 2 months ago

@blurymind when compiling for size, using wasm-opt and gzipping the result, Bevy is around 5 MiB big. But you can certainly use the non-Bevy version standalone, yes :) There is a demo of a TUI written with just that. I don't know whether we can expose the structs directly or we need to wrap them first. If you can annotate them and they're usable that way in JS, I'm happy to merge that.

blurymind commented 2 months ago

@janhohenheim this is the demo right? https://github.com/YarnSpinnerTool/YarnSpinner-Rust/blob/main/examples%2Fyarnspinner_without_bevy%2Fsrc%2Flib.rs

seems like a better starting point for my use case. Just hope i dont run into errors because of some dependency not being supported by wasm

janhohenheim commented 2 months ago

I have a running Wasm demo of the Yarn Spinner for Bevy code at https://janhohenheim.itch.io/yarnspinner-rust-demo, which is a superset of the Yarn Spinner for Rust standalone code. This means I can confirm that all dependencies support Wasm :)

blurymind commented 2 months ago

I am actually getting this error Error: error: extraneous input '\n ' expecting {ID, '#'} I think its because the content string has new lines?

this made it work

let content = "title: Foo
---
Narrator: Hello world!
===
".to_string()

yay! I can load the file from a string now :D progress lol

I added this thing to compilers.rs to do it with the hello_world demo

    /// //////////////////////
    pub fn try_read_from_content(&mut self, file_name: String, file_content: &String) -> std::io::Result<&mut Self> {
        self.files.push(File {
            file_name,
            source: file_content.to_string(),
        });
        Ok(self)
    }
    pub fn read_content(&mut self, file_name: String, file_content: &String) -> &mut Self {
        self.try_read_from_content(file_name, &file_content).unwrap()
    }
    ///////////////////////////////
blurymind commented 2 months ago

I am now trying to make a version of the hello_world demo that is a wasm module. I had a successful test of this on a fresh project here https://github.com/blurymind/wasm-module

[lib]
crate-type=["cdylib"]

and run 
wasm-pack build --target web

attempting the same on this one doesnt quite work. The compilation fails.

I think I am not structuring it right and setting build targets at the correct level. I need to learn more about how these cargo.toml files are configured before attempting to fit my basic wasm test with this project :)

I am not sure what folder my module should live in and if it needs to be added to an existing cargo.toml file as a target - or if a new one needs to be made. Just getting it to compile is now my target

janhohenheim commented 2 months ago

@blurymind I can take look over the next days. If you want something easier to setup for now, Bevy runs on Wasm out of the box, so you could tinker with that a bit to get your feet wet :) See the demo project in the repo.

blurymind commented 2 months ago

Thank you :bow: sorry I have a long way to go with rust. Would be amazing to give yarn classic a proper support for yarn spinner. I've wanted to do that for years now

janhohenheim commented 2 months ago

Alright, I've got some more time now. @blurymind, mind telling me what you're currently stuck with?

blurymind commented 1 month ago

I guess my general question is how to even begin with getting this to compile with my modifications to expose anything to wasm? Should I create another module folder somewhere and should i use wasm-bindgen with wasm-pack build --target web ? try to make something that replicates what the cli example does, but as a library? When I tried to do it directly on the cli example, I ran into compilation errors with dependencies not having web target