import { BaseDecorationOptions , Decoration } from '../decoration' ; import { LUA_REGISTRYINDEX , LuaReturn , LuaState , LuaType , PointerSize } from '../types' ; import Global from '../global' ; import MultiReturn from '../multireturn' ; import RawResult from '../raw-result' ; import Thread from '../thread' ; import TypeExtension from '../type-extension' ; export interface FunctionDecoration extends BaseDecorationOptions { receiveArgsQuantity ? : boolean ; receiveThread ? : boolean ; self ? : any ; } export type FunctionType = ( ... args : any [ ] ) => Promise < any > | any ; export function decorateFunction ( target , options ) { return new Decoration < FunctionType , FunctionDecoration > ( target , options ) ; } class FunctionTypeExtension extends TypeExtension < FunctionType , FunctionDecoration > { private readonly functionRegistry = typeof FinalizationRegistry !== 'undefined' ? new FinalizationRegistry ( ( func ) => { if ( ! this . thread . isClosed ( ) ) { this . thread . lua . luaL_unref ( this . thread . address , LUA_REGISTRYINDEX , func ) ; } } ) : undefined ; private gcPointer : number ; public constructor ( thread ) { super ( thread , 'js_function' ) ; if ( ! this . functionRegistry ) { console . warn ( 'FunctionTypeExtension: FinalizationRegistry not found. Memory leaks likely.' ) ; } this . gcPointer = thread . lua . module . addFunction ( ( calledL ) => { const userDataPointer = thread . lua . luaL_checkudata ( calledL , 1 , this . name ) ; const functionPointer = thread . lua . module . getValue ( userDataPointer , '*' ) ; thread . lua . module . removeFunction ( functionPointer ) ; return LuaReturn . Ok ; } , 'ii' ) ; if ( thread . lua . luaL_newmetatable ( thread . address , this . name ) ) { thread . lua . lua_pushstring ( thread . address , '__gc' ) ; thread . lua . lua_pushcclosure ( thread . address , this . gcPointer , 0 ) ; thread . lua . lua_settable ( thread . address , - 3 ) ; thread . lua . lua_pushstring ( thread . address , '__metatable' ) ; thread . lua . lua_pushstring ( thread . address , 'protected metatable' ) ; thread . lua . lua_settable ( thread . address , - 3 ) ; } thread . lua . lua_pop ( thread . address , 1 ) ; } public close ( ) { this . thread . lua . module . removeFunction ( this . gcPointer ) ; } public isType ( _thread , _index , type ) { return type === LuaType . Function ; } public pushValue ( thread , { target , options } ) { if ( typeof target !== 'function' ) { return false ; } const pointer = thread . lua . module . addFunction ( ( calledL ) => { const calledThread = thread . stateToThread ( calledL ) ; const argsQuantity = calledThread . getTop ( ) ; const args = [ ] ; if ( options . receiveThread ) { args . push ( calledThread ) ; } if ( options . receiveArgsQuantity ) { args . push ( argsQuantity ) ; } else { for ( let i = 1 ; i <= argsQuantity ; i ++ ) { const value = calledThread . getValue ( i ) ; if ( i !== 1 || ! options ?. self || value !== options . self ) { args . push ( value ) ; } } } try { const result = target . apply ( options ?. self , args ) ; if ( result === undefined ) { return 0 ; } else if ( result instanceof RawResult ) { return result . count ; } else if ( result instanceof MultiReturn ) { for ( const item of result ) { calledThread . pushValue ( item ) ; } return result . length ; } else { calledThread . pushValue ( result ) ; return 1 ; } } catch ( err ) { if ( err === 'longjmp' ) { throw err ; } calledThread . pushValue ( err ) ; return calledThread . lua . lua_error ( calledThread . address ) ; } } , 'ii' ) ; this . createAndPushFunctionReference ( thread , pointer ) ; thread . lua . lua_pushcclosure ( thread . address , pointer , 1 ) ; return true ; } public getValue ( thread , index ) { thread . lua . lua_pushvalue ( thread . address , index ) ; const func = thread . lua . luaL_ref ( thread . address , LUA_REGISTRYINDEX ) ; const jsFunc = ( ... args ) : any => { if ( thread . isClosed ( ) ) { console . warn ( 'Tried to call a function after closing lua state' ) ; return ; } const internalType = thread . lua . lua_rawgeti ( thread . address , LUA_REGISTRYINDEX , func ) ; if ( internalType !== LuaType . Function ) { throw new Error ( `A function of type '${ internalType }' was pushed, expected is ${ LuaType . Function }` ) ; } for ( const arg of args ) { thread . pushValue ( arg ) ; } const status = thread . lua . lua_pcallk ( thread . address , args . length , 1 , 0 , 0 , null ) ; if ( status === LuaReturn . Yield ) { throw new Error ( 'cannot yield in callbacks from javascript' ) ; } thread . assertOk ( status ) ; const result = thread . getValue ( - 1 ) ; thread . pop ( ) ; return result ; } ; this . functionRegistry ?. register ( jsFunc , func ) ; return jsFunc ; } private createAndPushFunctionReference ( thread , pointer ) { const userDataPointer = thread . lua . lua_newuserdatauv ( thread . address , PointerSize , 0 ) ; thread . lua . module . setValue ( userDataPointer , pointer , '*' ) ; if ( LuaType . Nil === thread . lua . luaL_getmetatable ( thread . address , this . name ) ) { thread . pop ( 2 ) ; throw new Error ( `metatable not found: ${ this . name }` ) ; } thread . lua . lua_setmetatable ( thread . address , - 2 ) ; } } export default function createTypeExtension ( thread ) { return new FunctionTypeExtension ( thread ) ; }