-
I found when I create an instance, then delete the canvas later (by unmounting the component in react or other UI frameworks), then wasm instance won't draw on the new canvas again. I guess this is because I didn't properly destroy the instance when the old canvas unmount. But I can't find a method to unmount it on typescript type generated by bindgen, so how to do this properly? Like trigger "Window close, game close" in browser wasm env. Seems wasm is a singleton, so the main function won't run again. So game is not show up on second try. |
Beta Was this translation helpful? Give feedback.
Replies: 5 comments 3 replies
-
Get When I try to not use use bevy::{prelude::*, sprite::MaterialMesh2dBundle};
use wasm_bindgen::prelude::*;
use web_sys::console;
#[wasm_bindgen]
pub fn start_game() {
App::new()
.add_plugins(DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {
canvas: Some(
".tw-gamification-bevy-canvas.scp-foundation-site-director".to_string(),
),
..default()
}),
..default()
}))
.add_systems(Startup, setup)
.run();
}
fn main() {
console::log_1(&"WASM loaded.".into());
} I found bindgen has this line, which is very bad function initSync(module) {
if (wasm !== undefined) return wasm; This cause it is impossible to create second instance. It works after remove this line, but will cause error
|
Beta Was this translation helpful? Give feedback.
-
This is not working too use bevy::{prelude::*, sprite::MaterialMesh2dBundle};
use serde::{Deserialize, Serialize};
use wasm_bindgen::prelude::*;
use web_sys::console;
use std::sync::atomic::{AtomicBool, Ordering};
use bevy::app::AppExit;
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_name = twGamificationSaveGameData, catch)]
fn save_game_data(data: &str) -> Result<(), JsValue>;
}
#[wasm_bindgen]
pub fn start_game() {
SHOULD_EXIT.store(false, Ordering::SeqCst);
App::new()
.add_plugins(DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {
canvas: Some(
".tw-gamification-bevy-canvas.scp-foundation-site-director".to_string(),
),
..default()
}),
..default()
}))
.add_systems(Startup, setup)
.add_plugins(GameControlPlugin)
.run();
}
static SHOULD_EXIT: AtomicBool = AtomicBool::new(false);
#[wasm_bindgen]
pub fn stop_game() {
console::log_1(&"Exiting 0.".into());
SHOULD_EXIT.store(true, Ordering::SeqCst);
}
pub struct GameControlPlugin;
impl Plugin for GameControlPlugin {
fn build(&self, app: &mut App) {
app
.add_systems(Update, exit_system);
}
}
fn exit_system(mut exit: EventWriter<AppExit>) {
console::log_1(&"Exiting 2.".into());
if SHOULD_EXIT.load(Ordering::SeqCst) {
console::log_1(&"Exiting 3.".into());
exit.send(AppExit);
console::log_1(&"Exiting 4.".into());
}
} Log is
|
Beta Was this translation helpful? Give feedback.
-
Alternatively you might want to spawn a completely new Wasm instance. There is no way to directly close a Wasm instance, it should eventually get garbage collected when it's not in use and doesn't have any references pointing to it. |
Beta Was this translation helpful? Give feedback.
-
Current workaround: Add a destroy functionscripts/buildGame.mjs #!/usr/bin/env zx
import { appendFile, readFile, writeFile } from 'fs/promises';
import { $, cd, path } from 'zx';
const gamePath = path.resolve(__dirname, '../src/scp-foundation-site-director/game');
/**
* Build the game
*/
cd(gamePath);
await $`cargo build --release --target wasm32-unknown-unknown`;
await $`wasm-bindgen --omit-default-module-path --reference-types --weak-refs --out-name game --out-dir wasm --target no-modules target/wasm32-unknown-unknown/release/game.wasm --debug`;
/**
* Fix Wasm Bindgen No Destroy Method
*/
// JS snippet to append
const jsSnippet = `
function destroy() {
wasm = undefined;
__wbg_init.__wbindgen_wasm_module = undefined;
cachedFloat32Memory0 = null;
cachedFloat64Memory0 = null;
cachedInt32Memory0 = null;
cachedUint32Memory0 = null;
cachedUint8Memory0 = null;
}
`;
// TS definition to append
const tsDefinition = `
/**
* remove previous game, to prevent \`Creating EventLoop multiple times is not supported.\` error.
* @url https://github.com/bevyengine/bevy/discussions/11619
*/
export function destroy(): void;
export type WasmContext = typeof WasmBindgen & { initAsync: typeof initAsync }
/**
* Create a new wasm context.
*
* ## If reuse previous one:
*
* There will be \`already borrowed: BorrowMutError\` \`RuntimeError: unreachable executed\` error, when switch from one game to another.
*
* And sometimes there still be \`Uncaught TypeError: wasm is undefined\` \`Uncaught RuntimeError: unreachable\` \`Uncaught TypeError: Cannot read properties of undefined (reading 'wasm_bindgen__convert__closures__invoke0_mut__h9f5f8d8886b9ec6c')\` \`Uncaught TypeError: Cannot read properties of undefined (reading '__wbindgen_export_3')\` error, when switch from one game to another, maybe switch is too fast? Change from \`0\` to \`1000\` some times still cannot fixes this, even the game should have been stopped. And it slowdown the game init. And this error throw on global can't be catch here.
*/
export function getWasmContext(): WasmContext;
`;
// Path to the wasm bindgen generated JS file
const jsFilePath = path.resolve(gamePath, 'wasm/game.js');
const tsFilePath = path.resolve(gamePath, 'wasm/game.d.ts');
try {
// Append JS snippet
// await appendFile(jsFilePath, jsSnippet);
const jsFile = await readFile(jsFilePath, 'utf8');
await writeFile(
jsFilePath,
jsFile
.replace('wasm_bindgen = Object.assign(__wbg_init, { initSync }, __exports);', 'return { initAsync: __wbg_init, initSync, ...__exports };')
.replace('})();', '}')
.replace(
`let wasm_bindgen;
(function() {`,
'export function getWasmContext() {',
),
);
console.log('JS snippet appended successfully.');
// Append TS definition
const tsFile = await readFile(tsFilePath, 'utf8');
await writeFile(
tsFilePath,
tsFile
.replace('declare namespace wasm_bindgen', 'declare namespace WasmBindgen')
.replace('declare function wasm_bindgen', 'function initAsync'),
);
await appendFile(tsFilePath, tsDefinition);
console.log('TS definition appended successfully.');
} catch (error) {
console.error('Failed to append to file:', error);
} create new context when switching game private async initializeGameCanvas() {
const gameWasm = $tw.wiki.getTiddlerText('$:/plugins/linonetwo/scp-foundation-site-director/game_bg.wasm');
// wasm is bundled into tw using `game/tiddlywiki.files` as base64
if (gameWasm !== undefined) {
const wasmBuffer = loadWasmModuleFromBase64(gameWasm);
console.time('gameLoad'); // 384 ~ 1551 ms
try {
this.wasmContext = getWasmContext();
/**
* Use `initAsync` for large wasm.
*
* Can't use `initSync`, it cause error:
* > RangeError: WebAssembly.Compile is disallowed on the main thread, if the buffer size is larger than 8MB. Use WebAssembly.compile, compile on a worker thread
*/
await this.wasmContext.initAsync(wasmBuffer);
this.wasmContext.startGame();
} catch (error) {
// https://users.rust-lang.org/t/getting-rid-of-this-error-error-using-exceptions-for-control-flow-dont-mind-me-this-isnt-actually-an-error/92209
// this throw from `startGame()`, but don't hurt anything.
if ((error as Error).message.includes('Using exceptions for control flow')) {
this.gameInitialized = true;
} else {
console.error('Game load with error', error);
}
} finally {
this.setLoading(false);
}
console.timeEnd('gameLoad');
}
}
// ...
function loadWasmModuleFromBase64(encodedWasm: string) {
// Decode the base64 string to binary data
console.time('wasmDecode'); // 157 ~ 445 ms
const binaryString = window.atob(encodedWasm);
const binaryLength = binaryString.length;
const bytes = new Uint8Array(binaryLength);
// Convert the binary string to a byte array
for (let index = 0; index < binaryLength; index++) {
bytes[index] = binaryString.codePointAt(index)!;
}
console.timeEnd('wasmDecode');
return bytes;
} Stop game in a system when unmount component in jsstatic SHOULD_EXIT: AtomicBool = AtomicBool::new(false);
#[wasm_bindgen(js_name = stopGame)]
pub fn stop_game() {
console::log_1(&"stop_game".into());
SHOULD_EXIT.store(true, Ordering::SeqCst);
}
pub struct GameControlPlugin;
impl Plugin for GameControlPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Update, exit_system);
}
}
fn exit_system(mut exit: EventWriter<AppExit>) {
if SHOULD_EXIT.load(Ordering::SeqCst) {
console::log_1(&"exit_system".into());
exit.send(AppExit);
}
} workaround?while this work around works, I still hope there can be an official solution, so this post is still open for new comments. online demo https://tiddly-gittly.github.io/tw-gamification/ |
Beta Was this translation helpful? Give feedback.
-
See wasm-bindgen/wasm-bindgen#3818 (comment) private async initializeGameCanvas() {
// wasm is bundled into tiddlywiki using `game/tiddlywiki.files` as base64
const gameWasm = $tw.wiki.getTiddlerText('$:/plugins/linonetwo/scp-foundation-site-director/game_bg.wasm');
// we parse and run the code on runtime, to create a new JS context each time, to prevent reuse last game's wasm.
const wasmBindGenJSCode = $tw.wiki.getTiddlerText('$:/plugins/linonetwo/scp-foundation-site-director/game.js');
if (gameWasm !== undefined && wasmBindGenJSCode !== undefined) {
const wasmBuffer = loadWasmModuleFromBase64(gameWasm);
console.time('gameLoad'); // 384 ~ 1551 ms
try {
const blob = new Blob([wasmBindGenJSCode], { type: 'text/javascript' });
const objectURL = URL.createObjectURL(blob);
this.wasmContext = await import(objectURL) as typeof IGameContext;
URL.revokeObjectURL(objectURL);
/**
* Use `initAsync` for large wasm.
*
* Can't use `initSync`, it cause error:
* > RangeError: WebAssembly.Compile is disallowed on the main thread, if the buffer size is larger than 8MB. Use WebAssembly.compile, compile on a worker thread
*/
await this.wasmContext.default(wasmBuffer);
this.wasmContext.startGame();
// ...
destroy(): void {
this.wasmContext?.stopGame?.();
this.wasmContext = undefined;
this.gameInitialized = false;
} And this use official |
Beta Was this translation helpful? Give feedback.
See wasm-bindgen/wasm-bindgen#3818 (comment)