868 lines
24 KiB
Lua
868 lines
24 KiB
Lua
--------------------------------------------------------------------------------
|
|
-- WireGPU class
|
|
--------------------------------------------------------------------------------
|
|
-- Usage:
|
|
-- Initialize:
|
|
-- self.GPU = WireGPU(self.Entity)
|
|
--
|
|
-- OnRemove:
|
|
-- self.GPU:Finalize()
|
|
--
|
|
-- Draw (if something changes):
|
|
-- self.GPU:RenderToGPU(function()
|
|
-- ...code...
|
|
-- end)
|
|
--
|
|
-- Draw (every frame):
|
|
-- self.GPU:Render()
|
|
--------------------------------------------------------------------------------
|
|
|
|
|
|
GPULib = {}
|
|
|
|
local GPU = {}
|
|
GPU.__index = GPU
|
|
GPULib.GPU = GPU
|
|
|
|
function GPULib.WireGPU(ent, ...)
|
|
local self = {
|
|
entindex = ent and ent:EntIndex() or 0,
|
|
Entity = ent or NULL,
|
|
}
|
|
setmetatable(self, GPU)
|
|
self:Initialize(...)
|
|
return self
|
|
end
|
|
WireGPU = GPULib.WireGPU
|
|
|
|
function GPU:SetTranslucentOverride(bool)
|
|
self.translucent = bool;
|
|
end
|
|
|
|
function GPU:GetInfo()
|
|
local ent = self.Entity
|
|
if not ent:IsValid() then ent = self.actualEntity end
|
|
if not ent then return end
|
|
|
|
local model = ent:GetModel()
|
|
local monitor = WireGPU_Monitors[model]
|
|
|
|
local pos = ent:LocalToWorld(monitor.offset)
|
|
local ang = ent:LocalToWorldAngles(monitor.rot)
|
|
|
|
return monitor, pos, ang
|
|
end
|
|
|
|
if CLIENT then
|
|
local materialCache = {}
|
|
function GPULib.Material(name)
|
|
if not materialCache[name] then
|
|
local protoMaterial = Material(name)
|
|
local textureName = protoMaterial:GetString("$basetexture")
|
|
local imageName = protoMaterial:GetName()
|
|
local materialParameters = {
|
|
["$basetexture"] = textureName,
|
|
["$vertexcolor"] = 1,
|
|
["$vertexalpha"] = 1,
|
|
}
|
|
materialCache[name] = CreateMaterial(imageName.."_DImage", "UnlitGeneric", materialParameters)
|
|
end
|
|
return materialCache[name]
|
|
end
|
|
|
|
-- Handles rendertarget caching
|
|
local RT_CACHE_SIZE = 32
|
|
local RenderTargetCache = { }
|
|
|
|
for i = 1,RT_CACHE_SIZE do
|
|
local Target = {
|
|
false, -- Is rendertarget in use
|
|
false -- The rendertarget (false if doesn't exist)
|
|
}
|
|
table.insert( RenderTargetCache, Target )
|
|
end
|
|
|
|
-- Returns a render target from the cache pool and marks it as used
|
|
local function GetRT()
|
|
|
|
for i, RT in pairs( RenderTargetCache ) do
|
|
if not RT[1] then -- not used
|
|
|
|
local rendertarget = RT[2]
|
|
if rendertarget then
|
|
RT[1] = true -- Mark as used
|
|
return rendertarget
|
|
end
|
|
|
|
end
|
|
end
|
|
|
|
-- No free rendertargets. Find first non used and create it.
|
|
for i, RT in pairs( RenderTargetCache ) do
|
|
if not RT[1] and RT[2] == false then -- not used and doesn't exist, let's create the render target.
|
|
|
|
local rendertarget = GetRenderTarget("WireGPU_RT_"..i, 512, 512)
|
|
|
|
if rendertarget then
|
|
RT[1] = true -- Mark as used
|
|
RT[2] = rendertarget -- Assign the RT
|
|
return rendertarget
|
|
else
|
|
RT[1] = true -- Mark as used since we couldn't create it
|
|
ErrorNoHalt("Wiremod: Render target ".."WireGPU_RT_"..i.." could not be created!\n")
|
|
end
|
|
|
|
end
|
|
end
|
|
|
|
ErrorNoHalt("All render targets are in use, some wire screens may not draw!\n")
|
|
return nil
|
|
|
|
end
|
|
|
|
-- Frees an used RT
|
|
local function FreeRT(rt)
|
|
|
|
for i, RT in pairs( RenderTargetCache ) do
|
|
if RT[2] == rt then
|
|
|
|
RT[1] = false
|
|
return
|
|
end
|
|
end
|
|
|
|
ErrorNoHalt("RT Screen ",rt," could not be freed (not found)\n")
|
|
|
|
end
|
|
|
|
//
|
|
// Create basic fonts
|
|
//
|
|
local fontData =
|
|
{
|
|
font="lucida console",
|
|
size=20,
|
|
weight=800,
|
|
antialias= true,
|
|
additive = false,
|
|
}
|
|
surface.CreateFont("WireGPU_ConsoleFont", fontData)
|
|
//
|
|
// Create screen textures and materials
|
|
//
|
|
WireGPU_matScreen = CreateMaterial("sprites/GPURT","UnlitGeneric",{
|
|
["$vertexcolor"] = 1,
|
|
["$vertexalpha"] = 1,
|
|
["$translucent"] = 1,
|
|
["$ignorez"] = 1,
|
|
["$nolod"] = 1,
|
|
})
|
|
WireGPU_matBuffer = CreateMaterial("sprites/GPUBUF","UnlitGeneric",{
|
|
["$vertexcolor"] = 1,
|
|
["$vertexalpha"] = 1,
|
|
["$translucent"] = 1,
|
|
["$ignorez"] = 1,
|
|
["$nolod"] = 1,
|
|
})
|
|
|
|
|
|
function GPU:Initialize(no_rendertarget)
|
|
if no_rendertarget then return nil end
|
|
-- Rendertarget cache management
|
|
|
|
-- This should not even happen.
|
|
if self.RT then
|
|
ErrorNoHalt("Warning: GPU:Initialize called, but an RT still existed. Maybe you are not killing it properly?")
|
|
FreeRT(self.RT)
|
|
end
|
|
|
|
-- find a free one
|
|
self.RT = GetRT()
|
|
if not self.RT then
|
|
return nil
|
|
end
|
|
|
|
-- clear the new RT
|
|
self.ForceClear = true
|
|
return self.RT
|
|
end
|
|
|
|
function GPULib.WireGPU(ent, ...)
|
|
local self = {
|
|
entindex = ent and ent:EntIndex() or 0,
|
|
Entity = ent or NULL,
|
|
}
|
|
setmetatable(self, GPU)
|
|
self:Initialize(...)
|
|
return self
|
|
end
|
|
|
|
function GPU:Finalize()
|
|
if not self.RT then return end
|
|
timer.Simple(0.2, function() -- This is to test if the entity has truly been removed. If you really know you need to remove the RT, call FreeRT()
|
|
if IsValid(self.Entity) then
|
|
--MsgN(self,"Entity still exists, exiting.")
|
|
return
|
|
end
|
|
self:FreeRT()
|
|
end)
|
|
end
|
|
|
|
function GPU:FreeRT()
|
|
FreeRT( self.RT )
|
|
self.RT = nil
|
|
end
|
|
|
|
function GPU:Clear(color)
|
|
if not self.RT then return end
|
|
render.ClearRenderTarget(self.RT, color or Color(0, 0, 0, 0))
|
|
end
|
|
|
|
local texcoords = {
|
|
[0] = {
|
|
{ u = 0, v = 0 },
|
|
{ u = 1, v = 0 },
|
|
{ u = 1, v = 1 },
|
|
{ u = 0, v = 1 },
|
|
},
|
|
{
|
|
{ u = 0, v = 1 },
|
|
{ u = 0, v = 0 },
|
|
{ u = 1, v = 0 },
|
|
{ u = 1, v = 1 },
|
|
},
|
|
{
|
|
{ u = 1, v = 1 },
|
|
{ u = 0, v = 1 },
|
|
{ u = 0, v = 0 },
|
|
{ u = 1, v = 0 },
|
|
},
|
|
{
|
|
{ u = 1, v = 0 },
|
|
{ u = 1, v = 1 },
|
|
{ u = 0, v = 1 },
|
|
{ u = 0, v = 0 },
|
|
},
|
|
}
|
|
-- helper function for GPU:Render
|
|
function GPU.DrawScreen(x, y, w, h, rotation, scale)
|
|
-- generate vertex data
|
|
local vertices = {
|
|
--[[
|
|
Vector(x , y ),
|
|
Vector(x+w, y ),
|
|
Vector(x+w, y+h),
|
|
Vector(x , y+h),
|
|
]]
|
|
{ x = x , y = y },
|
|
{ x = x+w, y = y },
|
|
{ x = x+w, y = y+h },
|
|
{ x = x , y = y+h },
|
|
}
|
|
|
|
-- rotation and scaling
|
|
local rotated_texcoords = texcoords[rotation] or texcoords[0]
|
|
for index,vertex in ipairs(vertices) do
|
|
local tex = rotated_texcoords[index]
|
|
if tex.u == 0 then
|
|
vertex.u = tex.u-scale
|
|
else
|
|
vertex.u = tex.u+scale
|
|
end
|
|
if tex.v == 0 then
|
|
vertex.v = tex.v-scale
|
|
else
|
|
vertex.v = tex.v+scale
|
|
end
|
|
end
|
|
|
|
surface.DrawPoly(vertices)
|
|
--render.DrawQuad(unpack(vertices))
|
|
end
|
|
|
|
function GPU:RenderToGPU(renderfunction)
|
|
if not self.RT then return end
|
|
|
|
if self.ForceClear then
|
|
self:Clear()
|
|
self.ForceClear = nil
|
|
end
|
|
|
|
local oldw = ScrW()
|
|
local oldh = ScrH()
|
|
|
|
local NewRT = self.RT
|
|
local OldRT = render.GetRenderTarget()
|
|
|
|
render.SetRenderTarget(NewRT)
|
|
render.SetViewPort(0, 0, 512, 512)
|
|
cam.Start2D()
|
|
local ok, err = xpcall(renderfunction, debug.traceback)
|
|
if not ok then WireLib.ErrorNoHalt(err) end
|
|
cam.End2D()
|
|
render.SetViewPort(0, 0, oldw, oldh)
|
|
render.SetRenderTarget(OldRT)
|
|
end
|
|
|
|
-- If width is specified, height is ignored. if neither is specified, a height of 512 is used.
|
|
function GPU:RenderToWorld(width, height, renderfunction, zoffset, emulateRT)
|
|
local monitor, pos, ang = self:GetInfo()
|
|
|
|
if zoffset then
|
|
pos = pos + ang:Up()*zoffset
|
|
end
|
|
|
|
if emulateRT then
|
|
pos = pos - ang:Right()*(monitor.y2-monitor.y1)/2
|
|
pos = pos - ang:Forward()*(monitor.x2-monitor.x1)/2
|
|
end
|
|
|
|
local h = width and width*monitor.RatioX or height or 512
|
|
local w = width or h/monitor.RatioX
|
|
local x = -w/2
|
|
local y = -h/2
|
|
|
|
local res = monitor.RS*512/h
|
|
cam.Start3D2D(pos, ang, res)
|
|
local ok, err = xpcall(renderfunction, debug.traceback, x, y, w, h, monitor, pos, ang, res)
|
|
if not ok then WireLib.ErrorNoHalt(err) end
|
|
cam.End3D2D()
|
|
end
|
|
|
|
function GPU:Render(rotation, scale, width, height, postrenderfunction)
|
|
if not self.RT then return end
|
|
|
|
local monitor, pos, ang = self:GetInfo()
|
|
|
|
local OldTex = WireGPU_matScreen:GetTexture("$basetexture")
|
|
WireGPU_matScreen:SetTexture("$basetexture", self.RT)
|
|
|
|
local res = monitor.RS
|
|
cam.Start3D2D(pos, ang, res)
|
|
local ok, err = xpcall(function()
|
|
local aspect = 1/monitor.RatioX
|
|
local w = (width or 512)*aspect
|
|
local h = (height or 512)
|
|
local x = -w/2
|
|
local y = -h/2
|
|
|
|
local translucent = self.translucent;
|
|
|
|
if translucent == nil then
|
|
translucent = monitor.translucent
|
|
end
|
|
|
|
if not translucent then
|
|
surface.SetDrawColor(0,0,0,255)
|
|
surface.DrawRect(-256*aspect,-256,512*aspect,512)
|
|
end
|
|
|
|
surface.SetDrawColor(255,255,255,255)
|
|
surface.SetMaterial(WireGPU_matScreen)
|
|
|
|
render.PushFilterMag(self.texture_filtering or TEXFILTER.POINT)
|
|
render.PushFilterMin(self.texture_filtering or TEXFILTER.POINT)
|
|
|
|
self.DrawScreen(x, y, w, h, rotation or 0, scale or 0)
|
|
|
|
render.PopFilterMin()
|
|
render.PopFilterMag()
|
|
|
|
if postrenderfunction then postrenderfunction(pos, ang, res, aspect, monitor) end
|
|
end, debug.traceback)
|
|
if not ok then WireLib.ErrorNoHalt(err) end
|
|
cam.End3D2D()
|
|
|
|
WireGPU_matScreen:SetTexture("$basetexture", OldTex)
|
|
end
|
|
|
|
-- compatibility
|
|
|
|
local GPUs = {}
|
|
|
|
function WireGPU_NeedRenderTarget(entindex)
|
|
if not GPUs[entindex] then GPUs[entindex] = GPULib.WireGPU(Entity(entindex)) end
|
|
return GPUs[entindex].RT
|
|
end
|
|
|
|
function WireGPU_GetMyRenderTarget(entindex)
|
|
local self = GPUs[entindex]
|
|
if self.RT then return self.RT end
|
|
|
|
return self:Initialize()
|
|
end
|
|
|
|
function WireGPU_ReturnRenderTarget(entindex)
|
|
return GPUs[entindex]:Finalize()
|
|
end
|
|
|
|
function WireGPU_DrawScreen(x, y, w, h, rotation, scale)
|
|
return GPU.DrawScreen(x, y, w, h, rotation, scale)
|
|
end
|
|
|
|
end
|
|
|
|
-- GPULib switcher functionality
|
|
if CLIENT then
|
|
|
|
usermessage.Hook("wire_gpulib_setent", function(um)
|
|
local screen = Entity(um:ReadShort())
|
|
if not screen:IsValid() then return end
|
|
if not screen.GPU then return end
|
|
|
|
local ent = Entity(um:ReadShort())
|
|
if not ent:IsValid() then return end
|
|
|
|
screen.GPU.Entity = ent
|
|
screen.GPU.entindex = ent:EntIndex()
|
|
|
|
if screen == ent then return end
|
|
|
|
screen.GPU.actualEntity = screen
|
|
|
|
local model = ent:GetModel()
|
|
local monitor = WireGPU_Monitors[model]
|
|
|
|
local h = 512*monitor.RS
|
|
local w = h/monitor.RatioX
|
|
local x = -w/2
|
|
local y = -h/2
|
|
|
|
local corners = {
|
|
{ x , y },
|
|
{ x , y+h },
|
|
{ x+w, y },
|
|
{ x+w, y+h },
|
|
}
|
|
|
|
local mins, maxs = screen:OBBMins(), screen:OBBMaxs()
|
|
|
|
local timerid = "wire_gpulib_updatebounds"..screen:EntIndex()
|
|
local function setbounds()
|
|
if not screen:IsValid() then
|
|
timer.Remove(timerid)
|
|
return
|
|
end
|
|
if not ent:IsValid() then
|
|
timer.Remove(timerid)
|
|
|
|
screen.ExtraRBoxPoints[1001] = nil
|
|
screen.ExtraRBoxPoints[1002] = nil
|
|
screen.ExtraRBoxPoints[1003] = nil
|
|
screen.ExtraRBoxPoints[1004] = nil
|
|
Wire_UpdateRenderBounds(screen)
|
|
|
|
screen.GPU.Entity = screen.GPU.actualEntity
|
|
screen.GPU.entindex = screen.GPU.actualEntity:EntIndex()
|
|
screen.GPU.actualEntity = nil
|
|
|
|
return
|
|
end
|
|
|
|
local ang = ent:LocalToWorldAngles(monitor.rot)
|
|
local pos = ent:LocalToWorld(monitor.offset)
|
|
|
|
screen.ExtraRBoxPoints = screen.ExtraRBoxPoints or {}
|
|
for i,x,y in ipairs_map(corners, unpack) do
|
|
local p = Vector(x, y, 0)
|
|
p:Rotate(ang)
|
|
p = screen:WorldToLocal(p+pos)
|
|
|
|
screen.ExtraRBoxPoints[i+1000] = p
|
|
end
|
|
|
|
Wire_UpdateRenderBounds(screen)
|
|
end
|
|
|
|
timer.Create(timerid, 5, 0, setbounds)
|
|
|
|
setbounds()
|
|
end) -- usermessage.Hook
|
|
|
|
elseif SERVER then
|
|
|
|
function GPULib.switchscreen(screen, ent)
|
|
screen.GPUEntity = ent
|
|
umsg.Start("wire_gpulib_setent")
|
|
umsg.Short(screen:EntIndex())
|
|
umsg.Short(ent:EntIndex())
|
|
umsg.End()
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- GPULib caching functionality
|
|
if CLIENT then
|
|
------------------------------------------------------------------------------
|
|
-- Attach cache receiver to this entity
|
|
------------------------------------------------------------------------------
|
|
local writeHandler = {}
|
|
function GPULib.ClientCacheCallback(ent, writeFunction)
|
|
writeHandler[ent and ent:EntIndex() or 0] = writeFunction
|
|
end
|
|
|
|
------------------------------------------------------------------------------
|
|
-- RLE-decompress incoming message
|
|
------------------------------------------------------------------------------
|
|
--[[local blockText = {
|
|
{ "[no offset]", "[1-offset]", "[2-offset]", "[4-offset]" },
|
|
{ "[no rep]", nil, "[rep 2]", "[rep 4]" },
|
|
{ "[cnt 1]", nil, "[cnt 2]", "[cnt 3]" },
|
|
{ "[1-byte]", "[2-byte]", "[4-byte]", "[marker]" },
|
|
} ]]--
|
|
|
|
local function GPULib_MemorySync(um)
|
|
-- Find the referenced entity
|
|
local GPUIdx = um:ReadLong()
|
|
local GPU = ents.GetByIndex(GPUIdx)
|
|
if not GPU then return end
|
|
if not GPU:IsValid() then return end
|
|
if not writeHandler[GPUIdx] then return end
|
|
|
|
-- Start reading blocks
|
|
local blockCount = 0
|
|
local currentOffset = 0
|
|
while true do
|
|
-- Read next block
|
|
blockCount = blockCount + 1
|
|
if blockCount > 256 then error("GPULib usermessage read error") return end
|
|
|
|
-- Read block flags
|
|
local dataFlags = um:ReadChar()+128
|
|
if dataFlags == 240 then return end
|
|
|
|
local offsetSize = dataFlags % 4
|
|
local repeatCount = math.floor(dataFlags/4) % 4
|
|
local dataCount = math.floor(dataFlags/16) % 4
|
|
local valueSize = math.floor(dataFlags/64) % 4
|
|
|
|
local Repeat = 0
|
|
local Count = 0
|
|
|
|
if offsetSize > 0 then
|
|
local deltaOffset = 0
|
|
if offsetSize == 1 then deltaOffset = um:ReadChar () end
|
|
if offsetSize == 2 then deltaOffset = um:ReadShort() end
|
|
if offsetSize == 3 then deltaOffset = um:ReadFloat() end
|
|
currentOffset = currentOffset + deltaOffset
|
|
--print(" dOffset = "..deltaOffset..", offset = "..currentOffset)
|
|
end
|
|
|
|
if dataCount == 0 then Count = 1 end
|
|
if dataCount == 1 then Count = um:ReadChar()+130 end
|
|
if dataCount == 2 then Count = 2 end
|
|
if dataCount == 3 then Count = 3 end
|
|
|
|
if repeatCount == 0 then Repeat = 1 end
|
|
if repeatCount == 1 then Repeat = um:ReadChar()+130 end
|
|
if repeatCount == 2 then Repeat = 2 end
|
|
if repeatCount == 3 then Repeat = 4 end
|
|
|
|
--[[print(" Block ",
|
|
blockText[1][offsetSize+1],
|
|
blockText[2][repeatCount+1] or ("[rep "..Repeat.."* ]"),
|
|
blockText[3][dataCount+1] or ("[cnt "..Count.."* ]"),
|
|
blockText[4][valueSize+1])]]--
|
|
|
|
for i=1,Count do
|
|
local Value = 0
|
|
if valueSize == 0 then Value = um:ReadChar() end
|
|
if valueSize == 1 then Value = um:ReadShort() end
|
|
if valueSize == 2 then Value = um:ReadLong() end
|
|
if valueSize == 3 then Value = um:ReadFloat() end
|
|
|
|
for j=1,Repeat do
|
|
--print(" ["..currentOffset.."] = "..Value)
|
|
writeHandler[GPUIdx](currentOffset,Value)
|
|
currentOffset = currentOffset + 1
|
|
end
|
|
end
|
|
end
|
|
end
|
|
usermessage.Hook("wire_memsync", GPULib_MemorySync)
|
|
elseif SERVER then
|
|
local CACHEMGR = {}
|
|
CACHEMGR.__index = CACHEMGR
|
|
GPULib.CACHEMGR = CACHEMGR
|
|
|
|
|
|
------------------------------------------------------------------------------
|
|
-- Create new cache manager (serverside)
|
|
------------------------------------------------------------------------------
|
|
function GPULib.GPUCacheManager(ent, orderMatters, ...)
|
|
local self = {
|
|
EntIndex = ent and ent:EntIndex() or 0,
|
|
Entity = ent or NULL,
|
|
}
|
|
setmetatable(self, CACHEMGR)
|
|
self.ValueOrderMatters = orderMatters
|
|
self.Enabled = true
|
|
self:Reset()
|
|
return self
|
|
end
|
|
GPUCacheManager = GPULib.GPUCacheManager
|
|
|
|
|
|
------------------------------------------------------------------------------
|
|
-- Get size of the value to write
|
|
------------------------------------------------------------------------------
|
|
local function getSize(value)
|
|
if (value >= -128) and (value <= 127) and (math.floor(value) == value) then return 1,false end
|
|
if (value >= -32768) and (value <= 32767) and (math.floor(value) == value) then return 2,false end
|
|
if (value >= -2147483648) and (value <= 2147483647) and (math.floor(value) == value) then return 4,false end
|
|
return 4,true
|
|
end
|
|
|
|
|
|
------------------------------------------------------------------------------
|
|
-- Initialize cache manager
|
|
------------------------------------------------------------------------------
|
|
function CACHEMGR:Reset()
|
|
self.Cache = {}
|
|
self.CacheBytes = 0
|
|
end
|
|
|
|
|
|
------------------------------------------------------------------------------
|
|
-- Write a single value to cache
|
|
------------------------------------------------------------------------------
|
|
function CACHEMGR:Write(Address,Value)
|
|
local valueSize,valueFloat
|
|
if Value then
|
|
valueSize,valueFloat = getSize(Value)
|
|
self.CacheBytes = self.CacheBytes + valueSize
|
|
end
|
|
|
|
table.insert(self.Cache,{ Address, Value, valueSize, valueFloat })
|
|
--if #self.Cache > 2048 then self:Flush() end
|
|
end
|
|
|
|
|
|
------------------------------------------------------------------------------
|
|
-- Send value right away
|
|
------------------------------------------------------------------------------
|
|
function CACHEMGR:WriteNow(Address,Value,forcePlayer)
|
|
umsg.Start("wire_memsync", forcePlayer)
|
|
umsg.Long(self.EntIndex)
|
|
umsg.Char(195-128)
|
|
umsg.Float(Address)
|
|
umsg.Float(Value)
|
|
umsg.Char(240-128)
|
|
umsg.End()
|
|
end
|
|
|
|
|
|
------------------------------------------------------------------------------
|
|
-- RLE-compress cache and send it
|
|
------------------------------------------------------------------------------
|
|
function CACHEMGR:Flush(forcePlayer)
|
|
-- Don't flush if nothing cached
|
|
if #self.Cache == 0 then return end
|
|
self.CacheBytes = 0
|
|
|
|
-- Sort cache so all addresses are continiously layed out
|
|
-- Do not sort if order at which values are written matters
|
|
if not self.ValueOrderMatters then
|
|
table.sort(self.Cache,function(A,B)
|
|
return A[1] < B[1]
|
|
end)
|
|
end
|
|
|
|
-- RLE-encode the data
|
|
local compressInfo = {}
|
|
for _,data in ipairs(self.Cache) do
|
|
local address,value,size,isfloat = data[1],(data[2] or 0),(data[3] or 1),(data[4] or false)
|
|
local compressBlock = compressInfo[#compressInfo]
|
|
local sequentialBlock
|
|
local previousBlockEnd
|
|
if compressBlock then
|
|
previousBlockEnd = compressBlock.Offset+#compressBlock.Data*compressBlock.Repeat
|
|
sequentialBlock = previousBlockEnd == address
|
|
end
|
|
|
|
if not compressBlock then
|
|
-- New block of data
|
|
compressBlock = {
|
|
Data = { value },
|
|
Offset = address,
|
|
SetOffset = address,
|
|
Repeat = 1,
|
|
Size = size,
|
|
IsFloat = isfloat,
|
|
}
|
|
table.insert(compressInfo,compressBlock)
|
|
elseif sequentialBlock and
|
|
(compressBlock.Size == size) then
|
|
-- Add to previous block of data
|
|
if (#compressBlock.Data == 1) and (compressBlock.Data[1] == value) and (sequentialBlock) and (compressBlock.Repeat < 256) then
|
|
-- RLE compression
|
|
compressBlock.Repeat = compressBlock.Repeat + 1
|
|
elseif compressBlock.Repeat > 1 then
|
|
-- Cant add to a repeating block, make new
|
|
compressBlock = {
|
|
Data = { value },
|
|
Offset = address,
|
|
Repeat = 1,
|
|
Size = size,
|
|
IsFloat = isfloat,
|
|
}
|
|
if not sequentialBlock then compressBlock.SetOffset = address-previousBlockEnd end
|
|
table.insert(compressInfo,compressBlock)
|
|
else
|
|
-- Append to a group of values, unless the block is too big
|
|
if #compressBlock.Data*compressBlock.Repeat*compressBlock.Size < 196 then
|
|
table.insert(compressBlock.Data,value)
|
|
else
|
|
-- Add it to a new block instead
|
|
compressBlock = {
|
|
Data = { value },
|
|
Offset = address,
|
|
Repeat = 1,
|
|
Size = size,
|
|
IsFloat = isfloat,
|
|
}
|
|
if not sequentialBlock then compressBlock.SetOffset = address-previousBlockEnd end
|
|
table.insert(compressInfo,compressBlock)
|
|
end
|
|
end
|
|
else
|
|
-- Create new block
|
|
compressBlock = {
|
|
Data = { value },
|
|
Offset = address,
|
|
Repeat = 1,
|
|
Size = size,
|
|
IsFloat = isfloat,
|
|
}
|
|
if not sequentialBlock then compressBlock.SetOffset = address-previousBlockEnd end
|
|
table.insert(compressInfo,compressBlock)
|
|
end
|
|
end
|
|
|
|
--PrintTable(compressInfo)
|
|
|
|
-- Start the message
|
|
local messageSize = 4
|
|
umsg.Start("wire_memsync", forcePlayer)
|
|
umsg.Long(self.EntIndex)
|
|
|
|
-- Start sending all compressed blocks
|
|
for k,v in ipairs(compressInfo) do
|
|
--======================================================================--
|
|
-- Generate flags for sending the data
|
|
--======================================================================--
|
|
-- [0..1] Delta offset
|
|
-- 0: no offset
|
|
-- 1: 1-byte offset
|
|
-- 2: 2-byte offset
|
|
-- 3: 4-byte offset
|
|
-- [2..3] Repeat count
|
|
-- 0: none
|
|
-- 1: repeat count 1-byte follows
|
|
-- 2: repeat 2 times
|
|
-- 3: repeat 4 times
|
|
-- [4..5] Data count
|
|
-- 0: 1 element
|
|
-- 1: data size 1-byte follows
|
|
-- 2: 2 elements
|
|
-- 3: 3 elements (but not floats)
|
|
-- [6..7] Size
|
|
-- 0: 1-byte
|
|
-- 1: 2-byte
|
|
-- 2: 4-byte int
|
|
-- 3: 4-byte float
|
|
--
|
|
-- If it's a special data marker, then bitmap is:
|
|
-- [0..1] Marker type
|
|
-- [2..5] Marker data
|
|
-- [6] 1
|
|
-- [7] 1
|
|
|
|
local dataFlags = 0
|
|
if v.SetOffset then
|
|
local offsetSize = getSize(v.SetOffset)
|
|
if offsetSize == 1 then dataFlags = dataFlags + 1 end
|
|
if offsetSize == 2 then dataFlags = dataFlags + 2 end
|
|
if offsetSize == 4 then dataFlags = dataFlags + 3 end
|
|
end
|
|
|
|
if v.Repeat > 1 then
|
|
if v.Repeat == 2 then dataFlags = dataFlags + 8
|
|
elseif v.Repeat == 4 then dataFlags = dataFlags + 12
|
|
else dataFlags = dataFlags + 4
|
|
end
|
|
end
|
|
|
|
if #v.Data > 1 then
|
|
if #v.Data == 2 then
|
|
dataFlags = dataFlags + 32
|
|
elseif (#v.Data == 3) and (not v.IsFloat) then
|
|
dataFlags = dataFlags + 48
|
|
else
|
|
dataFlags = dataFlags + 16
|
|
end
|
|
end
|
|
|
|
if v.Size == 1 then dataFlags = dataFlags + 0 end
|
|
if v.Size == 2 then dataFlags = dataFlags + 64 end
|
|
if (v.Size == 4) and (not v.IsFloat) then dataFlags = dataFlags + 128 end
|
|
if (v.Size == 4) and ( v.IsFloat) then dataFlags = dataFlags + 192 end
|
|
|
|
umsg.Char(dataFlags-128)
|
|
messageSize = messageSize + 4
|
|
|
|
|
|
--======================================================================--
|
|
-- Send the data
|
|
--======================================================================--
|
|
if v.SetOffset then
|
|
local offsetSize = getSize(v.SetOffset)
|
|
if offsetSize == 1 then umsg.Char (v.SetOffset) messageSize = messageSize + 1 end
|
|
if offsetSize == 2 then umsg.Short(v.SetOffset) messageSize = messageSize + 2 end
|
|
if offsetSize == 4 then umsg.Float(v.SetOffset) messageSize = messageSize + 4 end
|
|
end
|
|
|
|
if (#v.Data > 2) then
|
|
if (#v.Data ~= 3) or (v.IsFloat) then
|
|
umsg.Char(#v.Data-130) messageSize = messageSize + 1
|
|
end
|
|
end
|
|
|
|
if (v.Repeat > 1) and
|
|
(v.Repeat ~= 2) and
|
|
(v.Repeat ~= 4) then umsg.Char(v.Repeat-130) messageSize = messageSize + 1 end
|
|
|
|
for _,value in ipairs(v.Data) do
|
|
if v.Size == 1 then umsg.Char (value) messageSize = messageSize + 1 end
|
|
if v.Size == 2 then umsg.Short(value) messageSize = messageSize + 2 end
|
|
if (v.Size == 4) and (not v.IsFloat) then umsg.Long(value) messageSize = messageSize + 4 end
|
|
if (v.Size == 4) and ( v.IsFloat) then umsg.Float(value) messageSize = messageSize + 4 end
|
|
end
|
|
|
|
|
|
--======================================================================--
|
|
-- Check size of next data block. If it fits into usermessage, continue.
|
|
-- Otherwise just create new message
|
|
--======================================================================--
|
|
if compressInfo[k+1] then
|
|
local nextSize = #compressInfo[k+1].Data*compressInfo[k+1].Repeat*compressInfo[k+1].Size
|
|
if nextSize + messageSize > 248 then
|
|
umsg.Char(240-128)
|
|
umsg.End()
|
|
messageSize = 4
|
|
umsg.Start("wire_memsync", forcePlayer)
|
|
umsg.Long(self.EntIndex)
|
|
compressInfo[k+1].SetOffset = compressInfo[k+1].Offset -- Force set offset
|
|
end
|
|
else
|
|
umsg.Char(240-128)
|
|
umsg.End()
|
|
end
|
|
end
|
|
|
|
self.Cache = {}
|
|
end
|
|
end
|