Performance rewrite part 1
removed acking, took way too long with client delay added better debug messages
This commit is contained in:
150
Client/CMH.lua
150
Client/CMH.lua
@ -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
|
|
||||||
|
|
||||||
function CMH.OnNAK(sender, data)
|
|
||||||
local reqId = data:match("(%w%w%w%w%w%w)");
|
|
||||||
if not reqId then
|
|
||||||
return
|
|
||||||
end
|
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
|
||||||
144
Server/SMH.lua
144
Server/SMH.lua
@ -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
|
|
||||||
|
|
||||||
function SMH.OnNAK(sender, data)
|
|
||||||
local reqId = data:match("(%w%w%w%w%w%w)");
|
|
||||||
if not reqId then
|
|
||||||
return
|
|
||||||
end
|
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
|
||||||
Reference in New Issue
Block a user