Best Practices for Communicating Between Bevy ECS and JavaScript: Events and Global Mutex? #10172
Replies: 3 comments 3 replies
|
i've been working on this same problem. I haven't found a better way to communicate from JavaScript to Rust, but a bevy system can call a JS function just fine. here's some code from my actual project (permalink) // with the way vite-plugin-wasm-pack works, modules are relative to the js project root (i.e., next to package.json)
#[wasm_bindgen(raw_module = "./src/vtt/renderer_interface")]
extern "C" {
pub fn get_ships() -> Option<Vec<ShipObject>>;
pub fn get_ship(uuid: String) -> Option<ShipObject>;
pub fn update_primary_selection(uuid: String);
}
/// handle clicks on ships and set their `selected` field
pub fn handle_ship_click(
//
mut click: EventReader<GridClickInside>,
mut ships: Query<(&mut Ship, &GridTransform)>,
kb: Res<Input<KeyCode>>,
) {
if kb.pressed(KeyCode::Q) {
// clicking while holding Q
// another system handles burn-changing
return;
}
for event in click.iter() {
for (mut ship, trans) in ships.iter_mut() {
if trans.hex == event.location {
info!("clicked on ship at {:?}", event);
ship.selected = !ship.selected;
if ship.selected {
update_primary_selection(ship.uuid.to_string());
}
break;
}
}
}
} |
|
If your use case is only Bevy -> JS, than you could possibly get away with just sending a simple string over. In my case all I want is to save some data to local storage before unload. I decided to just buffer the game state every second and write that on In js: // Buffer game state
let bufferedGameState = {};
window.buffer_game_state = function(json) {
try {
bufferedGameState = JSON.parse(json);
} catch (e) {
console.error("Invalid state JSON:", e);
}
};
// Save game before unloading the page
window.addEventListener('beforeunload', () => {
for (const [key, value] of Object.entries(bufferedGameState)) {
localStorage.setItem(key, value);
}
});and in Bevy I run the following (scheduled to run once a second in my case): #[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = window)]
fn buffer_game_state(json: &str);
}
#[cfg(target_arch = "wasm32")]
fn sync_state_to_js(
core: Res<ProgressionCore>,
map_data: Res<MapData>,
q_player: Query<&Transform, With<Player>>,
) {
let Ok(player_transform) = q_player.single() else {
return;
};
let data = package_save_data(&core, &map_data, &player_transform);
let save_data: HashMap<&&str, &String> = WASM_KEYS.iter().zip(data.iter()).collect();
buffer_game_state(
&serde_json::to_string(&save_data).expect("failed to parse hashmap save data to json"),
);
} |
|
I don't know how idiomatic this is (especially the read-write locks, maybe that can be improved without unsafe code), but here is my bi-directional interface using static GUMBALLS_AVAILABLE_EVENT_SENDER: RwLock<Option<ChannelSender<GumballsAvailable>>> = RwLock::new(None);
static GUMBALL_DROP_EVENT_SENDER: RwLock<Option<ChannelSender<GumballDrop>>> = RwLock::new(None);
#[wasm_bindgen]
extern "C" {
/// relays information about which ball fell
pub fn dropped(id: u32);
}
#[derive(Event)]
pub struct GumballsAvailable(Vec<Ball>);
#[derive(Event)]
pub struct GumballDrop;
pub fn js_binding_plugin(app: &mut App) {
let mut gumballs_available_sender = GUMBALLS_AVAILABLE_EVENT_SENDER.write().unwrap();
let mut gumball_drop_sender = GUMBALL_DROP_EVENT_SENDER.write().unwrap();
*gumballs_available_sender = Some(app.add_channel_trigger::<GumballsAvailable>());
*gumball_drop_sender = Some(app.add_channel_trigger::<GumballDrop>());
}
#[wasm_bindgen]
pub fn gumballs_available(raw_gumballs: JsValue) -> Result<(), JsValue> {
let gumballs: Vec<Ball> = serde_wasm_bindgen::from_value(raw_gumballs)?;
GUMBALLS_AVAILABLE_EVENT_SENDER.read()
.unwrap()
.as_ref()
.unwrap()
.send(GumballsAvailable(gumballs));
Ok(())
}
#[wasm_bindgen]
pub fn drop_gumball() {
GUMBALL_DROP_EVENT_SENDER.read()
.unwrap()
.as_ref()
.unwrap()
.send(GumballDrop);
} |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
👋 Hello everyone,
I'm exploring how to efficiently communicate between the
Bevy ECSand the outside world (Javascriptin this case).I'm interested in passing events both ways -> getting events from the
Javascriptlayer into theECS,and also emitting events from the
ECSto theJavascriptlayer.In my current implementation I use a global
Mutex<Vec<JsEvent>>, like so:And then draining this queue in a
wasm-boundfunction.But I was wondering whether there is a "better" way
or what the recommended way to make such communication happen is?
Thanks for any insights :)
All reactions