1+ import { GrowerRsImports , JSNIFunction } from "./types.ts" ;
2+
3+ const FUNCTIONS = "__grower_jsni_functions" ;
4+ const ENTRY_POINT = "__grower_jsni_call" ;
5+ type _Window = Window & typeof global & {
6+ [ ENTRY_POINT ] : ( jsFuncNamePtr : number , argsPtr : number , argsCount : number ) => Promise < number > ;
7+ [ FUNCTIONS ] : Record < string , JSNIFunction > ;
8+ } ;
9+
10+ export default class JavaScriptNativeInterface {
11+
12+ private readonly imports : GrowerRsImports ;
13+ private readonly memory : WebAssembly . Memory ;
14+
15+ constructor ( imports : GrowerRsImports , memory : WebAssembly . Memory ) {
16+ this . imports = imports ;
17+ this . memory = memory ;
18+ }
19+
20+ init ( ) {
21+ if ( ! ( window as _Window ) [ FUNCTIONS ] ) {
22+ ( window as _Window ) [ FUNCTIONS ] = { } ;
23+ }
24+
25+ ( window as _Window ) [ ENTRY_POINT ] = async ( jsFuncNamePtr : number , argsPtr : number , argsCount : number ) : Promise < number > => {
26+ const session = new JSNIFunctionCallingSession ( this . imports , this . memory , jsFuncNamePtr , argsPtr , argsCount ) ;
27+ const value = await session . call ( ) ;
28+ return session . buildReturnValues ( value ) ;
29+ } ;
30+ }
31+
32+ register ( name : string , func : JSNIFunction ) {
33+ ( window as _Window ) [ FUNCTIONS ] [ name ] = func ;
34+ }
35+
36+ }
37+
38+ class JSNIFunctionCallingSession {
39+ private imports : GrowerRsImports ;
40+ private view : DataView ;
41+ private arr : Uint8Array ;
42+ private argsPtr : number ;
43+ private argsCount : number ;
44+
45+ functionName : string ;
46+ args : any [ ] = [ ] ;
47+
48+ constructor (
49+ imports : GrowerRsImports ,
50+ view : WebAssembly . Memory ,
51+ functionNamePtr : number ,
52+ argsPtr : number ,
53+ argsCount : number
54+ ) {
55+ this . imports = imports ;
56+ this . view = new DataView ( view . buffer ) ;
57+ this . arr = new Uint8Array ( view . buffer ) ;
58+ this . argsPtr = argsPtr ;
59+ this . argsCount = argsCount ;
60+
61+ this . functionName = this . readString (
62+ this . view . getUint32 ( functionNamePtr , true ) ,
63+ this . view . getUint32 ( functionNamePtr + 4 , true )
64+ ) ;
65+ this . args = this . parseArgs ( ) ;
66+ }
67+
68+ async call ( ) {
69+ return ( window as _Window ) [ FUNCTIONS ] [ this . functionName ] ( ...this . args ) ;
70+ }
71+
72+ private parseArgs ( ) : any [ ] {
73+ const args : any [ ] = [ ] ;
74+ for ( let i = 0 ; i < this . argsCount ; i ++ ) {
75+ const type = this . view . getUint8 ( this . argsPtr + i * 16 + 8 ) ;
76+ switch ( type ) {
77+ // i8
78+ case 0 :
79+ args . push ( this . view . getInt8 ( this . argsPtr + i * 16 ) ) ;
80+ break ;
81+ // i16
82+ case 1 :
83+ args . push ( this . view . getInt16 ( this . argsPtr + i * 16 , true ) ) ;
84+ break ;
85+ // i32
86+ case 2 :
87+ args . push ( this . view . getInt32 ( this . argsPtr + i * 16 , true ) ) ;
88+ break ;
89+ // i64
90+ case 3 :
91+ args . push ( this . view . getBigInt64 ( this . argsPtr + i * 16 , true ) ) ;
92+ break ;
93+ // u8
94+ case 4 :
95+ args . push ( this . view . getUint8 ( this . argsPtr + i * 16 ) ) ;
96+ break ;
97+ // u16
98+ case 5 :
99+ args . push ( this . view . getUint16 ( this . argsPtr + i * 16 , true ) ) ;
100+ break ;
101+ // u32
102+ case 6 :
103+ args . push ( this . view . getUint32 ( this . argsPtr + i * 16 , true ) ) ;
104+ break ;
105+ // u64
106+ case 7 :
107+ args . push ( this . view . getBigUint64 ( this . argsPtr + i * 16 , true ) ) ;
108+ break ;
109+ // f32
110+ case 8 :
111+ args . push ( this . view . getFloat32 ( this . argsPtr + i * 16 , true ) ) ;
112+ break ;
113+ // f64
114+ case 9 :
115+ args . push ( this . view . getFloat64 ( this . argsPtr + i * 16 , true ) ) ;
116+ break ;
117+ // String
118+ case 10 :
119+ args . push (
120+ this . readString (
121+ this . view . getUint32 ( this . argsPtr + i * 16 , true ) ,
122+ this . view . getUint32 ( this . argsPtr + i * 16 + 4 , true )
123+ )
124+ ) ;
125+ break ;
126+ // U8Array
127+ case 11 :
128+ args . push (
129+ this . readVec (
130+ this . view . getUint32 ( this . argsPtr + i * 16 , true ) ,
131+ this . view . getUint32 ( this . argsPtr + i * 16 + 4 , true )
132+ )
133+ ) ;
134+ break ;
135+ // null
136+ case 13 :
137+ args . push ( null ) ;
138+ break ;
139+ }
140+ }
141+ return args ;
142+ }
143+
144+ buildReturnValues ( values : any [ ] ) : number {
145+ const size = values . length ; // 16 bytes per value
146+ const fat_ptr = this . imports . alloc_jsni_value ( size ) ;
147+ const ptr = Number ( fat_ptr & BigInt ( 0xffffffff ) ) ;
148+
149+ for ( let i = 0 ; i < values . length ; i ++ ) {
150+ switch ( typeof values [ i ] ) {
151+ case "number" :
152+ if ( Number . isInteger ( values [ i ] ) ) {
153+ this . view . setBigInt64 ( ptr + i * 16 , BigInt ( values [ i ] ) , true ) ;
154+ this . view . setInt32 ( ptr + i * 16 + 8 , 3 , true ) ;
155+ } else {
156+ this . view . setFloat64 ( ptr + i * 16 , values [ i ] , true ) ;
157+ this . view . setInt32 ( ptr + i * 16 + 8 , 9 , true ) ;
158+ }
159+ break ;
160+ case "bigint" :
161+ this . view . setBigInt64 ( ptr + i * 16 , values [ i ] , true ) ;
162+ this . view . setInt32 ( ptr + i * 16 + 8 , 3 , true ) ;
163+ break ;
164+ case "string" :
165+ const strBytes = new TextEncoder ( ) . encode ( values [ i ] ) ;
166+ const strFatPtr = this . imports . alloc ( strBytes . length + 1 ) ; // 1 byte for null terminator
167+ const strPtr = Number ( strFatPtr & BigInt ( 0xffffffff ) ) ;
168+ this . arr . set ( strBytes , strPtr ) ;
169+ this . arr [ strPtr + strBytes . length ] = 0 ; // null terminator
170+ this . view . setUint32 ( ptr + i * 16 , Number ( strFatPtr >> BigInt ( 32 ) ) , true ) ;
171+ this . view . setInt32 ( ptr + i * 16 + 8 , 10 , true ) ;
172+ break ;
173+ case "object" :
174+ if ( values [ i ] instanceof Uint8Array ) {
175+ const vecBytes = values [ i ] ;
176+ const vecFatPtr = this . imports . alloc ( vecBytes . length ) ;
177+ const vecPtr = Number ( vecFatPtr & BigInt ( 0xffffffff ) ) ;
178+ this . arr . set ( vecBytes , vecPtr ) ;
179+ this . view . setUint32 ( ptr + i * 16 , Number ( vecFatPtr >> BigInt ( 32 ) ) , true ) ;
180+ this . view . setInt32 ( ptr + i * 16 + 8 , 11 , true ) ;
181+ } else if ( values [ i ] === null ) {
182+ this . view . setUint32 ( ptr + i * 16 , 0 , true ) ;
183+ this . view . setInt32 ( ptr + i * 16 + 8 , 13 , true ) ;
184+ } else {
185+ throw new Error ( `Unsupported object type: ${ typeof values [ i ] } ` ) ;
186+ }
187+ break ;
188+ }
189+ }
190+
191+ return Number ( fat_ptr >> BigInt ( 32 ) ) ;
192+ }
193+
194+ private readString ( ptr : number , len : number ) : string {
195+ return new TextDecoder ( ) . decode ( this . arr . slice ( ptr , ptr + len ) ) ;
196+ }
197+
198+ private readVec ( ptr : number , len : number ) : Uint8Array {
199+ return this . arr . slice ( ptr , ptr + len ) ;
200+ }
201+ }
0 commit comments