Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 98 additions & 50 deletions imports/dui/client.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<number, { used: boolean, txdObject: number|nil, version: number }>
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
75 changes: 66 additions & 9 deletions package/client/resource/dui/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,49 @@ import { cache } from '../cache';
const duis: Record<string, Dui> = {};
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;
Expand All @@ -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) {
Expand Down