import { Decoration } from '../decoration' ; import { LuaReturn , LuaState } from '../types' ; import { decorateFunction } from './function' ; import Global from '../global' ; import MultiReturn from '../multireturn' ; import RawResult from '../raw-result' ; import Thread from '../thread' ; import TypeExtension from '../type-extension' ; class PromiseTypeExtension < T = unknown > extends TypeExtension < Promise < T > > { private gcPointer : number ; private readonly functionRegistry = typeof FinalizationRegistry !== 'undefined' ? new FinalizationRegistry ( ( func ) => { this . thread . lua . module . removeFunction ( func ) ; } ) : undefined ; public constructor ( thread , injectObject ) { super ( thread , 'js_promise' ) ; if ( ! this . functionRegistry ) { console . warn ( 'PromiseTypeExtension: FinalizationRegistry not found. Memory leaks likely.' ) ; } this . gcPointer = thread . lua . module . addFunction ( ( functionStateAddress ) => { const userDataPointer = thread . lua . luaL_checkudata ( functionStateAddress , 1 , this . name ) ; const referencePointer = thread . lua . module . getValue ( userDataPointer , '*' ) ; thread . lua . unref ( referencePointer ) ; return LuaReturn . Ok ; } , 'ii' ) ; if ( thread . lua . luaL_newmetatable ( thread . address , this . name ) ) { const metatableIndex = thread . lua . lua_gettop ( thread . address ) ; thread . lua . lua_pushstring ( thread . address , 'protected metatable' ) ; thread . lua . lua_setfield ( thread . address , metatableIndex , '__metatable' ) ; thread . lua . lua_pushcclosure ( thread . address , this . gcPointer , 0 ) ; thread . lua . lua_setfield ( thread . address , metatableIndex , '__gc' ) ; const checkSelf = ( self ) : true => { if ( Promise . resolve ( self ) !== self ) { throw new Error ( 'promise method called without self instance' ) ; } return true ; } ; thread . pushValue ( { next : ( self , ... args ) => checkSelf ( self ) && self . then ( ... args ) , catch : ( self , ... args ) => checkSelf ( self ) && self . catch ( ... args ) , finally : ( self , ... args ) => checkSelf ( self ) && self . finally ( ... args ) , await : decorateFunction ( ( functionThread , self ) => { checkSelf ( self ) ; let promiseResult = undefined ; const awaitPromise = self . then ( ( res ) => { promiseResult = { status : 'fulfilled' , value : res } ; } ) . catch ( ( err ) => { promiseResult = { status : 'rejected' , value : err } ; } ) ; const continuance = this . thread . lua . module . addFunction ( ( continuanceState ) => { if ( ! promiseResult ) { return thread . lua . lua_yieldk ( functionThread . address , 0 , 0 , continuance ) ; } const continuanceThread = thread . stateToThread ( continuanceState ) ; if ( promiseResult . status === 'rejected' ) { continuanceThread . pushValue ( promiseResult . value || new Error ( 'promise rejected with no error' ) ) ; return this . thread . lua . lua_error ( continuanceState ) ; } if ( promiseResult . value instanceof RawResult ) { return promiseResult . value . count ; } else if ( promiseResult . value instanceof MultiReturn ) { for ( const arg of promiseResult . value ) { continuanceThread . pushValue ( arg ) ; } return promiseResult . value . length ; } else { continuanceThread . pushValue ( promiseResult . value ) ; return 1 ; } } , 'iiii' ) ; this . functionRegistry ?. register ( awaitPromise , continuance ) ; functionThread . pushValue ( awaitPromise ) ; return new RawResult ( thread . lua . lua_yieldk ( functionThread . address , 1 , 0 , continuance ) ) ; } , { receiveThread : true } ) , } ) ; thread . lua . lua_setfield ( thread . address , metatableIndex , '__index' ) ; thread . pushValue ( ( self , other ) => self === other ) ; thread . lua . lua_setfield ( thread . address , metatableIndex , '__eq' ) ; } thread . lua . lua_pop ( thread . address , 1 ) ; if ( injectObject ) { thread . set ( 'Promise' , { create : ( callback ) => new Promise ( callback ) , all : ( promiseArray ) => { if ( ! Array . isArray ( promiseArray ) ) { throw new Error ( 'argument must be an array of promises' ) ; } return Promise . all ( promiseArray . map ( ( potentialPromise ) => Promise . resolve ( potentialPromise ) ) ) ; } , resolve : ( value ) => Promise . resolve ( value ) , } ) ; } } public close ( ) { this . thread . lua . module . removeFunction ( this . gcPointer ) ; } public pushValue ( thread , decoration ) { if ( Promise . resolve ( decoration . target ) !== decoration . target ) { return false ; } return super . pushValue ( thread , decoration ) ; } } export default function createTypeExtension < T = unknown > ( thread , injectObject ) { return new PromiseTypeExtension < T > ( thread , injectObject ) ; }