Start networked lobbying

This commit is contained in:
Jaidyn Ann 2021-02-07 00:37:19 -06:00
parent 67b6482c3e
commit 487cde4801
37 changed files with 11622 additions and 35 deletions

View File

@ -1,3 +1,6 @@
mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST)))
current_dir := $(notdir $(patsubst %/,%,$(dir $(mkfile_path))))
love: love:
zip -9r bin/horsehorse.love ./* zip -9r bin/horsehorse.love ./*

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 B

26
lib/bitser/.travis.yml Normal file
View File

@ -0,0 +1,26 @@
language: python
sudo: false
env:
- LUA="luajit=2.0"
before_install:
- pip install hererocks
- hererocks lua_install -r^ --$LUA
- export PATH=$PATH:$PWD/lua_install/bin
install:
- luarocks install luacheck
- luarocks install busted
- luarocks install luacov
- luarocks install luacov-coveralls
- luarocks install middleclass
- wget https://raw.githubusercontent.com/bartbes/slither/b9cf6daa1e8995093aa80a40ee9ff98402eeb602/slither.lua
- wget https://raw.githubusercontent.com/vrld/hump/038bc9025f1cb850355f4b073357b087b8122da9/class.lua
script:
- luacheck --std max+busted bitser.lua spec --globals love --no-max-line-length
- busted --verbose --coverage
after_success:
- luacov-coveralls --include bitser -e $TRAVIS_BUILD_DIR/lua_install

53
lib/bitser/README.md Normal file
View File

@ -0,0 +1,53 @@
# bitser
[![Build Status](https://travis-ci.org/gvx/bitser.svg?branch=master)](https://travis-ci.org/gvx/bitser)
[![Coverage Status](https://coveralls.io/repos/github/gvx/bitser/badge.svg?branch=master)](https://coveralls.io/github/gvx/bitser?branch=master)
Serializes and deserializes Lua values with LuaJIT.
```lua
local bitser = require 'bitser'
bitser.register('someResource', someResource)
bitser.registerClass(SomeClass)
serializedString = bitser.dumps(someValue)
someValue = bitser.loads(serializedString)
```
Documentation can be found in [USAGE.md](USAGE.md).
Pull requests, bug reports and other feedback welcome! :heart:
Bitser is released under the ISC license (functionally equivalent to the BSD
2-Clause and MIT licenses).
Please note that bitser requires LuaJIT for its `ffi` library and JIT compilation. Without JIT, it may or may not run, but it will be much slower than usual. This primarily affects Android and iOS, because JIT is disabled on those platforms.
## Why would I use this?
Because it's fast. Because it produces tiny output. Because the name means "snappier"
or "unfriendlier" in Dutch. Because it's safe to use with untrusted data.
Because it's inspired by [binser](https://github.com/bakpakin/binser), which is great.
## How do I use the benchmark thingy?
Download zero or more of [binser.lua](https://raw.githubusercontent.com/bakpakin/binser/master/binser.lua),
[ser.lua](https://raw.githubusercontent.com/gvx/Ser/master/ser.lua),
[smallfolk.lua](https://raw.githubusercontent.com/gvx/Smallfolk/master/smallfolk.lua),
[serpent.lua](https://raw.githubusercontent.com/pkulchenko/serpent/master/src/serpent.lua) and
[MessagePack.lua](https://raw.githubusercontent.com/fperrad/lua-MessagePack/master/src/MessagePack.lua), and run:
love .
You do need [LÖVE](https://love2d.org/) for that.
You can add more cases in the folder `cases/` (check out `_new.lua`), and add other
serializers to the benchmark in `main.lua`. If you do either of those things, please
send me a pull request!
## You can register classes?
Yes. At the moment, bitser supports MiddleClass, SECL, hump.class, Slither and Moonscript classes (and
probably some other class libraries by accident).

234
lib/bitser/USAGE.md Normal file
View File

@ -0,0 +1,234 @@
* [Basic usage](#basic-usage)
* [Serializing class instances](#serializing-class-instances)
* [Advanced usage](#advanced-usage)
* [Reference](#reference)
* [`bitser.dumps`](#dumps)
* [`bitser.dumpLoveFile`](#dumplovefile)
* [`bitser.loads`](#loads)
* [`bitser.loadData`](#loaddata)
* [`bitser.loadLoveFile`](#loadlovefile)
* [`bitser.register`](#register)
* [`bitser.registerClass`](#registerclass)
* [`bitser.unregister`](#unregister)
* [`bitser.unregisterClass`](#unregisterclass)
* [`bitser.reserveBuffer`](#reservebuffer)
* [`bitser.clearBuffer`](#clearbuffer)
# Basic usage
```lua
local bitser = require 'bitser'
-- some_thing can be almost any lua value
local binary_data = bitser.dumps(some_thing)
-- binary_data is a string containing some serialized value
local copy_of_some_thing = bitser.loads(binary_data)
```
Bitser can't dump values of type `function`, `userdata` or `thread`, or anything that
contains one of those. If you need to, look into [`bitser.register`](#register).
# Serializing class instances
All you need to make bitser correctly serialize your class instances is register that class:
```lua
-- this is usually enough
bitser.registerClass(MyClass)
-- if you use Slither, you can add it to __attributes__
class 'MyClass' {
__attributes__ = {bitser.registerClass},
-- insert rest of class here
}
local data = bitser.dumps(MyClass(42))
local instance = bitser.loads(data)
```
Note that classnames need to be unique to avoid confusion, so if you have two different classes named `Foo` you'll need to do
something like:
```lua
-- in module_a.lua
bitser.registerClass('module_a.Foo', Foo)
-- in module_b.lua
bitser.registerClass('module_b.Foo', Foo)
```
See the reference sections on [`bitser.registerClass`](#registerclass) and
[`bitser.unregisterClass`](#unregisterclass) for more information.
## Supported class libraries
* MiddleClass
* SECL
* hump.class
* Slither
* Moonscript classes
# Advanced usage
If you use [LÖVE](https://love2d.org/), you'll want to use [`bitser.dumpLoveFile`](#dumplovefile) and [`bitser.loadLoveFile`](#loadlovefile) if you want to serialize to the save directory. You also might have images and other resources that you'll need to register, like follows:
```lua
function love.load()
bad_guy_img = bitser.register('bad_guy_img', love.graphics.newImage('img/bad_guy.png'))
if love.filesystem.exists('save_point.dat') then
level_data = bitser.loadLoveFile('save_point.dat')
else
level_data = create_level_data()
end
end
function save_point_reached()
bitser.dumpLoveFile('save_point.dat', level_data)
end
```
# Reference
## dumps
```lua
string = bitser.dumps(value)
```
Basic serialization of `value` into a Lua string.
See also: [`bitser.loads`](#loads).
## dumpLoveFile
```lua
bitser.dumpLoveFile(file_name, value)
```
Serializes `value` and writes the result to `file_name` more efficiently than serializing to a string and writing
that string to a file. Only useful if you're running [LÖVE](https://love2d.org/).
See also: [`bitser.loadLoveFile`](#loadlovefile).
## loads
```lua
value = bitser.loads(string)
```
Deserializes `value` from `string`.
See also: [`bitser.dumps`](#dumps).
## loadData
```lua
value = bitser.loadData(light_userdata, size)
```
Deserializes `value` from raw data. You probably won't need to use this function ever.
When running [LÖVE](https://love2d.org/), you would use it like this:
```lua
value = bitser.loadData(data:getPointer(), data:getSize())
```
Where `data` is an instance of a subclass of [Data](https://love2d.org/wiki/Data).
## loadLoveFile
```lua
value = bitser.loadLoveFile(file_name)
```
Reads from `file_name` and deserializes `value` more efficiently than reading the file and then deserializing that string.
Only useful if you're running [LÖVE](https://love2d.org/).
See also: [`bitser.dumpLoveFile`](#dumplovefile).
## register
```lua
resource = bitser.register(name, resource)
```
Registers the value `resource` with the name `name`, which has to be a unique string. Registering static resources like images,
functions, classes, huge strings and LuaJIT ctypes, makes sure bitser doesn't attempt to serialize them, but only stores a named
reference to them.
Returns the registered resource as a convenience.
See also: [`bitser.unregister`](#unregister).
## registerClass
```lua
class = bitser.registerClass(class)
class = bitser.registerClass(name, class)
class = bitser.registerClass(name, class, classkey, deserializer)
```
Registers the class `class`, so that bitser can correctly serialize and deserialize instances of `class`.
Note that if you want to serialize the class _itself_, you'll need to [register the class as a resource](#register).
Most of the time the first variant is enough, but some class libraries don't store the
class name on the class object itself, in which case you'll need to use the second variant.
Class names also have to be unique, so if you use multiple classes with the same name, you'll need to use the second
variant as well to give them different names.
The arguments `classkey` and `deserializer` exist so you can hook in unsupported class libraries without needing
to patch bitser. [See the list of supported class libraries](#supported-class-libraries).
If not nil, the argument `classkey` should be a string such that
`rawget(obj, classkey) == class` for any `obj` whose type is `class`. This is done so that key is skipped for serialization.
If not nil, the argument `deserializer` should be a function such that `deserializer(obj, class)` returns a valid
instance of `class` with the properties of `obj`. `deserializer` is allowed to mutate `obj`.
Returns the registered class as a convenience.
See also: [`bitser.unregisterClass`](#unregisterclass).
## unregister
```lua
bitser.unregister(name)
```
Deregisters the previously registered value with the name `name`.
See also: [`bitser.register`](#register).
## unregisterClass
```lua
bitser.unregisterClass(name)
```
Deregisters the previously registered class with the name `name`. Note that this works by name and not value,
which is useful in a context where you don't have a reference to the class you want to unregister.
See also: [`bitser.registerClass`](#registerclass).
## reserveBuffer
```lua
bitser.reserveBuffer(num_bytes)
```
Makes sure the buffer used for reading and writing serialized data is at least `num_bytes` large.
You probably don't need to ever use this function.
## clearBuffer
```lua
bitser.clearBuffer()
```
Frees up the buffer used for reading and writing serialized data for garbage collection.
You'll rarely need to use this function, except if you needed a huge buffer before and now only need a small buffer
(or are done (de)serializing altogether). Most of the time, using this function will decrease performance needlessly.

View File

@ -0,0 +1,3 @@
-- write your own!
-- data to be tested, repetitions, number of tries
return {}, 10000, 3

View File

@ -0,0 +1,7 @@
local t = {}
for i = 1, 2000 do
t[i] = 100
end
return t, 500, 5

View File

@ -0,0 +1,20 @@
local ffi = require("ffi")
ffi.cdef[[
struct simple_struct {
int a;
int b;
};
struct nested_struct {
int a;
struct simple_struct b;
};
]]
local int_data = ffi.new('int', 5)
local struct_data = ffi.new('struct nested_struct', {10, {20, 30}})
return {int_data, struct_data, {ffi.new("int",1),5,ffi.new("int",67)}}, 1000, 3

View File

@ -0,0 +1,7 @@
local cthulhu = {{}, {}, {}}
cthulhu.fhtagn = cthulhu
cthulhu[1][cthulhu[2]] = cthulhu[3]
cthulhu[2][cthulhu[1]] = cthulhu[2]
cthulhu[3][cthulhu[3]] = cthulhu
return cthulhu, 10000, 3

View File

@ -0,0 +1,7 @@
local t = {}
for i = 1, 200 do
t[math.random(1000)] = math.random(100)
end
return t, 30000, 5

View File

@ -0,0 +1,5 @@
-- test metatables
local metatable = {__mode = 'k', foo={1,2,3,4,5,6,7,8,10,11,12}}
metatable.__index = metatable
return setmetatable({test=true}, metatable), 10000, 3

View File

@ -0,0 +1,6 @@
local t = {}
local x = {10, 50, 40, 30, 20}
for i = 1, 40 do
t[i] = x
end
return t, 10000, 3

4
lib/bitser/conf.lua Normal file
View File

@ -0,0 +1,4 @@
function love.conf(t)
t.version = "11.3"
t.console = true
end

491
lib/bitser/init.lua Normal file
View File

@ -0,0 +1,491 @@
--[[
Copyright (c) 2020, Jasmijn Wellner
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
]]
local VERSION = '1.1'
local floor = math.floor
local pairs = pairs
local type = type
local insert = table.insert
local getmetatable = getmetatable
local setmetatable = setmetatable
local ffi = require("ffi")
local buf_pos = 0
local buf_size = -1
local buf = nil
local buf_is_writable = true
local writable_buf = nil
local writable_buf_size = nil
local SEEN_LEN = {}
local function Buffer_prereserve(min_size)
if buf_size < min_size then
buf_size = min_size
buf = ffi.new("uint8_t[?]", buf_size)
buf_is_writable = true
end
end
local function Buffer_clear()
buf_size = -1
buf = nil
buf_is_writable = true
writable_buf = nil
writable_buf_size = nil
end
local function Buffer_makeBuffer(size)
if not buf_is_writable then
buf = writable_buf
buf_size = writable_buf_size
writable_buf = nil
writable_buf_size = nil
buf_is_writable = true
end
buf_pos = 0
Buffer_prereserve(size)
end
local function Buffer_newReader(str)
Buffer_makeBuffer(#str)
ffi.copy(buf, str, #str)
end
local function Buffer_newDataReader(data, size)
if buf_is_writable then
writable_buf = buf
writable_buf_size = buf_size
end
buf_is_writable = false
buf_pos = 0
buf_size = size
buf = ffi.cast("uint8_t*", data)
end
local function Buffer_reserve(additional_size)
while buf_pos + additional_size > buf_size do
buf_size = buf_size * 2
local oldbuf = buf
buf = ffi.new("uint8_t[?]", buf_size)
buf_is_writable = true
ffi.copy(buf, oldbuf, buf_pos)
end
end
local function Buffer_write_byte(x)
Buffer_reserve(1)
buf[buf_pos] = x
buf_pos = buf_pos + 1
end
local function Buffer_write_raw(data, len)
Buffer_reserve(len)
ffi.copy(buf + buf_pos, data, len)
buf_pos = buf_pos + len
end
local function Buffer_write_string(s)
Buffer_write_raw(s, #s)
end
local function Buffer_write_data(ct, len, ...)
Buffer_write_raw(ffi.new(ct, ...), len)
end
local function Buffer_ensure(numbytes)
if buf_pos + numbytes > buf_size then
error("malformed serialized data")
end
end
local function Buffer_read_byte()
Buffer_ensure(1)
local x = buf[buf_pos]
buf_pos = buf_pos + 1
return x
end
local function Buffer_read_string(len)
Buffer_ensure(len)
local x = ffi.string(buf + buf_pos, len)
buf_pos = buf_pos + len
return x
end
local function Buffer_read_raw(data, len)
ffi.copy(data, buf + buf_pos, len)
buf_pos = buf_pos + len
return data
end
local function Buffer_read_data(ct, len)
return Buffer_read_raw(ffi.new(ct), len)
end
local resource_registry = {}
local resource_name_registry = {}
local class_registry = {}
local class_name_registry = {}
local classkey_registry = {}
local class_deserialize_registry = {}
local serialize_value
local function write_number(value, _)
if floor(value) == value and value >= -2147483648 and value <= 2147483647 then
if value >= -27 and value <= 100 then
--small int
Buffer_write_byte(value + 27)
elseif value >= -32768 and value <= 32767 then
--short int
Buffer_write_byte(250)
Buffer_write_data("int16_t[1]", 2, value)
else
--long int
Buffer_write_byte(245)
Buffer_write_data("int32_t[1]", 4, value)
end
else
--double
Buffer_write_byte(246)
Buffer_write_data("double[1]", 8, value)
end
end
local function write_string(value, _)
if #value < 32 then
--short string
Buffer_write_byte(192 + #value)
else
--long string
Buffer_write_byte(244)
write_number(#value)
end
Buffer_write_string(value)
end
local function write_nil(_, _)
Buffer_write_byte(247)
end
local function write_boolean(value, _)
Buffer_write_byte(value and 249 or 248)
end
local function write_table(value, seen)
local classkey
local metatable = getmetatable(value)
local classname = (class_name_registry[value.class] -- MiddleClass
or class_name_registry[value.__baseclass] -- SECL
or class_name_registry[metatable] -- hump.class
or class_name_registry[value.__class__] -- Slither
or class_name_registry[value.__class]) -- Moonscript class
if classname then
classkey = classkey_registry[classname]
Buffer_write_byte(242)
serialize_value(classname, seen)
elseif metatable then
Buffer_write_byte(253)
else
Buffer_write_byte(240)
end
local len = #value
write_number(len, seen)
for i = 1, len do
serialize_value(value[i], seen)
end
local klen = 0
for k in pairs(value) do
if (type(k) ~= 'number' or floor(k) ~= k or k > len or k < 1) and k ~= classkey then
klen = klen + 1
end
end
write_number(klen, seen)
for k, v in pairs(value) do
if (type(k) ~= 'number' or floor(k) ~= k or k > len or k < 1) and k ~= classkey then
serialize_value(k, seen)
serialize_value(v, seen)
end
end
if metatable and not classname then
serialize_value(metatable, seen)
end
end
local function write_cdata(value, seen)
local ty = ffi.typeof(value)
if ty == value then
-- ctype
Buffer_write_byte(251)
serialize_value(tostring(ty):sub(7, -2), seen)
return
end
-- cdata
Buffer_write_byte(252)
serialize_value(ty, seen)
local len = ffi.sizeof(value)
write_number(len)
Buffer_write_raw(ffi.typeof('$[1]', ty)(value), len)
end
local types = {number = write_number, string = write_string, table = write_table, boolean = write_boolean, ["nil"] = write_nil, cdata = write_cdata}
serialize_value = function(value, seen)
if seen[value] then
local ref = seen[value]
if ref < 64 then
--small reference
Buffer_write_byte(128 + ref)
else
--long reference
Buffer_write_byte(243)
write_number(ref, seen)
end
return
end
local t = type(value)
if t ~= 'number' and t ~= 'boolean' and t ~= 'nil' and t ~= 'cdata' then
seen[value] = seen[SEEN_LEN]
seen[SEEN_LEN] = seen[SEEN_LEN] + 1
end
if resource_name_registry[value] then
local name = resource_name_registry[value]
if #name < 16 then
--small resource
Buffer_write_byte(224 + #name)
Buffer_write_string(name)
else
--long resource
Buffer_write_byte(241)
write_string(name, seen)
end
return
end
(types[t] or
error("cannot serialize type " .. t)
)(value, seen)
end
local function serialize(value)
Buffer_makeBuffer(4096)
local seen = {[SEEN_LEN] = 0}
serialize_value(value, seen)
end
local function add_to_seen(value, seen)
insert(seen, value)
return value
end
local function reserve_seen(seen)
insert(seen, 42)
return #seen
end
local function deserialize_value(seen)
local t = Buffer_read_byte()
if t < 128 then
--small int
return t - 27
elseif t < 192 then
--small reference
return seen[t - 127]
elseif t < 224 then
--small string
return add_to_seen(Buffer_read_string(t - 192), seen)
elseif t < 240 then
--small resource
return add_to_seen(resource_registry[Buffer_read_string(t - 224)], seen)
elseif t == 240 or t == 253 then
--table
local v = add_to_seen({}, seen)
local len = deserialize_value(seen)
for i = 1, len do
v[i] = deserialize_value(seen)
end
len = deserialize_value(seen)
for _ = 1, len do
local key = deserialize_value(seen)
v[key] = deserialize_value(seen)
end
if t == 253 then
setmetatable(v, deserialize_value(seen))
end
return v
elseif t == 241 then
--long resource
local idx = reserve_seen(seen)
local value = resource_registry[deserialize_value(seen)]
seen[idx] = value
return value
elseif t == 242 then
--instance
local instance = add_to_seen({}, seen)
local classname = deserialize_value(seen)
local class = class_registry[classname]
local classkey = classkey_registry[classname]
local deserializer = class_deserialize_registry[classname]
local len = deserialize_value(seen)
for i = 1, len do
instance[i] = deserialize_value(seen)
end
len = deserialize_value(seen)
for _ = 1, len do
local key = deserialize_value(seen)
instance[key] = deserialize_value(seen)
end
if classkey then
instance[classkey] = class
end
return deserializer(instance, class)
elseif t == 243 then
--reference
return seen[deserialize_value(seen) + 1]
elseif t == 244 then
--long string
return add_to_seen(Buffer_read_string(deserialize_value(seen)), seen)
elseif t == 245 then
--long int
return Buffer_read_data("int32_t[1]", 4)[0]
elseif t == 246 then
--double
return Buffer_read_data("double[1]", 8)[0]
elseif t == 247 then
--nil
return nil
elseif t == 248 then
--false
return false
elseif t == 249 then
--true
return true
elseif t == 250 then
--short int
return Buffer_read_data("int16_t[1]", 2)[0]
elseif t == 251 then
--ctype
return ffi.typeof(deserialize_value(seen))
elseif t == 252 then
local ctype = deserialize_value(seen)
local len = deserialize_value(seen)
local read_into = ffi.typeof('$[1]', ctype)()
Buffer_read_raw(read_into, len)
return ctype(read_into[0])
else
error("unsupported serialized type " .. t)
end
end
local function deserialize_MiddleClass(instance, class)
return setmetatable(instance, class.__instanceDict)
end
local function deserialize_SECL(instance, class)
return setmetatable(instance, getmetatable(class))
end
local deserialize_humpclass = setmetatable
local function deserialize_Slither(instance, class)
return getmetatable(class).allocate(instance)
end
local function deserialize_Moonscript(instance, class)
return setmetatable(instance, class.__base)
end
return {dumps = function(value)
serialize(value)
return ffi.string(buf, buf_pos)
end, dumpLoveFile = function(fname, value)
serialize(value)
assert(love.filesystem.write(fname, ffi.string(buf, buf_pos)))
end, loadLoveFile = function(fname)
local serializedData, error = love.filesystem.newFileData(fname)
assert(serializedData, error)
Buffer_newDataReader(serializedData:getPointer(), serializedData:getSize())
local value = deserialize_value({})
-- serializedData needs to not be collected early in a tail-call
-- so make sure deserialize_value returns before loadLoveFile does
return value
end, loadData = function(data, size)
if size == 0 then
error('cannot load value from empty data')
end
Buffer_newDataReader(data, size)
return deserialize_value({})
end, loads = function(str)
if #str == 0 then
error('cannot load value from empty string')
end
Buffer_newReader(str)
return deserialize_value({})
end, register = function(name, resource)
assert(not resource_registry[name], name .. " already registered")
resource_registry[name] = resource
resource_name_registry[resource] = name
return resource
end, unregister = function(name)
resource_name_registry[resource_registry[name]] = nil
resource_registry[name] = nil
end, registerClass = function(name, class, classkey, deserializer)
if not class then
class = name
name = class.__name__ or class.name or class.__name
end
if not classkey then
if class.__instanceDict then
-- assume MiddleClass
classkey = 'class'
elseif class.__baseclass then
-- assume SECL
classkey = '__baseclass'
end
-- assume hump.class, Slither, Moonscript class or something else that doesn't store the
-- class directly on the instance
end
if not deserializer then
if class.__instanceDict then
-- assume MiddleClass
deserializer = deserialize_MiddleClass
elseif class.__baseclass then
-- assume SECL
deserializer = deserialize_SECL
elseif class.__index == class then
-- assume hump.class
deserializer = deserialize_humpclass
elseif class.__name__ then
-- assume Slither
deserializer = deserialize_Slither
elseif class.__base then
-- assume Moonscript class
deserializer = deserialize_Moonscript
else
error("no deserializer given for unsupported class library")
end
end
class_registry[name] = class
classkey_registry[name] = classkey
class_deserialize_registry[name] = deserializer
class_name_registry[class] = name
return class
end, unregisterClass = function(name)
class_name_registry[class_registry[name]] = nil
classkey_registry[name] = nil
class_deserialize_registry[name] = nil
class_registry[name] = nil
end, reserveBuffer = Buffer_prereserve, clearBuffer = Buffer_clear, version = VERSION}

195
lib/bitser/main.lua Normal file
View File

@ -0,0 +1,195 @@
local found_bitser, bitser = pcall(require, 'bitser')
local found_binser, binser = pcall(require, 'binser')
local found_ser, ser = pcall(require, 'ser')
local found_serpent, serpent = pcall(require, 'serpent')
local found_smallfolk, smallfolk = pcall(require, 'smallfolk')
local found_msgpack, msgpack = pcall(require, 'MessagePack')
local cases
local selected_case = 1
local sers = {}
local desers = {}
if found_bitser then
sers.bitser = bitser.dumps
desers.bitser = bitser.loads
bitser.reserveBuffer(1024 * 1024)
end
if found_binser then
sers.binser = binser.s
desers.binser = binser.d
end
if found_ser then
sers.ser = ser
desers.ser = loadstring
end
if found_serpent then
sers.serpent = serpent.dump
desers.serpent = loadstring
end
if found_smallfolk then
sers.smallfolk = smallfolk.dumps
desers.smallfolk = smallfolk.loads
end
if found_msgpack then
sers.msgpack = msgpack.pack
desers.msgpack = msgpack.unpack
end
local view_absolute = true
local resultname = "serialisation time in seconds"
function love.load()
cases = love.filesystem.getDirectoryItems("cases")
state = 'select_case'
love.graphics.setBackgroundColor(1, 230/256, 220/256)
love.graphics.setColor(40/256, 30/256, 0/256)
love.window.setTitle("Select a benchmark testcase")
end
function love.keypressed(key)
if state == 'select_case' then
if key == 'up' then
selected_case = (selected_case - 2) % #cases + 1
elseif key == 'down' then
selected_case = selected_case % #cases + 1
elseif key == 'return' then
state = 'calculate_results'
love.window.setTitle("Running benchmark...")
end
elseif state == 'results' then
if key == 'r' then
view_absolute = not view_absolute
elseif key == 'right' then
if results == results_ser then
results = results_deser
resultname = "deserialisation time in seconds"
elseif results == results_deser then
results = results_size
resultname = "size of output in bytes"
elseif results == results_size then
results = results_ser
resultname = "serialisation time in seconds"
end
elseif key == 'left' then
if results == results_ser then
results = results_size
resultname = "size of output in bytes"
elseif results == results_deser then
results = results_ser
resultname = "serialisation time in seconds"
elseif results == results_size then
results = results_deser
resultname = "deserialisation time in seconds"
end
elseif key == 'escape' then
state = 'select_case'
love.window.setTitle("Select a benchmark testcase")
end
end
end
function love.draw()
if state == 'select_case' then
for i, case in ipairs(cases) do
love.graphics.print(case, selected_case == i and 60 or 20, i * 20)
end
local i = 2
love.graphics.print('serialisation libraries installed:', 200, 20)
for sername in pairs(sers) do
love.graphics.print(sername, 200, i * 20)
i = i + 1
end
elseif state == 'calculate_results' then
love.graphics.print("Running benchmark...", 20, 20)
love.graphics.print("This may take a while", 20, 40)
state = 'calculate_results_2'
elseif state == 'calculate_results_2' then
local data, iters, tries = love.filesystem.load("cases/" .. cases[selected_case])()
results_ser = {}
results = results_ser
resultname = "serialisation time in seconds"
results_size = {}
results_deser = {}
errors = {}
for sername, serializer in pairs(sers) do
results_ser[sername] = math.huge
results_deser[sername] = math.huge
end
local outputs = {}
for try = 1, tries do
for sername, serializer in pairs(sers) do
local output
local success, diff = pcall(function()
local t = os.clock()
for i = 1, iters do
output = serializer(data)
end
return os.clock() - t
end)
if not success and not errors[sername] then
errors[sername] = diff
elseif success and diff < results_ser[sername] then
results_ser[sername] = diff
end
if try == 1 then
outputs[sername] = output
results_size[sername] = output and #output or math.huge
end
end
end
for try = 1, tries do
for sername, deserializer in pairs(desers) do
local input = outputs[sername]
local success, diff = pcall(function()
local t = os.clock()
for i = 1, iters / 10 do
deserializer(input)
end
return os.clock() - t
end)
if not success and not errors[sername] then
errors[sername] = diff
elseif success and diff < results_deser[sername] then
results_deser[sername] = diff
end
end
end
state = 'results'
love.window.setTitle("Results for " .. cases[selected_case])
elseif state == 'results' then
local results_min = math.huge
local results_max = -math.huge
for sername, result in pairs(results) do
if result < results_min then
results_min = result
end
if result > results_max and result < math.huge then
results_max = result
end
end
if view_absolute then results_min = 0 end
local i = 1
for sername, result in pairs(results) do
love.graphics.print(sername, 20, i * 20)
if result == math.huge then
love.graphics.setColor(220/256, 30/256, 0)
love.graphics.rectangle('fill', 100, i * 20, 780 - 100, 18)
love.graphics.setColor(40/256, 30/256, 0)
love.graphics.print(errors[sername], 102, i * 20 + 2)
else
love.graphics.rectangle('fill', 100, i * 20, (780 - 100) * (result - results_min) / (results_max - results_min), 18)
end
i = i + 1
end
love.graphics.print(results_min, 100, i * 20)
love.graphics.print(results_max, 780 - love.graphics.getFont():getWidth(results_max), i * 20)
love.graphics.print(resultname .." (smaller is better; try left, right, R, escape)", 100, i * 20 + 20)
end
end

View File

@ -0,0 +1,343 @@
local ffi = require 'ffi'
_G.love = {filesystem = {newFileData = function()
return {getPointer = function()
local buf = ffi.new("uint8_t[?]", #love.s)
ffi.copy(buf, love.s, #love.s)
return buf
end, getSize = function()
return #love.s
end}
end, write = function(_, s)
love.s = s
return true
end}}
local bitser = require 'bitser'
local function serdeser(value)
return bitser.loads(bitser.dumps(value))
end
local function test_serdeser(value)
assert.are.same(serdeser(value), value)
end
local function test_serdeser_idempotent(value)
assert.are.same(bitser.dumps(serdeser(value)), bitser.dumps(value))
end
describe("bitser", function()
it("serializes simple values", function()
test_serdeser(true)
test_serdeser(false)
test_serdeser(nil)
test_serdeser(1)
test_serdeser(-1)
test_serdeser(0)
test_serdeser(100000000)
test_serdeser(1.234)
test_serdeser(10 ^ 20)
test_serdeser(1/0)
test_serdeser(-1/0)
test_serdeser("")
test_serdeser("hullo")
test_serdeser([[this
is a longer string
such a long string
that it won't fit
in the "short string" representation
no it won't
listen to me
it won't]])
local nan = serdeser(0/0)
assert.is_not.equal(nan, nan)
end)
it("serializes simple tables", function()
test_serdeser({})
test_serdeser({10, 11, 12})
test_serdeser({foo = 10, bar = 99, [true] = false})
test_serdeser({[1000] = 9000})
test_serdeser({{}})
end)
it("serializes tables with tables as keys", function()
local thekey = {"Heyo"}
assert.are.same(thekey, (next(serdeser({[thekey] = 12}))))
end)
it("serializes cyclic tables", function()
local cthulhu = {{}, {}, {}}
cthulhu.fhtagn = cthulhu
--note: this does not test tables as keys because assert.are.same doesn't like that
cthulhu[1].cthulhu = cthulhu[3]
cthulhu[2].cthulhu = cthulhu[2]
cthulhu[3].cthulhu = cthulhu
test_serdeser(cthulhu)
end)
it("serializes resources", function()
local temp_resource = {}
bitser.register("temp_resource", temp_resource)
assert.are.equal(serdeser({this = temp_resource}).this, temp_resource)
bitser.unregister("temp_resource")
end)
it("serializes many resources", function()
local max = 1000
local t = {}
for i = 1, max do
bitser.register(tostring(i), i)
t[i] = i
end
test_serdeser(t)
for i = 1, max do
bitser.unregister(tostring(i))
end
end)
it("serializes deeply nested tables", function()
local max = 1000
local t = {}
for _ = 1, max do
t.t = {}
t = t.t
end
test_serdeser(t)
end)
it("serializes MiddleClass instances", function()
local class = require("middleclass")
local Horse = bitser.registerClass(class('Horse'))
function Horse:initialize(name)
self.name = name
self[1] = 'instance can be sequence'
end
local bojack = Horse('Bojack Horseman')
test_serdeser(bojack)
assert.is_true(serdeser(bojack):isInstanceOf(Horse))
bitser.unregisterClass('Horse')
end)
it("serializes SECL instances", function()
local class_mt = {}
function class_mt:__index(key)
return self.__baseclass[key]
end
local class = setmetatable({ __baseclass = {} }, class_mt)
function class:new(...)
local c = {}
c.__baseclass = self
setmetatable(c, getmetatable(self))
if c.init then
c:init(...)
end
return c
end
local Horse = bitser.registerClass('Horse', class:new())
function Horse:init(name)
self.name = name
self[1] = 'instance can be sequence'
end
local bojack = Horse:new('Bojack Horseman')
test_serdeser(bojack)
assert.are.equal(serdeser(bojack).__baseclass, Horse)
bitser.unregisterClass('Horse')
end)
it("serializes hump.class instances", function()
local class = require("class")
local Horse = bitser.registerClass('Horse', class{})
function Horse:init(name)
self.name = name
self[1] = 'instance can be sequence'
end
local bojack = Horse('Bojack Horseman')
test_serdeser(bojack)
assert.are.equal(getmetatable(serdeser(bojack)), Horse)
bitser.unregisterClass('Horse')
end)
it("serializes Slither instances", function()
local class = require("slither")
local Horse = class 'Horse' {
__attributes__ = {bitser.registerClass},
__init__ = function(self, name)
self.name = name
self[1] = 'instance can be sequence'
end
}
local bojack = Horse('Bojack Horseman')
test_serdeser(bojack)
assert.is_true(class.isinstance(serdeser(bojack), Horse))
bitser.unregisterClass('Horse')
end)
it("serializes Moonscript class instances", function()
local Horse
do
local _class_0
local _base_0 = {}
_base_0.__index = _base_0
_class_0 = setmetatable({
__init = function(self, name)
self.name = name
self[1] = 'instance can be sequence'
end,
__base = _base_0,
__name = "Horse"}, {
__index = _base_0,
__call = function(cls, ...)
local _self_0 = setmetatable({}, _base_0)
cls.__init(_self_0, ...)
return _self_0
end
})
_base_0.__class = _class_0
Horse = _class_0
end
assert.are.same(Horse.__name, "Horse") -- to shut coveralls up
bitser.registerClass(Horse)
local bojack = Horse('Bojack Horseman')
test_serdeser(bojack)
local new_bojack = serdeser(bojack)
assert.are.equal(new_bojack.__class, Horse)
bitser.unregisterClass('Horse')
end)
it("serializes custom class instances", function()
local Horse_mt = bitser.registerClass('Horse', {__index = {}}, nil, setmetatable)
local function Horse(name)
local self = {}
self.name = name
self[1] = 'instance can be sequence'
return setmetatable(self, Horse_mt)
end
local bojack = Horse('Bojack Horseman')
test_serdeser(bojack)
assert.are.equal(getmetatable(serdeser(bojack)), Horse_mt)
bitser.unregisterClass('Horse')
end)
it("serializes classes that repeat keys", function()
local my_mt = {"hi"}
local works = { foo = 'a', bar = {baz = 'b'}, }
local broken = { foo = 'a', bar = {foo = 'b'}, }
local more_broken = {
foo = 'a',
baz = {foo = 'b', bar = 'c'},
quz = {bar = 'd', bam = 'e'}
}
setmetatable(works, my_mt)
setmetatable(broken, my_mt)
setmetatable(more_broken, my_mt)
bitser.registerClass("Horse", my_mt, nil, setmetatable)
test_serdeser(works)
test_serdeser(broken)
test_serdeser(more_broken)
bitser.unregisterClass('Horse')
end)
it("serializes big data", function()
local text = "this is a lot of nonsense, please disregard, we need a lot of data to get past 4 KiB (114 characters should do it)"
local t = {}
for i = 1, 40 do
t[i] = text .. i -- no references allowed!
end
test_serdeser(t)
end)
it("serializes many references", function()
local max = 1000
local t = {}
local t2 = {}
for i = 1, max do
t.t = {}
t = t.t
t2[i] = t
end
test_serdeser({t, t2})
end)
it("serializes resources with long names", function()
local temp_resource = {}
bitser.register("temp_resource_or_whatever", temp_resource)
assert.are.equal(serdeser({this = temp_resource}).this, temp_resource)
bitser.unregister("temp_resource_or_whatever")
end)
it("serializes resources with the same name as serialized strings", function()
local temp_resource = {}
bitser.register('temp', temp_resource)
test_serdeser({temp='temp', {temp_resource}})
bitser.unregister('temp')
end)
it("serializes resources with the same long name as serialized strings", function()
local temp_resource = {}
bitser.register('temp_resource_or_whatever', temp_resource)
test_serdeser({temp_resource_or_whatever='temp_resource_or_whatever', {temp_resource}})
bitser.unregister('temp_resource_or_whatever')
end)
it("cannot serialize functions", function()
assert.has_error(function() bitser.dumps(function() end) end, "cannot serialize type function")
end)
it("cannot serialize unsupported class libraries without explicit deserializer", function()
assert.has_error(function() bitser.registerClass('Horse', {mane = 'majestic'}) end, "no deserializer given for unsupported class library")
end)
it("cannot deserialize values from unassigned type bytes", function()
assert.has_error(function() bitser.loads("\254") end, "unsupported serialized type 254")
assert.has_error(function() bitser.loads("\255") end, "unsupported serialized type 255")
end)
it("can load from raw data", function()
assert.are.same(bitser.loadData(ffi.new("uint8_t[4]", 195, 103, 118, 120), 4), "gvx")
end)
it("will not read from zero length data", function()
assert.has_error(function() bitser.loadData(ffi.new("uint8_t[1]", 0), 0) end)
end)
it("will not read from zero length string", function()
assert.has_error(function() bitser.loads("") end)
end)
it("will not read past the end of the buffer", function()
assert.has_error(function() bitser.loadData(ffi.new("uint8_t[4]", 196, 103, 118, 120), 4) end)
end)
it("can clear the buffer", function()
bitser.clearBuffer()
end)
it("can write to new buffer after reading from read-only buffer", function()
test_serdeser("bitser")
bitser.loadData(ffi.new("uint8_t[4]", 195, 103, 118, 120), 4)
test_serdeser("bitser")
end)
it("can dump and load LÖVE files", function()
local v = {value = "value"}
bitser.dumpLoveFile("some_file_name", v)
assert.are.same(v, bitser.loadLoveFile("some_file_name"))
end)
it("can read and write simple cdata", function()
test_serdeser(ffi.new('double', 42.5))
end)
it("can read and write cdata with a registered ctype", function()
pcall(ffi.cdef,[[
struct some_struct {
int a;
double b;
};
]])
local value = ffi.new('struct some_struct', 42, 1.25)
bitser.register('struct_type', ffi.typeof(value))
test_serdeser_idempotent(value)
bitser.unregister('struct_type')
end)
it("can read and write cdata without registering its ctype", function()
pcall(ffi.cdef,[[
struct some_struct {
int a;
double b;
};
]])
local value = ffi.new('struct some_struct', 42, 1.25)
test_serdeser_idempotent(value)
end)
it("cannot read from anonymous structs", function()
local v = bitser.dumps(ffi.new('struct { int a; }'))
assert.has_error(function() bitser.loads(v) end)
end)
it("can read and write simple multiple cdata of the same ctype without getting confused", function()
test_serdeser({ffi.new('double', 42.5), ffi.new('double', 12), ffi.new('double', 0.01)})
end)
it("can read and write metatables", function()
local t = setmetatable({foo="foo"}, {__index = {bar="bar"}})
test_serdeser(t)
assert.are.same(getmetatable(t), getmetatable(serdeser(t)))
assert.are.same(serdeser(t).bar, "bar")
end)
end)

32
lib/sock/.travis.yml Normal file
View File

@ -0,0 +1,32 @@
language: python
sudo: required
env:
- LUA="luajit=2.0"
- LUA="luajit=2.1"
before_install:
- pip install hererocks
- hererocks lua_install -r^ --$LUA
- export PATH=$PATH:$PWD/lua_install/bin
- git clone https://github.com/lsalzman/enet.git
- sudo apt-get install -y dh-autoreconf
- cd enet
- autoreconf -vfi
- ./configure && sudo make && sudo make install
- cd ..
install:
- luarocks install busted
- luarocks install enet
- luarocks install luacov
- luarocks install luacov-coveralls
- luarocks install luacheck
after_success:
- luacov-coveralls --exclude "bitser.lua" -e $TRAVIS_BUILD_DIR/lua_install
script:
# - luacheck --std=max+busted *.lua spec --new-globals=enet+bitser --no-max-line-length --ignore="61." --include-files sock.lua sock_spec.lua
- busted --verbose --coverage --no-auto-insulate -p "sock_spec.lua" spec

42
lib/sock/CHANGELOG Normal file
View File

@ -0,0 +1,42 @@
CHANGELOG
=========
0.3.0
-----
* Renamed 'data format' to 'schema'
* `Server:setDataFormat` is now `Server:setSchema`
* Added `Client:setSchema`
* Added enet's range coding compression
* Added `Server:enableCompression`
* Added `Client:enableCompression`
* Added optional code for `Client:connect`
* Added `Server:getClientCount`
* Added `Server:destroy`
* Added `Client:reset`
* Added `Client:isConnected`
* Added `Client:isConnecting`
* Added `Client:isDisconnected`
* Added `Client:isDisconnecting`
0.2.1
-----
* Fixed bitser not being required using relative location
0.2.0
-----
* Added custom serialization support
* Added Client:setSerialization
* Added Server:setSerialization
* Changed 'emit' functions to 'send'
* 'Client:emit' is now 'Client:send'
* 'Server:emitToAll' is now 'Server:emitToAll'
* 'Server:emitToAllBut' is now 'Server:emitToAllBut'
* Added new 'Server:sendToPeer' function
0.1.0
-----
* Initial release

21
lib/sock/LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2016 Cameron McHenry
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.

90
lib/sock/README.md Normal file
View File

@ -0,0 +1,90 @@
# sock.lua
[![Build Status](https://travis-ci.org/camchenry/sock.lua.svg?branch=master)](https://travis-ci.org/camchenry/sock.lua)
[![Coverage Status](https://coveralls.io/repos/github/camchenry/sock.lua/badge.svg?branch=master)](https://coveralls.io/github/camchenry/sock.lua?branch=master)
sock.lua is a networking library for LÖVE games. Its goal is to make getting started with networking as easy as possible.
[Documentation](https://camchenry.github.io/sock.lua/)
**sock requires [enet](https://github.com/leafo/lua-enet) (which comes with LÖVE 0.9 and up.)**
## Features
- Event trigger system makes it easy to add behavior to network events.
- Can send images and files over the network.
- Can use a custom serialization library.
- Logs events, errors, and warnings that occur.
# Installation
1. Clone or download sock.lua.
2. Clone or download [bitser](https://github.com/gvx/bitser).\*
3. Place bitser.lua in the same directory as sock.lua.
4. Require the library and start using it. `sock = require 'sock'`
\* If custom serialization support is needed, look at [setSerialization](https://camchenry.github.io/sock.lua//index.html#Server:setSerialization).
# Example
```lua
local sock = require "sock"
-- client.lua
function love.load()
-- Creating a new client on localhost:22122
client = sock.newClient("localhost", 22122)
-- Creating a client to connect to some ip address
client = sock.newClient("198.51.100.0", 22122)
-- Called when a connection is made to the server
client:on("connect", function(data)
print("Client connected to the server.")
end)
-- Called when the client disconnects from the server
client:on("disconnect", function(data)
print("Client disconnected from the server.")
end)
-- Custom callback, called whenever you send the event from the server
client:on("hello", function(msg)
print("The server replied: " .. msg)
end)
client:connect()
-- You can send different types of data
client:send("greeting", "Hello, my name is Inigo Montoya.")
client:send("isShooting", true)
client:send("bulletsLeft", 1)
client:send("position", {
x = 465.3,
y = 50,
})
end
function love.update(dt)
client:update()
end
```
```lua
-- server.lua
function love.load()
-- Creating a server on any IP, port 22122
server = sock.newServer("*", 22122)
-- Called when someone connects to the server
server:on("connect", function(data, client)
-- Send a message back to the connected client
local msg = "Hello from the server!"
client:send("hello", msg)
end)
end
function love.update(dt)
server:update()
end
```

16
lib/sock/config.ld Normal file
View File

@ -0,0 +1,16 @@
project = 'sock.lua'
title = 'sock.lua Documentation'
description = 'sock.lua is a networking library for Lua/LÖVE games.'
file = 'sock.lua'
dir = 'docs'
style = 'docstyle'
template = 'docstyle'
format = 'markdown'
one = true
all = true
no_lua_ref = true
not_luadoc = false
wrap = true
sort = true
no_space_before_args = true

3850
lib/sock/docs/index.html Normal file

File diff suppressed because it is too large Load Diff

223
lib/sock/docs/ldoc.css Normal file
View File

@ -0,0 +1,223 @@
/*
* Tag styles
*/
body {
box-sizing: border-box;
margin: 0;
color: #222;
font-size: 1.8em;
}
ul, ol {
list-style-type: disc;
}
pre {
background-color: #f7f7f7;
border: 1px solid #ccc;
border-radius: 3px;
box-shadow: 0px 2px 1px #eee;
padding: 10px;
margin: 10px 0 10px 0;
overflow: auto;
}
p {
max-width: 70ch;
}
a {
color: #07f;
text-decoration: none;
}
/* make the target distinct; helps when we're navigating to a function */
a:target + * {
background-color: #fff824;
}
/*
* Class styles
*/
.header {
background: #56CCF2; /* fallback for old browsers */
background: -webkit-linear-gradient(to right, #2F80ED, #56CCF2); /* Chrome 10-25, Safari 5.1-6 */
background: linear-gradient(to right, #2F80ED, #56CCF2); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
color: #fff;
text-align: center;
padding: 4rem;
}
.project-title {
margin: 0;
}
#main {
padding: 1.5rem;
background: #fff;
}
#navigation {
float: left;
width: 12.5rem;
vertical-align: top;
overflow: visible;
}
/* Sidebar */
.sidebar {
flex: 1 0 40rem;
z-index: 99;
transition: all 0.2s ease;
}
#sidebar_toggle {
display: none;
}
.sidebar_toggle_label {
display: flex;
position: fixed;
justify-content: center;
align-items: center;
z-index: 999;
width: 3rem;
height: 3rem;
margin: 0.5rem;
background-color: #eee;
border: 1px solid #bbb;
border-radius: 3px;
opacity: 1;
cursor: pointer;
}
.sidebar_toggle:checked ~ .sidebar {
z-index: -1;
transform: translateX(-100%);
opacity: 0;
}
.sidebar_toggle:checked ~ .contents {
transform: translateX(0);
margin-right: 0;
}
.sidebar_toggle_label:before {
content: '◀';
}
.sidebar_toggle:checked + .sidebar_toggle_label:before {
content: '▶';
}
.table-of-contents {
position: fixed;
overflow-y: scroll;
top: 0;
bottom: 0;
width: 40rem;
padding: 1.5rem;
padding-top: 4rem;
background: #f7f7f7;
}
.contents {
transition: transform 0.2s ease;
transform: translateX(40rem);
margin-right: 40rem;
}
.section {
margin-top: 3rem;
}
.section-header {
font-weight: bold;
border-bottom: 1px solid #ccc;
margin-top: 8rem;
}
.section-description {
}
.section-content {
padding-left: 1rem;
}
.function_def {
margin-top: 2.5rem;
}
.function_def:first-child {
margin-top: 0;
}
.function_def li {
padding-bottom: 4px;
}
.function_name {
display: inline-block;
font-weight: 400;
}
.anchor_link {
font-size: 85%;
}
.anchor_link:focus {
}
.type {
font-weight: bolder;
font-style: italic;
}
.parameter_info .type {
font-weight: bold;
}
.parameter_info {
background: #f7f7f7;
padding: 5px;
font-family: monospace;
}
/*
* Syntax highlighting
*/
pre .comment { color: #3a3432; font-style: italic; }
pre .constant { color: #01a252; }
pre .string { color: #01a252; }
pre .number { color: #01a252; }
pre .escape { color: #844631; }
pre .library { color: #0e7c6b; }
pre .marker { color: #512b1e; background: #fedc56; }
pre .operator { color: #4a4543; }
pre .keyword { color: #a16a94; }
pre .user-keyword { color: #01a0e4; }
pre .preprocessor,
pre .prepro { color: #db2d20; }
pre .global { color: #db2d20; }
pre .prompt { color: #558817; }
pre .url { color: #272fc2; text-decoration: underline; }
/* print rules */
@media print {
body {
font: 12pt "Times New Roman", "TimeNR", Times, serif;
}
a { font-weight: bold; color: #004080; text-decoration: underline; }
#main {
background-color: #ffffff;
border-left: 0px;
}
#container {
margin-left: 2%;
margin-right: 2%;
background-color: #ffffff;
}
#content {
padding: 1em;
background-color: #ffffff;
}
#navigation {
display: none;
}
pre.example {
font-family: "Droid Sans Mono", "Consolas", "Andale Mono", monospace;
font-size: 10pt;
page-break-inside: avoid;
}
}

File diff suppressed because it is too large Load Diff

223
lib/sock/docstyle/ldoc.css Normal file
View File

@ -0,0 +1,223 @@
/*
* Tag styles
*/
body {
box-sizing: border-box;
margin: 0;
color: #222;
font-size: 1.8em;
}
ul, ol {
list-style-type: disc;
}
pre {
background-color: #f7f7f7;
border: 1px solid #ccc;
border-radius: 3px;
box-shadow: 0px 2px 1px #eee;
padding: 10px;
margin: 10px 0 10px 0;
overflow: auto;
}
p {
max-width: 70ch;
}
a {
color: #07f;
text-decoration: none;
}
/* make the target distinct; helps when we're navigating to a function */
a:target + * {
background-color: #fff824;
}
/*
* Class styles
*/
.header {
background: #56CCF2; /* fallback for old browsers */
background: -webkit-linear-gradient(to right, #2F80ED, #56CCF2); /* Chrome 10-25, Safari 5.1-6 */
background: linear-gradient(to right, #2F80ED, #56CCF2); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
color: #fff;
text-align: center;
padding: 4rem;
}
.project-title {
margin: 0;
}
#main {
padding: 1.5rem;
background: #fff;
}
#navigation {
float: left;
width: 12.5rem;
vertical-align: top;
overflow: visible;
}
/* Sidebar */
.sidebar {
flex: 1 0 40rem;
z-index: 99;
transition: all 0.2s ease;
}
#sidebar_toggle {
display: none;
}
.sidebar_toggle_label {
display: flex;
position: fixed;
justify-content: center;
align-items: center;
z-index: 999;
width: 3rem;
height: 3rem;
margin: 0.5rem;
background-color: #eee;
border: 1px solid #bbb;
border-radius: 3px;
opacity: 1;
cursor: pointer;
}
.sidebar_toggle:checked ~ .sidebar {
z-index: -1;
transform: translateX(-100%);
opacity: 0;
}
.sidebar_toggle:checked ~ .contents {
transform: translateX(0);
margin-right: 0;
}
.sidebar_toggle_label:before {
content: '◀';
}
.sidebar_toggle:checked + .sidebar_toggle_label:before {
content: '▶';
}
.table-of-contents {
position: fixed;
overflow-y: scroll;
top: 0;
bottom: 0;
width: 40rem;
padding: 1.5rem;
padding-top: 4rem;
background: #f7f7f7;
}
.contents {
transition: transform 0.2s ease;
transform: translateX(40rem);
margin-right: 40rem;
}
.section {
margin-top: 3rem;
}
.section-header {
font-weight: bold;
border-bottom: 1px solid #ccc;
margin-top: 8rem;
}
.section-description {
}
.section-content {
padding-left: 1rem;
}
.function_def {
margin-top: 2.5rem;
}
.function_def:first-child {
margin-top: 0;
}
.function_def li {
padding-bottom: 4px;
}
.function_name {
display: inline-block;
font-weight: 400;
}
.anchor_link {
font-size: 85%;
}
.anchor_link:focus {
}
.type {
font-weight: bolder;
font-style: italic;
}
.parameter_info .type {
font-weight: bold;
}
.parameter_info {
background: #f7f7f7;
padding: 5px;
font-family: monospace;
}
/*
* Syntax highlighting
*/
pre .comment { color: #3a3432; font-style: italic; }
pre .constant { color: #01a252; }
pre .string { color: #01a252; }
pre .number { color: #01a252; }
pre .escape { color: #844631; }
pre .library { color: #0e7c6b; }
pre .marker { color: #512b1e; background: #fedc56; }
pre .operator { color: #4a4543; }
pre .keyword { color: #a16a94; }
pre .user-keyword { color: #01a0e4; }
pre .preprocessor,
pre .prepro { color: #db2d20; }
pre .global { color: #db2d20; }
pre .prompt { color: #558817; }
pre .url { color: #272fc2; text-decoration: underline; }
/* print rules */
@media print {
body {
font: 12pt "Times New Roman", "TimeNR", Times, serif;
}
a { font-weight: bold; color: #004080; text-decoration: underline; }
#main {
background-color: #ffffff;
border-left: 0px;
}
#container {
margin-left: 2%;
margin-right: 2%;
background-color: #ffffff;
}
#content {
padding: 1em;
background-color: #ffffff;
}
#navigation {
display: none;
}
pre.example {
font-family: "Droid Sans Mono", "Consolas", "Andale Mono", monospace;
font-size: 10pt;
page-break-inside: avoid;
}
}

277
lib/sock/docstyle/ldoc.ltp Normal file
View File

@ -0,0 +1,277 @@
<!DOCTYPE html>
<html>
<head lang="en">
<title>$(ldoc.title)</title>
<meta charset="utf-8">
<link href="https://cdnjs.cloudflare.com/ajax/libs/normalize/7.0.0/normalize.min.css" rel="styleshet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/milligram/1.3.0/milligram.min.css" rel="stylesheet">
<link rel="stylesheet" href="$(ldoc.css)">
</head>
<body>
# local no_spaces = ldoc.no_spaces
# local use_li = ldoc.use_li
# local display_name = ldoc.display_name
# local iter = ldoc.modules.iter
# local function M(txt,item) return ldoc.markup(txt,item,ldoc.plain) end
# local nowrap = ldoc.wrap and '' or 'nowrap'
# local orig_ldoc_href = ldoc.href
# local function H(see)
# local ref = orig_ldoc_href(see)
# ref = ref:gsub('index.html', '')
# return ref
# end
# ldoc.href = H
<input type="checkbox" id="sidebar_toggle" class="sidebar_toggle" role="button">
<label class="sidebar_toggle_label" for="sidebar_toggle"></label>
<div class="sidebar">
<div class="table-of-contents">
# if not ldoc.no_summary then
# -- bang out the tables of item types for this module (e.g Functions, Tables, etc)
# for kind,items in module.kinds() do
<h2><a href="#$(no_spaces(kind))">$(kind)</a></h2>
<div class="function_list">
# for item in items() do
<div class="name" $(nowrap)><a href="#$(item.name)">$(display_name(item))</a></div>
# end -- for items
</div>
<br>
#end -- for kinds
#end -- if not no_summary
</div>
</div>
<div class="contents">
<!-- Header -->
<header class="header">
<h1 class="project-title">$(ldoc.project)</h1>
</header>
<div id="main">
<!-- Menu -->
<div id="content">
# if ldoc.body then -- verbatim HTML as contents; 'non-code' entries
$(ldoc.body)
# elseif module then -- module documentation
<p>$(M(module.summary,module))</p>
<p>$(M(module.description,module))</p>
# if module.tags.include then
$(M(ldoc.include_file(module.tags.include)))
# end
# if module.usage then
# local li,il = use_li(module.usage)
<h3>Usage:</h3>
<ul>
# for usage in iter(module.usage) do
$(li)<pre class="example">$(ldoc.escape(usage))</pre>$(il)
# end -- for
</ul>
# end -- if usage
# if module.info then
<h3>Info:</h3>
<ul>
# for tag, value in module.info:iter() do
<li><strong>$(tag)</strong>: $(M(value,module))</li>
# end
</ul>
# end -- if module.info
# --- currently works for both Functions and Tables. The params field either contains
# --- function parameters or table fields.
# local show_return = not ldoc.no_return_or_parms
# local show_parms = show_return
# for kind, items in module.kinds() do
# local section_link_name = kind:gsub("Class ", ""):gsub(" ", "")
<section class="section" id="$(section_link_name)">
# local kitem = module.kinds:get_item(kind)
# local has_description = kitem and ldoc.descript(kitem) ~= ""
<h1 class="section-header $(has_description and 'has-description')"><a name="$(no_spaces(kind))"></a>$(kind)</h1>
<div class="section-content">
$(M(module.kinds:get_section_description(kind),nil))
# if kitem then
# if has_description then
<p class="section-description">
$(M(ldoc.descript(kitem),kitem))
</p>
# end
# if kitem.usage then
<h3>Usage:</h3>
<pre class="example">$(ldoc.prettify(kitem.usage[1]))</pre>
# end
# end
<dl class="function">
# for item in items() do
<div class="function_def">
<a class="anchor_link" href="#$(item.name)" name="$(item.name)">🔗</a>
<h3 class="function_name">$(display_name(item))</h3>
# if ldoc.prettify_files then
<a style="float:right;" href="$(ldoc.source_ref(item))">line $(item.lineno)</a>
# end
</dt>
<dd>
<p class="summary">
$(M(item.summary,item))
</p>
<p class="description">
$(M(item.description,item))
</p>
# if ldoc.custom_tags then
# for custom in iter(ldoc.custom_tags) do
# local tag = item.tags[custom[1]]
# if tag and not custom.hidden then
# local li,il = use_li(tag)
<h3>$(custom.title or custom[1]):</h3>
<ul>
# for value in iter(tag) do
$(li)$(custom.format and custom.format(value) or M(value))$(il)
# end -- for
# end -- if tag
</ul>
# end -- iter tags
# end
# if show_parms and item.params and #item.params > 0 then
# local subnames = module.kinds:type_of(item).subnames
# if subnames then
<h3>$(subnames):</h3>
# end
<dl>
# for parm in iter(item.params) do
# local param,sublist = item:subparam(parm)
# if sublist then
<dt><span class="parameter">$(sublist)</dt>$(M(item.params.map[sublist],item))
<dl><dd>
# end
# for p in iter(param) do
# local name,tp,def = item:display_name_of(p), ldoc.typename(item:type_of_param(p)), item:default_of_param(p)
<dt>
<span class="parameter_info">
# if tp ~= '' then
<span class="types">$(tp)</span>
# end
<span class="parameter">$(name)</span>
</span>
</dt>
<dd>
$(M(item.params.map[p],item))
</dt>
# if def == true then
(<em>optional</em>)
# elseif def then
(<em>default</em> $(def))
# end
# if item:readonly(p) then
<em>readonly</em>
# end
</li>
# end
# if sublist then
</dd></dl>
# end
# end -- for
</dl>
# end -- if params
# if show_return and item.retgroups then local groups = item.retgroups
<h3>Returns:</h3>
# for i,group in ldoc.ipairs(groups) do local li,il = use_li(group)
<ol>
# for r in group:iter() do local type, ctypes = item:return_type(r); local rt = ldoc.typename(type)
<li>
# if rt ~= '' then
<span class="types">$(rt)</span>
# end
$(M(r.text,item))$(il)
# if ctypes then
<ul>
# for c in ctypes:iter() do
<li><span class="parameter">$(c.name)</span>
<span class="types">$(ldoc.typename(c.type))</span>
$(M(c.comment,item))</li>
# end
</ul>
# end -- if ctypes
</li>
# end -- for r
</ol>
# if i < #groups then
<h3>Or</h3>
# end
# end -- for group
# end -- if returns
# if show_return and item.raise then
<h3>Raises:</h3>
$(M(item.raise,item))
# end
# if item.see then
# local li,il = use_li(item.see)
<h3>See also:</h3>
<ul class="see-items">
# for see in iter(item.see) do
<li>
<a href="#$(see.name)">$(see.label)</a>$(il)
</li>
# end -- for
</ul>
# end -- if see
# if item.usage then
# local li,il = use_li(item.usage)
<h3>Usage:</h3>
<ul>
# for usage in iter(item.usage) do
$(li)<pre class="example">$(ldoc.prettify(usage))</pre>$(il)
# end -- for
</ul>
# end -- if usage
</dd>
</div>
# end -- for items
</dl>
</div>
</section>
# end -- for kinds
# else -- if module; project-level contents
# if ldoc.description then
<h2>$(M(ldoc.description,nil))</h2>
# end
# if ldoc.full_description then
<p>$(M(ldoc.full_description,nil))</p>
# end
# for kind, mods in ldoc.kinds() do
<h2>$(kind)</h2>
# kind = kind:lower()
<table class="module_list">
# for m in mods() do
<tr>
<td class="name" $(nowrap)><a href="$(no_spaces(kind))/$(m.name).html">$(m.name)</a></td>
<td class="summary">$(M(ldoc.strip_header(m.summary),m))</td>
</tr>
# end -- for modules
</table>
# end -- for kinds
# end -- if module
</div> <!-- id="content" -->
</div> <!-- id="main" -->
<div id="about">
<i>Generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.3</a></i>
<i style="float:right;">Last updated $(ldoc.updatetime) </i>
</div> <!-- id="about" -->
</div> <!-- id="container" -->
</body>
</html>

View File

@ -0,0 +1,424 @@
--[[
Copyright (c) 2016, Robin Wellner
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
]]
local floor = math.floor
local pairs = pairs
local type = type
local insert = table.insert
local getmetatable = getmetatable
local setmetatable = setmetatable
local ffi = require("ffi")
local buf_pos = 0
local buf_size = -1
local buf = nil
local writable_buf = nil
local writable_buf_size = nil
local function Buffer_prereserve(min_size)
if buf_size < min_size then
buf_size = min_size
buf = ffi.new("uint8_t[?]", buf_size)
end
end
local function Buffer_clear()
buf_size = -1
buf = nil
writable_buf = nil
writable_buf_size = nil
end
local function Buffer_makeBuffer(size)
if writable_buf then
buf = writable_buf
buf_size = writable_buf_size
writable_buf = nil
writable_buf_size = nil
end
buf_pos = 0
Buffer_prereserve(size)
end
local function Buffer_newReader(str)
Buffer_makeBuffer(#str)
ffi.copy(buf, str, #str)
end
local function Buffer_newDataReader(data, size)
writable_buf = buf
writable_buf_size = buf_size
buf_pos = 0
buf_size = size
buf = ffi.cast("uint8_t*", data)
end
local function Buffer_reserve(additional_size)
while buf_pos + additional_size > buf_size do
buf_size = buf_size * 2
local oldbuf = buf
buf = ffi.new("uint8_t[?]", buf_size)
ffi.copy(buf, oldbuf, buf_pos)
end
end
local function Buffer_write_byte(x)
Buffer_reserve(1)
buf[buf_pos] = x
buf_pos = buf_pos + 1
end
local function Buffer_write_string(s)
Buffer_reserve(#s)
ffi.copy(buf + buf_pos, s, #s)
buf_pos = buf_pos + #s
end
local function Buffer_write_data(ct, len, ...)
Buffer_reserve(len)
ffi.copy(buf + buf_pos, ffi.new(ct, ...), len)
buf_pos = buf_pos + len
end
local function Buffer_ensure(numbytes)
if buf_pos + numbytes > buf_size then
error("malformed serialized data")
end
end
local function Buffer_read_byte()
Buffer_ensure(1)
local x = buf[buf_pos]
buf_pos = buf_pos + 1
return x
end
local function Buffer_read_string(len)
Buffer_ensure(len)
local x = ffi.string(buf + buf_pos, len)
buf_pos = buf_pos + len
return x
end
local function Buffer_read_data(ct, len)
Buffer_ensure(len)
local x = ffi.new(ct)
ffi.copy(x, buf + buf_pos, len)
buf_pos = buf_pos + len
return x
end
local resource_registry = {}
local resource_name_registry = {}
local class_registry = {}
local class_name_registry = {}
local classkey_registry = {}
local class_deserialize_registry = {}
local serialize_value
local function write_number(value, _)
if floor(value) == value and value >= -2147483648 and value <= 2147483647 then
if value >= -27 and value <= 100 then
--small int
Buffer_write_byte(value + 27)
elseif value >= -32768 and value <= 32767 then
--short int
Buffer_write_byte(250)
Buffer_write_data("int16_t[1]", 2, value)
else
--long int
Buffer_write_byte(245)
Buffer_write_data("int32_t[1]", 4, value)
end
else
--double
Buffer_write_byte(246)
Buffer_write_data("double[1]", 8, value)
end
end
local function write_string(value, seen)
if #value < 32 then
--short string
Buffer_write_byte(192 + #value)
else
--long string
Buffer_write_byte(244)
write_number(#value, seen)
end
Buffer_write_string(value)
end
local function write_nil(_, _)
Buffer_write_byte(247)
end
local function write_boolean(value, _)
Buffer_write_byte(value and 249 or 248)
end
local function write_table(value, seen)
local classkey
local class = (class_name_registry[value.class] -- MiddleClass
or class_name_registry[value.__baseclass] -- SECL
or class_name_registry[getmetatable(value)] -- hump.class
or class_name_registry[value.__class__]) -- Slither
if class then
classkey = classkey_registry[class]
Buffer_write_byte(242)
write_string(class)
else
Buffer_write_byte(240)
end
local len = #value
write_number(len, seen)
for i = 1, len do
serialize_value(value[i], seen)
end
local klen = 0
for k in pairs(value) do
if (type(k) ~= 'number' or floor(k) ~= k or k > len or k < 1) and k ~= classkey then
klen = klen + 1
end
end
write_number(klen, seen)
for k, v in pairs(value) do
if (type(k) ~= 'number' or floor(k) ~= k or k > len or k < 1) and k ~= classkey then
serialize_value(k, seen)
serialize_value(v, seen)
end
end
end
local types = {number = write_number, string = write_string, table = write_table, boolean = write_boolean, ["nil"] = write_nil}
serialize_value = function(value, seen)
if seen[value] then
local ref = seen[value]
if ref < 64 then
--small reference
Buffer_write_byte(128 + ref)
else
--long reference
Buffer_write_byte(243)
write_number(ref, seen)
end
return
end
local t = type(value)
if t ~= 'number' and t ~= 'boolean' and t ~= 'nil' then
seen[value] = seen.len
seen.len = seen.len + 1
end
if resource_name_registry[value] then
local name = resource_name_registry[value]
if #name < 16 then
--small resource
Buffer_write_byte(224 + #name)
Buffer_write_string(name)
else
--long resource
Buffer_write_byte(241)
write_string(name, seen)
end
return
end
(types[t] or
error("cannot serialize type " .. t)
)(value, seen)
end
local function serialize(value)
Buffer_makeBuffer(4096)
local seen = {len = 0}
serialize_value(value, seen)
end
local function add_to_seen(value, seen)
insert(seen, value)
return value
end
local function reserve_seen(seen)
insert(seen, 42)
return #seen
end
local function deserialize_value(seen)
local t = Buffer_read_byte()
if t < 128 then
--small int
return t - 27
elseif t < 192 then
--small reference
return seen[t - 127]
elseif t < 224 then
--small string
return add_to_seen(Buffer_read_string(t - 192), seen)
elseif t < 240 then
--small resource
return add_to_seen(resource_registry[Buffer_read_string(t - 224)], seen)
elseif t == 240 then
--table
local v = add_to_seen({}, seen)
local len = deserialize_value(seen)
for i = 1, len do
v[i] = deserialize_value(seen)
end
len = deserialize_value(seen)
for _ = 1, len do
local key = deserialize_value(seen)
v[key] = deserialize_value(seen)
end
return v
elseif t == 241 then
--long resource
local idx = reserve_seen(seen)
local value = resource_registry[deserialize_value(seen)]
seen[idx] = value
return value
elseif t == 242 then
--instance
local instance = add_to_seen({}, seen)
local classname = deserialize_value(seen)
local class = class_registry[classname]
local classkey = classkey_registry[classname]
local deserializer = class_deserialize_registry[classname]
local len = deserialize_value(seen)
for i = 1, len do
instance[i] = deserialize_value(seen)
end
len = deserialize_value(seen)
for _ = 1, len do
local key = deserialize_value(seen)
instance[key] = deserialize_value(seen)
end
if classkey then
instance[classkey] = class
end
return deserializer(instance, class)
elseif t == 243 then
--reference
return seen[deserialize_value(seen) + 1]
elseif t == 244 then
--long string
return add_to_seen(Buffer_read_string(deserialize_value(seen)), seen)
elseif t == 245 then
--long int
return Buffer_read_data("int32_t[1]", 4)[0]
elseif t == 246 then
--double
return Buffer_read_data("double[1]", 8)[0]
elseif t == 247 then
--nil
return nil
elseif t == 248 then
--false
return false
elseif t == 249 then
--true
return true
elseif t == 250 then
--short int
return Buffer_read_data("int16_t[1]", 2)[0]
else
error("unsupported serialized type " .. t)
end
end
local function deserialize_MiddleClass(instance, class)
return setmetatable(instance, class.__instanceDict)
end
local function deserialize_SECL(instance, class)
return setmetatable(instance, getmetatable(class))
end
local deserialize_humpclass = setmetatable
local function deserialize_Slither(instance, class)
return getmetatable(class).allocate(instance)
end
return {dumps = function(value)
serialize(value)
return ffi.string(buf, buf_pos)
end, dumpLoveFile = function(fname, value)
serialize(value)
love.filesystem.write(fname, ffi.string(buf, buf_pos))
end, loadLoveFile = function(fname)
local serializedData = love.filesystem.newFileData(fname)
Buffer_newDataReader(serializedData:getPointer(), serializedData:getSize())
return deserialize_value({})
end, loadData = function(data, size)
Buffer_newDataReader(data, size)
return deserialize_value({})
end, loads = function(str)
Buffer_newReader(str)
return deserialize_value({})
end, register = function(name, resource)
assert(not resource_registry[name], name .. " already registered")
resource_registry[name] = resource
resource_name_registry[resource] = name
return resource
end, unregister = function(name)
resource_name_registry[resource_registry[name]] = nil
resource_registry[name] = nil
end, registerClass = function(name, class, classkey, deserializer)
if not class then
class = name
name = class.__name__ or class.name
end
if not classkey then
if class.__instanceDict then
-- assume MiddleClass
classkey = 'class'
elseif class.__baseclass then
-- assume SECL
classkey = '__baseclass'
end
-- assume hump.class, Slither, or something else that doesn't store the
-- class directly on the instance
end
if not deserializer then
if class.__instanceDict then
-- assume MiddleClass
deserializer = deserialize_MiddleClass
elseif class.__baseclass then
-- assume SECL
deserializer = deserialize_SECL
elseif class.__index == class then
-- assume hump.class
deserializer = deserialize_humpclass
elseif class.__name__ then
-- assume Slither
deserializer = deserialize_Slither
else
error("no deserializer given for unsupported class library")
end
end
class_registry[name] = class
classkey_registry[name] = classkey
class_deserialize_registry[name] = deserializer
class_name_registry[class] = name
return class
end, unregisterClass = function(name)
class_name_registry[class_registry[name]] = nil
classkey_registry[name] = nil
class_deserialize_registry[name] = nil
class_registry[name] = nil
end, reserveBuffer = Buffer_prereserve, clearBuffer = Buffer_clear}

View File

@ -0,0 +1,47 @@
-- Loading sock from root directory relative to this one
-- This is not required in your own projects
package.path = package.path .. ";../?.lua"
local sock = require "sock"
local binser = require "spec.binser"
function love.load()
client = sock.newClient("localhost", 22122)
server = sock.newServer("localhost", 22122)
-- If the connect/disconnect callbacks aren't defined some warnings will
-- be thrown, but nothing bad will happen.
-- Called when someone connects to the server
server:on("connect", function(data, peer)
local msg = "Hello from server!"
peer:send("hello", msg)
end)
-- Called when a connection is made to the server
client:on("connect", function(data)
print("Client connected to the server.")
end)
-- Custom callback, called whenever you send the event from the server
client:on("hello", function(msg)
print(msg)
end)
client:connect()
end
function love.update(dt)
server:update()
client:update()
if love.math.random() > 0.95 then
server:sendToAll("hello", "This is an update message")
end
end
function love.keypressed(key)
if key == "q" then
client:reset()
end
end

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -0,0 +1,48 @@
-- Loading sock from root directory relative to this one
-- This is not required in your own projects
package.path = package.path .. ";../../?.lua"
local sock = require "sock"
local bitser = require "spec.bitser"
function love.load()
client = sock.newClient("localhost", 22122)
server = sock.newServer("*", 22122)
client:setSerialization(bitser.dumps, bitser.loads)
server:setSerialization(bitser.dumps, bitser.loads)
client:enableCompression()
server:enableCompression()
client:connect()
client:on("image", function(data)
local file = love.filesystem.newFileData(data, "")
receivedImage = love.image.newImageData(file)
receivedImage = love.graphics.newImage(receivedImage)
end)
server:on("connect", function(data, client)
local image = love.filesystem.newFileData("hello.png")
server:sendToAll("image", image)
end)
lastModified = 0
end
function love.update(dt)
server:update()
client:update()
if lastModified < love.filesystem.getLastModified("hello.png") then
-- Sleep for some milliseconds for the image to write to disk
love.timer.sleep(0.2)
lastModified = love.filesystem.getLastModified("hello.png")
local image = love.filesystem.newFileData("hello.png")
server:sendToAll("image", image)
end
end
function love.draw()
if receivedImage then
love.graphics.draw(receivedImage, 100, 100)
end
end

View File

@ -0,0 +1,116 @@
package.path = package.path .. ";../../?.lua"
sock = require "sock"
bitser = require "spec.bitser"
function love.load()
-- how often an update is sent out
tickRate = 1/60
tick = 0
client = sock.newClient("localhost", 22122)
client:setSerialization(bitser.dumps, bitser.loads)
client:setSchema("playerState", {
"index",
"player",
})
-- store the client's index
-- playerNumber is nil otherwise
client:on("playerNum", function(num)
playerNumber = num
end)
-- receive info on where the players are located
client:on("playerState", function(data)
local index = data.index
local player = data.player
-- only accept updates for the other player
if playerNumber and index ~= playerNumber then
players[index] = player
end
end)
client:on("ballState", function(data)
ball = data
end)
client:on("scores", function(data)
scores = data
end)
client:connect()
function newPlayer(x, y)
return {
x = x,
y = y,
w = 20,
h = 100,
}
end
function newBall(x, y)
return {
x = x,
y = y,
vx = 150,
vy = 150,
w = 15,
h = 15,
}
end
local marginX = 50
players = {
newPlayer(marginX, love.graphics.getHeight()/2),
newPlayer(love.graphics.getWidth() - marginX, love.graphics.getHeight()/2)
}
scores = {0, 0}
ball = newBall(love.graphics.getWidth()/2, love.graphics.getHeight()/2)
end
function love.update(dt)
client:update()
if client:getState() == "connected" then
tick = tick + dt
-- simulate the ball locally, and receive corrections from the server
ball.x = ball.x + ball.vx * dt
ball.y = ball.y + ball.vy * dt
end
if tick >= tickRate then
tick = 0
if playerNumber then
local mouseY = love.mouse.getY()
local playerY = mouseY - players[playerNumber].h/2
-- Update our own player position and send it to the server
players[playerNumber].y = playerY
client:send("mouseY", playerY)
end
end
end
function love.draw()
for _, player in pairs(players) do
love.graphics.rectangle('fill', player.x, player.y, player.w, player.h)
end
love.graphics.rectangle('fill', ball.x, ball.y, ball.w, ball.h)
love.graphics.print(client:getState(), 5, 5)
if playerNumber then
love.graphics.print("Player " .. playerNumber, 5, 25)
else
love.graphics.print("No player number assigned", 5, 25)
end
local score = ("%d - %d"):format(scores[1], scores[2])
love.graphics.print(score, 5, 45)
end

View File

@ -0,0 +1,136 @@
package.path = package.path .. ";../../?.lua"
sock = require "sock"
bitser = require "spec.bitser"
-- Utility functions
function isColliding(this, other)
return this.x < other.x + other.w and
this.y < other.y + other.h and
this.x + this.w > other.x and
this.y + this.h > other.y
end
function love.load()
-- how often an update is sent out
tickRate = 1/60
tick = 0
server = sock.newServer("*", 22122, 2)
server:setSerialization(bitser.dumps, bitser.loads)
-- Players are being indexed by peer index here, definitely not a good idea
-- for a larger game, but it's good enough for this application.
server:on("connect", function(data, client)
-- tell the peer what their index is
client:send("playerNum", client:getIndex())
end)
-- receive info on where a player is located
server:on("mouseY", function(y, client)
local index = client:getIndex()
players[index].y = y
end)
function newPlayer(x, y)
return {
x = x,
y = y,
w = 20,
h = 100,
}
end
function newBall(x, y)
return {
x = x,
y = y,
vx = 150,
vy = 150,
w = 15,
h = 15,
}
end
local marginX = 50
players = {
newPlayer(marginX, love.graphics.getHeight()/2),
newPlayer(love.graphics.getWidth() - marginX, love.graphics.getHeight()/2)
}
scores = {0, 0}
ball = newBall(love.graphics.getWidth()/2, love.graphics.getHeight()/2)
end
function love.update(dt)
server:update()
-- wait until 2 players connect to start playing
local enoughPlayers = #server.clients >= 2
if not enoughPlayers then return end
for i, player in pairs(players) do
-- This is a naive solution, if the ball is inside the paddle it might bug out
-- But hey, it's low stakes pong
if isColliding(ball, player) then
ball.vx = ball.vx * -1
ball.vy = ball.vy * -1
end
end
-- Left/Right bounds
if ball.x < 0 or ball.x > love.graphics.getWidth() then
if ball.x < 0 then
scores[2] = scores[2] + 1
else
scores[1] = scores[1] + 1
end
server:sendToAll("scores", scores)
ball.x = love.graphics.getWidth()/2
ball.y = love.graphics.getHeight()/2
ball.vx = ball.vx * -1
ball.vy = ball.vy * -1
end
-- Top/Bottom bounds
if ball.y < 0 or ball.y > love.graphics.getHeight() - ball.h then
ball.vy = ball.vy * -1
if ball.y < 0 then
ball.y = 0
end
if ball.y > love.graphics.getHeight() - ball.h then
ball.y = love.graphics.getHeight() - ball.h
end
end
ball.x = ball.x + ball.vx * dt
ball.y = ball.y + ball.vy * dt
tick = tick + dt
if tick >= tickRate then
tick = 0
for i, player in pairs(players) do
server:sendToAll("playerState", {i, player})
end
server:sendToAll("ballState", ball)
end
end
function love.draw()
for i, player in pairs(players) do
love.graphics.rectangle('fill', player.x, player.y, player.w, player.h)
end
love.graphics.rectangle('fill', ball.x, ball.y, ball.w, ball.h)
local score = ("%d - %d"):format(scores[1], scores[2])
love.graphics.print(score, 5, 5)
end

1418
lib/sock/init.lua Executable file

File diff suppressed because it is too large Load Diff

626
lib/sock/spec/binser.lua Normal file
View File

@ -0,0 +1,626 @@
-- binser.lua
--[[
Copyright (c) 2016 Calvin Rose
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.
]]
local assert = assert
local error = error
local select = select
local pairs = pairs
local getmetatable = getmetatable
local setmetatable = setmetatable
local tonumber = tonumber
local type = type
local loadstring = loadstring
local concat = table.concat
local char = string.char
local byte = string.byte
local format = string.format
local sub = string.sub
local dump = string.dump
local floor = math.floor
local frexp = math.frexp
local ldexp = math.ldexp
local unpack = unpack or table.unpack
-- NIL = 202
-- FLOAT = 203
-- TRUE = 204
-- FALSE = 205
-- STRING = 206
-- TABLE = 207
-- REFERENCE = 208
-- CONSTRUCTOR = 209
-- FUNCTION = 210
-- RESOURCE = 211
local mts = {}
local ids = {}
local serializers = {}
local deserializers = {}
local resources = {}
local resources_by_name = {}
local function pack(...)
return {...}, select("#", ...)
end
local function not_array_index(x, len)
return type(x) ~= "number" or x < 1 or x > len or x ~= floor(x)
end
local function type_check(x, tp, name)
assert(type(x) == tp,
format("Expected parameter %q to be of type %q.", name, tp))
end
-- Copyright (C) 2012-2015 Francois Perrad.
-- number serialization code modified from https://github.com/fperrad/lua-MessagePack
-- Encode a number as a big-endian ieee-754 double (or a small integer)
local function number_to_str(n)
if floor(n) == n then -- int
if n <= 100 and n >= -27 then -- 1 byte, 7 bits of data
return char(n + 27)
elseif n <= 8191 and n >= -8192 then -- 2 bytes, 14 bits of data
n = n + 8192
return char(128 + (floor(n / 0x100) % 0x100), n % 0x100)
end
end
local sign = 0
if n < 0.0 then
sign = 0x80
n = -n
end
local m, e = frexp(n) -- mantissa, exponent
if m ~= m then
return char(203, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
elseif m == 1/0 then
if sign == 0 then
return char(203, 0x7F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
else
return char(203, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
end
end
e = e + 0x3FE
if e < 1 then -- denormalized numbers
m = m * ldexp(0.5, 53 + e)
e = 0
else
m = (m * 2 - 1) * ldexp(0.5, 53)
end
return char(203,
sign + floor(e / 0x10),
(e % 0x10) * 0x10 + floor(m / 0x1000000000000),
floor(m / 0x10000000000) % 0x100,
floor(m / 0x100000000) % 0x100,
floor(m / 0x1000000) % 0x100,
floor(m / 0x10000) % 0x100,
floor(m / 0x100) % 0x100,
m % 0x100)
end
-- Copyright (C) 2012-2015 Francois Perrad.
-- number deserialization code also modified from https://github.com/fperrad/lua-MessagePack
local function number_from_str(str, index)
local b = byte(str, index)
if b < 128 then
return b - 27, index + 1
elseif b < 192 then
return byte(str, index + 1) + 0x100 * (b - 128) - 8192, index + 2
end
local b1, b2, b3, b4, b5, b6, b7, b8 = byte(str, index + 1, index + 8)
local sign = b1 > 0x7F and -1 or 1
local e = (b1 % 0x80) * 0x10 + floor(b2 / 0x10)
local m = ((((((b2 % 0x10) * 0x100 + b3) * 0x100 + b4) * 0x100 + b5) * 0x100 + b6) * 0x100 + b7) * 0x100 + b8
local n
if e == 0 then
if m == 0 then
n = sign * 0.0
else
n = sign * ldexp(m / ldexp(0.5, 53), -1022)
end
elseif e == 0x7FF then
if m == 0 then
n = sign * (1/0)
else
n = 0.0/0.0
end
else
n = sign * ldexp(1.0 + m / ldexp(0.5, 53), e - 0x3FF)
end
return n, index + 9
end
local types = {}
types["nil"] = function(x, visited, accum)
accum[#accum + 1] = "\202"
end
function types.number(x, visited, accum)
accum[#accum + 1] = number_to_str(x)
end
function types.boolean(x, visited, accum)
accum[#accum + 1] = x and "\204" or "\205"
end
function types.string(x, visited, accum)
local alen = #accum
if visited[x] then
accum[alen + 1] = "\208"
accum[alen + 2] = number_to_str(visited[x])
else
visited[x] = visited.next
visited.next = visited.next + 1
accum[alen + 1] = "\206"
accum[alen + 2] = number_to_str(#x)
accum[alen + 3] = x
end
end
local function check_custom_type(x, visited, accum)
local res = resources[x]
if res then
accum[#accum + 1] = "\211"
types[type(res)](res, visited, accum)
return true
end
local mt = getmetatable(x)
local id = mt and ids[mt]
if id then
if x == visited.temp then
error("Infinite loop in constructor.")
end
visited.temp = x
accum[#accum + 1] = "\209"
types[type(id)](id, visited, accum)
local args, len = pack(serializers[id](x))
accum[#accum + 1] = number_to_str(len)
for i = 1, len do
local arg = args[i]
types[type(arg)](arg, visited, accum)
end
visited[x] = visited.next
visited.next = visited.next + 1
return true
end
end
function types.userdata(x, visited, accum)
if visited[x] then
accum[#accum + 1] = "\208"
accum[#accum + 1] = number_to_str(visited[x])
else
if check_custom_type(x, visited, accum) then return end
error("Cannot serialize this userdata.")
end
end
function types.table(x, visited, accum)
if visited[x] then
accum[#accum + 1] = "\208"
accum[#accum + 1] = number_to_str(visited[x])
else
if check_custom_type(x, visited, accum) then return end
visited[x] = visited.next
visited.next = visited.next + 1
local xlen = #x
accum[#accum + 1] = "\207"
accum[#accum + 1] = number_to_str(xlen)
for i = 1, xlen do
local v = x[i]
types[type(v)](v, visited, accum)
end
local key_count = 0
for k in pairs(x) do
if not_array_index(k, xlen) then
key_count = key_count + 1
end
end
accum[#accum + 1] = number_to_str(key_count)
for k, v in pairs(x) do
if not_array_index(k, xlen) then
types[type(k)](k, visited, accum)
types[type(v)](v, visited, accum)
end
end
end
end
types["function"] = function(x, visited, accum)
if visited[x] then
accum[#accum + 1] = "\208"
accum[#accum + 1] = number_to_str(visited[x])
else
if check_custom_type(x, visited, accum) then return end
visited[x] = visited.next
visited.next = visited.next + 1
local str = dump(x)
accum[#accum + 1] = "\210"
accum[#accum + 1] = number_to_str(#str)
accum[#accum + 1] = str
end
end
types.cdata = function(x, visited, accum)
if visited[x] then
accum[#accum + 1] = "\208"
accum[#accum + 1] = number_to_str(visited[x])
else
if check_custom_type(x, visited, #accum) then return end
error("Cannot serialize this cdata.")
end
end
types.thread = function() error("Cannot serialize threads.") end
local function deserialize_value(str, index, visited)
local t = byte(str, index)
if not t then return end
if t < 128 then
return t - 27, index + 1
elseif t < 192 then
return byte(str, index + 1) + 0x100 * (t - 128) - 8192, index + 2
elseif t == 202 then
return nil, index + 1
elseif t == 203 then
return number_from_str(str, index)
elseif t == 204 then
return true, index + 1
elseif t == 205 then
return false, index + 1
elseif t == 206 then
local length, dataindex = deserialize_value(str, index + 1, visited)
local nextindex = dataindex + length
local substr = sub(str, dataindex, nextindex - 1)
visited[#visited + 1] = substr
return substr, nextindex
elseif t == 207 then
local count, nextindex = number_from_str(str, index + 1)
local ret = {}
visited[#visited + 1] = ret
for i = 1, count do
ret[i], nextindex = deserialize_value(str, nextindex, visited)
end
count, nextindex = number_from_str(str, nextindex)
for i = 1, count do
local k, v
k, nextindex = deserialize_value(str, nextindex, visited)
v, nextindex = deserialize_value(str, nextindex, visited)
ret[k] = v
end
return ret, nextindex
elseif t == 208 then
local ref, nextindex = number_from_str(str, index + 1)
return visited[ref], nextindex
elseif t == 209 then
local count
local name, nextindex = deserialize_value(str, index + 1, visited)
count, nextindex = number_from_str(str, nextindex)
local args = {}
for i = 1, count do
args[i], nextindex = deserialize_value(str, nextindex, visited)
end
local ret = deserializers[name](unpack(args))
visited[#visited + 1] = ret
return ret, nextindex
elseif t == 210 then
local length, dataindex = deserialize_value(str, index + 1, visited)
local nextindex = dataindex + length
local ret = loadstring(sub(str, dataindex, nextindex - 1))
visited[#visited + 1] = ret
return ret, nextindex
elseif t == 211 then
local res, nextindex = deserialize_value(str, index + 1, visited)
return resources_by_name[res], nextindex
else
error("Could not deserialize type byte " .. t .. ".")
end
end
local function serialize(...)
local visited = {next = 1}
local accum = {}
for i = 1, select("#", ...) do
local x = select(i, ...)
types[type(x)](x, visited, accum)
end
return concat(accum)
end
local function make_file_writer(file)
return setmetatable({}, {
__newindex = function(_, _, v)
file:write(v)
end
})
end
local function serialize_to_file(path, mode, ...)
local file, err = io.open(path, mode)
assert(file, err)
local visited = {next = 1}
local accum = make_file_writer(file)
for i = 1, select("#", ...) do
local x = select(i, ...)
types[type(x)](x, visited, accum)
end
-- flush the writer
file:flush()
file:close()
end
local function writeFile(path, ...)
return serialize_to_file(path, "wb", ...)
end
local function appendFile(path, ...)
return serialize_to_file(path, "ab", ...)
end
local function deserialize(str)
assert(type(str) == "string", "Expected string to deserialize.")
local vals = {}
local index = 1
local visited = {}
local len = 0
local val
while index do
val, index = deserialize_value(str, index, visited)
if index then
len = len + 1
vals[len] = val
end
end
return vals, len
end
local function deserializeN(str, n)
assert(type(str) == "string", "Expected string to deserialize.")
n = n or 1
assert(type(n) == "number", "Expected a number for parameter n.")
assert(n > 0 and floor(n) == n, "N must be a poitive integer.")
local vals = {}
local index = 1
local visited = {}
local len = 0
local val
while index and len < n do
val, index = deserialize_value(str, index, visited)
if index then
len = len + 1
vals[len] = val
end
end
return unpack(vals, 1, n)
end
local function readFile(path)
local file, err = io.open(path, "rb")
assert(file, err)
local str = file:read("*all")
file:close()
return deserialize(str)
end
local function default_deserialize(metatable)
return function(...)
local ret = {}
for i = 1, select("#", ...), 2 do
ret[select(i, ...)] = select(i + 1, ...)
end
return setmetatable(ret, metatable)
end
end
local function default_serialize(x)
assert(type(x) == "table",
"Default serialization for custom types only works for tables.")
local args = {}
local len = 0
for k, v in pairs(x) do
args[len + 1], args[len + 2] = k, v
len = len + 2
end
return unpack(args, 1, len)
end
-- Templating
local function normalize_template(template)
local ret = {}
for i = 1, #template do
ret[i] = template[i]
end
local non_array_part = {}
-- The non-array part of the template (nested templates) have to be deterministic, so they are sorted.
-- This means that inherently non deterministicly sortable keys (tables, functions) should NOT be used
-- in templates. Looking for way around this.
for k in pairs(template) do
if not_array_index(k, #template) then
non_array_part[#non_array_part + 1] = k
end
end
table.sort(non_array_part)
for i = 1, #non_array_part do
local name = non_array_part[i]
ret[#ret + 1] = {name, normalize_template(template[name])}
end
return ret
end
local function templatepart_serialize(part, argaccum, x, len)
local extras = {}
local extracount = 0
for k, v in pairs(x) do
extras[k] = v
extracount = extracount + 1
end
for i = 1, #part do
extracount = extracount - 1
if type(part[i]) == "table" then
extras[part[i][1]] = nil
len = templatepart_serialize(part[i][2], argaccum, x[part[i][1]], len)
else
extras[part[i]] = nil
len = len + 1
argaccum[len] = x[part[i]]
end
end
if extracount > 0 then
argaccum[len + 1] = extras
else
argaccum[len + 1] = nil
end
return len + 1
end
local function templatepart_deserialize(ret, part, values, vindex)
for i = 1, #part do
local name = part[i]
if type(name) == "table" then
local newret = {}
ret[name[1]] = newret
vindex = templatepart_deserialize(newret, name[2], values, vindex)
else
ret[name] = values[vindex]
vindex = vindex + 1
end
end
local extras = values[vindex]
if extras then
for k, v in pairs(extras) do
ret[k] = v
end
end
return vindex + 1
end
local function template_serializer_and_deserializer(metatable, template)
return function(x)
argaccum = {}
local len = templatepart_serialize(template, argaccum, x, 0)
return unpack(argaccum, 1, len)
end, function(...)
local ret = {}
local len = select("#", ...)
local args = {...}
templatepart_deserialize(ret, template, args, 1)
return setmetatable(ret, metatable)
end
end
local function register(metatable, name, serialize, deserialize)
name = name or metatable.name
serialize = serialize or metatable._serialize
deserialize = deserialize or metatable._deserialize
if not serialize then
if metatable._template then
local t = normalize_template(metatable._template)
serialize, deserialize = template_serializer_and_deserializer(metatable, t)
elseif not deserialize then
serialize = default_serialize
deserialize = default_deserialize(metatable)
else
serialize = metatable
end
end
type_check(metatable, "table", "metatable")
type_check(name, "string", "name")
type_check(serialize, "function", "serialize")
type_check(deserialize, "function", "deserialize")
assert(not ids[metatable], "Metatable already registered.")
assert(not mts[name], ("Name %q already registered."):format(name))
mts[name] = metatable
ids[metatable] = name
serializers[name] = serialize
deserializers[name] = deserialize
return metatable
end
local function unregister(item)
local name, metatable
if type(item) == "string" then -- assume name
name, metatable = item, mts[item]
else -- assume metatable
name, metatable = ids[item], item
end
type_check(name, "string", "name")
type_check(metatable, "table", "metatable")
mts[name] = nil
ids[metatable] = nil
serializers[name] = nil
deserializers[name] = nil
return metatable
end
local function registerClass(class, name)
name = name or class.name
if class.__instanceDict then -- middleclass
register(class.__instanceDict, name)
else -- assume 30log or similar library
register(class, name)
end
return class
end
local function registerResource(resource, name)
type_check(name, "string", "name")
assert(not resources[resource],
"Resource already registered.")
assert(not resources_by_name[name],
format("Resource %q already exists.", name))
resources_by_name[name] = resource
resources[resource] = name
return resource
end
local function unregisterResource(name)
type_check(name, "string", "name")
assert(resources_by_name[name], format("Resource %q does not exist.", name))
local resource = resources_by_name[name]
resources_by_name[name] = nil
resources[resource] = nil
return resource
end
return {
-- aliases
s = serialize,
d = deserialize,
dn = deserializeN,
r = readFile,
w = writeFile,
a = appendFile,
serialize = serialize,
deserialize = deserialize,
deserializeN = deserializeN,
readFile = readFile,
writeFile = writeFile,
appendFile = appendFile,
register = register,
unregister = unregister,
registerResource = registerResource,
unregisterResource = unregisterResource,
registerClass = registerClass
}

424
lib/sock/spec/bitser.lua Normal file
View File

@ -0,0 +1,424 @@
--[[
Copyright (c) 2016, Robin Wellner
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
]]
local floor = math.floor
local pairs = pairs
local type = type
local insert = table.insert
local getmetatable = getmetatable
local setmetatable = setmetatable
local ffi = require("ffi")
local buf_pos = 0
local buf_size = -1
local buf = nil
local writable_buf = nil
local writable_buf_size = nil
local function Buffer_prereserve(min_size)
if buf_size < min_size then
buf_size = min_size
buf = ffi.new("uint8_t[?]", buf_size)
end
end
local function Buffer_clear()
buf_size = -1
buf = nil
writable_buf = nil
writable_buf_size = nil
end
local function Buffer_makeBuffer(size)
if writable_buf then
buf = writable_buf
buf_size = writable_buf_size
writable_buf = nil
writable_buf_size = nil
end
buf_pos = 0
Buffer_prereserve(size)
end
local function Buffer_newReader(str)
Buffer_makeBuffer(#str)
ffi.copy(buf, str, #str)
end
local function Buffer_newDataReader(data, size)
writable_buf = buf
writable_buf_size = buf_size
buf_pos = 0
buf_size = size
buf = ffi.cast("uint8_t*", data)
end
local function Buffer_reserve(additional_size)
while buf_pos + additional_size > buf_size do
buf_size = buf_size * 2
local oldbuf = buf
buf = ffi.new("uint8_t[?]", buf_size)
ffi.copy(buf, oldbuf, buf_pos)
end
end
local function Buffer_write_byte(x)
Buffer_reserve(1)
buf[buf_pos] = x
buf_pos = buf_pos + 1
end
local function Buffer_write_string(s)
Buffer_reserve(#s)
ffi.copy(buf + buf_pos, s, #s)
buf_pos = buf_pos + #s
end
local function Buffer_write_data(ct, len, ...)
Buffer_reserve(len)
ffi.copy(buf + buf_pos, ffi.new(ct, ...), len)
buf_pos = buf_pos + len
end
local function Buffer_ensure(numbytes)
if buf_pos + numbytes > buf_size then
error("malformed serialized data")
end
end
local function Buffer_read_byte()
Buffer_ensure(1)
local x = buf[buf_pos]
buf_pos = buf_pos + 1
return x
end
local function Buffer_read_string(len)
Buffer_ensure(len)
local x = ffi.string(buf + buf_pos, len)
buf_pos = buf_pos + len
return x
end
local function Buffer_read_data(ct, len)
Buffer_ensure(len)
local x = ffi.new(ct)
ffi.copy(x, buf + buf_pos, len)
buf_pos = buf_pos + len
return x
end
local resource_registry = {}
local resource_name_registry = {}
local class_registry = {}
local class_name_registry = {}
local classkey_registry = {}
local class_deserialize_registry = {}
local serialize_value
local function write_number(value, _)
if floor(value) == value and value >= -2147483648 and value <= 2147483647 then
if value >= -27 and value <= 100 then
--small int
Buffer_write_byte(value + 27)
elseif value >= -32768 and value <= 32767 then
--short int
Buffer_write_byte(250)
Buffer_write_data("int16_t[1]", 2, value)
else
--long int
Buffer_write_byte(245)
Buffer_write_data("int32_t[1]", 4, value)
end
else
--double
Buffer_write_byte(246)
Buffer_write_data("double[1]", 8, value)
end
end
local function write_string(value, _)
if #value < 32 then
--short string
Buffer_write_byte(192 + #value)
else
--long string
Buffer_write_byte(244)
write_number(#value)
end
Buffer_write_string(value)
end
local function write_nil(_, _)
Buffer_write_byte(247)
end
local function write_boolean(value, _)
Buffer_write_byte(value and 249 or 248)
end
local function write_table(value, seen)
local classkey
local classname = (class_name_registry[value.class] -- MiddleClass
or class_name_registry[value.__baseclass] -- SECL
or class_name_registry[getmetatable(value)] -- hump.class
or class_name_registry[value.__class__]) -- Slither
if classname then
classkey = classkey_registry[classname]
Buffer_write_byte(242)
serialize_value(classname, seen)
else
Buffer_write_byte(240)
end
local len = #value
write_number(len, seen)
for i = 1, len do
serialize_value(value[i], seen)
end
local klen = 0
for k in pairs(value) do
if (type(k) ~= 'number' or floor(k) ~= k or k > len or k < 1) and k ~= classkey then
klen = klen + 1
end
end
write_number(klen, seen)
for k, v in pairs(value) do
if (type(k) ~= 'number' or floor(k) ~= k or k > len or k < 1) and k ~= classkey then
serialize_value(k, seen)
serialize_value(v, seen)
end
end
end
local types = {number = write_number, string = write_string, table = write_table, boolean = write_boolean, ["nil"] = write_nil}
serialize_value = function(value, seen)
if seen[value] then
local ref = seen[value]
if ref < 64 then
--small reference
Buffer_write_byte(128 + ref)
else
--long reference
Buffer_write_byte(243)
write_number(ref, seen)
end
return
end
local t = type(value)
if t ~= 'number' and t ~= 'boolean' and t ~= 'nil' then
seen[value] = seen.len
seen.len = seen.len + 1
end
if resource_name_registry[value] then
local name = resource_name_registry[value]
if #name < 16 then
--small resource
Buffer_write_byte(224 + #name)
Buffer_write_string(name)
else
--long resource
Buffer_write_byte(241)
write_string(name, seen)
end
return
end
(types[t] or
error("cannot serialize type " .. t)
)(value, seen)
end
local function serialize(value)
Buffer_makeBuffer(4096)
local seen = {len = 0}
serialize_value(value, seen)
end
local function add_to_seen(value, seen)
insert(seen, value)
return value
end
local function reserve_seen(seen)
insert(seen, 42)
return #seen
end
local function deserialize_value(seen)
local t = Buffer_read_byte()
if t < 128 then
--small int
return t - 27
elseif t < 192 then
--small reference
return seen[t - 127]
elseif t < 224 then
--small string
return add_to_seen(Buffer_read_string(t - 192), seen)
elseif t < 240 then
--small resource
return add_to_seen(resource_registry[Buffer_read_string(t - 224)], seen)
elseif t == 240 then
--table
local v = add_to_seen({}, seen)
local len = deserialize_value(seen)
for i = 1, len do
v[i] = deserialize_value(seen)
end
len = deserialize_value(seen)
for _ = 1, len do
local key = deserialize_value(seen)
v[key] = deserialize_value(seen)
end
return v
elseif t == 241 then
--long resource
local idx = reserve_seen(seen)
local value = resource_registry[deserialize_value(seen)]
seen[idx] = value
return value
elseif t == 242 then
--instance
local instance = add_to_seen({}, seen)
local classname = deserialize_value(seen)
local class = class_registry[classname]
local classkey = classkey_registry[classname]
local deserializer = class_deserialize_registry[classname]
local len = deserialize_value(seen)
for i = 1, len do
instance[i] = deserialize_value(seen)
end
len = deserialize_value(seen)
for _ = 1, len do
local key = deserialize_value(seen)
instance[key] = deserialize_value(seen)
end
if classkey then
instance[classkey] = class
end
return deserializer(instance, class)
elseif t == 243 then
--reference
return seen[deserialize_value(seen) + 1]
elseif t == 244 then
--long string
return add_to_seen(Buffer_read_string(deserialize_value(seen)), seen)
elseif t == 245 then
--long int
return Buffer_read_data("int32_t[1]", 4)[0]
elseif t == 246 then
--double
return Buffer_read_data("double[1]", 8)[0]
elseif t == 247 then
--nil
return nil
elseif t == 248 then
--false
return false
elseif t == 249 then
--true
return true
elseif t == 250 then
--short int
return Buffer_read_data("int16_t[1]", 2)[0]
else
error("unsupported serialized type " .. t)
end
end
local function deserialize_MiddleClass(instance, class)
return setmetatable(instance, class.__instanceDict)
end
local function deserialize_SECL(instance, class)
return setmetatable(instance, getmetatable(class))
end
local deserialize_humpclass = setmetatable
local function deserialize_Slither(instance, class)
return getmetatable(class).allocate(instance)
end
return {dumps = function(value)
serialize(value)
return ffi.string(buf, buf_pos)
end, dumpLoveFile = function(fname, value)
serialize(value)
love.filesystem.write(fname, ffi.string(buf, buf_pos))
end, loadLoveFile = function(fname)
local serializedData = love.filesystem.newFileData(fname)
Buffer_newDataReader(serializedData:getPointer(), serializedData:getSize())
return deserialize_value({})
end, loadData = function(data, size)
Buffer_newDataReader(data, size)
return deserialize_value({})
end, loads = function(str)
Buffer_newReader(str)
return deserialize_value({})
end, register = function(name, resource)
assert(not resource_registry[name], name .. " already registered")
resource_registry[name] = resource
resource_name_registry[resource] = name
return resource
end, unregister = function(name)
resource_name_registry[resource_registry[name]] = nil
resource_registry[name] = nil
end, registerClass = function(name, class, classkey, deserializer)
if not class then
class = name
name = class.__name__ or class.name
end
if not classkey then
if class.__instanceDict then
-- assume MiddleClass
classkey = 'class'
elseif class.__baseclass then
-- assume SECL
classkey = '__baseclass'
end
-- assume hump.class, Slither, or something else that doesn't store the
-- class directly on the instance
end
if not deserializer then
if class.__instanceDict then
-- assume MiddleClass
deserializer = deserialize_MiddleClass
elseif class.__baseclass then
-- assume SECL
deserializer = deserialize_SECL
elseif class.__index == class then
-- assume hump.class
deserializer = deserialize_humpclass
elseif class.__name__ then
-- assume Slither
deserializer = deserialize_Slither
else
error("no deserializer given for unsupported class library")
end
end
class_registry[name] = class
classkey_registry[name] = classkey
class_deserialize_registry[name] = deserializer
class_name_registry[class] = name
return class
end, unregisterClass = function(name)
class_name_registry[class_registry[name]] = nil
classkey_registry[name] = nil
class_deserialize_registry[name] = nil
class_registry[name] = nil
end, reserveBuffer = Buffer_prereserve, clearBuffer = Buffer_clear}

543
lib/sock/spec/sock_spec.lua Normal file
View File

@ -0,0 +1,543 @@
package.path = package.path .. ";../?.lua"
local bitser = require "spec.bitser"
-- this is a hack :)
package.loaded['bitser'] = bitser
local sock = require "sock"
describe('sock.lua core', function()
it("creates clients", function()
local client = sock.newClient()
assert.are_not.equal(client, nil)
assert.equal(client.address, "localhost")
assert.equal(client.port, 22122)
assert.equal(client.maxChannels, 1)
end)
it("creates clients on localhost", function()
local client = sock.newClient("localhost")
assert.truthy(client)
local client = sock.newClient("localhost", 22122)
assert.truthy(client)
local client = sock.newClient("127.0.0.1")
assert.truthy(client)
local client = sock.newClient("127.0.0.1", 22122)
assert.truthy(client)
end)
it("creates servers", function()
local server = sock.newServer()
assert.are_not.equal(server, nil)
assert.equal(server.address, "localhost")
assert.equal(server.port, 22122)
assert.equal(server.maxPeers, 64)
assert.equal(server.maxChannels, 1)
server:destroy()
end)
end)
describe("the client", function()
it("connects to a server", function()
local client = sock.newClient("localhost", 22122)
local server = sock.newServer("0.0.0.0", 22122)
local connected = false
client:on("connect", function(data)
connected = true
end)
client:connect()
client:update()
server:update()
client:update()
server:update()
assert.True(client:isConnected())
assert.True(connected)
server:destroy()
end)
it("adds callbacks", function()
local client = sock.newClient()
local helloCallback = function()
print("hello")
end
local callback = client:on("helloMessage", helloCallback)
assert.equal(helloCallback, callback)
local found = false
for i, callbacks in pairs(client.listener.triggers) do
for j, callback in pairs(callbacks) do
if callback == helloCallback then
found = true
end
end
end
assert.True(found)
end)
it("removes callbacks", function()
local client = sock.newClient()
local helloCallback = function()
print("hello")
end
local callback = client:on("helloMessage", helloCallback)
assert.True(client:removeCallback(callback))
end)
it("does not remove non-existent callbacks", function()
local client = sock.newClient()
local nonsense = function() end
assert.False(client:removeCallback(nonsense))
end)
it("triggers callbacks", function()
local client = sock.newClient()
local handled = false
local handleConnect = function()
handled = true
end
client:on("connect", handleConnect)
client:_activateTriggers("connect", "connection event test")
assert.True(handled)
end)
it("sets send channel", function()
local client = sock.newClient(nil, nil, 8)
client:setSendChannel(7)
assert.equal(client.sendChannel, 7)
end)
it('can send a message with a schema', function()
local client = sock.newClient("localhost", 22122)
local server = sock.newServer("0.0.0.0", 22122)
client:connect()
client:update()
server:update()
client:update()
local received = false
server:setSchema('test', {
'first',
'second',
'third',
})
client:setSchema('test', {
'first',
'second',
'third',
})
server:on('test', function(data, client)
assert.are.same(data, {
first = 'this is the first message',
second = 'this is the second message',
third = 'this is the third message',
})
received = true
end)
client:send('test', {
'this is the first message',
'this is the second message',
'this is the third message',
})
client:update()
server:update()
assert.True(received)
server:destroy()
end)
insulate('can send', function()
before_each(function()
_G.client = sock.newClient("localhost", 22122)
_G.server = sock.newServer("0.0.0.0", 22122)
client:connect()
client:update()
server:update()
client:update()
end)
after_each(function()
server:destroy()
end)
it('a string', function()
local received = false
server:on('test', function(data, client)
assert.equal(data, 'this is the test string')
received = true
end)
client:send('test', 'this is the test string')
client:update()
server:update()
assert.True(received)
end)
it('an integer', function()
local received = false
server:on('test', function(data, client)
assert.equal(data, 12345678)
received = true
end)
client:send('test', 12345678)
client:update()
server:update()
assert.True(received)
end)
it('a floating point number', function()
local received = false
server:on('test', function(data, client)
assert.equal(data, 0.123456789)
received = true
end)
client:send('test', 0.123456789)
client:update()
server:update()
assert.True(received)
end)
it('a huge number', function()
local received = false
server:on('test', function(data, client)
assert.equal(data, math.huge)
received = true
end)
client:send('test', math.huge)
client:update()
server:update()
assert.True(received)
end)
it('a negative huge number', function()
local received = false
server:on('test', function(data, client)
assert.equal(data, -math.huge)
received = true
end)
client:send('test', -math.huge)
client:update()
server:update()
assert.True(received)
end)
it('a boolean', function()
local received = false
server:on('test', function(data, client)
assert.equal(data, false)
received = true
end)
client:send('test', false)
client:update()
server:update()
assert.True(received)
end)
it('nil', function()
local received = false
server:on('test', function(data, client)
assert.equal(data, nil)
received = true
end)
client:send('test', nil)
client:update()
server:update()
assert.True(received)
end)
it('a table', function()
local received = false
server:on('test', function(data, client)
assert.are.same(data, {
a = 0.12,
b = -987345,
c = "test",
d = true,
e = {},
})
received = true
end)
client:send('test', {
a = 0.12,
b = -987345,
c = "test",
d = true,
e = {},
})
client:update()
server:update()
assert.True(received)
end)
it('a table array', function()
local received = false
server:on('test', function(data, client)
assert.are.same(data, {
0.12,
-987345,
"test",
true,
{},
})
received = true
end)
client:send('test', {
0.12,
-987345,
"test",
true,
{},
})
client:update()
server:update()
assert.True(received)
end)
end)
end)
describe('the server', function()
insulate('can send', function()
before_each(function()
_G.client = sock.newClient("localhost", 22122)
_G.server = sock.newServer("0.0.0.0", 22122)
client:connect()
client:update()
server:update()
client:update()
server:update()
end)
after_each(function()
server:destroy()
end)
it('a string', function()
local received = false
client:on('test', function(data, client)
assert.equal(data, 'this is the test string')
received = true
end)
server:sendToAll('test', 'this is the test string')
server:update()
client:update()
server:update()
client:update()
assert.True(received)
end)
it('an integer', function()
local received = false
client:on('test', function(data, client)
assert.equal(data, 12345678)
received = true
end)
server:sendToAll('test', 12345678)
client:update()
server:update()
client:update()
server:update()
assert.True(received)
end)
it('a floating point number', function()
local received = false
client:on('test', function(data, client)
assert.equal(data, 0.123456789)
received = true
end)
server:sendToAll('test', 0.123456789)
server:update()
client:update()
server:update()
client:update()
assert.True(received)
end)
it('a huge number', function()
local received = false
client:on('test', function(data, client)
assert.equal(data, math.huge)
received = true
end)
server:sendToAll('test', math.huge)
server:update()
client:update()
server:update()
client:update()
assert.True(received)
end)
it('a negative huge number', function()
local received = false
client:on('test', function(data, client)
assert.equal(data, -math.huge)
received = true
end)
server:sendToAll('test', -math.huge)
server:update()
client:update()
server:update()
client:update()
assert.True(received)
end)
it('a boolean', function()
local received = false
client:on('test', function(data, client)
assert.equal(data, false)
received = true
end)
server:sendToAll('test', false)
server:update()
client:update()
server:update()
client:update()
assert.True(received)
end)
it('nil', function()
local received = false
client:on('test', function(data, client)
assert.equal(data, nil)
received = true
end)
server:sendToAll('test', nil)
server:update()
client:update()
server:update()
client:update()
assert.True(received)
end)
it('a table', function()
local received = false
client:on('test', function(data, client)
assert.are.same(data, {
a = 0.12,
b = -987345,
c = "test",
d = true,
e = {},
})
received = true
end)
server:sendToAll('test', {
a = 0.12,
b = -987345,
c = "test",
d = true,
e = {},
})
server:update()
client:update()
server:update()
client:update()
assert.True(received)
end)
it('a table array', function()
local received = false
client:on('test', function(data, client)
assert.are.same(data, {
0.12,
-987345,
"test",
true,
{},
})
received = true
end)
server:sendToAll('test', {
0.12,
-987345,
"test",
true,
{},
})
server:update()
client:update()
assert.True(received)
end)
end)
end)

236
main.lua
View File

@ -2,6 +2,8 @@
class = require "lib/middleclass" class = require "lib/middleclass"
wind = require "lib/windfield" wind = require "lib/windfield"
stalker = require "lib/STALKER-X" stalker = require "lib/STALKER-X"
bitser = require "lib/bitser"
sock = require "lib/sock"
SWORDLENGTH = 40; KNIFELENGTH = 10 SWORDLENGTH = 40; KNIFELENGTH = 10
SWORDWIDTH = 3 SWORDWIDTH = 3
@ -22,7 +24,7 @@ function love.load()
camera = stalker() camera = stalker()
mainmenu_load() menu_load(makeMainMenu())
end end
@ -55,13 +57,10 @@ function love.keyreleased (key)
end end
-- MAIN-MENU -- MENUS
---------------------------------------- ----------------------------------------
function mainmenu_load() function menu_load(menu)
local mainMenu = makeMainMenu() menu:install()
mainMenu:install()
camera = stalker()
end end
@ -69,15 +68,36 @@ function makeMainMenu()
return Menu:new(100, 100, 30, 50, 3, { return Menu:new(100, 100, 30, 50, 3, {
{love.graphics.newText(a_ttf, "Local"), {love.graphics.newText(a_ttf, "Local"),
function () lobby_load(LocalLobby) end}, function () lobby_load(LocalLobby) end},
{love.graphics.newText(a_ttf, "Net"),
function () menu_load(makeNetMenu()) end},
{love.graphics.newText(a_ttf, "Quit"), {love.graphics.newText(a_ttf, "Quit"),
function () love.event.quit(0) end }}) function () love.event.quit(0) end }})
end end
function makeNetMenu()
return Menu:new(100, 100, 30, 50, 3, {
{love.graphics.newText(a_ttf, "Join"),
function ()
local addressBox =
TextBox:new(100, 100, 3, 99, "127.0.0.1", "Address/Host: ",
function (text) lobby_load(ClientLobby, text) end)
addressBox:install()
end},
{love.graphics.newText(a_ttf, "Host"),
function () lobby_load(HostLobby) end},
{love.graphics.newText(a_ttf, "Back"),
function () menu_load(makeMainMenu()) end}})
end
-- NET
----------------------------------------
-- GAME LOBBY -- GAME LOBBY
---------------------------------------- ----------------------------------------
function lobby_load(lobbyClass) function lobby_load(lobbyClass, arg)
lobby = lobbyClass:new() lobby = lobbyClass:new(arg)
lobby:install() lobby:install()
end end
@ -107,9 +127,6 @@ function Fighter:initialize(game, x, y, character, swordType, swordSide)
self.directionals = {} self.directionals = {}
self.deadPieces = {} self.deadPieces = {}
self.sprite = love.graphics.newImage("art/sprites/"
.. CHARACTERS[self.character]["file"])
self:initBody(x, y) self:initBody(x, y)
self:initSword() self:initSword()
self:initShield() self:initShield()
@ -130,7 +147,8 @@ end
function Fighter:draw() function Fighter:draw()
local x,y = self.body:getWorldPoints(self.body.shape:getPoints()) local x,y = self.body:getWorldPoints(self.body.shape:getPoints())
love.graphics.draw(self.sprite, x, y, self.body:getAngle(), 1, 1) love.graphics.draw(CHARACTERS[self.character], x, y, self.body:getAngle(),
1, 1)
end end
@ -406,7 +424,7 @@ function Lobby:draw()
local rowX = 10 local rowX = 10
local rowY = (25 * i) + 50 local rowY = (25 * i) + 50
love.graphics.draw(lobbiest.sprite, rowX, rowY, 0, 1, 1) love.graphics.draw(CHARACTERS[lobbiest.character], rowX, rowY, 0, 1, 1)
if (lobbiest.swordType == "normal") then if (lobbiest.swordType == "normal") then
love.graphics.rectangle("fill", rowX + 18, rowY + SWORDWIDTH, love.graphics.rectangle("fill", rowX + 18, rowY + SWORDWIDTH,
SWORDLENGTH, SWORDWIDTH) SWORDLENGTH, SWORDWIDTH)
@ -459,7 +477,7 @@ function Lobby:keypressed(key)
self:newLocalLobbiest() self:newLocalLobbiest()
else else
for i,lobbiest in pairs(self.localLobbiests) do for i,lobbiest in pairs(self.localLobbiests) do
lobbiest:keypressed(key) lobbiest:keypressed(key, self)
end end
end end
end end
@ -492,6 +510,16 @@ function Lobby:lobbiestsN()
end end
function Lobby:localLobbiestTables()
local lobbiests = {}
table.foreach(self.localLobbiests,
function (k, lobbiest)
table.insert(lobbiests, lobbiest:toTable())
end)
return lobbiests
end
-- LOCAL LOBBY -- LOCAL LOBBY
---------------------------------------- ----------------------------------------
LocalLobby = class("LocalLobby", Lobby) LocalLobby = class("LocalLobby", Lobby)
@ -510,20 +538,135 @@ function LocalLobby:keypressed(key)
end end
-- NET - HOST LOBBY
----------------------------------------
HostLobby = class("HostLobby", Lobby)
function HostLobby:initialize()
Lobby.initialize(self)
self.server = sock.newServer("*", 13371)
self.server:setSerialization(bitser.dumps, bitser.loads)
self.server:on("connect",
function (data, client)
print("GOD IS DEAD BUT THERE'S A CONNECTION")
end)
self.server:on("lobbiestsUpdate",
function (localLobbiests, client)
for i,v in pairs(localLobbiests) do
print(v["name"])
end
self:updateLobbiests(localLobbiests, client)
end)
end
function HostLobby:update(dt)
self.server:update()
end
function HostLobby:sendLobbiests()
-- for i,v in pairs(localLobbiests) do
self.serverToAll:send("lobbiestsUpdate", self:localLobbiestTables())
end
function HostLobby:newLocalLobbiest()
Lobby.newLocalLobbiest(self)
self:sendLobbiests()
end
function HostLobby:updateLobbiests(localLobbiests, client)
local newRemotes = {}
for k,lobbiest in pairs(self.remoteLobbiests) do
if (lobbiest.client == client) then
self.remoteLobbiestsN = self.remoteLobbiestsN - 1
else
table.insert(newRemotes, lobbiest)
end
end
for k,lobbiest in pairs(localLobbiests) do
table.insert(newRemotes, ClientLobbiest:new(self, client, lobbiest))
self.remoteLobbiestsN = self.remoteLobbiestsN + 1
end
self.remoteLobbiests = newRemotes
end
-- NET - CLIENT LOBBY
----------------------------------------
ClientLobby = class("ClientLobby", Lobby)
function ClientLobby:initialize(address)
Lobby.initialize(self)
self.status = "Connecting..."
self.client = sock.newClient(address, 13371)
self.client:setSerialization(bitser.dumps, bitser.loads)
self.client:on("connect",
function (data)
self.status = "Connected!"
self.client:send("lobbiestsUpdate", self:localLobbiestTables())
end)
self.client:on("disconnect",
function (data)
self.status = "Disconnected"
end)
self.client:on("lobbiestsUpdate",
function (lobbiests)
self.remoteLobbiests = lobbiests
end)
self.client:connect()
end
function ClientLobby:update(dt)
self.client:update()
end
function ClientLobby:draw()
Lobby.draw(self)
love.graphics.draw(love.graphics.newText(self.ttf, self.status),
200, 10, 0, self.scale)
end
function ClientLobby:updateLobbiests(localLobbiests)
function ClientLobby:newLocalLobbiest()
Lobby.newLocalLobbiest(self)
self.client:send("lobbiestsUpdate", self:localLobbiestTables())
end
-- LOBBIEST proposed fighter -- LOBBIEST proposed fighter
---------------------------------------- ----------------------------------------
Lobbiest = class("Lobbiest") Lobbiest = class("Lobbiest")
function Lobbiest:initialize(lobby, name) function Lobbiest:initialize(lobby, name)
self.lobby = lobby
self.name = name or NAMES[math.random(1, table.maxn(NAMES))] self.name = name or NAMES[math.random(1, table.maxn(NAMES))]
self.character = math.random(1, table.maxn(CHARACTERS)) self.character = math.random(1, table.maxn(CHARACTERS))
self.sprite = love.graphics.newImage("art/sprites/"
.. CHARACTERS[self.character]["file"])
self.swordType = "normal" self.swordType = "normal"
end end
function Lobbiest:toTable()
return {["name"] = self.name, ["character"] = self.character,
["swordType"] = self.swordType}
end
-- LOCAL LOBBIEST -- LOCAL LOBBIEST
---------------------------------------- ----------------------------------------
LocalLobbiest = class("LocalLobbiest", Lobbiest) LocalLobbiest = class("LocalLobbiest", Lobbiest)
@ -534,7 +677,7 @@ function LocalLobbiest:initialize(lobby, name, keymap)
end end
function LocalLobbiest:keypressed(key, playerNo) function LocalLobbiest:keypressed(key, lobby)
if (key == self.keymap["accel"]) then if (key == self.keymap["accel"]) then
if (self.swordType == "normal") then self.swordType = "knife" if (self.swordType == "normal") then self.swordType = "knife"
elseif (self.swordType == "knife") then self.swordType = "normal" elseif (self.swordType == "knife") then self.swordType = "normal"
@ -544,7 +687,7 @@ function LocalLobbiest:keypressed(key, playerNo)
local textBox = TextBox:new(20, 400, 3, 10, self.name, "Name: ", local textBox = TextBox:new(20, 400, 3, 10, self.name, "Name: ",
function (text) function (text)
self.name = text self.name = text
self.lobby:install() lobby:install()
end) end)
textBox:install(false, drawFunction, nil, false) textBox:install(false, drawFunction, nil, false)
@ -555,16 +698,43 @@ function LocalLobbiest:keypressed(key, playerNo)
else else
self.character = self.character + 1 self.character = self.character + 1
end end
self.sprite = love.graphics.newImage("art/sprites/"
.. CHARACTERS[self.character]["file"])
end end
end end
function LocalLobbiest:toTable()
local tablo = Lobbiest.toTable(self)
tablo["keymap"] = self.keymap
return tablo
end
-- CLIENT LOBBIEST used by HostLobby
----------------------------------------
ClientLobbiest = class("ClientLobbiest", Lobbiest)
function ClientLobbiest:initialize(lobby, client, lobbiestTable)
self.client = client
self.name = lobbiestTable["name"]
self.keymap = lobbiestTable["keymap"]
self.swordType = lobbiestTable["swordType"]
self.character = lobbiestTable["character"]
end
-- HOST LOBBIEST used by ClientLobby
----------------------------------------
HostLobbiest = class("HostLobbiest", Lobbiest)
function HostLobbiest:initialize(lobby)
Lobbiest.initialize(self, lobby)
end
-- MENU used for creating menus (lol) -- MENU used for creating menus (lol)
---------------------------------------- ----------------------------------------
Menu = class("Menu") Menu = class("Menu")
function Menu:initialize(x, y, offset_x, offset_y, scale, menuItems) function Menu:initialize(x, y, offset_x, offset_y, scale, menuItems)
self.x,self.y = x,y self.x,self.y = x,y
self.offset_x,self.offset_y = offset_x,offset_y self.offset_x,self.offset_y = offset_x,offset_y
@ -706,18 +876,11 @@ end
-- UTIL -- UTIL
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function split(inputString, seperator)
local newString = {}
for stringBit in string.gmatch(inputString, "([^"..seperator.."]+)") do
table.insert(newString, stringBit)
end
return newString
end
-- Install the important 'hook' functions (draw, update, keypressed/released) -- Install the important 'hook' functions (draw, update, keypressed/released)
-- If any of the 'old' functions passed are not nil, then both the new and -- If any of the 'old' functions passed are not nil, then both the new and
-- old will be added into the new corresponding hook function -- old will be added into the new corresponding hook function
-- This function is too god damn long and it makes me want to cry
-- Could be pretty easily shortened, now that I think about it
function hookInstall(newUpdate, newDraw, newPress, newRelease, function hookInstall(newUpdate, newDraw, newPress, newRelease,
oldUpdate, oldDraw, oldPress, oldRelease) oldUpdate, oldDraw, oldPress, oldRelease)
local ignored = 1 local ignored = 1
@ -757,9 +920,12 @@ end
-- CHARACTERS -- CHARACTERS
------------------------------------------ ------------------------------------------
CHARACTERS = {} CHARACTERS = {}
CHARACTERS[1] = {["file"] = "jellyfish-lion.png", ["name"] = "Lion Jellyfish", ["desc"] = "hey, hey. you know whats shocking?", ["author"] = "rapidpunches", ["license"] = "CC-BY-SA 4.0"} -- Lion Jellyfish by rapidpunches, CC-BY-SA 4.0
CHARACTERS[2] = {["file"] = "jellyfish-n.png", "Jellyfish N", "(electricity)", "rapidpunches", "CC-BY-SA 4.0"} CHARACTERS[1] = love.graphics.newImage("art/sprites/jellyfish-lion.png")
CHARACTERS[3] = {["file"] = "shark-unicorn.png", "Shark-Unicorn", "A masterpiece", "My little bro", "CC-BY-SA 4.0"} -- N Jellyfish by rapidpunches, CC-BY-SA 4.0
CHARACTERS[2] = love.graphics.newImage("art/sprites/jellyfish-n.png")
-- Something Indecipherable by my little brother (<3<3), CC-BY-SA 4.0
CHARACTERS[3] = love.graphics.newImage("art/sprites/shark-unicorn.png")
-- DEFAULT NAMES -- DEFAULT NAMES
------------------------------------------ ------------------------------------------