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  });