# bevy-wasm-api
**Repository Path**: happydpc/bevy-wasm-api
## Basic Information
- **Project Name**: bevy-wasm-api
- **Description**: No description available
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: main
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2025-03-13
- **Last Updated**: 2025-08-07
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
## Getting Started
### Installation
Install the `bevy-wasm-api` crate.
```bash
cargo add --git https://github.com/sanspointes/bevy-wasm-api
cargo add wasm-bindgen
```
Install optional dependencies to help your development
```bash
# Required if you want to return custom structs from your api
cargo add serde --features derive
# Helpful crate for generating typescript types for custom structs
cargo add tsify --features js --no-default-features
```
### Add Plugin to app
```rust
use bevy_wasm_api::BevyWasmApiPlugin;
#[wasm_bindgen]
pub fn setup_app(canvas_selector: String) {
let mut app = App::new();
app.add_plugins(BevyWasmApiPlugin).run();
}
```
### Define an Api
:warning: The first argument must be a `world: &mut World`.
```rust
#[wasm_bindgen(skip_typescript)] // Let bevy-wasm-api generate the types
struct MyApi;
#[bevy_wasm_api]
impl MyApi {
pub fn spawn_entity(world: &mut World, x: f32, y: f32, z: f32) -> Entity {
world.spawn(
TransformBundle {
transform: Transform {
translation: Vec3::new(x, y, z),
..Default::default(),
},
..Default::default(),
}
).id()
}
pub fn set_entity_position(world: &mut World, entity: u32, x: f32, y: f32, z: f32) -> Result<(), String> {
let entity = Entity::from_raw(entity);
let mut transform = world.get_mut::(entity).ok_or("Could not find entity".to_string())?;
transform.translation.x = x;
transform.translation.y = y;
transform.translation.z = z;
Ok(())
}
pub fn get_entity_position(world: &mut World, entity: u32) -> Option<(f32, f32, f32)> {
let transform = world.get::(Entity::from_raw(entity));
transform.map(|transform| {
let pos = transform.translation;
(pos.x, pos.y, pos.z)
})
}
}
```
### Use your api in typescript
```typescript
import { setup_app, MyApi } from 'bevy-app';
async function start() {
try {
setup_app('#canvas-element');
} catch (error) {
// Ignore, Bevy apps for wasm error for control flow.
}
const api = new MyApi();
const id = await api.spawn_entity(0, 0, 0);
await api.set_entity_position(id, 10, 0, 0);
const pos = await api.get_entity_position(id)
console.log(pos) // [10, 0, 0]
const otherPos = await api.get_entity_position(1000) // (Made up entity)
console.log(pos) // undefined
}
```
(back to top)
## How it works
The crate uses a similar approach to the [deferred promise](https://dev.to/webduvet/deferred-promise-pattern-2j59)
by parking the function that we want to execute (See `Task` in [`sync.rs`](./src/sync.rs)),
executing all the parked tasks, and then converts the result back to a JsValue.
The real complexity is in the effort to support typed returns in typescript which is handled in the [bevy-wasm-api-macro-core`](./bevy-wasm-api-macro-core/src/analyze/) crate.
Given the following input
```rust
#[bevy_wasm_api]
impl MyApi {
pub fn my_function(world: &mut World, x:f32, y: f32) -> bool {
// Do anything with your &mut World
true
}
}
```
The output will look something like this.
```rust
// Exposes `MyApiWasmApi` as `MyApi` in javascript
#[wasm_bindgen(js_class = "MyApi")]
impl MyApiWasmApi {
// Skips wasm_bindgen typescript types so we can generate better typescript types.
#[wasm_bindgen(skip_typescript)]
pub fn my_function(x: f32, y: f32) -> js_sys::Promise {
// Uses execute_in_world to get a `world: &mut World`, converts the future to a Js Promise
wasm_bindgen_futures::future_to_promise(bevy_wasm_api::execute_in_world(bevy_wasm_api::ExecutionChannel::FrameStart, |world| {
// Calls the original method
let ret_val = MyApi::my_function(world, x, y);
// Return the original return type as a JsValue
// The real code that's generated here is actually dependent on the return type but I'll keep it simple in this example.
Ok(JsValue::from(ret_val))
}))
}
}
```
(back to top)
## Examples
### `vite-app`
This is your "kitchen sink" example showcasing a lot of the features of the crate.
This is how I am personally using the package to develop my app (a CAD/design program).
### `wasm-app`
This shows how to use the crate purely from the bevy side.
Showcasing the changes you'd make / dependencies you'd need in bevy.
(back to top)
## Features
Here's an outline of the currently supported feature set + features that I'd like to implement.
- [ ] Type inference / handling of return types
- [x] Infers any number (`i32`, ...) as typescript `number` type
- [x] Infers `bool` as typescript `bool` type
- [x] Correctly handles custom struct returns (must implement From/IntoWasmAbi) (use [tsify](https://github.com/madonoharu/tsify) to generate typescript types).
- [x] Infers `&str`/`String` as typescript `string`
- [x] Infers `Result` as typescript `Promise`
- [ ] Use a Result polyfill so the final return type is `Result>`
- [x] Infers `Vec` as typescript typescript `Array` type
- [ ] Infers an `Iter` as typescript `Array`?
- [x] Infers `Option` as typescript `T | undefined` type
- [x] Infers tuples (i.e. `(f32, String)`) as typescript `[number, String]` type
- [ ] Infers `&[i32]`, and other number arrays as typescript `Int32Array`
- [ ] Infers `i32[]`, and other number arrays as typescript `Int32Array`
- [ ] Handle `Future` as typescript `Promise`?
- [ ] Type inference / handling of argument types
- [x] Input parameters handled entirely by `wasm_bindgen`. [tsify](https://github.com/madonoharu/tsify) is good for making this more ergonomic.
- [ ] Implement custom handling supporting the same typed parameters as return types (above)
- [ ] Targets:
- [x] Exposes an api in JS that communicates directly with the bevy wasm app. (For use in browser contexts)
- [ ] Exposes an api in JS that communicates with a desktop app over a HTTP endpoint + RPC layer. (For use in desktop contexts with ui in [bevy_wry_webview](https://github.com/hytopiagg/bevy_wry_webview))
- [ ] Support systems as the Api handler. Make use of [`In`](https://docs.rs/bevy/latest/bevy/ecs/system/struct.In.html) and [`Out`](https://docs.rs/bevy/latest/bevy/ecs/prelude/trait.System.html#associatedtype.Out) for args / return value.
- [ ] Support multiple bevy apps
- [ ] Less restrictive dependency versions
- [ ] Adding proc macro attributes to declare when in the frame lifecycle we want to execute the api method.
(back to top)
## Contributing
This crate is an ends to a means for developing an app so I am not sure what level of support I will be
able to provide and I might not be able to support a lot of additional features. That being said, if you
run into bugs or have ideas for improvements/features feel free to create an issue or, even better, submit a PR.
> :warning: If the PR is fairly large and complex it could be worth submitting an issue introducing the desired
> changes + the usecase so I can verify if it's something that belongs in this crate.
(back to top)
## Help me out?
This is also my first proc_macro and I am not that experience with the "bevy" way of doing things so
if you know have some technical ideas on how this crate can be improved (improve modularity/adaptability,
performance, simplify code) I would be very grateful to hear it in an issue.
Some things I'd love feedback on is:
- Making the dependency versions lest restrictive.
- Adding proc macro attributes on each function to declare when the ApiMethod should run.
- Making better use of bevy paradigms
- Making better use of wasm_bindgen type inference (currently duplicating logic converting `str` (rust) -> `string` (typescript))
- All of this is only tested with my depenencies, anything that makes it more versatile (I might be a bit too dumb to make it fully generic)
- Generalising the type inference improvements into its own crate (could be useful outside of the bevy ecosystem)
## Compatibility
| bevy-wasm-api version | Bevy version |
|-----------------------|--------------|
| 0.2 | 0.14 |
| 0.1 | 0.13 |