github.com/primecitizens/pcz/std@v0.2.1/ffi/js/callback/ffi_bindings.ts (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright 2023 The Prime Citizens 3 4 import { Application, Pointer, heap, importModule } from "@ffi"; 5 6 importModule("ffi/js/callback", (A: Application) => { 7 type ReturnType = any | PromiseLike<any>; 8 9 interface CallbackContext { 10 dispFnPC: number; 11 targetPC: number; 12 dispRecv: number; 13 retRef: heap.Ref<ReturnType>; 14 args: heap.Ref<any>[]; 15 resolveFn: heap.Ref<() => void>; 16 } 17 18 const RET_PLACEHOLDER = "_CBRET_"; 19 const PROMISE_PLACEHOLDER = "_CBPROM_"; 20 21 return { 22 "func": (dispFnPC: number, dispRecv: number, targetPC: number): heap.Ref<Function> => { 23 dispFnPC >>>= 0; 24 dispRecv >>>= 0; 25 targetPC >>>= 0; 26 const runid = A.runid; 27 28 // NOTE: do not use arrow function, whose `this` would always be the Application. 29 return A.H.push(function (): ReturnType { 30 // NOTE: on calling go function, the app could have exited, 31 // in that case, the app may has something like active `setInterval` routine 32 // not cancelled. 33 if (A.exited || runid != A.runid) { 34 throw new Error(`unexpected function call after program exit: pc=${targetPC}`); 35 } 36 37 if (arguments.length > 15) { 38 throw new Error("too many args to callback function"); 39 } 40 41 const heapArgs = new Array(arguments.length + 1); 42 heapArgs[0] = A.H.push(this); 43 for (let i = 0; i < arguments.length; i++) { 44 heapArgs[i + 1] = A.H.push(arguments[i]); 45 } 46 47 const context: CallbackContext = { 48 dispFnPC, 49 dispRecv, 50 targetPC, 51 args: heapArgs, 52 retRef: A.H.push(RET_PLACEHOLDER), // cannot push the `undefined` value 53 resolveFn: A.H.push(PROMISE_PLACEHOLDER), // see below 54 }; 55 A.H.replace(context.retRef, undefined); 56 57 let resolved = false; 58 // the callback may return due to awaiting, 59 // so we have to wait for the actual return in a promise. 60 const p: Promise<ReturnType> = new Promise<boolean>((resolve: (value: boolean) => void) => { 61 A.H.replace(context.resolveFn, () => { 62 resolved = true; 63 A.H.free(context.resolveFn); 64 resolve(true); 65 }); 66 }).then(() => { 67 heapArgs.forEach((x) => A.H.free(x)); 68 if (A.exited) { 69 A.resolveExitPromise(); 70 } 71 return A.H.take(context.retRef); 72 }); 73 74 A.call(A.H.push(context), 0); 75 76 // The callback may also return synchronously, in that case we 77 // should always return the value; 78 // 79 // resolved can only be true when the callbacked returned synchronously. 80 if (resolved) { 81 return A.H.get(context.retRef); 82 } 83 84 return p; 85 }); 86 }, 87 "ctx": (ref: heap.Ref<CallbackContext>, pCallbackContext: Pointer): void => { 88 const cb = A.H.take<CallbackContext>(ref); 89 90 // offsets MUST agree with ../callback.go#type:CallbackContext 91 pCallbackContext >>>= 0; 92 A.store.Uint32(pCallbackContext + 0, cb.dispFnPC); 93 A.store.Uint32(pCallbackContext + 4, cb.dispRecv); 94 A.store.Uint32(pCallbackContext + 8, cb.targetPC); 95 A.store.Uint32(pCallbackContext + 12, cb.retRef); 96 A.store.Uint32(pCallbackContext + 16, cb.resolveFn); 97 if (!cb.args || cb.args.length === 0) { 98 A.store.Uint32(pCallbackContext + 20, 0); 99 return; 100 } 101 102 A.store.Uint32(pCallbackContext + 20, cb.args.length); 103 pCallbackContext += 24; 104 // at most 15 args as guarded by the 'func' 105 for (let i = 0; i < cb.args.length; i++) { 106 A.store.Uint32(pCallbackContext + i * 4, cb.args[i]); 107 } 108 }, 109 }; 110 });