import { BaseDecorationOptions , Decoration } from '../decoration' ; import { LuaReturn , LuaState , LuaType } from '../types' ; import { decorateFunction } from './function' ; import Global from '../global' ; import MultiReturn from '../multireturn' ; import Thread from '../thread' ; import TypeExtension from '../type-extension' ; export interface ProxyDecorationOptions extends BaseDecorationOptions { proxy ? : boolean ; } export function decorateProxy ( target , options ? ) { return new Decoration < any , ProxyDecorationOptions > ( target , options || { } ) ; } class ProxyTypeExtension extends TypeExtension < any , ProxyDecorationOptions > { private readonly gcPointer : number ; public constructor ( thread ) { super ( thread , 'js_proxy' ) ; 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' ) ; thread . pushValue ( ( self , key ) => { switch ( typeof key ) { case 'number' : key = key - 1 ; case 'string' : break ; default : throw new Error ( 'Only strings or numbers can index js objects' ) ; } const value = self [ key as string | number ] ; if ( typeof value === 'function' ) { return decorateFunction ( value as ( ... args : any [ ] ) => any , { self } ) ; } return value ; } ) ; thread . lua . lua_setfield ( thread . address , metatableIndex , '__index' ) ; thread . pushValue ( ( self , key , value ) => { switch ( typeof key ) { case 'number' : key = key - 1 ; case 'string' : break ; default : throw new Error ( 'Only strings or numbers can index js objects' ) ; } self [ key as string | number ] = value ; } ) ; thread . lua . lua_setfield ( thread . address , metatableIndex , '__newindex' ) ; thread . pushValue ( ( self ) => { return self . toString ?. ( ) ?? typeof self ; } ) ; thread . lua . lua_setfield ( thread . address , metatableIndex , '__tostring' ) ; thread . pushValue ( ( self ) => { return self . length || 0 ; } ) ; thread . lua . lua_setfield ( thread . address , metatableIndex , '__len' ) ; thread . pushValue ( ( self ) => { const keys = Object . getOwnPropertyNames ( self ) ; let i = 0 ; return MultiReturn . of ( ( ) => { const ret = MultiReturn . of ( keys [ i ] , self [ keys [ i ] ] ) ; i ++ ; return ret ; } , self , null ) ; } ) ; thread . lua . lua_setfield ( thread . address , metatableIndex , '__pairs' ) ; thread . pushValue ( ( self , other ) => { return self === other ; } ) ; thread . lua . lua_setfield ( thread . address , metatableIndex , '__eq' ) ; thread . pushValue ( ( self , ... args ) => { if ( args [ 0 ] === self ) { args . shift ( ) ; } return self ( ... args ) ; } ) ; thread . lua . lua_setfield ( thread . address , metatableIndex , '__call' ) ; } thread . lua . lua_pop ( thread . address , 1 ) ; } public isType ( _thread , _index , type , name ? ) { return type === LuaType . Userdata && name === this . name ; } public getValue ( thread , index ) { const refUserdata = thread . lua . lua_touserdata ( thread . address , index ) ; const referencePointer = thread . lua . module . getValue ( refUserdata , '*' ) ; return thread . lua . getRef ( referencePointer ) ; } public pushValue ( thread , decoratedValue ) { const { target , options } = decoratedValue ; if ( options . proxy === undefined ) { if ( target === null || target === undefined ) { return false ; } if ( typeof target !== 'object' ) { const isClass = typeof target === 'function' && target . prototype ?. constructor === target && target . toString ( ) . startsWith ( 'class ' ) ; if ( ! isClass ) { return false ; } } if ( Promise . resolve ( target ) === target ) { return false ; } } else if ( options . proxy === false ) { return false ; } if ( options . metatable && ! ( options . metatable instanceof Decoration ) ) { decoratedValue . options . metatable = decorateProxy ( options . metatable , { proxy : false } ) ; return false ; } return super . pushValue ( thread , decoratedValue ) ; } public close ( ) { this . thread . lua . module . removeFunction ( this . gcPointer ) ; } } export default function createTypeExtension ( thread ) { return new ProxyTypeExtension ( thread ) ; }