From 77761ba0f93ad594449d5b7fc6ba5354ab0319b2 Mon Sep 17 00:00:00 2001 From: Cyril Beeckman Date: Tue, 27 Jan 2026 20:45:05 +0100 Subject: [PATCH] feat(dui): implement texture slot management for Dui instances --- imports/dui/client.lua | 148 ++++++++++++++++++--------- package/client/resource/dui/index.ts | 75 ++++++++++++-- 2 files changed, 164 insertions(+), 59 deletions(-) diff --git a/imports/dui/client.lua b/imports/dui/client.lua index b88f8a316..454be0fdd 100644 --- a/imports/dui/client.lua +++ b/imports/dui/client.lua @@ -13,11 +13,10 @@ ---@field debug? boolean ---@class Dui : OxClass ----@field private private { id: string, debug: boolean } +---@field private private { id: string, debug: boolean, slotIndex: number } ---@field url string ---@field duiObject number ---@field duiHandle string ----@field runtimeTxd number ---@field txdObject number ---@field dictName string ---@field txtName string @@ -28,91 +27,140 @@ local duis = {} local currentId = 0 +-- Pool configuration +local POOL_SIZE = 50 +local POOL_TXD_NAME = "ox_lib_dui_pool" +local poolTxd = nil +---@type table +local textureSlots = {} + +local function initPool() + if poolTxd then return end + poolTxd = CreateRuntimeTxd(POOL_TXD_NAME) + for i = 1, POOL_SIZE do + textureSlots[i] = { used = false, txdObject = nil, version = 0 } + end +end + +---@return number|nil slotIndex +local function acquireSlot() + initPool() + for i = 1, POOL_SIZE do + if not textureSlots[i].used then + textureSlots[i].used = true + textureSlots[i].version = textureSlots[i].version + 1 + return i + end + end + return nil +end + +---@param slotIndex number +local function releaseSlot(slotIndex) + if slotIndex and textureSlots[slotIndex] then + textureSlots[slotIndex].used = false + end +end + +---@param slotIndex number +---@param version number +---@return string +local function getSlotTextureName(slotIndex, version) + return ("ox_lib_dui_txt_%d_v%d"):format(slotIndex, version) +end + ---@param data DuiProperties function lib.dui:constructor(data) - local time = GetGameTimer() - local id = ("%s_%s_%s"):format(cache.resource, time, currentId) - currentId = currentId + 1 - local dictName = ('ox_lib_dui_dict_%s'):format(id) - local txtName = ('ox_lib_dui_txt_%s'):format(id) - local duiObject = CreateDui(data.url, data.width, data.height) - local duiHandle = GetDuiHandle(duiObject) - local runtimeTxd = CreateRuntimeTxd(dictName) - local txdObject = CreateRuntimeTextureFromDuiHandle(runtimeTxd, txtName, duiHandle) - self.private.id = id - self.private.debug = data.debug or false - self.url = data.url - self.duiObject = duiObject - self.duiHandle = duiHandle - self.runtimeTxd = runtimeTxd - self.txdObject = txdObject - self.dictName = dictName - self.txtName = txtName - duis[id] = self - - if self.private.debug then - print(('Dui %s created'):format(id)) - end + local slotIndex = acquireSlot() + if not slotIndex then + error(("No available texture slots in pool (max %d)"):format(POOL_SIZE)) + end + + local time = GetGameTimer() + local id = ("%s_%s_%s"):format(cache.resource, time, currentId) + currentId = currentId + 1 + + local txtName = getSlotTextureName(slotIndex, textureSlots[slotIndex].version) + local duiObject = CreateDui(data.url, data.width, data.height) + local duiHandle = GetDuiHandle(duiObject) + local txdObject = CreateRuntimeTextureFromDuiHandle(poolTxd, txtName, duiHandle) + + textureSlots[slotIndex].txdObject = txdObject + + self.private.id = id + self.private.debug = data.debug or false + self.private.slotIndex = slotIndex + self.url = data.url + self.duiObject = duiObject + self.duiHandle = duiHandle + self.txdObject = txdObject + self.dictName = POOL_TXD_NAME + self.txtName = txtName + duis[id] = self + + if self.private.debug then + print(('Dui %s created (slot %d)'):format(id, slotIndex)) + end end function lib.dui:remove() - SetDuiUrl(self.duiObject, 'about:blank') - DestroyDui(self.duiObject) - duis[self.private.id] = nil - - if self.private.debug then - print(('Dui %s removed'):format(self.private.id)) - end + SetDuiUrl(self.duiObject, 'about:blank') + DestroyDui(self.duiObject) + releaseSlot(self.private.slotIndex) + duis[self.private.id] = nil + + if self.private.debug then + print(('Dui %s removed (slot %d released)'):format(self.private.id, self.private.slotIndex)) + end end ---@param url string function lib.dui:setUrl(url) - self.url = url - SetDuiUrl(self.duiObject, url) + self.url = url + SetDuiUrl(self.duiObject, url) - if self.private.debug then - print(('Dui %s url set to %s'):format(self.private.id, url)) - end + if self.private.debug then + print(('Dui %s url set to %s'):format(self.private.id, url)) + end end ---@param message table function lib.dui:sendMessage(message) - SendDuiMessage(self.duiObject, json.encode(message)) + SendDuiMessage(self.duiObject, json.encode(message)) - if self.private.debug then - print(('Dui %s message sent with data :'):format(self.private.id), json.encode(message, { indent = true })) - end + if self.private.debug then + print(('Dui %s message sent with data :'):format(self.private.id), json.encode(message, { indent = true })) + end end ---@param x number ---@param y number function lib.dui:sendMouseMove(x, y) - SendDuiMouseMove(self.duiObject, x, y) + SendDuiMouseMove(self.duiObject, x, y) end ---@param button 'left' | 'middle' | 'right' function lib.dui:sendMouseDown(button) - SendDuiMouseDown(self.duiObject, button) + SendDuiMouseDown(self.duiObject, button) end ---@param button 'left' | 'middle' | 'right' function lib.dui:sendMouseUp(button) - SendDuiMouseUp(self.duiObject, button) + SendDuiMouseUp(self.duiObject, button) end ---@param deltaX number ---@param deltaY number function lib.dui:sendMouseWheel(deltaX, deltaY) - SendDuiMouseWheel(self.duiObject, deltaY, deltaX) + SendDuiMouseWheel(self.duiObject, deltaY, deltaX) end - AddEventHandler('onResourceStop', function(resourceName) - if cache.resource ~= resourceName then return end + if cache.resource ~= resourceName then return end - for _, dui in pairs(duis) do - dui:remove() - end + for _, dui in pairs(duis) do + dui:remove() + end end) return lib.dui diff --git a/package/client/resource/dui/index.ts b/package/client/resource/dui/index.ts index 40d1ed848..a3de2a909 100644 --- a/package/client/resource/dui/index.ts +++ b/package/client/resource/dui/index.ts @@ -3,6 +3,49 @@ import { cache } from '../cache'; const duis: Record = {}; let currentId = 0; +// Pool configuration +const POOL_SIZE = 50; +const POOL_TXD_NAME = 'ox_lib_dui_pool'; +let poolTxd: number | null = null; + +interface TextureSlot { + used: boolean; + txdObject: number | null; + version: number; +} + +const textureSlots: TextureSlot[] = []; + +function initPool(): void { + if (poolTxd !== null) return; + poolTxd = CreateRuntimeTxd(POOL_TXD_NAME); + for (let i = 0; i < POOL_SIZE; i++) { + textureSlots[i] = { used: false, txdObject: null, version: 0 }; + } +} + +function acquireSlot(): number | null { + initPool(); + for (let i = 0; i < POOL_SIZE; i++) { + if (!textureSlots[i].used) { + textureSlots[i].used = true; + textureSlots[i].version++; + return i; + } + } + return null; +} + +function releaseSlot(slotIndex: number): void { + if (slotIndex >= 0 && textureSlots[slotIndex]) { + textureSlots[slotIndex].used = false; + } +} + +function getSlotTextureName(slotIndex: number, version: number): string { + return `ox_lib_dui_txt_${slotIndex}_v${version}`; +} + interface DuiProperties { url: string; width: number; @@ -13,38 +56,52 @@ interface DuiProperties { export class Dui { private id: string = ''; private debug: boolean = false; + private slotIndex: number = -1; url: string = ''; duiObject: number = 0; duiHandle: string = ''; - runtimeTxd: number = 0; txdObject: number = 0; dictName: string = ''; txtName: string = ''; constructor(data: DuiProperties) { + const slotIndex = acquireSlot(); + if (slotIndex === null) { + throw new Error(`No available texture slots in pool (max ${POOL_SIZE})`); + } + const time = GetGameTimer(); const id = `${cache.resource}_${time}_${currentId}`; currentId++; + + const txtName = getSlotTextureName(slotIndex, textureSlots[slotIndex].version); + const duiObject = CreateDui(data.url, data.width, data.height); + const duiHandle = GetDuiHandle(duiObject); + const txdObject = CreateRuntimeTextureFromDuiHandle(poolTxd!, txtName, duiHandle); + + textureSlots[slotIndex].txdObject = txdObject; + this.id = id; this.debug = data.debug || false; + this.slotIndex = slotIndex; this.url = data.url; - this.dictName = `ox_lib_dui_dict_${id}`; - this.txtName = `ox_lib_dui_txt_${id}`; - this.duiObject = CreateDui(data.url, data.width, data.height); - this.duiHandle = GetDuiHandle(this.duiObject); - this.runtimeTxd = CreateRuntimeTxd(this.dictName); - this.txdObject = CreateRuntimeTextureFromDuiHandle(this.runtimeTxd, this.txtName, this.duiHandle); + this.duiObject = duiObject; + this.duiHandle = duiHandle; + this.txdObject = txdObject; + this.dictName = POOL_TXD_NAME; + this.txtName = txtName; duis[id] = this; - if (this.debug) console.log(`Dui ${this.id} created`); + if (this.debug) console.log(`Dui ${this.id} created (slot ${slotIndex})`); } remove() { SetDuiUrl(this.duiObject, 'about:blank'); DestroyDui(this.duiObject); + releaseSlot(this.slotIndex); delete duis[this.id]; - if (this.debug) console.log(`Dui ${this.id} removed`); + if (this.debug) console.log(`Dui ${this.id} removed (slot ${this.slotIndex} released)`); } setUrl(url: string) {