Browse Source

Initial commit;

bjorn 9 năm trước cách đây
commit
b3dfe3558f
3 tập tin đã thay đổi với 286 bổ sung0 xóa
  1. 19 0
      LICENSE
  2. 45 0
      README.md
  3. 222 0
      trickle.lua

+ 19 - 0
LICENSE

@@ -0,0 +1,19 @@
+Copyright (c) 2015 Bjorn Swenson
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 45 - 0
README.md

@@ -0,0 +1,45 @@
+Trickle
+===
+
+Trickle is a bitstream for Lua.  When writing data to a trickle stream, you must specify its size in
+bits.  The stream will automatically pack this data into compressed bytes, which often leads to
+greatly reduced network bandwidth.  This is useful in networking applications where it is necessary
+to reduce network traffic, such as in realtime multiplayer games.
+
+How it works
+---
+
+Coming soon.
+
+Usage
+---
+
+```
+local trickle = require 'trickle'
+local stream = trickle.create()
+
+stream:write(7, '3bits')
+stream:write(true, 'bool')
+stream:write(1.8, 'float')
+
+-- send over the network
+
+stream:read('3bits') -- 7
+stream:read('bool') -- true
+stream:read('float') -- 1.8
+```
+
+Signatures
+---
+
+Coming soon.
+
+Documentation
+---
+
+Coming soon.
+
+License
+---
+
+MIT, see [`LICENSE`](LICENSE) for details.

+ 222 - 0
trickle.lua

@@ -0,0 +1,222 @@
+local trickle = {}
+
+local function byteExtract(x, a, b)
+  b = b or a
+  x = x % (2 ^ (b + 1))
+  for i = 1, a do
+    x = math.floor(x / 2)
+  end
+  return x
+end
+
+local function byteInsert(x, y, a, b)
+  local res = x
+  for i = a, b do
+    local e = byteExtract(y, i - a)
+    if e ~= byteExtract(x, i) then
+      res = (e == 1) and res + (2 ^ i) or res - (2 ^ i)
+    end
+  end
+  return res
+end
+
+function trickle.create(str)
+  local stream = {
+    str = str or '',
+    byte = nil,
+    byteLen = nil
+  }
+
+  return setmetatable(stream, trickle)
+end
+
+function trickle:truncate()
+  if self.byte then
+    self.str = self.str .. string.char(self.byte)
+    self.byte = nil
+    self.byteLen = nil
+  end
+
+  return self.str
+end
+
+function trickle:clear()
+  self.str = ''
+  self.byte = nil
+  self.byteLen = nil
+  return self
+end
+
+function trickle:write(x, sig)
+  if sig == 'string' then self:writeString(x)
+  elseif sig == 'bool' then self:writeBool(x)
+  elseif sig == 'float' then self:writeFloat(x)
+  else
+    local n = sig:match('(%d+)bit')
+    self:writeBits(x, n)
+  end
+
+  return self
+end
+
+function trickle:writeString(string)
+  self:truncate()
+  string = tostring(string)
+  self.str = self.str .. string.char(#string) .. string
+end
+
+function trickle:writeBool(bool)
+  local x = bool and 1 or 0
+  self:writeBits(x, 1)
+end
+
+function trickle:writeFloat(float)
+  self:writeString(float)
+end
+
+function trickle:writeBits(x, n)
+  local idx = 0
+  repeat
+    if not self.byte then self.byte = 0 self.byteLen = 0 end
+    local numWrite = math.min(n, (7 - self.byteLen) + 1)
+    local toWrite = byteExtract(x, idx, idx + (numWrite - 1))
+    self.byte = byteInsert(self.byte, toWrite, self.byteLen, self.byteLen + (numWrite - 1))
+    self.byteLen = self.byteLen + numWrite
+
+    if self.byteLen == 8 then
+      self.str = self.str .. string.char(self.byte)
+      self.byte = nil
+      self.byteLen = nil
+    end
+
+    n = n - numWrite
+    idx = idx + numWrite
+  until n == 0
+end
+
+function trickle:read(kind)
+  if kind == 'string' then return self:readString()
+  elseif kind == 'bool' then return self:readBool()
+  elseif kind == 'float' then return self:readFloat()
+  else
+    local n = tonumber(kind:match('(%d+)bit'))
+    return self:readBits(n)
+  end
+end
+
+function trickle:readString()
+  if self.byte then
+    self.str = self.str:sub(2)
+    self.byte = nil
+    self.byteLen = nil
+  end
+  local len = self.str:byte(1)
+  local res = ''
+  if len then
+    self.str = self.str:sub(2)
+    res = self.str:sub(1, len)
+    self.str = self.str:sub(len + 1)
+  end
+  return res
+end
+
+function trickle:readBool()
+  return self:readBits(1) > 0
+end
+
+function trickle:readFloat()
+  return tonumber(self:readString())
+end
+
+function trickle:readBits(n)
+  local x = 0
+  local idx = 0
+  while n > 0 do
+    if not self.byte then self.byte = self.str:byte(1) or 0 self.byteLen = 0 end
+    local numRead = math.min(n, (7 - self.byteLen) + 1)
+    x = x + (byteExtract(self.byte, self.byteLen, self.byteLen + (numRead - 1)) * (2 ^ idx))
+    self.byteLen = self.byteLen + numRead
+
+    if self.byteLen == 8 then
+      self.str = self.str:sub(2)
+      self.byte = nil
+      self.byteLen = nil
+    end
+
+    n = n - numRead
+    idx = idx + numRead
+  end
+
+  return x
+end
+
+function trickle:pack(data, signature)
+  local keys
+  if signature.delta then
+    keys = {}
+    for _, key in ipairs(signature.delta) do
+      if type(key) == 'table' then
+        local has = 0
+        for i = 1, #key do
+          if data[key[i]] ~= nil then
+            keys[key[i]] = true
+            has = has + 1
+          else
+            keys[key[i]] = false
+          end
+        end
+        if has == 0 then self:write(0, '1bit')
+        elseif has == #key then self:write(1, '1bit')
+        else error('Only part of message delta group "' .. table.concat(key, ', ') .. '" was provided.') end
+      else
+        self:write(data[key] ~= nil and 1 or 0, '1bit')
+        keys[key] = data[key] ~= nil and true or false
+      end
+    end
+  end
+
+  for _, sig in ipairs(signature) do
+    if not keys or keys[sig[1]] ~= false then
+      if type(sig[2]) == 'table' then
+        self:write(#data[sig[1]], '4bits')
+        for i = 1, #data[sig[1]] do self:pack(data[sig[1]][i], sig[2]) end
+      else
+        self:write(data[sig[1]], sig[2])
+      end
+    end
+  end
+end
+
+function trickle:unpack(signature)
+  local keys
+  if signature.delta then
+    keys = {}
+    for i = 1, #signature.delta do
+      local val = self:read('1bit') > 0
+      if type(signature.delta[i]) == 'table' then
+        for j = 1, #signature.delta[i] do keys[signature.delta[i][j]] = val end
+      else
+        keys[signature.delta[i]] = val
+      end
+    end
+  end
+
+  local data = {}
+  for _, sig in ipairs(signature) do
+    if not keys or keys[sig[1]] ~= false then
+      if type(sig[2]) == 'table' then
+        local ct = self:read('4bits')
+        data[sig[1]] = {}
+        for i = 1, ct do table.insert(data[sig[1]], self:unpack(sig[2])) end
+      else
+        data[sig[1]] = self:read(sig[2])
+      end
+    end
+  end
+  return data
+end
+
+trickle.__tostring = trickle.truncate
+trickle.__index = trickle
+
+return { create = trickle.create }