Complete rewrite of message handler

This commit is contained in:
Foereaper
2021-07-18 02:35:10 +02:00
parent 74f928d542
commit 73acd33579
6 changed files with 549 additions and 167 deletions

View File

@ -1,65 +1,34 @@
local debug = false
local CMH = {} local CMH = {}
local datacache = {}
local links = {} local CSMHMsgPrefix = ""
local delim = {"", "", "", ""}
local pck = {REQ = 1, ACK = 2, DAT = 3, NAK = 4}
function CMH.OnReceive(self, event, prefix, _, Type, sender) -- HELPERS START
if event == "CHAT_MSG_ADDON" and sender == UnitName("player") and Type == "WHISPER" then local function debugOut(msg)
local source, functionId, link, linkCount, MSG = prefix:match("(%D)(%d%d)(%d%d%d)(%d%d%d)(.+)"); if(debug == true) then
if not source or not functionId or not link or not linkCount or not MSG then print("CMH Debug: "..msg)
return
end
if(source == "S") then
functionId, link, linkCount = tonumber(functionId), tonumber(link), tonumber(linkCount);
links[functionId] = links[functionId] or {count = 0};
links[functionId][link] = MSG;
links[functionId].count = links[functionId].count + 1;
if (links[functionId].count ~= linkCount) then
return
end
local fullMessage = table.concat(links[functionId]);
links[functionId] = {count = 0};
local VarTable = ParseMessage(fullMessage)
if not VarTable then
return
end
if not(CMH[VarTable[1]]) then
return
end
local func = CMH[VarTable[1]][functionId]
if func then
_G[func](sender, VarTable)
end
return
end
end end
end end
local CMHFrame = CreateFrame("Frame") local function GenerateReqId()
CMHFrame:RegisterEvent("CHAT_MSG_ADDON") local length = 6
CMHFrame:SetScript("OnEvent", CMH.OnReceive) local reqId = ""
function RegisterServerResponses(config) for i = 1, length do
if(CMH[config.Prefix]) then reqId = reqId .. string.char(math.random(97, 122))
return;
end end
CMH[config.Prefix] = {} return reqId
for functionId, functionName in pairs(config.Functions) do
CMH[config.Prefix][functionId] = functionName
end
end end
function ParseMessage(str) local function ParseMessage(prefix, str)
local output = {} local output = {}
local valTemp = {} local valTemp = {}
local typeTemp = {} local typeTemp = {}
local delim = {"", "", "", "", ""}
local valMatch = "[^"..table.concat(delim).."]+" local valMatch = "[^"..table.concat(delim).."]+"
local typeMatch = "["..table.concat(delim).."]+" local typeMatch = "["..table.concat(delim).."]+"
@ -81,11 +50,11 @@ 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 == 3) then -- Ints if(varType == 2) then -- Ints
v = tonumber(v) v = tonumber(v)
elseif(varType == 4) then -- Tables elseif(varType == 3) then -- Tables
v = Smallfolk.loads(v, #v) v = Smallfolk.loads(v, #v)
elseif(varType == 5) then -- Booleans elseif(varType == 4) 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)
@ -97,45 +66,262 @@ function ParseMessage(str)
return output return output
end end
function SendClientRequest(prefix, functionId, ...) local function ProcessVariables(reqId, ...)
-- ♠ = Prefix prefix
-- ♥ = ArgumentPrefix for Strings
-- ♚ = ArgumentPrefix for Ints
-- ♛ = ArgumentPrefix for Tables
-- ♜ = ArgumentPrefix for Boolean
local arg = {...} local arg = {...}
local splitLength = 230 local splitLength = 200
local msg = "" .. prefix 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 .. "" msg = msg .. delim[1]
elseif(type(v) == "number") then elseif(type(v) == "number") then
msg = msg .. "" msg = msg .. delim[2]
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 .. "" msg = msg .. delim[3]
elseif(type(v) == "boolean") then elseif(type(v) == "boolean") then
v = tostring(v) v = tostring(v)
msg = msg .. "" msg = msg .. delim[4]
end end
msg = msg .. v msg = msg .. v
end end
local splits = math.ceil(msg:len() / splitLength) if not datacache[reqId] then
local send datacache[reqId] = { count = 1, data = {}}
local counter = 1
for i=1, msg:len(), splitLength do
send = string.format("%01s%03d%02d%02d", "C", functionId, counter, splits)
if ((i + splitLength) > msg:len()) then
send = send .. msg:sub(i, msg:len())
else
send = send .. msg:sub(i, i + splitLength - 1)
end end
counter = counter + 1
SendAddonMessage(send, "", "WHISPER", UnitName("player")) 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
end
return datacache[reqId]
end
-- HELPERS END
-- Rx START
function CMH.OnReceive(self, event, prefix, _, Type, sender)
-- 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
-- unpack and validate addon message structure
local pfx, source, pckId, data = prefix:match("(...)(%u)(%d%d)(.+)")
if not pfx or not source or not pckId or not data then
return
end
-- Make sure we're only processing addon messages using our framework prefix character as welll as server messages
if(pfx == CSMHMsgPrefix and source == "S") then
debugOut("Received CSMH packet, processing data.")
-- convert ID to number so we can compare with our packet list
pckId = tonumber(pckId)
if(pckId == pck.REQ) then
debugOut("REQ received, data: "..data)
CMH.OnREQ(sender, data)
elseif(pckId == pck.ACK) then
debugOut("ACK received, data: "..data)
CMH.OnACK(sender, data)
elseif(pckId == pck.DAT) then
debugOut("DAT received, data: "..data)
CMH.OnDAT(sender, data)
elseif(pckId == pck.NAK) then
debugOut("NAK received, data: "..data)
CMH.OnNAK(sender, data)
else
debugOut("Invalid packet ID, aborting")
return
end
end
end end
end end
local CMHFrame = CreateFrame("Frame")
CMHFrame:RegisterEvent("CHAT_MSG_ADDON")
CMHFrame:SetScript("OnEvent", CMH.OnReceive)
function CMH.OnREQ(sender, data)
debugOut("Processing REQ data")
-- 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)(%w%w%w%w%w%w)(.+)");
if not functionId or not linkCount or not reqId or not addon then
debugOut("Malformed REQ data, aborting.")
return
end
-- make sure the functionId and linkCount is converted to a number
functionId, linkCount = tonumber(functionId), tonumber(linkCount);
-- if the addon does not exist, abort
if not CMH[addon] then
CMH.SendNAK(reqId)
debugOut("Invalid addon, aborting")
return
end
-- if the functionId does not exist for said addon, abort
if not CMH[addon][functionId] then
CMH.SendNAK(reqId)
debugOut("Invalid addon function, aborting")
return
end
-- the request cache already exists, this should not happen.
-- abort and send error to the client, as well as purge id from cache.
if(datacache[reqId]) then
CMH.SendNAK(reqId)
datacache[reqId] = nil
debugOut("Request cache already exists, aborting.")
return
end
-- Insert header info for request id and prepare temporary data storage
datacache[reqId] = {addon = addon, funcId = functionId, count = linkCount, data = {}}
-- send ACK to client notifying client that data is ready to be received
debugOut("REQ OK, sending ACK..")
CMH.SendACK(reqId)
end
function CMH.OnACK(sender, reqId)
-- 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
function CMH.OnDAT(sender, data)
-- Separate REQ ID from payload and verify
local reqId, payload = data:match("(%w%w%w%w%w%w)(.*)");
if not reqId and not payload then
return
end
-- If no REQ header info has been cached, abort
if not datacache[reqId] then
debugOut("Data received, but not expected. Aborting.")
return
end
local reqTable = datacache[reqId]
local sizeOfDataCache = #reqTable.data
-- Some functions are trigger functions and expect no payload
-- Skip the rest of the functionality and call the expected function
if reqTable.count == 0 then
-- Retrieve the function from global namespace and pass variables if it exists
local func = SMH[reqTable.addon][reqTable.funcId]
if func then
debugOut(func)
_G[func](sender, {})
datacache[reqId] = nil
end
return
end
-- If the size of the cache is larger than expected, abort
if sizeOfDataCache+1 > reqTable.count then
debugOut("Received more data than expected. Aborting.")
return
end
-- Add payload to cache and update size variable
reqTable["data"][sizeOfDataCache+1] = payload
sizeOfDataCache = #reqTable.data
-- If the last expected message has been received, process it
if(sizeOfDataCache == reqTable.count) then
-- Concatenate the cache and parse the full payload for function variables to return
local fullPayload = table.concat(reqTable.data);
local VarTable = ParseMessage(reqTable.addon, fullPayload)
-- Retrieve the function from global namespace and pass variables if it exists
local func = CMH[reqTable.addon][reqTable.funcId]
if func then
debugOut(func)
_G[func](sender, VarTable)
end
-- Delete the request session cache
datacache[reqId] = nil
end
end
function CMH.OnNAK(sender, reqId)
-- 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
-- Rx END
-- Tx START
function CMH.SendREQ(functionId, linkCount, reqId, addon)
debugOut("Sending REQ with ID: "..reqId)
local send = string.format("%01s%01s%02d%02d%02d%06s%0"..tostring(#addon).."s", CSMHMsgPrefix, "C", pck.REQ, functionId, linkCount, reqId, addon)
SendAddonMessage(send, "", "WHISPER", UnitName("player"))
end
function CMH.SendACK(reqId)
local send = string.format("%01s%01s%02d%06s", CSMHMsgPrefix, "C", pck.ACK, reqId)
SendAddonMessage(send, "", "WHISPER", UnitName("player"))
end
function CMH.SendDAT(reqId)
-- Build data message header
local send = string.format("%01s%01s%02d%06s", CSMHMsgPrefix, "C", pck.DAT, reqId)
-- 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
if(#datacache[reqId]["data"] == 0) then
SendAddonMessage(send, "", "WHISPER", UnitName("player"))
else
for _, v in pairs (datacache[reqId]["data"]) do
local payload = send..v
SendAddonMessage(payload, "", "WHISPER", UnitName("player"))
end
end
-- all items have been sent, cache can be purged
debugOut("All data sent, cleaning up cache.")
datacache[reqId] = nil
end
function CMH.SendNAK(reqId)
local send = string.format("%01s%01s%02d%06s", CSMHMsgPrefix, "C", pck.NAK, reqId)
SendAddonMessage(send, "", "WHISPER", UnitName("player"))
end
-- Tx END
-- API START
function RegisterServerResponses(config)
if(CMH[config.Prefix]) then
return;
end
CMH[config.Prefix] = {}
for functionId, functionName in pairs(config.Functions) do
CMH[config.Prefix][functionId] = functionName
end
end
function SendClientRequest(prefix, functionId, ...)
local reqId = GenerateReqId()
local varTable = ProcessVariables(reqId, ...)
CMH.SendREQ(functionId, #varTable["data"], reqId, prefix)
end
--A API END

View File

@ -1,7 +1,7 @@
## Interface: 30300 ## Interface: 30300
## Title: CMH ## Title: CMH
## Notes: Client & Server Message Handler framework for communication between server and client. ## Notes: Client & Server Message Handler framework for communication between server and client.
## Version: 1.0 ## Version: 2.0
## Author: Foereaper, Stoneharry, Terrorblade ## Author: Foereaper, Stoneharry, Terrorblade
smallfolk.lua smallfolk.lua

View File

@ -138,7 +138,7 @@ function StatPointUI.OnLoad()
end end
function OnCacheReceived(sender, argTable) function OnCacheReceived(sender, argTable)
StatPointUI.cache = argTable[2] StatPointUI.cache = argTable[1]
local rowContent = {"Strength", "Agility", "Stamina", "Intellect", "Spirit"} local rowContent = {"Strength", "Agility", "Stamina", "Intellect", "Spirit"}
for i = 1, 5 do for i = 1, 5 do
StatPointUI[rowContent[i]].Val:SetText(StatPointUI.cache[i]) StatPointUI[rowContent[i]].Val:SetText(StatPointUI.cache[i])

View File

@ -1,7 +1,7 @@
## Interface: 30300 ## Interface: 30300
## Title: StatPointUI ## Title: StatPointUI
## Notes: CMH Stat Point UI example script. ## Notes: CMH Stat Point UI example script.
## Version: 1.0 ## Version: 1.1
## Author: Foereaper, Kaev ## Author: Foereaper, Kaev
##RequiredDeps: CMH ##RequiredDeps: CMH

View File

@ -107,8 +107,8 @@ end
function OnSpendPointRequest(player, argTable) function OnSpendPointRequest(player, argTable)
if(StatPointUI.cache[player:GetGUIDLow()][6] > 0) then if(StatPointUI.cache[player:GetGUIDLow()][6] > 0) then
-- Double check that the stat requested is actually a valid number -- Double check that the stat requested is actually a valid number
if(tonumber(argTable[2]) <= 5 and tonumber(argTable[2]) >= 0) then if(tonumber(argTable[1]) <= 5 and tonumber(argTable[1]) >= 0) then
StatPointUI.OnPointSpent(player:GetGUIDLow(), argTable[2]) StatPointUI.OnPointSpent(player:GetGUIDLow(), argTable[1])
end end
else else
player:SendBroadcastMessage("You have no points left!") player:SendBroadcastMessage("You have no points left!")

View File

@ -1,70 +1,36 @@
local smallfolk = smallfolk or require("smallfolk") local smallfolk = smallfolk or require("smallfolk")
local debug = false
local SMH = {} local SMH = {}
local links = {} local datacache = {}
function SMH.OnReceive(event, sender, _type, prefix, _, target) local CSMHMsgPrefix = ""
if not sender or not target or not sender.GetName or not target.GetName or type(sender) ~= "userdata" or type(target) ~= "userdata" then local delim = {"", "", "", ""}
return local pck = {REQ = 1, ACK = 2, DAT = 3, NAK = 4}
end
if sender:GetName() == target:GetName() and _type == 7 then
local source, functionId, link, linkCount, MSG = prefix:match("(%D)(%d%d%d)(%d%d)(%d%d)(.+)");
if not source or not functionId or not link or not linkCount or not MSG then
return
end
if(source == "C") then
functionId, link, linkCount = tonumber(functionId), tonumber(link), tonumber(linkCount);
links[sender:GetGUIDLow()] = links[sender:GetGUIDLow()] or {} -- HELPERS START
links[sender:GetGUIDLow()][functionId] = links[sender:GetGUIDLow()][functionId] or {count = 0}; local function debugOut(msg)
links[sender:GetGUIDLow()][functionId][link] = MSG; if(debug == true) then
links[sender:GetGUIDLow()][functionId].count = links[sender:GetGUIDLow()][functionId].count + 1; print("SMH Debug: "..msg)
if (links[sender:GetGUIDLow()][functionId].count ~= linkCount) then
return
end
local fullMessage = table.concat(links[sender:GetGUIDLow()][functionId]);
links[sender:GetGUIDLow()][functionId] = {count = 0};
local VarTable = ParseMessage(fullMessage)
if not VarTable then
return
end
if not(SMH[VarTable[1]]) then
return
end
local func = SMH[VarTable[1]][functionId]
if func then
_G[func](sender, VarTable)
end
return
end
end end
end end
RegisterServerEvent(30, SMH.OnReceive) local function GenerateReqId()
local length = 6
local reqId = ""
function RegisterClientRequests(config) for i = 1, length do
-- If a config table with the Prefix already exists, abort loading it into the register. reqId = reqId .. string.char(math.random(97, 122))
if(SMH[config.Prefix]) then
return;
end end
-- Create subtable for PrefixName return reqId
SMH[config.Prefix] = {}
-- Insert function ID and function name into the register table.
for functionId, functionName in pairs(config.Functions) do
SMH[config.Prefix][functionId] = functionName
end
end end
function ParseMessage(str) local function ParseMessage(prefix, str)
local output = {} local output = {}
local valTemp = {} local valTemp = {}
local typeTemp = {} local typeTemp = {}
local delim = {"", "", "", "", ""}
local valMatch = "[^"..table.concat(delim).."]+" local valMatch = "[^"..table.concat(delim).."]+"
local typeMatch = "["..table.concat(delim).."]+" local typeMatch = "["..table.concat(delim).."]+"
@ -86,11 +52,11 @@ 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 == 3) then -- Ints if(varType == 2) then -- Ints
v = tonumber(v) v = tonumber(v)
elseif(varType == 4) then -- Tables elseif(varType == 3) then -- Tables
v = smallfolk.loads(v) v = smallfolk.loads(v)
elseif(varType == 5) then -- Booleans elseif(varType == 4) 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)
@ -102,44 +68,274 @@ function ParseMessage(str)
return output return output
end end
function Player:SendServerResponse(prefix, functionId, ...) local function ProcessVariables(sender, reqId, ...)
-- ♠ = Prefix prefix
-- ♥ = ArgumentPrefix for Strings
-- ♚ = ArgumentPrefix for Ints
-- ♛ = ArgumentPrefix for Tables
-- ♜ = ArgumentPrefix for Boolean
local arg = {...} local arg = {...}
local splitLength = 230 local splitLength = 200
local msg = "" .. prefix 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 .. "" msg = msg .. delim[1]
elseif(type(v) == "number") then elseif(type(v) == "number") then
msg = msg .. "" msg = msg .. delim[2]
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 .. "" msg = msg .. delim[3]
elseif(type(v) == "boolean") then elseif(type(v) == "boolean") then
v = tostring(v) v = tostring(v)
msg = msg .. "" msg = msg .. delim[4]
end end
msg = msg .. v msg = msg .. v
end end
local splits = math.ceil(msg:len() / splitLength) datacache[sender:GetGUIDLow()] = datacache[sender:GetGUIDLow()] or {}
local send
local counter = 1 if not datacache[sender:GetGUIDLow()][reqId] then
for i=1, msg:len(), splitLength do datacache[sender:GetGUIDLow()][reqId] = { count = 1, data = {}}
send = string.format("%01s%02d%03d%03d", "S", functionId, counter, splits) end
if ((i + splitLength) > msg:len()) then
send = send .. msg:sub(i, msg:len()) for i=1, msg:len(), splitLength do
else datacache[sender:GetGUIDLow()][reqId]["data"][#datacache[sender:GetGUIDLow()][reqId]["data"]+1] = msg:sub(i,i+splitLength - 1)
send = send .. msg:sub(i, i + splitLength - 1) datacache[sender:GetGUIDLow()][reqId].count = datacache[sender:GetGUIDLow()][reqId].count + 1
end
return datacache[sender:GetGUIDLow()][reqId]
end
-- HELPERS END
-- Rx START
function SMH.OnReceive(event, sender, _type, prefix, _, target)
-- Make sure the sender and receiver of the addon message is set and is the correct type.
-- 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
return
end
-- Ensure the sender and receiver is the same, and the message type is WHISPER
if sender:GetName() == target:GetName() and _type == 7 then
-- unpack and validate addon message structure
local pfx, source, pckId, data = prefix:match("(...)(%u)(%d%d)(.+)")
if not pfx or not source or not pckId then
return
end
-- 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
debugOut("Received CSMH packet, processing data.")
-- convert ID to number so we can compare with our packet list
pckId = tonumber(pckId)
if(pckId == pck.REQ) then
debugOut("REQ received, data: "..data)
SMH.OnREQ(sender, data)
elseif(pckId == pck.ACK) then
debugOut("ACK received, data: "..data)
SMH.OnACK(sender, data)
elseif(pckId == pck.DAT) then
debugOut("DAT received, data: "..data)
SMH.OnDAT(sender, data)
elseif(pckId == pck.NAK) then
debugOut("NAK received, data: "..data)
SMH.OnNAK(sender, data)
else
debugOut("Invalid packet ID, aborting")
return
end
end end
counter = counter + 1
self:SendAddonMessage(send, "", 7, self)
end end
end end
RegisterServerEvent(30, SMH.OnReceive)
function SMH.OnREQ(sender, data)
debugOut("Processing REQ data")
-- 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)(%w%w%w%w%w%w)(.+)");
if not functionId or not linkCount or not reqId or not addon then
debugOut("Malformed REQ data, aborting.")
return
end
-- make sure the functionId and linkCount is converted to a number
functionId, linkCount = tonumber(functionId), tonumber(linkCount);
-- if the addon does not exist, abort
if not SMH[addon] then
SMH.SendNAK(sender, reqId)
debugOut("Invalid addon, aborting")
return
end
-- if the functionId does not exist for said addon, abort
if not SMH[addon][functionId] then
SMH.SendNAK(sender, reqId)
debugOut("Invalid addon function, aborting")
return
end
-- header is OK, create cache
datacache[sender:GetGUIDLow()] = datacache[sender:GetGUIDLow()] or {}
-- the request cache already exists, this should not happen.
-- abort and send error to the client, as well as purge id from cache.
if(datacache[sender:GetGUIDLow()][reqId]) then
SMH.SendNAK(sender, reqId)
datacache[sender:GetGUIDLow()][reqId] = nil
debugOut("Request cache already exists, aborting.")
return
end
-- Insert header info for request id and prepare temporary data storage
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 OK, sending ACK..")
SMH.SendACK(sender, reqId)
end
function SMH.OnACK(sender, reqId)
-- 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)
-- Separate REQ ID from payload and verify
local reqId, payload = data:match("(%w%w%w%w%w%w)(.*)");
if not reqId then
return
end
-- If no REQ header info has been cached, abort
if not datacache[sender:GetGUIDLow()][reqId] then
debugOut("Data received, but not expected. Aborting.")
return
end
local reqTable = datacache[sender:GetGUIDLow()][reqId]
local sizeOfDataCache = #reqTable.data
-- Some functions are trigger functions and expect no payload
-- Skip the rest of the functionality and call the expected function
if reqTable.count == 0 then
-- Retrieve the function from global namespace and pass variables if it exists
local func = SMH[reqTable.addon][reqTable.funcId]
if func then
debugOut(func)
_G[func](sender, {})
datacache[sender:GetGUIDLow()][reqId] = nil
end
return
end
-- If the size of the cache is larger than expected, abort
if sizeOfDataCache+1 > reqTable.count then
debugOut("Received more data than expected. Aborting.")
return
end
-- Add payload to cache and update size variable
reqTable["data"][sizeOfDataCache+1] = payload
sizeOfDataCache = #reqTable.data
-- If the last expected message has been received, process it
if(sizeOfDataCache == reqTable.count) then
-- Concatenate the cache and parse the full payload for function variables to return
local fullPayload = table.concat(reqTable.data);
local VarTable = ParseMessage(reqTable.addon, fullPayload)
-- Retrieve the function from global namespace and pass variables if it exists
local func = SMH[reqTable.addon][reqTable.funcId]
if func then
debugOut(func)
_G[func](sender, VarTable)
end
-- Delete the request session cache
datacache[sender:GetGUIDLow()][reqId] = nil
end
end
function SMH.OnNAK(sender, reqId)
-- 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
-- Rx END
-- Tx START
function SMH.SendREQ(sender, functionId, linkCount, reqId, addon)
debugOut("Sending REQ with ID: "..reqId)
local send = string.format("%01s%01s%02d%02d%02d%06s%0"..tostring(#addon).."s", CSMHMsgPrefix, "S", pck.REQ, functionId, linkCount, reqId, addon)
sender:SendAddonMessage(send, "", 7, sender)
end
function SMH.SendACK(sender, reqId)
local send = string.format("%01s%01s%02d%06s", CSMHMsgPrefix, "S", pck.ACK, reqId)
sender:SendAddonMessage(send, "", 7, sender)
end
function SMH.SendDAT(sender, reqId)
-- Build data message header
local send = string.format("%01s%01s%02d%06s", CSMHMsgPrefix, "S", pck.DAT, reqId)
-- 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
if(#datacache[sender:GetGUIDLow()][reqId]["data"] == 0) then
sender:SendAddonMessage(send, "", 7, sender)
else
for _, v in pairs (datacache[sender:GetGUIDLow()][reqId]["data"]) do
local payload = send..v
sender:SendAddonMessage(payload, "", 7, sender)
end
end
debugOut("All data sent, cleaning up cache.")
-- all items have been sent, cache can be purged
datacache[sender:GetGUIDLow()][reqId] = nil
end
function SMH.SendNAK(sender, reqId)
local send = string.format("%01s%01s%02d%06s", CSMHMsgPrefix, "S", pck.NAK, reqId)
sender:SendAddonMessage(send, "", 7, sender)
end
-- Tx END
-- API START
function RegisterClientRequests(config)
-- If a config table with the Prefix already exists, abort loading it into the register.
if(SMH[config.Prefix]) then
return;
end
-- Create subtable for PrefixName
SMH[config.Prefix] = {}
-- Insert function ID and function name into the register table.
for functionId, functionName in pairs(config.Functions) do
SMH[config.Prefix][functionId] = functionName
end
end
function Player:SendServerResponse(prefix, functionId, ...)
local reqId = GenerateReqId()
local varTable = ProcessVariables(self, reqId, ...)
SMH.SendREQ(self, functionId, #varTable["data"], reqId, prefix)
end
-- API END