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
75 changes: 57 additions & 18 deletions lua/d3bot/1_navmesh_sv.lua
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,68 @@ return function(lib)
return file.Exists(lib.GetMapNavMeshPath(mapName), "DATA")
end

util.AddNetworkString(lib.MapNavMeshNetworkStr)
function lib.UploadMapNavMesh(plOrPls)
local rawData = util.Compress(lib.MapNavMesh:Serialize()) or ""
local dataLen = rawData:len()
local maxChunkSize = 2^16 - 10 -- Leave 10 bytes for other stuff than the data.
local uploadQueue = {}
local uploadWorker = nil
local uploading = false

local NextTick = 0
local function MapNavMeshUpload()
if NextTick > CurTime() then return end
NextTick = CurTime() + 0.2

for i = 1, dataLen, maxChunkSize do
local dataLeft = dataLen + 1 - i
local chunkSize = math.min(maxChunkSize, dataLeft)
local subDataComp = string.sub(rawData, i, i + chunkSize - 1)
local running, err = D3bot.Async.Run(uploadWorker, function(worker)
local rawData = util.Compress(lib.MapNavMesh:Serialize()) or ""
local dataLen = rawData:len()
local maxChunkSize = 2^16 - 10 -- Leave 10 bytes for other stuff than the data.

for i = 1, dataLen, maxChunkSize do
local dataLeft = dataLen + 1 - i
local chunkSize = math.min(maxChunkSize, dataLeft)
local subDataComp = string.sub(rawData, i, i + chunkSize - 1)

net.Start(lib.MapNavMeshNetworkStr, false)
net.WriteBool(false)
net.WriteUInt(chunkSize, 16)
net.WriteData(subDataComp, chunkSize)
net.Send(worker:GetData())

coroutine.yield()
end

-- Finish the transfer.
net.Start(lib.MapNavMeshNetworkStr, false)
net.WriteBool(false)
net.WriteUInt(chunkSize, 16)
net.WriteData(subDataComp, chunkSize)
net.Send(plOrPls)
net.WriteBool(true)
net.WriteUInt(0, 16)
net.Send(worker:GetData())
end)

if err then
print(string.format("D3bot: Navmesh upload worker failed: %s", err))
end

if not running then
if not uploadQueue[1] then
hook.Remove("Think", "d3bot.MapNavMeshUpload")
uploading = false
elseif uploadWorker:GetFinished() then
uploadWorker = D3bot.Async.CreateWorker()
uploadWorker:SetData(table.remove(uploadQueue, 1))
uploading = true
end
end
end

-- Finish the transfer.
net.Start(lib.MapNavMeshNetworkStr, false)
net.WriteBool(true)
net.WriteUInt(0, 16)
net.Send(plOrPls)
util.AddNetworkString(lib.MapNavMeshNetworkStr)

function lib.UploadMapNavMesh(plOrPls)
uploadQueue[#uploadQueue + 1] = plOrPls

if not uploading then
uploadWorker = D3bot.Async.CreateWorker()
uploadWorker:SetData(table.remove(uploadQueue, 1))
hook.Add("Think", "d3bot.MapNavMeshUpload", MapNavMeshUpload)
uploading = true
end
end

file.CreateDir(lib.MapNavMeshDir)
Expand Down
3 changes: 3 additions & 0 deletions lua/d3bot/2_mapnavmeshui_cl.lua
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ return function(lib)
if isEnabled then
nodeVisualProperties = {}
hook.Add("Think", hooksId, function()
if not lib.MapNavMesh then return end
cursoredItemOrNil = lib.MapNavMesh:GetCursoredItemOrNil(LocalPlayer())

local smartDraw = D3bot.Convar_Navmeshing_SmartDraw:GetBool()
Expand Down Expand Up @@ -96,6 +97,7 @@ return function(lib)

end)
hook.Add("PostDrawOpaqueRenderables", hooksId, function(bDrawingDepth, bDrawingSkybox)
if not lib.MapNavMesh then return end
if forceDrawInSkyboxCounter > 1 then
forceDrawInSkybox = true
print("D3bot: Force drawing of navmesh in skybox, it won't draw correctly otherwise.")
Expand Down Expand Up @@ -408,6 +410,7 @@ return function(lib)
end
end)
hook.Add("HUDPaint", hooksId, function()
if not lib.MapNavMesh then return end
local smartDraw = D3bot.Convar_Navmeshing_SmartDraw:GetBool()
local maxDrawingDistanceSqr = math.pow(math.min(D3bot.Convar_Navmeshing_DrawDistance:GetInt(), 500), 2)
if maxDrawingDistanceSqr <= 0 then maxDrawingDistanceSqr = 500*500 end
Expand Down
58 changes: 51 additions & 7 deletions lua/d3bot/sh_async.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,74 @@ local ASYNC = D3bot.Async
---Runs the given function asynchronously.
---This can be used to run blocking functions *seemingly* parallel to other code.
---There is no parallelism, so ASYNC.Run has to be called until it returns false, which means the function has ended, or has panicked.
---@param state table @Contains the coroutine and its state. Initialize and reuse an empty table for this.
---@param worker table @Contains the coroutine and its state. Use a table created with D3bot.CreateWorker()
---@param func function @The function to call asynchronously.
---@return boolean running
---@return string | nil err @The error message, if there was any error.
function ASYNC.Run(state, func)
function ASYNC.Run(worker, func)
-- Start coroutine on the first call.
local cr = state[1]
local cr = worker:GetCoroutine()
if not cr then
cr = coroutine.create(func)
state[1] = cr
worker:SetCoroutine(cr)
end

-- Resume coroutine, catch and print any error.
local succ, msg = coroutine.resume(cr)
local succ, msg = coroutine.resume(cr, worker)
if not succ then
-- Coroutine ended unexpectedly.
print(string.format("D3bot: %s failed: %s.", cr, msg))
worker:SetFinished()
return false, string.format("%s failed: %s", cr, msg)
end

-- Check if the coroutine finished. We will never encounter "running", as we don't call coroutine.status from inside the coroutine.
if coroutine.status(cr) ~= "suspended" then
if not cr or coroutine.status(cr) ~= "suspended" then
worker:SetFinished(true)
return false, nil
end

worker:SetActive(true)

return true, nil
end

---Defers the given function to be later run asynchronously. This does not immediately start running
---This can be used to run blocking functions *seemingly* parallel to other code.
---There is no parallelism, so ASYNC.Run has to be called until it returns false, which means the function has ended, or has panicked.
---@param worker table @Contains the coroutine and its state. Use a table created with D3bot.CreateWorker()
---@param func function @The function to call asynchronously.
---@return boolean created
function ASYNC.Defer(worker, func)
-- Start coroutine on the first call.
local cr = worker:GetCoroutine()
if not cr then
cr = coroutine.create(func)
worker:SetCoroutine(cr)
end

-- Check if the coroutine finished. We will never encounter "running", as we don't call coroutine.status from inside the coroutine.
if not cr or coroutine.status(cr) ~= "suspended" then
worker:SetFinished(true)
return false
end

return true
end

local WORKER = {}
WORKER.__index = WORKER
AccessorFunc(WORKER, "cr", "Coroutine")
AccessorFunc(WORKER, "active", "Active")
AccessorFunc(WORKER, "finished", "Finished")
AccessorFunc(WORKER, "data", "Data")

---Creates a new worker structure for use with
---@return table worker
function ASYNC.CreateWorker()
-- cr the coroutine
-- active shows if the worker has started, it will be set to nil when finished by ASYNC.Run
-- finished shows if the worker has finished, it will be true if the couroutine finishes successfully
-- data ambiguous persistent data (for organization)
local newWorker = { cr = nil, active = false, finished = false, data = nil }
return setmetatable(newWorker, WORKER)
end