├── .gitignore ├── lualib-src ├── CMakeLists.txt ├── lru │ ├── CMakeLists.txt │ ├── lru.h │ ├── lru_test.lua │ └── lua-lru.cc ├── random │ ├── CMakeLists.txt │ ├── test.lua │ ├── random_test.lua │ ├── README.md │ └── random.c └── kcp │ ├── CMakeLists.txt │ ├── lua-interface.md │ ├── lkcp.c │ └── ikcp.h ├── run.sh ├── thirdparty ├── lua-protobuf │ ├── .gitignore │ ├── rockspecs │ │ └── lua-protobuf-scm-1.rockspec │ ├── LICENSE │ ├── .travis.yml │ ├── serpent.lua │ └── README.md └── CMakeLists.txt ├── lualib ├── bw │ ├── hotfix │ │ ├── hotfix_module_names.lua │ │ ├── hotfix_runner.lua │ │ └── hotfix_helper.lua │ └── preload.lua ├── base │ ├── os_test.lua │ ├── table_test.lua │ ├── path_test.lua │ ├── url_test.lua │ ├── os.lua │ ├── io.lua │ ├── url.lua │ ├── math.lua │ ├── math_test.lua │ ├── class.lua │ ├── path.lua │ ├── string_test.lua │ ├── string.lua │ └── table.lua └── perf │ └── memory.lua ├── service ├── hotfix │ ├── mod.lua │ └── init.lua ├── gwagent │ └── init.lua ├── perf │ └── init.lua ├── chat │ └── init.lua ├── roundtrip │ └── init.lua ├── memcached │ └── init.lua ├── ttcp │ └── init.lua └── test │ └── init.lua ├── etc ├── config.memcached ├── chat_conf.lua ├── perf_conf.lua ├── test_conf.lua ├── roundtrip_conf.lua ├── ttcp_conf.lua ├── hotfix_conf.lua └── gwagent_conf.lua ├── tools ├── compare_memory_snapshot.lua ├── unittest.py ├── new_service.py └── deploy.py ├── .luacheckrc ├── .gitmodules ├── Makefile ├── CMakeLists.txt ├── README.md ├── .clang-format └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | deploy 3 | .vscode 4 | lualib/3rd 5 | luaclib/* 6 | -------------------------------------------------------------------------------- /lualib-src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(lru) 2 | add_subdirectory(kcp) 3 | add_subdirectory(random) 4 | -------------------------------------------------------------------------------- /lualib-src/lru/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # LRU 2 | aux_source_directory(. SRC_LRU) 3 | add_lua_library(lru ${SRC_LRU}) -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | if [ $# -lt 1 ] 2 | then 3 | echo 请输入配置名,如:run.sh test 4 | exit 5 | fi 6 | 7 | cd skynet; ./skynet ../etc/$1_conf.lua 8 | -------------------------------------------------------------------------------- /lualib-src/random/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # https://github.com/ejoy/ejoylualib 2 | aux_source_directory(. SRC_RANDOM) 3 | add_lua_library(random ${SRC_RANDOM}) -------------------------------------------------------------------------------- /lualib-src/kcp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # kcp 2 | # https://github.com/linxiaolong/lua-kcp 3 | # https://github.com/skywind3000/kcp/ 4 | aux_source_directory(. SRC_KCP) 5 | add_lua_library(kcp ${SRC_KCP}) -------------------------------------------------------------------------------- /thirdparty/lua-protobuf/.gitignore: -------------------------------------------------------------------------------- 1 | *.dll 2 | *.dll.* 3 | *.so 4 | *.exp 5 | *.rock 6 | *.gcov 7 | *.gcda 8 | *.gcno 9 | *.pb 10 | *.dSYM 11 | *.out 12 | test*.lua 13 | lua*/ 14 | test* 15 | *.natvis 16 | out* 17 | -------------------------------------------------------------------------------- /lualib/bw/hotfix/hotfix_module_names.lua: -------------------------------------------------------------------------------- 1 | -- Module names need hotfix. 2 | -- hotfix_helper.lua will reload this module in check(). 3 | -- So it can be changed dynamically. 4 | 5 | local hotfix_module_names = { 6 | } 7 | 8 | return hotfix_module_names 9 | -------------------------------------------------------------------------------- /lualib/base/os_test.lua: -------------------------------------------------------------------------------- 1 | local lu = require("test.luaunit") 2 | 3 | local os = require("base.os") 4 | 5 | function _G.test_get_file_size(t) 6 | local size = os.get_file_size("run.sh") 7 | lu.assertEquals(size, 119) 8 | end 9 | 10 | 11 | os.exit(lu.LuaUnit.run(), true) -------------------------------------------------------------------------------- /service/hotfix/mod.lua: -------------------------------------------------------------------------------- 1 | local test = {} 2 | test.count = 10 3 | count = 10 4 | local d_count = 20 5 | function test.func() 6 | count = count + 200 7 | d_count = d_count + 100 8 | test.count = test.count + 100 9 | print("test", count, d_count, test.count) 10 | return true 11 | end 12 | return test -------------------------------------------------------------------------------- /lualib/base/table_test.lua: -------------------------------------------------------------------------------- 1 | local lu = require("test.luaunit") 2 | 3 | local table = require("base.table") 4 | 5 | function _G.test_contain() 6 | local t = {"a", "b", "c"} 7 | lu.assertTrue(table.contains(t, "a")) 8 | lu.assertFalse(table.contains(t, "d")) 9 | end 10 | 11 | 12 | os.exit(lu.LuaUnit.run(), true) -------------------------------------------------------------------------------- /lualib/base/path_test.lua: -------------------------------------------------------------------------------- 1 | local lu = require("test.luaunit") 2 | 3 | local path = require("base.path") 4 | 5 | function _G.test_split(t) 6 | lu.assertEquals(path.split(""), {}) 7 | lu.assertEquals(path.split("a"), {'a'}) 8 | lu.assertEquals(path.split("/a/b"), {'a','b'}) 9 | end 10 | 11 | 12 | os.exit(lu.LuaUnit.run(), true) -------------------------------------------------------------------------------- /lualib/bw/preload.lua: -------------------------------------------------------------------------------- 1 | local skynet = require "skynet" 2 | 3 | -- 替换标准输出 4 | print = skynet.error 5 | 6 | local need_hotfix = skynet.getenv("need_hotfix") 7 | if need_hotfix then 8 | -- 开启热更新 9 | local hotfix_runner = require("bw.hotfix.hotfix_runner") 10 | import = hotfix_runner.import 11 | else 12 | import = require 13 | end 14 | 15 | skynet.error("preload done") -------------------------------------------------------------------------------- /lualib-src/random/test.lua: -------------------------------------------------------------------------------- 1 | local random = require "random" 2 | 3 | local r = random(0) -- random generator with seed 0 4 | local r2 = random(0) 5 | 6 | for i=1,10 do 7 | local x = r() 8 | assert(x == r2()) 9 | print(x) 10 | end 11 | 12 | for i=1,10 do 13 | local x = r(2) 14 | assert(x == r2(2)) 15 | print(x) 16 | end 17 | 18 | for i=1,10 do 19 | local x = r(0,3) 20 | assert(x == r2(0,3)) 21 | print(x) 22 | end 23 | -------------------------------------------------------------------------------- /lualib/perf/memory.lua: -------------------------------------------------------------------------------- 1 | -- 对内存进行采样 2 | -- 底层的检测原理见 https://www.cnblogs.com/yaukey/p/unity_lua_memory_leak_trace.html 3 | 4 | local mri = require "perf/MemoryReferenceInfo" 5 | 6 | local _M = {} 7 | 8 | -- 打印当前 Lua 虚拟机的所有内存引用快照到文件 9 | -- 10 | function _M.dump_snapshot(filename) 11 | local save_path = './' 12 | collectgarbage("collect") 13 | mri.m_cMethods.DumpMemorySnapshot(save_path, filename, -1) 14 | end 15 | 16 | return _M 17 | -------------------------------------------------------------------------------- /service/hotfix/init.lua: -------------------------------------------------------------------------------- 1 | -- 测试:热更新机制 2 | local skynet = require "skynet" 3 | 4 | -- import 加载模块,会支持热更新 5 | local mod = import("mod") 6 | 7 | local function print_info() 8 | -- 如果mod 文件有修改, 打印的结果会改变. 9 | mod.func() 10 | skynet.timeout(500, print_info) 11 | end 12 | 13 | skynet.start(function() 14 | skynet.newservice("debug_console",8000) 15 | skynet.error("start to test hotfix...") 16 | 17 | -- 10秒 触发定时器 18 | skynet.timeout(500, print_info) -- 19 | end) 20 | -------------------------------------------------------------------------------- /etc/config.memcached: -------------------------------------------------------------------------------- 1 | thread = 1 2 | harbor = 0 3 | lualoader = "lualib/loader.lua" 4 | bootstrap = "snlua bootstrap" -- The service for bootstrap 5 | 6 | start = "memcached" -- main script 7 | 8 | lua_cpath = "../luaclib/?.so;luaclib/?.so" 9 | lua_path = "../lualib/?.lua;../lualib/3rd/?.lua;lualib/?.lua;" 10 | 11 | snax = "../service/?.lua;../service/?/init.lua;service/?.lua" 12 | luaservice = "../service/?.lua;../service/?/init.lua;service/?.lua" 13 | cpath = "../cservice/?.so;cservice/?.so" -------------------------------------------------------------------------------- /tools/compare_memory_snapshot.lua: -------------------------------------------------------------------------------- 1 | -- 分析比较出内存文件中新增加的对象 2 | -- 生成内存镜像,细节见 examples/service/perf 3 | -- 底层的检测原理见 https://www.cnblogs.com/yaukey/p/unity_lua_memory_leak_trace.html 4 | package.path = '../lualib/?.lua;' .. package.path 5 | 6 | local mri = require('perf/MemoryReferenceInfo') 7 | 8 | -- 先, 生成的内存镜像文件 9 | local before_file = arg[1] 10 | -- 后, 生成的内存镜像文件 11 | local after_file = arg[2] 12 | 13 | mri.m_cMethods.DumpMemorySnapshotComparedFile('./', "Compared", -1, before_file, after_file) 14 | -------------------------------------------------------------------------------- /.luacheckrc: -------------------------------------------------------------------------------- 1 | std = "max" 2 | read_globals = {"skynet"} 3 | new_globals = { 4 | "import", 5 | "print", 6 | } 7 | 8 | include_files = { 9 | "lualib/*", 10 | 'lualib-src/lru/lru_test.lua', 11 | } 12 | 13 | exclude_files = { 14 | 'lualib/test/luaunit.lua', 15 | 'lualib/perf/MemoryReferenceInfo.lua', 16 | 'examples/service/hotfix/mod.lua', 17 | 'lualib/3rd/*', 18 | } 19 | 20 | ignore = { 21 | "211", -- Unused local variable. 22 | "212", -- Unused argument. 23 | } 24 | -------------------------------------------------------------------------------- /etc/chat_conf.lua: -------------------------------------------------------------------------------- 1 | -- 聊天服务 2 | thread = 1 3 | harbor = 0 4 | lualoader = "lualib/loader.lua" 5 | bootstrap = "snlua bootstrap" -- The service for bootstrap 6 | 7 | start = "chat" -- main script 8 | 9 | lua_cpath = "../luaclib/?.so;luaclib/?.so" 10 | lua_path = "../lualib/?.lua;../lualib/3rd/?.lua;lualib/?.lua;" 11 | 12 | snax = "../service/?.lua;../service/?/init.lua;service/?.lua" 13 | luaservice = "../service/?.lua;../service/?/init.lua;service/?.lua" 14 | cpath = "../cservice/?.so;cservice/?.so" 15 | -------------------------------------------------------------------------------- /etc/perf_conf.lua: -------------------------------------------------------------------------------- 1 | -- 测试服务性能相关接口 2 | thread = 1 3 | harbor = 0 4 | lualoader = "lualib/loader.lua" 5 | bootstrap = "snlua bootstrap" -- The service for bootstrap 6 | 7 | start = "perf" -- main script 8 | 9 | lua_cpath = "../luaclib/?.so;luaclib/?.so" 10 | lua_path = "../lualib/?.lua;../lualib/3rd/?.lua;lualib/?.lua;" 11 | 12 | snax = "../service/?.lua;../service/?/init.lua;service/?.lua" 13 | luaservice = "../service/?.lua;../service/?/init.lua;service/?.lua" 14 | cpath = "../cservice/?.so;cservice/?.so" -------------------------------------------------------------------------------- /etc/test_conf.lua: -------------------------------------------------------------------------------- 1 | -- 测试常用的Lua 库 2 | thread = 1 3 | harbor = 0 4 | lualoader = "lualib/loader.lua" 5 | bootstrap = "snlua bootstrap" -- The service for bootstrap 6 | 7 | start = "test" -- main script 8 | 9 | lua_cpath = "../luaclib/?.so;luaclib/?.so" 10 | lua_path = "../lualib/?.lua;../lualib/3rd/?.lua;lualib/?.lua;" 11 | 12 | snax = "../service/?.lua;../service/?/init.lua;service/?.lua" 13 | luaservice = "../service/?.lua;../service/?/init.lua;service/?.lua" 14 | cpath = "../cservice/?.so;cservice/?.so" 15 | -------------------------------------------------------------------------------- /etc/roundtrip_conf.lua: -------------------------------------------------------------------------------- 1 | -- 测试常用的Lua 库 2 | thread = 1 3 | harbor = 0 4 | lualoader = "lualib/loader.lua" 5 | bootstrap = "snlua bootstrap" -- The service for bootstrap 6 | 7 | start = "roundtrip" -- main script 8 | 9 | lua_cpath = "../luaclib/?.so;luaclib/?.so" 10 | lua_path = "../lualib/?.lua;../lualib/3rd/?.lua;lualib/?.lua;" 11 | 12 | snax = "../service/?.lua;../service/?/init.lua;service/?.lua" 13 | luaservice = "../service/?.lua;../service/?/init.lua;service/?.lua" 14 | cpath = "../cservice/?.so;cservice/?.so" 15 | -------------------------------------------------------------------------------- /etc/ttcp_conf.lua: -------------------------------------------------------------------------------- 1 | -- 测试常用的Lua 库 2 | thread = 1 3 | logger = nil 4 | harbor = 0 5 | lualoader = "lualib/loader.lua" 6 | bootstrap = "snlua bootstrap" -- The service for bootstrap 7 | 8 | start = "ttcp" -- main script 9 | 10 | lua_cpath = "../luaclib/?.so;luaclib/?.so" 11 | lua_path = "../lualib/?.lua;../lualib/3rd/?.lua;lualib/?.lua;" 12 | 13 | snax = "../service/?.lua;../service/?/init.lua;service/?.lua" 14 | luaservice = "../service/?.lua;../service/?/init.lua;service/?.lua" 15 | cpath = "../cservice/?.so;cservice/?.so" -------------------------------------------------------------------------------- /lualib-src/random/random_test.lua: -------------------------------------------------------------------------------- 1 | local random = require "random" 2 | local lu = require("test.luaunit") 3 | 4 | 5 | function _G.test_random(t) 6 | local r = random(0) -- random generator with seed 0 7 | local r2 = random(0) 8 | 9 | for i=1,10 do 10 | local x = r() 11 | lu.assertTrue(x == r2()) 12 | end 13 | 14 | for i=1,10 do 15 | local x = r(2) 16 | lu.assertTrue(x == r2(2)) 17 | end 18 | 19 | for i=1,10 do 20 | local x = r(0,3) 21 | lu.assertTrue(x == r2(0,3)) 22 | end 23 | end 24 | 25 | 26 | 27 | os.exit(lu.LuaUnit.run(), true) -------------------------------------------------------------------------------- /lualib/base/url_test.lua: -------------------------------------------------------------------------------- 1 | local lu = require("test.luaunit") 2 | 3 | local url = require("base.url") 4 | 5 | function _G.test_escape() 6 | local s = "a%2Bb+%3D+c" 7 | local s2 = "a+b = c" 8 | lu.assertTrue(url.unescape(s) == s2) 9 | lu.assertTrue(url.escape(s2) == s) 10 | end 11 | 12 | function _G.test_encode() 13 | local t = {name = "al", query = "a+b = c", q = "yes or no"} 14 | local expect = "name=al&q=yes+or+no&query=a%2Bb+%3D+c" 15 | lu.assertTrue(url.encode(t) == expect) 16 | end 17 | 18 | os.exit(lu.LuaUnit.run(), true) -------------------------------------------------------------------------------- /etc/hotfix_conf.lua: -------------------------------------------------------------------------------- 1 | -- 测试:热更新机制 2 | thread = 1 3 | harbor = 0 4 | lualoader = "lualib/loader.lua" 5 | bootstrap = "snlua bootstrap" -- The service for bootstrap 6 | 7 | start = "hotfix" -- main script 8 | 9 | lua_cpath = "../luaclib/?.so;luaclib/?.so" 10 | lua_path = "../lualib/?.lua;../lualib/3rd/?.lua;lualib/?.lua;" 11 | 12 | snax = "../service/?.lua;../service/?/init.lua;service/?.lua" 13 | luaservice = "../service/?.lua;../service/?/init.lua;service/?.lua" 14 | cpath = "../cservice/?.so;cservice/?.so" 15 | 16 | preload="../lualib/bw/preload.lua" 17 | need_hotfix=true -- 开启热更新 18 | -------------------------------------------------------------------------------- /etc/gwagent_conf.lua: -------------------------------------------------------------------------------- 1 | -- gate/watchdog/agent三剑客 2 | thread = 1 -- 启动多少个线程 3 | harbor = 0 -- 单节点 4 | lualoader = "lualib/loader.lua" -- 不建议修改 5 | bootstrap = "snlua bootstrap" -- 不建议修改 6 | 7 | start = "gwagent" -- 入口脚本 8 | 9 | -- 日志配置,默认打印到标准输出 10 | logservice = "logger" 11 | logger = nil 12 | 13 | lua_cpath = "../luaclib/?.so;luaclib/?.so" 14 | lua_path = "../lualib/?.lua;../lualib/3rd/?.lua;lualib/?.lua;" 15 | 16 | snax = "../service/?.lua;../service/?/init.lua;service/?.lua" 17 | luaservice = "../service/?.lua;../service/?/init.lua;service/?.lua;examples/?.lua" 18 | cpath = "../cservice/?.so;cservice/?.so" 19 | -------------------------------------------------------------------------------- /thirdparty/lua-protobuf/rockspecs/lua-protobuf-scm-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "lua-protobuf" 2 | version = "scm-1" 3 | 4 | source = { 5 | url = "git://github.com/starwing/lua-protobuf.git", 6 | } 7 | 8 | description = { 9 | summary = "protobuf data support for Lua", 10 | detailed = [[ 11 | This project offers a simple C library for basic protobuf wire format encode/decode. 12 | ]], 13 | homepage = "https://github.com/starwing/lua-protobuf", 14 | license = "MIT", 15 | } 16 | 17 | dependencies = { 18 | "lua >= 5.1" 19 | } 20 | 21 | build = { 22 | type = "builtin", 23 | modules = { 24 | pb = "pb.c"; 25 | protoc = "protoc.lua"; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lualib/base/os.lua: -------------------------------------------------------------------------------- 1 | -------------------------------------- 2 | ----- 对标准库 io 的补充 3 | ------------------------------------- 4 | local os = os or {} 5 | 6 | local lfs = require "lfs" 7 | 8 | -- 返回当前工作目录 9 | os.getcwd = function() 10 | return lfs.currentdir() 11 | end 12 | 13 | 14 | -- 15 | -- Time when data was last modified. 16 | -- 17 | function os.get_file_mtime(path) 18 | local file_time, err = lfs.attributes (path, "modification") 19 | return file_time, err 20 | end 21 | 22 | -- 23 | -- get file size (bytes) 24 | -- 25 | function os.get_file_size(path) 26 | local size, err = lfs.attributes(path, "size") 27 | return size, err 28 | end 29 | 30 | return os -------------------------------------------------------------------------------- /lualib/base/io.lua: -------------------------------------------------------------------------------- 1 | -------------------------------------- 2 | ----- 对标准库 io 的补充 3 | ------------------------------------- 4 | -- define module 5 | local io = io or {} 6 | 7 | -- 8 | -- Write content to a new file. 9 | -- 10 | function io.writefile(filename, content) 11 | local file = io.open(filename, "w+b") 12 | if file then 13 | file:writz_match_completione(content) 14 | file:close() 15 | return true 16 | end 17 | end 18 | 19 | -- 20 | -- Read content from new file. 21 | -- 22 | function io.readfile(filename) 23 | local file = io.open(filename, "rb") 24 | if file then 25 | local content = file:read("*a") 26 | file:close() 27 | return content 28 | end 29 | end 30 | 31 | 32 | 33 | return io -------------------------------------------------------------------------------- /lualib-src/random/README.md: -------------------------------------------------------------------------------- 1 | 伪随机数发生器 2 | ============== 3 | 4 | 算法来自于 http://marc-b-reynolds.github.io/shf/2016/04/19/prns.html 5 | 6 | 编译 7 | ==== 8 | 9 | 独立库用 `gcc --shared -o random.dll random.c` 并加上 lua 的头文件及库文件依赖。 10 | 11 | 嵌入应用直接包含 random.c 文件,然后在 host 中调用 `luaL_requiref(L, "random", luaopen_random, 0);` 12 | 13 | API 14 | === 15 | 16 | 使用 g = random(seed) 获得一个随机数发生器函数,之后可以调用这个函数 g 产生伪随机数。g 的参数和 math.random 相同。seed 必须为一个整数。 17 | 18 | 在不带参数时,产生一个 [0,1) 间的浮点随机数,带一个整数参数时,产生 [1, n] 的整数随机数,带两个整数参数时,产生 [m,n] 的整数随机数。 19 | 20 | 同时,这个模块支持 C 接口(仅兼容整数版本),可以在 C 里直接调用,获得和 Lua API 一致的结果: 21 | 22 | `void random_init(struct random_t *rd, uint64_t seed)` 用 seed 初始化一个随机数发生器。 23 | 24 | `uint64_t random_get(struct random_t *rd)` 获得一个 64 位随机数。 25 | 26 | `int random_range(struct random_t *rd, int range)` 获得 [0, range) 间的一个随机数。 27 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "thirdparty/hotfix"] 2 | path = thirdparty/hotfix 3 | url = https://github.com/jinq0123/hotfix 4 | [submodule "thirdparty/lua-cjson"] 5 | path = thirdparty/lua-cjson 6 | url = https://github.com/cloudwu/lua-cjson 7 | [submodule "thirdparty/lua-cmsgpack"] 8 | path = thirdparty/lua-cmsgpack 9 | url = https://github.com/samuelyao314/lua-cmsgpack 10 | [submodule "thirdparty/lua-zset"] 11 | path = thirdparty/lua-zset 12 | url = https://github.com/xjdrew/lua-zset 13 | [submodule "thirdparty/luafilesystem"] 14 | path = thirdparty/luafilesystem 15 | url = https://github.com/keplerproject/luafilesystem/ 16 | [submodule "skynet"] 17 | path = skynet 18 | url = https://github.com/colinsusie/skynet 19 | [submodule "thirdparty/skynet-rabbitmq"] 20 | path = thirdparty/skynet-rabbitmq 21 | url = https://github.com/samuelyao314/skynet-rabbitmq 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY:all skynet clean dev build test check 2 | 3 | all: skynet build 4 | 5 | PLAT="linux" 6 | ifeq ($(shell uname), Darwin) 7 | PLAT="macosx" 8 | endif 9 | 10 | 11 | SKYNET_MAKEFILE=skynet/Makefile 12 | $(SKYNET_MAKEFILE): 13 | git submodule update --init 14 | skynet: | $(SKYNET_MAKEFILE) 15 | cd skynet && $(MAKE) $(PLAT) MYCFLAGS="-fno-omit-frame-pointer " 16 | 17 | build: 18 | -mkdir $@ 19 | cd build && cmake -DCMAKE_BUILD_TYPE=Debug .. && make 20 | 21 | clean: 22 | -rm -rf deploy 23 | -rm -rf build 24 | -rm -rf lualib/3rd/* 25 | -rm -f luaclib/*so 26 | cd skynet && make clean 27 | 28 | 29 | INSTALL_DIR = deploy/ 30 | INSTALL_SKYNET = ${INSTALL_DIR}/skynet 31 | 32 | dev: 33 | python tools/deploy.py . deploy 34 | 35 | test: 36 | python tools/unittest.py skynet/3rd/lua/lua lualib 37 | 38 | check: 39 | luacheck --config .luacheckrc . 40 | 41 | update: 42 | git submodule foreach git submodule update 43 | -------------------------------------------------------------------------------- /lualib/bw/hotfix/hotfix_runner.lua: -------------------------------------------------------------------------------- 1 | local _M = {} 2 | 3 | local skynet = require "skynet" 4 | local table = require("base.table") 5 | local hotfix_helper = require("bw.hotfix.hotfix_helper") 6 | local hotfix_module_names = require("bw.hotfix.hotfix_module_names") 7 | 8 | -- 热更新模块初始化 9 | hotfix_helper.init() 10 | 11 | -- 触发热更新的时间间隔,单位是0.01s 12 | local delay = 100 13 | local function hot_update_cb() 14 | hotfix_helper.check() 15 | skynet.timeout(delay, hot_update_cb) 16 | end 17 | 18 | 19 | local start = false 20 | 21 | -- 增加模块,到热更新列表 22 | -- @param mod, 需要进行热更新的模块 23 | -- @RET,返回模块 24 | function _M.import(mod) 25 | if not table.contains(hotfix_module_names, mod) then 26 | -- 保证不重复 27 | table.insert(hotfix_module_names, mod) 28 | end 29 | local m = require(mod) 30 | if not start then 31 | skynet.timeout(delay, hot_update_cb) 32 | start = true 33 | end 34 | 35 | return m 36 | end 37 | 38 | return _M -------------------------------------------------------------------------------- /tools/unittest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 对项目的Lua 库, 尝试进行单元测试""" 5 | 6 | import os.path 7 | import sys 8 | 9 | 10 | def run_test(lib_path, luabin, file): 11 | command = 'LUA_PATH="%s/?.lua" LUA_CPATH="luaclib/?.so" %s %s' %(lib_path, luabin, file) 12 | print("RUN: ", command) 13 | status = os.system(command) 14 | if status != 0: 15 | raise Exception("UNITEST FAILED, file: %s" %(file)) 16 | 17 | def main(luabin, lib_path, rootdir): 18 | for root, dirs, files in os.walk(rootdir, topdown=True): 19 | for file in files: 20 | if file.endswith('_test.lua'): 21 | file_path = os.path.join(root, file) 22 | run_test(lib_path, luabin, file_path) 23 | 24 | 25 | if __name__ == '__main__': 26 | luabin = sys.argv[1]; 27 | lib_path = sys.argv[2] 28 | rootdir = "." 29 | if len(sys.argv) >= 4: 30 | rootdir = sys.argv[3] 31 | main(luabin, lib_path, rootdir) -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 2.8) 2 | project(terminator C CXX) 3 | 4 | if(NOT CMAKE_BUILD_TYPE) 5 | set(CMAKE_BUILD_TYPE "DEBUG") 6 | endif() 7 | 8 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O2 -g -fno-omit-frame-pointer") 9 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2 -g -fno-omit-frame-pointer -std=c++11") 10 | 11 | if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") 12 | set(CMAKE_SHARED_LINKER_FLAGS "-undefined dynamic_lookup") 13 | set(CMAKE_MACOSX_RPATH 0) 14 | endif() 15 | 16 | macro (add_lua_library lname) 17 | add_library(${lname} SHARED ${ARGN}) 18 | set_target_properties(${lname} PROPERTIES PREFIX "") 19 | set_target_properties(${lname} PROPERTIES SUFFIX ".so") 20 | endmacro() 21 | 22 | set(LUA_INCLUDE_DIR ${PROJECT_SOURCE_DIR}/skynet/3rd/lua) 23 | include_directories (${LUA_INCLUDE_DIR}) 24 | 25 | # so 文件(Lua C模块)输出目录 26 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/luaclib) 27 | 28 | add_subdirectory(thirdparty) 29 | add_subdirectory(lualib-src) 30 | -------------------------------------------------------------------------------- /service/gwagent/init.lua: -------------------------------------------------------------------------------- 1 | -- 功能: gate/watchdog/agent三剑客 2 | -- 测试方法:~/terminator/skynet]$3rd/lua/lua examples/client.lua 3 | -- 测试输入: 4 | --[[ 5 | msg Welcome to skynet, I will send heartbeat every 5 sec. 6 | RESPONSE 2 7 | hello # 需要查询的 key 值 8 | Request: 3 9 | RESPONSE 3 10 | result world 11 | --]] 12 | 13 | local skynet = require "skynet" 14 | local sprotoloader = require "sprotoloader" 15 | 16 | local max_client = 64 17 | 18 | skynet.start(function() 19 | skynet.error("-----------start gwagent server.----------------") 20 | 21 | skynet.uniqueservice("protoloader") 22 | if not skynet.getenv "daemon" then 23 | local console = skynet.newservice("console") 24 | end 25 | skynet.newservice("debug_console",8000) 26 | skynet.newservice("simpledb") 27 | local watchdog = skynet.newservice("watchdog") 28 | skynet.call(watchdog, "lua", "start", { 29 | port = 8888, 30 | maxclient = max_client, 31 | nodelay = true, 32 | }) 33 | skynet.error("Watchdog listen on", 8888) 34 | skynet.exit() 35 | end) 36 | 37 | -------------------------------------------------------------------------------- /lualib/base/url.lua: -------------------------------------------------------------------------------- 1 | --- 2 | --- url.lua 3 | --- URL encoding 4 | --- 5 | local url = {} 6 | 7 | local function unescape (s) 8 | s = string.gsub(s, "+", " ") 9 | s = string.gsub(s, "%%(%x%x)", function (h) 10 | return string.char(tonumber(h, 16)) 11 | end) 12 | return s 13 | end 14 | url.unescape = unescape 15 | 16 | url.decode = function (s) 17 | local cgi = {} 18 | for name, value in string.gmatch(s, "([^&=]+)=([^&=]+)") do 19 | name = unescape(name) 20 | value = unescape(value) 21 | cgi[name] = value 22 | end 23 | return cgi 24 | end 25 | 26 | local function escape (s) 27 | s = string.gsub(s, "[&=+%%%c]", function (c) 28 | return string.format("%%%02X", string.byte(c)) 29 | end) 30 | s = string.gsub(s, " ", "+") 31 | return s 32 | end 33 | url.escape = escape 34 | 35 | url.encode = function (t) 36 | local b = {} 37 | for k,v in pairs(t) do 38 | b[#b + 1] = (escape(k) .. "=" .. escape(v)) 39 | end 40 | table.sort(b) 41 | return table.concat(b, "&") 42 | end 43 | 44 | return url -------------------------------------------------------------------------------- /thirdparty/lua-protobuf/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Xavier Wang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /lualib/base/math.lua: -------------------------------------------------------------------------------- 1 | -------------------------------------- 2 | ----- 对标准库 math 的补充 3 | ------------------------------------- 4 | 5 | -- define module 6 | local math = math or {} 7 | 8 | -- init constants 9 | math.nan = math.log(-1) 10 | math.e = math.exp(1) 11 | 12 | -- check a number is int 13 | -- 14 | -- @returns true for int, otherwise false 15 | -- 16 | function math:isint() 17 | -- check 18 | assert(type(self) == "number", "number expacted") 19 | return self == math.floor(self) and self ~= math.huge and self ~= -math.huge 20 | end 21 | 22 | -- check a number is inf or -inf 23 | -- 24 | -- @returns 1 for inf, -1 for -inf, otherwise false 25 | -- 26 | function math:isinf() 27 | -- check 28 | assert(type(self) == "number", "number expacted") 29 | if self == math.huge then 30 | return 1 31 | elseif self == -math.huge then 32 | return -1 33 | else 34 | return false 35 | end 36 | end 37 | 38 | -- check a number is nan 39 | -- 40 | -- @returns true for nan, otherwise false 41 | -- 42 | function math:isnan() 43 | -- check 44 | assert(type(self) == "number", "number expacted") 45 | 46 | return self ~= self 47 | end 48 | 49 | 50 | -- return module 51 | return math -------------------------------------------------------------------------------- /thirdparty/lua-protobuf/.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | sudo: false 3 | 4 | env: 5 | global: 6 | - LUAROCKS=2.4.3 7 | - ROCKSPEC=rockspecs/lua-protobuf-scm-1.rockspec 8 | matrix: 9 | - LUA="lua 5.1" 10 | - LUA="lua 5.2" 11 | - LUA="lua 5.3" 12 | - LUA="luajit 2.0" 13 | - LUA="luajit 2.1" 14 | 15 | branches: 16 | only: 17 | - master 18 | - new 19 | 20 | before_install: 21 | - pip install --user hererocks urllib3[secure] cpp-coveralls 22 | - hererocks env --$LUA -rlatest # Use latest LuaRocks, install into 'env' directory. 23 | - source env/bin/activate # Add directory with all installed binaries to PATH. 24 | 25 | install: 26 | # - sudo luarocks make $ROCKSPEC CFLAGS="-O2 -fPIC -ftest-coverage -fprofile-arcs" LIBFLAG="-shared --coverage" 27 | - luarocks make $ROCKSPEC CFLAGS="-O3 -fPIC -Wall -Wextra --coverage" LIBFLAG="-shared --coverage" 28 | 29 | script: 30 | - lua test.lua 31 | # - lunit.sh test.lua 32 | 33 | after_success: 34 | - coveralls 35 | # - coveralls -b .. -r .. --dump c.report.json 36 | # - luacov-coveralls -j c.report.json -v 37 | 38 | notifications: 39 | email: 40 | on_success: change 41 | on_failure: always 42 | 43 | # vim: ft=yaml nu et sw=2 fdc=2 fdm=syntax 44 | -------------------------------------------------------------------------------- /lualib/base/math_test.lua: -------------------------------------------------------------------------------- 1 | local lu = require("test.luaunit") 2 | 3 | local math = require("base.math") 4 | 5 | function _G.test_isinf(t) 6 | lu.assertError(function() math.isinf(nil) end) 7 | lu.assertError(function() math.isinf(true) end) 8 | lu.assertFalse(math.isinf(0)) 9 | lu.assertFalse(math.isinf(math.nan)) 10 | lu.assertIs(math.isinf(math.huge), 1) 11 | lu.assertIs(math.isinf(-math.huge), -1) 12 | end 13 | 14 | function _G.test_isnan(t) 15 | lu.assertError(function() math.isinf(nil) end) 16 | lu.assertError(function() math.isinf(true) end) 17 | 18 | lu.assertFalse(math.isnan(0)) 19 | lu.assertTrue(math.isnan(math.nan)) 20 | lu.assertFalse(math.isnan(math.huge)) 21 | lu.assertFalse(math.isnan(-math.huge)) 22 | end 23 | 24 | function _G.test_isint(t) 25 | lu.assertError(function() math.isint(nil) end) 26 | lu.assertError(function() math.isint(true) end) 27 | 28 | lu.assertTrue(math.isint(0)) 29 | lu.assertTrue(math.isint(-10)) 30 | lu.assertTrue(math.isint(123456)) 31 | lu.assertFalse(math.isint(123456.1)) 32 | lu.assertFalse(math.isint(-9.99)) 33 | lu.assertFalse(math.isint(math.nan)) 34 | lu.assertFalse(math.isint(math.huge)) 35 | lu.assertFalse(math.isint(-math.huge)) 36 | end 37 | 38 | os.exit(lu.LuaUnit.run(), true) -------------------------------------------------------------------------------- /thirdparty/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(LUA_3RD_PATH ${PROJECT_SOURCE_DIR}/lualib/3rd/) 2 | 3 | # https://github.com/xjdrew/lua-zset 4 | aux_source_directory(lua-zset/ SRC_SKIPLIST) 5 | add_lua_library(skiplist ${SRC_SKIPLIST}) 6 | file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/lua-zset/zset.lua 7 | DESTINATION ${LUA_3RD_PATH}) 8 | 9 | # https://github.com/openresty/lua-cjson/ 10 | set(SRC_CJSON lua-cjson/lua_cjson.c lua-cjson/strbuf.c lua-cjson/fpconv.c) 11 | add_lua_library(cjson ${SRC_CJSON}) 12 | 13 | # https://github.com/samuelyao314/lua-cmsgpack 14 | aux_source_directory(lua-cmsgpack/ SRC_MSGPACK) 15 | add_lua_library(cmsgpack ${SRC_MSGPACK}) 16 | 17 | # https://github.com/keplerproject/luafilesystem/ 18 | aux_source_directory(luafilesystem/src SRC_LFS) 19 | add_lua_library(lfs ${SRC_LFS}) 20 | 21 | # https://github.com/starwing/lua-protobuf/ 22 | aux_source_directory(lua-protobuf/ SRC_PB) 23 | add_lua_library(pb ${SRC_PB}) 24 | file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/lua-protobuf/protoc.lua 25 | DESTINATION ${LUA_3RD_PATH}) 26 | 27 | # https://github.com/jinq0123/hotfix 28 | file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/hotfix/lua/hotfix 29 | DESTINATION ${LUA_3RD_PATH}) 30 | 31 | # https://github.com/cloudfreexiao/skynet-rabbitmq 32 | file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/skynet-rabbitmq/rabbitmqstomp.lua 33 | DESTINATION ${LUA_3RD_PATH}) 34 | -------------------------------------------------------------------------------- /service/perf/init.lua: -------------------------------------------------------------------------------- 1 | local skynet = require "skynet" 2 | local memory = require "perf/memory" 3 | 4 | local handler = {} 5 | 6 | function handler.dump_memory() 7 | local address = skynet.address(skynet.self()) 8 | local filename = 'simulate_memory' .. address 9 | memory.dump_snapshot(filename) 10 | skynet.error('succ to dump memory snapshot') 11 | end 12 | 13 | 14 | local function simulate_use_memory() 15 | local author = { 16 | Name = "yaukeywang", 17 | Job = "Game Developer", 18 | Hobby = "Game, Travel, Gym", 19 | City = "Beijing", 20 | Country = "China", 21 | Ask = function (question) 22 | return "My answer is for your question: " .. question .. "." 23 | end 24 | } 25 | 26 | handler.Author = author 27 | end 28 | 29 | skynet.dispatch("lua", function(_,_, cmd, ...) 30 | skynet.error("cmd", cmd) 31 | skynet.error("arg", ...) 32 | local f = assert(handler[cmd]) 33 | skynet.retpack(f(...)) 34 | end) 35 | 36 | 37 | skynet.start(function() 38 | skynet.newservice("debug_console", 8000) 39 | skynet.error("Be water my friend.") 40 | skynet.error("simulate memory leak.") 41 | 42 | -- 当前服务的名称 43 | -- 先导出一份内存镜像 44 | handler.dump_memory() 45 | -- 模拟: 使用了一些对象, 无法回收,处于内存泄漏 46 | simulate_use_memory() 47 | 48 | -- 用debug_conole,触发再次导出内存镜像 49 | -- $ telnet 127.0.0.1 8000 50 | -- 输入以下内容 51 | -- call 8 "dump_memory",1,2 52 | end) 53 | -------------------------------------------------------------------------------- /service/chat/init.lua: -------------------------------------------------------------------------------- 1 | -- 实现:聊天服务器 2 | local skynet = require "skynet" 3 | local socket = require "skynet.socket" 4 | local table = require "base.table" 5 | 6 | local host = "127.0.0.1" 7 | local port = 10001 8 | 9 | -- 保存当前连接上的所有客户端 10 | local client_list = {} 11 | 12 | local function handler(id, addr) 13 | while true do 14 | local line = socket.readline(id, "\r\n") 15 | if not line then 16 | -- 断开连接 17 | local k = table.indexof(client_list, id) 18 | table.remove(client_list, k) 19 | skynet.error(string.format("client %d[%s] closed", id, addr)) 20 | break 21 | end 22 | local logstr = string.format("client %d[%s] receive, %s", id, addr, line) 23 | skynet.error(logstr) 24 | -- 广播 25 | for k, client in ipairs(client_list) do 26 | if client ~= id then 27 | local message = string.format("[%s] say: %s\r\n", addr, line) 28 | socket.write(client, message) 29 | end 30 | end 31 | end 32 | end 33 | 34 | 35 | 36 | local function chat_server() 37 | local listen_id = socket.listen(host, port) 38 | assert(listen_id) 39 | skynet.error("listen id: ", listen_id) 40 | socket.start(listen_id, function(id, addr) 41 | skynet.error("client id: ", id, ", addr: ", addr) 42 | socket.start(id) -- 这行很重要. 否则id 不可读. 43 | table.insert(client_list, id) 44 | skynet.fork(handler, id, addr) 45 | end) 46 | end 47 | 48 | 49 | 50 | skynet.start(function() 51 | skynet.error("-----------start chat server.----------------") 52 | chat_server() 53 | end) 54 | -------------------------------------------------------------------------------- /service/roundtrip/init.lua: -------------------------------------------------------------------------------- 1 | local skynet = require "skynet" 2 | local socket = require "skynet.socket" 3 | 4 | 5 | local host = "127.0.0.1" 6 | local port = 8765 7 | local pack_fmt = 'i8i8' 8 | 9 | 10 | local function current_time() 11 | local now = skynet.time() -- 单位是秒 12 | return now * 1000000 -- 单位是 us 13 | end 14 | 15 | 16 | local function udp_server() 17 | local id 18 | id = socket.udp(function (str, from) 19 | local size = string.packsize(pack_fmt) 20 | skynet.error("server, receive message, size: ", #str, ", from: ", 21 | socket.udp_address(from), ", packsize: ", size) 22 | if size == #str then 23 | local t1, t2 = string.unpack(pack_fmt, str) 24 | assert(t2 == 0) 25 | local message = {t1, 0} 26 | message[2] = current_time() 27 | local resp = string.pack(pack_fmt, message[1], message[2]) 28 | --skynet.error("message: ", message[1], message[2]) 29 | socket.sendto(id, from, resp) 30 | end 31 | end, host, port) 32 | end 33 | 34 | 35 | local function udp_client() 36 | local id = socket.udp(function(str, from) 37 | --print("client recv, size:", #str, ", from: ", socket.udp_address(from)) 38 | local size = string.packsize(pack_fmt) 39 | if size == #str then 40 | local t1, t2 = string.unpack(pack_fmt, str) 41 | local back = current_time() 42 | local mine = (back + t1) / 2; 43 | local msg = string.format("client, now %d round trip %d clock error %d\n", back, back - t1, t2 - mine) 44 | skynet.error(msg) 45 | end 46 | end) 47 | socket.udp_connect(id, host, port) 48 | for i=1, 20 do 49 | local now = current_time() 50 | local data = string.pack(pack_fmt, now, 0) 51 | socket.write(id, data) 52 | skynet.sleep(10) 53 | end 54 | end 55 | 56 | 57 | 58 | skynet.start(function() 59 | skynet.fork(udp_server) 60 | skynet.fork(udp_client) 61 | end) 62 | -------------------------------------------------------------------------------- /lualib/bw/hotfix/hotfix_helper.lua: -------------------------------------------------------------------------------- 1 | -- 热更新机制,基于https://github.com/jinq0123/hotfix 2 | --- Hotfix helper which hotfixes modified modules. 3 | -- Using lfs to detect files' modification. 4 | -- 建议:只用在开发环境 5 | local M = { } 6 | 7 | local skynet = require("skynet") 8 | local lfs = require("lfs") 9 | local hotfix = require("hotfix.hotfix") 10 | 11 | -- Map file path to file time to detect modification. 12 | local path_to_time = { } 13 | 14 | -- global_objects which must not hotfix. 15 | local global_objects = { 16 | arg, 17 | assert, 18 | collectgarbage, 19 | coroutine, 20 | debug, 21 | dofile, 22 | error, 23 | getmetatable, 24 | io, 25 | ipairs, 26 | lfs, 27 | load, 28 | loadfile, 29 | math, 30 | next, 31 | os, 32 | package, 33 | pairs, 34 | pcall, 35 | print, 36 | rawequal, 37 | rawget, 38 | rawlen, 39 | rawset, 40 | require, 41 | select, 42 | setmetatable, 43 | string, 44 | table, 45 | tonumber, 46 | tostring, 47 | type, 48 | utf8, 49 | xpcall, 50 | skynet, -- skynet 模块不能够热更 51 | } 52 | 53 | --- Check modules and hotfix. 54 | function M.check() 55 | local MOD_NAME = "bw.hotfix.hotfix_module_names" 56 | if not package.searchpath(MOD_NAME, package.path) then return end 57 | -- package.loaded[MOD_NAME] = nil -- always reload it 58 | local module_names = require(MOD_NAME) 59 | 60 | for _, module_name in pairs(module_names) do 61 | local path, err = package.searchpath(module_name, package.path) 62 | -- Skip non-exist module. 63 | if not path then 64 | skynet.error(string.format("No such module: %s. %s", module_name, err)) 65 | goto continue 66 | end 67 | 68 | local file_time = lfs.attributes (path, "modification") 69 | if file_time == path_to_time[path] then goto continue end 70 | 71 | skynet.error(string.format("Hot fix module %s (%s)", module_name, path)) 72 | path_to_time[path] = file_time 73 | hotfix.hotfix_module(module_name) 74 | ::continue:: 75 | end -- for 76 | end -- check() 77 | 78 | function M.init() 79 | hotfix.log_debug = function(s) skynet.error(s) end 80 | hotfix.add_protect(global_objects) 81 | end 82 | 83 | return M 84 | -------------------------------------------------------------------------------- /tools/new_service.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 根据模版生成服务 """ 5 | import sys 6 | import os 7 | import os.path 8 | import multiprocessing 9 | 10 | # cpu 核数 11 | CPU_COUNT = multiprocessing.cpu_count() 12 | 13 | ETC_TEMLATE = """-- {0} 14 | thread = 1 -- 启动多少个线程 15 | harbor = 0 -- 单节点 16 | lualoader = "lualib/loader.lua" -- 不建议修改 17 | bootstrap = "snlua bootstrap" -- 不建议修改 18 | 19 | start = "{2}" -- 入口脚本 20 | 21 | -- 日志配置,默认打印到标准输出 22 | logservice = "logger" 23 | logger = nil 24 | 25 | lua_cpath = "../luaclib/?.so;luaclib/?.so" 26 | lua_path = "../lualib/?.lua;../lualib/3rd/?.lua;lualib/?.lua;" 27 | 28 | snax = "../service/?.lua;../service/?/init.lua;service/?.lua" 29 | luaservice = "../service/?.lua;../service/?/init.lua;service/?.lua;examples/?.lua" 30 | cpath = "../cservice/?.so;cservice/?.so" 31 | """ 32 | 33 | def generate_etc(svr_name, infomation): 34 | content = ETC_TEMLATE.format(information, CPU_COUNT, svr_name) 35 | path = "etc" 36 | if not os.path.exists(path): 37 | #os.makedirs(path) 38 | print("root dir must has 'etc' directory\n") 39 | sys.exit(1) 40 | filename = os.path.join(path, "%s_conf.lua" %(svr_name)) 41 | with open(filename, "w") as fp: 42 | fp.write(content) 43 | 44 | 45 | SVR_INIT_TEMPLATE =""" 46 | -- 功能: {1} 47 | 48 | local skynet = require "skynet" 49 | local socket = require "skynet.socket" 50 | 51 | 52 | skynet.start(function() 53 | skynet.error("-----------start {0} server.----------------") 54 | end) 55 | 56 | """ 57 | 58 | def generate_svr(svr_name, information): 59 | content = SVR_INIT_TEMPLATE.format(svr_name, information) 60 | path = "service/%s" %(svr_name) 61 | if not os.path.exists(path): 62 | os.makedirs(path) 63 | filename = os.path.join(path, "init.lua") 64 | if os.path.exists(filename): 65 | raise Exception("service exist, %s" %(svr_name)) 66 | with open(filename, "w") as fp: 67 | fp.write(content) 68 | 69 | 70 | def main(svr_name, information): 71 | generate_etc(svr_name, information) 72 | generate_svr(svr_name, information) 73 | 74 | if __name__ == '__main__': 75 | if len(sys.argv) < 3: 76 | print("usage: %s <服务说明>\n", sys.argv[0]) 77 | print("例如: python new_service.py hellosvr 测试程序\n") 78 | sys.exit(0) 79 | svr_name = sys.argv[1] 80 | information = sys.argv[2] 81 | main(svr_name, information) 82 | -------------------------------------------------------------------------------- /lualib-src/kcp/lua-interface.md: -------------------------------------------------------------------------------- 1 | 2 | # LUA-INTERFACE 3 | 4 | ## LKcp.lkcp_create(session, func) 5 | 6 | ### DESCRIPTION 7 | Create kcp object. 8 | 9 | ### PARAMETERS 10 | session: number mark session 11 | func: extra closures, which KCP layer invoke callback to send data, see detail in testkcp.lua 12 | 13 | ### RETURN 14 | kcp: kcp object 15 | 16 | ## kcp:lkcp_wndsize(sndwnd, rcvwnd) 17 | 18 | ### DESCRIPTION 19 | Set maximum window size: sndwnd=32, rcvwnd=32 by default 20 | 21 | ### PARAMETERS 22 | sndwnd: send window size 23 | rcvwnd: recive window size 24 | 25 | ### RETURN 26 | None 27 | 28 | ## kcp:lkcp_nodelay(nodelay, interval, resend, nc) 29 | 30 | ### DESCRIPTION 31 | Config re-transmission and flow control 32 | 33 | ### PARAMETERS 34 | nodelay: 0:disable(default), 1:enable 35 | interval: internal update timer interval in millisec, default is 100ms 36 | resend: 0:disable fast resend(default), 1:enable fast resend 37 | nc: 0:normal congestion control(default), 1:disable congestion control 38 | 39 | ### RETURN 40 | ret: always 0 41 | 42 | ## kcp:lkcp_check(current) 43 | 44 | ### DESCRIPTION 45 | Get when to invoke lkcp_update 46 | 47 | ### PARAMETERS 48 | current: current timestamp in millisec 49 | 50 | ### RETURN 51 | when: timestamp in millisec when to invoke lkcp_update 52 | 53 | ## kcp:lkcp_update(current) 54 | 55 | ### DESCRIPTION 56 | Update state (call it repeatedly, every 10ms-100ms), or you can ask 57 | 58 | ### PARAMETERS 59 | current: current timestamp in millisec 60 | 61 | ### RETURN 62 | None 63 | 64 | ## kcp:lkcp_send(data) 65 | 66 | ### DESCRIPTION 67 | User/upper level send 68 | 69 | ### PARAMETERS 70 | data: data to be sent 71 | 72 | ### RETURN 73 | sent_len: below zero for error, otherwise succeed 74 | 75 | ## kcp:lkcp_flush() 76 | 77 | ### DESCRIPTION 78 | Flush pending data 79 | 80 | ### PARAMETERS 81 | None 82 | 83 | ### RETURN 84 | None 85 | 86 | ## kcp:lkcp_input(data) 87 | 88 | ### DESCRIPTION 89 | When you received a low level packet (eg. UDP packet), call it 90 | 91 | ### PARAMETERS 92 | data: data received from transport layer 93 | 94 | ### RETURN 95 | ret: below zero for error, otherwise succeed 96 | 97 | ## kcp:lkcp_recv() 98 | 99 | ### DESCRIPTION 100 | User/upper level recv 101 | 102 | ### PARAMETERS 103 | None 104 | 105 | ### RETURN 106 | rcv_len: Less than or equal to 0 for EAGAIN, otherwise for rcv_len 107 | rcv_buf: if rcv_len greater than 0, rcv_buf is data to recv 108 | 109 | -------------------------------------------------------------------------------- /lualib/base/class.lua: -------------------------------------------------------------------------------- 1 | -- 类实现 2 | -- 来自 https://www.cnblogs.com/yaukey/p/4547882.html 3 | 4 | -- The hold all class type. 5 | local __TxClassTypeList = {} 6 | 7 | -- The inherit class function. 8 | local function TxClass(SuperType) 9 | -- Create new class type. 10 | local ClassType = {} 11 | ClassType.Ctor = false 12 | ClassType.SuperType = SuperType 13 | 14 | -- Create new class instance function. 15 | local function ClassTypeInstance(...) 16 | local Obj = {} 17 | do 18 | local Create 19 | Create = function (c, ...) 20 | if c.SuperType then 21 | Create(c.SuperType, ...) 22 | end 23 | 24 | if c.Ctor then 25 | c.Ctor(Obj, ...) 26 | end 27 | end 28 | Create(ClassType, ...) 29 | end 30 | 31 | setmetatable(Obj, {__index = __TxClassTypeList[ClassType]}) 32 | return Obj 33 | end 34 | 35 | -- The new function of this class. 36 | ClassType.new = ClassTypeInstance 37 | 38 | -- The super class type of this class. 39 | if SuperType then 40 | ClassType.super = setmetatable({}, 41 | { 42 | __index = function (t, k) 43 | local Func = __TxClassTypeList[SuperType][k] 44 | if "function" == type(Func) then 45 | t[k] = Func 46 | return Func 47 | else 48 | error("Accessing super class field are not allowed!") 49 | end 50 | end 51 | }) 52 | end 53 | 54 | -- Virtual table 55 | local Vtbl = {} 56 | --print('Vtbl', Vtbl) 57 | __TxClassTypeList[ClassType] = Vtbl 58 | 59 | -- Set index and new index of ClassType, and provide a default create method. 60 | setmetatable(ClassType, 61 | { 62 | __index = function (t, k) 63 | return Vtbl[k] 64 | end, 65 | 66 | __newindex = function (t, k, v) 67 | Vtbl[k] = v 68 | --print('set vtbl', k, v) 69 | end, 70 | 71 | __call = function (self, ...) 72 | return ClassTypeInstance(...) 73 | end 74 | }) 75 | 76 | -- To copy super class things that this class not have. 77 | if SuperType then 78 | setmetatable(Vtbl, 79 | { 80 | __index = function (t, k) 81 | local Ret = __TxClassTypeList[SuperType][k] 82 | Vtbl[k] = Ret 83 | return Ret 84 | end 85 | }) 86 | end 87 | 88 | return ClassType 89 | end 90 | 91 | return TxClass 92 | -------------------------------------------------------------------------------- /service/memcached/init.lua: -------------------------------------------------------------------------------- 1 | -- 实现 memcached 服务 2 | -- 网络编程实践: 例子 memecached 3 | local skynet = require "skynet" 4 | local socket = require "skynet.socket" 5 | local class = require "base.class" 6 | local string = require "base.string" 7 | 8 | -- 监听端口 9 | local host = "127.0.0.1" 10 | local port = 10001 11 | 12 | -- 值类型 13 | -- @class CValue 14 | local CValue = class() 15 | 16 | function CValue:Ctor(flags, exptime, cas, val) 17 | self.flags = flags 18 | self.exptime = exptime 19 | self.cas = cas 20 | self.val = val 21 | end 22 | 23 | function CValue:get_val() 24 | return self.val 25 | end 26 | 27 | 28 | -- 当前只支持一个存储 [key -> Value] 29 | local storage = {} 30 | -- 命令表 31 | local commands = {} 32 | 33 | commands.set = function (id, key, flags, exptime, bytes) 34 | local nbytes = tonumber(bytes) 35 | local data = socket.readline(id, "\r\n") 36 | if #data ~= nbytes then 37 | skynet.error("invalid data length", #data, nbytes) 38 | return 39 | end 40 | skynet.error("data", data) 41 | local v = CValue(flags, exptime, 0, data) 42 | storage[key] = v 43 | skynet.error("set", key, storage[key].val) 44 | socket.write(id, "STORED\r\n") 45 | end 46 | 47 | commands.get = function(id, key) 48 | local v = storage[key] 49 | skynet.error("get", key, v:get_val()) 50 | if v then 51 | socket.write(id, v:get_val() .. "\r\n") 52 | end 53 | socket.write(id, "END\r\n") 54 | end 55 | 56 | 57 | local function handler(id, addr) 58 | while true do 59 | local line = socket.readline(id, "\r\n") 60 | local args = string.split(line, "%s+") 61 | local cmd = args[1] 62 | skynet.error("cmd: ", cmd) 63 | if cmd == 'quit' then 64 | break 65 | end 66 | commands[cmd](id, table.unpack(args, 2)) 67 | end 68 | socket.close(id) 69 | end 70 | 71 | 72 | local function start_server() 73 | local listen_id = socket.listen(host, port) 74 | assert(listen_id) 75 | skynet.error("listen id: ", listen_id) 76 | socket.start(listen_id, function(id, addr) 77 | skynet.error("client id: ", id, ", addr: ", addr) 78 | socket.start(id) -- 这行很重要. 否则id 不可读. 79 | skynet.fork(handler, id, addr) 80 | end) 81 | end 82 | 83 | 84 | --[[ 例子: 85 | (python3) samuel@workspace$ telnet 127.0.0.1 10001 86 | Trying 127.0.0.1... 87 | Connected to localhost. 88 | Escape character is '^]'. 89 | set hello 0 0 5 90 | world 91 | STORED 92 | get hello 93 | world 94 | END 95 | --]] 96 | skynet.start(function() 97 | skynet.error("-----------start memcached server.----------------") 98 | start_server() 99 | end) 100 | -------------------------------------------------------------------------------- /lualib/base/path.lua: -------------------------------------------------------------------------------- 1 | --!A cross-platform build utility based on Lua 2 | -- 3 | -- Licensed under the Apache License, Version 2.0 (the "License"); 4 | -- you may not use this file except in compliance with the License. 5 | -- You may obtain a copy of the License at 6 | -- 7 | -- http://www.apache.org/licenses/LICENSE-2.0 8 | -- 9 | -- Unless required by applicable law or agreed to in writing, software 10 | -- distributed under the License is distributed on an "AS IS" BASIS, 11 | -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | -- See the License for the specific language governing permissions and 13 | -- limitations under the License. 14 | -- 15 | -- Copyright (C) 2015 - 2019, TBOOX Open Source Group. 16 | -- 17 | -- @author ruki, samuelyao 18 | -- @file path.lua 19 | -- 20 | 21 | -- define module: path 22 | local path = {} 23 | 24 | -- load modules 25 | local string = require("base.string") 26 | 27 | -- get the directory of the path 28 | function path.directory(p) 29 | -- check 30 | assert(p) 31 | 32 | local i = p:find_last("[/\\]") 33 | if i then 34 | if i > 1 then i = i - 1 end 35 | return p:sub(1, i) 36 | else 37 | return "." 38 | end 39 | end 40 | 41 | -- get the filename of the path 42 | function path.filename(p) 43 | -- check 44 | assert(p) 45 | 46 | local i = p:find_last("[/\\]") 47 | if i then 48 | return p:sub(i + 1) 49 | else 50 | return p 51 | end 52 | end 53 | 54 | -- get the basename of the path 55 | function path.basename(p) 56 | -- check 57 | assert(p) 58 | 59 | local name = path.filename(p) 60 | local i = name:find_last(".", true) 61 | if i then 62 | return name:sub(1, i - 1) 63 | else 64 | return name 65 | end 66 | end 67 | 68 | -- get the file extension of the path: .xxx 69 | function path.extension(p) 70 | -- check 71 | assert(p) 72 | 73 | -- get extension 74 | local i = p:find_last(".", true) 75 | if i then 76 | return p:sub(i) 77 | else 78 | return "" 79 | end 80 | end 81 | 82 | -- join path 83 | function path.join(p, ...) 84 | -- check 85 | assert(p) 86 | 87 | -- join them 88 | for _, name in ipairs({...}) do 89 | p = p .. "/" .. name 90 | end 91 | 92 | -- translate path 93 | return path.translate(p) 94 | end 95 | 96 | -- split path by the separator 97 | function path.split(p) 98 | -- check 99 | assert(p) 100 | 101 | return p:split("/") 102 | end 103 | 104 | -- the last character is the path seperator? 105 | function path.islastsep(p) 106 | -- check 107 | assert(p) 108 | 109 | local sep = p:sub(#p, #p) 110 | return sep == '/' 111 | end 112 | 113 | -- convert path pattern to a lua pattern 114 | function path.pattern(pattern) 115 | -- translate wildcards, e.g. *, ** 116 | pattern = pattern:gsub("([%+%.%-%^%$%(%)%%])", "%%%1") 117 | pattern = pattern:gsub("%*%*", "\001") 118 | pattern = pattern:gsub("%*", "\002") 119 | pattern = pattern:gsub("\001", ".*") 120 | pattern = pattern:gsub("\002", "[^/]*") 121 | 122 | return pattern 123 | end 124 | 125 | -- return module: path 126 | return path -------------------------------------------------------------------------------- /service/ttcp/init.lua: -------------------------------------------------------------------------------- 1 | -- 实现 TTCP 服务 2 | -- 类似 https://github.com/chenshuo/muduo/blob/master/examples/ace/ttcp/ttcp_blocking.cc 3 | local skynet = require "skynet" 4 | local socket = require "skynet.socket" 5 | 6 | local host = "127.0.0.1" 7 | local port = 10001 8 | 9 | 10 | local function handler(id, addr) 11 | local fmt = ">ii" 12 | local size = string.packsize(fmt) 13 | local data, succ = socket.read(id, size) 14 | if false == succ then 15 | skynet.error("read failed, id: ", id, ", addr: ", addr) 16 | return 17 | end 18 | local session_message = {number = 0, length = 0} 19 | session_message.number, session_message.length = string.unpack(fmt, data) 20 | 21 | -- 读取 PlayLoadMessage 22 | for i = 1, session_message.number do 23 | --skynet.error("loop: i ", i) 24 | local fmt2 = ">i" 25 | local size2 = string.packsize(fmt2) 26 | local data2 = socket.read(id, size2) 27 | local payload = {length = 0, data = ""} 28 | payload.length = string.unpack(fmt2, data2) 29 | --skynet.error("payload.length:", payload.length) 30 | assert(payload.length == session_message.length) 31 | payload.data = socket.read(id, session_message.length) 32 | local ack = string.pack('>i', payload.length) 33 | socket.write(id, ack) 34 | end 35 | end 36 | 37 | 38 | local function ttcp_server() 39 | local listen_id = socket.listen(host, port) 40 | assert(listen_id) 41 | skynet.error("listen id: ", listen_id) 42 | socket.start(listen_id, function(id, addr) 43 | skynet.error("client id: ", id, ", addr: ", addr) 44 | socket.start(id) -- 这行很重要. 否则id 不可读. 45 | skynet.fork(handler, id, addr) 46 | end) 47 | end 48 | 49 | 50 | -- 客户端. 行为类似 ./ttcp_blocking --trans 127.0.0.1 -p 10001 --length 1024 -n 10 51 | local function ttcp_client() 52 | local session_message = {number = 10000, length = 8192} 53 | skynet.error("port", port) 54 | skynet.error("buffer length", session_message.length) 55 | skynet.error("connecting to ", host, ":", port) 56 | local id = socket.open(host, port) 57 | assert(id) 58 | skynet.error("connected") 59 | -- 这里可以修改 60 | local stime = skynet.time() 61 | local data = string.pack(">ii", session_message.number, session_message.length) 62 | socket.write(id, data) 63 | for i = 1, session_message.number do 64 | local payload = string.rep("a", session_message.length) 65 | socket.write(id, string.pack(">s4", payload)) 66 | local fmt = ">i" 67 | local size = string.packsize(fmt) 68 | local body = socket.read(id, size) 69 | local ack = string.unpack(fmt, body) 70 | --skynet.error("loop, i: ", i, ", ack: ", ack) 71 | assert(ack == session_message.length) 72 | end 73 | local etime = skynet.time() 74 | local seconds = etime - stime 75 | local total_mib = session_message.number * session_message.length / 1024 / 1024 76 | skynet.error(string.format("%.2f MiB in total", total_mib)) 77 | skynet.error(string.format("%.2f seconds", seconds)) 78 | -- 吞吐量 79 | local throughput = session_message.number / seconds; 80 | skynet.error(string.format("throughput: %.2f req/s", throughput)) 81 | -- 平均延迟 82 | local latency = seconds / session_message.number 83 | skynet.error(string.format("latency: %.6f", latency)) 84 | end 85 | 86 | 87 | skynet.start(function() 88 | skynet.error("-----------start ttcp server.----------------") 89 | ttcp_server() 90 | ttcp_client() 91 | end) 92 | -------------------------------------------------------------------------------- /lualib-src/random/random.c: -------------------------------------------------------------------------------- 1 | // Marc B. Reynolds, 2013-2016 2 | // Public Domain under http://unlicense.org, see link for details. 3 | // 4 | // Documentation: http://marc-b-reynolds.github.io/shf/2016/04/19/prns.html 5 | // 6 | // Modified by Cloud Wu 7 | 8 | #include 9 | 10 | struct random_t { 11 | uint64_t x; 12 | }; 13 | 14 | #ifndef PRNS_WEYL 15 | #define PRNS_WEYL 0x61c8864680b583ebL 16 | #define PRNS_WEYL_I 0x0e217c1e66c88cc3L 17 | #endif 18 | 19 | #ifndef PRNS_WEYL_D 20 | #define PRNS_WEYL_D 0x4f1bbcdcbfa54001L 21 | #endif 22 | 23 | #ifndef PRNS_MIX_S0 24 | #ifdef PRNS_MIX_13 25 | #define PRNS_MIX_S0 30 26 | #define PRNS_MIX_S1 27 27 | #define PRNS_MIX_S2 31 28 | #define PRNS_MIX_M0 0xbf58476d1ce4e5b9L 29 | #define PRNS_MIX_M1 0x94d049bb133111ebL 30 | #else 31 | #define PRNS_MIX_S0 31 32 | #define PRNS_MIX_S1 27 33 | #define PRNS_MIX_S2 33 34 | #define PRNS_MIX_M0 0x7fb5d329728ea185L 35 | #define PRNS_MIX_M1 0x81dadef4bc2dd44dL 36 | #endif 37 | #endif 38 | 39 | #ifndef PRNS_MIX 40 | #ifndef PRNS_SMALLCRUSH 41 | #define PRNS_MIX(X) prns_mix(X) 42 | #else 43 | #define PRNS_MIX(X) prns_min_mix(X) 44 | #endif 45 | #endif 46 | 47 | #ifndef PRNS_MIX_D 48 | #ifndef PRNS_SMALLCRUSH 49 | #define PRNS_MIX_D(X) prns_mix(X) 50 | #else 51 | #define PRNS_MIX_D(X) prns_min_mix(X) 52 | #endif 53 | #endif 54 | 55 | static inline uint64_t 56 | prns_mix(uint64_t x) { 57 | x ^= (x >> PRNS_MIX_S0); 58 | x *= PRNS_MIX_M0; 59 | x ^= (x >> PRNS_MIX_S1); 60 | x *= PRNS_MIX_M1; 61 | 62 | #ifndef PRNS_NO_FINAL_XORSHIFT 63 | x ^= (x >> PRNS_MIX_S2); 64 | #endif 65 | 66 | return x; 67 | } 68 | 69 | void 70 | random_init(struct random_t *rd, uint64_t seed) { 71 | rd->x = PRNS_MIX(PRNS_WEYL*seed) + PRNS_WEYL_D; 72 | } 73 | 74 | uint64_t 75 | random_get(struct random_t *rd) { 76 | uint64_t i = rd->x; 77 | uint64_t r = PRNS_MIX_D(i); 78 | rd->x = i + PRNS_WEYL_D; 79 | return r; 80 | } 81 | 82 | // random [0, range) 83 | int 84 | random_range(struct random_t *rd, int range) { 85 | uint64_t x = random_get(rd); 86 | return (int)(x % range); 87 | } 88 | 89 | /// lua binding 90 | 91 | #include 92 | #include 93 | 94 | static int 95 | lrandom_get(lua_State *L) { 96 | struct random_t r; 97 | r.x = lua_tointeger(L, lua_upvalueindex(1)); 98 | uint64_t x = random_get(&r); 99 | lua_pushinteger(L, r.x); 100 | lua_replace(L, lua_upvalueindex(1)); 101 | uint64_t low, up; 102 | const uint64_t mask = (((uint64_t)1 << 50) - 1); // 50bit 103 | switch (lua_gettop(L)) { 104 | case 0: 105 | // return [0,1) 106 | x &= mask; 107 | double r = (double)x / (double)(mask+1); 108 | lua_pushnumber(L, r); 109 | return 1; 110 | case 1: 111 | // return [1, up] 112 | low = 1; 113 | up = luaL_checkinteger(L, 1); 114 | break; 115 | case 2: 116 | // return [low, up] 117 | low = luaL_checkinteger(L, 1); 118 | up = luaL_checkinteger(L, 2); 119 | break; 120 | default: 121 | return luaL_error(L, "Only support 0/1/2 parms"); 122 | } 123 | luaL_argcheck(L, low <= up, 1, "interval is empty"); 124 | luaL_argcheck(L, low >= 0 || up <= LUA_MAXINTEGER + low, 1, "interval too large"); 125 | 126 | x %= (up - low) + 1; 127 | lua_pushinteger(L, x + low); 128 | 129 | return 1; 130 | } 131 | 132 | static int 133 | lrandom_init(lua_State *L) { 134 | lua_Integer seed = luaL_checkinteger(L, 1); 135 | struct random_t r; 136 | random_init(&r, seed); 137 | lua_pushinteger(L, r.x); 138 | lua_pushcclosure(L, lrandom_get, 1); 139 | return 1; 140 | } 141 | 142 | LUAMOD_API int 143 | luaopen_random(lua_State *L) { 144 | lua_pushcfunction(L, lrandom_init); 145 | return 1; 146 | } 147 | -------------------------------------------------------------------------------- /service/test/init.lua: -------------------------------------------------------------------------------- 1 | local skynet = require "skynet" 2 | local string = require "base.string" 3 | 4 | 5 | local function test_zset() 6 | local zset = require "zset" 7 | local function random_choose(t) 8 | if #t == 0 then 9 | return 10 | end 11 | local i = math.random(#t) 12 | return table.remove(t, i) 13 | end 14 | local zs = zset.new() 15 | local total = 100 16 | local all = {} 17 | for i=1, total do 18 | all[#all + 1] = i 19 | end 20 | while true do 21 | local score = random_choose(all) 22 | if not score then 23 | break 24 | end 25 | local name = "a" .. score 26 | zs:add(score, name) 27 | end 28 | 29 | skynet.error("=============== zset ================") 30 | skynet.error("rank 28:", zs:rank("a28")) 31 | skynet.error("rev rank 28:", zs:rev_rank("a28")) 32 | skynet.error("") 33 | end 34 | 35 | 36 | local function test_cjson() 37 | local cjson = require "cjson" 38 | local sampleJson = [[{"age":"23","testArray":{"array":[8,9,11,14,25]},"Himi":"himigame.com"}]]; 39 | local data = cjson.decode(sampleJson) 40 | skynet.error("=============== cjson ================") 41 | skynet.error("age:", data["age"]) 42 | skynet.error("array[1]:", data.testArray.array[1]) 43 | end 44 | 45 | 46 | local function test_msgpack() 47 | local cmsgpack = require "cmsgpack" 48 | local a = {a1 = 1, a2 = 1, a3 = 1, a4 = 1, a5 = 1, a6 = 1, a7 = 1, a8 = 1, a9 = 1} 49 | 50 | skynet.error("=============== cmsgpack================") 51 | local encode = cmsgpack.pack(a) 52 | skynet.error("a: ", string.hexlify(encode)) 53 | local t = cmsgpack.unpack(encode) 54 | skynet.error("t:", t, "\n") 55 | end 56 | 57 | 58 | local function test_lfs() 59 | skynet.error("=============== lfs ================") 60 | local lfs = require "lfs" 61 | skynet.error("pwd: ", lfs.currentdir()) 62 | end 63 | 64 | local function test_protobuf() 65 | skynet.error("=============== protobuf ================") 66 | local pb = require "pb" 67 | local protoc = require "protoc" 68 | -- load schema from text 69 | assert(protoc:load [[ 70 | message Phone { 71 | optional string name = 1; 72 | optional int64 phonenumber = 2; 73 | } 74 | message Person { 75 | optional string name = 1; 76 | optional int32 age = 2; 77 | optional string address = 3; 78 | repeated Phone contacts = 4; 79 | } ]]) 80 | 81 | -- lua table data 82 | local data = { 83 | name = "ilse", 84 | age = 18, 85 | contacts = { 86 | { name = "alice", phonenumber = 12312341234 }, 87 | { name = "bob", phonenumber = 45645674567 } 88 | } 89 | } 90 | 91 | -- encode lua table data into binary format in lua string and return 92 | local bytes = assert(pb.encode("Person", data)) 93 | -- and decode the binary data back into lua table 94 | local data2 = assert(pb.decode("Person", bytes)) 95 | assert(data2.name == data.name) 96 | assert(data2.contacts[1].phonenumber == data.contacts[1].phonenumber) 97 | end 98 | 99 | local function test_lru() 100 | skynet.error("=============== lru ================") 101 | local lru = require "lru" 102 | local cache = lru.new(3, "integer", "map") 103 | cache:set(1, "1") 104 | cache:set(2, {2}) 105 | cache:set(3, 3) 106 | cache:set(4, 14) 107 | skynet.error("cache:count():", cache:count()) 108 | for k, v in pairs(cache) do 109 | skynet.error("cache: ", k, v) 110 | end 111 | end 112 | 113 | 114 | 115 | skynet.start(function() 116 | skynet.newservice("debug_console",8000) 117 | skynet.error("Be water my friend.") 118 | 119 | test_zset() 120 | test_cjson() 121 | test_msgpack() 122 | test_lfs() 123 | test_protobuf() 124 | test_lru() 125 | end) 126 | -------------------------------------------------------------------------------- /lualib/base/string_test.lua: -------------------------------------------------------------------------------- 1 | local lu = require("test.luaunit") 2 | 3 | local string = require("base.string") 4 | 5 | function _G.test_endswith() 6 | lu.assertTrue(("aaaccc"):endswith("ccc")) 7 | lu.assertTrue(("aaaccc"):endswith("aaaccc")) 8 | lu.assertFalse(("rc"):endswith("xcadas")) 9 | lu.assertFalse(("aaaccc "):endswith("%s")) 10 | end 11 | 12 | function _G.test_startswith() 13 | lu.assertTrue(("aaaccc"):startswith("aaa")) 14 | lu.assertTrue(("aaaccc"):startswith("aaaccc")) 15 | lu.assertFalse(("rc"):startswith("xcadas")) 16 | lu.assertFalse((" aaaccc"):startswith("%s")) 17 | end 18 | 19 | function _G.test_strip() 20 | lu.assertEquals((""):strip(), "") 21 | lu.assertEquals((" "):strip(), "") 22 | lu.assertEquals((""):strip(""), "") 23 | lu.assertEquals((" "):strip(""), "") 24 | lu.assertEquals((" aaa ccc "):strip(), "aaa ccc") 25 | lu.assertEquals(("aaa ccc "):strip(), "aaa ccc") 26 | lu.assertEquals((" aaa ccc"):strip(), "aaa ccc") 27 | lu.assertEquals(("aaa ccc"):strip(), "aaa ccc") 28 | lu.assertEquals(("\t\naaa ccc\r\n"):strip(), "aaa ccc") 29 | lu.assertEquals(("aba"):strip("a"), "b") 30 | end 31 | 32 | function _G.test_lstrip() 33 | lu.assertEquals((""):lstrip(), "") 34 | lu.assertEquals((" "):lstrip(), "") 35 | lu.assertEquals((""):lstrip(""), "") 36 | lu.assertEquals((" "):lstrip(""), "") 37 | lu.assertEquals((" aaa ccc "):lstrip(), "aaa ccc ") 38 | lu.assertEquals(("aaa ccc "):lstrip(), "aaa ccc ") 39 | lu.assertEquals((" aaa ccc"):lstrip(), "aaa ccc") 40 | lu.assertEquals(("aaa ccc"):lstrip(), "aaa ccc") 41 | lu.assertEquals(("\t\naaa ccc\r\n"):lstrip(), "aaa ccc\r\n") 42 | lu.assertEquals(("aba"):lstrip("a"), "ba") 43 | end 44 | 45 | function _G.test_rstrip() 46 | lu.assertEquals((""):rstrip(), "") 47 | lu.assertEquals((" "):rstrip(), "") 48 | lu.assertEquals((""):rstrip(""), "") 49 | lu.assertEquals((" "):rstrip(""), "") 50 | lu.assertEquals((" aaa ccc "):rstrip(), " aaa ccc") 51 | lu.assertEquals(("aaa ccc "):rstrip(), "aaa ccc") 52 | lu.assertEquals((" aaa ccc"):rstrip(), " aaa ccc") 53 | lu.assertEquals(("aaa ccc"):rstrip(), "aaa ccc") 54 | lu.assertEquals(("\t\naaa ccc\r\n"):rstrip(), "\t\naaa ccc") 55 | lu.assertEquals(("aba"):rstrip("a"), "ab") 56 | end 57 | 58 | function _G.test_split() 59 | -- pattern match and ignore empty string 60 | lu.assertEquals(("1\n\n2\n3"):split('\n'), {"1", "2", "3"}) 61 | lu.assertEquals(("abc123123xyz123abc"):split('123'), {"abc", "xyz", "abc"}) 62 | lu.assertEquals(("abc123123xyz123abc"):split('[123]+'), {"abc", "xyz", "abc"}) 63 | 64 | -- plain match and ignore empty string 65 | lu.assertEquals(("1\n\n2\n3"):split('\n', {plain = true}), {"1", "2", "3"}) 66 | lu.assertEquals(("abc123123xyz123abc"):split('123', {plain = true}), {"abc", "xyz", "abc"}) 67 | 68 | -- pattern match and contains empty string 69 | lu.assertEquals(("1\n\n2\n3"):split('\n', {strict = true}), {"1", "", "2", "3"}) 70 | lu.assertEquals(("abc123123xyz123abc"):split('123', {strict = true}), {"abc", "", "xyz", "abc"}) 71 | lu.assertEquals(("abc123123xyz123abc"):split('[123]+', {strict = true}), {"abc", "xyz", "abc"}) 72 | 73 | -- plain match and contains empty string 74 | lu.assertEquals(("1\n\n2\n3"):split('\n', {plain = true, strict = true}), {"1", "", "2", "3"}) 75 | lu.assertEquals(("abc123123xyz123abc"):split('123', {plain = true, strict = true}), {"abc", "", "xyz", "abc"}) 76 | 77 | -- limit split count 78 | lu.assertEquals(("1\n\n2\n3"):split('\n', {limit = 2}), {"1", "2\n3"}) 79 | lu.assertEquals(("1\n\n2\n3"):split('\n', {limit = 2, strict = true}), {"1", "\n2\n3"}) 80 | lu.assertEquals(("1.2.3.4.5"):split('%.', {limit = 3}), {"1", "2", "3.4.5"}) 81 | lu.assertEquals(("123.45"):split('%.', {limit = 3}), {"123", "45"}) 82 | end 83 | 84 | function _G.test_hexlify() 85 | local s = "helloworld\n" 86 | local data = string.hexlify(s) 87 | lu.assertEquals(data, "68656c6c6f776f726c640a") 88 | end 89 | 90 | function _G.test_unhexlify() 91 | local data = "68656c6c6f776f726c640a" 92 | local s = string.unhexlify(data) 93 | lu.assertEquals(s, "helloworld\n") 94 | end 95 | 96 | 97 | 98 | os.exit(lu.LuaUnit.run(), true) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 介绍 2 | terminator 是基于 [skynet](https://github.com/cloudwu/skynet) 服务端开发方案. 3 | 4 | ## 编译和运行 5 | 项目只支持在 Linux 下编译和运行。 6 | 需要提前安装构建工具 cmake, 以及 python. 7 | 8 | 编译项目 9 | 10 | ```shell 11 | $ git clone https://github.com/samuelyao314/workspace terminator 12 | $ cd terminator 13 | $ make 14 | 15 | ``` 16 | 17 | 下一步,运行服务 18 | 19 | ```shell 20 | $ ./run.sh test # test 是服务名 21 | ``` 22 | 23 | 24 | 如果需要部署,执行 25 | 26 | ```shell 27 | $ make dev 28 | ``` 29 | 30 | 最后,复制 deploy 目录到目标机器。 31 | 32 | 33 | ## 项目结构 34 | 35 | ```txt 36 | lualib(公共lua库) 37 | base(通用库) 38 | perf(性能相关) 39 | test(单元测试) 40 | bw (基于skynet的公共库) 41 | hotfix (热更新机制) 42 | etc(启动配置) 43 | config.test (test 服务配置) 44 | config.chat (chat 服务配置) 45 | service(服务入口) 46 | test (简单测试服务) 47 | chat (聊天服务) 48 | skynet 49 | tools(辅助工具) 50 | deploy.py (生成部署目录) 51 | unittest.py. (单元测试驱动) 52 | new_service.py (创建自定义服务) 53 | thirdparty (第三方依赖) 54 | 55 | ``` 56 | 57 | 58 | ## 创建新服务 59 | 新的项目,通常都需要创建新服务。一般情况,用模版工具生成。 60 | 61 | ```shell 62 | $ python tools/new_service.py hello "just test" # 参数1是服务名称(保证唯一),参数2是描述信息 63 | ``` 64 | 65 | 执行后,会生成以下文件。如果需要删除服务,手动清除以下文件。 66 | 67 | ``` 68 | etc 69 | config.hello (配置) 70 | service 71 | hello 72 | init.lua (服务的入口) 73 | ``` 74 | 75 | 接着,启动新服务 76 | 77 | ``` 78 | $ ./run.sh hello 79 | ``` 80 | 81 | 82 | ## 静态检测 83 | 使用 luacheck进行静态检查,配置文件.luacheckrc. 84 | 85 | 安装完 luacheck 后 (建议用 hererock + luarocks 进行安装) 86 | 87 | ```shell 88 | $ make check 89 | ``` 90 | 91 | ## 单元测试 92 | 单元测试文件, 是 xx_test.lua 命名的文件。 93 | 执行单元测试 94 | 95 | ```shell 96 | $ make test 97 | ``` 98 | 99 | 100 | ## 代码热更新 101 | 热更新机制可以在开发阶段,帮忙更好地调试代码。 102 | 因为Lua的灵活性以及游戏逻辑的复杂,热更新很难做完备,因此不建议应用在生产环境。 103 | 生产环境,需要临时修复代码,可以用 skynet 自带的 inject 机制。 104 | 105 | 启动热更新,需要配置当前环境为开发环境 106 | 107 | ```lua 108 | # config 文件 109 | need_hotfix=true 110 | ``` 111 | 112 | 接着,**import** 加载的文件,一旦文件被修改,就会自动热加载。 113 | 114 | ```lua 115 | local mod = import("mod") 116 | ``` 117 | 118 | 更多细节看 services/service/hotfix. 119 | 120 | ## 内存泄露 121 | 内存泄露,可以通过2次对Lua State 进行切片,比较差异,进行分析。 122 | 具体的接口使用见例子 perf . 123 | 124 | ``` shell 125 | $ ./run.sh perf 126 | # 启动后,在skynet 目录,这个服务会产生一个内存切片。 127 | # 例如产生类似这种文件:LuaMemRefInfo-All-[XXX]-[simulate_memory:00000008].txt。 128 | # 等待少许时间,该服务会分配一些对象 129 | # debug_console 服务提供了管理端工具,通过它再生成一份切片 130 | $ telnet 127.0.0.1 8000 131 | Trying 127.0.0.1... 132 | Connected to localhost. 133 | Escape character is '^]'. 134 | call 8 "dump_memory",1,2 # 这个是输入, 8 代表perf服务的ID 135 | n 0 # 这个是返回 136 | 137 | 138 | # 下一步,利用2个切片文件,得到内存差异 139 | $ 3rd/lua/lua ../tools/compare_memory_snapshot.lua LuaMemRefInfo-All* 140 | # 当前目录会生成一个 LuaMemRefInfo-All-[XXX]-[Compared].txt 141 | # 比较代码,以及这个差异,就可以知道是否存在内存泄露 142 | 143 | ``` 144 | 145 | 具体实现细节见:[关于 Lua 内存泄漏的检测](https://www.cnblogs.com/yaukey/p/unity_lua_memory_leak_trace.html) 146 | 147 | 148 | ## 单步调试 149 | 结合 VSCode 的插件[Skynet Debugger](https://github.com/colinsusie/skynetda), 本项目支持单步调试。 150 | 151 | 例如服务 chat,进行单步调试。 VSCode 的配置文件 launch.json 设置如下 152 | 153 | ```json 154 | { 155 | // 这个版本号,根据实际的插件版本号,进行修改 156 | "version": "1.0.0", 157 | "configurations": [ 158 | { 159 | "name": "skynet debugger", 160 | "type": "lua", 161 | "request": "launch", 162 | "program": "${workspaceFolder}/skynet", 163 | "config": "../etc/config.chat" 164 | } 165 | ] 166 | } 167 | ``` 168 | 169 | 配置 config.chat 里,加了以下4行配置 170 | 171 | ```lua 172 | logger = "vscdebuglog" 173 | logservice = "snlua" 174 | vscdbg_open = "$vscdbg_open" 175 | vscdbg_bps = [=[$vscdbg_bps]=] 176 | ``` 177 | 178 | 然后,点击菜单:Debug-Start Debugging. 最后就可以设置断点,进行调试了。 179 | 180 | 181 | ## 火焰图 182 | *TODO* 183 | 184 | * 参考: [skynet 火焰图的新方法](https://spin6lock.github.io/2020/05/24/skynet%E7%81%AB%E7%84%B0%E5%9B%BE%E7%9A%84%E6%96%B0%E6%96%B9%E6%B3%95.html) 185 | 186 | 187 | ## Lua 第3方模块 188 | * [lua-zset](https://github.com/xjdrew/lua-zset), Lua 的sorted set实现。基于Redis 的skiplist源码 189 | * [lua-cjson](https://github.com/openresty/lua-cjson), 高性能的JSON解析器和编码器 190 | * [lua-cmsgpack](https://github.com/antirez/lua-cmsgpack), C语言实现的msgpack解析器和编码器 191 | * [luafilesystem](https://github.com/keplerproject/luafilesystem), lua的一个专门用来进行文件操作的库 192 | * [lua-protobuf](https://github.com/starwing/lua-protobuf/), XLua 作者实现的PB解析库。[文档在这里](https://zhuanlan.zhihu.com/p/26014103) 193 | * [lua-kcp](https://github.com/linxiaolong/lua-kcp), UDP 可靠协议 194 | 195 | 196 | ## 参考资料 197 | * [bewater](https://github.com/zhandouxiaojiji/bewater), skynet通用模块 198 | -------------------------------------------------------------------------------- /lualib-src/lru/lru.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | template class MAP_TYPE = std::map> 11 | class lru { 12 | public: 13 | lru(int size) { 14 | m_size = size; 15 | m_next_index = 1; 16 | lru_reserve(m_iter_map, size); 17 | } 18 | 19 | ~lru() { 20 | } 21 | 22 | int get(const TKEY& key) { 23 | auto map_it = m_iter_map.find(key); 24 | if (map_it == m_iter_map.end()) { 25 | return 0; 26 | } 27 | 28 | auto list_it = map_it->second; 29 | int index = list_it->index; 30 | m_node_list.push_front(*list_it); 31 | m_node_list.erase(list_it); 32 | map_it->second = m_node_list.begin(); 33 | return index; 34 | } 35 | 36 | int set(const TKEY& key, const std::function& discard_callback) { 37 | auto map_it = m_iter_map.find(key); 38 | if (map_it != m_iter_map.end()) { 39 | auto list_it = map_it->second; 40 | int index = list_it->index; 41 | m_node_list.push_front(*list_it); 42 | m_node_list.erase(list_it); 43 | map_it->second = m_node_list.begin(); 44 | return index; 45 | } 46 | 47 | int index = 0; 48 | if (count() >= m_size) { 49 | node& last = m_node_list.back(); 50 | index = last.index; 51 | 52 | if (discard_callback != nullptr) 53 | discard_callback(index, last.key); 54 | 55 | m_iter_map.erase(last.key); 56 | m_node_list.pop_back(); 57 | m_node_list.push_front({key, index}); 58 | m_iter_map[key] = m_node_list.begin(); 59 | return index; 60 | } 61 | 62 | if (m_free_list.empty()) { 63 | index = m_next_index++; 64 | } else { 65 | index = m_free_list.back(); 66 | m_free_list.pop_back(); 67 | } 68 | 69 | m_node_list.push_front({ key, index }); 70 | m_iter_map[key] = m_node_list.begin(); 71 | return index; 72 | } 73 | 74 | int del(const TKEY& key) { 75 | auto map_it = m_iter_map.find(key); 76 | if (map_it == m_iter_map.end()) 77 | return 0; 78 | 79 | auto list_it = map_it->second; 80 | int index = list_it->index; 81 | m_node_list.erase(list_it); 82 | m_iter_map.erase(map_it); 83 | m_free_list.push_back(index); 84 | return index; 85 | } 86 | 87 | int first(TKEY& key) { 88 | if (m_iter_map.empty()) 89 | return 0; 90 | 91 | auto map_it = m_iter_map.begin(); 92 | node& data = *(map_it->second); 93 | key = data.key; 94 | return data.index; 95 | } 96 | 97 | int next(TKEY& key) { 98 | auto map_it = m_iter_map.find(key); 99 | if (map_it == m_iter_map.end()) 100 | return 0; 101 | 102 | if (++map_it == m_iter_map.end()) 103 | return 0; 104 | 105 | node& data = *(map_it->second); 106 | key = data.key; 107 | return data.index; 108 | } 109 | 110 | int count() { 111 | // 这里使用map.size(),因为有些版本的stl计算list.size()的复杂度是线性的 112 | return (int)m_iter_map.size(); 113 | } 114 | 115 | void resize(std::vector& remove_list, int size) { 116 | int remve_num = count() - size; 117 | for (int i = 0; i < remve_num; i++) { 118 | node& last = m_node_list.back(); 119 | remove_list.push_back(last.index); 120 | m_iter_map.erase(last.key); 121 | m_free_list.push_back(last.index); 122 | m_node_list.pop_back(); 123 | } 124 | m_size = size; 125 | } 126 | 127 | // 这里并没有返回被清除的对象列表,调用者要负责清除lua数组中存储的所有对象 128 | void clear() { 129 | m_next_index = 1; 130 | m_free_list.clear(); 131 | m_node_list.clear(); 132 | m_iter_map.clear(); 133 | } 134 | 135 | private: 136 | struct node { 137 | TKEY key; 138 | int index; 139 | }; 140 | 141 | template 142 | auto lru_reserve(T& obj, int size) -> decltype(std::declval().reserve(0)) { obj.reserve(size); } 143 | 144 | template 145 | void lru_reserve(T&, ...) { } 146 | 147 | private: 148 | int m_size; 149 | int m_next_index; 150 | std::vector m_free_list; 151 | std::list m_node_list; 152 | MAP_TYPE::iterator> m_iter_map; 153 | }; 154 | 155 | -------------------------------------------------------------------------------- /tools/deploy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 生成项目的部署文件夹 """ 5 | 6 | import os.path 7 | import os 8 | import shutil 9 | import glob 10 | import sys 11 | import hashlib 12 | 13 | def file_is_same(srcfile, destfile, comparator): 14 | return comparator(srcfile, destfile) 15 | 16 | def get_file_md5(f): 17 | m = hashlib.md5() 18 | while True: 19 | data = f.read(10240) 20 | if not data: 21 | break 22 | m.update(data) 23 | return m.hexdigest() 24 | 25 | def md5_cmp(srcfile, destfile): 26 | src_md5 = "" 27 | with open(srcfile, 'rb') as f: 28 | src_md5 = get_file_md5(f) 29 | with open(destfile, 'rb') as f: 30 | dest_md5= get_file_md5(f) 31 | return src_md5 == dest_md5 32 | 33 | def copyfile(srcfile, destfile): 34 | assert(os.path.exists(srcfile)) 35 | if not os.path.exists(destfile): 36 | path = os.path.dirname(destfile) 37 | if not os.path.exists(path): 38 | os.makedirs(path) 39 | shutil.copy2(srcfile, destfile) 40 | print(srcfile, "####", destfile) 41 | return 42 | # 存在差异,才进行覆盖 43 | same = file_is_same(srcfile, destfile, md5_cmp) 44 | if not same: 45 | shutil.copy2(srcfile, destfile) 46 | print(srcfile, "--->", destfile) 47 | 48 | 49 | def copydir(project_dir, srcdir, deploy_dir, destdir, extension, exclude): 50 | """ 51 | 同步指定格式的文件. 52 | 遍历真实路径 ${project_dir}/${srcdir}. 如果找到文件 ${project_dir}/${srcdir}/xxx/yyy/z.lua, 53 | 目标路径是 ${deploy_dir}/${destdir}/xxx/yyy/z.lua, 54 | 55 | :param project_dir: 项目根目录 56 | :param deploy_dir: 部署目录 57 | :param srcdir: 遍历目录 (相对 project_dir) 58 | :param destdir: 目标目录 (相对 deploy_dir) 59 | :param extension: 文件后缀 60 | :param exclude: 需要忽略的子目录 61 | """ 62 | src_path = os.path.join(project_dir, srcdir) 63 | dest_path = os.path.join(deploy_dir, destdir) 64 | for root, dirs, files in os.walk(src_path, topdown=True): 65 | if exclude: 66 | if isinstance(exclude, str) and exclude == "*": # 跳过所有子目录 67 | dirs[:] = [] 68 | else: 69 | assert(isinstance(exclude, list)) 70 | remove_dirs = [] 71 | for d in dirs: 72 | for key in exclude: 73 | if d.endswith(key): # 判断指定后缀的目录 74 | # print("skip", root, d) 75 | remove_dirs.append(d) 76 | for d in remove_dirs: 77 | dirs.remove(d) 78 | for file in files: 79 | if extension != "*" and (not file.endswith(extension)): 80 | continue # 不复制这个文件 81 | if file.endswith("_test.lua"): # 忽略单元测试文件 82 | continue 83 | srcfile = os.path.join(root, file) 84 | destfile = srcfile.replace(src_path, dest_path) 85 | copyfile(srcfile, destfile) 86 | 87 | 88 | def main(project_dir, deploy_dir): 89 | """ 90 | :param project_dir: 项目的根目录 91 | :param deploy_dir: 部署二进制程序的目录 92 | :return: 93 | """ 94 | if not os.path.exists(deploy_dir): 95 | os.makedirs(deploy_dir) 96 | builddir = os.path.join(project_dir, "build") 97 | if not os.path.exists(builddir): 98 | os.makedirs(builddir) 99 | 100 | # 格式:起启文件路径,目标文件路径 101 | files = [ 102 | ("skynet/skynet", "skynet"), 103 | ("skynet/3rd/lua/lua", "skynet"), 104 | ("skynet/3rd/lua/luac", "skynet"), 105 | ("run.sh", ""), 106 | ] 107 | for path, destdir in files: 108 | src_path = os.path.join(project_dir, path) 109 | name = os.path.basename(path) 110 | dest_path = os.path.join(deploy_dir, os.path.join(destdir, name)) 111 | copyfile(src_path, dest_path) 112 | 113 | # 格式:起启路径,目标路径,文件后缀, 需要跳过的子目录; 支持递归 114 | dirs = [ 115 | ("skynet/lualib", "skynet/lualib", ".lua", None), 116 | ("skynet/service", "skynet/service", ".lua", None), 117 | ("skynet/luaclib", "skynet/luaclib", ".so", [".dSYM"]), 118 | ("skynet/cservice", "skynet/cservice", ".so", [".dSYM"]), 119 | ("luaclib", "luaclib", ".so", "*"), # 星号, 标示不遍历子目录 120 | ("service", "service", ".lua", None), 121 | ("lualib","lualib", ".lua", None), 122 | ("etc", "etc", '*', None), # 第2个星号,忽略文件后缀,复制所有的文件 123 | ] 124 | 125 | for srcdir, destdir, extension, exclude in dirs: 126 | copydir(project_dir, srcdir, deploy_dir, destdir, extension, exclude) 127 | 128 | if __name__ == "__main__": 129 | project_dir = sys.argv[1] 130 | deploy_dir = sys.argv[2] 131 | main(project_dir, deploy_dir) 132 | -------------------------------------------------------------------------------- /lualib-src/lru/lru_test.lua: -------------------------------------------------------------------------------- 1 | local lru = require("lru") 2 | local t = {} 3 | local lu = require("test.luaunit") 4 | 5 | function t.equal(val1, val2) 6 | lu.assertEquals(val1, val2) 7 | end 8 | 9 | function t.basic() 10 | local cache = lru.new(10, "string", "map") 11 | cache:set('key', 'value') 12 | 13 | t.equal(cache:get('key'), 'value') 14 | t.equal(cache:get('nada'), nil) 15 | 16 | cache = lru.new(10, "integer", "map") 17 | cache:set(5, 6) 18 | 19 | t.equal(cache:get(5), 6) 20 | t.equal(cache:get(1), nil) 21 | 22 | cache = lru.new(10, "string", "hashmap") 23 | cache:set('key', 'value') 24 | 25 | t.equal(cache:get('key'), 'value') 26 | t.equal(cache:get('nada'), nil) 27 | 28 | cache = lru.new(10, "integer", "hashmap") 29 | cache:set(5, 6) 30 | 31 | t.equal(cache:get(5), 6) 32 | t.equal(cache:get(1), nil) 33 | end 34 | 35 | function t.set_with_discard() 36 | local cache = lru.new(10, "integer", "map") 37 | local discard_times = 0 38 | 39 | -- container is not full. 40 | for i = 1, 10 do 41 | cache:set(i, i + 100, function(k, v) 42 | discard_times = discard_times + 1 43 | end) 44 | end 45 | t.equal(discard_times, 0) 46 | 47 | -- duplicate keys. 48 | for i = 1, 10 do 49 | cache:set(i, i + 100, function(k, v) 50 | discard_times = discard_times + 1 51 | end) 52 | end 53 | t.equal(discard_times, 0) 54 | 55 | -- container is full. 56 | for i = 1, 10 do 57 | cache:set(i + 10, i + 10 + 100, function(k, v) 58 | t.equal(k, i) 59 | t.equal(v, i + 100) 60 | 61 | discard_times = discard_times + 1 62 | end) 63 | end 64 | 65 | t.equal(discard_times, 10) 66 | 67 | -- callback error 68 | cache:set(1024, 1024, function(k, v) 69 | t.equal(k, 11) 70 | t.equal(v, 11 + 100) 71 | 72 | assert(false) 73 | end) 74 | t.equal(cache:get(1024), 1024) 75 | end 76 | 77 | function t.least_recently_set() 78 | local cache = lru.new(2, "string") 79 | cache:set('a', 'A') 80 | cache:set('b', 'B') 81 | cache:set('c', 'C') 82 | 83 | t.equal(cache:get('c'), 'C') 84 | t.equal(cache:get('b'), 'B') 85 | t.equal(cache:get('a'), nil) 86 | end 87 | 88 | function t.lru_recently_gotten() 89 | local cache = lru.new(2, "string") 90 | cache:set('a', 'A') 91 | cache:set('b', 'B') 92 | cache:get('a') 93 | cache:set('c', 'C') 94 | 95 | t.equal(cache:get('c'), 'C') 96 | t.equal(cache:get('b'), nil) 97 | t.equal(cache:get('a'), 'A') 98 | end 99 | 100 | function t.del() 101 | local cache = lru.new(2, "string") 102 | cache:set('a', 'A') 103 | cache:del('a') 104 | t.equal(cache:get('a'), nil) 105 | end 106 | 107 | function t.resize() 108 | local cache = lru.new(2, "integer") 109 | 110 | -- test changing the max, verify that the LRU items get dropped. 111 | cache:resize(100) 112 | for i = 0, 99 do 113 | cache:set(i, i) 114 | end 115 | 116 | t.equal(cache:count(), 100) 117 | 118 | for i = 0, 99 do 119 | t.equal(cache:get(i), i) 120 | end 121 | 122 | cache:resize(3) 123 | t.equal(cache:count(), 3) 124 | 125 | for i = 0, 96 do 126 | t.equal(cache:get(i), nil) 127 | end 128 | 129 | for i = 97, 99 do 130 | t.equal(cache:get(i), i) 131 | end 132 | 133 | cache:resize(20) 134 | for i = 0, 96 do 135 | t.equal(cache:get(i), nil) 136 | end 137 | 138 | for i = 97, 99 do 139 | t.equal(cache:get(i), i) 140 | end 141 | end 142 | 143 | function t.clear() 144 | local cache = lru.new(10, "string") 145 | cache:set('a', 'A') 146 | cache:set('b', 'B') 147 | cache:clear() 148 | t.equal(cache:count(), 0) 149 | t.equal(cache:get('a'), nil) 150 | t.equal(cache:get('b'), nil) 151 | end 152 | 153 | function t.delete_non_existent() 154 | local cache = lru.new(2, "string") 155 | cache:set('foo', 1) 156 | cache:set('bar', 2) 157 | cache:del('baz') 158 | t.equal(cache:get('foo'), 1) 159 | t.equal(cache:get('bar'), 2) 160 | end 161 | 162 | function t.next() 163 | local count = 128 164 | local cache = lru.new(count, "integer") 165 | local check = {} 166 | for i = 1, count * 2 do 167 | cache:set(i, 0) 168 | end 169 | for i = 10000, 10000 + count - 1 do 170 | cache:set(i, i) 171 | check[i] = i 172 | end 173 | local key = nil 174 | while true do 175 | local k, v = cache:next(key) 176 | if not k then 177 | break 178 | end 179 | t.equal(v, check[k]) 180 | check[k] = nil 181 | key = k 182 | end 183 | t.equal(next(check), nil) 184 | end 185 | 186 | function t.pairs() 187 | local count = 128 188 | local cache = lru.new(count, "integer") 189 | local check = {} 190 | for i = 1, count * 2 do 191 | cache:set(i, 0) 192 | end 193 | for i = 10000, 10000 + count - 1 do 194 | cache:set(i, i) 195 | check[i] = i 196 | end 197 | 198 | for k, v in pairs(cache) do 199 | t.equal(v, check[k]) 200 | check[k] = nil 201 | end 202 | t.equal(next(check), nil) 203 | end 204 | 205 | local function print_traceback(msg) 206 | print(debug.traceback(msg)) 207 | end 208 | 209 | for _, v in pairs(t) do 210 | local ret = xpcall(v, print_traceback) 211 | assert(ret) 212 | end -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | # 语言: None, Cpp, Java, JavaScript, ObjC, Proto, TableGen, TextProto 2 | Language: Cpp 3 | # BasedOnStyle: LLVM 4 | # 访问说明符(public、private等)的偏移 5 | AccessModifierOffset: -4 6 | # 开括号(开圆括号、开尖括号、开方括号)后的对齐: Align, DontAlign, AlwaysBreak(总是在开括号后换行) 7 | AlignAfterOpenBracket: Align 8 | # 连续赋值时,对齐所有等号 9 | AlignConsecutiveAssignments: false 10 | # 连续声明时,对齐所有声明的变量名 11 | AlignConsecutiveDeclarations: false 12 | # 左对齐逃脱换行(使用反斜杠换行)的反斜杠 13 | AlignEscapedNewlinesLeft: true 14 | # 水平对齐二元和三元表达式的操作数 15 | AlignOperands: false 16 | # 对齐连续的尾随的注释 17 | AlignTrailingComments: false 18 | # 允许函数声明的所有参数在放在下一行 19 | AllowAllParametersOfDeclarationOnNextLine: true 20 | # 允许短的块放在同一行 21 | AllowShortBlocksOnASingleLine: true 22 | # 允许短的case标签放在同一行 23 | AllowShortCaseLabelsOnASingleLine: false 24 | # 允许短的函数放在同一行: None, InlineOnly(定义在类中), Empty(空函数), Inline(定义在类中,空函数), All 25 | AllowShortFunctionsOnASingleLine: Empty 26 | # 允许短的if语句保持在同一行 27 | AllowShortIfStatementsOnASingleLine: false 28 | # 允许短的循环保持在同一行 29 | AllowShortLoopsOnASingleLine: true 30 | # 总是在定义返回类型后换行(deprecated) 31 | AlwaysBreakAfterDefinitionReturnType: None 32 | # 总是在返回类型后换行: None, All, TopLevel(顶级函数,不包括在类中的函数), 33 | # AllDefinitions(所有的定义,不包括声明), TopLevelDefinitions(所有的顶级函数的定义) 34 | AlwaysBreakAfterReturnType: None 35 | # 总是在多行string字面量前换行 36 | AlwaysBreakBeforeMultilineStrings: false 37 | # 总是在template声明后换行 38 | AlwaysBreakTemplateDeclarations: false 39 | # false表示函数实参要么都在同一行,要么都各自一行 40 | BinPackArguments: true 41 | # false表示所有形参要么都在同一行,要么都各自一行 42 | BinPackParameters: true 43 | # 大括号换行,只有当BreakBeforeBraces设置为Custom时才有效 44 | BraceWrapping: 45 | # class定义后面 46 | AfterClass: true 47 | # 控制语句后面 48 | AfterControlStatement: true 49 | # enum定义后面 50 | AfterEnum: true 51 | # 函数定义后面 52 | AfterFunction: true 53 | # 命名空间定义后面 54 | AfterNamespace: true 55 | # ObjC定义后面 56 | AfterObjCDeclaration: true 57 | # struct定义后面 58 | AfterStruct: true 59 | # union定义后面 60 | AfterUnion: true 61 | # catch之前 62 | BeforeCatch: true 63 | # else之前 64 | BeforeElse: true 65 | # 缩进大括号 66 | IndentBraces: false 67 | # 在二元运算符前换行: None(在操作符后换行), NonAssignment(在非赋值的操作符前换行), All(在操作符前换行) 68 | BreakBeforeBinaryOperators: NonAssignment 69 | # 在大括号前换行: Attach(始终将大括号附加到周围的上下文), Linux(除函数、命名空间和类定义,与Attach类似), 70 | # Mozilla(除枚举、函数、记录定义,与Attach类似), Stroustrup(除函数定义、catch、else,与Attach类似), 71 | # Allman(总是在大括号前换行), GNU(总是在大括号前换行,并对于控制语句的大括号增加额外的缩进), WebKit(在函数前换行), Custom 72 | # 注:这里认为语句块也属于函数 73 | BreakBeforeBraces: Custom 74 | # 在三元运算符前换行 75 | BreakBeforeTernaryOperators: true 76 | # 在构造函数的初始化列表的逗号前换行 77 | BreakConstructorInitializersBeforeComma: false 78 | # 每行字符的限制,0表示没有限制 79 | ColumnLimit: 0 80 | # 描述具有特殊意义的注释的正则表达式,它不应该被分割为多行或以其它方式改变 81 | CommentPragmas: '^ IWYU pragma:' 82 | # 构造函数的初始化列表要么都在同一行,要么都各自一行 83 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 84 | # 构造函数的初始化列表的缩进宽度 85 | ConstructorInitializerIndentWidth: 4 86 | # 延续的行的缩进宽度 87 | ContinuationIndentWidth: 4 88 | # 去除C++11的列表初始化的大括号{后和}前的空格 89 | Cpp11BracedListStyle: true 90 | # 继承最常用的指针和引用的对齐方式 91 | DerivePointerAlignment: false 92 | # 关闭格式化 93 | DisableFormat: false 94 | # 自动检测函数的调用和定义是否被格式为每行一个参数(Experimental) 95 | ExperimentalAutoDetectBinPacking: false 96 | # 需要被解读为foreach循环而不是函数调用的宏 97 | ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] 98 | # 对#include进行排序,匹配了某正则表达式的#include拥有对应的优先级,匹配不到的则默认优先级为INT_MAX(优先级越小排序越靠前), 99 | # 可以定义负数优先级从而保证某些#include永远在最前面 100 | IncludeCategories: 101 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 102 | Priority: 2 103 | - Regex: '^(<|"(gtest|isl|json)/)' 104 | Priority: 3 105 | - Regex: '.*' 106 | Priority: 1 107 | # 缩进case标签 108 | IndentCaseLabels: false 109 | # 缩进宽度 110 | IndentWidth: 4 111 | # 函数返回类型换行时,缩进函数声明或函数定义的函数名 112 | IndentWrappedFunctionNames: false 113 | # 保留在块开始处的空行 114 | KeepEmptyLinesAtTheStartOfBlocks: true 115 | # 开始一个块的宏的正则表达式 116 | MacroBlockBegin: '' 117 | # 结束一个块的宏的正则表达式 118 | MacroBlockEnd: '' 119 | # 连续空行的最大数量 120 | MaxEmptyLinesToKeep: 1 121 | # 命名空间的缩进: None, Inner(缩进嵌套的命名空间中的内容), All 122 | NamespaceIndentation: Inner 123 | # 使用ObjC块时缩进宽度 124 | ObjCBlockIndentWidth: 4 125 | # 在ObjC的@property后添加一个空格 126 | ObjCSpaceAfterProperty: false 127 | # 在ObjC的protocol列表前添加一个空格 128 | ObjCSpaceBeforeProtocolList: true 129 | # 在call(后对函数调用换行的penalty 130 | PenaltyBreakBeforeFirstCallParameter: 19 131 | # 在一个注释中引入换行的penalty 132 | PenaltyBreakComment: 300 133 | # 第一次在<<前换行的penalty 134 | PenaltyBreakFirstLessLess: 120 135 | # 在一个字符串字面量中引入换行的penalty 136 | PenaltyBreakString: 1000 137 | # 对于每个在行字符数限制之外的字符的penalty 138 | PenaltyExcessCharacter: 1000000 139 | # 将函数的返回类型放到它自己的行的penalty 140 | PenaltyReturnTypeOnItsOwnLine: 60 141 | # 指针和引用的对齐: Left, Right, Middle 142 | PointerAlignment: Left 143 | # 允许重新排版注释 144 | ReflowComments: true 145 | # 允许排序#include 146 | SortIncludes: true 147 | # 在C风格类型转换后添加空格 148 | SpaceAfterCStyleCast: false 149 | # 在赋值运算符之前添加空格 150 | SpaceBeforeAssignmentOperators: true 151 | # 开圆括号之前添加一个空格: Never, ControlStatements, Always 152 | SpaceBeforeParens: ControlStatements 153 | # 在空的圆括号中添加空格 154 | SpaceInEmptyParentheses: false 155 | # 在尾随的评论前添加的空格数(只适用于//) 156 | SpacesBeforeTrailingComments: 2 157 | # 在尖括号的<后和>前添加空格 158 | SpacesInAngles: false 159 | # 在容器(ObjC和JavaScript的数组和字典等)字面量中添加空格 160 | SpacesInContainerLiterals: false 161 | # 在C风格类型转换的括号中添加空格 162 | SpacesInCStyleCastParentheses: false 163 | # 在圆括号的(后和)前添加空格 164 | SpacesInParentheses: false 165 | # 在方括号的[后和]前添加空格,lamda表达式和未指明大小的数组的声明不受影响 166 | SpacesInSquareBrackets: false 167 | # 标准: Cpp03, Cpp11, Auto 168 | Standard: C++11 169 | # tab宽度 170 | TabWidth: 4 171 | # 使用tab字符: Never, ForIndentation, ForContinuationAndIndentation, Always 172 | UseTab: Never -------------------------------------------------------------------------------- /lualib-src/kcp/lkcp.c: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Copyright (C) 2015 by David Lin 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in 13 | * all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALING IN 21 | * THE SOFTWARE. 22 | * 23 | */ 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | #include 30 | #include 31 | 32 | #include "ikcp.h" 33 | 34 | #define RECV_BUFFER_LEN 4*1024*1024 35 | 36 | #define check_kcp(L, idx)\ 37 | *(ikcpcb**)luaL_checkudata(L, idx, "kcp_meta") 38 | 39 | #define check_buf(L, idx)\ 40 | (char*)luaL_checkudata(L, idx, "recv_buffer") 41 | 42 | struct Callback { 43 | uint64_t handle; 44 | lua_State* L; 45 | }; 46 | 47 | static int kcp_output_callback(const char *buf, int len, ikcpcb *kcp, void *arg) { 48 | struct Callback* c = (struct Callback*)arg; 49 | lua_State* L = c -> L; 50 | uint64_t handle = c -> handle; 51 | 52 | lua_rawgeti(L, LUA_REGISTRYINDEX, handle); 53 | lua_pushlstring(L, buf, len); 54 | lua_call(L, 1, 0); 55 | 56 | return 0; 57 | } 58 | 59 | static int kcp_gc(lua_State* L) { 60 | ikcpcb* kcp = check_kcp(L, 1); 61 | if (kcp == NULL) { 62 | return 0; 63 | } 64 | if (kcp->user != NULL) { 65 | struct Callback* c = (struct Callback*)kcp -> user; 66 | uint64_t handle = c -> handle; 67 | luaL_unref(L, LUA_REGISTRYINDEX, handle); 68 | free(c); 69 | kcp->user = NULL; 70 | } 71 | ikcp_release(kcp); 72 | kcp = NULL; 73 | return 0; 74 | } 75 | 76 | static int lkcp_create(lua_State* L){ 77 | uint64_t handle = luaL_ref(L, LUA_REGISTRYINDEX); 78 | int32_t conv = luaL_checkinteger(L, 1); 79 | 80 | struct Callback* c = malloc(sizeof(struct Callback)); 81 | memset(c, 0, sizeof(struct Callback)); 82 | c -> handle = handle; 83 | c -> L = L; 84 | 85 | ikcpcb* kcp = ikcp_create(conv, (void*)c); 86 | if (kcp == NULL) { 87 | lua_pushnil(L); 88 | lua_pushstring(L, "error: fail to create kcp"); 89 | return 2; 90 | } 91 | 92 | kcp->output = kcp_output_callback; 93 | 94 | *(ikcpcb**)lua_newuserdata(L, sizeof(void*)) = kcp; 95 | luaL_getmetatable(L, "kcp_meta"); 96 | lua_setmetatable(L, -2); 97 | return 1; 98 | } 99 | 100 | static int lkcp_recv(lua_State* L){ 101 | ikcpcb* kcp = check_kcp(L, 1); 102 | if (kcp == NULL) { 103 | lua_pushnil(L); 104 | lua_pushstring(L, "error: kcp not args"); 105 | return 2; 106 | } 107 | lua_getfield(L, LUA_REGISTRYINDEX, "kcp_lua_recv_buffer"); 108 | char* buf = check_buf(L, -1); 109 | lua_pop(L, 1); 110 | 111 | int32_t hr = ikcp_recv(kcp, buf, RECV_BUFFER_LEN); 112 | if (hr <= 0) { 113 | lua_pushinteger(L, hr); 114 | return 1; 115 | } 116 | 117 | lua_pushinteger(L, hr); 118 | lua_pushlstring(L, (const char *)buf, hr); 119 | 120 | return 2; 121 | } 122 | 123 | static int lkcp_send(lua_State* L){ 124 | ikcpcb* kcp = check_kcp(L, 1); 125 | if (kcp == NULL) { 126 | lua_pushnil(L); 127 | lua_pushstring(L, "error: kcp not args"); 128 | return 2; 129 | } 130 | size_t size; 131 | const char *data = luaL_checklstring(L, 2, &size); 132 | int32_t hr = ikcp_send(kcp, data, size); 133 | 134 | lua_pushinteger(L, hr); 135 | return 1; 136 | } 137 | 138 | static int lkcp_update(lua_State* L){ 139 | ikcpcb* kcp = check_kcp(L, 1); 140 | if (kcp == NULL) { 141 | lua_pushnil(L); 142 | lua_pushstring(L, "error: kcp not args"); 143 | return 2; 144 | } 145 | int32_t current = luaL_checkinteger(L, 2); 146 | ikcp_update(kcp, current); 147 | return 0; 148 | } 149 | 150 | static int lkcp_check(lua_State* L){ 151 | ikcpcb* kcp = check_kcp(L, 1); 152 | if (kcp == NULL) { 153 | lua_pushnil(L); 154 | lua_pushstring(L, "error: kcp not args"); 155 | return 2; 156 | } 157 | int32_t current = luaL_checkinteger(L, 2); 158 | int32_t hr = ikcp_check(kcp, current); 159 | lua_pushinteger(L, hr); 160 | return 1; 161 | } 162 | 163 | static int lkcp_input(lua_State* L){ 164 | ikcpcb* kcp = check_kcp(L, 1); 165 | if (kcp == NULL) { 166 | lua_pushnil(L); 167 | lua_pushstring(L, "error: kcp not args"); 168 | return 2; 169 | } 170 | size_t size; 171 | const char *data = luaL_checklstring(L, 2, &size); 172 | int32_t hr = ikcp_input(kcp, data, size); 173 | 174 | lua_pushinteger(L, hr); 175 | return 1; 176 | } 177 | 178 | static int lkcp_flush(lua_State* L){ 179 | ikcpcb* kcp = check_kcp(L, 1); 180 | if (kcp == NULL) { 181 | lua_pushnil(L); 182 | lua_pushstring(L, "error: kcp not args"); 183 | return 2; 184 | } 185 | ikcp_flush(kcp); 186 | return 0; 187 | } 188 | 189 | static int lkcp_wndsize(lua_State* L){ 190 | ikcpcb* kcp = check_kcp(L, 1); 191 | if (kcp == NULL) { 192 | lua_pushnil(L); 193 | lua_pushstring(L, "error: kcp not args"); 194 | return 2; 195 | } 196 | int32_t sndwnd = luaL_checkinteger(L, 2); 197 | int32_t rcvwnd = luaL_checkinteger(L, 3); 198 | ikcp_wndsize(kcp, sndwnd, rcvwnd); 199 | return 0; 200 | } 201 | 202 | static int lkcp_nodelay(lua_State* L){ 203 | ikcpcb* kcp = check_kcp(L, 1); 204 | if (kcp == NULL) { 205 | lua_pushnil(L); 206 | lua_pushstring(L, "error: kcp not args"); 207 | return 2; 208 | } 209 | int32_t nodelay = luaL_checkinteger(L, 2); 210 | int32_t interval = luaL_checkinteger(L, 3); 211 | int32_t resend = luaL_checkinteger(L, 4); 212 | int32_t nc = luaL_checkinteger(L, 5); 213 | int32_t hr = ikcp_nodelay(kcp, nodelay, interval, resend, nc); 214 | lua_pushinteger(L, hr); 215 | return 1; 216 | } 217 | 218 | 219 | static const struct luaL_Reg lkcp_methods [] = { 220 | { "lkcp_recv" , lkcp_recv }, 221 | { "lkcp_send" , lkcp_send }, 222 | { "lkcp_update" , lkcp_update }, 223 | { "lkcp_check" , lkcp_check }, 224 | { "lkcp_input" , lkcp_input }, 225 | { "lkcp_flush" , lkcp_flush }, 226 | { "lkcp_wndsize" , lkcp_wndsize }, 227 | { "lkcp_nodelay" , lkcp_nodelay }, 228 | {NULL, NULL}, 229 | }; 230 | 231 | static const struct luaL_Reg l_methods[] = { 232 | { "lkcp_create" , lkcp_create }, 233 | {NULL, NULL}, 234 | }; 235 | 236 | int luaopen_lkcp(lua_State* L) { 237 | luaL_checkversion(L); 238 | 239 | luaL_newmetatable(L, "kcp_meta"); 240 | 241 | lua_newtable(L); 242 | luaL_setfuncs(L, lkcp_methods, 0); 243 | lua_setfield(L, -2, "__index"); 244 | lua_pushcfunction(L, kcp_gc); 245 | lua_setfield(L, -2, "__gc"); 246 | 247 | luaL_newmetatable(L, "recv_buffer"); 248 | 249 | char* global_recv_buffer = lua_newuserdata(L, sizeof(char)*RECV_BUFFER_LEN); 250 | memset(global_recv_buffer, 0, sizeof(char)*RECV_BUFFER_LEN); 251 | luaL_getmetatable(L, "recv_buffer"); 252 | lua_setmetatable(L, -2); 253 | lua_setfield(L, LUA_REGISTRYINDEX, "kcp_lua_recv_buffer"); 254 | 255 | luaL_newlib(L, l_methods); 256 | 257 | return 1; 258 | } 259 | 260 | -------------------------------------------------------------------------------- /lualib/base/string.lua: -------------------------------------------------------------------------------- 1 | -------------------------------------- 2 | ----- 对标准库 string 的补充 3 | ------------------------------------- 4 | 5 | -- define module: string 6 | local string = string or {} 7 | 8 | -- load modules 9 | 10 | -- save original interfaces 11 | -- string._dump = string._dump or string.dump 12 | 13 | -- find the last substring with the given pattern 14 | function string:find_last(pattern, plain) 15 | 16 | -- find the last substring 17 | local curr = 0 18 | repeat 19 | local next = self:find(pattern, curr + 1, plain) 20 | if next then 21 | curr = next 22 | end 23 | until (not next) 24 | 25 | -- found? 26 | if curr > 0 then 27 | return curr 28 | end 29 | end 30 | 31 | -- split string with the given substring/characters 32 | -- 33 | -- pattern match and ignore empty string 34 | -- ("1\n\n2\n3"):split('\n') => 1, 2, 3 35 | -- ("abc123123xyz123abc"):split('123') => abc, xyz, abc 36 | -- ("abc123123xyz123abc"):split('[123]+') => abc, xyz, abc 37 | -- 38 | -- plain match and ignore empty string 39 | -- ("1\n\n2\n3"):split('\n', {plain = true}) => 1, 2, 3 40 | -- ("abc123123xyz123abc"):split('123', {plain = true}) => abc, xyz, abc 41 | -- 42 | -- pattern match and contains empty string 43 | -- ("1\n\n2\n3"):split('\n', {strict = true}) => 1, , 2, 3 44 | -- ("abc123123xyz123abc"):split('123', {strict = true}) => abc, , xyz, abc 45 | -- ("abc123123xyz123abc"):split('[123]+', {strict = true}) => abc, xyz, abc 46 | -- 47 | -- plain match and contains empty string 48 | -- ("1\n\n2\n3"):split('\n', {plain = true, strict = true}) => 1, , 2, 3 49 | -- ("abc123123xyz123abc"):split('123', {plain = true, strict = true}) => abc, , xyz, abc 50 | -- 51 | -- limit split count 52 | -- ("1\n\n2\n3"):split('\n', {limit = 2}) => 1, 2\n3 53 | -- ("1.2.3.4.5"):split('%.', {limit = 3}) => 1, 2, 3.4.5 54 | -- 55 | function string:split(delimiter, opt) 56 | local result = {} 57 | local start = 1 58 | local pos, epos = self:find(delimiter, start, opt and opt.plain) 59 | while pos do 60 | local substr = self:sub(start, pos - 1) 61 | if (#substr > 0) or (opt and opt.strict) then 62 | if opt and opt.limit and opt.limit > 0 and #result + 1 >= opt.limit then 63 | break 64 | end 65 | table.insert(result, substr) 66 | end 67 | start = epos + 1 68 | pos, epos = self:find(delimiter, start, opt and opt.plain) 69 | end 70 | if start <= #self then 71 | table.insert(result, self:sub(start)) 72 | end 73 | return result 74 | end 75 | 76 | -- Return a copy of the string with leading characters removed. 77 | function string:lstrip(chars) 78 | if self == nil then return nil end 79 | local pattern 80 | if chars == nil or chars == "" then 81 | pattern = '^%s+' 82 | else 83 | pattern = '^[' .. chars .. ']' 84 | end 85 | local s = self:gsub(pattern, '') 86 | return s 87 | end 88 | 89 | -- Return a copy of the string with trailing characters removed. 90 | function string:rstrip(chars) 91 | if self == nil then return nil end 92 | local pattern 93 | if chars == nil or chars == "" then 94 | pattern = '%s+$' 95 | else 96 | pattern = '[' .. chars .. ']$' 97 | end 98 | local s = self:gsub(pattern, '') 99 | return s 100 | end 101 | 102 | -- Return a copy of the string with the leading and trailing characters removed. 103 | -- The chars argument is a string specifying the set of characters to be removed. 104 | -- If omitted or None, the chars argument defaults to removing whitespace. 105 | -- The chars argument is not a prefix or suffix; rather, all combinations of its values are stripped. 106 | function string:strip(chars) 107 | return self:lstrip(chars):rstrip(chars) 108 | end 109 | 110 | -- encode: ' ', '=', '\"', '<' 111 | function string:encode() 112 | return (self:gsub("[%s=\"<]", function (w) return string.format("%%%x", w:byte()) end)) 113 | end 114 | 115 | -- decode: ' ', '=', '\"' 116 | function string:decode() 117 | return (self:gsub("%%(%x%x)", function (w) return string.char(tonumber(w, 16)) end)) 118 | end 119 | 120 | -- try to format 121 | function string.tryformat(format, ...) 122 | -- attempt to format it 123 | local ok, str = pcall(string.format, format, ...) 124 | if ok then 125 | return str 126 | else 127 | return tostring(format) 128 | end 129 | end 130 | 131 | -- case-insensitive pattern-matching 132 | -- 133 | -- print(("src/dadasd.C"):match(string.ipattern("sR[cd]/.*%.c", true))) 134 | -- print(("src/dadasd.C"):match(string.ipattern("src/.*%.c", true))) 135 | -- 136 | -- print(string.ipattern("sR[cd]/.*%.c")) 137 | -- [sS][rR][cd]/.*%.[cC] 138 | -- 139 | -- print(string.ipattern("sR[cd]/.*%.c", true)) 140 | -- [sS][rR][cCdD]/.*%.[cC] 141 | -- 142 | function string.ipattern(pattern, brackets) 143 | local tmp = {} 144 | local i = 1 145 | while i <= #pattern do 146 | -- get current charactor 147 | local char = pattern:sub(i, i) 148 | 149 | -- escape? 150 | if char == '%' then 151 | tmp[#tmp + 1] = char 152 | i = i + 1 153 | char = pattern:sub(i,i) 154 | tmp[#tmp + 1] = char 155 | 156 | -- '%bxy'? add next 2 chars 157 | if char == 'b' then 158 | tmp[#tmp + 1] = pattern:sub(i + 1, i + 2) 159 | i = i + 2 160 | end 161 | -- brackets? 162 | elseif char == '[' then 163 | tmp[#tmp + 1] = char 164 | i = i + 1 165 | while i <= #pattern do 166 | char = pattern:sub(i, i) 167 | if char == '%' then 168 | tmp[#tmp + 1] = char 169 | tmp[#tmp + 1] = pattern:sub(i + 1, i + 1) 170 | i = i + 1 171 | elseif char:match("%a") then 172 | tmp[#tmp + 1] = not brackets and char or char:lower() .. char:upper() 173 | else 174 | tmp[#tmp + 1] = char 175 | end 176 | if char == ']' then break end 177 | i = i + 1 178 | end 179 | -- letter, [aA] 180 | elseif char:match("%a") then 181 | tmp[#tmp + 1] = '[' .. char:lower() .. char:upper() .. ']' 182 | else 183 | tmp[#tmp + 1] = char 184 | end 185 | i = i + 1 186 | end 187 | return table.concat(tmp) 188 | end 189 | 190 | -- Return True if string starts with the prefix, otherwise return False. 191 | function string:startswith(text) 192 | local size = text:len() 193 | if self:sub(1, size) == text then 194 | return true 195 | end 196 | return false 197 | end 198 | 199 | -- Return True if the string ends with the specified suffix, otherwise return False. 200 | function string:endswith(text) 201 | return text == "" or self:sub(-#text) == text 202 | end 203 | 204 | 205 | -- 返回二进制数据 data 的十六进制表示形式 206 | -- 207 | function string.hexlify(data) 208 | return string.gsub(data, ".", 209 | function(x) return string.format("%02x", string.byte(x)) end) 210 | end 211 | 212 | string.b2a_hex = string.hexlify 213 | 214 | local function ascii_to_num(c) 215 | if (c >= string.byte("0") and c <= string.byte("9")) then 216 | return c - string.byte("0") 217 | elseif (c >= string.byte("A") and c <= string.byte("F")) then 218 | return (c - string.byte("A"))+10 219 | elseif (c >= string.byte("a") and c <= string.byte("f")) then 220 | return (c - string.byte("a"))+10 221 | else 222 | error "Wrong input for ascii to num convertion." 223 | end 224 | end 225 | 226 | -- 返回由十六进制字符串表示的二进制数据 227 | -- 此函数功能与 hexlify 相反 228 | function string.unhexlify(h) 229 | local s = "" 230 | for i = 1, #h, 2 do 231 | local high = ascii_to_num(string.byte(h,i)) 232 | local low = ascii_to_num(string.byte(h,i+1)) 233 | s = s .. string.char((high*16)+low) 234 | end 235 | return s 236 | end 237 | 238 | string.a2b_hex = string.unhexlify 239 | 240 | 241 | -- 242 | -- Capitalize the first letter of the string. 243 | -- 244 | 245 | function string.capitalize(s) 246 | return s:gsub("^%l", string.upper) 247 | end 248 | 249 | -- 250 | -- Returns true if the string has a match for the plain specified pattern 251 | -- 252 | function string.contains(s, match) 253 | return string.find(s, match, 1, true) ~= nil 254 | end 255 | 256 | 257 | -- 258 | -- Returns the number of lines of text contained by the string. 259 | -- 260 | function string.lines(s) 261 | local trailing, n = s:gsub('.-\n', '') 262 | if #trailing > 0 then 263 | n = n + 1 264 | end 265 | return n 266 | end 267 | 268 | 269 | -- return module: string 270 | return string 271 | -------------------------------------------------------------------------------- /thirdparty/lua-protobuf/serpent.lua: -------------------------------------------------------------------------------- 1 | local n, v = "serpent", "0.30" -- (C) 2012-17 Paul Kulchenko; MIT License 2 | local c, d = "Paul Kulchenko", "Lua serializer and pretty printer" 3 | local snum = {[tostring(1/0)]='1/0 --[[math.huge]]',[tostring(-1/0)]='-1/0 --[[-math.huge]]',[tostring(0/0)]='0/0'} 4 | local badtype = {thread = true, userdata = true, cdata = true} 5 | local getmetatable = debug and debug.getmetatable or getmetatable 6 | local pairs = function(t) return next, t end -- avoid using __pairs in Lua 5.2+ 7 | local keyword, globals, G = {}, {}, (_G or _ENV) 8 | for _,k in ipairs({'and', 'break', 'do', 'else', 'elseif', 'end', 'false', 9 | 'for', 'function', 'goto', 'if', 'in', 'local', 'nil', 'not', 'or', 'repeat', 10 | 'return', 'then', 'true', 'until', 'while'}) do keyword[k] = true end 11 | for k,v in pairs(G) do globals[v] = k end -- build func to name mapping 12 | for _,g in ipairs({'coroutine', 'debug', 'io', 'math', 'string', 'table', 'os'}) do 13 | for k,v in pairs(type(G[g]) == 'table' and G[g] or {}) do globals[v] = g..'.'..k end end 14 | 15 | local function s(t, opts) 16 | local name, indent, fatal, maxnum = opts.name, opts.indent, opts.fatal, opts.maxnum 17 | local sparse, custom, huge = opts.sparse, opts.custom, not opts.nohuge 18 | local space, maxl = (opts.compact and '' or ' '), (opts.maxlevel or math.huge) 19 | local maxlen, metatostring = tonumber(opts.maxlength), opts.metatostring 20 | local iname, comm = '_'..(name or ''), opts.comment and (tonumber(opts.comment) or math.huge) 21 | local numformat = opts.numformat or "%.17g" 22 | local seen, sref, syms, symn = {}, {'local '..iname..'={}'}, {}, 0 23 | local function gensym(val) return '_'..(tostring(tostring(val)):gsub("[^%w]",""):gsub("(%d%w+)", 24 | -- tostring(val) is needed because __tostring may return a non-string value 25 | function(s) if not syms[s] then symn = symn+1; syms[s] = symn end return tostring(syms[s]) end)) end 26 | local function safestr(s) return type(s) == "number" and tostring(huge and snum[tostring(s)] or numformat:format(s)) 27 | or type(s) ~= "string" and tostring(s) -- escape NEWLINE/010 and EOF/026 28 | or ("%q"):format(s):gsub("\010","n"):gsub("\026","\\026") end 29 | local function comment(s,l) return comm and (l or 0) < comm and ' --[['..select(2, pcall(tostring, s))..']]' or '' end 30 | local function globerr(s,l) return globals[s] and globals[s]..comment(s,l) or not fatal 31 | and safestr(select(2, pcall(tostring, s))) or error("Can't serialize "..tostring(s)) end 32 | local function safename(path, name) -- generates foo.bar, foo[3], or foo['b a r'] 33 | local n = name == nil and '' or name 34 | local plain = type(n) == "string" and n:match("^[%l%u_][%w_]*$") and not keyword[n] 35 | local safe = plain and n or '['..safestr(n)..']' 36 | return (path or '')..(plain and path and '.' or '')..safe, safe end 37 | local alphanumsort = type(opts.sortkeys) == 'function' and opts.sortkeys or function(k, o, n) -- k=keys, o=originaltable, n=padding 38 | local maxn, to = tonumber(n) or 12, {number = 'a', string = 'b'} 39 | local function padnum(d) return ("%0"..tostring(maxn).."d"):format(tonumber(d)) end 40 | table.sort(k, function(a,b) 41 | -- sort numeric keys first: k[key] is not nil for numerical keys 42 | return (k[a] ~= nil and 0 or to[type(a)] or 'z')..(tostring(a):gsub("%d+",padnum)) 43 | < (k[b] ~= nil and 0 or to[type(b)] or 'z')..(tostring(b):gsub("%d+",padnum)) end) end 44 | local function val2str(t, name, indent, insref, path, plainindex, level) 45 | local ttype, level, mt = type(t), (level or 0), getmetatable(t) 46 | local spath, sname = safename(path, name) 47 | local tag = plainindex and 48 | ((type(name) == "number") and '' or name..space..'='..space) or 49 | (name ~= nil and sname..space..'='..space or '') 50 | if seen[t] then -- already seen this element 51 | sref[#sref+1] = spath..space..'='..space..seen[t] 52 | return tag..'nil'..comment('ref', level) end 53 | -- protect from those cases where __tostring may fail 54 | if type(mt) == 'table' then 55 | local to, tr = pcall(function() return mt.__tostring(t) end) 56 | local so, sr = pcall(function() return mt.__serialize(t) end) 57 | if (opts.metatostring ~= false and to or so) then -- knows how to serialize itself 58 | seen[t] = insref or spath 59 | t = so and sr or tr 60 | ttype = type(t) 61 | end -- new value falls through to be serialized 62 | end 63 | if ttype == "table" then 64 | if level >= maxl then return tag..'{}'..comment('maxlvl', level) end 65 | seen[t] = insref or spath 66 | if next(t) == nil then return tag..'{}'..comment(t, level) end -- table empty 67 | if maxlen and maxlen < 0 then return tag..'{}'..comment('maxlen', level) end 68 | local maxn, o, out = math.min(#t, maxnum or #t), {}, {} 69 | for key = 1, maxn do o[key] = key end 70 | if not maxnum or #o < maxnum then 71 | local n = #o -- n = n + 1; o[n] is much faster than o[#o+1] on large tables 72 | for key in pairs(t) do if o[key] ~= key then n = n + 1; o[n] = key end end end 73 | if maxnum and #o > maxnum then o[maxnum+1] = nil end 74 | if opts.sortkeys and #o > maxn then alphanumsort(o, t, opts.sortkeys) end 75 | local sparse = sparse and #o > maxn -- disable sparsness if only numeric keys (shorter output) 76 | for n, key in ipairs(o) do 77 | local value, ktype, plainindex = t[key], type(key), n <= maxn and not sparse 78 | if opts.valignore and opts.valignore[value] -- skip ignored values; do nothing 79 | or opts.keyallow and not opts.keyallow[key] 80 | or opts.keyignore and opts.keyignore[key] 81 | or opts.valtypeignore and opts.valtypeignore[type(value)] -- skipping ignored value types 82 | or sparse and value == nil then -- skipping nils; do nothing 83 | elseif ktype == 'table' or ktype == 'function' or badtype[ktype] then 84 | if not seen[key] and not globals[key] then 85 | sref[#sref+1] = 'placeholder' 86 | local sname = safename(iname, gensym(key)) -- iname is table for local variables 87 | sref[#sref] = val2str(key,sname,indent,sname,iname,true) end 88 | sref[#sref+1] = 'placeholder' 89 | local path = seen[t]..'['..tostring(seen[key] or globals[key] or gensym(key))..']' 90 | sref[#sref] = path..space..'='..space..tostring(seen[value] or val2str(value,nil,indent,path)) 91 | else 92 | out[#out+1] = val2str(value,key,indent,insref,seen[t],plainindex,level+1) 93 | if maxlen then 94 | maxlen = maxlen - #out[#out] 95 | if maxlen < 0 then break end 96 | end 97 | end 98 | end 99 | local prefix = string.rep(indent or '', level) 100 | local head = indent and '{\n'..prefix..indent or '{' 101 | local body = table.concat(out, ','..(indent and '\n'..prefix..indent or space)) 102 | local tail = indent and "\n"..prefix..'}' or '}' 103 | return (custom and custom(tag,head,body,tail,level) or tag..head..body..tail)..comment(t, level) 104 | elseif badtype[ttype] then 105 | seen[t] = insref or spath 106 | return tag..globerr(t, level) 107 | elseif ttype == 'function' then 108 | seen[t] = insref or spath 109 | if opts.nocode then return tag.."function() --[[..skipped..]] end"..comment(t, level) end 110 | local ok, res = pcall(string.dump, t) 111 | local func = ok and "((loadstring or load)("..safestr(res)..",'@serialized'))"..comment(t, level) 112 | return tag..(func or globerr(t, level)) 113 | else return tag..safestr(t) end -- handle all other types 114 | end 115 | local sepr = indent and "\n" or ";"..space 116 | local body = val2str(t, name, indent) -- this call also populates sref 117 | local tail = #sref>1 and table.concat(sref, sepr)..sepr or '' 118 | local warn = opts.comment and #sref>1 and space.."--[[incomplete output with shared/self-references skipped]]" or '' 119 | return not name and body..warn or "do local "..body..sepr..tail.."return "..name..sepr.."end" 120 | end 121 | 122 | local function deserialize(data, opts) 123 | local env = (opts and opts.safe == false) and G 124 | or setmetatable({}, { 125 | __index = function(t,k) return t end, 126 | __call = function(t,...) error("cannot call functions") end 127 | }) 128 | local f, res = (loadstring or load)('return '..data, nil, nil, env) 129 | if not f then f, res = (loadstring or load)(data, nil, nil, env) end 130 | if not f then return f, res end 131 | if setfenv then setfenv(f, env) end 132 | return pcall(f) 133 | end 134 | 135 | local function merge(a, b) if b then for k,v in pairs(b) do a[k] = v end end; return a; end 136 | return { _NAME = n, _COPYRIGHT = c, _DESCRIPTION = d, _VERSION = v, serialize = s, 137 | load = deserialize, 138 | dump = function(a, opts) return s(a, merge({name = '_', compact = true, sparse = true}, opts)) end, 139 | line = function(a, opts) return s(a, merge({sortkeys = true, comment = true}, opts)) end, 140 | block = function(a, opts) return s(a, merge({indent = ' ', sortkeys = true, comment = true}, opts)) end } 141 | -------------------------------------------------------------------------------- /lualib-src/lru/lua-lru.cc: -------------------------------------------------------------------------------- 1 | #include "lru.h" 2 | #include "lua.hpp" 3 | #include 4 | #include 5 | #include 6 | 7 | #define LUA_LRU_MT_INT_MAP ("hive-framework.lruMT.int.map") 8 | #define LUA_LRU_MT_INT_HASHMAP ("hive-framework.lruMT.int.hashmap") 9 | #define LUA_LRU_MT_STRING_MAP ("hive-framework.lruMT.string.map") 10 | #define LUA_LRU_MT_STRING_HASHMAP ("hive-framework.lruMT.string.hashmap") 11 | 12 | #define LUA_LRU_CORE ("__lru__") 13 | #define LUA_LRU_DATA ("__data__") 14 | #define lru_t lru 15 | 16 | template class MapT> 17 | static int lua_tolru(lua_State* L, int idx, lru_t** ret_container, int* ret_data_idx) { 18 | if (!lua_istable(L, idx)) 19 | return false; 20 | 21 | lua_getfield(L, idx, LUA_LRU_CORE); 22 | lru_t* container = (lru_t*)lua_touserdata(L, -1); 23 | if (!container) 24 | return false; 25 | 26 | lua_getfield(L, 1, LUA_LRU_DATA); 27 | if (!lua_istable(L, -1)) 28 | return false; 29 | 30 | *ret_container = container; 31 | *ret_data_idx = lua_gettop(L); 32 | return true; 33 | } 34 | 35 | static int lru_tokey(lua_State* L, int idx, int64_t* key) { 36 | if (!lua_isinteger(L, idx)) 37 | return false; 38 | *key = lua_tointeger(L, idx); 39 | return true; 40 | } 41 | 42 | static int lru_tokey(lua_State* L, int idx, std::string* key) { 43 | if (!lua_isstring(L, idx)) 44 | return false; 45 | *key = lua_tostring(L, idx); 46 | return true; 47 | } 48 | 49 | static void lru_pushkey(lua_State* L, const int64_t& key) { 50 | lua_pushinteger(L, key); 51 | } 52 | 53 | static void lru_pushkey(lua_State* L, const std::string& key) { 54 | lua_pushstring(L, key.c_str()); 55 | } 56 | 57 | template class MapT> 58 | static int lru_gc(lua_State* L) { 59 | lru_t* container = nullptr; 60 | int data_idx = 0; 61 | if (!lua_tolru(L, 1, &container, &data_idx)) 62 | return luaL_argerror(L, 1, "expect lru object"); 63 | 64 | delete container; 65 | return 0; 66 | } 67 | 68 | template class MapT> 69 | static int lru_set(lua_State* L) { 70 | T key; 71 | if (!lru_tokey(L, 2, &key)) 72 | return luaL_argerror(L, 2, "invalid argument type"); 73 | 74 | if (lua_isnil(L, 3)) 75 | return luaL_argerror(L, 2, "value cannot be nil"); 76 | 77 | int discard_callback_exist = lua_isfunction(L, 4); 78 | 79 | lru_t* container = nullptr; 80 | int data_idx = 0; 81 | if (!lua_tolru(L, 1, &container, &data_idx)) 82 | return luaL_argerror(L, 1, "expect lru object"); 83 | 84 | int index; 85 | if (!discard_callback_exist) { 86 | index = container->set(key, nullptr); 87 | assert(index > 0); 88 | } else { 89 | index = container->set(key, [L, data_idx](int discard_index, const T& discard_key){ 90 | lua_pushvalue(L, 4); 91 | lru_pushkey(L, discard_key); 92 | lua_rawgeti(L, data_idx, discard_index); 93 | lua_pcall(L, 2, 0, 0); 94 | }); 95 | assert(index > 0); 96 | } 97 | 98 | lua_pushvalue(L, 3); 99 | lua_rawseti(L, data_idx, index); 100 | return 0; 101 | } 102 | 103 | template class MapT> 104 | static int lru_del(lua_State* L) { 105 | T key; 106 | if (!lru_tokey(L, 2, &key)) 107 | return luaL_argerror(L, 2, "invalid argument type"); 108 | 109 | lru_t* container = nullptr; 110 | int data_idx = 0; 111 | if (!lua_tolru(L, 1, &container, &data_idx)) 112 | return luaL_argerror(L, 1, "expect lru object"); 113 | 114 | int index = container->del(key); 115 | if (index > 0) { 116 | lua_pushnil(L); 117 | lua_rawseti(L, data_idx, index); 118 | } 119 | return 0; 120 | } 121 | 122 | template class MapT> 123 | static int lru_get(lua_State* L) { 124 | T key; 125 | if (!lru_tokey(L, 2, &key)) 126 | return luaL_argerror(L, 1, "invalid argument type"); 127 | 128 | lru_t* container = nullptr; 129 | int data_idx = 0; 130 | if (!lua_tolru(L, 1, &container, &data_idx)) 131 | return luaL_argerror(L, 1, "expect lru object"); 132 | 133 | int index = container->get(key); 134 | if (index == 0) 135 | return 0; 136 | 137 | lua_rawgeti(L, data_idx, index); 138 | return 1; 139 | } 140 | 141 | template class MapT> 142 | static int lru_count(lua_State* L) { 143 | lru_t* container = nullptr; 144 | int data_idx = 0; 145 | if (!lua_tolru(L, 1, &container, &data_idx)) 146 | return luaL_argerror(L, 1, "expect lru object"); 147 | 148 | int count = container->count(); 149 | lua_pushinteger(L, count); 150 | return 1; 151 | } 152 | 153 | template class MapT> 154 | static int lru_next(lua_State* L) { 155 | int key_isnil = lua_isnil(L, 2); 156 | lru_t* container = nullptr; 157 | int data_idx = 0; 158 | if (!lua_tolru(L, 1, &container, &data_idx)) 159 | return luaL_argerror(L, 1, "expect lru object"); 160 | 161 | int index = 0; 162 | T key; 163 | if (key_isnil) { 164 | index = container->first(key); 165 | } else { 166 | if (!lru_tokey(L, 2, &key)) 167 | return luaL_argerror(L, 1, "invalid argument type"); 168 | index = container->next(key); 169 | } 170 | 171 | if (index == 0) 172 | return 0; 173 | 174 | lru_pushkey(L, key); 175 | lua_rawgeti(L, data_idx, index); 176 | return 2; 177 | } 178 | 179 | template class MapT> 180 | static int lru_pairs(lua_State *L) { 181 | luaL_checktype(L, 1, LUA_TTABLE); 182 | lua_pushcclosure(L, lru_next, 0); 183 | lua_pushvalue(L, 1); 184 | lua_pushnil(L); 185 | return 3; 186 | } 187 | 188 | template class MapT> 189 | static int lru_resize(lua_State* L) { 190 | int size = (int)lua_tointeger(L, 2); 191 | if (size <= 0) 192 | return luaL_argerror(L, 1, "expect size > 0"); 193 | 194 | lru_t* container = nullptr; 195 | int data_idx = 0; 196 | if (!lua_tolru(L, 1, &container, &data_idx)) 197 | return luaL_argerror(L, 1, "expect lru object"); 198 | 199 | std::vector remove_list; 200 | container->resize(remove_list, size); 201 | 202 | for (auto& index : remove_list) { 203 | lua_pushnil(L); 204 | lua_rawseti(L, data_idx, index); 205 | } 206 | return 2; 207 | } 208 | 209 | template class MapT> 210 | static int lru_clear(lua_State* L) { 211 | lru_t* container = nullptr; 212 | int data_idx = 0; 213 | if (!lua_tolru(L, 1, &container, &data_idx)) 214 | return luaL_argerror(L, 1, "expect lru object"); 215 | 216 | container->clear(); 217 | 218 | lua_pushnil(L); 219 | while (lua_next(L, data_idx)) { 220 | int index = (int)lua_tointeger(L, -2); 221 | lua_pushnil(L); 222 | lua_rawseti(L, data_idx, index); 223 | lua_pop(L, 1); 224 | } 225 | return 0; 226 | } 227 | 228 | static int create_lru_container(lua_State* L, int type_idx, int map_idx, int size, void** container, const char** tname) { 229 | const char* type = lua_tostring(L, type_idx); 230 | const char* map = lua_tostring(L, map_idx); 231 | if (!map) 232 | map = "map"; 233 | 234 | if (strcmp(type, "integer") == 0 && strcmp(map, "map") == 0) { 235 | *container = new lru(size); 236 | *tname = LUA_LRU_MT_INT_MAP; 237 | return true; 238 | } 239 | 240 | if (strcmp(type, "integer") == 0 && strcmp(map, "hashmap") == 0) { 241 | *container = new lru(size); 242 | *tname = LUA_LRU_MT_INT_HASHMAP; 243 | return true; 244 | } 245 | 246 | if (strcmp(type, "string") == 0 && strcmp(map, "map") == 0) { 247 | *container = new lru(size); 248 | *tname = LUA_LRU_MT_STRING_MAP; 249 | return true; 250 | } 251 | 252 | if (strcmp(type, "string") == 0 && strcmp(map, "hashmap") == 0) { 253 | *container = new lru(size); 254 | *tname = LUA_LRU_MT_STRING_HASHMAP; 255 | return true; 256 | } 257 | 258 | return false; 259 | } 260 | 261 | static int new_lru(lua_State* L) { 262 | int size = (int)lua_tointeger(L, 1); 263 | if (size <= 0) 264 | return luaL_argerror(L, 1, "expect size > 0"); 265 | 266 | void* container = nullptr; 267 | const char* tname = nullptr; 268 | 269 | if (!create_lru_container(L, 2, 3, size, &container, &tname)) 270 | return luaL_argerror(L, 2, "parameter invalid"); 271 | 272 | lua_newtable(L); 273 | 274 | lua_createtable(L, size, 0); 275 | lua_setfield(L, -2, LUA_LRU_DATA); 276 | 277 | lua_pushlightuserdata(L, container); 278 | lua_setfield(L, -2, LUA_LRU_CORE); 279 | 280 | luaL_getmetatable(L, tname); 281 | lua_setmetatable(L, -2); 282 | 283 | return 1; 284 | } 285 | 286 | template class MapT> 287 | static int lru_newmetatable(lua_State* L, const char* tname) { 288 | if (!luaL_newmetatable(L, tname)) 289 | return false; 290 | 291 | luaL_Reg l[] = { 292 | { "__gc", lru_gc }, 293 | { "__len", lru_count }, 294 | { "__pairs", lru_pairs }, 295 | { "get", lru_get }, 296 | { "set", lru_set }, 297 | { "del", lru_del }, 298 | { "count", lru_count }, 299 | { "next", lru_next }, 300 | { "resize", lru_resize }, 301 | { "clear", lru_clear }, 302 | 303 | { NULL, NULL }, 304 | }; 305 | 306 | lua_pushvalue(L, -1); 307 | lua_setfield(L, -2, "__index"); 308 | luaL_setfuncs(L, l, 0); 309 | lua_pop(L, 1); 310 | 311 | return true; 312 | } 313 | 314 | extern "C" int luaopen_lru(lua_State* L) { 315 | luaL_checkversion(L); 316 | 317 | lru_newmetatable(L, LUA_LRU_MT_INT_MAP); 318 | lru_newmetatable(L, LUA_LRU_MT_INT_HASHMAP); 319 | lru_newmetatable(L, LUA_LRU_MT_STRING_MAP); 320 | lru_newmetatable(L, LUA_LRU_MT_STRING_HASHMAP); 321 | 322 | luaL_Reg l[] = { 323 | { "new", new_lru }, 324 | { NULL, NULL }, 325 | }; 326 | luaL_newlib(L, l); 327 | return 1; 328 | } 329 | 330 | 331 | 332 | 333 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /lualib-src/kcp/ikcp.h: -------------------------------------------------------------------------------- 1 | //===================================================================== 2 | // 3 | // KCP - A Better ARQ Protocol Implementation 4 | // skywind3000 (at) gmail.com, 2010-2011 5 | // 6 | // Features: 7 | // + Average RTT reduce 30% - 40% vs traditional ARQ like tcp. 8 | // + Maximum RTT reduce three times vs tcp. 9 | // + Lightweight, distributed as a single source file. 10 | // 11 | //===================================================================== 12 | #ifndef __IKCP_H__ 13 | #define __IKCP_H__ 14 | 15 | #include 16 | #include 17 | #include 18 | 19 | 20 | //===================================================================== 21 | // 32BIT INTEGER DEFINITION 22 | //===================================================================== 23 | #ifndef __INTEGER_32_BITS__ 24 | #define __INTEGER_32_BITS__ 25 | #if defined(_WIN64) || defined(WIN64) || defined(__amd64__) || \ 26 | defined(__x86_64) || defined(__x86_64__) || defined(_M_IA64) || \ 27 | defined(_M_AMD64) 28 | typedef unsigned int ISTDUINT32; 29 | typedef int ISTDINT32; 30 | #elif defined(_WIN32) || defined(WIN32) || defined(__i386__) || \ 31 | defined(__i386) || defined(_M_X86) 32 | typedef unsigned long ISTDUINT32; 33 | typedef long ISTDINT32; 34 | #elif defined(__MACOS__) 35 | typedef UInt32 ISTDUINT32; 36 | typedef SInt32 ISTDINT32; 37 | #elif defined(__APPLE__) && defined(__MACH__) 38 | #include 39 | typedef u_int32_t ISTDUINT32; 40 | typedef int32_t ISTDINT32; 41 | #elif defined(__BEOS__) 42 | #include 43 | typedef u_int32_t ISTDUINT32; 44 | typedef int32_t ISTDINT32; 45 | #elif (defined(_MSC_VER) || defined(__BORLANDC__)) && (!defined(__MSDOS__)) 46 | typedef unsigned __int32 ISTDUINT32; 47 | typedef __int32 ISTDINT32; 48 | #elif defined(__GNUC__) 49 | #include 50 | typedef uint32_t ISTDUINT32; 51 | typedef int32_t ISTDINT32; 52 | #else 53 | typedef unsigned long ISTDUINT32; 54 | typedef long ISTDINT32; 55 | #endif 56 | #endif 57 | 58 | 59 | //===================================================================== 60 | // Integer Definition 61 | //===================================================================== 62 | #ifndef __IINT8_DEFINED 63 | #define __IINT8_DEFINED 64 | typedef char IINT8; 65 | #endif 66 | 67 | #ifndef __IUINT8_DEFINED 68 | #define __IUINT8_DEFINED 69 | typedef unsigned char IUINT8; 70 | #endif 71 | 72 | #ifndef __IUINT16_DEFINED 73 | #define __IUINT16_DEFINED 74 | typedef unsigned short IUINT16; 75 | #endif 76 | 77 | #ifndef __IINT16_DEFINED 78 | #define __IINT16_DEFINED 79 | typedef short IINT16; 80 | #endif 81 | 82 | #ifndef __IINT32_DEFINED 83 | #define __IINT32_DEFINED 84 | typedef ISTDINT32 IINT32; 85 | #endif 86 | 87 | #ifndef __IUINT32_DEFINED 88 | #define __IUINT32_DEFINED 89 | typedef ISTDUINT32 IUINT32; 90 | #endif 91 | 92 | #ifndef __IINT64_DEFINED 93 | #define __IINT64_DEFINED 94 | #if defined(_MSC_VER) || defined(__BORLANDC__) 95 | typedef __int64 IINT64; 96 | #else 97 | typedef long long IINT64; 98 | #endif 99 | #endif 100 | 101 | #ifndef __IUINT64_DEFINED 102 | #define __IUINT64_DEFINED 103 | #if defined(_MSC_VER) || defined(__BORLANDC__) 104 | typedef unsigned __int64 IUINT64; 105 | #else 106 | typedef unsigned long long IUINT64; 107 | #endif 108 | #endif 109 | 110 | #ifndef INLINE 111 | #if defined(__GNUC__) 112 | 113 | #if (__GNUC__ > 3) || ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 1)) 114 | #define INLINE __inline__ __attribute__((always_inline)) 115 | #else 116 | #define INLINE __inline__ 117 | #endif 118 | 119 | #elif (defined(_MSC_VER) || defined(__BORLANDC__) || defined(__WATCOMC__)) 120 | #define INLINE __inline 121 | #else 122 | #define INLINE 123 | #endif 124 | #endif 125 | 126 | #if (!defined(__cplusplus)) && (!defined(inline)) 127 | #define inline INLINE 128 | #endif 129 | 130 | 131 | //===================================================================== 132 | // QUEUE DEFINITION 133 | //===================================================================== 134 | #ifndef __IQUEUE_DEF__ 135 | #define __IQUEUE_DEF__ 136 | 137 | struct IQUEUEHEAD { 138 | struct IQUEUEHEAD *next, *prev; 139 | }; 140 | 141 | typedef struct IQUEUEHEAD iqueue_head; 142 | 143 | 144 | //--------------------------------------------------------------------- 145 | // queue init 146 | //--------------------------------------------------------------------- 147 | #define IQUEUE_HEAD_INIT(name) { &(name), &(name) } 148 | #define IQUEUE_HEAD(name) \ 149 | struct IQUEUEHEAD name = IQUEUE_HEAD_INIT(name) 150 | 151 | #define IQUEUE_INIT(ptr) ( \ 152 | (ptr)->next = (ptr), (ptr)->prev = (ptr)) 153 | 154 | #define IOFFSETOF(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) 155 | 156 | #define ICONTAINEROF(ptr, type, member) ( \ 157 | (type*)( ((char*)((type*)ptr)) - IOFFSETOF(type, member)) ) 158 | 159 | #define IQUEUE_ENTRY(ptr, type, member) ICONTAINEROF(ptr, type, member) 160 | 161 | 162 | //--------------------------------------------------------------------- 163 | // queue operation 164 | //--------------------------------------------------------------------- 165 | #define IQUEUE_ADD(node, head) ( \ 166 | (node)->prev = (head), (node)->next = (head)->next, \ 167 | (head)->next->prev = (node), (head)->next = (node)) 168 | 169 | #define IQUEUE_ADD_TAIL(node, head) ( \ 170 | (node)->prev = (head)->prev, (node)->next = (head), \ 171 | (head)->prev->next = (node), (head)->prev = (node)) 172 | 173 | #define IQUEUE_DEL_BETWEEN(p, n) ((n)->prev = (p), (p)->next = (n)) 174 | 175 | #define IQUEUE_DEL(entry) (\ 176 | (entry)->next->prev = (entry)->prev, \ 177 | (entry)->prev->next = (entry)->next, \ 178 | (entry)->next = 0, (entry)->prev = 0) 179 | 180 | #define IQUEUE_DEL_INIT(entry) do { \ 181 | IQUEUE_DEL(entry); IQUEUE_INIT(entry); } while (0) 182 | 183 | #define IQUEUE_IS_EMPTY(entry) ((entry) == (entry)->next) 184 | 185 | #define iqueue_init IQUEUE_INIT 186 | #define iqueue_entry IQUEUE_ENTRY 187 | #define iqueue_add IQUEUE_ADD 188 | #define iqueue_add_tail IQUEUE_ADD_TAIL 189 | #define iqueue_del IQUEUE_DEL 190 | #define iqueue_del_init IQUEUE_DEL_INIT 191 | #define iqueue_is_empty IQUEUE_IS_EMPTY 192 | 193 | #define IQUEUE_FOREACH(iterator, head, TYPE, MEMBER) \ 194 | for ((iterator) = iqueue_entry((head)->next, TYPE, MEMBER); \ 195 | &((iterator)->MEMBER) != (head); \ 196 | (iterator) = iqueue_entry((iterator)->MEMBER.next, TYPE, MEMBER)) 197 | 198 | #define iqueue_foreach(iterator, head, TYPE, MEMBER) \ 199 | IQUEUE_FOREACH(iterator, head, TYPE, MEMBER) 200 | 201 | #define iqueue_foreach_entry(pos, head) \ 202 | for( (pos) = (head)->next; (pos) != (head) ; (pos) = (pos)->next ) 203 | 204 | 205 | #define __iqueue_splice(list, head) do { \ 206 | iqueue_head *first = (list)->next, *last = (list)->prev; \ 207 | iqueue_head *at = (head)->next; \ 208 | (first)->prev = (head), (head)->next = (first); \ 209 | (last)->next = (at), (at)->prev = (last); } while (0) 210 | 211 | #define iqueue_splice(list, head) do { \ 212 | if (!iqueue_is_empty(list)) __iqueue_splice(list, head); } while (0) 213 | 214 | #define iqueue_splice_init(list, head) do { \ 215 | iqueue_splice(list, head); iqueue_init(list); } while (0) 216 | 217 | 218 | #ifdef _MSC_VER 219 | #pragma warning(disable:4311) 220 | #pragma warning(disable:4312) 221 | #pragma warning(disable:4996) 222 | #endif 223 | 224 | #endif 225 | 226 | 227 | //--------------------------------------------------------------------- 228 | // BYTE ORDER & ALIGNMENT 229 | //--------------------------------------------------------------------- 230 | #ifndef IWORDS_BIG_ENDIAN 231 | #ifdef _BIG_ENDIAN_ 232 | #if _BIG_ENDIAN_ 233 | #define IWORDS_BIG_ENDIAN 1 234 | #endif 235 | #endif 236 | #ifndef IWORDS_BIG_ENDIAN 237 | #if defined(__hppa__) || \ 238 | defined(__m68k__) || defined(mc68000) || defined(_M_M68K) || \ 239 | (defined(__MIPS__) && defined(__MIPSEB__)) || \ 240 | defined(__ppc__) || defined(__POWERPC__) || defined(_M_PPC) || \ 241 | defined(__sparc__) || defined(__powerpc__) || \ 242 | defined(__mc68000__) || defined(__s390x__) || defined(__s390__) 243 | #define IWORDS_BIG_ENDIAN 1 244 | #endif 245 | #endif 246 | #ifndef IWORDS_BIG_ENDIAN 247 | #define IWORDS_BIG_ENDIAN 0 248 | #endif 249 | #endif 250 | 251 | #ifndef IWORDS_MUST_ALIGN 252 | #if defined(__i386__) || defined(__i386) || defined(_i386_) 253 | #define IWORDS_MUST_ALIGN 0 254 | #elif defined(_M_IX86) || defined(_X86_) || defined(__x86_64__) 255 | #define IWORDS_MUST_ALIGN 0 256 | #elif defined(__amd64) || defined(__amd64__) 257 | #define IWORDS_MUST_ALIGN 0 258 | #else 259 | #define IWORDS_MUST_ALIGN 1 260 | #endif 261 | #endif 262 | 263 | 264 | //===================================================================== 265 | // SEGMENT 266 | //===================================================================== 267 | struct IKCPSEG 268 | { 269 | struct IQUEUEHEAD node; 270 | IUINT32 conv; 271 | IUINT32 cmd; 272 | IUINT32 frg; 273 | IUINT32 wnd; 274 | IUINT32 ts; 275 | IUINT32 sn; 276 | IUINT32 una; 277 | IUINT32 len; 278 | IUINT32 resendts; 279 | IUINT32 rto; 280 | IUINT32 fastack; 281 | IUINT32 xmit; 282 | char data[1]; 283 | }; 284 | 285 | 286 | //--------------------------------------------------------------------- 287 | // IKCPCB 288 | //--------------------------------------------------------------------- 289 | struct IKCPCB 290 | { 291 | IUINT32 conv, mtu, mss, state; 292 | IUINT32 snd_una, snd_nxt, rcv_nxt; 293 | IUINT32 ts_recent, ts_lastack, ssthresh; 294 | IINT32 rx_rttval, rx_srtt, rx_rto, rx_minrto; 295 | IUINT32 snd_wnd, rcv_wnd, rmt_wnd, cwnd, probe; 296 | IUINT32 current, interval, ts_flush, xmit; 297 | IUINT32 nrcv_buf, nsnd_buf; 298 | IUINT32 nrcv_que, nsnd_que; 299 | IUINT32 nodelay, updated; 300 | IUINT32 ts_probe, probe_wait; 301 | IUINT32 dead_link, incr; 302 | struct IQUEUEHEAD snd_queue; 303 | struct IQUEUEHEAD rcv_queue; 304 | struct IQUEUEHEAD snd_buf; 305 | struct IQUEUEHEAD rcv_buf; 306 | IUINT32 *acklist; 307 | IUINT32 ackcount; 308 | IUINT32 ackblock; 309 | void *user; 310 | char *buffer; 311 | int fastresend; 312 | int fastlimit; 313 | int nocwnd, stream; 314 | int logmask; 315 | int (*output)(const char *buf, int len, struct IKCPCB *kcp, void *user); 316 | void (*writelog)(const char *log, struct IKCPCB *kcp, void *user); 317 | }; 318 | 319 | 320 | typedef struct IKCPCB ikcpcb; 321 | 322 | #define IKCP_LOG_OUTPUT 1 323 | #define IKCP_LOG_INPUT 2 324 | #define IKCP_LOG_SEND 4 325 | #define IKCP_LOG_RECV 8 326 | #define IKCP_LOG_IN_DATA 16 327 | #define IKCP_LOG_IN_ACK 32 328 | #define IKCP_LOG_IN_PROBE 64 329 | #define IKCP_LOG_IN_WINS 128 330 | #define IKCP_LOG_OUT_DATA 256 331 | #define IKCP_LOG_OUT_ACK 512 332 | #define IKCP_LOG_OUT_PROBE 1024 333 | #define IKCP_LOG_OUT_WINS 2048 334 | 335 | #ifdef __cplusplus 336 | extern "C" { 337 | #endif 338 | 339 | //--------------------------------------------------------------------- 340 | // interface 341 | //--------------------------------------------------------------------- 342 | 343 | // create a new kcp control object, 'conv' must equal in two endpoint 344 | // from the same connection. 'user' will be passed to the output callback 345 | // output callback can be setup like this: 'kcp->output = my_udp_output' 346 | ikcpcb* ikcp_create(IUINT32 conv, void *user); 347 | 348 | // release kcp control object 349 | void ikcp_release(ikcpcb *kcp); 350 | 351 | // set output callback, which will be invoked by kcp 352 | void ikcp_setoutput(ikcpcb *kcp, int (*output)(const char *buf, int len, 353 | ikcpcb *kcp, void *user)); 354 | 355 | // user/upper level recv: returns size, returns below zero for EAGAIN 356 | int ikcp_recv(ikcpcb *kcp, char *buffer, int len); 357 | 358 | // user/upper level send, returns below zero for error 359 | int ikcp_send(ikcpcb *kcp, const char *buffer, int len); 360 | 361 | // update state (call it repeatedly, every 10ms-100ms), or you can ask 362 | // ikcp_check when to call it again (without ikcp_input/_send calling). 363 | // 'current' - current timestamp in millisec. 364 | void ikcp_update(ikcpcb *kcp, IUINT32 current); 365 | 366 | // Determine when should you invoke ikcp_update: 367 | // returns when you should invoke ikcp_update in millisec, if there 368 | // is no ikcp_input/_send calling. you can call ikcp_update in that 369 | // time, instead of call update repeatly. 370 | // Important to reduce unnacessary ikcp_update invoking. use it to 371 | // schedule ikcp_update (eg. implementing an epoll-like mechanism, 372 | // or optimize ikcp_update when handling massive kcp connections) 373 | IUINT32 ikcp_check(const ikcpcb *kcp, IUINT32 current); 374 | 375 | // when you received a low level packet (eg. UDP packet), call it 376 | int ikcp_input(ikcpcb *kcp, const char *data, long size); 377 | 378 | // flush pending data 379 | void ikcp_flush(ikcpcb *kcp); 380 | 381 | // check the size of next message in the recv queue 382 | int ikcp_peeksize(const ikcpcb *kcp); 383 | 384 | // change MTU size, default is 1400 385 | int ikcp_setmtu(ikcpcb *kcp, int mtu); 386 | 387 | // set maximum window size: sndwnd=32, rcvwnd=32 by default 388 | int ikcp_wndsize(ikcpcb *kcp, int sndwnd, int rcvwnd); 389 | 390 | // get how many packet is waiting to be sent 391 | int ikcp_waitsnd(const ikcpcb *kcp); 392 | 393 | // fastest: ikcp_nodelay(kcp, 1, 20, 2, 1) 394 | // nodelay: 0:disable(default), 1:enable 395 | // interval: internal update timer interval in millisec, default is 100ms 396 | // resend: 0:disable fast resend(default), 1:enable fast resend 397 | // nc: 0:normal congestion control(default), 1:disable congestion control 398 | int ikcp_nodelay(ikcpcb *kcp, int nodelay, int interval, int resend, int nc); 399 | 400 | 401 | void ikcp_log(ikcpcb *kcp, int mask, const char *fmt, ...); 402 | 403 | // setup allocator 404 | void ikcp_allocator(void* (*new_malloc)(size_t), void (*new_free)(void*)); 405 | 406 | // read conv 407 | IUINT32 ikcp_getconv(const void *ptr); 408 | 409 | 410 | #ifdef __cplusplus 411 | } 412 | #endif 413 | 414 | #endif 415 | 416 | 417 | -------------------------------------------------------------------------------- /lualib/base/table.lua: -------------------------------------------------------------------------------- 1 | -------------------------------------- 2 | ----- 对标准库 table 的补充 3 | ------------------------------------- 4 | 5 | -- define module: table 6 | local table = table or {} 7 | 8 | 9 | -- append all objects to array 10 | function table.append(array, ...) 11 | for _, value in ipairs({...}) do 12 | table.insert(array, value) 13 | end 14 | return array 15 | end 16 | 17 | -- slice table array 18 | function table.slice(self, first, last, step) 19 | 20 | -- slice it 21 | local sliced = {} 22 | for i = first or 1, last or #self, step or 1 do 23 | sliced[#sliced + 1] = self[i] 24 | end 25 | return sliced 26 | end 27 | 28 | -- is array? 29 | function table.is_array(array) 30 | return type(array) == "table" and array[1] ~= nil 31 | end 32 | 33 | -- is dictionary? 34 | function table.is_dictionary(dict) 35 | return type(dict) == "table" and dict[1] == nil 36 | end 37 | 38 | -- read data from iterator, push them to an array 39 | -- usage: table.to_array(ipairs("a", "b")) -> {{1,"a",n=2},{2,"b",n=2}},2 40 | -- usage: table.to_array(io.lines("file")) -> {"line 1","line 2", ... , "line n"},n 41 | function table.to_array(iterator, state, var) 42 | 43 | assert(iterator) 44 | 45 | local result = {} 46 | local count = 0 47 | while true do 48 | local data = table.pack(iterator(state, var)) 49 | if data[1] == nil then break end 50 | var = data[1] 51 | 52 | if data.n == 1 then 53 | table.insert(result, var) 54 | else 55 | table.insert(result, data) 56 | end 57 | count = count + 1 58 | end 59 | 60 | return result, count 61 | end 62 | 63 | -- unwrap object if be only one 64 | function table.unwrap(object) 65 | if type(object) == "table" then 66 | if #object == 1 then 67 | return object[1] 68 | end 69 | end 70 | return object 71 | end 72 | 73 | -- wrap object to table 74 | function table.wrap(object) 75 | -- no object? 76 | if nil == object then 77 | return {} 78 | end 79 | 80 | -- wrap it if not table 81 | if type(object) ~= "table" then 82 | return {object} 83 | end 84 | 85 | -- ok 86 | return object 87 | end 88 | 89 | -- remove repeat from the given array 90 | function table.unique(array, barrier) 91 | -- remove repeat for array 92 | if table.is_array(array) then 93 | -- not only one? 94 | if table.getn(array) ~= 1 then 95 | -- done 96 | local exists = {} 97 | local unique = {} 98 | for _, v in ipairs(array) do 99 | -- exists barrier? clear the current existed items 100 | if barrier and barrier(v) then 101 | exists = {} 102 | end 103 | 104 | -- add unique item 105 | if not exists[v] then 106 | -- v will not be nil 107 | exists[v] = true 108 | table.insert(unique, v) 109 | end 110 | end 111 | 112 | -- update it 113 | array = unique 114 | end 115 | end 116 | 117 | -- ok 118 | return array 119 | end 120 | 121 | -- get keys of a table 122 | function table.keys(tab) 123 | assert(tab) 124 | 125 | local keyset = {} 126 | local n = 0 127 | for k, _ in pairs(tab) do 128 | n = n + 1 129 | keyset[n] = k 130 | end 131 | return keyset, n 132 | end 133 | 134 | -- get values of a table 135 | function table.values(tab) 136 | assert(tab) 137 | 138 | local valueset = {} 139 | local n = 0 140 | for _, v in pairs(tab) do 141 | n = n + 1 142 | valueset[n] = v 143 | end 144 | return valueset, n 145 | end 146 | 147 | -- map values to a new table 148 | function table.map(tab, mapper) 149 | 150 | assert(tab) 151 | assert(mapper) 152 | 153 | local newtab = {} 154 | for k, v in pairs(tab) do 155 | newtab[k] = mapper(k, v) 156 | end 157 | return newtab 158 | end 159 | 160 | table.imap = table.foreachi 161 | 162 | function table.reverse(arr) 163 | assert(arr) 164 | 165 | local revarr = {} 166 | local l = #arr 167 | for i = 1, l do 168 | revarr[i] = arr[l - i + 1] 169 | end 170 | return revarr 171 | end 172 | 173 | function table.length(T) 174 | local count = 0 175 | if T == nil then return 0 end 176 | for _ in pairs(T) do count = count + 1 end 177 | return count 178 | end 179 | 180 | 181 | -- 182 | -- Make a copy of the indexed elements of the table. 183 | -- 184 | function table.arraycopy(object) 185 | local result = {} 186 | for i, value in ipairs(object) do 187 | result[i] = value 188 | end 189 | return result 190 | end 191 | 192 | 193 | -- 194 | -- Returns true if the table contains the specified value. 195 | -- 196 | function table.contains(t, value) 197 | for _,v in pairs(t) do 198 | if (v == value) then 199 | return true 200 | end 201 | end 202 | return false 203 | end 204 | 205 | 206 | -- 207 | -- Make a shallow copy of a table 208 | -- 209 | function table.shallowcopy(object) 210 | local copy = {} 211 | for k, v in pairs(object) do 212 | copy[k] = v 213 | end 214 | return copy 215 | end 216 | 217 | 218 | -- 219 | -- Make a complete copy of a table, including any child tables it contains. 220 | -- 221 | function table.deepcopy(object) 222 | -- keep track of already seen objects to avoid loops 223 | local seen = {} 224 | 225 | local function copy(o) 226 | if type(o) ~= "table" then 227 | return o 228 | elseif seen[o] then 229 | return seen[o] 230 | end 231 | 232 | local clone = {} 233 | seen[o] = clone 234 | for key, value in pairs(o) do 235 | clone[key] = copy(value) 236 | end 237 | 238 | setmetatable(clone, getmetatable(o)) 239 | return clone 240 | end 241 | 242 | return copy(object) 243 | end 244 | 245 | 246 | -- 247 | -- Enumerates an array of objects and returns a new table containing 248 | -- only the value of one particular field. 249 | -- 250 | function table.extract(arr, fname) 251 | local result = { } 252 | for _,v in ipairs(arr) do 253 | table.insert(result, v[fname]) 254 | end 255 | return result 256 | end 257 | 258 | 259 | -- 260 | -- Enumerates an array of objects and returns a new table containing 261 | -- only the values satisfying the given predicate. 262 | -- 263 | function table.filter(arr, fn) 264 | local result = { } 265 | table.foreachi(arr, function(val) 266 | if fn(val) then 267 | table.insert(result, val) 268 | end 269 | end) 270 | return result 271 | end 272 | 273 | 274 | -- 275 | -- Flattens a hierarchy of tables into a single array containing all 276 | -- of the values. 277 | -- 278 | function table.flatten(arr) 279 | local result = {} 280 | 281 | local function flatten(a) 282 | local n = #a 283 | for i = 1, n do 284 | local v = a[i] 285 | if type(v) == "table" then 286 | flatten(v) 287 | elseif v then 288 | table.insert(result, v) 289 | end 290 | end 291 | end 292 | 293 | flatten(arr) 294 | return result 295 | end 296 | 297 | table.join = table.flatten 298 | 299 | 300 | -- 301 | -- Walk the elements of an array and call the specified function 302 | -- for each non-nil element. This works around a "feature" of the 303 | -- ipairs() function that stops iteration at the first nil. 304 | -- 305 | -- @param arr 306 | -- The array to iterate. 307 | -- @param func 308 | -- The function to call. The value (not the index) will be passed 309 | -- as the only argument. 310 | -- 311 | function table.foreachi(arr, func) 312 | if arr then 313 | local n = #arr 314 | for i = 1, n do 315 | local v = arr[i] 316 | if v then 317 | func(v, i) 318 | end 319 | end 320 | end 321 | end 322 | 323 | 324 | -- 325 | -- Merge two lists into an array of objects, containing pairs 326 | -- of values, one from each list. 327 | -- 328 | function table.fold(list1, list2) 329 | local result = {} 330 | for _, item1 in ipairs(list1 or {}) do 331 | if list2 and #list2 > 0 then 332 | for _, item2 in ipairs(list2) do 333 | table.insert(result, { item1, item2 }) 334 | end 335 | else 336 | table.insert(result, { item1 }) 337 | end 338 | end 339 | return result 340 | end 341 | 342 | 343 | -- 344 | -- Merges an array of items into a string. 345 | -- 346 | function table.implode(arr, before, after, between) 347 | local result = "" 348 | for _,v in ipairs(arr) do 349 | if (result ~= "" and between) then 350 | result = result .. between 351 | end 352 | result = result .. before .. v .. after 353 | end 354 | return result 355 | end 356 | 357 | 358 | 359 | -- 360 | -- Looks for an object within an array. Returns its index if found, 361 | -- or nil if the object could not be found. 362 | -- 363 | function table.indexof(tbl, obj) 364 | for k, v in ipairs(tbl) do 365 | if v == obj then 366 | return k 367 | end 368 | end 369 | end 370 | 371 | 372 | -- 373 | -- Looks for an object within a table. Returns the key if found, 374 | -- or nil if the object could not be found. 375 | -- 376 | function table.findKeyByValue(tbl, obj) 377 | for k, v in pairs(tbl) do 378 | if v == obj then 379 | return k 380 | end 381 | end 382 | end 383 | 384 | 385 | --- 386 | -- Insert a new value into a table in the position after the specified 387 | -- existing value. If the specified value does not exist in the table, 388 | -- the new value is appended to the end of the table. 389 | -- 390 | -- @param tbl 391 | -- The table in which to insert. 392 | -- @param after 393 | -- The existing value to insert after. 394 | -- @param value 395 | -- The new value to insert. 396 | -- 397 | function table.insertafter(tbl, after, value) 398 | local i = table.indexof(tbl, after) 399 | if i then 400 | table.insert(tbl, i + 1, value) 401 | else 402 | table.insert(tbl, value) 403 | end 404 | end 405 | 406 | 407 | -- 408 | -- Inserts a value or array of values into a table. If the value is 409 | -- itself a table, its contents are enumerated and added instead. So 410 | -- these inputs give these outputs: 411 | -- 412 | -- "x" -> { "x" } 413 | -- { "x", "y" } -> { "x", "y" } 414 | -- { "x", { "y" }} -> { "x", "y" } 415 | -- 416 | function table.insertflat(tbl, values) 417 | if values == nil then 418 | return 419 | elseif type(values) == "table" then 420 | for _, value in ipairs(values) do 421 | table.insertflat(tbl, value) 422 | end 423 | else 424 | table.insert(tbl, values) 425 | end 426 | return tbl 427 | end 428 | 429 | 430 | -- 431 | -- Inserts a value into a table as both a list item and a key-value pair. 432 | -- Useful for set operations. Returns false if the value already exists, true otherwise. 433 | -- 434 | function table.insertkeyed(tbl, pos, value) 435 | if value == nil then 436 | value = pos 437 | pos = #tbl + 1 438 | end 439 | 440 | if tbl[value] ~= nil then 441 | return false 442 | end 443 | 444 | table.insert(tbl, pos, value) 445 | tbl[value] = value 446 | return true 447 | end 448 | 449 | 450 | -- 451 | -- Inserts a value into a table in sorted order. Assumes that the 452 | -- table is already sorted according to the sort function. If fn is 453 | -- nil, the table is sorted according to the < operator. 454 | -- 455 | function table.insertsorted(tbl, value, fn) 456 | if value == nil then 457 | return 458 | else 459 | fn = fn or function(a, b) return a < b end 460 | 461 | local minindex = 1 462 | local maxindex = #tbl + 1 463 | while minindex < maxindex do 464 | local index = minindex + ((maxindex - minindex) >> 1) 465 | local test = tbl[index] 466 | if fn(value, test) then 467 | maxindex = index 468 | else 469 | minindex = index + 1 470 | if not fn(test, value) then 471 | break 472 | end 473 | end 474 | end 475 | 476 | table.insert(tbl, minindex, value) 477 | end 478 | 479 | return tbl 480 | end 481 | 482 | 483 | -- 484 | -- Returns true if the table is empty, and contains no indexed or keyed values. 485 | -- 486 | function table.isempty(t) 487 | return next(t) == nil 488 | end 489 | 490 | 491 | 492 | -- 493 | -- Return a list of all keys used in a table. 494 | -- 495 | function table.keys(tbl) 496 | local keys = {} 497 | for k, _ in pairs(tbl) do 498 | table.insert(keys, k) 499 | end 500 | return keys 501 | end 502 | 503 | 504 | -- 505 | -- Adds the key-value associations from one table into another 506 | -- and returns the resulting merged table. 507 | -- 508 | function table.merge(...) 509 | local result = {} 510 | local arg = {...} 511 | for _,t in ipairs(arg) do 512 | 513 | if type(t) == "table" then 514 | for k,v in pairs(t) do 515 | if type(result[k]) == "table" and type(v) == "table" then 516 | result[k] = table.merge(result[k], v) 517 | else 518 | result[k] = v 519 | end 520 | end 521 | 522 | else 523 | error("invalid value") 524 | end 525 | end 526 | 527 | return result 528 | end 529 | 530 | 531 | --- 532 | -- Replace all instances of `value` with `replacement` in an array. Array 533 | -- elements are modified in place. 534 | -- 535 | -- @param value 536 | -- The value to be replaced. 537 | -- @param replacement 538 | -- The new value. 539 | --- 540 | function table.replace(self, value, replacement) 541 | for i = 1, #self do 542 | if self[i] == value then 543 | self[i] = replacement 544 | end 545 | end 546 | end 547 | 548 | 549 | -- 550 | -- Translates the values contained in array, using the specified 551 | -- translation table, and returns the results in a new array. 552 | -- 553 | function table.translate(arr, translation) 554 | if not translation then return {} end 555 | 556 | local result = {} 557 | for i = 1, #arr do 558 | local tvalue 559 | if type(translation) == "function" then 560 | tvalue = translation(arr[i]) 561 | else 562 | tvalue = translation[arr[i]] 563 | end 564 | if (tvalue) then 565 | table.insert(result, tvalue) 566 | end 567 | end 568 | return result 569 | end 570 | 571 | 572 | -- 573 | -- Dumps a table to a string 574 | -- 575 | function table.tostring(tab, recurse, indent) 576 | local res = '' 577 | 578 | if not indent then 579 | indent = 0 580 | end 581 | 582 | local format_value = function(k, v, i) 583 | local formatting = string.rep("\t", i) 584 | 585 | if k then 586 | if type(k) == "table" then 587 | k = '[table]' 588 | end 589 | formatting = formatting .. k .. ": " 590 | end 591 | 592 | if not v then 593 | return formatting .. '(nil)' 594 | elseif type(v) == "table" then 595 | if recurse and recurse > 0 then 596 | return formatting .. '\n' .. table.tostring(v, recurse-1, i+1) 597 | else 598 | return formatting .. "" 599 | end 600 | elseif type(v) == "function" then 601 | return formatting .. tostring(v) 602 | elseif type(v) == "userdata" then 603 | return formatting .. "" 604 | elseif type(v) == "boolean" then 605 | if v then 606 | return formatting .. 'true' 607 | else 608 | return formatting .. 'false' 609 | end 610 | else 611 | return formatting .. v 612 | end 613 | end 614 | 615 | if type(tab) == "table" then 616 | local first = true 617 | 618 | -- add the meta table. 619 | local mt = getmetatable(tab) 620 | if mt then 621 | res = res .. format_value('__mt', mt, indent) 622 | first = false 623 | end 624 | 625 | -- add all values. 626 | for k, v in pairs(tab) do 627 | if not first then 628 | res = res .. '\n' 629 | end 630 | 631 | res = res .. format_value(k, v, indent) 632 | first = false 633 | end 634 | else 635 | res = res .. format_value(nil, tab, indent) 636 | end 637 | 638 | return res 639 | end 640 | 641 | 642 | -- 643 | -- Returns a copy of a list with all duplicate elements removed. 644 | -- 645 | function table.unique(tab) 646 | local elems = { } 647 | local result = { } 648 | table.foreachi(tab, function(elem) 649 | if not elems[elem] then 650 | table.insert(result, elem) 651 | elems[elem] = true 652 | end 653 | end) 654 | 655 | return result 656 | end 657 | 658 | -- 659 | -- Filters a table for empty entries. primarly useful for lists of string. 660 | -- 661 | function table.filterempty(dirs) 662 | return table.translate(dirs, function(val) 663 | if val and #val > 0 then 664 | return val 665 | else 666 | return nil 667 | end 668 | end) 669 | end 670 | 671 | 672 | -- 673 | -- Compares two tables. 674 | -- 675 | function table.equals(a, b) 676 | for k, v in pairs(a) do 677 | if b[k] ~= v then 678 | return false 679 | end 680 | end 681 | for k, v in pairs(b) do 682 | if a[k] ~= v then 683 | return false 684 | end 685 | end 686 | return true 687 | end 688 | 689 | 690 | -- 691 | -- Enumerate a table sorted by its keys. 692 | -- 693 | function table.spairs(t) 694 | -- collect the keys 695 | local keys = {} 696 | for k in pairs(t) do 697 | table.insert(keys, k) 698 | end 699 | table.sort(keys) 700 | 701 | -- return the iterator function 702 | local i = 0 703 | return function() 704 | i = i + 1 705 | if keys[i] then 706 | return keys[i], t[keys[i]] 707 | end 708 | end 709 | end 710 | 711 | 712 | -- 713 | -- Intersect two arrays and return a new array 714 | -- 715 | function table.intersect(a, b) 716 | local result = {} 717 | for _, v in ipairs(b) do 718 | if table.indexof(a, v) then 719 | table.insert(result, v) 720 | end 721 | end 722 | return result 723 | end 724 | 725 | -- 726 | -- The difference of A and B is the set containing those elements that are in A but not in B 727 | -- 728 | function table.difference(a, b) 729 | local result = {} 730 | for _, v in ipairs(a) do 731 | if not table.indexof(b, v) then 732 | table.insert(result, v) 733 | end 734 | end 735 | return result 736 | end 737 | 738 | -- return module: table 739 | return table 740 | -------------------------------------------------------------------------------- /thirdparty/lua-protobuf/README.md: -------------------------------------------------------------------------------- 1 | # Google protobuf support for Lua 2 | 3 | [![Build Status](https://travis-ci.org/starwing/lua-protobuf.svg?branch=master)](https://travis-ci.org/starwing/lua-protobuf)[![Coverage Status](https://coveralls.io/repos/github/starwing/lua-protobuf/badge.svg?branch=master)](https://coveralls.io/github/starwing/lua-protobuf?branch=master) 4 | 5 | 中文使用说明:https://zhuanlan.zhihu.com/p/26014103 6 | 7 | Urho3d集成说明:https://note.youdao.com/ynoteshare1/index.html?id=20d06649fab669371140256abd7a362b&type=note 8 | 9 | Unreal SLua集成:https://github.com/zengdelang/slua-unreal-pb 10 | 11 | ToLua集成说明:http://changxianjie.com/tolua集成lua-protobuf/ 12 | 13 | QQ群:485016061 [![lua-protobuf1交流群](https://pub.idqqimg.com/wpa/images/group.png)](https://shang.qq.com/wpa/qunwpa?idkey=d7e2973604a723c4f77d0a837df39be26e15be2c2ec29d5ebfdb64f94e74e6ae) 14 | 15 | This project offers a C module for Lua manipulating Google's protobuf protocol, both for version 2 and 3 syntax and semantics. It splits to the lower-level and the high-level parts for different goals. 16 | 17 | For converting between binary protobuf data with Lua tables, using `pb.load()` loads the compiled protobuf schema content (`*.pb` file) generated by Google protobuf's compiler named `protoc` and call `pb.encode()`/`pb.decode()`. 18 | 19 | Or use these modules to manipulate the raw wire format in lower-level way: 20 | 21 | - `pb.slice`: a wire format decoding module. 22 | - `pb.buffer`: a buffer implement that use to encode basic types into protobuf's wire format. It can be used to support streaming decode protobuf data. 23 | - `pb.conv`: a module converting integers in the protobuf wire format. 24 | - `pb.io`: a module access `stdin/stdout` or other files in binary mode. 25 | 26 | If you don't want to depend Google's protobuf compiler, `protoc.lua` is a pure Lua module translating text-based protobuf schema content into the `*.pb` binary format. 27 | 28 | ## Install 29 | 30 | To install, you could just use `luarocks`: 31 | 32 | ```shell 33 | luarocks install lua-protobuf 34 | ``` 35 | 36 | If you want to build it from source, just clone the repo and use luarocks: 37 | 38 | ```shell 39 | git clone https://github.com/starwing/lua-protobuf 40 | luarocks make rockspecs/lua-protobuf-scm-1.rockspec 41 | ``` 42 | 43 | If you don't have luarocks, use `hererocks` to install Lua and luarocks: 44 | 45 | ```shell 46 | pip install hererocks 47 | git clone https://github.com/starwing/lua-protobuf 48 | hererocks -j 2.0 -rlatest . 49 | bin/luarocks make lua-protobuf/rockspecs/lua-protobuf-scm-1.rockspec CFLAGS="-fPIC -Wall -Wextra" LIBFLAGS="-shared" 50 | cp protoc.lua pb.so .. 51 | ``` 52 | 53 | Or you can build it by hand, it only has a pure Lua module `protoc.lua` and a pair of C source: `pb.h` and `pb.c`. 54 | 55 | To build it on macOS, use your favor compiler: 56 | 57 | ```shell 58 | gcc -O2 -shared -undefined dynamic_lookup pb.c -o pb.so 59 | ``` 60 | 61 | On Linux, use the nearly same command: 62 | 63 | ```shell 64 | gcc -O2 -shared -fPIC pb.c -o pb.so 65 | ``` 66 | 67 | On Windows, you could use MinGW or MSVC, create a `*.sln` project or build it on the command line (notice the `Lua_BUILD_AS_DLL` flag): 68 | 69 | ```shell 70 | cl /O2 /LD /Fepb.dll /I Lua53\include /DLUA_BUILD_AS_DLL pb.c Lua53\lib\lua53.lib 71 | ``` 72 | 73 | ## Example 74 | 75 | ```lua 76 | local pb = require "pb" 77 | local protoc = require "protoc" 78 | 79 | -- load schema from text (just for demo, use protoc.new() in real world) 80 | assert(protoc:load [[ 81 | message Phone { 82 | optional string name = 1; 83 | optional int64 phonenumber = 2; 84 | } 85 | message Person { 86 | optional string name = 1; 87 | optional int32 age = 2; 88 | optional string address = 3; 89 | repeated Phone contacts = 4; 90 | } ]]) 91 | 92 | -- lua table data 93 | local data = { 94 | name = "ilse", 95 | age = 18, 96 | contacts = { 97 | { name = "alice", phonenumber = 12312341234 }, 98 | { name = "bob", phonenumber = 45645674567 } 99 | } 100 | } 101 | 102 | -- encode lua table data into binary format in lua string and return 103 | local bytes = assert(pb.encode("Person", data)) 104 | print(pb.tohex(bytes)) 105 | 106 | -- and decode the binary data back into lua table 107 | local data2 = assert(pb.decode("Person", bytes)) 108 | print(require "serpent".block(data2)) 109 | 110 | ``` 111 | 112 | ## Use case 113 | 114 | [![零境交错](https://img.tapimg.com/market/images/e59627dc9039ff22ba7d000b5c9fe7f6.jpg?imageView2/2/h/560/q/40/format/jpg/interlace/1/ignore-error/1)](http://djwk.qq.com) 115 | 116 | 117 | 118 | ## Usage 119 | 120 | ### `protoc` Module 121 | 122 | | Function | Returns | Descriptions | 123 | | ------------------- | ------------- | ---------------------------------------------------- | 124 | | `protoc.new()` | Proroc object | create a new compiler instance | 125 | | `protoc.reload()` | true | reload all google standard messages into `pb` module | 126 | | `p:parse(string)` | table | transform schema to `DescriptorProto` table | 127 | | `p:compile(string)` | string | transform schema to binary *.pb format data | 128 | | `p:load(string)` | true | load schema into `pb` module | 129 | | `p.loaded` | table | contains all parsed `DescriptorProto` table | 130 | | `p.unknown_module` | see below | handle schema import error | 131 | | `p.unknown_type` | see below | handle unknown type in schema | 132 | | `p.include_imports` | bool | auto load imported proto | 133 | 134 | To parse a text schema content, create a compiler instance first: 135 | 136 | ```lua 137 | local p = protoc.new() 138 | ``` 139 | 140 | Then, set some options to the compiler, e.g. the unknown handlers: 141 | 142 | ```lua 143 | -- set some hooks 144 | p.unknown_module = function(self, module_name) ... end 145 | p.unknown_type = function(self, type_name) ... end 146 | -- ... and options 147 | p.include_imports = true 148 | ``` 149 | 150 | The `unknwon_module` and `unknown_type` handle could be `true`, string or a function. Seting it to `true` means all *non-exist* modules and types are given a default value without triggering an error; A string means a Lua pattern that indicates whether an unknown module or type should raise an error, e.g. 151 | 152 | ```lua 153 | p.unknown_type = "Foo.*" 154 | ``` 155 | 156 | means all types prefixed by `Foo` will be treat as existing type and do not trigger errors. 157 | 158 | If these are functions, the unknown type and module name will be passed to functions. For module handler, it should return a `DescriptorProto` Table produced by `p:load()` functions, for type handler, it should return a type name and type, such as `message` or `enum`, e.g. 159 | 160 | ```lua 161 | function p:unknown_module(name) 162 | -- if can not find "foo.proto", load "my_foo.proto" instead 163 | return p:load("my_"..name) 164 | end 165 | 166 | function p:unknown_type(name) 167 | -- if cannot find "Type", treat it as ".MyType" and is a message type return ".My"..name, "message" 168 | end 169 | ``` 170 | 171 | After setting options, use `load()` or `compile()` or `parse()` function to get result. 172 | 173 | ### `pb` Module 174 | 175 | `pb` module has high-level routines to manipulate protobuf messages. 176 | 177 | In below table of functions, we have several types that have special means: 178 | 179 | - `type`: a string that indicates the protobuf message type, `".Foo"` means the type in a proto definition that has not `package` statement declared. `"foo.Foo"` means the type in a proto definition that declared `package foo;` 180 | 181 | - `data`: could be string, `pb.Slice` value or `pb.Buffer` value. 182 | 183 | - `iterator`: a function that can use in Lua `for in` statement, e.g. 184 | 185 | ```lua 186 | for name in pb.types() do 187 | print(name) 188 | end 189 | ``` 190 | 191 | **NOTICE**: Only `pb.load()` returns error on failure, *do check* the result it returns. Other routines raise a error when failure for convenience. 192 | 193 | | Function | Returns | Description | 194 | | ------------------------------ | --------------- | ------------------------------------------------------- | 195 | | `pb.clear()` | None | clear all types | 196 | | `pb.clear(type)` | None | delete specific type | 197 | | `pb.load(data)` | boolean,integer | load a binary schema data into `pb` module | 198 | | `pb.encode(type, table)` | string | encode a message table into binary form | 199 | | `pb.encode(type, table, b)` | buffer | encode a message table into binary form to buffer | 200 | | `pb.decode(type, data)` | table | decode a binary message into Lua table | 201 | | `pb.decode(type, data, table)` | table | decode a binary message into a given Lua table | 202 | | `pb.pack(fmt, ...)` | string | same as `buffer.pack()` but return string | 203 | | `pb.unpack(data, fmt, ...)` | values... | same as `slice.unpack()` but accept data | 204 | | `pb.types()` | iterator | iterate all types in `pb` module | 205 | | `pb.type(type)` | see below | return informations for specific type | 206 | | `pb.fields(type)` | iterator | iterate all fields in a message | 207 | | `pb.field(type, string)` | see below | return informations for specific field of type | 208 | | `pb.typefmt(type)` | String | transform type name of field into pack/unpack formatter | 209 | | `pb.enum(type, string)` | number | get the value of a enum by name | 210 | | `pb.enum(type, number)` | string | get the name of a enum by value | 211 | | `pb.defaults(type[, table])` | table | get the default table of type | 212 | | `pb.hook(type[, function])` | function | get or set hook functions | 213 | | `pb.option(string)` | string | set options to decoder/encoder | 214 | | `pb.state()` | `pb.State` | retrieve current pb state | 215 | | `pb.state(newstate \| nil)` | `pb.State` | set new pb state and retrieve the old one | 216 | 217 | #### Schema loading 218 | 219 | `pb.load()` accepts the schema binary data and returns a boolean indicates the result of loading, success or failure, and a offset reading in schema so far that is useful to figure out the reason of failure. 220 | 221 | #### Type Information 222 | 223 | Using `pb.(type|field)[s]()` functions retrieve type information for loaded messages. 224 | 225 | `pb.type()` returns multiple informations for specified type: 226 | 227 | - name : the full qualifier name of type, e.g. ".package.TypeName" 228 | - basename: the type name without package prefix, e.g. "TypeName" 229 | - `"map"` | `"enum"` | `"message"`: whether the type is a map_entry type, enum type or message type. 230 | 231 | `pb.types()` returns a iterators, behavior like call `pb.type()` on every types of all messages. 232 | 233 | ```lua 234 | print(pb.type "MyType") 235 | 236 | -- list all types that loaded into pb 237 | for name, basename, type in pb.types() do 238 | print(name, basename, type) 239 | end 240 | ``` 241 | 242 | `pb.field()` returns information of the specified field for one type: 243 | 244 | - name: the name of the field 245 | - number: number of field in the schema 246 | - type: field type 247 | - default value: if no default value, nil 248 | - `"packed"`|`"repeated"`| `"optional"`: label of the field, optional or repeated, required is not supported 249 | - [oneof_name, oneof_index]: if this is a `oneof` field, this is the `oneof` name and index 250 | 251 | And `pb.fields()` iterates all fields in a message: 252 | 253 | ```lua 254 | print(pb.field("MyType", "the_first_field")) 255 | 256 | -- notice that you needn't receive all return values from iterator 257 | for name, number, type in pb.fields "MyType" do 258 | print(name, number, type) 259 | end 260 | ``` 261 | 262 | `pb.enum()` maps from enum name and value: 263 | 264 | ```lua 265 | protoc:load [[ 266 | enum Color { Red = 1; Green = 2; Blue = 3 } 267 | ]] 268 | print(pb.enum("Color", "Red")) --> 1 269 | print(pb.enum("Color", 2)) --> "Green" 270 | ``` 271 | 272 | #### Default Values 273 | 274 | Using `pb.defaults()` to get a table with all default values from a message. this table will be used as the metatable of the corresponding decoded message table when setting `use_default_metatable` option. 275 | 276 | ```lua 277 | check_load [[ 278 | message TestDefault { 279 | optional int32 defaulted_int = 10 [ default = 777 ]; 280 | optional bool defaulted_bool = 11 [ default = true ]; 281 | optional string defaulted_str = 12 [ default = "foo" ]; 282 | optional float defaulted_num = 13 [ default = 0.125 ]; 283 | } ]] 284 | print(require "serpent".block(pb.defaults "TestDefault")) 285 | -- output: 286 | -- { 287 | -- defaulted_bool = true, 288 | -- defaulted_int = 777, 289 | -- defaulted_num = 0.125, 290 | -- defaulted_str = "foo" 291 | -- } --[[table: 0x7f8c1e52b050]] 292 | 293 | ``` 294 | 295 | #### Hooks 296 | 297 | If set `pb.option "enable_hooks"`, the hook function will enabled. you could use `pb.hook()` to set or get a hook function. call it with type name directly get current setted hook. call it with two arguments to set a hook. and call it with `nil` as the second argument to remove the hook. in all case, the original one will be returned. 298 | 299 | After the hook function setted and hook enabled, the function will be called *after* a message get decoded. So you could get all values in the table passed to hook function. That's the only argument of hook. 300 | 301 | If you need type name in hook functions, use this helper: 302 | 303 | ```lua 304 | local function make_hook(name, func) 305 | return pb.hook(name, function(t) 306 | return func(name, t) 307 | end) 308 | end 309 | ``` 310 | 311 | #### Options 312 | 313 | Setting options to change the behavior of other routines. 314 | These options are supported currently: 315 | 316 | | Option | Description | 317 | | ----------------------- | ------------------------------------------------------------ | 318 | | `enum_as_name` | set value to enum name when decode a enum **(default)** | 319 | | `enum_as_value` | set value to enum value when decode a enum | 320 | | `int64_as_number` | set value to integer when it fit int32, otherwise return a number **(default)** | 321 | | `int64_as_string` | same as above, but when it not fit int32, return a string instead | 322 | | `int64_as_hexstring` | same as above, but return a hexadigit string instead | 323 | | `no_default_values` | do not default values for decoded message table **(default)** | 324 | | `use_default_values` | set default values by copy values from default table before decode | 325 | | `use_default_metatable` | set default values by set table from `pb.default()` as the metatable | 326 | | `enable_hooks` | `pb.decode` will call `pb.hooks()` hook functions | 327 | | `disable_hooks` | `pb.decode` do not call hooks **(default)** | 328 | 329 | *Note*: The string returned by `int64_as_string` or `int64_as_hexstring` will prefix a `'#'` character. Because Lua may convert between string with number, prefix a `'#'` makes Lua return the string as-is. 330 | 331 | all routines in all module accepts `'#'` prefix `string`/`hex string` as arguments regardless of the option setting. 332 | 333 | #### Multiple State 334 | 335 | `pb` module support multiple states. A state is a database that contains all type information of registered messages. You can retrieve current state by `pb.state()`, or set new state by `pb.state(newstate)`. 336 | 337 | Use `pb.state(nil)` to discard current state, but not to set a new one (the following routines call that use the state will create a new default state automatedly). Use `pb.state()` to retrieve current state without setting a new one. e.g. 338 | 339 | ```lua 340 | local old = pb.state(nil) 341 | -- if you use protoc.lua, call protoc.reload() here. 342 | assert(pb.load(...)) 343 | -- do someting ... 344 | pb.state(old) 345 | ``` 346 | 347 | Notice that if you use `protoc.lua` module, it will register some message to the state, so you should call `proto.reload()` after setting a new state. 348 | 349 | 350 | 351 | ### `pb.io` Module 352 | 353 | `pb.io` module reads binary data from a file or `stdin`/`stdout`, `pb.io.read()` reads binary data from a file, or `stdin` if no file name given as the first parameter. 354 | 355 | `pb.io.write()` and `pb.io.dump()` are same as Lua's `io.write()` except they write binary data. the former writes data to `stdout`, and the latter writes data to a file specified by the first parameter as the file name. 356 | 357 | All these functions return a true value when success, and return `nil, errmsg` when an error occurs. 358 | 359 | | Function | Returns | Description | 360 | | ---------------------- | ------- | ----------------------------------- | 361 | | `io.read()` | string | read all binary data from `stdin` | 362 | | `io.read(string)` | string | read all binary data from file name | 363 | | `io.write(...)` | true | write binary data to `stdout` | 364 | | `io.dump(string, ...)` | string | write binary data to file name | 365 | 366 | 367 | 368 | ### `pb.conv` Module 369 | 370 | `pb.conv` provide functions to convert between numbers. 371 | 372 | | Encode Function | Decode Function | 373 | | ---------------------- | ---------------------- | 374 | | `conv.encode_int32()` | `conv.decode_int32()` | 375 | | `conv.encode_uint32()` | `conv.decode_uint32()` | 376 | | `conv.encode_sint32()` | `conv.decode_sint32()` | 377 | | `conv.encode_sint64()` | `conv.decode_sint64()` | 378 | | `conv.encode_float()` | `conv.decode_float()` | 379 | | `conv.encode_double()` | `conv.decode_double()` | 380 | 381 | 382 | 383 | ### `pb.slice` Module 384 | 385 | Slice object parse binary protobuf data in a low-level way. Use `slice.new()` to create a slice object, with the optional offset `i` and `j` to access a subpart of the original data (named a *view*). 386 | 387 | As protobuf usually nest sub message with in a range of slice, a slice object has a stack itself to support this. Calling `s:enter(i, j)` saves current position and enters next level with the optional offset `i` and `j` just as `slice.new()`. calling `s:leave()` restore the prior view. `s:level()` returns the current level, and `s:level(n)` returns the current position, the start and the end position information of the `n`th level. calling `s:enter()` without parameter will read a length delimited type value from the slice and enter the view in reading value. Using `#a` to get the count of bytes remains in current view. 388 | 389 | ```lua 390 | local s = slice.new("") 391 | local tag = s:unpack "v" 392 | if tag%8 == 2 then -- tag has a type of string/bytes? maybe it's a sub-message. 393 | s:enter() -- read following bytes value, and enter the view of bytes value. 394 | -- do something with bytes value, e.g. reads a lot of fixed32 integers from bytes. 395 | local t = {} 396 | while #s > 0 do 397 | t[#t+1] = s:unpack "d" 398 | end 399 | s:leave() -- after done, leave bytes value and ready to read next value. 400 | end 401 | ``` 402 | 403 | To read values from slice, use `slice.unpack()`, it use a format string to control how to read into a slice as below table (same format character are also used in `buffer.pack()`). Notice that you can use `pb.typefmt()` to convert between format and protobuf type names (returned from `pb.field()`). 404 | 405 | | Format | Description | 406 | | ------ | ------------------------------------------------------------ | 407 | | v | variable Int value | 408 | | d | 4 bytes fixed32 value | 409 | | q | 8 bytes fixed64 value | 410 | | s | length delimited value, usually a `string`, `bytes` or `message` in protobuf. | 411 | | c | receive a extra number parameter `count` after the format, and reads `count` bytes in slice. | 412 | | b | variable int value as a Lua `boolean` value. | 413 | | f | 4 bytes `fixed32` value as floating point `number` value. | 414 | | F | 8 bytes `fixed64` value as floating point `number` value. | 415 | | i | variable int value as signed int value, i.e. `int32` | 416 | | j | variable int value as zig-zad encoded signed int value, i.e.`sint32` | 417 | | u | variable int value as unsigned int value, i.e. `uint32` | 418 | | x | 4 bytes fixed32 value as unsigned fixed32 value, i.e.`fixed32` | 419 | | y | 4 bytes fixed32 value as signed fixed32 value, i.e. `sfixed32` | 420 | | I | variable int value as signed int value, i.e.`int64` | 421 | | J | variable int value as zig-zad encoded signed int value, i.e. `sint64` | 422 | | U | variable int value and treat it as `uint64` | 423 | | X | 8 bytes fixed64 value as unsigned fixed64 value, i.e. `fixed64` | 424 | | Y | 8 bytes fixed64 value as signed fixed64 value, i.e. `sfixed64` | 425 | 426 | And extra format can be used to control the read cursor in one `slice.unpack()` process: 427 | 428 | | Format | Description | 429 | | ------ | ------------------------------------------------------------ | 430 | | @ | returns current cursor position in the slice, related with the beginning of the current view. | 431 | | * | set the current cursor position to the extra parameter after format string. | 432 | | + | set the relate cursor position, i.e. add the extra parameter to the current position. | 433 | 434 | e.g. If you want to read a `varint` value twice, you can write it as: 435 | 436 | ```lua 437 | local v1, v2 = s:unpack("v*v", 1) 438 | -- v: reads a `varint` value 439 | -- *: receive the second parameter 1 and set it to the current cursor position, i.e. restore the cursor to the head of the view 440 | -- v: reads the first `varint` value again 441 | ``` 442 | 443 | All routines in `pb.slice` module: 444 | 445 | | Function | Returns | Description | 446 | | ------------------------- | ------------ | ------------------------------------------------------------ | 447 | | `slice.new(data[,i[,j]])` | Slice object | create a new slice object | 448 | | `s:delete()` | none | same as `s:reset()`, free it's content | 449 | | `tostring(s)` | string | return the string repr of the object | 450 | | `#s` | number | returns the count of bytes can read in current view | 451 | | `s:reset([...])` | self | reset object to another data | 452 | | `s:level()` | number | returns the count of stored state | 453 | | `s:level(number)` | p, i, j | returns the informations of the `n`th stored state | 454 | | `s:enter()` | self | reads a bytes value, and enter it's view | 455 | | `s:enter(i[, j])` | self | enter a view start at `i` and ends at `j`, includes | 456 | | `s:leave([number])` | self, n | leave the number count of level (default 1) and return current level | 457 | | `s:unpack(fmt, ...)` | values... | reads values of current view from slice | 458 | 459 | 460 | 461 | ### `pb.buffer` Module 462 | 463 | Buffer module used to construct a protobuf data format stream in a low-level way. It's just a bytes data buffer. using `buffer.pack()` to append values to the buffer, and `buffer.result()` to get the encoded raw data, or `buffer.tohex()` to get the human-readable hex digit value of data. 464 | 465 | `buffer.pack()` use the same format syntax with `slice.unpack()`, and support `'()'` format means the inner value will be encoded as a length delimited value, i.e. a message value encoded format. 466 | 467 | parenthesis can be nested. 468 | 469 | e.g. 470 | 471 | ```lua 472 | b:pack("(vvv)", 1, 2, 3) -- get a bytes value that contains three varint value. 473 | ``` 474 | 475 | 476 | 477 | `buffer.pack()` also support '#' format, it means prepends a length into the buffer. 478 | 479 | e.g. 480 | 481 | ```lua 482 | b:pack("#", 5) -- prepends a varint length #b-5+1 at offset 5 483 | ``` 484 | 485 | All routines in `pb.buffer` module: 486 | 487 | | Function | Returns | Description | 488 | | ------------------- | ------------- | ------------------------------------------------------------ | 489 | | `buffer.new([...])` | Buffer object | create a new buffer object, extra args will passed to `b:reset()` | 490 | | `b:delete()` | none | same as `b:reset()`, free it's content | 491 | | `tostring(b)` | string | returns the string repr of the object | 492 | | `#b` | number | returns the encoded count of bytes in buffer | 493 | | `b:reset()` | self | reset to a empty buffer | 494 | | `b:reset([...])` | self | resets the buffer and set its content as the concat of it's args | 495 | | `b:tohex([i[, j]])` | string | return the string of hexadigit represent of the data, `i` and `j` are ranges in encoded data, includes. Omit it means the whole range | 496 | | `b:result([i[,j]])` | string | return the raw data, `i` and `j` are ranges in encoded data, includes,. Omit it means the whole range | 497 | | `b:pack(fmt, ...)` | self | encode the values passed to `b:pack()`, use `fmt` to indicate how to encode value | 498 | 499 | --------------------------------------------------------------------------------