├── LICENSE ├── README.md └── trickle.lua /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Bjorn Swenson 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Trickle 2 | === 3 | 4 | > **NOTE** - It's probably better to use Lua's built-in `string.pack` and `string.unpack` now. 5 | 6 | Trickle is a bitstream for Lua. When writing data to a trickle stream, you must specify its size in 7 | bits. The stream will automatically pack this data into compressed bytes, which often leads to 8 | greatly reduced network bandwidth. This is useful in networking applications where it is necessary 9 | to reduce network traffic, such as realtime multiplayer games. 10 | 11 | Usage 12 | --- 13 | 14 | ```lua 15 | local trickle = require 'trickle' 16 | local stream = trickle.create() 17 | 18 | stream:write(7, '3bits') 19 | stream:write(true, 'bool') 20 | stream:write(1.8, 'float') 21 | 22 | -- send over the network 23 | -- to convert a stream to a string, call tostring(stream) 24 | -- to convert a string to a stream, call trickle.create(string) 25 | 26 | stream:read('3bits') -- 7 27 | stream:read('bool') -- true 28 | stream:read('float') -- 1.8 29 | ``` 30 | 31 | Signatures 32 | --- 33 | 34 | A signature describes a high level template for a message. It can be used to reduce duplication in 35 | reading and writing of messages, and allows reading and writing of tables of data without having to 36 | write each individual field in a specific order. First, define a signature: 37 | 38 | ```lua 39 | local signature = { 40 | { 'playerId', '4bits' }, 41 | { 'x', '8bits' }, 42 | { 'y', '8bits' }, 43 | { 'direction', '9bits' } -- direction in degrees 44 | } 45 | ``` 46 | 47 | Then, pack a message into a trickle stream using the signature: 48 | 49 | ```lua 50 | local playerData = { 51 | playerId = 1, 52 | x = 63, 53 | y = 41, 54 | direction = 270 55 | } 56 | 57 | stream:pack(playerData, signature) 58 | ``` 59 | 60 | `unpack` may be used to decode the stream: 61 | 62 | ```lua 63 | local playerData = stream:unpack(signature) 64 | ``` 65 | 66 | ### Delta Groups 67 | 68 | Let's say we only want to send the position of the player if it has changed. Right now this isn't 69 | possible with our signature, because we *have* to send 8 bits for the x position and 8 bits for the y 70 | position to keep the structure of the signature uniform for the receiving end. **Delta groups** 71 | provide a mechanism to specify these optional groups of data. 72 | 73 | To declare a delta group, specify a `delta` key in the signature table: 74 | 75 | ```lua 76 | local signature = { 77 | { 'playerId', '4bits'}, 78 | { 'x', '8bits' }, 79 | { 'y', '8bits' }, 80 | { 'direction', '9bits' }, 81 | delta = { 82 | { 'x', 'y' }, 83 | 'direction' 84 | } 85 | } 86 | ``` 87 | 88 | What we've done is defined two optional pieces of our message: a group for the position of the 89 | player and a group for the direction of the player. Now, if we pack a message like this: 90 | 91 | ```lua 92 | local playerData = { 93 | playerId = 1, 94 | x = nil, 95 | y = nil, 96 | direction = nil 97 | } 98 | 99 | stream:pack(playerData, signature) 100 | ``` 101 | 102 | trickle will only pack the 4 bit player id into the message, which saves 25 bits! Internally, 103 | a single bit is added to the beginning of the message for each delta group, signifying whether or 104 | not its data is present. This means that if a delta group contains more than one key (such as the 105 | 'x' and 'y' group above), either all of the data must be present or all of it must be `nil`. This, 106 | for example, would result in an error: 107 | 108 | ```lua 109 | local playerData = { 110 | playerId = 1, 111 | x = 17, 112 | y = nil, 113 | direction = 90 114 | } 115 | 116 | stream:pack(playerData, signature) -- Error: Only part of message delta group "x, y" was provided. 117 | ``` 118 | 119 | Documentation 120 | --- 121 | 122 | - `trickle.create([str])` - Creates a stream. If `str` is provided then the contents of the stream 123 | are set to this initial value. 124 | - `tostring(stream)` - Serialize the stream's data to a string. 125 | - `stream:clear()` - Resets the stream to contain no data. 126 | - `stream:truncate()` - If a byte has been partially written, it will not be present in the stream 127 | yet. `truncate` pads the rest of the byte with null data and adds it to the stream's data. This 128 | is mostly for internal use, and is automatically called when converting the stream to a string. 129 | 130 | #### Writing 131 | 132 | - `stream:write(value, type)` - Writes a value to the stream. `type` can either be `string`, 133 | `float`, `bool`, or `bits` for integers, where `n` is the number of bits the integer should use. 134 | - `stream:writeString(str)` - Writes a string to the stream. Uses one byte per character. 135 | - `stream:writeBool(bool)` - Writes a boolean to the stream. Uses one bit. 136 | - `stream:writeFloat(float)` - Writes a float to the stream. Note that the float is currently 137 | serialized as a string. This is suboptimal and should be improved in the future. 138 | - `stream:writeBits(value, n)` - Writes a number to the stream, using `n` bits. 139 | 140 | #### Reading 141 | 142 | - `stream:read(type)` - Reads and returns a value from the stream. 143 | - `stream:readString()` - Reads and returns a string. 144 | - `stream:readBool()` - Reads and returns a boolean. 145 | - `stream:readFloat()` - Reads and returns a float. 146 | - `stream:readBits(n)` - Readss `n` bits from the string and returns them as a number. 147 | 148 | #### Signatures 149 | 150 | - `stream:pack(data, signature)` - Packs `data` into the stream according to the structure specified 151 | by `signature`. 152 | - `stream:unpack(signature)` - Uses `signature` to read data from the string, returning a table with 153 | the data. 154 | 155 | License 156 | --- 157 | 158 | MIT, see [`LICENSE`](LICENSE) for details. 159 | -------------------------------------------------------------------------------- /trickle.lua: -------------------------------------------------------------------------------- 1 | -- trickle v0.1.0 - Lua bitstream 2 | -- https://github.com/bjornbytes/trickle 3 | -- MIT License 4 | 5 | local trickle = {} 6 | 7 | local bit = bit32 or require 'bit' 8 | local lshift, rshift, band, bxor, bnot = bit.lshift, bit.rshift, bit.band, bit.bxor, bit.bnot 9 | 10 | local extract = bit.extract or function(x, i, n) 11 | return band(rshift(x, i), lshift(1, n or 1) - 1) 12 | end 13 | 14 | local replace = bit.replace or function(x, y, i, n) 15 | local mask = lshift(rshift(bit.bnot(0), 31 - (n - 1)), i) 16 | return bit.bxor(x, bit.band(bit.bxor(x, lshift(y, i)), mask)) 17 | end 18 | 19 | function trickle.create(str) 20 | local stream = { 21 | str = str or '', 22 | byte = nil, 23 | byteLen = nil 24 | } 25 | 26 | return setmetatable(stream, trickle) 27 | end 28 | 29 | function trickle:truncate() 30 | if self.byte then 31 | self.str = self.str .. string.char(self.byte) 32 | self.byte = nil 33 | self.byteLen = nil 34 | end 35 | 36 | return self.str 37 | end 38 | 39 | function trickle:clear() 40 | self.str = '' 41 | self.byte = nil 42 | self.byteLen = nil 43 | return self 44 | end 45 | 46 | function trickle:write(x, sig) 47 | if sig == 'string' then self:writeString(x) 48 | elseif sig == 'bool' then self:writeBool(x) 49 | elseif sig == 'float' then self:writeFloat(x) 50 | else 51 | local n = sig:match('(%d+)bit') 52 | self:writeBits(x, n) 53 | end 54 | 55 | return self 56 | end 57 | 58 | function trickle:writeString(string) 59 | self:truncate() 60 | string = tostring(string) 61 | self.str = self.str .. string.char(#string) .. string 62 | end 63 | 64 | function trickle:writeBool(bool) 65 | local x = bool and 1 or 0 66 | self:writeBits(x, 1) 67 | end 68 | 69 | function trickle:writeFloat(float) 70 | self:writeString(float) 71 | end 72 | 73 | function trickle:writeBits(x, n) 74 | local idx = 0 75 | repeat 76 | if not self.byte then self.byte = 0 self.byteLen = 0 end 77 | local numWrite = math.min(n, (7 - self.byteLen) + 1) 78 | local toWrite = extract(x, idx, numWrite) 79 | self.byte = replace(self.byte, toWrite, self.byteLen, numWrite) 80 | self.byteLen = self.byteLen + numWrite 81 | 82 | if self.byteLen == 8 then 83 | self.str = self.str .. string.char(self.byte) 84 | self.byte = nil 85 | self.byteLen = nil 86 | end 87 | 88 | n = n - numWrite 89 | idx = idx + numWrite 90 | until n == 0 91 | end 92 | 93 | function trickle:read(kind) 94 | if kind == 'string' then return self:readString() 95 | elseif kind == 'bool' then return self:readBool() 96 | elseif kind == 'float' then return self:readFloat() 97 | else 98 | local n = tonumber(kind:match('(%d+)bit')) 99 | return self:readBits(n) 100 | end 101 | end 102 | 103 | function trickle:readString() 104 | if self.byte then 105 | self.str = self.str:sub(2) 106 | self.byte = nil 107 | self.byteLen = nil 108 | end 109 | local len = self.str:byte(1) 110 | local res = '' 111 | if len then 112 | self.str = self.str:sub(2) 113 | res = self.str:sub(1, len) 114 | self.str = self.str:sub(len + 1) 115 | end 116 | return res 117 | end 118 | 119 | function trickle:readBool() 120 | return self:readBits(1) > 0 121 | end 122 | 123 | function trickle:readFloat() 124 | return tonumber(self:readString()) 125 | end 126 | 127 | function trickle:readBits(n) 128 | local x = 0 129 | local idx = 0 130 | while n > 0 do 131 | if not self.byte then self.byte = self.str:byte(1) or 0 self.byteLen = 0 end 132 | local numRead = math.min(n, (7 - self.byteLen) + 1) 133 | x = x + (extract(self.byte, self.byteLen, numRead) * (2 ^ idx)) 134 | self.byteLen = self.byteLen + numRead 135 | 136 | if self.byteLen == 8 then 137 | self.str = self.str:sub(2) 138 | self.byte = nil 139 | self.byteLen = nil 140 | end 141 | 142 | n = n - numRead 143 | idx = idx + numRead 144 | end 145 | 146 | return x 147 | end 148 | 149 | function trickle:pack(data, signature) 150 | local keys 151 | if signature.delta then 152 | keys = {} 153 | for _, key in ipairs(signature.delta) do 154 | if type(key) == 'table' then 155 | local has = 0 156 | for i = 1, #key do 157 | if data[key[i]] ~= nil then 158 | keys[key[i]] = true 159 | has = has + 1 160 | else 161 | keys[key[i]] = false 162 | end 163 | end 164 | if has == 0 then self:write(0, '1bit') 165 | elseif has == #key then self:write(1, '1bit') 166 | else error('Only part of message delta group "' .. table.concat(key, ', ') .. '" was provided.') end 167 | else 168 | self:write(data[key] ~= nil and 1 or 0, '1bit') 169 | keys[key] = data[key] ~= nil and true or false 170 | end 171 | end 172 | end 173 | 174 | for _, sig in ipairs(signature) do 175 | if not keys or keys[sig[1]] ~= false then 176 | if type(sig[2]) == 'table' then 177 | self:write(#data[sig[1]], '4bits') 178 | for i = 1, #data[sig[1]] do self:pack(data[sig[1]][i], sig[2]) end 179 | else 180 | self:write(data[sig[1]], sig[2]) 181 | end 182 | end 183 | end 184 | end 185 | 186 | function trickle:unpack(signature) 187 | local keys 188 | if signature.delta then 189 | keys = {} 190 | for i = 1, #signature.delta do 191 | local val = self:read('1bit') > 0 192 | if type(signature.delta[i]) == 'table' then 193 | for j = 1, #signature.delta[i] do keys[signature.delta[i][j]] = val end 194 | else 195 | keys[signature.delta[i]] = val 196 | end 197 | end 198 | end 199 | 200 | local data = {} 201 | for _, sig in ipairs(signature) do 202 | if not keys or keys[sig[1]] ~= false then 203 | if type(sig[2]) == 'table' then 204 | local ct = self:read('4bits') 205 | data[sig[1]] = {} 206 | for i = 1, ct do table.insert(data[sig[1]], self:unpack(sig[2])) end 207 | else 208 | data[sig[1]] = self:read(sig[2]) 209 | end 210 | end 211 | end 212 | return data 213 | end 214 | 215 | trickle.__tostring = trickle.truncate 216 | trickle.__index = trickle 217 | 218 | return { create = trickle.create } 219 | --------------------------------------------------------------------------------