Skip to content

Commit 852ef1e

Browse files
authored
Dynamic wasm import (#483)
1 parent b3ef624 commit 852ef1e

File tree

1 file changed

+53
-23
lines changed

1 file changed

+53
-23
lines changed

frontend/src/WasmOrRpcProvider.tsx

Lines changed: 53 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import * as React from "react";
2-
import * as Shengji from "../shengji-wasm/pkg/shengji-core.js";
32
import WasmContext from "./WasmContext";
43
import { isWasmAvailable } from "./detectWasm";
54
import {
@@ -35,6 +34,9 @@ interface IProps {
3534
children: React.ReactNode;
3635
}
3736

37+
// Type for the dynamically imported WASM module
38+
type ShengjiModule = typeof import("../shengji-wasm/pkg/shengji-core.js");
39+
3840
// Define the RPC request types
3941
type WasmRpcRequest =
4042
| ({ type: "FindViablePlays" } & FindViablePlaysRequest)
@@ -75,60 +77,63 @@ async function callRpc<T>(request: WasmRpcRequest): Promise<T> {
7577
}
7678

7779
// Create async versions of each function that can fallback to RPC
78-
const createAsyncFunctions = (useWasm: boolean) => {
79-
if (useWasm) {
80-
// WASM is available, use synchronous WASM functions wrapped in promises
80+
const createAsyncFunctions = (
81+
useWasm: boolean,
82+
wasmModule: ShengjiModule | null,
83+
) => {
84+
if (useWasm && wasmModule) {
85+
// WASM is available and loaded, use synchronous WASM functions wrapped in promises
8186
return {
8287
findViablePlays: async (
8388
trump: Trump,
8489
tractorRequirements: TractorRequirements,
8590
cards: string[],
8691
): Promise<FoundViablePlay[]> => {
87-
return Shengji.find_viable_plays({
92+
return wasmModule.find_viable_plays({
8893
trump,
8994
cards,
9095
tractor_requirements: tractorRequirements,
9196
}).results;
9297
},
9398
findValidBids: async (req: FindValidBidsRequest): Promise<Bid[]> => {
94-
return Shengji.find_valid_bids(req).results;
99+
return wasmModule.find_valid_bids(req).results;
95100
},
96101
sortAndGroupCards: async (
97102
req: SortAndGroupCardsRequest,
98103
): Promise<SuitGroup[]> => {
99-
return Shengji.sort_and_group_cards(req).results;
104+
return wasmModule.sort_and_group_cards(req).results;
100105
},
101106
decomposeTrickFormat: async (
102107
req: DecomposeTrickFormatRequest,
103108
): Promise<DecomposedTrickFormat[]> => {
104-
return Shengji.decompose_trick_format(req).results;
109+
return wasmModule.decompose_trick_format(req).results;
105110
},
106111
canPlayCards: async (req: CanPlayCardsRequest): Promise<boolean> => {
107-
return Shengji.can_play_cards(req).playable;
112+
return wasmModule.can_play_cards(req).playable;
108113
},
109114
explainScoring: async (
110115
req: ExplainScoringRequest,
111116
): Promise<ExplainScoringResponse> => {
112-
return Shengji.explain_scoring(req);
117+
return wasmModule.explain_scoring(req);
113118
},
114119
nextThresholdReachable: async (
115120
req: NextThresholdReachableRequest,
116121
): Promise<boolean> => {
117-
return Shengji.next_threshold_reachable(req);
122+
return wasmModule.next_threshold_reachable(req);
118123
},
119124
computeScore: async (
120125
req: ComputeScoreRequest,
121126
): Promise<ComputeScoreResponse> => {
122-
return Shengji.compute_score(req);
127+
return wasmModule.compute_score(req);
123128
},
124129
computeDeckLen: async (decks: Deck[]): Promise<number> => {
125-
return Shengji.compute_deck_len({ decks });
130+
return wasmModule.compute_deck_len({ decks });
126131
},
127132
batchGetCardInfo: async (
128133
req: BatchCardInfoRequest,
129134
): Promise<BatchCardInfoResponse> => {
130135
// WASM doesn't have batch API, so call individually
131-
const results = req.requests.map((r) => Shengji.get_card_info(r));
136+
const results = req.requests.map((r) => wasmModule.get_card_info(r));
132137
return { results };
133138
},
134139
};
@@ -255,17 +260,41 @@ export const EngineContext = React.createContext<EngineContext | null>(null);
255260

256261
const WasmOrRpcProvider = (props: IProps): JSX.Element => {
257262
const useWasm = isWasmAvailable();
263+
const [wasmModule, setWasmModule] = React.useState<ShengjiModule | null>(
264+
null,
265+
);
266+
const [isLoading, setIsLoading] = React.useState(useWasm);
267+
268+
// Load WASM module dynamically if available
269+
React.useEffect(() => {
270+
if (useWasm) {
271+
import("../shengji-wasm/pkg/shengji-core.js")
272+
.then((module) => {
273+
setWasmModule(module);
274+
// Set module on window for debugging
275+
(window as Window & { shengji?: ShengjiModule }).shengji = module;
276+
setIsLoading(false);
277+
})
278+
.catch((error) => {
279+
console.error("Failed to load WASM module:", error);
280+
setIsLoading(false);
281+
});
282+
} else {
283+
setIsLoading(false);
284+
}
285+
}, [useWasm]);
286+
258287
const engineFuncs = React.useMemo(
259-
() => createAsyncFunctions(useWasm),
260-
[useWasm],
288+
() => createAsyncFunctions(useWasm, wasmModule),
289+
[useWasm, wasmModule],
261290
);
262291

263292
// Only provide decodeWireFormat in the synchronous context
264293
const syncContextValue = React.useMemo(
265294
() => ({
266295
decodeWireFormat: (req: Uint8Array) => {
267-
if (useWasm) {
268-
return JSON.parse(Shengji.zstd_decompress(req));
296+
if (useWasm && wasmModule) {
297+
return JSON.parse(wasmModule.zstd_decompress(req));
269298
} else {
270299
// When WASM is not available, messages should already be decompressed
271300
// by the server, so we can just parse them directly
@@ -274,20 +303,21 @@ const WasmOrRpcProvider = (props: IProps): JSX.Element => {
274303
}
275304
},
276305
}),
277-
[useWasm],
306+
[useWasm, wasmModule],
278307
);
279308

280309
const engineContextValue: EngineContext = React.useMemo(
281310
() => ({
282311
...engineFuncs,
283312
decodeWireFormat: syncContextValue.decodeWireFormat,
284-
isUsingWasm: useWasm,
313+
isUsingWasm: useWasm && wasmModule !== null,
285314
}),
286-
[engineFuncs, syncContextValue, useWasm],
315+
[engineFuncs, syncContextValue, useWasm, wasmModule],
287316
);
288317

289-
if (useWasm) {
290-
(window as Window & { shengji?: typeof Shengji }).shengji = Shengji;
318+
// Show loading indicator while WASM is being loaded
319+
if (isLoading) {
320+
return <div>Loading game engine...</div>;
291321
}
292322

293323
return (

0 commit comments

Comments
 (0)