I have a web client for my game with all the rendering, networking, logic, etc. implemented in wasm (as a rust library) with minimal js glue. In the wasm module I have global static mut
s to store state such as entities like the current player, players, walls, bullets, etc.
static mut PLAYER: Player = Player::default();
static mut WALLS: Vec<Wall> = Vec::new();
static mut PLAYERS: Vec<Player> = Vec::new();
static mut BULLETS: Vec<Bullet> = Vec::new();
static mut CANVAS_WIDTH: usize = 0;
static mut CANVAS_HEIGHT: usize = 0;
static mut CAM: Camera = Camera::default();
I also have a render function that I export to js that will be called using requestAnimationFrame
.
#[no_mangle]
extern "C" fn render(dt: f32) {
unsafe {
PLAYER.x += PLAYER.v_x * dt;
PLAYER.y += PLAYER.v_y * dt;
#[allow(static_mut_refs)]
for p in &mut PLAYERS {
p.x += p.v_x * dt;
p.y += p.v_y * dt;
}
#[allow(static_mut_refs)]
for b in &mut BULLETS {
b.x += b.v_x * dt;
b.y += b.v_y * dt;
}
}
#[allow(static_mut_refs)]
draw(&CTX, unsafe { &CAM });
}
It calls to draw()
which is what actually does the rendering.
fn draw(ctx: &Context, cam: &Camera) {
ctx.clear_rect(0.0, 0.0, canvas_width() as f32, canvas_height() as f32);
ctx.fill_style("#efeff5");
ctx.fill_rect(0.0, 0.0, canvas_width() as f32, canvas_height() as f32);
ctx.save();
ctx.translate(
canvas_width() as f32 / 2.0 - cam.z * cam.x,
canvas_height() as f32 / 2.0 - cam.z * cam.y,
);
ctx.scale(cam.z, cam.z);
draw_grid(ctx);
draw_border(ctx);
#[allow(static_mut_refs)]
draw_walls(ctx, unsafe { &WALLS });
#[allow(static_mut_refs)]
draw_bullets(ctx, unsafe { &BULLETS });
#[allow(static_mut_refs)]
draw_players(ctx, unsafe { &PLAYERS });
#[allow(static_mut_refs)]
draw_player(ctx, unsafe { &PLAYER });
ctx.restore();
#[allow(static_mut_refs)]
draw_hud(ctx, unsafe { &HUD }, &MINIMAP);
}
There will never be 2 instances of this wasm module sharing the same underlying Memory
while also being called simultaneously in different threads. So is it safe to use static mut
in this way? Specifically, is it safe to:
- Use or pass a
static mut
as an immutable reference usingunsafe { &MY_STATIC }
as seen at the end ofrender()
and indraw()
above. - Mutate a
static mut
as seen inrender()
above, whether that be directly or indirectly through a reference (see the 2 loops inrender()
) - Read a
static mut
such as in
fn canvas_width() -> usize {
unsafe { CANVAS_WIDTH }
}
Side note: Everything works as intended and no unexpected behavior was observed. I ask only because I am now moving to 2024 edition where static_mut_refs
is now deny
by default