local Textures = {} if (type (Require) ~= 'function') then loadstring(exports['nc_libraries']:extend('Extend'), 'Require')() end Require('Math') Require('File') Require('Hash') Require('Element') Require('Thread') local blankTextureGlobal = getElementData(root, 'nc_libraries:blankTexture') if not isElement(blankTextureGlobal) then blankTextureGlobal = dxCreateTexture(1, 1, 'dxt5') setElementData(root, 'nc_libraries:blankTexture', blankTextureGlobal, false) setElementData(blankTextureGlobal, 'Texture:Data', { Width = 1, Height = 1, CreatedAt = getTickCount(), Type = 'Texture', TextureType = 'dxt5', }) end Textures.blankTexture = blankTextureGlobal function Textures:Constructor() if not self.Initialized then self.Initialized = true else return self end if not Responsive or not _G.Responsive then _G.Responsive = Require('Interface/Responsive') end self.Responsive = _G.Responsive self.Icons = {} self.IconsIndexs = {} self.Textures = {} self.TexturesIndexs = {} return self end function Textures:SetDestroyTimeout(Timeout) assert(self.Initialized, 'Textures:SetDestroyTimeout: Not initialized') self.DestroyTimeout = Timeout end function Textures:GetDestroyTimeout() assert(self.Initialized, 'Textures:GetDestroyTimeout: Not initialized') return self.DestroyTimeout or 5 * 60 * 1000 end function Textures:RequestIcon(Width, Height, isResponsive, Raw, callback, ID) assert(self.Initialized, 'Textures:RequestIcon: Not initialized') assert(type(Width) == 'number', 'Textures:RequestIcon: Width is not a number') assert(type(Height) == 'number', 'Textures:RequestIcon: Height is not a number') assert(type(isResponsive) == 'boolean', 'Textures:RequestIcon: isResponsive is not a boolean') assert(type(Raw) == 'string', 'Textures:RequestIcon: Raw is not a string') local Width_ = isResponsive and self.Responsive:Respc(Width) or Width local Height_ = isResponsive and self.Responsive:Respc(Height) or Height local CacheKey if ID and ID ~= nil then CacheKey = md5(Width_ .. Height_ .. Raw .. tostring(ID)) else CacheKey = md5(Width_ .. Height_ .. Raw) end local Creation = false if not self.Icons[CacheKey] then local SVG_ = svgCreate(Width_, Height_, Raw, function(SVG) if isElement(SVG) then dxSetTextureEdge(SVG, 'clamp') end end) if isElement(SVG_) then setElementData(SVG_, 'Texture:Data', { Width = Width_, Height = Height_, isResponsive = isResponsive, [isFilePath(Raw) and 'Path' or 'Raw'] = Raw, Type = 'Svg', TextureType = 'dxt5', ID = ID, }) self.Icons[CacheKey] = { Element = SVG_, Width = Width_, Height = Height_, ID = ID, } self.IconsIndexs[SVG_] = CacheKey addEventHandler('onClientElementDestroy', SVG_, function() removeEventHandler('onClientElementDestroy', source, debug.getinfo(1).func) self.Icons[CacheKey] = nil self.IconsIndexs[source] = nil collectgarbage() end) if not self.MainThreadRunning then self:MainThread() end Creation = true end end if self.Icons[CacheKey] then self.Icons[CacheKey].LastUsage = getTickCount() end return (self.Icons[CacheKey] or {}).Element or self.blankTexture, Creation end function Textures:GetSizedTexture(Path, Width, Height) assert(self.Initialized, 'Textures:GetSizedTexture: Not initialized') local Index = md5(Path .. Width .. Height) if self.Textures[Index] and isElement(self.Textures[Index].Element) then self.Textures[Index].LastUsage = getTickCount() return self.Textures[Index].Element end return false end function Textures:RequestSizedTexture(Path, Width, Height, Renew, ...) assert(self.Initialized, 'Textures:RequestSizedTexture: Not initialized') assert(type(Path) == 'string', 'Textures:RequestSizedTexture: Path is not a string') assert(type(Width) == 'number', 'Textures:RequestSizedTexture: Width is not a number') assert(type(Height) == 'number', 'Textures:RequestSizedTexture: Height is not a number') assert(type(Renew) == 'boolean', 'Textures:RequestSizedTexture: Renew is not a boolean') local Index = md5(Path .. Width .. Height) if not self.Textures[Index] or not isElement(self.Textures[Index].Element) then local SourceTexture = dxCreateTexture(Path, ...) if SourceTexture then local RenderTarget = dxCreateRenderTarget(Width, Height, true) if RenderTarget then dxSetRenderTarget(RenderTarget, true) dxDrawImage(0, 0, Width, Height, SourceTexture) dxSetRenderTarget() destroyElement(SourceTexture) self.Textures[Index] = { Element = RenderTarget, CreatedAt = os.time(), Width = Width, Height = Height, } self.TexturesIndexs[RenderTarget] = Index setElementData(RenderTarget, 'Texture:Data', { Width = Width, Height = Height, Path = Path, Type = 'RenderTarget', TextureType = 'argb', }) addEventHandler('onClientElementDestroy', RenderTarget, function() removeEventHandler('onClientElementDestroy', source, debug.getinfo(1).func) self.Textures[Index] = nil self.TexturesIndexs[source] = nil collectgarbage() end, true, 'high+99') if not self.MainThreadRunning then self:MainThread() end else destroyElement(SourceTexture) end end end if Renew and self.Textures[Index] then self.Textures[Index].LastUsage = getTickCount() end return (self.Textures[Index] and isElement(self.Textures[Index].Element) and self.Textures[Index].Element) or self.blankTexture end function Textures:UpdateIconLastUsage(Index) assert(self.Initialized, 'Textures:UpdateIconLastUsage: Not initialized') --assert(type(Index) == 'string' or isElement(Index), 'Textures:UpdateIconLastUsage: Index is not a string or element ('..type(Index)..')') if type(Index) ~= 'string' and not isElement(Index) then outputDebugString(debug.traceback(), 1) return end if isElement(Index) then Index = self:GetIconIndex(Index) end if self.Icons[Index] then self.Icons[Index].LastUsage = getTickCount() end end function Textures:GetIcon(Width, Height, isResponsive, Raw) assert(self.Initialized, 'Textures:GetIcon: Not initialized') assert(type(Width) == 'number', 'Textures:GetIcon: Width is not a number') assert(type(Height) == 'number', 'Textures:GetIcon: Height is not a number') assert(type(Raw) == 'string', 'Textures:GetIcon: Raw is not a string ('..type(Raw)..')') local Width_ = isResponsive and self.Responsive:Respc(Width) or Width local Height_ = isResponsive and self.Responsive:Respc(Height) or Height local Index = md5(Width_..Height_..Raw) if self.Icons[Index] and isElement(self.Icons[Index].Element) then self.Icons[Index].LastUsage = getTickCount() return self.Icons[Index].Element end return false end function Textures:GetIconByIndex(Index) assert(self.Initialized, 'Textures:GetIconByIndex: Not initialized') assert(type(Index) == 'string', 'Textures:GetIconByIndex: Index is not a string ('..type(Index)..')') if self.Icons[Index] and isElement(self.Icons[Index].Element) then self.Icons[Index].LastUsage = getTickCount() return self.Icons[Index].Element end return false end function Textures:GetIconIndex(Element) assert(self.Initialized, 'Textures:GetIconIndex: Not initialized') assert(isElement(Element), 'Textures:GetIconIndex: Element is not an element') return self.IconsIndexs[Element] or false end function Textures:GetIconSize(Index) assert(self.Initialized, 'Textures:GetIconSize: Not initialized') assert(type(Index) == 'string' or isElement(Index), 'Textures:GetIconSize: Index is not a string or element ('..type(Index)..')') if isElement(Index) then Index = self:GetIconIndex(Index) end return (self.Icons[Index] or {}).Width or 0, (self.Icons[Index] or {}).Height or 0 end function Textures:GetIconWidth(Index) assert(self.Initialized, 'Textures:GetIconWidth: Not initialized') assert(type(Index) == 'string' or isElement(Index), 'Textures:GetIconWidth: Index is not a string or element ('..type(Index)..')') if isElement(Index) then Index = self:GetIconIndex(Index) end local Width = self:GetIconSize(Index) return Width end function Textures:GetIconHeight(Index) assert(self.Initialized, 'Textures:GetIconHeight: Not initialized') assert(type(Index) == 'string' or isElement(Index), 'Textures:GetIconHeight: Index is not a string or element ('..type(Index)..')') if isElement(Index) then Index = self:GetIconIndex(Index) end local Height = self:GetIconSize(Index) return Height end function Textures:RequestTexture(Path, Renew, ...) assert(self.Initialized, 'Textures:RequestTexture: Not initialized') assert(type(Path) == 'string', 'Textures:RequestTexture: Path is not a string') assert(type(Renew) == 'boolean', 'Textures:RequestTexture: Renew is not a boolean') -- deprecated Renew parameter local Index = md5(Path) if (not self.Textures[Index] or not isElement(self.Textures[Index].Element)) then if self.Textures[Index] and self.Textures[Index].Trying and self.Textures[Index].Trying >= 3 then return self.blankTexture end if self.Textures[Index] and self.Textures[Index].LastTry and (os.time() - self.Textures[Index].LastTry) < 5 then return self.blankTexture end local TextureCreated = dxCreateTexture(Path, ...) if TextureCreated then local Width, Height = dxGetMaterialSize(TextureCreated) local Thread = { Element = TextureCreated, CreatedAt = os.time(), Width = Width, Height = Height, } addEventHandler('onClientElementDestroy', Thread.Element, function() removeEventHandler('onClientElementDestroy', Thread.Element, debug.getinfo(1).func) self.Textures[Index] = nil self.TexturesIndexs[source] = nil Thread = nil collectgarbage() end, true, 'high+99') self.Textures[Index] = Thread self.TexturesIndexs[TextureCreated] = Index setElementData(TextureCreated, 'Texture:Data', { Width = Width, Height = Height, Type = 'Texture', TextureType = 'argb', CreatedAt = getTickCount(), [isFilePath(Path) and 'Path' or 'Raw'] = Path, }) if not self.MainThreadRunning then self:MainThread() end else local Thread = self.Textures[Index] or { Element = false, Trying = 0, } Thread.LastTry = os.time() Thread.Trying = Thread.Trying + 1 self.Textures[Index] = Thread end end if Renew then self.Textures[Index].LastUsage = getTickCount() end return (isElement(self.Textures[Index].Element) and self.Textures[Index].Element or Path) or false end function Textures:RequestARGBTexture(Path, Renew, ...) assert(self.Initialized, 'Textures:RequestTexture: Not initialized') assert(type(Path) == 'string', 'Textures:RequestTexture: Path is not a string') assert(type(Renew ) == 'boolean', 'Textures:RequestTexture: Renew is not a boolean') local Index = md5(Path) if (not self.Textures[Index] or not isElement(self.Textures[Index].Element)) then if self.Textures[Index] and self.Textures[Index].Trying and self.Textures[Index].Trying >= 3 then return self.blankTexture end if self.Textures[Index] and self.Textures[Index].LastTry and (os.time() - self.Textures[Index].LastTry) < 5 then return self.blankTexture end local _File = fileOpen(Path, true) local Content; if _File then Content = fileRead(_File, fileGetSize(_File)) fileClose(_File) end if Content then local Width, Height = dxGetPixelsSize(Content) if Width and Height then local TextureCreated = dxCreateTexture(Width, Height, 'argb', false, ...) if TextureCreated then if dxSetTexturePixels(TextureCreated, Content, 0, 0, Width, Height) then local Thread = { Element = TextureCreated, CreatedAt = os.time(), Width = Width, Height = Height, } addEventHandler('onClientElementDestroy', Thread.Element, function() removeEventHandler('onClientElementDestroy', Thread.Element, debug.getinfo(1).func) self.Textures[Index] = nil self.TexturesIndexs[source] = nil collectgarbage() end, true, 'high+99') self.Textures[Index] = Thread self.TexturesIndexs[TextureCreated] = Index setElementData(TextureCreated, 'Texture:Data', { Width = Width, Height = Height, CreatedAt = getTickCount(), Type = 'Texture', TextureType = 'argb', [isFilePath(Path) and 'Path' or 'Raw'] = Path, }) if not self.MainThreadRunning then self:MainThread() end else destroyElement(TextureCreated) end else local Thread = self.Textures[Index] or { Element = false, Trying = 0, } Thread.LastTry = os.time() Thread.Trying = Thread.Trying + 1 self.Textures[Index] = Thread end end end end if Renew then self.Textures[Index].LastUsage = getTickCount() end return (isElement(self.Textures[Index].Element) and self.Textures[Index].Element or Path) or false end function Textures:GetTexture(Path) assert(self.Initialized, 'Textures:GetTexture: Not initialized') assert(type(Path) == 'string', 'Textures:GetTexture: Path is not a string ('..type(Path)..')') local Index = md5(Path) if self.Textures[Index] and isElement(self.Textures[Index].Element) then self.Textures[Index].LastUsage = getTickCount() return self.Textures[Index].Element end return false end function Textures:UpdateTextureLastUsage(Index) assert(self.Initialized, 'Textures:UpdateTextureLastUsage: Not initialized') -- assert(type(Index) == 'string' or type (Index) == 'userdata', 'Textures:UpdateTextureLastUsage: Index is not a string or element ('..type(Index)..')') if type(Index) ~= 'string' and not isElement(Index) then outputDebugString(debug.traceback(), 1) return end if isElement(Index) then Index = self:GetTextureIndex(Index) end if self.Textures[Index] then self.Textures[Index].LastUsage = getTickCount() end end function Textures:GetTextureSize(Index) assert(self.Initialized, 'Textures:GetTextureSize: Not initialized') assert(type(Index) == 'string' or isElement(Index), 'Textures:GetTextureSize: Index is not a string or element ('..type(Index)..')') if isElement(Index) then Index = self:GetTextureIndex(Index) end return (self.Textures[Index] or {}).Width or 0, (self.Textures[Index] or {}).Height or 0 end function Textures:GetTextureWidth(Index) assert(self.Initialized, 'Textures:GetTextureWidth: Not initialized') assert(type(Index) == 'string' or isElement(Index), 'Textures:GetTextureWidth: Index is not a string or element ('..type(Index)..')') if isElement(Index) then Index = self:GetTextureIndex(Index) end local Width = self:GetTextureSize(Index) return Width end function Textures:GetTextureHeight(Index) assert(self.Initialized, 'Textures:GetTextureHeight: Not initialized') assert(type(Index) == 'string' or isElement(Index), 'Textures:GetTextureHeight: Index is not a string or element ('..type(Index)..')') if isElement(Index) then Index = self:GetTextureIndex(Index) end local Height = self:GetTextureSize(Index) return Height end function Textures:GetTextureIndex(Element) assert(self.Initialized, 'Textures:GetTextureIndex: Not initialized') assert(isElement(Element), 'Textures:GetTextureIndex: Element is not an element') return self.TexturesIndexs[Element] or false end function Textures:DestroyAll() for Id, element in pairs(self.Icons) do if isElement(element) then destroyElement(element) end end for Id, element in pairs(self.Textures) do if isElement(element.Element) then destroyElement(element.Element) end if self.Textures[Id] then self.Textures[Id] = nil end end self.Icons = {} self.Textures = {} self.MainThreadRunning = nil end function Textures:MainThread() if self.MainThreadRunning then return false, 'Main thread already running' end self.MainThreadRunning = true CreateThread(function(self) Wait (5 * 1000) -- wait 25 seconds to let the textures be created while self.MainThreadRunning and (next(self.Icons) ~= nil or next(self.Textures) ~= nil) do local Now = getTickCount() local DebugMode = getElementData(localPlayer, 'nc_libraries:debugMode') local ICONS_DESTROYED = 0 local TEXTURES_DESTROYED = 0 if next(self.Icons) ~= nil then for Id, Cache in pairs(self.Icons) do if isElement(Cache.Element) then if Cache.LastUsage and (Cache.LastUsage + (self:GetDestroyTimeout())) < Now then if DebugMode then outputDebugString('['..resourceName..'] Destroying icon: ' .. Id) end destroyElement(Cache.Element) ICONS_DESTROYED = ICONS_DESTROYED + 1 end if ICONS_DESTROYED >= 50 then break end else self.Icons[Id] = nil end end end if next(self.Textures) ~= nil then for Id, Cache in pairs(self.Textures) do if isElement(Cache.Element) then if Cache.LastUsage and (Cache.LastUsage + (self:GetDestroyTimeout())) < Now then if DebugMode then outputDebugString('['..resourceName..'] Destroying texture: ' .. Id) end destroyElement(Cache.Element) TEXTURES_DESTROYED = TEXTURES_DESTROYED + 1 end if TEXTURES_DESTROYED >= 10 then break end else self.Textures[Id] = nil end end end Wait(self:GetDestroyTimeout() / 2) -- wait 3 minutes to check again end self.MainThreadRunning = false end, self) end Textures:Constructor() _G.Textures = Textures; return Textures;