Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- --[[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
- 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
- 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
- and sent over rednet. The recipient ID is used as the sending channel: pastebin:8CfjPgCu]]
- print("Packet Utility 1.0")
- --Utility for quickly yielding. This is very important for the queue functions as they are intended to run parallel to other functions
- function yield()
- --Queue an event and then yield for it. This is faster than using a timer, but does the same job
- os.queueEvent("threadyield")
- coroutine.yield("threadyield")
- end
- --[[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
- 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
- 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]]
- function checkType(mVar, sType, func, nLevel)
- --The function to run if the type doesn't match, or a default function returning false
- local func = func or function() return false end
- --The level above this function's call which caused the issue, or the level above the caller
- local nLevel = nLevel or 2
- --Response to most type mismatches: "Expected [expected type], got [actual type]"
- local function standard_error(gVar, sExpectedType, sType, nLevel)
- error(string.format("Expected %s, got %s", sExpectedType, sType), nLevel)
- end
- if func == "standard_error" then
- func = standard_error
- end
- --Check our argument's types
- if type(sType) ~= "string" then standard_error(nil, "string", type(sType), 3) end
- if type(func) ~= "function" then standard_error(nil, "function", type(func), 3) end
- if type(nLevel) ~= "number" then standard_error(nil, "number", type(nLevel), 3) end
- --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
- --convenience we add 2 so they may use 1 as their level
- if nLevel ~= 0 then
- nLevel = nLevel + 2
- end
- --If the type of mVar does not match what is defined in sType, call func
- if type(mVar) ~= sType then
- --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
- return func(mVar, sType, type(mVar), nLevel)
- end
- --mVar passed the test
- return true
- end
- --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
- --user's mistake
- local function upcallL(nLevel, func, ...)
- --Call the function with pcall
- local tResult = {pcall(func, ...)}
- --Extract the result of pcall's attempt
- local bResult = table.remove(tResult, 1)
- --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
- --convenience we add 1 so they may use 1 as their level
- if nLevel ~= 0 then
- nLevel = nLevel + 1
- end
- if bResult then
- --Unpack what the function returned
- return unpack(tResult)
- else
- --Raise an error at the specified level
- error(tResult[1], nLevel)
- end
- end
- --Utility for quickly clearing a table, used to empty the queues after they've been processed in multi-packet mode
- function clearTable(tTbl)
- checkType(tTbl, "table", "standard_error")
- --Get the first index in the table
- local k = next(tTbl)
- while k do
- --Set the index's value to nil
- tTbl[k] = nil
- --Set the index to the next non-nil index. This will be nil if the table is empty, which will end the loop
- k = next(tTbl)
- end
- end
- --Copies a table (in case you didn't know)
- function copyTable(tTbl)
- checkType(tTbl, "table", "standard_error")
- --The table to copy to
- local tCopy = {}
- --Get the first index in the table
- local k = next(tTbl)
- while k do
- --Set the index's value in the copy, to its value in the original
- tCopy[k] = tTbl[k]
- --Get the next index after the current one. This will be nil if there are no more indexes, which will end the loop
- k = next(tTbl, k)
- end
- --Return the copy, and the original next to it for convenience
- return tCopy, tTbl
- end
- --Utility for creating packet tables
- function asPacketData(senderType, senderName, senderID, recipientName, recipientID, dataType, data, tOther)
- --[[
- Packet contains:
- -what kind of server or client is sending the information
- -the computer name and ID of the sender
- -the computer name and ID of the computer intended to receive the packet (the ID is usually used as the sending channel)
- -the type of data being sent
- -the data itself
- -any other information which a program may acknowledge, but the packet API will ignore
- ]]
- local tPacket = {
- senderType=senderType,
- senderName=senderName,
- senderID=senderID,
- recipientName=recipientName,
- recipientID=recipientID,
- dataType=dataType,
- data=data
- }
- --If tOther is nil, set it to an empty table
- local tOther = tOther or {}
- checkType(tOther, "table", "standard_error")
- --Add all the values in tOther to the packet
- for k, v in pairs(tOther) do
- tPacket[k] = v
- end
- return tPacket
- end
- --Utility for creating packet tables to be passed to send as send does not need to know the sender name or ID
- function asPacketDataS(senderType, recipientName, recipientID, dataType, data, tOther)
- --Leave the sender name and ID nil, as send will set it to this computer's name and ID for us
- return upcallL(2, asPacketData,
- senderType,
- nil, nil,
- recipientName,
- recipientID,
- dataType,
- data,
- tOther
- )
- end
- --[[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
- 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
- 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.
- Otherwise, it will return false. Any values in the packet which are not in the sample packet will be ignored]]
- function checkPacket(tSamplePacket, tPacket)
- checkType(tSamplePacket, "table", "standard_error")
- checkType(tPacket, "table", "standard_error")
- --We only care about items that tSamplePacket has in it. If tPacket has an item the tSamplePacket doesn't, we'll ignore it
- for k, v in pairs(tSamplePacket) do
- --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
- if not (tPacket[k] == v or (checkType(v, "function") and v(tPacket[k]))) then
- return false
- end
- end
- return true
- end
- --Confirms that a table contains all of the parts of a standard packet
- local function validatePacket(tPacket)
- --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
- return checkPacket(asPacketData(
- function(mData) return checkType(mData, "string") end,--Sender type
- function(mData) return checkType(mData, "string") end,--Sender name
- function(mData) return checkType(mData, "number") end,--Sender ID
- function(mData) return checkType(mData, "string") end,--Recipient name
- function(mData) return checkType(mData, "number") end,--Recipient ID
- function(mData) return checkType(mData, "string") end--Data type
- ), tPacket)
- end
- --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
- function asPacket(...)
- --Convert the arguments into a packet table
- local tPacket = upcallL(2, asPacketData, ...)
- --Check it against the standard
- if not validatePacket(tPacket) then
- error("asPacket: You are not allowed to generate corrupted packets", 2)
- end
- --Serialize the packet as a string to be sent over rednet. If the packet contains a function, an error will be raised
- return upcallL(2, textutils.serialize, tPacket)
- end
- --Formats a string as a table of data, handling any error caused by the process and returning them as an errorMessage packet
- function asData(sPacket)
- checkType(sPacket, "string", "standard_error")
- --Attempt to format the string as a table of information
- local tPacket = textutils.unserialize(sPacket)
- --If the result is a table, and the table passes as a standard packet, return it
- if checkType(tPacket, "table") and validatePacket(tPacket) then
- return tPacket
- end
- --Corrupted packet error, will usually be ignored as checkPacket will reject it in most cases
- return asPacketData(
- "system",
- "asData",
- -1,
- "none",
- -1,
- "errorMessage",
- "Packet corrupted"
- )
- end
- --Sends a packet on the channel of the given recipient recipient ID
- function send(sSenderType, sRecipientName, nRecipientID, sDataType, mData)
- --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
- rednet.send(nRecipientID,
- upcallL(2, asPacket, sSenderType, (os.getComputerLabel() or "LABEL_NOT_SET"), os.getComputerID(),
- sRecipientName, nRecipientID, sDataType, mData)
- )
- end
- --[[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
- 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
- function may return blank packets, even if they do not match the sample packet. This defaults to false]]
- function receive(nTimeout, tSamplePacket)
- --nTimeout may be nil, but otherwise it must be a number
- if nTimeout then
- checkType(nTimeout, "number", "standard_error")
- end
- --If tSamplePacket is nil, set it to an empty table
- local tSamplePacket = tSamplePacket or {}
- checkType(tSamplePacket, "table", "standard_error")
- while true do
- --Rednet may raise a termination error, we will catch this and return it as an error message if this is permitted
- local tMessage = {pcall(rednet.receive, nTimeout)}
- local tPacket
- if not tMessage[1] then
- --Some error occurred, create a packet containing the error message
- tPacket = asPacketData(
- "system",
- "receive",
- -1,
- "none",
- -1,
- "errorMessage",
- tMessage[2]
- )
- end
- --If receive timed out, it returned nil. If this is the case, create a blank packet
- if not tMessage[3] then
- tPacket = asPacketData(
- "system",
- "receive",
- -1,
- "none",
- -1,
- "blank"
- )
- end
- --If nothing has gone wrong so far, try to convert the received message into a packet
- if not tPacket then
- tPacket = asData(tMessage[3])
- end
- --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.
- --If the packet is blank, we'll return it regardless of whether or not it passes
- if checkPacket(tSamplePacket, tPacket) or tPacket.dataType == "blank" then
- return tPacket
- end
- end
- end
- --Receives a packet, or multi-packet, and places it in the queue
- function queueReceiver(tQueue, tSamplePacket)
- checkType(tQueue, "table", "standard_error")
- --If tSamplePacket is nil, set it to an empty table
- local tSamplePacket = tSamplePacket or {}
- checkType(tSamplePacket, "table", "standard_error")
- while true do
- --Wait indefinitely for a packet which matches the sample packet
- tPacket = receive(nil, tSamplePacket)
- --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
- --multiMode flag in this table will be set to true
- if checkType(tPacket.data, "table") and tPacket.data.multiMode then
- --Erase the flag, we don't need it any more
- tPacket.data.multiMode = nil
- --Add every piece of data being sent to the queue
- for k, v in pairs(tPacket.data) do
- table.insert(tQueue, v)
- end
- else
- --Add the data to the queue
- table.insert(tQueue, tPacket.data)
- end
- end
- end
- --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
- --received, it will be formatted as a packet, or as a multi-packet if the third function argument is true
- function queueSender(tQueue, tTemplatePacket, bMultiPacketMode)
- checkType(tQueue, "table", "standard_error")
- checkType(tTemplatePacket, "table", "standard_error")
- checkType(bMultiPacketMode, "boolean", "standard_error")
- --tTemplatePacket may specify multiple recipients. They will be stored here
- local tRecipients = {}
- if tTemplatePacket.recipients and checkType(tTemplatePacket.recipients, "table") then
- for k, v in pairs(tTemplatePacket.recipients) do
- --If v is not a table
- checkType(v, "table",
- function(gVar, sExpectedType, sType, nLevel)
- error(string.format("Error processing entry %s, entry: Expected %s, got %s", tostring(k), sExpectedType, sType), nLevel)
- end)
- --If v.recipientName is not a string
- checkType(v.recipientName, "string",
- function(gVar, sExpectedType, sType, nLevel)
- error(string.format("Error processing entry %s, recipientName: Expected %s, got %s", tostring(k), sExpectedType, sType), nLevel)
- end)
- --If v.recipientID is not a number
- checkType(v.recipientID, "number",
- function(gVar, sExpectedType, sType, nLevel)
- error(string.format("Error processing entry %s, recipientID: Expected %s, got %s", tostring(k), sExpectedType, sType), nLevel)
- end)
- table.insert(tRecipients, {recipientName=v.recipientName, recipientID=v.recipientID})
- end
- else
- --Just treat tTemplatePacket as a regular packet
- table.insert(tRecipients, {recipientName=tTemplatePacket.recipientName, recipientID=tTemplatePacket.recipientID})
- end
- while true do
- --If there is something in the queue
- if #tQueue > 0 then
- for k, v in pairs(tRecipients) do
- --In multi-mode we send the entire queue at once as a table, then clear the entire queue at once
- if bMultiPacketMode then
- tQueue.multiMode = true
- upcallL(2, send, tTemplatePacket.senderType, v.recipientName,
- v.recipientID, tTemplatePacket.dataType, tQueue)
- clearTable(tQueue)
- else
- --In single-mode, we clear one entry at a time, sending its value as the packet data
- upcallL(2, send, tTemplatePacket.senderType, v.recipientName,
- v.recipientID, tTemplatePacket.dataType, table.remove(tQueue, 1))
- end
- end
- end
- --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
- pcall(sleep, 0)
- end
- end
- --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
- function packetListener(func, tSamplePacket)
- checkType(func, "function", "standard_error")
- --The queue to receive our packets in
- local tQueue = {}
- parallel.waitForAny(
- function()
- upcallL(2, queueReceiver, tQueue, tSamplePacket)
- end,
- function()
- while true do
- if #tQueue > 0 then
- --Empty out the queue
- while #tQueue > 0 do
- --Call the function, passing it the value being removed from the queue. If the function returns false, end the loop
- if not func(table.remove(tQueue, 1)) then
- return
- end
- end
- end
- yield()
- end
- end
- )
- end
- --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
- function packetSender(func, tTemplatePacket, bMultiPacketMode)
- checkType(func, "function", "standard_error")
- --The queue to send out packets from
- local tQueue = {}
- parallel.waitForAny(
- function()
- upcallL(2, queueSender, tQueue, tTemplatePacket, bMultiPacketMode)
- end,
- function()
- --The running loop must happen in this function
- func(tQueue)
- end
- )
- end
Advertisement
Add Comment
Please, Sign In to add comment