| 23 | } |
| 24 | |
| 25 | class FunctionTypeExtension extends TypeExtension<FunctionType, FunctionDecoration> { |
| 26 | private readonly functionRegistry = |
| 27 | typeof FinalizationRegistry !== 'undefined' |
| 28 | ? new FinalizationRegistry((func: number) => { |
| 29 | if (!this.thread.isClosed()) { |
| 30 | this.thread.lua.luaL_unref(this.thread.address, LUA_REGISTRYINDEX, func) |
| 31 | } |
| 32 | }) |
| 33 | : undefined |
| 34 | |
| 35 | private gcPointer: number |
| 36 | private functionWrapper: number |
| 37 | private callbackContext: Thread |
| 38 | private callbackContextIndex: number |
| 39 | private options?: FunctionTypeExtensionOptions |
| 40 | |
| 41 | public constructor(thread: Global, options?: FunctionTypeExtensionOptions) { |
| 42 | super(thread, 'js_function') |
| 43 | |
| 44 | this.options = options |
| 45 | // Create a thread off of the global thread to be used to create function call threads without |
| 46 | // interfering with the global context. This creates a callback context that will always exist |
| 47 | // even if the thread that called getValue() has been destroyed. |
| 48 | this.callbackContext = thread.newThread() |
| 49 | // Pops it from the global stack but keeps it alive |
| 50 | this.callbackContextIndex = this.thread.lua.luaL_ref(thread.address, LUA_REGISTRYINDEX) |
| 51 | |
| 52 | if (!this.functionRegistry) { |
| 53 | console.warn('FunctionTypeExtension: FinalizationRegistry not found. Memory leaks likely.') |
| 54 | } |
| 55 | |
| 56 | this.gcPointer = thread.lua.module.addFunction((calledL: LuaState) => { |
| 57 | // Throws a lua error which does a jump if it does not match. |
| 58 | thread.lua.luaL_checkudata(calledL, 1, this.name) |
| 59 | |
| 60 | const userDataPointer = thread.lua.luaL_checkudata(calledL, 1, this.name) |
| 61 | const referencePointer = thread.lua.module.getValue(userDataPointer, '*') |
| 62 | thread.lua.unref(referencePointer) |
| 63 | |
| 64 | return LuaReturn.Ok |
| 65 | }, 'ii') |
| 66 | |
| 67 | // Creates metatable if it doesn't exist, always pushes it onto the stack. |
| 68 | if (thread.lua.luaL_newmetatable(thread.address, this.name)) { |
| 69 | thread.lua.lua_pushstring(thread.address, '__gc') |
| 70 | thread.lua.lua_pushcclosure(thread.address, this.gcPointer, 0) |
| 71 | thread.lua.lua_settable(thread.address, -3) |
| 72 | |
| 73 | thread.lua.lua_pushstring(thread.address, '__metatable') |
| 74 | thread.lua.lua_pushstring(thread.address, 'protected metatable') |
| 75 | thread.lua.lua_settable(thread.address, -3) |
| 76 | } |
| 77 | // Pop the metatable from the stack. |
| 78 | thread.lua.lua_pop(thread.address, 1) |
| 79 | |
| 80 | this.functionWrapper = thread.lua.module.addFunction((calledL: LuaState) => { |
| 81 | const calledThread = thread.stateToThread(calledL) |
| 82 | |