2

i have already have a bevy app that run in the browser.

what i want to do is having some function in the js/ts side that can create or destory a entity in the bevy world, can this be possible? I have try to let app=App::new();, then bind a function to run app app.run();, and bind a function to override resource app.insert_resource(...);. but when i call the function to override resource after run app, it show error with message: recursive use of an object detected which would lead to unsafe aliasing in rust. enter image description here


thanks for @kmdreko 's advice, i try to use Arc to update resrouce, but it seems having another problem before this,the problem is after i init the bevy app, the rest code will never reach, there is my code:

<script type="module">
    import init, {BevyApp} from '../pkg/wasm_bevy_demo.js';

    init().then(() => {
        // new() function create and run a bevy app, and return a Arc<Mutex> in BevyApp{}
        const bevyCode = BevyApp.new();
        // this log info never show in the console
        console.log("reach after run bevy app");
        bevyCode.update_scroll_rate(10, 10);
    })
</script>
chantey
  • 4,252
  • 1
  • 35
  • 40
  • 2
    Just a guess, but you could probably try modifying the existing resource instead of using `insert_resource` to override it. You'd probably have to use shared mutability though (`Arc` or `Rc`). – kmdreko Oct 26 '22 at 18:48
  • Re your edit, note that `App.Run()` is blocking, see my answer for calling update manually. – chantey Feb 26 '23 at 04:33

2 Answers2

0

Let's look at the implementation of run() function for Bevy, when run() is executed, the app you created before has been replaced with empty():

pub fn run(&mut self) {
    // ...
    let mut app = std::mem::replace(self, App::empty());
    let runner = std::mem::replace(&mut app.runner, Box::new(run_once));
    
    (runner)(app);
}

So, cannot update bevy app state or resource outside the bevy system.

But, if you still want to update the bevy app more freely, you need to implement your own runner. That's what I did in bevy-in-app.

Jinlei Li
  • 240
  • 6
0

Yes absolutely! A prerequisite is some understanding of Shared-State Concurrency.

As you noted we need to tap into some of the capabilities of wasm-bindgen, specifically its ability to create a stateful struct, containing an Arc<Mutex<App>>.

Example

  • In this case the SimplePlugin just creates a camera and cube with a Speed component.
  • We are manually calling Update because App.Run() is blocking and consumes the app. I'm not sure if doing this breaks some winit capabilities.
fn main() {}

#[wasm_bindgen]
pub struct Runner {
    app: Arc<Mutex<App>>
}

#[wasm_bindgen]
impl Runner {
    #[wasm_bindgen(constructor)]
    pub fn new() -> Runner {
        core::set_panic_hook();
        let mut app = App::new();
        app.add_plugins(DefaultPlugins)
            .add_plugin(SimplePlugin);

        let app_arc = Arc::new(Mutex::new(app));
        let app_update = Arc::clone(&app_arc);
        let update = Closure::<dyn FnMut()>::new(move || {
            app_update.lock().unwrap().update();
        });

        web_sys::window()
            .unwrap()
            .set_interval_with_callback_and_timeout_and_arguments_0(
                update.as_ref().unchecked_ref(),
                16,
            );
        update.forget(); //memory leak, use carefully!

        Runner {
            app: Arc::clone(&app_arc)
        }
    }
    pub fn set_speed(&mut self, speed: f32) {
        self.app.lock().unwrap().insert_resource(Speed::new(speed));
    }
}

Now we can call set_speed from html:


<script type="module">
    import init, { Runner } from './MY_FILE.js'
    init().then(() => {
        var runner = new Runner()
        setInterval(() => {
            runner.set_speed(performance.now() * 0.0001)
        }, 10);
    })
</script>

chantey
  • 4,252
  • 1
  • 35
  • 40