IGDev

Packet

Sep 7th, 2015
261
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 17.05 KB | None | 0 0
  1. --[[PACKET API: This API allows information to be easily transferred between computers. Packets are formed in a similar fashion to real network packets. Each packet
  2. contains the type of the program sending it ("server", "client", "browser", etc.), the sender's computer name and ID, the recipient's (destination's) computer name and
  3. ID, the type of the data being sent, and the data itself. The data may be anything except a function or a table containing a function. Packets are serialized as strings
  4. and sent over rednet. The recipient ID is used as the sending channel: pastebin:8CfjPgCu]]
  5. print("Packet Utility 1.0")
  6. --Utility for quickly yielding. This is very important for the queue functions as they are intended to run parallel to other functions
  7. function yield()
  8.     --Queue an event and then yield for it. This is faster than using a timer, but does the same job
  9.     os.queueEvent("threadyield")
  10.     coroutine.yield("threadyield")
  11.  
  12. end
  13. --[[Utility for checking if a variable is of a required type. If the type matches, true is returned. Otherwise false may be returned, or a function may be provided to
  14. take come custom action such as raising an error or returning an error message. Passing "standard_error" as the function will cause an error to be raised with a common
  15. error message. The level argument is the stack level above this function call which is responsible for the error. This defaults to the level above the caller]]
  16. function checkType(mVar, sType, func, nLevel)
  17.     --The function to run if the type doesn't match, or a default function returning false
  18.     local func = func or function() return false end
  19.     --The level above this function's call which caused the issue, or the level above the caller
  20.     local nLevel = nLevel or 2
  21.     --Response to most type mismatches: "Expected [expected type], got [actual type]"
  22.     local function standard_error(gVar, sExpectedType, sType, nLevel)
  23.        
  24.         error(string.format("Expected %s, got %s", sExpectedType, sType), nLevel)
  25.        
  26.     end
  27.    
  28.     if func == "standard_error" then
  29.        
  30.         func = standard_error
  31.        
  32.     end
  33.     --Check our argument's types
  34.     if type(sType) ~= "string" then standard_error(nil, "string", type(sType), 3) end
  35.     if type(func) ~= "function" then standard_error(nil, "function", type(func), 3) end
  36.     if type(nLevel) ~= "number" then standard_error(nil, "number", type(nLevel), 3) end
  37.     --If the level is 0, leave it. Otherwise, add 2 to compensate for the two function calls to get to func. The level of the calling function is technically 3, but for
  38.     --convenience we add 2 so they may use 1 as their level
  39.     if nLevel ~= 0 then
  40.        
  41.         nLevel = nLevel + 2
  42.        
  43.     end
  44.     --If the type of mVar does not match what is defined in sType, call func
  45.     if type(mVar) ~= sType then
  46.         --func is passed the variable which failed the test, the type which it needed to be, the actual type, and the level to raise the error at
  47.         return func(mVar, sType, type(mVar), nLevel)
  48.        
  49.     end
  50.     --mVar passed the test
  51.     return true
  52.    
  53. end
  54. --UnProtected CALL at a specified Level. Re-raises any error raised by the given function at the specified level. This makes sure our functions don't get blamed for a
  55. --user's mistake
  56. local function upcallL(nLevel, func, ...)
  57.     --Call the function with pcall
  58.     local tResult = {pcall(func, ...)}
  59.     --Extract the result of pcall's attempt
  60.     local bResult = table.remove(tResult, 1)
  61.     --If the level is 0, leave it. Otherwise, add 1 to compensate for the call to this function. The level of the calling function is technically 2, but for
  62.     --convenience we add 1 so they may use 1 as their level
  63.     if nLevel ~= 0 then
  64.        
  65.         nLevel = nLevel + 1
  66.        
  67.     end
  68.    
  69.     if bResult then
  70.         --Unpack what the function returned
  71.         return unpack(tResult)
  72.        
  73.     else
  74.         --Raise an error at the specified level
  75.         error(tResult[1], nLevel)
  76.        
  77.     end
  78.    
  79. end
  80. --Utility for quickly clearing a table, used to empty the queues after they've been processed in multi-packet mode
  81. function clearTable(tTbl)
  82.    
  83.     checkType(tTbl, "table", "standard_error")
  84.     --Get the first index in the table
  85.     local k = next(tTbl)
  86.    
  87.     while k do
  88.         --Set the index's value to nil
  89.         tTbl[k] = nil
  90.         --Set the index to the next non-nil index. This will be nil if the table is empty, which will end the loop
  91.         k = next(tTbl)
  92.  
  93.     end
  94.  
  95. end
  96. --Copies a table (in case you didn't know)
  97. function copyTable(tTbl)
  98.    
  99.     checkType(tTbl, "table", "standard_error")
  100.     --The table to copy to
  101.     local tCopy = {}
  102.     --Get the first index in the table
  103.     local k = next(tTbl)
  104.  
  105.     while k do
  106.         --Set the index's value in the copy, to its value in the original
  107.         tCopy[k] = tTbl[k]
  108.         --Get the next index after the current one. This will be nil if there are no more indexes, which will end the loop
  109.         k = next(tTbl, k)
  110.  
  111.     end
  112.     --Return the copy, and the original next to it for convenience
  113.     return tCopy, tTbl
  114.    
  115. end
  116. --Utility for creating packet tables
  117. function asPacketData(senderType, senderName, senderID, recipientName, recipientID, dataType, data, tOther)
  118.     --[[
  119.         Packet contains:
  120.             -what kind of server or client is sending the information
  121.             -the computer name and ID of the sender
  122.             -the computer name and ID of the computer intended to receive the packet (the ID is usually used as the sending channel)
  123.             -the type of data being sent
  124.             -the data itself
  125.             -any other information which a program may acknowledge, but the packet API will ignore
  126.     ]]
  127.     local tPacket = {
  128.  
  129.         senderType=senderType,
  130.         senderName=senderName,
  131.         senderID=senderID,
  132.         recipientName=recipientName,
  133.         recipientID=recipientID,
  134.         dataType=dataType,
  135.         data=data
  136.  
  137.     }
  138.     --If tOther is nil, set it to an empty table
  139.     local tOther = tOther or {}
  140.     checkType(tOther, "table", "standard_error")
  141.     --Add all the values in tOther to the packet
  142.     for k, v in pairs(tOther) do
  143.        
  144.         tPacket[k] = v
  145.        
  146.     end
  147.    
  148.     return tPacket
  149.    
  150. end
  151. --Utility for creating packet tables to be passed to send as send does not need to know the sender name or ID
  152. function asPacketDataS(senderType, recipientName, recipientID, dataType, data, tOther)
  153.     --Leave the sender name and ID nil, as send will set it to this computer's name and ID for us
  154.     return upcallL(2, asPacketData,
  155.        
  156.         senderType,
  157.         nil, nil,
  158.         recipientName,
  159.         recipientID,
  160.         dataType,
  161.         data,
  162.         tOther
  163.        
  164.     )
  165.    
  166. end
  167. --[[Compares two packets, a sample and a real packet, to check for things like who the sender was, and who they were sending to. This is intended to be used to check if
  168. this computer was the intended recipient. If all values in the sample packet match the same values in the real packet, all values in the sample packets are functions
  169. which take the same value of the sample packet as an argument and calling these functions return true, or any combination of the two, this function will return true.
  170. Otherwise, it will return false. Any values in the packet which are not in the sample packet will be ignored]]
  171. function checkPacket(tSamplePacket, tPacket)
  172.    
  173.     checkType(tSamplePacket, "table", "standard_error")
  174.     checkType(tPacket, "table", "standard_error")
  175.     --We only care about items that tSamplePacket has in it. If tPacket has an item the tSamplePacket doesn't, we'll ignore it
  176.     for k, v in pairs(tSamplePacket) do
  177.         --If the packet value doesn't match the sample packet value, and the sample packet value is not a function or is a function and it returns false
  178.         if not (tPacket[k] == v or (checkType(v, "function") and v(tPacket[k]))) then
  179.  
  180.             return false
  181.  
  182.         end
  183.  
  184.     end
  185.  
  186.     return true
  187.  
  188. end
  189. --Confirms that a table contains all of the parts of a standard packet
  190. local function validatePacket(tPacket)
  191.    
  192.     --Create a sample packet of functions which returns if the types of the packet match the defined standard component types, and check it against the packet with checkPacket
  193.     return checkPacket(asPacketData(
  194.        
  195.         function(mData) return checkType(mData, "string") end,--Sender type
  196.         function(mData) return checkType(mData, "string") end,--Sender name
  197.         function(mData) return checkType(mData, "number") end,--Sender ID
  198.         function(mData) return checkType(mData, "string") end,--Recipient name
  199.         function(mData) return checkType(mData, "number") end,--Recipient ID
  200.         function(mData) return checkType(mData, "string") end--Data type
  201.        
  202.     ), tPacket)
  203.  
  204. end
  205. --Formats informations about who's sending, who they're sending to, and what they're sending and returns it as a string which can be sent over rednet
  206. function asPacket(...)
  207.     --Convert the arguments into a packet table
  208.     local tPacket = upcallL(2, asPacketData, ...)
  209.     --Check it against the standard
  210.     if not validatePacket(tPacket) then
  211.        
  212.         error("asPacket: You are not allowed to generate corrupted packets", 2)
  213.  
  214.     end
  215.     --Serialize the packet as a string to be sent over rednet. If the packet contains a function, an error will be raised
  216.     return upcallL(2, textutils.serialize, tPacket)
  217.  
  218. end
  219. --Formats a string as a table of data, handling any error caused by the process and returning them as an errorMessage packet
  220. function asData(sPacket)
  221.    
  222.     checkType(sPacket, "string", "standard_error")
  223.     --Attempt to format the string as a table of information
  224.     local tPacket = textutils.unserialize(sPacket)
  225.     --If the result is a table, and the table passes as a standard packet, return it
  226.     if checkType(tPacket, "table") and validatePacket(tPacket) then
  227.  
  228.         return tPacket
  229.  
  230.     end
  231.     --Corrupted packet error, will usually be ignored as checkPacket will reject it in most cases
  232.     return asPacketData(
  233.  
  234.         "system",
  235.         "asData",
  236.         -1,
  237.         "none",
  238.         -1,
  239.         "errorMessage",
  240.         "Packet corrupted"
  241.  
  242.     )
  243.  
  244. end
  245. --Sends a packet on the channel of the given recipient recipient ID
  246. function send(sSenderType, sRecipientName, nRecipientID, sDataType, mData)
  247.    
  248.     --The packet is sent to the specified recipient's computer ID, assuming it will be receiving over rednet. This computers label and ID are used to form the packet
  249.     rednet.send(nRecipientID,
  250.         upcallL(2, asPacket, sSenderType, (os.getComputerLabel() or "LABEL_NOT_SET"), os.getComputerID(),
  251.             sRecipientName, nRecipientID, sDataType, mData)
  252.     )
  253.  
  254. end
  255. --[[Receives a packet and converts it into data. If a timeout is specified, and no data is received in that amount of time, an empty or "blank" packet will be returned
  256. If a sample packet is specified, this function will not return until it receives a packet which passes the check against the specified sample packet. If specified, this
  257. function may return blank packets, even if they do not match the sample packet. This defaults to false]]
  258. function receive(nTimeout, tSamplePacket)
  259.     --nTimeout may be nil, but otherwise it must be a number
  260.     if nTimeout then
  261.        
  262.         checkType(nTimeout, "number", "standard_error")
  263.        
  264.     end
  265.     --If tSamplePacket is nil, set it to an empty table
  266.     local tSamplePacket = tSamplePacket or {}
  267.     checkType(tSamplePacket, "table", "standard_error")
  268.    
  269.     while true do
  270.         --Rednet may raise a termination error, we will catch this and return it as an error message if this is permitted
  271.         local tMessage = {pcall(rednet.receive, nTimeout)}
  272.         local tPacket
  273.        
  274.         if not tMessage[1] then
  275.             --Some error occurred, create a packet containing the error message
  276.             tPacket = asPacketData(
  277.                
  278.                 "system",
  279.                 "receive",
  280.                 -1,
  281.                 "none",
  282.                 -1,
  283.                 "errorMessage",
  284.                 tMessage[2]
  285.                
  286.             )
  287.            
  288.         end
  289.         --If receive timed out, it returned nil. If this is the case, create a blank packet
  290.         if not tMessage[3] then
  291.            
  292.             tPacket = asPacketData(
  293.                
  294.                 "system",
  295.                 "receive",
  296.                 -1,
  297.                 "none",
  298.                 -1,
  299.                 "blank"
  300.                
  301.             )
  302.            
  303.         end
  304.         --If nothing has gone wrong so far, try to convert the received message into a packet
  305.         if not tPacket then
  306.            
  307.             tPacket = asData(tMessage[3])
  308.            
  309.         end
  310.         --Compare the packet to the sample packet. If it fails, we'll receive again until we get a packet that passes. When we get a packet that passes, we'll return it.
  311.         --If the packet is blank, we'll return it regardless of whether or not it passes
  312.         if checkPacket(tSamplePacket, tPacket) or tPacket.dataType == "blank" then
  313.            
  314.             return tPacket
  315.            
  316.         end
  317.        
  318.     end
  319.  
  320. end
  321. --Receives a packet, or multi-packet, and places it in the queue
  322. function queueReceiver(tQueue, tSamplePacket)
  323.    
  324.     checkType(tQueue, "table", "standard_error")
  325.     --If tSamplePacket is nil, set it to an empty table
  326.     local tSamplePacket = tSamplePacket or {}
  327.     checkType(tSamplePacket, "table", "standard_error")
  328.  
  329.     while true do
  330.         --Wait indefinitely for a packet which matches the sample packet
  331.         tPacket = receive(nil, tSamplePacket)
  332.         --If the packet was sent in multi-mode, it must be received in multi-mode. This means the data will be a table containing multiple pieces of data, and a flag the
  333.         --multiMode flag in this table will be set to true
  334.         if checkType(tPacket.data, "table") and tPacket.data.multiMode then
  335.             --Erase the flag, we don't need it any more
  336.             tPacket.data.multiMode = nil
  337.             --Add every piece of data being sent to the queue
  338.             for k, v in pairs(tPacket.data) do
  339.                
  340.                 table.insert(tQueue, v)
  341.  
  342.             end
  343.  
  344.         else
  345.             --Add the data to the queue
  346.             table.insert(tQueue, tPacket.data)
  347.  
  348.         end
  349.  
  350.     end
  351.  
  352. end
  353. --Runs parallel to a function which should fill the given queue with information to send. This information should NOT be formatted as a packet. As information is
  354. --received, it will be formatted as a packet, or as a multi-packet if the third function argument is true
  355. function queueSender(tQueue, tTemplatePacket, bMultiPacketMode)
  356.    
  357.     checkType(tQueue, "table", "standard_error")
  358.     checkType(tTemplatePacket, "table", "standard_error")
  359.     checkType(bMultiPacketMode, "boolean", "standard_error")
  360.     --tTemplatePacket may specify multiple recipients. They will be stored here
  361.     local tRecipients = {}
  362.    
  363.     if tTemplatePacket.recipients and checkType(tTemplatePacket.recipients, "table") then
  364.        
  365.         for k, v in pairs(tTemplatePacket.recipients) do
  366.             --If v is not a table
  367.             checkType(v, "table",
  368.                 function(gVar, sExpectedType, sType, nLevel)
  369.                    
  370.                     error(string.format("Error processing entry %s, entry: Expected %s, got %s", tostring(k), sExpectedType, sType), nLevel)
  371.                    
  372.                 end)
  373.             --If v.recipientName is not a string
  374.             checkType(v.recipientName, "string",
  375.                 function(gVar, sExpectedType, sType, nLevel)
  376.                    
  377.                     error(string.format("Error processing entry %s, recipientName: Expected %s, got %s", tostring(k), sExpectedType, sType), nLevel)
  378.                    
  379.                 end)
  380.             --If v.recipientID is not a number
  381.             checkType(v.recipientID, "number",
  382.                 function(gVar, sExpectedType, sType, nLevel)
  383.                    
  384.                     error(string.format("Error processing entry %s, recipientID: Expected %s, got %s", tostring(k), sExpectedType, sType), nLevel)
  385.                    
  386.                 end)
  387.            
  388.             table.insert(tRecipients, {recipientName=v.recipientName, recipientID=v.recipientID})
  389.            
  390.         end
  391.        
  392.     else
  393.         --Just treat tTemplatePacket as a regular packet
  394.         table.insert(tRecipients, {recipientName=tTemplatePacket.recipientName, recipientID=tTemplatePacket.recipientID})
  395.        
  396.     end
  397.    
  398.     while true do
  399.         --If there is something in the queue
  400.         if #tQueue > 0 then
  401.            
  402.             for k, v in pairs(tRecipients) do
  403.                 --In multi-mode we send the entire queue at once as a table, then clear the entire queue at once
  404.                 if bMultiPacketMode then
  405.                    
  406.                     tQueue.multiMode = true
  407.                     upcallL(2, send, tTemplatePacket.senderType, v.recipientName,
  408.                     v.recipientID, tTemplatePacket.dataType, tQueue)
  409.                     clearTable(tQueue)
  410.                    
  411.                 else
  412.                     --In single-mode, we clear one entry at a time, sending its value as the packet data
  413.                     upcallL(2, send, tTemplatePacket.senderType, v.recipientName,
  414.                     v.recipientID, tTemplatePacket.dataType, table.remove(tQueue, 1))
  415.                    
  416.                 end
  417.                
  418.             end
  419.            
  420.         end
  421.         --In single packet mode, if this runs too fast, the receiver will glitch out. yield is too fast, so we use sleep to slow the loop down a little
  422.         pcall(sleep, 0)
  423.  
  424.     end
  425.  
  426. end
  427. --Runs the given function parallel to the queueReceiver. The function is called every time a new piece of data is received and is passed that piece of data as an argument
  428. function packetListener(func, tSamplePacket)
  429.    
  430.     checkType(func, "function", "standard_error")
  431.     --The queue to receive our packets in
  432.     local tQueue = {}
  433.    
  434.     parallel.waitForAny(
  435.        
  436.         function()
  437.            
  438.             upcallL(2, queueReceiver, tQueue, tSamplePacket)
  439.  
  440.         end,
  441.         function()
  442.            
  443.             while true do
  444.                
  445.                 if #tQueue > 0 then
  446.                     --Empty out the queue
  447.                     while #tQueue > 0 do
  448.                         --Call the function, passing it the value being removed from the queue. If the function returns false, end the loop
  449.                         if not func(table.remove(tQueue, 1)) then
  450.  
  451.                             return
  452.  
  453.                         end
  454.  
  455.                     end
  456.  
  457.                 end
  458.  
  459.                 yield()
  460.  
  461.             end
  462.  
  463.         end
  464.  
  465.     )
  466.  
  467. end
  468. --Runs the given function parallel to the queueSender. The queue is passed to the function as an argument, anything placed in the queue will be sent over rednet and deleted
  469. function packetSender(func, tTemplatePacket, bMultiPacketMode)
  470.    
  471.     checkType(func, "function", "standard_error")
  472.     --The queue to send out packets from
  473.     local tQueue = {}
  474.  
  475.     parallel.waitForAny(
  476.  
  477.         function()
  478.            
  479.             upcallL(2, queueSender, tQueue, tTemplatePacket, bMultiPacketMode)
  480.  
  481.         end,
  482.         function()
  483.             --The running loop must happen in this function
  484.             func(tQueue)
  485.  
  486.         end
  487.  
  488.     )
  489.  
  490. end
Advertisement
Add Comment
Please, Sign In to add comment