Performance rewrite part 1

removed acking, took way too long with client delay

added better debug messages
This commit is contained in:
Foereaper
2021-07-21 10:16:54 +02:00
parent b7a4c9947e
commit 98a08522c5
2 changed files with 107 additions and 191 deletions

View File

@ -2,15 +2,16 @@ local debug = false
local CMH = {} local CMH = {}
local datacache = {} local datacache = {}
local delim = {"", "", "", "", ""}
local CSMHMsgPrefix = "" local pck = {REQ = 1, DAT = 2}
local delim = {"", "", "", ""}
local pck = {REQ = 1, ACK = 2, DAT = 3, NAK = 4}
-- HELPERS START -- HELPERS START
local function debugOut(msg) local function debugOut(prefix, x, msg)
prefix = prefix or ""
msg = msg or ""
x = x or ""
if(debug == true) then if(debug == true) then
print("CMH Debug: "..msg) print("["..date("%X", time()).."][CSMH]["..x.."]["..prefix.."]: "..msg)
end end
end end
@ -26,6 +27,7 @@ local function GenerateReqId()
end end
local function ParseMessage(str) local function ParseMessage(str)
str = str or ""
local output = {} local output = {}
local valTemp = {} local valTemp = {}
local typeTemp = {} local typeTemp = {}
@ -50,11 +52,11 @@ local function ParseMessage(str)
-- Convert value to correct type -- Convert value to correct type
for k, v in pairs(valTemp) do for k, v in pairs(valTemp) do
local varType = typeTemp[k] local varType = typeTemp[k]
if(varType == 2) then -- Ints if(varType == 3) then -- Ints
v = tonumber(v) v = tonumber(v)
elseif(varType == 3) then -- Tables elseif(varType == 4) then -- Tables
v = Smallfolk.loads(v, #v) v = Smallfolk.loads(v, #v)
elseif(varType == 4) then -- Booleans elseif(varType == 5) then -- Booleans
if(v == "true") then v = true else v = false end if(v == "true") then v = true else v = false end
end end
table.insert(output, v) table.insert(output, v)
@ -67,22 +69,22 @@ local function ParseMessage(str)
end end
local function ProcessVariables(reqId, ...) local function ProcessVariables(reqId, ...)
local arg = {...}
local splitLength = 200 local splitLength = 200
local arg = {...}
local msg = "" local msg = ""
for _, v in pairs(arg) do for _, v in pairs(arg) do
if(type(v) == "string") then if(type(v) == "string") then
msg = msg .. delim[1]
elseif(type(v) == "number") then
msg = msg .. delim[2] msg = msg .. delim[2]
elseif(type(v) == "number") then
msg = msg .. delim[3]
elseif(type(v) == "table") then elseif(type(v) == "table") then
-- use Smallfolk to convert table structure to string -- use Smallfolk to convert table structure to string
v = Smallfolk.dumps(v) v = Smallfolk.dumps(v)
msg = msg .. delim[3] msg = msg .. delim[4]
elseif(type(v) == "boolean") then elseif(type(v) == "boolean") then
v = tostring(v) v = tostring(v)
msg = msg .. delim[4] msg = msg .. delim[5]
end end
msg = msg .. v msg = msg .. v
end end
@ -92,8 +94,8 @@ local function ProcessVariables(reqId, ...)
end end
for i=1, msg:len(), splitLength do for i=1, msg:len(), splitLength do
datacache[reqId]["data"][#datacache[reqId]["data"]+1] = msg:sub(i,i+splitLength - 1)
datacache[reqId].count = datacache[reqId].count + 1 datacache[reqId].count = datacache[reqId].count + 1
datacache[reqId]["data"][datacache[reqId].count] = msg:sub(i,i+splitLength - 1)
end end
return datacache[reqId] return datacache[reqId]
@ -103,36 +105,28 @@ end
-- Rx START -- Rx START
function CMH.OnReceive(self, event, prefix, _, Type, sender) function CMH.OnReceive(self, event, header, data, Type, sender)
-- Ensure the sender and receiver is the same, the message is an addon message, and the message type is WHISPER -- Ensure the sender and receiver is the same, the message is an addon message, and the message type is WHISPER
if event == "CHAT_MSG_ADDON" and sender == UnitName("player") and Type == "WHISPER" then if event == "CHAT_MSG_ADDON" and sender == UnitName("player") and Type == "WHISPER" then
-- unpack and validate addon message structure -- unpack and validate addon message structure
local pfx, source, pckId, data = prefix:match("(...)(%u)(%d%d)(.+)") local pfx, source, pckId = header:match("(...)(%u)(%d%d)")
if not pfx or not source or not pckId then if not pfx or not source or not pckId then
return return
end end
-- Make sure we're only processing addon messages using our framework prefix character as welll as server messages -- Make sure we're only processing addon messages using our framework prefix character as well as client messages
if(pfx == CSMHMsgPrefix and source == "S") then if(pfx == delim[1] and source == "S") then
debugOut("Received CSMH packet, processing data.")
-- convert ID to number so we can compare with our packet list -- convert ID to number so we can compare with our packet list
pckId = tonumber(pckId) pckId = tonumber(pckId)
if(pckId == pck.REQ) then if(pckId == pck.REQ) then
debugOut("REQ received, data: "..data) debugOut("REQ", "Rx", "REQ received, data: "..data)
CMH.OnREQ(sender, data) CMH.OnREQ(sender, data)
elseif(pckId == pck.ACK) then
debugOut("ACK received, data: "..data)
CMH.OnACK(sender, data)
elseif(pckId == pck.DAT) then elseif(pckId == pck.DAT) then
debugOut("DAT received, data: "..data) debugOut("DAT", "Rx", "DAT received, data: "..data)
CMH.OnDAT(sender, data) CMH.OnDAT(sender, data)
elseif(pckId == pck.NAK) then
debugOut("NAK received, data: "..data)
CMH.OnNAK(sender, data)
else else
debugOut("Invalid packet ID, aborting") debugOut("ERR", "Rx", "Invalid packet type, aborting")
return return
end end
end end
@ -144,11 +138,11 @@ CMHFrame:RegisterEvent("CHAT_MSG_ADDON")
CMHFrame:SetScript("OnEvent", CMH.OnReceive) CMHFrame:SetScript("OnEvent", CMH.OnReceive)
function CMH.OnREQ(sender, data) function CMH.OnREQ(sender, data)
debugOut("Processing REQ data") debugOut("REQ", "Rx", "Processing data..")
-- split header string into proper variables and ensure the string is the expected format -- split header string into proper variables and ensure the string is the expected format
local functionId, linkCount, reqId, addon = data:match("(%d%d)(%d%d%d)(%w%w%w%w%w%w)(.+)"); local functionId, linkCount, reqId, addon = data:match("(%d%d)(%d%d%d)(%w%w%w%w%w%w)(.+)");
if not functionId or not linkCount or not reqId or not addon then if not functionId or not linkCount or not reqId or not addon then
debugOut("Malformed REQ data, aborting.") debugOut("REQ", "Rx", "Malformed data, aborting.")
return return
end end
@ -157,62 +151,43 @@ function CMH.OnREQ(sender, data)
-- if the addon does not exist, abort -- if the addon does not exist, abort
if not CMH[addon] then if not CMH[addon] then
CMH.SendNAK(reqId) debugOut("REQ", "Rx", "Invalid addon, aborting.")
debugOut("Invalid addon, aborting")
return return
end end
-- if the functionId does not exist for said addon, abort -- if the functionId does not exist for said addon, abort
if not CMH[addon][functionId] then if not CMH[addon][functionId] then
CMH.SendNAK(reqId) debugOut("REQ", "Rx", "Invalid addon function, aborting.")
debugOut("Invalid addon function, aborting")
return return
end end
-- the request cache already exists, this should not happen. -- the request cache already exists, this should not happen.
-- abort and send error to the client, as well as purge id from cache. -- abort and send error to the client, as well as purge id from cache.
if(datacache[reqId]) then if(datacache[reqId]) then
CMH.SendNAK(reqId)
datacache[reqId] = nil datacache[reqId] = nil
debugOut("Request cache already exists, aborting.") debugOut("REQ", "Rx", "Cache already exists, aborting.")
return return
end end
-- Insert header info for request id and prepare temporary data storage -- Insert header info for request id and prepare temporary data storage
datacache[reqId] = {addon = addon, funcId = functionId, count = linkCount, data = {}} datacache[reqId] = {addon = addon, funcId = functionId, count = linkCount, data = {}}
-- send ACK to client notifying client that data is ready to be received debugOut("REQ", "Rx", "Header validated, cache created. Awaitng data..")
debugOut("REQ OK, sending ACK..")
CMH.SendACK(reqId)
end
function CMH.OnACK(sender, data)
local reqId = data:match("(%w%w%w%w%w%w)");
if not reqId then
return
end
-- We received ACK but no data is available in cache. This should never happen
if not datacache[reqId] then
debugOut("ACK received but no data available to transmit. Aborting.")
return
end
-- If data exists, we send it
debugOut("ACK validated, data exists. Sending..")
CMH.SendDAT(reqId)
end end
function CMH.OnDAT(sender, data) function CMH.OnDAT(sender, data)
debugOut("DAT", "Rx", "Validating data..")
-- Separate REQ ID from payload and verify -- Separate REQ ID from payload and verify
local reqId, payload = data:match("(%w%w%w%w%w%w)(.*)"); local reqId = data:sub(1, 6)
if not reqId and not payload then local payload = data:sub(#reqId+1)
if not reqId then
debugOut("DAT", "Rx", "Request ID missing, aborting.")
return return
end end
-- If no REQ header info has been cached, abort -- If no REQ header info has been cached, abort
if not datacache[reqId] then if not datacache[reqId] then
debugOut("Data received, but not expected. Aborting.") debugOut("DAT", "Rx", "Cache does not exist, aborting.")
return return
end end
@ -222,28 +197,32 @@ function CMH.OnDAT(sender, data)
-- Some functions are trigger functions and expect no payload -- Some functions are trigger functions and expect no payload
-- Skip the rest of the functionality and call the expected function -- Skip the rest of the functionality and call the expected function
if reqTable.count == 0 then if reqTable.count == 0 then
debugOut("DAT", "Rx", "Function expects no data, triggering function..")
-- Retrieve the function from global namespace and pass variables if it exists -- Retrieve the function from global namespace and pass variables if it exists
local func = SMH[reqTable.addon][reqTable.funcId] local func = CMH[reqTable.addon][reqTable.funcId]
if func then if func then
debugOut(func)
_G[func](sender, {}) _G[func](sender, {})
datacache[reqId] = nil datacache[reqId] = nil
debugOut("DAT", "Rx", "Function "..func.." @ "..reqTable.addon.." executed, cache cleared.")
end end
return return
end end
-- If the size of the cache is larger than expected, abort -- If the size of the cache is larger than expected, abort
if sizeOfDataCache+1 > reqTable.count then if sizeOfDataCache+1 > reqTable.count then
debugOut("Received more data than expected. Aborting.") debugOut("DAT", "Rx", "Received more data than expected. Aborting.")
return return
end end
-- Add payload to cache and update size variable -- Add payload to cache and update size variable
reqTable["data"][sizeOfDataCache+1] = payload reqTable["data"][sizeOfDataCache+1] = payload
sizeOfDataCache = #reqTable.data sizeOfDataCache = #reqTable.data
debugOut("DAT", "Rx", "Data part "..sizeOfDataCache.." of "..reqTable.count.." added to cache.")
-- If the last expected message has been received, process it -- If the last expected message has been received, process it
if(sizeOfDataCache == reqTable.count) then if(sizeOfDataCache == reqTable.count) then
debugOut("DAT", "Rx", "All expected data received, processing..")
-- Concatenate the cache and parse the full payload for function variables to return -- Concatenate the cache and parse the full payload for function variables to return
local fullPayload = table.concat(reqTable.data); local fullPayload = table.concat(reqTable.data);
local VarTable = ParseMessage(fullPayload) local VarTable = ParseMessage(fullPayload)
@ -251,24 +230,11 @@ function CMH.OnDAT(sender, data)
-- Retrieve the function from global namespace and pass variables if it exists -- Retrieve the function from global namespace and pass variables if it exists
local func = CMH[reqTable.addon][reqTable.funcId] local func = CMH[reqTable.addon][reqTable.funcId]
if func then if func then
debugOut(func)
_G[func](sender, VarTable) _G[func](sender, VarTable)
end
-- Delete the request session cache
datacache[reqId] = nil datacache[reqId] = nil
debugOut("DAT", "Rx", "Function "..func.." @ "..reqTable.addon.." executed, cache cleared.")
end end
end end
function CMH.OnNAK(sender, data)
local reqId = data:match("(%w%w%w%w%w%w)");
if not reqId then
return
end
-- when we receive an error from the server, purge the local cache data
debugOut("Purging cache data with REQ ID: "..reqId)
datacache[reqId] = nil
end end
-- Rx END -- Rx END
@ -276,39 +242,30 @@ end
-- Tx START -- Tx START
function CMH.SendREQ(functionId, linkCount, reqId, addon) function CMH.SendREQ(functionId, linkCount, reqId, addon)
debugOut("Sending REQ with ID: "..reqId) local header = string.format("%01s%01s%02d", delim[1], "C", pck.REQ)
local send = string.format("%01s%01s%02d%02d%03d%06s%0"..tostring(#addon).."s", CSMHMsgPrefix, "C", pck.REQ, functionId, linkCount, reqId, addon) local data = string.format("%02d%03d%06s%0"..tostring(#addon).."s", functionId, linkCount, reqId, addon)
SendAddonMessage(send, "", "WHISPER", UnitName("player")) SendAddonMessage(header, data, "WHISPER", UnitName("player"))
end debugOut("REQ", "Tx", "Sent REQ with ID "..reqId..", sending DAT..")
function CMH.SendACK(reqId)
local send = string.format("%01s%01s%02d%06s", CSMHMsgPrefix, "C", pck.ACK, reqId)
SendAddonMessage(send, "", "WHISPER", UnitName("player"))
end end
function CMH.SendDAT(reqId) function CMH.SendDAT(reqId)
-- Build data message header -- Build data message header
local send = string.format("%01s%01s%02d%06s", CSMHMsgPrefix, "C", pck.DAT, reqId) local header = string.format("%01s%01s%02d", delim[1], "C", pck.DAT)
-- iterate all items in the message data cache and send -- iterate all items in the message data cache and send
-- functions can also be trigger functions without any data, only send header and no payload -- functions can also be trigger functions without any data, only send header and no payload
if(#datacache[reqId]["data"] == 0) then if(#datacache[reqId]["data"] == 0) then
SendAddonMessage(send, "", "WHISPER", UnitName("player")) SendAddonMessage(header, reqId, "WHISPER", UnitName("player"))
else else
for _, v in pairs (datacache[reqId]["data"]) do for _, v in pairs (datacache[reqId]["data"]) do
local payload = send..v local payload = reqId..v
SendAddonMessage(payload, "", "WHISPER", UnitName("player")) SendAddonMessage(header, payload, "WHISPER", UnitName("player"))
end end
end end
-- all items have been sent, cache can be purged -- all items have been sent, cache can be purged
debugOut("All data sent, cleaning up cache.")
datacache[reqId] = nil datacache[reqId] = nil
end debugOut("DAT", "Tx", "Sent all DAT for ID "..reqId..", cache cleared, closing.")
function CMH.SendNAK(reqId)
local send = string.format("%01s%01s%02d%06s", CSMHMsgPrefix, "C", pck.NAK, reqId)
SendAddonMessage(send, "", "WHISPER", UnitName("player"))
end end
-- Tx END -- Tx END
@ -332,6 +289,7 @@ function SendClientRequest(prefix, functionId, ...)
local varTable = ProcessVariables(reqId, ...) local varTable = ProcessVariables(reqId, ...)
CMH.SendREQ(functionId, varTable.count, reqId, prefix) CMH.SendREQ(functionId, varTable.count, reqId, prefix)
CMH.SendDAT(reqId)
end end
--A API END --A API END

View File

@ -4,15 +4,16 @@ local debug = false
local SMH = {} local SMH = {}
local datacache = {} local datacache = {}
local delim = {"", "", "", "", ""}
local CSMHMsgPrefix = "" local pck = {REQ = 1, DAT = 2}
local delim = {"", "", "", ""}
local pck = {REQ = 1, ACK = 2, DAT = 3, NAK = 4}
-- HELPERS START -- HELPERS START
local function debugOut(msg) local function debugOut(prefix, x, msg)
prefix = prefix or ""
msg = msg or ""
x = x or ""
if(debug == true) then if(debug == true) then
print("SMH Debug: "..msg) print("["..os.clock().."][CSMH]["..x.."]["..prefix.."]: "..msg)
end end
end end
@ -28,6 +29,7 @@ local function GenerateReqId()
end end
local function ParseMessage(str) local function ParseMessage(str)
str = str or ""
local output = {} local output = {}
local valTemp = {} local valTemp = {}
local typeTemp = {} local typeTemp = {}
@ -52,11 +54,11 @@ local function ParseMessage(str)
-- Convert value to correct type -- Convert value to correct type
for k, v in pairs(valTemp) do for k, v in pairs(valTemp) do
local varType = typeTemp[k] local varType = typeTemp[k]
if(varType == 2) then -- Ints if(varType == 3) then -- Ints
v = tonumber(v) v = tonumber(v)
elseif(varType == 3) then -- Tables elseif(varType == 4) then -- Tables
v = smallfolk.loads(v) v = smallfolk.loads(v)
elseif(varType == 4) then -- Booleans elseif(varType == 5) then -- Booleans
if(v == "true") then v = true else v = false end if(v == "true") then v = true else v = false end
end end
table.insert(output, v) table.insert(output, v)
@ -69,22 +71,22 @@ local function ParseMessage(str)
end end
local function ProcessVariables(sender, reqId, ...) local function ProcessVariables(sender, reqId, ...)
local arg = {...}
local splitLength = 200 local splitLength = 200
local arg = {...}
local msg = "" local msg = ""
for _, v in pairs(arg) do for _, v in pairs(arg) do
if(type(v) == "string") then if(type(v) == "string") then
msg = msg .. delim[1]
elseif(type(v) == "number") then
msg = msg .. delim[2] msg = msg .. delim[2]
elseif(type(v) == "number") then
msg = msg .. delim[3]
elseif(type(v) == "table") then elseif(type(v) == "table") then
-- use Smallfolk to convert table structure to string -- use Smallfolk to convert table structure to string
v = Smallfolk.dumps(v) v = Smallfolk.dumps(v)
msg = msg .. delim[3] msg = msg .. delim[4]
elseif(type(v) == "boolean") then elseif(type(v) == "boolean") then
v = tostring(v) v = tostring(v)
msg = msg .. delim[4] msg = msg .. delim[5]
end end
msg = msg .. v msg = msg .. v
end end
@ -96,8 +98,8 @@ local function ProcessVariables(sender, reqId, ...)
end end
for i=1, msg:len(), splitLength do for i=1, msg:len(), splitLength do
datacache[sender:GetGUIDLow()][reqId]["data"][#datacache[sender:GetGUIDLow()][reqId]["data"]+1] = msg:sub(i,i+splitLength - 1)
datacache[sender:GetGUIDLow()][reqId].count = datacache[sender:GetGUIDLow()][reqId].count + 1 datacache[sender:GetGUIDLow()][reqId].count = datacache[sender:GetGUIDLow()][reqId].count + 1
datacache[sender:GetGUIDLow()][reqId]["data"][datacache[sender:GetGUIDLow()][reqId].count] = msg:sub(i,i+splitLength - 1)
end end
return datacache[sender:GetGUIDLow()][reqId] return datacache[sender:GetGUIDLow()][reqId]
@ -107,7 +109,7 @@ end
-- Rx START -- Rx START
function SMH.OnReceive(event, sender, _type, prefix, _, target) function SMH.OnReceive(event, sender, _type, header, data, target)
-- Make sure the sender and receiver of the addon message is set and is the correct type. -- Make sure the sender and receiver of the addon message is set and is the correct type.
-- Prevents error spam in the console -- Prevents error spam in the console
if not sender or not target or not sender.GetName or not target.GetName or type(sender) ~= "userdata" or type(target) ~= "userdata" then if not sender or not target or not sender.GetName or not target.GetName or type(sender) ~= "userdata" or type(target) ~= "userdata" then
@ -117,32 +119,24 @@ function SMH.OnReceive(event, sender, _type, prefix, _, target)
-- Ensure the sender and receiver is the same, and the message type is WHISPER -- Ensure the sender and receiver is the same, and the message type is WHISPER
if sender:GetName() == target:GetName() and _type == 7 then if sender:GetName() == target:GetName() and _type == 7 then
-- unpack and validate addon message structure -- unpack and validate addon message structure
local pfx, source, pckId, data = prefix:match("(...)(%u)(%d%d)(.+)") local pfx, source, pckId = header:match("(...)(%u)(%d%d)")
if not pfx or not source or not pckId then if not pfx or not source or not pckId then
return return
end end
-- Make sure we're only processing addon messages using our framework prefix character as well as client messages -- Make sure we're only processing addon messages using our framework prefix character as well as client messages
if(pfx == CSMHMsgPrefix and source == "C") then if(pfx == delim[1] and source == "C") then
debugOut("Received CSMH packet, processing data.")
-- convert ID to number so we can compare with our packet list -- convert ID to number so we can compare with our packet list
pckId = tonumber(pckId) pckId = tonumber(pckId)
if(pckId == pck.REQ) then if(pckId == pck.REQ) then
debugOut("REQ received, data: "..data) debugOut("REQ", "Rx", "REQ received, data: "..data)
SMH.OnREQ(sender, data) SMH.OnREQ(sender, data)
elseif(pckId == pck.ACK) then
debugOut("ACK received, data: "..data)
SMH.OnACK(sender, data)
elseif(pckId == pck.DAT) then elseif(pckId == pck.DAT) then
debugOut("DAT received, data: "..data) debugOut("DAT", "Rx", "DAT received, data: "..data)
SMH.OnDAT(sender, data) SMH.OnDAT(sender, data)
elseif(pckId == pck.NAK) then
debugOut("NAK received, data: "..data)
SMH.OnNAK(sender, data)
else else
debugOut("Invalid packet ID, aborting") debugOut("ERR", "Rx", "Invalid packet type, aborting")
return return
end end
end end
@ -152,11 +146,11 @@ end
RegisterServerEvent(30, SMH.OnReceive) RegisterServerEvent(30, SMH.OnReceive)
function SMH.OnREQ(sender, data) function SMH.OnREQ(sender, data)
debugOut("Processing REQ data") debugOut("REQ", "Rx", "Processing data..")
-- split header string into proper variables and ensure the string is the expected format -- split header string into proper variables and ensure the string is the expected format
local functionId, linkCount, reqId, addon = data:match("(%d%d)(%d%d%d)(%w%w%w%w%w%w)(.+)"); local functionId, linkCount, reqId, addon = data:match("(%d%d)(%d%d%d)(%w%w%w%w%w%w)(.+)");
if not functionId or not linkCount or not reqId or not addon then if not functionId or not linkCount or not reqId or not addon then
debugOut("Malformed REQ data, aborting.") debugOut("REQ", "Rx", "Malformed data, aborting.")
return return
end end
@ -165,15 +159,13 @@ function SMH.OnREQ(sender, data)
-- if the addon does not exist, abort -- if the addon does not exist, abort
if not SMH[addon] then if not SMH[addon] then
SMH.SendNAK(sender, reqId) debugOut("REQ", "Rx", "Invalid addon, aborting.")
debugOut("Invalid addon, aborting")
return return
end end
-- if the functionId does not exist for said addon, abort -- if the functionId does not exist for said addon, abort
if not SMH[addon][functionId] then if not SMH[addon][functionId] then
SMH.SendNAK(sender, reqId) debugOut("REQ", "Rx", "Invalid addon function, aborting.")
debugOut("Invalid addon function, aborting")
return return
end end
@ -183,47 +175,31 @@ function SMH.OnREQ(sender, data)
-- the request cache already exists, this should not happen. -- the request cache already exists, this should not happen.
-- abort and send error to the client, as well as purge id from cache. -- abort and send error to the client, as well as purge id from cache.
if(datacache[sender:GetGUIDLow()][reqId]) then if(datacache[sender:GetGUIDLow()][reqId]) then
SMH.SendNAK(sender, reqId)
datacache[sender:GetGUIDLow()][reqId] = nil datacache[sender:GetGUIDLow()][reqId] = nil
debugOut("Request cache already exists, aborting.") debugOut("REQ", "Rx", "Cache already exists, aborting.")
return return
end end
-- Insert header info for request id and prepare temporary data storage -- Insert header info for request id and prepare temporary data storage
datacache[sender:GetGUIDLow()][reqId] = {addon = addon, funcId = functionId, count = linkCount, data = {}} datacache[sender:GetGUIDLow()][reqId] = {addon = addon, funcId = functionId, count = linkCount, data = {}}
-- send ACK to client notifying client that data is ready to be received debugOut("REQ", "Rx", "Header validated, cache created. Awaitng data..")
debugOut("REQ OK, sending ACK..")
SMH.SendACK(sender, reqId)
end end
function SMH.OnACK(sender, data)
local reqId = data:match("(%w%w%w%w%w%w)");
if not reqId then
return
end
-- We received ACK but no data is available in cache. This should never happen
if not datacache[sender:GetGUIDLow()][reqId] then
debugOut("ACK received but no data available to transmit. Aborting.")
return
end
-- If data exists, we send it
debugOut("ACK validated, data exists. Sending..")
SMH.SendDAT(sender, reqId)
end
function SMH.OnDAT(sender, data) function SMH.OnDAT(sender, data)
debugOut("DAT", "Rx", "Validating data..")
-- Separate REQ ID from payload and verify -- Separate REQ ID from payload and verify
local reqId, payload = data:match("(%w%w%w%w%w%w)(.*)"); local reqId = data:sub(1, 6)
local payload = data:sub(#reqId+1)
if not reqId then if not reqId then
debugOut("DAT", "Rx", "Request ID missing, aborting.")
return return
end end
-- If no REQ header info has been cached, abort -- If no REQ header info has been cached, abort
if not datacache[sender:GetGUIDLow()][reqId] then if not datacache[sender:GetGUIDLow()][reqId] then
debugOut("Data received, but not expected. Aborting.") debugOut("DAT", "Rx", "Cache does not exist, aborting.")
return return
end end
@ -233,28 +209,32 @@ function SMH.OnDAT(sender, data)
-- Some functions are trigger functions and expect no payload -- Some functions are trigger functions and expect no payload
-- Skip the rest of the functionality and call the expected function -- Skip the rest of the functionality and call the expected function
if reqTable.count == 0 then if reqTable.count == 0 then
debugOut("DAT", "Rx", "Function expects no data, triggering function..")
-- Retrieve the function from global namespace and pass variables if it exists -- Retrieve the function from global namespace and pass variables if it exists
local func = SMH[reqTable.addon][reqTable.funcId] local func = SMH[reqTable.addon][reqTable.funcId]
if func then if func then
debugOut(func)
_G[func](sender, {}) _G[func](sender, {})
datacache[sender:GetGUIDLow()][reqId] = nil datacache[sender:GetGUIDLow()][reqId] = nil
debugOut("DAT", "Rx", "Function "..func.." @ "..reqTable.addon.." executed, cache cleared.")
end end
return return
end end
-- If the size of the cache is larger than expected, abort -- If the size of the cache is larger than expected, abort
if sizeOfDataCache+1 > reqTable.count then if sizeOfDataCache+1 > reqTable.count then
debugOut("Received more data than expected. Aborting.") debugOut("DAT", "Rx", "Received more data than expected. Aborting.")
return return
end end
-- Add payload to cache and update size variable -- Add payload to cache and update size variable
reqTable["data"][sizeOfDataCache+1] = payload reqTable["data"][sizeOfDataCache+1] = payload
sizeOfDataCache = #reqTable.data sizeOfDataCache = #reqTable.data
debugOut("DAT", "Rx", "Data part "..sizeOfDataCache.." of "..reqTable.count.." added to cache.")
-- If the last expected message has been received, process it -- If the last expected message has been received, process it
if(sizeOfDataCache == reqTable.count) then if(sizeOfDataCache == reqTable.count) then
debugOut("DAT", "Rx", "All expected data received, processing..")
-- Concatenate the cache and parse the full payload for function variables to return -- Concatenate the cache and parse the full payload for function variables to return
local fullPayload = table.concat(reqTable.data); local fullPayload = table.concat(reqTable.data);
local VarTable = ParseMessage(fullPayload) local VarTable = ParseMessage(fullPayload)
@ -262,24 +242,11 @@ function SMH.OnDAT(sender, data)
-- Retrieve the function from global namespace and pass variables if it exists -- Retrieve the function from global namespace and pass variables if it exists
local func = SMH[reqTable.addon][reqTable.funcId] local func = SMH[reqTable.addon][reqTable.funcId]
if func then if func then
debugOut(func)
_G[func](sender, VarTable) _G[func](sender, VarTable)
end
-- Delete the request session cache
datacache[sender:GetGUIDLow()][reqId] = nil datacache[sender:GetGUIDLow()][reqId] = nil
debugOut("DAT", "Rx", "Function "..func.." @ "..reqTable.addon.." executed, cache cleared.")
end end
end end
function SMH.OnNAK(sender, data)
local reqId = data:match("(%w%w%w%w%w%w)");
if not reqId then
return
end
-- when we receive an error from the server, purge the local cache data
debugOut("Purging cache data with REQ ID: "..reqId)
datacache[sender:GetGUIDLow()][reqId] = nil
end end
-- Rx END -- Rx END
@ -287,39 +254,30 @@ end
-- Tx START -- Tx START
function SMH.SendREQ(sender, functionId, linkCount, reqId, addon) function SMH.SendREQ(sender, functionId, linkCount, reqId, addon)
debugOut("Sending REQ with ID: "..reqId) local header = string.format("%01s%01s%02d", delim[1], "S", pck.REQ)
local send = string.format("%01s%01s%02d%02d%03d%06s%0"..tostring(#addon).."s", CSMHMsgPrefix, "S", pck.REQ, functionId, linkCount, reqId, addon) local data = string.format("%02d%03d%06s%0"..tostring(#addon).."s", functionId, linkCount, reqId, addon)
sender:SendAddonMessage(send, "", 7, sender) sender:SendAddonMessage(header, data, 7, sender)
end debugOut("REQ", "Tx", "Sent REQ with ID "..reqId..", sending DAT..")
function SMH.SendACK(sender, reqId)
local send = string.format("%01s%01s%02d%06s", CSMHMsgPrefix, "S", pck.ACK, reqId)
sender:SendAddonMessage(send, "", 7, sender)
end end
function SMH.SendDAT(sender, reqId) function SMH.SendDAT(sender, reqId)
-- Build data message header -- Build data message header
local send = string.format("%01s%01s%02d%06s", CSMHMsgPrefix, "S", pck.DAT, reqId) local header = string.format("%01s%01s%02d", delim[1], "S", pck.DAT)
-- iterate all items in the message data cache and send -- iterate all items in the message data cache and send
-- functions can also be trigger functions without any data, only send header and no payload -- functions can also be trigger functions without any data, only send header and no payload
if(#datacache[sender:GetGUIDLow()][reqId]["data"] == 0) then if(#datacache[sender:GetGUIDLow()][reqId]["data"] == 0) then
sender:SendAddonMessage(send, "", 7, sender) sender:SendAddonMessage(header, reqId, 7, sender)
else else
for _, v in pairs (datacache[sender:GetGUIDLow()][reqId]["data"]) do for _, v in pairs (datacache[sender:GetGUIDLow()][reqId]["data"]) do
local payload = send..v local payload = reqId..v
sender:SendAddonMessage(payload, "", 7, sender) sender:SendAddonMessage(header, payload, 7, sender)
end end
end end
debugOut("All data sent, cleaning up cache.")
-- all items have been sent, cache can be purged -- all items have been sent, cache can be purged
datacache[sender:GetGUIDLow()][reqId] = nil datacache[sender:GetGUIDLow()][reqId] = nil
end debugOut("DAT", "Tx", "Sent all DAT for ID "..reqId..", cache cleared, closing.")
function SMH.SendNAK(sender, reqId)
local send = string.format("%01s%01s%02d%06s", CSMHMsgPrefix, "S", pck.NAK, reqId)
sender:SendAddonMessage(send, "", 7, sender)
end end
-- Tx END -- Tx END
@ -344,8 +302,8 @@ end
function Player:SendServerResponse(prefix, functionId, ...) function Player:SendServerResponse(prefix, functionId, ...)
local reqId = GenerateReqId() local reqId = GenerateReqId()
local varTable = ProcessVariables(self, reqId, ...) local varTable = ProcessVariables(self, reqId, ...)
SMH.SendREQ(self, functionId, varTable.count, reqId, prefix) SMH.SendREQ(self, functionId, varTable.count, reqId, prefix)
SMH.SendDAT(self, reqId)
end end
-- API END -- API END