11import * as React from "react" ;
2- import * as Shengji from "../shengji-wasm/pkg/shengji-core.js" ;
32import WasmContext from "./WasmContext" ;
43import { isWasmAvailable } from "./detectWasm" ;
54import {
@@ -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
3941type 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
256261const 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