Repository: iwiniwin/LuaKit Branch: master Commit: 5237d57c9230 Files: 37 Total size: 136.2 KB Directory structure: gitextract_vpofiopn/ ├── .gitignore ├── README.md ├── _load.lua ├── build/ │ └── int64.c ├── core/ │ ├── component/ │ │ ├── component_base.lua │ │ ├── component_extend.lua │ │ └── component_factory.lua │ ├── event/ │ │ ├── event.lua │ │ └── event_system.lua │ ├── object.lua │ └── sandbox.lua ├── init.lua ├── lib/ │ ├── file.lua │ ├── function.lua │ ├── string.lua │ ├── table.lua │ └── time.lua ├── mvc/ │ ├── loader.lua │ ├── module1/ │ │ ├── test1_ctr.lua │ │ └── test1_view.lua │ ├── module2/ │ │ ├── test2_ctr.lua │ │ └── test2_view.lua │ └── module_config.lua ├── pattern/ │ ├── AbstractFactoryPattern.lua │ ├── AdapterPattern.lua │ ├── CORPattern.lua │ ├── CompositePattern.lua │ ├── FactoryMethodPattern.lua │ ├── ObserverPattern.lua │ ├── ProxyPattern.lua │ ├── StrategyPattern.lua │ └── TemplatePattern.lua ├── test.lua └── utils/ ├── dump.lua ├── dump_to_file.lua ├── memory_monitor.lua └── profiler.lua ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ other/ ================================================ FILE: README.md ================================================ # LuaKit Lua核心工具包,提供对面向对象,组件系统,mvc模块化加载,事件分发系统等常用模式的封装。同时提供打印,内存泄漏检测,字符串操作等常用工具类。 PS:添加了注释的Lua源码可以参考这里 LuaKit部分特性介绍如下,用法/测试用例请参考这里 # Contents - [打印复杂表结构](#打印复杂表结构) - [组件系统](#组件系统) - [事件分发系统](#事件分发系统) - [面向对象封装](#面向对象封装) - [分模块加载](#分模块加载) - [性能分析](#性能分析) - [内存泄漏检测](#内存泄漏检测) ### 打印复杂表结构 dump支持按照指定格式打印任意类型的数据 dump_to_file支持将数据序列化到文件 ```lua local data = { key1 = 34, key2 = "str", key3 = { key4 = { key5 = 56 }, key6 = 78 } } dump(data, "this is a dump test") ``` 输出结果如下: ``` - "this is a dump test" = { - "key1" = 34 - "key2" = "str" - "key3" = { - "key4" = { - "key5" = 56 - } - "key6" = 78 - } - } ``` ### 组件系统 游戏开发中很多功能无法单纯靠继承实现,因为类继承会导致难以轻易改变结构,功能全都向上依赖,子类的数据爆炸,大量冗余数据和方法导致内存消耗过大。而采用组件系统,组件才是功能的携带者,可以实时增减,动态为对象增减功能。对象绑定组件就可以拥有该组件提供的功能,解绑组件则移除对应功能,通过组合构建拥有完整功能的对象,更加灵活解耦。 例如: Fly组件提供飞的能力,鸟对象绑定Fly组件就可以飞,移除Fly组件就不能飞 ```lua local ComponentBase = require("core.component.component_base") local ComponentExtend = require("core.component.component_extend") local A = class() ComponentExtend(A) -- 组件1 local Component1 = class(ComponentBase) Component1.exportInterface = { {"test1"}, } function Component1:test1( ... ) dump("call test1 ...") end -- 组件2 local Component2 = class(ComponentBase) Component2.exportInterface = { {"test2"}, } function Component2:test2( ... ) dump("call test2 ...") end local a = new(A) a:bind_component(Component1) -- 对象a绑定组件1 拥有test1方法 a:bind_component(Component2) -- 对象a绑定组件2 拥有test2方法 a:test1() a:test2() a:unbind_component(Component1) -- 解绑组件1 丧失test1方法 -- a:test1() -- 报错 attempt to call method 'test1' (a nil value) ``` ### 事件分发系统 基于观察者模式封装的一套事件分发系统 ```lua local EventSystem = new(require("core.event.event_system")) local Event = require("core.event.event") -- 简单用法 EventSystem:on("test", function ( ... ) dump({...}) end) EventSystem:emit("test", "param1", "param2") -- 高级用法 local A = class() function A:on_key_down( key ) dump(key, "key name A") end EventSystem:on(Event.KeyDown, A.on_key_down, {target = A}) local B = class() function B:on_key_down( key ) dump(key, "key name B") return true -- 可以中断事件派发 end -- 后注册的事件通过提高优先级可以保证先被调用 EventSystem:on(Event.KeyDown, B.on_key_down, {target = B, priority = 2}) EventSystem:emit(Event.KeyDown, "Ctrl") EventSystem:off_all(B) -- 通过target取消注册 EventSystem:emit(Event.KeyDown, "Ctrl") ``` 高级用法中,第一次emit时,首先触发B,B的回调返回true中断了派发,导致A的回调不会被执行,所以只打印了key name B 第二次emit时,B已经被off_all,不会触发B的回调,自然也没有人再中断事件的派发,所以只打印了key name A 输出结果如下所示: ``` - dump from: E:\Project\LuaKit\test.lua:155: in function 'func' - "" = { - 1 = "param1" - 2 = "param2" - } - dump from: E:\Project\LuaKit\test.lua:170: in function 'func' - "key name B" = "Ctrl" - dump from: E:\Project\LuaKit\test.lua:164: in function 'func' - "key name A" = "Ctrl" ``` ### 面向对象封装 基于Lua原表提供了`class`, `new`, `delete`等面向对象中思想中的常用函数 ```lua local Class1 = class() function Class1:ctor( ... ) dump("Class1:ctor") end function Class1:dtor( ... ) dump("Class1:dtor") end -- Class2集成于Class1 local Class2 = class(Class1) function Class2:ctor( ... ) dump("Class2:ctor") end function Class2:dtor( ... ) dump("Class2:dtor") end -- 实例化对象 local c1 = new(Class1) local c2 = new (Class2) -- 销毁对象 delete(c1) delete(c2) ``` ### 分模块加载 分模块加载是一种模块化设计思想,通过配置文件,实现对模块的按需加载。再结合mvc,可以将一个大系统,看做由多个子模块组合而成。配置了指定模块,则系统拥有该模块的功能,取消配置则不会加载该模块。 ```lua local ModuleConfig = {} ModuleConfig.Module1 = { file = "mvc.module1.test1_view", initOrder = 2, -- 配置模块加载顺序 } ModuleConfig.Module2 = { file = "mvc.module2.test2_view", initOrder = 1, } return ModuleConfig ``` ### 性能分析 通过LuaKit提供的profile工具,可以获取函数的调用情况,调用次数,调用时间,子函数调用时间等信息,以此来分析是否存在异常的函数调用或耗时操作。在需要进行性能优化时十分有用。 ```lua local new_profiler = require("utils.profiler") local profiler = new_profiler("call") profiler:start() -- 开启性能分析 local function aaa( ) for i = 1, 10000000 do end end local function ttt( ) aaa() end ttt() -- 同时支持分析协程内的函数调用情况 local co = coroutine.create(function ( ... ) aaa() end) coroutine.resume(co) profiler:stop() -- 停止性能分析 -- 输出分析结果到文件 profiler:dump_report_to_file("profile.txt") ``` 分析结果部分内容如下 ``` Total time spent in profiled functions: 0.113s Total call count spent in profiled functions: 12 Lua Profile output created by profiler.lua. author: iwiniwin ----------------------------------------------------------------------------------- | FILE : FUNCTION : TIME : % : Call count| ----------------------------------------------------------------------------------- | L:E:\Project\LuaKit\test.lua:61 : aaa : 0.1130 : 100.0 : 2 | | L:E:\Project\LuaKit\test.lua:71 : unknow : 0.0580 : 51.3 : 1 | | C:resume@=[C]:-1 : resume : 0.0580 : 51.3 : 1 | | L:E:\Project\LuaKit\test.lua:66 : ttt : 0.0550 : 48.7 : 1 | | C:insert@=[C]:-1 : insert : ~ : ~ : 1 | | C:sethook@=[C]:-1 : sethook : ~ : ~ : 2 | | C:coroutine_create@=[C]:-1 : coroutine_create : ~ : ~ : 1 | | L:.\utils\profiler.lua:122 : stop : ~ : ~ : 1 | | C:clock@=[C]:-1 : clock : ~ : ~ : 1 | | L:.\utils\profiler.lua:106 : create : ~ : ~ : 1 | ----------------------------------------------------------------------------------- --------------------- L:aaa@@E:\Project\LuaKit\test.lua:61 --------------------- Call count: 2 Time spend total: 0.1130s Time spent in children: 0.0000s Time spent in self: 0.1130s ``` ### 内存泄漏检测 对于游戏开发而言,内存泄露往往是最容易忽视的问题,很多开发者并不知道自己的代码是否存在内存泄露。此类问题可以借助MemoryMonitor来检测,具体原理是借助lua的弱引用,把某个需要观察的对象加入到弱表,如果不存在外部引用,那么在gc时候,弱表上的该对象也就自然消失,如果弱表还存在该对象,说明外部仍存在引用。 ```lua local MemoryMonitor = require("utils.memory_monitor") local memoryMonitor = new(MemoryMonitor) a = {} function test( ... ) local b = {xxx = "xxx"} a.b = b memoryMonitor:add_to_leak_monitor(b, "b") --将b添加到内存检测工具,此时a没有被释放掉 则b也释放不掉 end test() -- 由于a在引用b,因此b存在内存泄漏 memoryMonitor:update() -- 这里会打印日志 -- a不再引用b,b也被释放 a = nil memoryMonitor:update() -- 没有内存泄漏,这里不会打印日志 ``` 第一次update时存在内存泄漏,输出如下所示 ``` 存在以下内存泄漏: b@table: 02C5F7A8 = table: 02C5F7A8 请仔细检查代码!!! ``` ================================================ FILE: _load.lua ================================================ --[[-- LuaKit入口 @module _load @author iwiniwin Date 2019-11-15 19:20:39 Last Modified by iwiniwin Last Modified time 2020-01-16 13:57:21 ]] require("init")() dump("Hello LuaKit") -- require("test") -- 运行测试用例 --[[ 什么叫组合 Composition 在类中增加一个私有域,引用另一个已有的类的实例,通过调用实例的方法从而获得新的功能 ]] ================================================ FILE: build/int64.c ================================================ #include #include #include #include #include #include // 将指定索引处的值转换为int64 static int64_t _int64(lua_State *L, int index) { int type = lua_type(L,index); int64_t n = 0; switch(type) { case LUA_TNUMBER: { // lua number 类型直接强转 lua_Number d = lua_tonumber(L,index); n = (int64_t)d; break; } case LUA_TSTRING: { size_t len = 0; const uint8_t * str = (const uint8_t *)lua_tolstring(L, index, &len); if (len>8) { return luaL_error(L, "The string (length = %d) is not an int64 string", len); } int i = 0; uint64_t n64 = 0; // 最多8个字节字符串,每个字符1个字节对应8位,将每个字符对应的8位拼起来 for (i=0;i<(int)len;i++) { n64 |= (uint64_t)str[i] << (i*8); } n = (int64_t)n64; break; } case LUA_TLIGHTUSERDATA: { // 指针地址值就是int64值 void * p = lua_touserdata(L,index); n = (intptr_t)p; break; } default: return luaL_error(L, "argument %d error type %s", index, lua_typename(L,type)); } return n; } // 入栈一个int64,转换成指针入栈的 static inline void _pushint64(lua_State *L, int64_t n) { void * p = (void *)(intptr_t)n; lua_pushlightuserdata(L,p); } static int int64_add(lua_State *L) { int64_t a = _int64(L,1); int64_t b = _int64(L,2); _pushint64(L, a+b); return 1; } static int int64_sub(lua_State *L) { int64_t a = _int64(L,1); int64_t b = _int64(L,2); _pushint64(L, a-b); return 1; } static int int64_mul(lua_State *L) { int64_t a = _int64(L,1); int64_t b = _int64(L,2); _pushint64(L, a * b); return 1; } static int int64_div(lua_State *L) { int64_t a = _int64(L,1); int64_t b = _int64(L,2); if (b == 0) { return luaL_error(L, "div by zero"); } _pushint64(L, a / b); return 1; } static int int64_mod(lua_State *L) { int64_t a = _int64(L,1); int64_t b = _int64(L,2); if (b == 0) { return luaL_error(L, "mod by zero"); } _pushint64(L, a % b); return 1; } static int64_t _pow64(int64_t a, int64_t b) { if (b == 1) { return a; } int64_t a2 = a * a; if (b % 2 == 1) { return _pow64(a2, b/2) * a; } else { return _pow64(a2, b/2); } } static int int64_pow(lua_State *L) { int64_t a = _int64(L,1); int64_t b = _int64(L,2); int64_t p; if (b > 0) { p = _pow64(a,b); } else if (b == 0) { p = 1; } else { return luaL_error(L, "pow by nagtive number %d",(int)b); } _pushint64(L, p); return 1; } static int int64_unm(lua_State *L) { int64_t a = _int64(L,1); _pushint64(L, -a); return 1; } // 构建int64 static int int64_new(lua_State *L) { int top = lua_gettop(L); int64_t n; switch(top) { case 0 : lua_pushlightuserdata(L,NULL); break; case 1 : n = _int64(L,1); _pushint64(L,n); break; default: { int base = luaL_checkinteger(L,2); // 第二个参数,指明几进制 if (base < 2) { luaL_error(L, "base must be >= 2"); } const char * str = luaL_checkstring(L, 1); n = strtoll(str, NULL, base); // 将字符串根据base转换成长整型数 _pushint64(L,n); break; } } return 1; } static int int64_eq(lua_State *L) { int64_t a = _int64(L,1); int64_t b = _int64(L,2); printf("%s %s\n",lua_typename(L,1),lua_typename(L,2)); printf("%ld %ld\n",a,b); lua_pushboolean(L,a == b); return 1; } static int int64_lt(lua_State *L) { int64_t a = _int64(L,1); int64_t b = _int64(L,2); lua_pushboolean(L,a < b); return 1; } static int int64_le(lua_State *L) { int64_t a = _int64(L,1); int64_t b = _int64(L,2); lua_pushboolean(L,a <= b); return 1; } static int int64_len(lua_State *L) { int64_t a = _int64(L,1); lua_pushnumber(L,(lua_Number)a); return 1; } static int tostring(lua_State *L) { static char hex[16] = "0123456789ABCDEF"; uintptr_t n = (uintptr_t)lua_touserdata(L,1); if (lua_gettop(L) == 1) { // 只传了n, 不带base处理 luaL_Buffer b; // 开辟一个字符串缓存,预分配28大小的空间 luaL_buffinitsize(L , &b , 28); luaL_addstring(&b, "int64: 0x"); int i; bool strip = true; // 将int64转换为十六进制显示 for (i=15;i>=0;i--) { int c = (n >> (i*4)) & 0xf; if (strip && c ==0) { continue; } strip = false; luaL_addchar(&b, hex[c]); } if (strip) { luaL_addchar(&b , '0'); } luaL_pushresult(&b); } else { // 带base处理 int base = luaL_checkinteger(L,2); int shift , mask; switch(base) { case 0: { unsigned char buffer[8]; int i; for (i=0;i<8;i++) { buffer[i] = (n >> (i*8)) & 0xff; } lua_pushlstring(L,(const char *)buffer, 8); return 1; } case 10: { // int64转换为10进制显示 int64_t dec = (int64_t)n; luaL_Buffer b; luaL_buffinitsize(L , &b , 28); if (dec<0) { luaL_addchar(&b, '-'); dec = -dec; } int buffer[32]; int i; for (i=0;i<32;i++) { buffer[i] = dec%10; dec /= 10; if (dec == 0) break; } while (i>=0) { luaL_addchar(&b, hex[buffer[i]]); --i; } luaL_pushresult(&b); return 1; } case 2: shift = 1; mask = 1; break; case 8: shift = 3; mask = 7; break; case 16: shift = 4; mask = 0xf; break; default: luaL_error(L, "Unsupport base %d",base); break; } int i; char buffer[64]; for (i=0;i<64;i+=shift) { buffer[i/shift] = hex[(n>>(64-shift-i)) & mask]; } lua_pushlstring(L, buffer, 64 / shift); } return 1; } static void make_mt(lua_State *L) { // 创建包含以下函数的元表 luaL_Reg lib[] = { { "__add", int64_add }, { "__sub", int64_sub }, { "__mul", int64_mul }, { "__div", int64_div }, { "__mod", int64_mod }, { "__unm", int64_unm }, { "__pow", int64_pow }, { "__eq", int64_eq }, { "__lt", int64_lt }, { "__le", int64_le }, { "__len", int64_len }, { "__tostring", tostring }, { NULL, NULL }, }; luaL_newlib(L,lib); } int luaopen_int64(lua_State *L) { // 64位机器上指针占8字节,32位机器上指针占用4字节 if (sizeof(intptr_t)!=sizeof(int64_t)) { return luaL_error(L, "Only support 64bit architecture"); } lua_pushlightuserdata(L,NULL); make_mt(L); lua_setmetatable(L,-2); lua_pop(L,1); lua_newtable(L); lua_pushcfunction(L, int64_new); lua_setfield(L, -2, "new"); lua_pushcfunction(L, tostring); lua_setfield(L, -2, "tostring"); return 1; } ================================================ FILE: core/component/component_base.lua ================================================ --[[--组件基类 注意:所有组件必须继承该类 @module ComponentBase @author iwiniwin Date 2020-01-16 13:27:14 Last Modified by iwiniwin Last Modified time 2020-01-16 13:45:20 ]] local ComponentBase = class() ComponentBase._class_name = "ComponentBase" ComponentBase.exportInterface = { } --[[-- 组件依赖列表 ]] ComponentBase.depends = {} --[[-- 组件优先级 ]] ComponentBase.priority = 1; function ComponentBase:ctor( componentName ) --[[-- 组件名称 ]] self.name = componentName or "Component"; end --[[-- 组件绑定对象 @tparam Object object 需要绑定组件的“宿主” ]] function ComponentBase:bind( object ) for i,v in ipairs(self.exportInterface) do object:bind_method(self, v[1], handler(self, self[v[1]]), v[2], v[3]); end end --[[-- 组件解绑对象 @tparam Object object 需要解除绑定的“宿主” ]] function ComponentBase:unbind( object ) for i,v in ipairs(self.exportInterface) do object:unbind_method(self, v[1]); end end --[[-- 重置组件,按优先级调用,需要注意的是reset是配合priority使用priority越高组件越先执行 @tparam Object object 需要解除绑定的“宿主” ]] function ComponentBase:reset( object ) end return ComponentBase ================================================ FILE: core/component/component_extend.lua ================================================ --[[--组件扩展 赋予类绑定解绑组件的能力 @module ComponentExtend @author iwiniwin Date 2020-01-16 13:27:14 Last Modified by iwiniwin Last Modified time 2020-01-16 13:45:20 ]] local ComponentFactory = import("core.component.component_factory") local component_extend = function ( Class ) function Class:has_component( ComponentClass ) local component_name = tostring(ComponentClass) return self._component_objects and self._component_objects[component_name] end function Class:get_component( ComponentClass ) if not self._component_objects then return end local component_name = tostring(ComponentClass) return self._component_objects[component_name] end function Class:bind_component( ComponentClass ) assert(ComponentClass, "required component class") local component_name = tostring(ComponentClass); if not self._component_objects then self._component_objects = {} end if self._component_objects[component_name] then return end local component = ComponentFactory.create_component(ComponentClass); for i,DependComponentClass in ipairs(component.depends) do self:bind_component(DependComponentClass) local depend_component_name = tostring(DependComponentClass) if not self._component_depends then self._component_depends = {} end if not self._component_depends[depend_component_name] then self._component_depends[depend_component_name] = {} end table.insert(self._component_depends[depend_component_name], component_name) end component.object = self component:bind(self) self._component_objects[component_name] = component return component end function Class:unbind_component( ComponentClass ) local component_name = tostring(ComponentClass); assert(self._component_objects and self._component_objects[component_name], string.format("component %s not binding", component_name)) assert(not self._component_depends or not self._component_depends[component_name], string.format("component %s depends by other binding", component_name)) local component = self._component_objects[component_name] for i,DependComponentClass in ipairs(component.depends) do local depend_component_name = tostring(DependComponentClass) for i,name in ipairs(self._component_depends[depend_component_name]) do if name == component_name then table.remove(self._component_depends[depend_component_name], i) if #self._component_depends[depend_component_name] == 0 then self._component_depends[depend_component_name] = nil end break end end end component:unbind(self) self._component_objects[component_name] = nil component.object = nil delete(component) end function Class:unbind_all_component( ) if not self._component_objects then return end for component_name,component in pairs(self._component_objects) do component:unbind(self) component.object = nil delete(component) end self._component_objects = {} self._component_depends = {} end function Class:bind_method( component, method_name, method, deprecate_origin_method, call_origin_method_last ) if not self._bind_methods then self._bind_methods = {} end if not self._bind_methods[method_name] then self._bind_methods[method_name] = {} end -- 根据优先级确定绑定 local index local component_priority = component.priority or 1 for i,c in ipairs(self._bind_methods[method_name]) do local priority = c[1].priority or 1 if component_priority > priority then index = i break end end local chain = { component } local new_method if index then chain[2] = self._bind_methods[method_name][index][2] else chain[2] = self[method_name] end if deprecate_origin_method then new_method = method elseif call_origin_method_last then new_method = function ( ... ) if chain[2] then method(...) return chain[2](...) else return method(...) end end else new_method = function ( ... ) local func = chain[2] if func then local ret = func(...) if ret then local args = {...} args[#args + 1] = ret return method(unpack(args)) else return method(...) end else return method(...) end end end if index then self._bind_methods[method_name][index][2] = new_method table.insert(self._bind_methods[method_name], index, chain) else self[method_name] = new_method table.insert(self._bind_methods[method_name], chain) end chain[3] = new_method end function Class:unbind_method( component, method_name ) if not self._bind_methods or not self._bind_methods[method_name] then return end local methods = self._bind_methods[method_name] local count = #methods for i = count, 1, -1 do local chain = methods[i] if chain[1] == component then if i < count then methods[i + 1][2] = chain[2] elseif count > 1 then self[method_name] = methods[i - 1][3] elseif count == 1 then self[method_name] = chain[2] self._bind_methods[method_name] = nil end table.remove(methods, i) break; end end end end return component_extend ================================================ FILE: core/component/component_factory.lua ================================================ --[[--组件工厂类 @module ComponentFactory @author iwiniwin Date 2020-01-16 13:27:14 Last Modified by iwiniwin Last Modified time 2020-01-16 13:45:20 ]] local ComponentBase = import("core.component.component_base") local ComponentFactory = {} function ComponentFactory.create_component( component ) assert(component ~= nil, "invalid component : " .. tostring(component)) if typeof(component, ComponentBase) then return new(component); elseif type(component) == "table" and component.require and component.path then return new(component.require(component.path)) else error("invalid component : " .. tostring(component)) end end return ComponentFactory ================================================ FILE: core/event/event.lua ================================================ --[[--事件配置,便于统一管理项目中所有使用的事件 @module Event @author iwiniwin Date 2020-01-16 13:27:14 Last Modified by iwiniwin Last Modified time 2020-04-01 14:47:14 ]] local Event = {} local event_id = 1; Event.get_unique_id = function ( ) event_id = event_id + 1 return "event_" .. event_id end Event.KeyDown = Event.get_unique_id() return Event ================================================ FILE: core/event/event_system.lua ================================================ --[[--事件分发系统 @module EventSystem @author iwiniwin Date 2020-03-30 14:23:50 Last Modified by iwiniwin Last Modified time 2020-04-01 16:18:51 ]] local EventSystem = class() local id = 1 EventSystem._class_name = "EventSystem" function EventSystem:ctor( ) self._listeners = {} end function EventSystem:dtor( ) self._listeners = nil end --[[ 注册监听一个事件 ]] function EventSystem:on( event, func, params ) event = tostring(event) if not self._listeners[event] then self._listeners[event] = {list = {}, emit_count = 0} end local event_listener = self._listeners[event] params = params or {} local priority = params.priority or 0 local target = params.target for i,cb in ipairs(event_listener.list) do if cb.target == target and cb.func == func then error("register the same callback multiple times") end end local cb = {target = target, func = func, id = id, priority = priority} table.insert(event_listener.list, cb) id = id + 1 if priority > 0 then event_listener.need_sort = true self:sort(event_listener) end end --[[ 反注册事件,取消对一个事件的监听 ]] function EventSystem:off( event, func, params ) event = tostring(event) if not self._listeners[event] then return end local event_listener = self._listeners[event] params = params or {} for i,cb in ipairs(event_listener.list) do if cb.func == func and cb.target == params.target then if event_listener.emit_count > 0 then -- 派发过程中只进行标记删除 cb.need_remove = true event_listener.need_clean = true else table.remove(event_listener.list, i) end break; end end end --[[ 根据target移除所有其注册的事件监听 ]] function EventSystem:off_all( target ) for event,listener in pairs(self._listeners) do for i,cb in ipairs(listener.list) do if cb.target == target then cb.need_remove = true end end listener.need_clean = true self:clean(listener) end end --[[ 派发一个事件 ]] function EventSystem:emit( event, ... ) event = tostring(event) if not self._listeners[event] then return end local event_listener = self._listeners[event] local interrupt = false -- 这里不能使用ipairs,确保不会触发在派发过程中注册的事件 -- 只取当前已经注册的事件数量,如果在派发过程中再注册 则当次不会调用 local length = #event_listener.list for i = 1, length do if interrupt == true then break end local cb = event_listener.list[i] if cb.func and cb.need_remove ~= true then -- 这里必须使用count计数形式判断是否处于派发过程中 -- 如果仅使用true/false标记,在派发的回调中又进行派发,会导致标记错误 event_listener.emit_count = event_listener.emit_count + 1 if cb.target then interrupt = cb.func(cb.target, ...) else interrupt = cb.func(...) end event_listener.emit_count = event_listener.emit_count - 1 end end self:sort(event_listener); self:clean(event_listener); return interrupt end function EventSystem:sort( listener ) --[[ 在派发过程中不会进行优先级排序 比如当前事件有4个回调 1, 2, 3, 4,已经执行到3回调 如果在3回调中又注册了一个优先级最高的回调,需要往第一个插入,此时会导致3回调又被调用一次 ]] if listener.need_sort == true and listener.emit_count == 0 then table.sort(listener.list, function ( a, b ) if a.priority == b.priority then return a.id < b.id else return a.priority > b.priority end end) listener.need_sort = false; end end function EventSystem:clean( listener ) --[[ 在派发过程中不会进行移除 比如当前事件有4个回调 1, 2, 3, 4,已经执行到3回调 如果在3回调中移除了自己,则会导致4回调不会被触发 ]] if listener.need_clean == true and listener.emit_count == 0 then for i = #listener.list, 1, -1 do if listener.list[i].need_remove then table.remove(listener.list, i) end end listener.need_clean = false; end end --[[ 修改已经注册的一个事件监听的优先级 ]] function EventSystem:update_priority( event, func, params ) event = tostring(event) if not self._listeners[event] then return end local event_listener = self._listeners[event] params = params or {} local priority = params.priority or 0 for i,cb in ipairs(event_listener.list) do if cb.func == func and cb.target == params.target then cb.priority = priority event_listener.need_sort = true self:sort(event_listener); break; end end end return EventSystem ================================================ FILE: core/object.lua ================================================ --[[--用于模拟面向对象 @usage require("core/object") @module object @author iwiniwin Date 2020-01-16 13:27:14 Last Modified by iwiniwin Last Modified time 2020-01-16 13:45:13 ]] -------------------------------------------------------------------------------- -- Description: Provide object mechanism for lua -- Note for the object model here: -- 1.The feature like C++ static members is not support so perfect. -- What that means is that if u need something like c++ static members, -- U can access it as a rvalue like C++, but if u need access it -- as a lvalue u must use [class.member] to access,but not [object.member]. -- 2.The function delete cannot release the object, because the gc is based on -- reference count in lua.If u want to relase all the object memory, u have to -- set the obj to nil to enable lua gc to recover the memory after calling delete. ---------------------Global functon class --------------------------------------------------- --Parameters: super -- The super class -- autoConstructSuper -- If it is true, it will call super ctor automatic,when -- new a class obj. Vice versa. --Return : return an new class type --Note : This function make single inheritance possible. --------------------------------------------------------------------------------------------- --- -- 用于定义一个类. -- -- @param #table super 父类。如果不指定,则表示不继承任何类,如果指定,则该指定的对象也必须是使用class()函数定义的类。 -- @param #boolean autoConstructSuper 是否自动调用父类构造函数,默认为true。如果指定为false,若不在ctor()中手动调用super()函数则不会执行父类的构造函数。 -- @return #table class 返回定义的类。 -- @usage -- Human = class() -- Human.ctor = function(self) -- self.m_type = "human" -- end -- Human.dtor = function(self) -- print_string("deleted") -- end -- Human.speak = function(self) -- print_string("I am a " .. self.m_type) -- end -- -- Man = class(Human, true) -- Man.ctor = function(self, name) -- self.m_sex = "m" -- self.m_name = name -- end function class( super, autoConstructSuper ) local classType = { autoConstructSuper = autoConstructSuper or (autoConstructSuper == nil) } if super then classType.super = super local mt = getmetatable(super) setmetatable(classType, {__index = super}) else classType.setDelegate = function( self, delegate ) self.m_delegate = delegate end end return classType end ---------------------Global functon super ---------------------------------------------- --Parameters: obj -- The current class which not contruct completely. -- ... -- The super class ctor params. --Return : return an class obj. --Note : This function should be called when newClass = class(super,false). ----------------------------------------------------------------------------------------- --- -- 手动调用父类的构造函数. -- 只有当定义类时采用class(super,false)的调用方式时才可以调用此方法,若此时不手动调用则不会执行父类的构造函数。 -- **只能在子类的构造函数中调用。** -- @param #table obj 类的实例。 -- @param ... 父类构造函数需要传入的参数。 -- @usage -- local baseClass = class() -- local derivedClass = class(baseClass,false) -- derivedClass.ctor = function() -- super(self) - -此处如果不手动调用super()则不会执行基类的ctor() -- end function super(obj, ...) do local create; create = function(c, ...) if c.super and c.autoConstructSuper then create(c.super, ...); end if rawget(c,"ctor") then obj.currentSuper = c.super; c.ctor(obj, ...); elseif rawget(c, "__init__") then obj.currentSuper = c.super; c.__init__(obj, ...); end end create(obj.currentSuper, ...); end end ---------------------Global functon new ------------------------------------------------- --Parameters: classType -- Table(As Class in C++) -- ... -- All other parameters requisted in constructor --Return : return an object --Note : This function is defined to simulate C++ new function. -- First it called the constructor of base class then to be derived class's. ----------------------------------------------------------------------------------------- --- -- 创建一个类的实例. -- 调用此方法时会按照类的继承顺序,自上而下调用每个类的构造函数,并返回新创建的实例。 -- -- @param #table classType 类名。 使用class()返回的类。 -- @param ... 构造函数需要传入的参数。 -- @return #table obj 新创建的实例。 -- @usage -- local me = new(Man, "zzp") -- me:speak() function new(classType, ...) local obj = {}; setmetatable(obj, { __index = classType, __object=1}); do local create; create = function(c, ...) if c.super and c.autoConstructSuper then create(c.super, ...); end if rawget(c,"ctor") then obj.currentSuper = c.super; c.ctor(obj, ...); elseif rawget(c,"__init__") then obj.currentSuper = c.super; c.__init__(obj, ...); end end create(classType, ...); end obj.currentSuper = nil; return obj; end ---------------------Global functon delete ---------------------------------------------- --Parameters: obj -- the object to be deleted --Return : no return --Note : This function is defined to simulate C++ delete function. -- First it called the destructor of derived class then to be base class's. ----------------------------------------------------------------------------------------- --- -- 删除某个实例. -- 类似c++里的delete ,会按照继承顺序,依次自下而上调用每个类的析构方法。 -- -- **需要留意的是,删除此实例后,lua里该对象的引用(obj)依然有效,再次使用可能会发生无法预知的意外。** -- -- @param #table obj 需要删除的实例。 function delete(obj) do local destory = function(c) while c do if rawget(c,"dtor") then c.dtor(obj); end c = getmetatable(c); c = c and c.__index; end end destory(obj); end end ---------------------Global functon delete ---------------------------------------------- --Parameters: class -- The class type to add property -- varName -- The class member name to be get or set -- propName -- The name to be added after get or set to organize a function name. -- createGetter-- if need getter, true,otherwise false. -- createSetter-- if need setter, true,otherwise false. --Return : no return --Note : This function is going to add get[PropName] / set[PropName] to [class]. ----------------------------------------------------------------------------------------- --- -- 为类定义一个property (java里的getter/setter). -- 会自动为类生成getter/setter方法。 -- -- @param #table class 使用class()方法定义的类。 -- @param #string varName 类里的成员变量名。 -- @param #string propName 属性名,也就是生成的方法setXX/getXX里的'XX'。 -- @param #boolean createGetter 是否生成getter。 -- @param #boolean createSetter 是否生成setter。
-- 如果createGetter不为false或nil,则给class生成一个get#propName()方法,可以获取class的varName的值。
-- 如果createSetter不为false或nil,则给class生成一个set#propName(Value)方法,可以设置class的varName为Value。 -- @usage -- property(Man, "m_name", "Name", true, false) -- local me = new(Man, "zzp") -- print_string(me:getName()) function property(class, varName, propName, createGetter, createSetter) createGetter = createGetter or (createGetter == nil); createSetter = createSetter or (createSetter == nil); if createGetter then class[string.format("get%s",propName)] = function(self) return self[varName]; end end if createSetter then class[string.format("set%s",propName)] = function(self,var) self[varName] = var; end end end ---------------------Global functon delete ---------------------------------------------- --Parameters: obj -- A class object -- classType -- A class --Return : return true, if the obj is a object of the classType or a object of the -- classType's derive class. otherwise ,return false; ----------------------------------------------------------------------------------------- --- -- 判断一个对象是否是某个类(包括其父类)的实例. -- 类似java里的instanceof。 -- -- @param obj 需要判断的对象。 -- @param classType 使用class()方法定义的类。 -- @return #boolean 若obj是classType的实例,则返回true;否则,返回false。 -- @usage -- local me = new(Man, "zzp") -- if typeof(me, Man) == true then -- print_string("me is instance of Man") -- end function typeof(obj, classType) if type(obj) ~= type(table) or type(classType) ~= type(table) then return type(obj) == type(classType); end while obj do if obj == classType then return true; end obj = getmetatable(obj) and getmetatable(obj).__index; end return false; end ---------------------Global functon delete ---------------------------------------------- --Parameters: obj -- A class object --Return : return the object's type class. ----------------------------------------------------------------------------------------- --- -- 通过一个对象反向得到此对象的类. -- -- @param obj 对象。 -- @return class 此对象的类。 -- @return #nil 如果obj不是某个类的对象,则返回nil。 function decltype(obj) if type(obj) ~= type(table) or obj.autoConstructSuper == nil then --error("Not a class obj"); return nil; end if rawget(obj,"autoConstructSuper") ~= nil then --error("It is a class but not a class obj"); return nil; end local class = getmetatable(obj) and getmetatable(obj).__index; if not class then --error("No class reference"); return nil; end return class; end function mkproperty(getter, setter) return setmetatable({}, { __get = getter, __set = setter, }) end ================================================ FILE: core/sandbox.lua ================================================ --[[--lua沙盒 @module sandbox @author iwiniwin Date 2020-01-16 13:27:14 Last Modified by iwiniwin Last Modified time 2020-01-16 13:45:08 ]] --[[ 2. lua沙盒 理解:lua沙盒就是通过改变上下文环境,使函数可以在不同的环境表中运行,访问得到限制,从而避免相互 影响。可以利用其构建一个安全的环境,用来执行一些未知的危险代码 ]] function test( ... ) print("hello") end test() -- 设置test在环境表e运行,此时test不能调用_G中的print -- local e = {} -- setfenv(test, {}) -- test() -- setfenv(test, _G) -- test() -- 在e环境表对x赋值不会影响到f环境 local e = {print = print, setfenv = setfenv} setfenv(1, e) x = 3 print(x) local f = {print = print, setfenv = setfenv} setfenv(1, f) print(x) ---保护环境 人人有责 local env = getfenv(); local protectEnv = function(env) local mt = getmetatable(env); local cache = {}; mt.__newindex = function( t,k,v ) if cache[k] == nil then cache[k] = v; else if cache[k] ~= v then error("不允许复写" .. k) end end rawset(mt.__index, k, v) end end protectEnv(env); -- http://timothyqiu.com/archives/lua-note-sandboxes/ ================================================ FILE: init.lua ================================================ --[[-- LuaKit初始化函数,初始化全局变量 @module init @author iwiniwin Date 2021-09-04 20:20:39 Last Modified by iwiniwin Last Modified time 2021-09-04 20:20:39 ]] local init = function ( requirePrefix ) if requirePrefix then import = function ( modname ) return require(requirePrefix .. modname) end else import = require end import("core.object") import("lib.string") local func_lib = import("lib.function") time = import("lib.time") dump = import("utils.dump") dump_to_file = import("utils.dump_to_file").dump_to_file handler = func_lib.handler end return init ================================================ FILE: lib/file.lua ================================================ --[[--文件操作 @module File @author iwiniwin Date 2020-01-16 13:27:14 Last Modified by iwiniwin Last Modified time 2020-01-16 13:45:04 ]] local File = {} ---插入文件内容到指定行 --@table content 插入内容 --@string path 目标文件 --@number row 行数,默认插入到文件末尾 File.insert_to_file = function (content, path, row) file = io.open(path, "r") local lines = {} for line in file:lines() do if row and #lines == row - 1 then for _, l in ipairs(content) do table.insert(lines, l) end end table.insert(lines, line) end file:close() if not row then for _, l in ipairs(content) do table.insert(lines, l) end end file = io.open(path, "w+") for _,line in ipairs(lines) do file:write(line .. "\n") end file:close() end ---查询文件是否包含指定内容 --@string path 目标文件 --@string text 关键字 --@return 关键字所在行数,不存在返回空表 File.find_text = function (path, text) local result = {} local row = 0 file = io.open(path, "r") for line in file:lines() do row = row + 1 if string.find(line, text) then table.insert(result, row) end end return result end return File ================================================ FILE: lib/function.lua ================================================ --[[--常用函数集合 @module Function @author iwiniwin Date 2020-02-27 13:27:14 Last Modified by iwiniwin Last Modified time 2020-01-16 13:45:04 ]] local M = {} function M.handler( obj, method ) return function ( ... ) if obj and method then method(obj, ...) end end end return M ================================================ FILE: lib/string.lua ================================================ --[[--字符串操作 @module string @author iwiniwin Date 2020-01-16 13:27:14 Last Modified by iwiniwin Last Modified time 2020-01-16 13:44:59 ]] -- local string = {}; ---Allows the ability to index into a string using square-bracket notation -- For example: -- s = "hello" -- s[1] = "h" getmetatable('').__index = function(str, i) if (type(i) == 'number') then return string.sub(str, i, i) end return string[i] end --- Allows the ability to index into a string like above, but using normal brackes to -- return the substring -- For example: -- s = "hello" -- s(2,5) = "ello" -- -- However, it also allows indexing into the string to return the byte (unicode) value -- of the character found at the index. This only occurs if the second value is omitted -- For example: -- s = "hello" -- s(2) = 101 (e) -- -- Furthermore, it also allows for the ability to replace a character at the given index -- with the given characters, iff the second value is a string -- For example: -- s = "hello" -- s(2,'p') = "hpllo" getmetatable('').__call = function(str, i, j) if (type(i) == 'number' and type(j) == 'number') then return string.sub(str, i, j) elseif (type(i) == 'number' and type(j) == 'string') then return table.concat{string.sub(str, 1, i - 1), j, string.sub(str, i + 1)} elseif (type(i) == 'number' and type(j) == 'nil') then return string.byte(str, i) end return string[i] end ---Checks to see if the string starts with the given characters function string.starts_with(str, chars) return chars == '' or string.sub(str, 1, string.len(chars)) == chars end ---Checks to see if the string ends with the given characters function string.ends_with(str, chars) return chars == '' or string.sub(str, -string.len(chars)) == chars end ---Removes the length from the start of the string, returning the result ---Length can be a number or string function string.remove_from_start(str, length) if (type(length) == 'number') then return string.sub(str, length + 1, string.len(str)) elseif (type(length) == 'string') then return string.sub(str, string.len(length) + 1, string.len(str)) else return str end end ---Removes the length from the end of the string, returning the result ---Length can be a number or string function string.remove_from_end(str, length) if (type(length) == 'number') then return string.sub(str, 1, string.len(str) - length) elseif (type(length) == 'string') then return string.sub(str, 1, string.len(str) - string.len(length)) else return str end end ---Removes a number of occurrences of the pattern from the string ---If limit is blank, removes all occurrences function string.remove(str, pattern, limit) if (pattern == '' or pattern == nil) then return str end if (limit == '' or limit == nil) then str = string.gsub(str, pattern, '') else str = string.gsub(str, pattern, '', limit) end return str end --拼接字符串 function string.concat(str1, str2, concatStr) local ret = table.concat({str1, str2}, concatStr) return ret end ---Removes all occurrences of the pattern from the string function string.remove_all(str, pattern) if (pattern == '' or pattern == nil) then return str end str = string.gsub(str, pattern, '') return str end ---Removes the first occurrence of the pattern from the string function string.remove_first(str, pattern) if (pattern == '' or pattern == nil) then return str end str = string.gsub(str, pattern, '', 1) return str end ---Returns whether the string contains the pattern function string.contains(str, pattern) if (pattern == '' or string.find(str, pattern, 1)) then return true end return false end ---A case-insensitive string.find, returning start and end position of pattern in string function string.findi(str, pattern) return string.find(string.lower(str), string.lower(pattern), 1) end ---Returns the first substring which matches the pattern in the string from a start index function string.find_pattern(str, pattern, start) if (pattern == '' or pattern == nil) then return '' end if (start == '' or start == nil) then start = 1 end return string.sub(str, string.find(str, pattern, start)) end ------Split the string by the given pattern, returning an array of the result ------If pattern is omitted or nil, then default is to split on spaces ------Array index starts at 1 ---function string.split(str, pattern) --- local split = {} --- local index = 1 --- if (pattern == '' or pattern == nil) then --- pattern = '%s' --- end --- local previousstart = 1 --- local startpos, endpos = string.find(str, pattern, 1) --- while (startpos ~= nil) do --- split[index] = string.sub(str, previousstart, startpos - 1) --- previousstart = endpos + 1 --- index = index + 1 --- startpos, endpos = string.find(str, pattern, endpos + 1) --- end --- split[index] = string.sub(str, previousstart, string.len(str)) --- return split ---end ---分割字字符串 --@usage split[index] = string.sub(str, previousstart, string.len(str)) --[[-- 分割字字符串 ]] function string.split(str, delimiter) if (delimiter == '') then return false end local pos, arr = 0, {} -- for each divider found for st, sp in function() return string.find(str, delimiter, pos, true) end do table.insert(arr, string.sub(str, pos, st - 1)) pos = sp + 1 end table.insert(arr, string.sub(str, pos)) return arr end ---Returns the array of word contained within the string ---Array index starts at 1 function string.to_word_array(str) local words = {} local index = 1 for word in string.gmatch(str, '%w+') do words[index] = word index = index + 1 end return words end ---Returns the number of letters within the string function string.letter_count(str) local _, count = string.gsub(str, '%a', '') return count end ---Returns the number of spaces within the string function string.space_count(str) local _, count = string.gsub(str, '%s', '') return count end ---Returns the number of times the pattern occurs within the string function string.pattern_count(str, pattern) if (pattern == '' or pattern == nil) then return nil end local _, count = string.gsub(str, pattern, '') return count end ---Returns a table of how many of each character appears in the string ---Table in the format: ["char"] = 2 function string.char_totals(str) local totals = {} local temp = '' for i = 1, string.len(str), 1 do temp = str[i] if (totals[temp]) then totals[temp] = totals[temp] + 1 else totals[temp] = 1 end end return totals end --模糊搜索,返回true、false,匹配单词中如果含有特殊字符串,返回false function string.fuzzy_match(sourceStr, searchStr) if string.find(searchStr,"[().%+-*?[^$]")then return end sourceStr = string.upper(sourceStr) searchStr = string.upper(searchStr) searchStr = string.gsub(searchStr,"",".*") if string.find(sourceStr,searchStr) then return true end end ---Returns the number of words within the string function string.word_count(str) local _, count = string.gsub(str, '%w+', '') return count end ---Returns a string which contains the lengths of each each word found in the given string function string.word_length(str) local lengths = string.gsub(str, '%w+', function(w) return string.len(w) end) return lengths end ---Returns a table of how many of each word appears in the string ---Table in the format: ["word"] = 2 function string.word_totals(str) local totals = {} for word in string.gmatch(str, '%w+') do if (totals[word]) then totals[word] = totals[word] + 1 else totals[word] = 1 end end return totals end ---Returns byte (unicode) representation of each character within the string as an array ---Array index starts at 1 function string.to_byte_array(str) local bytes = {} for i = 1, string.len(str), 1 do bytes[i] = string.byte(str, i) end return bytes end ---Returns character representation of each character within the string as an array ---Array index starts at 1 function string.to_char_array(str) local chars = {} for i = 1, string.len(str), 1 do chars[i] = str[i] end return chars end ---Returns a string where occurrences of the pattern are put into upper-case function string.pattern_to_upper(str, pattern) if (pattern == '' or pattern == nil) then return str end local upper = string.gsub(str, pattern, string.upper) return upper end ---Returns a string where occurrences of the pattern are put into lower-case function string.pattern_to_lower(str, pattern) if (pattern == '' or pattern == nil) then return str end local lower = string.gsub(str, pattern, string.lower) return lower end ---Returns a string, where the given string's occurrences of the pattern is replaced by ---the given characters, restricted by the given limit function string.replace(str, pattern, chars, limit) if (pattern == '' or pattern == nil) then return str end if (limit == '' or limit == nil) then str = string.gsub(str, pattern, chars) else str = string.gsub(str, pattern, chars, limit) end return str end ---Replaces the character at the given index with the given characters function string.replace_at(str, index, chars) return table.concat{string.sub(str, 1, index - 1), chars, string.sub(str, index + 1)} end ---Returns a string, where the given string's occurrences of the pattern is replaced by ---the given characters function string.replace_all(str, pattern, chars) if (pattern == '' or pattern == nil) then return str end str = string.gsub(str, pattern, chars) return str end ---Returns a string, where the given string's first occurrence of the pattern is replaced ---by the given characters function string.replace_first(str, pattern, chars) if (pattern == '' or pattern == nil) then return str end str = string.gsub(str, pattern, chars, 1) return str end ---Returns the index within the string for the first occurrence of the pattern after the ---given starting index function string.index_of(str, pattern, start) if (pattern == '' or pattern == nil) then return nil end if (start == '' or start == nil) then start = 1 end local position = string.find(str, pattern, start) return position end ---Returns the index within the string for the first occurrence of the pattern function string.first_index_of(str, pattern) if (pattern == '' or pattern == nil) then return nil end local position = string.find(str, pattern, 1) return position end ---Returns the index within the string for the last occurrence of the pattern function string.last_index_of(str, pattern) if (pattern == '' or pattern == nil) then return nil end local position = string.find(str, pattern, 1) local previous = nil while (position ~= nil) do previous = position position = string.find(str, pattern, previous + 1) end return previous end ---Returns the character at the specified index in the string function string.char_at(str, index) return str[index] end ---Returns the byte (unicode) value of the character at given index in the string ---Basically the same as 'string.byte' function string.byte_at(str, index) return string.byte(str, index) end ---Returns the byte (unicode) value for the single given character ---nil is returned if not single character or otherwise function string.byte_value(char) if (string.len(char) == 1) then return string.byte(char, 1) end return nil end ---Compares two strings lexiographically. 1 is returned if str1 is greater than ---str2. -1 if str1 is less than str2. And 0 if they are equal ---This comparing is case-sensitive function string.compare(str1, str2) local len1 = string.len(str1) local len2 = string.len(str2) local smallestLen = 0; if (len1 <= len2) then smallestLen = len1 else smallestLen = len2 end for i = 1, smallestLen, 1 do if (str1(i) > str2(i)) then return 1 elseif (str1(i) < str2(i)) then return -1 end end local lengthDiff = len1 - len2 if (lengthDiff < 0) then return -1 elseif (lengthDiff > 0) then return 1 else return 0 end end ---Compares two strings lexiographically. 1 is returned if str1 is greater than ---str2. -1 if str1 is less than str2. And 0 if they are equal ---This comparing is case-insensitive function string.comparei(str1, str2) return string.compare(string.lower(str1), string.lower(str2)) end ---Returns whether the two strings are equal to one another. True of they are, ---false otherwise ---This equals function is case-sensitive function string.equal(str1, str2) local len1 = string.len(str1) local len2 = string.len(str2) if (len1 ~= len2) then return false end for i = 1, len1, 1 do if (str1[i] ~= str2[i]) then return false end end return true end ---Returns whether the two strings are equal to one another. True of they are, ---false otherwise ---This equals function is case-insensitive function string.equali(str1, str2) return string.equal(string.lower(str1), string.lower(str1)) end ---Returns the string representation of the given value. Be it either a ---number, boolean, string or a table. nil is returned otherwise for functions, ---threads, userdata and nil. function string.valueof(value) local t = type(value) if (t == 'string') then return value elseif (t == 'number') then return '' .. value .. '' elseif (t == 'boolean') then if (value) then return "true" else return "false" end elseif (t == 'table') then local str = "" for k,v in pairs(value) do str = str .. "[" .. k .. "] = " .. v .. "\n" end str = string.sub(str, 1, string.len(str) - string.len("\n")) return str else return "nil" end end ---Returns a string, where the given characters have been inserted into the ---string at the required index. An index of 0 specifies the front of the string function string.insert(str, chars, index) if (index == 0) then return chars .. str elseif (index == string.len(str)) then return str .. chars else return string.sub(str, 1, index) .. chars .. string.sub(str, index + 1, string.len(str)) end end ---Returns a string, where the given characters have been inserted into the ---string rep times at the required index. An index of 0 specifies the front of ---the string ---For example: -- string.insert_rep("ello", "h", 4, 0) = "hhhhello" function string.insert_rep(str, chars, rep, index) local rep = string.rep(chars, rep) return string.insert(str, rep, index) end ---Returns a string where all characters starting at the given index have ---been removed up to the end of the string (including the start index character) function string.remove_to_end(str, index) if (index == 1) then return "" else return string.sub(str, 1, index - 1) end end ---Returns a string where all char_aters starting at the given index have ---been removed down to the start of the string (including the start index character) function string.remove_to_start(str, index) if (index == string.len(str)) then return "" else return string.sub(str, index + 1, string.len(str)) end end ---Returns a string where the given string has had any leading and ---trailing characters removed ---If char is left blank, then whitespaces are removed --@usage --string.trim("[[[word[[[", "%[") => "word" --string.trim(" word ") => "word" function string.trim(str, char) if (char == '' or char == nil) then char = '%s' end local trimmed = string.gsub(str, '^' .. char .. '*(.-)' .. char .. '*$', '%1') return trimmed end ---Returns a string where the given string has had any leading ---characters removed ---If char is left blank, then whitespaces are removed function string.trim_start(str, char) if (char == '' or char == nil) then char = '%s' end local trimmed = string.gsub(str, '^' .. char .. '*', '') return trimmed end ---Returns a string where the gievn string has had any trailing ---characters removed ---If char is left blank, then whitespaces are removed function string.trim_end(str, char) if (char == '' or char == nil) then char = '%s' end local length = string.len(str) while (length > 0 and string.find(str, '^' .. char .. '', length)) do length = length - 1 end return string.sub(str, 1, length) end ---Returns a string where the given string has had variables substituted into it --@usage --string.subvar("x=$(x), y=$(y)", {x=200, y=300}) => "x=200, y=300" --string.subvar("x=$(x), y=$(y)", {['x']=200, ['y']=300}) => "x=200, y=300" function string.subvar(str, _table) str = string.gsub(str, "%$%(([%w_]+)%)", function(key) local value = _table[key] return value ~= nil and tostring(value) end) return str end ---Rotates the string about the given index, returning the result. --@usage --string.rotate("hello, 3) => "lohel" function string.rotate(str, index) local str1 = string.sub(str, 1, index) local str2 = string.sub(str, index + 1, string.len(str)) return str2 .. str1 end ---Averages the two strings together. This is done by adding the byte (unicode) values ---of parallel characters and dividing by 2. function string.average(str1, str2) local len1 = string.len(str1) local len2 = string.len(str2) local smallestLen = 0 local newstr = '' if (len1 <= len2) then smallestLen = len1 else smallestLen = len2 end for i = 1, smallestLen, 1 do newstr = newstr .. string.char( (str1(i) + str2(i)) / 2 ) end if (len1 <= len2) then newstr = newstr .. string.sub(str2, smallestLen + 1, string.len(str2)) else newstr = newstr .. string.sub(str1, smallestLen + 1, string.len(str1)) end return newstr end ---Swaps the two characters at the given indices of the string function string.swap(str, index1, index2) local temp = str[index1] str = str(index1, str[index2]) return str(index2, temp) end ---Sorts the string into ascending order according to their unicode values. function string.sort_ascending(str) local chars = str:to_char_array() table.sort(chars, function(a,b) return a(1) < b(1) end) return table.concat(chars) end ---Sorts the string into descending order according to their unicode values. function string.sort_descending(str) local chars = str:to_char_array() table.sort(chars, function(a,b) return a(1) > b(1) end) return table.concat(chars) end ---Returns the character with the highest byte (unicode) value function string.highest(str) local s = string.sort_descending(str) return s[1] end ---Returns the character with the lowest byte (unicode) value function string.lowest(str) local s = string.sort_ascending(str) return s[1] end ---Checks to see if the string is empty function string.is_empty(str) if (str == '' or str == nil) then return true end return false end ---Returns a table for the percentage of how much the string is formed of ---each word. --@usage --string.word_percents("hello, world!") = {"hello" = 38.46, "world" = 38.46} function string.word_percents(str) local t = string.word_totals(str) local count = string.len(str) for k,v in pairs(t) do t[k] = ((string.len(k) * v) / count) * 100.0 end return t end ---Returns the percentage for how much of the string is formed by the given word --@usage --string.word_percent("hello, world!", "hello") = 50 function string.word_percent(str, word) local t = string.word_percents(str) if (t[word]) then return t[word] end return 0 end ---Returns a table for the percentage of how much the string is formed of ---each character. --@usage --string.char_percents("hello") = {"h" = 20, "e" = 20, "l" = 40, "o" = 20} function string.char_percents(str) local t = string.char_totals(str) local count = string.len(str) for k,v in pairs(t) do t[k] = (v/count) * 100.0 end return t end ---Returns the percentage for how much of the string is formed by the given character --@usage --string.char_percent("hello", "h") = 20 function string.char_percent(str, char) local t = string.char_percents(str) if (t[char]) then return t[char] end return 0 end ---Returns the percentage for how much of the string is formed by whitespace function string.space_percent(str) local count = string.space_count(str) return (count / string.len(str)) * 100.0 end ---Returns the number of uppercase characters in the string function string.upper_count(str) local _, count = string.gsub(str, '%u', '') return count end ---Returns the percentage for how much of the string is formed by uppercase ---characters function string.upper_percent(str) local count = string.upper_count(str) return (count / string.len(str)) * 100.0 end ---Returns the number of lowercase characters in the string function string.lower_count(str) local _, count = string.gsub(str, '%l', '') return count end ---Returns the percentage for how much of the string is formed by lowercase ---characters function string.lower_percent(str) local count = string.lower_count(str) return (count / string.len(str)) * 100.0 end ---Returns the number of single digits in the string function string.digit_count(str) local _, count = string.gsub(str, '%d', '') return count end ---Returns a table of how many of each single digit appears in the string function string.digit_totals(str) local totals = {} for digit in string.gmatch(str, '%d') do if (totals[digit]) then totals[digit] = totals[digit] + 1 else totals[digit] = 1 end end return totals end ---Returns a table for the percentage of how much the string is formed of ---each single digit. --@usage --string.digit_percents("hello, 2world!") = {"2" = 7.14} function string.digit_percents(str) local t = string.digit_totals(str) local count = string.len(str) for k,v in pairs(t) do t[k] = ((string.len(k) * v) / count) * 100.0 end return t end ---Returns the percentage for how much of the string is formed by the given single digit --@usage --string.digit_percent("hello2", "2") = 16.67 function string.digit_percent(str, digit) local t = string.digit_percents(str) if (t[digit]) then return t[digit] end return 0 end ---Returns the amount of punctuation in the string function string.punc_count(str) local _, count = string.gsub(str, '%p', '') return count end ---Returns a table of how many of each punctuation appears in the string function string.punc_totals(str) local totals = {} for punc in string.gmatch(str, '%p') do if (totals[punc]) then totals[punc] = totals[punc] + 1 else totals[punc] = 1 end end return totals end ---Returns a table for the percentage of how much the string is formed of ---each punctuation. --@usage --string.punc_percents("hello, world!") = {"," = 7.69, "!" = 7.69} function string.punc_percents(str) local t = string.punc_totals(str) local count = string.len(str) for k,v in pairs(t) do t[k] = ((string.len(k) * v) / count) * 100.0 end return t end ---Returns the percentage for how much of the string is formed by the given punctuation --@usage --string.punc_percent("hello, world!", ",") = 7.69 function string.punc_percent(str, punc) local t = string.punc_percents(str) if (t[punc]) then return t[punc] end return 0 end ---Concatenates an array of strings together, with optional seperation characters ---This is basically the same as doing table.concat(table, sep) function string.join(array, sep) return table.concat(array, sep) end ---Returns the Levenshtein distance between the two given strings function string.levenshtein(str1, str2) local len1 = string.len(str1) local len2 = string.len(str2) local matrix = {} local cost = 0 if (len1 == 0) then return len2 elseif (len2 == 0) then return len1 elseif (str1 == str2) then return 0 end for i = 0, len1, 1 do matrix[i] = {} matrix[i][0] = i end for j = 0, len2, 1 do matrix[0][j] = j end for i = 1, len1, 1 do for j = 1, len2, 1 do if (str1[i] == str2[j]) then cost = 0 else cost = 1 end matrix[i][j] = math.min(matrix[i-1][j] + 1, matrix[i][j-1] + 1, matrix[i-1][j-1] + cost) end end return matrix[len1][len2] end ---Makes the string's first character lowercase function string.lower_first(str) return str(1, string.lower(str[1])) end ---Makes the string's first character uppercase function string.upper_first(str) return str(1, string.upper(str[1])) end ---Randomly shuffles the given string function string.shuffle(str) local temp = '' local length = string.len(str) local ran1, ran2 = 0, 0 math.randomseed(os.time()) for i = 1, length , 1 do ran1 = math.random(length) ran2 = math.random(length) temp = str[ran1] str = str(ran1, str[ran2]) str = str(ran2, temp) end return str end ---Converts the given integer value into a binary string of length limit ---If limit is omitted, then a binary string of length 8 is returned function dectobin(dec, limit) if (limit == '' or limit == nil) then limit = 8 end local bin = '' local rem = 0 for i = 1, dec, 1 do rem = dec % 2 dec = dec - rem bin = rem .. bin dec = dec / 2 if (dec <= 0) then break end end local padding = limit - (string.len(bin) % limit) if (padding ~= limit) then bin = string.insert_rep(bin, '0', padding, 0) end return bin end ---Returns the uuencoded representation of the given string function string.uuencode(str) local padding = 3 - (string.len(str) % 3) if (padding ~= 3) then str = string.insert_rep(str, string.char(1), padding, string.len(str)) end local uuenc = '' local bin1, bin2, bin3, binall = '', '', '', '' for i = 1, string.len(str) - 2, 3 do bin1 = dectobin(string.byte(str[i]), 8) bin2 = dectobin(string.byte(str[i+1]), 8) bin3 = dectobin(string.byte(str[i+2]), 8) binall = bin1 .. bin2 .. bin3 uuenc = uuenc .. string.char(tonumber(binall(1,6), 2) + 32) uuenc = uuenc .. string.char(tonumber(binall(7,12), 2) + 32) uuenc = uuenc .. string.char(tonumber(binall(13,18), 2) + 32) uuenc = uuenc .. string.char(tonumber(binall(19,24), 2) + 32) end return uuenc end ---Returns the actual string from a uuencoded string function string.uudecode(str) local padding = 4 - (string.len(str) % 4) if (padding ~= 4) then str = string.insert_rep(str, string.char(1), padding, string.len(str)) end local uudec = '' local bin1, bin2, bin3, bin4, binall = '', '', '', '', '' for i = 1, string.len(str) - 3, 4 do bin1 = dectobin(string.byte(str[i]) - 32, 6) bin2 = dectobin(string.byte(str[i+1]) - 32, 6) bin3 = dectobin(string.byte(str[i+2]) - 32, 6) bin4 = dectobin(string.byte(str[i+3]) - 32, 6) binall = bin1 .. bin2 .. bin3 .. bin4 uudec = uudec .. string.char(tonumber(binall(1,8), 2)) uudec = uudec .. string.char(tonumber(binall(9,16), 2)) uudec = uudec .. string.char(tonumber(binall(17,24), 2)) end return string.trim(uudec, string.char(1)) end ---Returns a simple hash key for a string. If the check value is ommited ---then the string is hashed by the prime value of 17 ---Best results occur when the check value is prime function string.hash(str, check) local sum = 0 local checksum = 17 local length = string.len(str) if (check ~= '' and check ~= nil) then checksum = check end sum = str(1) + 1 sum = sum + str(length) + length sum = sum + str(length/2) + math.ceil(length/2) return sum % checksum end ---url字符转换 function string.urlencode_char(char) return "%" .. string.format("%02X", string.byte(char)) end ---url字符转换 function string.urlencode(str) ---convert line endings str = string.gsub(tostring(str), "\n", "\r\n") ---escape all characters but alphanumeric, '.' and '-' str = string.gsub(str, "([^%w%.%- ])", string.urlencode_char) ---convert spaces to "+" symbols return string.gsub(str, " ", "+") end function string.ltrim(str) return string.gsub(str, "^[ \t\n\r]+", "") end function string.rtrim(str) return string.gsub(str, "[ \t\n\r]+$", "") end function string.trim(str) str = string.gsub(str, "^[ \t\n\r]+", "") return string.gsub(str, "[ \t\n\r]+$", "") end ---检测手机号码是否正确 function string.is_phone_num( PhoneNumText ) local phoneNum = string.trim(PhoneNumText); local start, length = string.find(phoneNum, "^1[3|4|5|8|7][0-9]%d+$"); ---判断手机号码是否正确 if start ~= nil and length == 11 then return true ; end return false; end ---计算文字utf8的长度 function string.utf8len(str) local len = #str local left = len local cnt = 0 local arr = {0, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc } while left > 0 do local tmp = string.byte(str, -left) local i = #arr while arr[i] do if tmp >= arr[i] then left = left - i break end i = i - 1 end cnt = cnt + 1 end return cnt end --按照utf8格式取子串 function string.utf8_sub_str(str,subLen) if subLen == 0 then return "" end if str == nil then debug.traceback() print(str, "str") end local len = #str local left = len local cnt = 0 local arr = {0, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc } while left > 0 do local tmp = string.byte(str, -left) local i = #arr while arr[i] do if tmp >= arr[i] then left = left - i break end i = i - 1 end cnt = cnt + 1 if cnt >= subLen then break; end end local temp = string.sub(str,0,len - left); return temp end function string.utf8_char_str( str, index ) local last = string.utf8_sub_str(str, index - 1) local tem = string.utf8_sub_str(str, index) local utf8_char_str = string.sub(str, #last + 1, #tem) return utf8_char_str end -- local string = _G['string']; -- for k,v in pairs(string) do -- -- print_string(k) -- -- print_string(v) -- if string[k] then -- -- print("k---",k) -- else -- string[k] = v; -- -- print("k2---",k) -- end -- end return string; ================================================ FILE: lib/table.lua ================================================ --[[--table操作 @module table @author iwiniwin Date 2020-01-16 13:27:14 Last Modified by iwiniwin Last Modified time 2020-01-16 13:45:20 ]] local tableConcat = table.concat local tableInsert = table.insert local type = type local pairs = pairs local tostring = tostring local next = next local TableLib = {} --[[-- 计算表格包含的字段数量 Lua table 的 "#" 操作只对依次排序的数值下标数组有效,TableLib.nums() 则计算 table 中所有不为 nil 的值的个数。 @tparam table t 要计算的表格 @return number 结果 @usage local TableLib = import("bos.utils").TableLib TableLib.nums({a= 1,b=2}) ]] function TableLib.nums(t) local temp = checktable(t) local count = 0 for k, v in pairs(temp) do count = count + 1 end return count end --[[-- 将来源表格中所有键及其值复制到目标表格对象中,如果存在同名键,则覆盖其值 @tparam table dest 目标表格 @tparam table src 来源表格 @usage local dest = {a = 1, b = 2} local src = {c = 3, d = 4} TableLib.merge(dest, src) -- dest = {a = 1, b = 2, c = 3, d = 4} ]] function TableLib.merge(dest, src) if not src or not dest then return; end for k, v in pairs(src) do dest[k] = v end end --[[-- 合并两个表格的内容 @tparam table src1 来源表格1 @tparam table src2 来源表格2 @return table 合并后的新表 @usage local src1 = {a = 1, b = 2} local src2 = {c = 3, d = 4} local temp = TableLib.merge(src1, src2) -- src1 = {a = 1, b = 2} -- temp = {a = 1, b = 2, c = 3, d = 4} ]] function TableLib.merge2(src1, src2) local tb ={} if src1 and next(src1) then for k, v in pairs(src1) do tableInsert(tb,v); end end if src2 and next(src2) then for k, v in pairs(src2) do tableInsert(tb,v); end end return tb; end --[[-- 合并两个表格的以数字开头的内容 @tparam table src1 来源表格1 @tparam table src2 来源表格2 @return table 合并后的新表 @usage local src1 = {a = 1, b = 2, 3} local src2 = {c = 3, d = 4, 4} local temp = TableLib.merge3(src1, src2) return {3, 4} ]] function TableLib.merge3(src1, src2) local tb ={} if src1 and next(src1) then for k, v in pairs(src1) do if type(k) == "number" then tableInsert(tb,v); end end end if src2 and next(src2) then for k, v in pairs(src2) do if type(k) == "number" then tableInsert(tb,v); end end end return tb; end --[[-- 同步数据,把tab2 的数据同步到 tab1(不是合并) @tparam table tab1 来源表格1 @tparam table tab2 来源表格2 @usage local tab1 = {c = 1, b = 2,g=9} local tab2 = {c = 3, d = 4} TableLib.sync(tab1, tab2) -- tab1 = {c = 3, b = 2,g=9} -- tab2 = {c = 3, d = 4} ]] function TableLib.sync(tab1, tab2) for k, v in pairs(tab2) do if tab1[k] ~= nil then tab1[k] = v; end end end --[[-- 从表格中查找指定值,返回其 key,如果没找到返回 nil @tparam table hashtable 表格 @tparam mixed value 要查找的值 @return string 该值对应的 key @usage local hashtable = {name = "dualface", comp = "chukong"} print(TableLib.key_of(hashtable, "chukong")) -- 输出 comp ]] function TableLib.key_of(hashtable, value) for k, v in pairs(hashtable) do if v == value then return k end end return nil end --[[-- 从表格中查找指定值,返回其索引,如果没找到返回 false @tparam table array 表格 @tparam mixed value 要查找的值 @tparam number begin 起始索引值 @return number @usage local a = {"a","b","c"} TableLib.index_of(a,"b",1) ]] function TableLib.index_of(array, value, begin) for i = begin or 1, #array do if array[i] == value then return i end end return false end --[[-- 从表格中删除指定值,返回删除的值的个数 @tparam table array 表格 @tparam mixed value 要删除的值 @tparam boolean remove_all 是否删除所有相同的值 @return number 删除的值的个数 @usage local array = {"a", "b", "c", "c"} print(TableLib.remove_by_value(array, "c", true)) -- 输出 2 ]] function TableLib.remove_by_value(array, value, remove_all) local c, i, max = 0, 1, #array while i <= max do if array[i] == value then table.remove(array, i) c = c + 1 i = i - 1 max = max - 1 if not remove_all then break end end i = i + 1 end return c end --[[-- 判断table是否为空 @tparam table t 表格 @return boolean 是否为空 @usage TableLib.is_empty({}) -- true ]] function TableLib.is_empty(t) if t and type(t)=="table" then --FIXME 此句可以判空,为何还要循环表内元素? return next(t)==nil; end return true; end --[[-- 判断table是否为table @tparam table t 表格 @return boolean 是否为table @usage TableLib.is_table({}) -- true ]] function TableLib.is_table(t) if type(t)=="table" then return true; end return false; end --[[-- 复制table @tparam table st 表格 @return table 复制后的新表 @usage TableLib.copy_tab({1,2,3,4,5}) ]] function TableLib.copy_tab(st) local tab = {} for k, v in pairs(st or {}) do if type(v) ~= "table" then tab[k] = v else tab[k] = TableLib.copy_tab(v) end end return tab end --[[-- 从table1复制到table2 @tparam table target 目标表格 @tparam table source 被复制表格 @usage TableLib.copy_to({1,2,3,4,5},{"c","c","c"}) ]] function TableLib.copy_to(target, source) for _,v in ipairs(source or {}) do table.insert(target, v) end end --[[-- table校验,返回自身或者{} @tparam table t 目标表格 @return table 返回自身或者{} @usage TableLib.checktable({1,2,3,4,5}) ]] function TableLib.checktable(t) return TableLib.verify(t) end --[[-- table校验,返回自身或者{} @tparam table t 目标表格 @return table 返回自身或者{} @usage TableLib.verify({1,2,3,4,5}) ]] function TableLib.verify(t) if t and type(t)=="table" then return t; end return {}; end --[[-- 获取table 元素数量 @tparam table t 目标表格 @return number 数量 @usage TableLib.size({1,2,3,4,5}) ]] function TableLib.size(t) if type(t) ~= "table" then return 0; end local count = 0; for _,v in pairs(t) do count = count + 1; end return count; end --[[-- 比较两个table的内容是否相同 @tparam table t1 目标表格 @tparam table t2 目标表格 @return boolean 是否相同 @usage TableLib.equal({1,2,3,4,5},{5,4,3,2,1}) ]] function TableLib.equal(t1,t2) if type(t1) ~= type(t2) then return false; else if type(t1) ~= "table" then return t1 == t2; else local len1 = TableLib.size(t1); local len2 = TableLib.size(t2); if len1 ~= len2 then return false; else local isEqual = true; for k,v in pairs(t1) do if t2[k] == nil then isEqual = false; break; else if type(t2[k]) ~= type(v) then isEqual = false; break; else if type(v) ~= "table" then if t2[k] ~= v then isEqual = false; break; end else isEqual = TableLib.equal(v,t2[k]); if not isEqual then break; end end end end end return isEqual; end end end end --[[-- 从表里获取n个随机值 @tparam table t 目标表格 @tparam number num 获取个数 @return table 获取的值的table @usage TableLib.random({1,2,3,4,5},2) ]] function TableLib.random(t, num) assert(type(t) == "table", "invalid arg"); local randomList = { } if not num or num > #t then num = #t; end local rangeList = { }; for i,v in ipairs(t) do rangeList[i] = v; end for i = 1, num do local index = math.random(i, #rangeList);--生成一个随机数 rangeList[i], rangeList[index] = rangeList[index], rangeList[i];--交换 randomList[i] = rangeList[i];--交换以后把i位置的牌放到要返回的函数中 end return randomList; end --[[-- 序列化table @tparam table root 目标表格 @return string 结果 @usage TableLib.tostring({1,2,3,4,5}) ]] function TableLib.tostring(root) if not root then return end local cache = { [root] = "root" } local flag = {}; local function _dump(t,name) local mt = getmetatable(t) if mt and mt.__tostring then return tostring(t) end local temp = {} for i,v in ipairs(t) do flag[i] = true; if cache[v] then tableInsert(temp, cache[v]) elseif type(v) == "table" then cache[v] = string.format("%s[%d]", name, i) tableInsert(temp, string.format("%s", _dump(v, cache[v]))) else tableInsert(temp, tostring(v)) end end for k,v in pairs(t) do if not flag[k] then local key = tostring(k) if cache[v] then tableInsert(temp, string.format("%s=%s", key, cache[v])) elseif type(v) == "table" then cache[v] = string.format("%s.%s", name, key) tableInsert(temp, string.format("%s=%s", key, _dump(v, cache[v]))) else tableInsert(temp, string.format("%s=%s", key, tostring(v))) end end end return string.format("{%s}", tableConcat(temp,",")); end return _dump(root, "root"); end --[[-- 合并多个表到src @tparam table src 目标表格 @tparam tables ... 待合并表,可多个 @usage TableLib.deep_merge({1,2,3,4,5},{"c","c","s","c"},{a = "ccc"}) ]] function TableLib.deep_merge( src, ... ) local arg = {...}; for i,v1 in ipairs(arg) do for k,v in pairs(v1) do if type(v) == "table" and type(src[k]) == "table" then TableLib.deep_merge(src[k], v); else src[k] = v; end end end end --[[-- 遍历表,处理函数返回true终止 @tparam table t 目标表格 @tparam function func 处理函数 @usage local function a(i,v) -- return true 返回true时停止遍历 end TableLib.select({1,2,3,4}, a) ]] function TableLib.select(t, func) for i,v in ipairs(t) do if func and func(i,v) == true then return i, v; end end end --[[-- 遍历所有元素,返回所有处理函数return true的元素 @tparam table t 目标表格 @tparam function func 处理函数 @return table array所有处理函数return true的元素 @usage local function a(i,v) -- return true 返回true时最后要返回该元素 end TableLib.select({1,2,3,4}, a) ]] function TableLib.select_all(t, func) local temp = {}; for i,v in ipairs(t) do if func and func(i,v) == true then temp[#temp+1] = v; end end return temp; end --[[-- 检索某个元素 @tparam table t 目标表格 @tparam mixed ... 键值索引,传入多个会递归检索 @return nil|mixed 返回键值对应的值 @usage local a = { b = { -- 传入"b"作为参数时就返回该表 c = 1 -- 在"b"之后传入"c",就返回1 } } TableLib.retrive(a,"b","c") -- return 1 ]] function TableLib.retrive(t, ...) if not t then return end local arg = {...} local tmp = t; for _,v in ipairs( arg ) do if tmp[v] then tmp = tmp[v]; else return; end end return tmp; end --[[-- 设置table为只读 @tparam table t 目标表格 @usage local a = {1,1,1,1} TableLib.lock_write(a) ]] function TableLib.lock_write(t) local mt = getmetatable(t) or {}; mt.__newindex = function(_,k,v) error(string.format("can't write [%s] into table",k)) end; if not getmetatable(t) then setmetatable(t, mt); end end --[[-- 取消设置table为只读 @tparam table t 目标表格 @usage local a = {1,1,1,1} TableLib.lock_write(a) TableLib.release_lock_write(a) ]] function TableLib.release_lock_write(t) local mt = getmetatable(t); if not (mt and mt.__newindex) then return end mt.__newindex = nil end --[[-- 通过连续下标,获取table子集 @tparam table t 目标表格,array @tparam number from 起始下标 @tparam number to 截止下标 @return table 子集 @usage local a = {1,1,1,1} TableLib.get_subset(a,2,3) ]] function TableLib.get_subset(t, from, to) assert(from > 0 and from <= to and to <= #t, string.format("invalid range : %d, %d", from, to)); local sub = {} for i=from,to do sub[#sub + 1] = t[i] end return sub end --[[-- 克隆一份表数据 @tparam table object 目标表格 @return table 克隆表 @usage local a = {1,1,1,1} TableLib.clone(a) ]] function TableLib.clone(object) local lookup_table = {} local function _copy(object) if type(object) ~= "table" then return object elseif lookup_table[object] then return lookup_table[object] end local new_table = {} lookup_table[object] = new_table for key, value in pairs(object) do new_table[_copy(key)] = _copy(value) end return setmetatable(new_table, getmetatable(object)) end return _copy(object) end local function infilter( filter, key, value ) if filter.key then for i,v in ipairs(filter.key) do if v == key then return i end end end if filter.class then for i,v in ipairs(filter.class) do if typeof and typeof(value, v) then return i end end end return false end --[[-- 克隆一份表数据,可过滤 @tparam table object 目标表格 @tparam table filter 过滤器{key = {"ingorekey1",...},class = {class1,...} } @return table 克隆表 @usage local a = {1,1,1,1} local filter = { key = {"1","3"} -- 键值1、3对应的值不克隆 } TableLib.clone(a,filter) ]] function TableLib.clone2(object, filter) local lookup_table = {} filter = filter or {} local function _copy(object, filter) if type(object) ~= "table" then return object elseif lookup_table[object] then return lookup_table[object] end local new_table = {} lookup_table[object] = new_table for key, value in pairs(object) do if not infilter(filter, key, value) then new_table[_copy(key, filter)] = _copy(value, filter) end end return setmetatable(new_table, getmetatable(object)) end return _copy(object, filter) end --[[-- 加载string为table @tparam string strTab 定义table的字符串 @return table 定义的表 @usage local a = "{1,1,1,1}" TableLib.load_str_tab(a) ]] function TableLib.load_str_tab(strTab) if string.match(strTab, "^%s*%{.*%}%s*$") then return loadstring("return " .. strTab)() end return strTab; end --[[-- 返回table中所有key的集合 @tparam table tab 表 @return table key表 @usage local a = "{1,1,1,1}" TableLib.all_keys(a) ]] function TableLib.all_keys( tab ) if next(tab) == nil then print("nilTab") return end local keys = {} for k,_ in pairs(tab) do table.insert(keys, k) end return keys end return TableLib; ================================================ FILE: lib/time.lua ================================================ --[[--时间操作 @module time @author iwiniwin Date 2020-01-16 13:27:14 Last Modified by iwiniwin Last Modified time 2020-01-16 13:44:56 ]] local time = {} -- time.sleep = function ( num ) -- -- print("hhhh") -- -- os.execute("sleep 1000") -- io.popen("sleep 10000") -- end -- print(os.clock()) -- time.sleep(500000) -- print("你好") -- 单位是秒 -- require("socket") -- time.sleep = function ( second ) -- socket.select(nil, nil, second) -- end -- time.sleep = function ( second ) -- if second > 0 then -- os.execute("ping -n " .. second + 1 .. " localhost > NUL") -- end -- end -- time.sleep() -- print("uuuuuu") return time ================================================ FILE: mvc/loader.lua ================================================ --[[-- 模块加载器 @module Loader @author iwiniwin Date 2019-11-15 19:20:39 Last Modified by iwiniwin Last Modified time 2020-01-16 13:58:34 ]] local ModuleConfig = import("mvc.module_config") -- 根据配置读取模块 local config = {} for k,v in pairs(ModuleConfig) do table.insert(config, {key = k, value = v}) end table.sort(config, function ( a, b ) -- 根据initOrder排序,确定加载顺序 if a.value.initOrder and not b.value.initOrder then return true end if a.value.initOrder and b.value.initOrder then return a.value.initOrder < b.value.initOrder end return false end) -- 模块创建函数 local function create_module( name, params ) params = params or {} assert(params.file) local viewClass = require(params.file) local view = new(viewClass) view._tag = {name = name, params = params} return view end -- 根据配置加载模块 local moduleList = {} for i,v in ipairs(config) do if moduleList[v.key] then error("模块已经存在:" .. v.key) end local mod = create_module(v.key, v.value) moduleList[v.key] = mod end return moduleList ================================================ FILE: mvc/module1/test1_ctr.lua ================================================ --[[--Test1Ctr 示例Ctr @module Test1Ctr @author iwiniwin Date 2019-11-15 19:20:39 Last Modified by iwiniwin Last Modified time 2020-01-16 13:44:50 ]] local Test1Ctr = class() Test1Ctr._class_name = "Test1Ctr" function Test1Ctr:ctor( delegate ) dump("load Test1Ctr") self.m_delegate = delegate end function Test1Ctr:get_ui( ) return self.m_delegate end -- 刷新视图 function Test1Ctr:update_view( data ) -- Ctr负责逻辑处理,转换视图可识别的数据 -- data = process(data) -- 由View负责刷新视图 local ui = self:get_ui(); if ui then ui:update_view(data) end end function Test1Ctr:dtor( ... ) dump("unload Test1Ctr") self.m_delegate = nil end return Test1Ctr; ================================================ FILE: mvc/module1/test1_view.lua ================================================ --[[--ldoc desc Test1View 示例View @module Test1View @author iwiniwin Date 2019-11-15 19:20:39 Last Modified by iwiniwin Last Modified time 2020-01-16 14:00:03 ]] local Test1Ctr = import("mvc.module1.test1_ctr") local Test1View = class() Test1View._class_name = "Test1View" function Test1View:ctor( ... ) dump("load Test1View") self:bind_ctr() end function Test1View:bind_ctr( ) if self.mCtr then return false else self.mCtr = new(Test1Ctr, self) return true end end function Test1View:get_ctr( ... ) return self.mCtr end -- 更新视图 function Test1View:update_view( data ) -- 数据驱动 -- 视图与逻辑分离,数据有什么就更新什么 if data.title then -- 更新title end if data.content then -- 更新content end if data.other then -- 更新other end end function Test1View:unbind_ctr( ... ) if self.mCtr then delete(self.mCtr) self.mCtr = nil end end function Test1View:dtor( ... ) dump("unload Test1View") self:unbind_ctr() end return Test1View; ================================================ FILE: mvc/module2/test2_ctr.lua ================================================ --[[--ldoc desc Test2Ctr 示例Ctr @module Test2Ctr @author iwiniwin Date 2019-11-15 19:20:39 Last Modified by iwiniwin Last Modified time 2020-01-16 13:37:37 ]] local Test2Ctr = class() Test2Ctr._class_name = "Test2Ctr" function Test2Ctr:ctor( delegate ) dump("load Test2Ctr") self.m_delegate = delegate end function Test2Ctr:getUI( ) return self.m_delegate end -- 刷新视图 function Test2Ctr:update_view( data ) -- Ctr负责逻辑处理,转换视图可识别的数据 -- data = process(data) -- 由View负责刷新视图 local ui = self:getUI(); if ui then ui:update_view(data) end end function Test2Ctr:dtor( ... ) dump("unload Test2Ctr") self.m_delegate = nil end return Test2Ctr; ================================================ FILE: mvc/module2/test2_view.lua ================================================ --[[--ldoc desc Test2View 示例View @module Test2View @author iwiniwin Date 2019-11-15 19:20:39 Last Modified by iwiniwin Last Modified time 2020-01-16 13:59:57 ]] local Test2Ctr = import("mvc.module2.test2_ctr") local Test2View = class() Test2View._class_name = "Test2View" function Test2View:ctor( ... ) dump("load Test2View") self:bind_ctr() end function Test2View:bind_ctr( ) if self.mCtr then return false else self.mCtr = new(Test2Ctr, self) return true end end function Test2View:get_ctr( ... ) return self.mCtr end -- 更新视图 function Test2View:update_view( data ) -- 数据驱动 -- 视图与逻辑分离,数据有什么就更新什么 if data.title then -- 更新title end if data.content then -- 更新content end if data.other then -- 更新other end end function Test2View:unbind_ctr( ... ) if self.mCtr then delete(self.mCtr) self.mCtr = nil end end function Test2View:dtor( ... ) dump("unload Test2View") self:unbind_ctr() end return Test2View; ================================================ FILE: mvc/module_config.lua ================================================ --[[-- ModuleConfig @module ModuleConfig @author iwiniwin Date 2019-11-15 19:20:39 Last Modified by iwiniwin Last Modified time 2020-01-16 13:59:10 ]] local ModuleConfig = {} ModuleConfig.Module1 = { file = "mvc.module1.test1_view", initOrder = 2, } ModuleConfig.Module2 = { file = "mvc.module2.test2_view", initOrder = 1, } return ModuleConfig ================================================ FILE: pattern/AbstractFactoryPattern.lua ================================================ --[[-- 抽象工厂模式 @module AbstractFactoryPattern @author iwiniwin Date 2019-11-15 19:20:39 Last Modified by iwiniwin Last Modified time 2020-01-16 13:39:53 ]] package.path = package.path .. ";..\\?.lua;" require("_load") --[[ 抽象工厂模式 定义: 抽象工厂模式提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类 优点: 1. 抽象工厂允许客户使用抽象的接口来创建一组相关的产品, 而不需要知道(或关心)实际产出的具体产品是什么。这样一来,客户就从具体的产品中被解耦 设计原则: 1. 依赖倒置原则,依赖抽象,而不依赖具体类 ]] -- 抽象工厂,披萨原料工厂,定义一组接口用于生产产品家族。原料的获取采用了抽象工厂模式 local PizzaIngredientFactory = class(); -- 创建面团接口 function PizzaIngredientFactory:createDough( ... ) -- body end -- 创建酱料接口 function PizzaIngredientFactory:createSauce( ... ) -- body end -- 具体工厂类1 local NYPizzaIngredientFactory = class(); -- 实现创建面团接口 function NYPizzaIngredientFactory:createDough( ... ) dump("create NY Ingredient Factory dough") end -- 实现创建酱料接口 function NYPizzaIngredientFactory:createSauce( ... ) dump("create NY Ingredient Factory sauce") end -- 具体工厂类2 local ChicagoPizzaIngredientFactory = class(); -- 实现创建面团接口 function ChicagoPizzaIngredientFactory:createDough( ... ) dump("create Chicago Ingredient Factory dough") end -- 实现创建酱料接口 function ChicagoPizzaIngredientFactory:createSauce( ... ) dump("create Chicago Ingredient Factory sauce") end -- 抽象披萨类 local Pizza = class(); function Pizza:prepare( ... ) -- body end -- 具体披萨类 local CheesePizza = class(); -- 接收PizzaIngredientFactory对象 function CheesePizza:ctor( factory ) self.factory = factory end -- 实现prepare接口 function CheesePizza:prepare( ... ) local dough = self.factory:createDough(); local sauce = self.factory:createSauce(); end local PizzaStore = class(); function PizzaStore:orderPizza( type ) local pizza = self:createPizza(type); -- 调用子类的创建披萨方法 pizza:prepare() -- pizza:bake() -- pizza:cut() -- pizza:box() return pizza; end -- 声明一个抽象的工厂方法,由子类去实现。pizza的获取采用了工厂方法模式 function PizzaStore:createPizza( type ) end local NYStyleCheesePizza = class(); -- 第一个子类,纽约披萨店 local NYPizzaStore = class(PizzaStore) function NYPizzaStore:createPizza( type ) local factory = new(NYPizzaIngredientFactory); -- 使用具体的原料工厂 if type == "cheese" then return new(CheesePizza, factory); end end local ChicagoCheesePizza = class(); -- 第二个子类,芝加哥披萨店 local ChicagoStore = class(PizzaStore) function ChicagoStore:createPizza( type ) local factory = new(ChicagoPizzaIngredientFactory); -- 使用具体的原料工厂 if type == "cheese" then return new(CheesePizza, factory) end end -------------- 测试 -------------- local store = new(ChicagoStore) store:orderPizza("cheese") -- create Chicago Ingredient Factory dough -- create Chicago Ingredient Factory sauce ================================================ FILE: pattern/AdapterPattern.lua ================================================ --[[-- 适配器模式 @module AdapterPattern @author iwiniwin Date 2019-11-15 19:20:39 Last Modified by iwiniwin Last Modified time 2020-01-16 13:39:53 ]] package.path = package.path .. ";..\\?.lua;" require("_load") --[[ 适配器模式 定义: 将一个类的接口,转换成客户期望的另一个接口。适配器让原本接口不相容的类可以合作无间 优点: 可以通过创建适配器进行接口转换,让不兼容的接口编程兼容。这可以让客户从实现的接口解耦 如果在一段时间以后,我们想改变接口,适配器可以将改变的部分封装起来,客户就不必为了应对不同的系统而每次跟着修改 ]] -- 鸭子类 local Duck = class(); -- 鸭子有呱呱叫能力 function Duck:quack( ... ) -- body end -- 鸭子有飞行能力 function Duck:fly( ... ) -- body end -- 绿头鸭 local MallardDuck = class(Duck); function MallardDuck:quack( ... ) dump("mallard duck quack") end function MallardDuck:fly( ... ) dump("mallard duck fly") end -- 火鸡类 local Turkey = class(); -- 火鸡有咯咯叫能力 function Turkey:gobble( ... ) -- body end -- 火鸡有飞行能力 function Turkey:fly( ... ) -- body end -- 野生火鸡 local WildTurkey = class(Turkey); function WildTurkey:gobble( ... ) dump("wild turkey gobble") end function WildTurkey:fly( ... ) dump("wild turkey fly") end -- 适配器,让火鸡来冒充鸭子 local TurkeyAdapter = class(Duck); -- 适配器需要实现想转换成的类型接口,也就是客户所期望看到的接口。即quack和fly function TurkeyAdapter:ctor( turkey ) self.turkey = turkey end function TurkeyAdapter:quack( ... ) self.turkey:gobble(); end -- 火鸡有飞行能力 function TurkeyAdapter:fly( ... ) self.turkey:fly(); end -------------- 测试 -------------- local turkey = new(WildTurkey); local turkeyAdapter = new(TurkeyAdapter, turkey); -- 将火鸡包装进一个火鸡适配器,使它看起来像是一只鸭子 -- 测试鸭子 local duck = turkeyAdapter; duck:quack(); -- wild turkey gobble duck:fly(); -- wild turkey fly ================================================ FILE: pattern/CORPattern.lua ================================================ --[[-- 责任链模式 @module CORPattern @author iwiniwin Date 2019-11-15 19:20:39 Last Modified by iwiniwin Last Modified time 2020-01-16 13:39:53 ]] package.path = package.path .. ";..\\?.lua;" require("_load") --[[ 责任链模式 定义: 将接收者对象连成一条链,并在该链上传递请求,直到有一个接收者对象处理它 通过让更多对象有机会处理请求,避免了请求发送者和接收者之间的耦合 使用场景: 经常被用在窗口系统中,处理鼠标和键盘之类的事件 优点: 1. 将请求的发送者和接收者解耦 2. 通过改变链内的成员或调动它们的次序,允许动态地新增或者删除责任 缺点: 1. 内存消耗,链上的所有对象都需要创建,可能有些对象根本不会被用到(或很少走进满足该对象处理的条件) 2. 性能消耗,处理需要一层层的传递,才能被正确的对象所处理 ]] --[[ 责任链模式实例 客户到售楼处买房,请求折扣的例子 ]] -- 价格处理类 local PriceHandler = class() function PriceHandler:ctor( ... ) -- 抽象方法 processDiscount assert(self.processDiscount, "子类必须实现processDiscount接口") end function PriceHandler:setSuccessor( successor ) self.successor = successor end -- 目前是三个Handler,sale销售 和 manager经理 和 ceo local Sale = class(PriceHandler) Sale._class_name = "Sale" function Sale:processDiscount( discount ) if discount < 0.2 then print("sale处理了" .. discount .. "的折扣") else self.successor:processDiscount(discount) end end local Manager = class(PriceHandler) Manager._class_name = "Manager" function Manager:processDiscount( discount ) if discount < 0.4 then print("manager处理了" .. discount .. "的折扣") else self.successor:processDiscount(discount) end end local CEO = class(PriceHandler) function CEO:processDiscount( discount ) if discount < 0.8 then print("ceo处理了" .. discount .. "的折扣") else print("ceo拒绝了" .. discount .. "的折扣") end end -- PriceHandler工厂类 -- 添加一个工厂类提供createPriceHandler方法, -- 而不直接在PriceHandler中提供的原因是基于单一职责原则, -- PriceHandler见名知意是用于价格处理的,而不应该有提供PriceHandler的功能 local PriceHandlerFactor = class() function PriceHandlerFactor.createPriceHandler( ... ) -- 构造责任链 local sale = new(Sale) local manager = new(Manager) local ceo = new(CEO) -- 销售设置后继是经理 sale:setSuccessor(manager) -- 经理的后继是ceo manager:setSuccessor(ceo) -- ceo不存在直接后继 -- 由sale优先处理 return sale end -- 顾客 local Customer = class() function Customer:setPriceHandler( priceHandler ) self.priceHandler = priceHandler end function Customer:requestDiscount( discount ) self.priceHandler:processDiscount(discount) end -- 测试 local customer = new(Customer) customer:setPriceHandler(PriceHandlerFactor.createPriceHandler()) for i = 1, 10 do -- 100次折扣申请 customer:requestDiscount(math.random()) end -- 如何应对变化 -- 如果此时ceo希望添加一个vp的角色,帮他审核0.5以下的折扣 -- 只需要添加一个vp类,同时修改以下工厂方法 print("加入一个vp角色") local VP = class(PriceHandler) function VP:processDiscount( discount ) if discount < 0.6 then print("vp处理了" .. discount .. "的折扣") else self.successor:processDiscount(discount) end end function PriceHandlerFactor.createPriceHandler( ... ) local sale = new(Sale) local manager = new(Manager) local ceo = new(CEO) -- 添加一个vp local vp = new(VP) -- 销售设置后继是经理 sale:setSuccessor(manager) -- 修改经理的后继为vp manager:setSuccessor(vp) -- vp的后继为ceo vp:setSuccessor(ceo) -- ceo不存在直接后继 -- 由sale优先处理 return sale end -- 测试 local customer = new(Customer) customer:setPriceHandler(PriceHandlerFactor.createPriceHandler()) for i = 1, 10 do -- 100次折扣申请 customer:requestDiscount(math.random()) end ================================================ FILE: pattern/CompositePattern.lua ================================================ --[[-- 组合模式 @module CompositePattern @author iwiniwin Date 2019-11-15 19:20:39 Last Modified by iwiniwin Last Modified time 2020-01-16 13:39:53 ]] package.path = package.path .. ";..\\?.lua;" require("_load") --[[ 适配器模式 定义: 将一个类的接口,转换成客户期望的另一个接口。适配器让原本接口不相容的类可以合作无间 优点: 组合以单一责任设计原则换取透明性。什么是透明性?通过让组件的接口同时包含一些管理子节点和叶子节点的操作, 客户就可以将组合和叶子节点一视同仁。也就是说,一个元素究竟是组合还是叶子节点,对客户是透明的 ]] -- 利用组合模式来设计菜单 -- 菜单组件提供了一组接口,让菜单和菜单项共同使用 local MenuComponent = class(); function MenuComponent:getName( ... ) end function MenuComponent:getPrice( ... ) end function MenuComponent:add( component ) end function MenuComponent:remove( component ) end function MenuComponent:getChild( index ) end function MenuComponent:print( ) end -- 菜单(组合菜单)。覆盖一些菜单组件对它有用的方法。此组合类可以持有菜单项和其它菜单 local Menu = class(MenuComponent); function Menu:ctor( name ) self.name = name self.menuComponents = {} -- 菜单下可以有更多组件 end function Menu:getName( ... ) end function Menu:add( component ) table.insert(self.menuComponents, component) end function Menu:remove( component ) table.remove(self.menuComponents, component) end function Menu:getChild( index ) return self.menuComponents[index] end function Menu:print( ) dump("menu : name is " .. self.name) for i,v in ipairs(self.menuComponents) do v:print(); end end -- 菜单项。也覆盖一些对它有意义的方法。没有意义的就置之不理。因为菜单项已经是叶子节点,所以它的下面不能有任何组件 local MenuItem = class(MenuComponent); function MenuItem:ctor( name, price ) self.name = name self.price = price end function MenuItem:getName( ... ) end function MenuItem:getPrice( ... ) end function MenuItem:print( ... ) dump("menu item : name is " .. self.name .. ", price is " .. self.price) end -------------- 测试 -------------- local pancakeHouseMenu = new(Menu, "PANCAKE HOUSE MENU"); -- 煎饼屋菜单 local dinerMenu = new(Menu, "DINER MENU"); -- 餐厅菜单 local cafeMenu = new(Menu, "CAFE MENU"); -- 咖啡菜单 local allMenus = new(Menu, "ALL MENUS") allMenus:add(pancakeHouseMenu); allMenus:add(dinerMenu) allMenus:add(cafeMenu) dinerMenu:add(new(MenuItem, "Pasta", 3.89)); -- 加入菜单项,面团 local dessertMenu = new(Menu, "DESSERT MENU"); -- 甜点菜单 dessertMenu:add(new(MenuItem, "Apple Pie", 1.59)); -- 加入菜单项 dinerMenu:add(dessertMenu); -- 加入菜单,甜点菜单(甜点菜单属于餐厅菜单的子菜单) allMenus:print() --[[ menu : name is ALL MENUS menu : name is PANCAKE HOUSE MENU" menu : name is DINER MENU" menu item : name is Pasta, price is 3.89" menu : name is DESSERT MENU" menu item : name is Apple Pie, price is 1.59" menu : name is CAFE MENU" ]] ================================================ FILE: pattern/FactoryMethodPattern.lua ================================================ --[[-- 工厂方法模式 @module FactoryMethodPattern @author iwiniwin Date 2019-11-15 19:20:39 Last Modified by iwiniwin Last Modified time 2020-01-16 13:39:53 ]] package.path = package.path .. ";..\\?.lua;" require("_load") --[[ 简单工厂模式,不是一个真正的模式,但经常被用于封装创建对象的代码 ]] local CheesePizza = class(); -- 芝士披萨 local PepperoniPizza = class(); -- 意大利香肠披萨 local SimplePizzaFactory = class() -- 简单工厂,根据传入的参数,决定创建出哪一种产品类的实例 SimplePizzaFactory.createPizza = function ( type ) if type == "cheese" then return new(CheesePizza) elseif type == "pepperoni" then return new(PepperoniPizza) end end --[[ 工厂方法模式 定义: 定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。 工厂方法让类把实例化推迟到子类 优点: 1. 通过让子类决定该创建的对象是什么,来达到将对象创建的过程封装的目的 设计原则: 1. 依赖倒置原则,依赖抽象,而不依赖具体类 ]] local PizzaStore = class(); function PizzaStore:orderPizza( type ) local pizza = self:createPizza(type); -- 调用子类的创建披萨方法 -- pizza:prepare() -- pizza:bake() -- pizza:cut() -- pizza:box() return pizza; end -- 声明一个抽象的工厂方法,由子类去实现。实例化的责任被移到一个方法中,此方法就如同是一个工厂 function PizzaStore:createPizza( type ) -- body end local NYStyleCheesePizza = class(); -- 第一个子类,纽约披萨店 local NYPizzaStore = class(PizzaStore) function NYPizzaStore:createPizza( type ) if type == "cheese" then dump("create ny cheese pizza") return new(NYStyleChesePizza); end end local ChicagoCheesePizza = class(); -- 第二个子类,芝加哥披萨店 local ChicagoStore = class(PizzaStore) function ChicagoStore:createPizza( type ) if type == "cheese" then dump("create chicago cheese pizza") return new(ChicagoCheesePizza) end end -------------- 测试 -------------- local store = new(ChicagoStore) store:orderPizza("cheese") -- create chicago cheese pizza ================================================ FILE: pattern/ObserverPattern.lua ================================================ --[[-- 观察者模式 @module ObserverPattern @author iwiniwin Date 2019-11-15 19:20:39 Last Modified by iwiniwin Last Modified time 2020-01-16 13:40:08 ]] package.path = package.path .. ";..\\?.lua;" require("_load") --[[ 观察者模式 定义: 定义了对象之间的一对多依赖,这样一来, 当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新 优点: 1. 主题和观察者之间是松耦合,主题只知道观察者实现了观察者接口,不需要知道 观察者的具体是谁,做了些什么 2. 任何时候都可以增加或删除观察者,主题不会受到任何影响 设计原则: 1. 为了交互对象之间的松耦合设计而努力 松耦合的设计能让我们建立有弹性的OO系统,能够应对变化,因为 对象之间的依赖降到了最低 2. 找出程序会变化的方面,然后将其和固定不变的方面相分离 3. 针对接口编程,不针对实现编程 观察者利用主题的接口注册,主题利用观察者的接口通知观察者 4. 多用组合,少用继承 观察者模式利用组合将许多观察者组合进主题中 注意: 有多个观察者时,不可以依赖特定的通知顺序 ]] --[[ 观察者模式原型 ]] -- 主题 出版者 local Subject = class() function Subject:ctor( ... ) -- 观察者队列 self.observers = {} end -- 注册观察者 function Subject:registerObserver( observer ) table.insert(self.observers, observer) end -- 移除观察者 function Subject:removeObserver( observer ) for i,v in ipairs(self.observers) do if v == observer then table.remove(self.observers, i) end end end -- 通知所有的观察者对象 function Subject:notifyObservers( ... ) -- ...参数可以是自己,拉模型,观察者通过自己这个对象去获取更新的信息 -- 也可以是具体的状态信息,推模型(推荐这个) for i,v in ipairs(self.observers) do v:update( ... ) end end -- 观察者 订阅者 local Observer = class() -- 纯接口 -- 所有观察者必须实现观察接口 function Observer:ctor( ... ) -- 定义了update接口 assert(self.update, "必选实现update方法") end --[[ 观察者模式实例 小明和小红订阅天气信息 ]] -- 天气(主题) local Weather = class(Subject) -- 扩展内容 -- 添加changed标志 java.util.Observer中有 function Weather:setChanged( ... ) self.changed = true end function Weather:clearChanged( ... ) self.changed = false end function Weather:hasChanged( ... ) return self.changed end -- 发布天气信息 -- 利用changed的好处,使更新观察者时有更多的弹性 -- 如果没有changed则一旦天气信息有了变化就会通知观察者,太过明锐 -- 而通过changed,可以在天气变化达到某个条件时,再调用setChanged()进行有效的更新 function Weather:setWeatcherInfo( ... ) if self.changed then self:notifyObservers( ... ) end self.changed = false end -- 订阅天气的人(观察者) local People = class(Observer) function People:ctor( name ) self.name = name end function People:update( ... ) print(string.format("%s收到了天气信息:%s", self.name, ...)) end -- 测试 local weather = new(Weather) -- object.lua new实现有问题 这两个的name都是小红 local ming = new(People, "小明") local hong = new(People, "小红") weather:registerObserver(ming) weather:registerObserver(hong) -- 发布天气信息 -- 设置changed通知观察者 weather:setChanged() weather:setWeatcherInfo("晴朗") -- 移除一个观察者 weather:removeObserver(ming) -- 没有设置changed,不会通知观察者 print(weather:hasChanged(), "change状态") weather:setWeatcherInfo("阴天") ================================================ FILE: pattern/ProxyPattern.lua ================================================ --[[-- @module ProxyPattern @author iwiniwin Date 2019-11-15 19:20:39 Last Modified by iwiniwin Last Modified time 2020-01-16 13:40:27 ]] ================================================ FILE: pattern/StrategyPattern.lua ================================================ --[[--策略模式 @module StrategyPattern @author iwiniwin Date 2019-11-15 19:20:39 Last Modified by iwiniwin Last Modified time 2020-01-16 13:40:54 ]] package.path = package.path .. ";..\\?.lua;" require("_load") --[[ 策略模式 定义: 将可变的部分从程序中抽象分离成算法接口 在该接口下分别封装一系列算法实现,并使他们可以相互替换 从而导致客户端程序独立于算法的改变 优点: 1. 足够灵活,不同的策略只需要给出封装的接口的不同实现即可,富有弹性,可以较好地应对变化 2. 复用代码,更易于维护,可以复用相同的策略 3. 消除大量的条件语句 缺点: 1. 增加了对象的数目 2. 客户代码需要了解策略的具体细节 设计原则: 1. 找出应用中需要变化的部分,把他们独立出来,不要和那些不需要变化的代码混在一起 鸭子的飞行行为是千变万化的,但是鸭子具有飞行行为是不变的, 将这个不变的部分抽象为飞行策略接口,而具体的飞行行为交给子类去实现 2. 面向接口编程,而不是面向实现编程 不如鸭子超类只是持有了飞行策略接口,而不是具体的飞行实现 3. 多用组合,少用继承 ]] --[[ 策略模式原型 ]] local Super = class() function Super:ctor( ... ) -- body end function Super:setStrategy( strategy ) -- 通过组合注入策略 self.strategy = strategy end function Super:interface( ... ) -- 通过策略实现某个功能 self.strategy:interface() end -- 策略接口 local Strategy = class() function Strategy:ctor( ... ) -- 声明了某个策略接口 assert(self.interface, "必须实现某策略") end --[[ 策略模式实例 有一个鸭子的父类,已经有一个正常鸭子的实现,后面需要再实现橡皮鸭(不会飞),太空鸭(坐火箭飞) ]] -- 鸭子超类 local Duck = class() -- 鸭子都有一个外观 function Duck:display( ... ) end function Duck:setFlyStrategy( flyStrategy ) self.flyStrategy = flyStrategy end function Duck:fly( ... ) self.flyStrategy:fly() end -- 飞行策略接口 local FlyStrategy = class() function FlyStrategy:ctor( ... ) assert(self.fly, "必须实现飞行接口") end -- 具体飞行策略 -- 振翅高飞 local FlyWithWin = class(FlyStrategy) function FlyWithWin:fly( ... ) print("振翅高飞") end local flyWithWin = new(FlyWithWin) -- 正常鸭 local NormalDuck = class(Duck) function NormalDuck:ctor( ... ) self:setFlyStrategy(flyWithWin) end function NormalDuck:display( ... ) print("我是正常鸭") end -- 测试 local normalDuck = new(NormalDuck) normalDuck:display() normalDuck:fly() -- 不会飞(也是一种飞行策略) local FlyNoWay = class(FlyStrategy) function FlyWithWin:fly( ... ) print("不会飞") end local flyNoWay = new(FlyNoWay) -- 橡皮鸭 local RubberDuck = class(Duck) function RubberDuck:ctor( ... ) self:setFlyStrategy(flyNoWay) end function RubberDuck:display( ... ) print("我是橡皮鸭") end -- 测试 local rubberDuck = new(RubberDuck) rubberDuck:display() rubberDuck:fly() -- 坐火箭飞 local FlyWithRocket = class(FlyStrategy) function FlyWithRocket:fly( ... ) print("坐火箭飞") end local flyWithRocket = new(FlyWithRocket) -- 太空鸭 local SpaceDuck = class(Duck) function SpaceDuck:ctor( ... ) self:setFlyStrategy(flyWithRocket) end function SpaceDuck:display( ... ) print("我是正常鸭") end -- 测试 local spaceDuck = new(SpaceDuck) spaceDuck:display() spaceDuck:fly() -- 对于鸭子超类,没有在其中直接定义fly方法,或者fly接口的原因 -- 不直接定义fly方法,不是所有的鸭子都会飞,对于不会飞的鸭子需要覆盖该方法,但是可能会由于某些原因忘记覆盖 -- 不直接定义fly接口,这样的话,所有的子类都必须要实现该接口, -- 而且代码无法复用,重复代码多,比如同一个飞行策略,在具有相同的策略的子类中都要写一遍 -- 采用策略模式的好处,灵活不同的策略可以有不同的实现,当某些子类有共同的飞行策略,还可以直接复用该策略 ================================================ FILE: pattern/TemplatePattern.lua ================================================ --[[--模板方法模式 @module TemplatePattern @author iwiniwin Date 2019-11-15 19:20:39 Last Modified by iwiniwin Last Modified time 2020-01-16 13:41:10 ]] package.path = package.path .. ";..\\?.lua;" require("_load") --[[ 模板方法模式 定义: 在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。 模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤 优点: 1. 对算法有更多的控制权,超类主导一切,拥有且保护这个算法 2. 超类的存在将代码的复用最大化,算法只存在超类中,容易修改 3. 模板方法提供了一个框架,可以让各种子类插进来,不同的子类实现自己的方法就可以 设计原则: 好莱坞原则:别调用我们,我们会调用你 允许底层组件将自己挂钩到系统上,但是高层组件会决定什么时候和怎样使用这些底层组件 ]] --[[ 模板方法模式原型 ]] local SuperClass = class() function SuperClass:templateMethod( ... ) -- 模板方法 子类不应该覆盖它 assert(self.primitiveOperation1, "子类必须实现primitiveOperation1") assert(self.primitiveOperation2, "子类必须实现primitiveOperation2") self.primitiveOperation1() self.primitiveOperation1() self.concreteOperation() self.hook() end function SuperClass:concreteOperation( ... ) -- 在超类中具体实现,子类不应该覆盖 -- 可以被模板方法直接使用,或者被子类使用 end function SuperClass:hook( ... ) -- 一个具体方法,但什么也不做 -- 钩子方法,子类可以根据情况决定要不要覆盖他 -- 如果算法的这个部分是可选的,就用钩子 -- 钩子可以让子类能够有机会对模板方法中某些即将发生的步骤做出反应 end --[[ 模板方法模式实例 封装一个制作饮品的具体算法 ]] local Drink = class() function Drink:prepareDrink( ... ) assert(self.brew) assert(self.addCondiments) -- 煮沸水 self.boilWater() -- 冲泡 self.brew() -- 倒入杯中 self.pourInCap() -- 添加调料 if self:wantsCondiments() then self.addCondiments() end end function Drink:boilWater( ... ) print("把水煮沸。。。") end function Drink:pourInCap( ... ) print("倒入杯中。。。") end function Drink:wantsCondiments( ... ) -- 钩子方法 return true end -- 子类 local Coffee = class(Drink) function Coffee:brew( ... ) print("用沸水冲泡咖啡粉") end function Coffee:addCondiments( ... ) print("添加牛奶和糖") end local coffee = new(Coffee) coffee:prepareDrink() local Tea = class(Drink) function Tea:brew( ... ) print("用沸水浸泡茶叶") end function Tea:addCondiments( ... ) print("添加柠檬") end local tea = new(Tea) tea:prepareDrink() -- 不加调料的茶 local TeaNoCondiments = class(Drink) function TeaNoCondiments:brew( ... ) print("用沸水浸泡茶叶") end function TeaNoCondiments:addCondiments( ... ) print("添加柠檬") end function TeaNoCondiments:wantsCondiments( ... ) return false end local tea = new(Tea) tea:prepareDrink() ================================================ FILE: test.lua ================================================ --[[-- LuaKit测试用例 @module test @author iwiniwin Date 2019-11-15 19:20:39 Last Modified by iwiniwin Last Modified time 2020-04-01 16:59:33 ]] -- 测试面向对象 local function test_oop( ... ) local Class1 = class() function Class1:ctor( ... ) dump("Class1:ctor") end function Class1:dtor( ... ) dump("Class1:dtor") end -- Class2集成于Class1 local Class2 = class(Class1) function Class2:ctor( ... ) dump("Class2:ctor") end function Class2:dtor( ... ) dump("Class2:dtor") end -- 实例化对象 local c1 = new(Class1) local c2 = new (Class2) -- 销毁对象 delete(c1) delete(c2) end -- 测试dump local function test_dump() local data = { key1 = 34, key2 = "str", key3 = { key4 = { key5 = 56 }, key6 = 78 } } dump(data, "this is a dump test") end -- 测试分模块加载 local function test_load_module( ... ) local moduleList = import("mvc.loader") -- 卸载模块 for k,v in pairs(moduleList) do delete(v) end end -- 测试性能分析 local function test_profile( ... ) local new_profiler = import("utils.profiler") local profiler = new_profiler("call") profiler:start() -- 开启性能分析 local function aaa( ) for i = 1, 10000000 do end end local function ttt( ) aaa() end ttt() -- 同时支持分析协程内的函数调用情况 local co = coroutine.create(function ( ... ) aaa() end) coroutine.resume(co) profiler:stop() -- 停止性能分析 -- 输出分析结果到文件 profiler:dump_report_to_file("profile.txt") end -- 测试内存泄漏检测工具 local function test_memory_monitor( ... ) local MemoryMonitor = import("utils.memory_monitor") local memoryMonitor = new(MemoryMonitor) a = {} function test( ... ) local b = {xxx = "xxx"} a.b = b memoryMonitor:add_to_leak_monitor(b, "b") --将b添加到内存检测工具,此时a没有被释放掉 则b也释放不掉 end test() -- 由于a在引用b,因此b存在内存泄漏 memoryMonitor:update() -- a不再引用b,b也被释放 a = nil memoryMonitor:update() -- 没有内存泄漏,这里不会打印日志 end -- 测试组件系统 local function test_component( ... ) local ComponentBase = import("core.component.component_base") local ComponentExtend = import("core.component.component_extend") local A = class() ComponentExtend(A) -- 组件1 local Component1 = class(ComponentBase) Component1.exportInterface = { {"test1"}, } function Component1:test1( ... ) dump("call test1 ...") end -- 组件2 local Component2 = class(ComponentBase) Component2.exportInterface = { {"test2"}, } function Component2:test2( ... ) dump("call test2 ...") end local a = new(A) a:bind_component(Component1) -- 对象a绑定组件1 拥有test1方法 a:bind_component(Component2) -- 对象a绑定组件2 拥有test2方法 a:test1() a:test2() a:unbind_component(Component1) -- 解绑组件1 丧失test1方法 -- a:test1() -- 报错 attempt to call method 'test1' (a nil value) end -- 测试事件分发系统 local function test_event_system( ... ) local EventSystem = new(import("core.event.event_system")) local Event = import("core.event.event") -- 简单用法 EventSystem:on("test", function ( ... ) dump({...}) end) EventSystem:emit("test", "param1", "param2") -- 高级用法 local A = class() function A:on_key_down( key ) dump(key, "key name A") end EventSystem:on(Event.KeyDown, A.on_key_down, {target = A}) local B = class() function B:on_key_down( key ) dump(key, "key name B") return true -- 可以中断事件派发 end -- 后注册的事件通过提高优先级可以保证先被调用 EventSystem:on(Event.KeyDown, B.on_key_down, {target = B, priority = 2}) EventSystem:emit(Event.KeyDown, "Ctrl") EventSystem:off_all(B) -- 通过target取消注册 EventSystem:emit(Event.KeyDown, "Ctrl") end -- test_oop() -- test_dump() -- test_load_module() -- test_profile() -- test_memory_monitor() -- test_component() test_event_system() -- 数据观察追踪 回退系统 ================================================ FILE: utils/dump.lua ================================================ --[[-- 格式化输出table(格式化过程中,排序操作会比较耗时) @module dump @author iwiniwin Date 2019-11-15 19:20:39 Last Modified by iwiniwin Last Modified time 2020-01-16 13:41:51 ]] local isSort = true; local table_format = string.format local string_len = string.len local string_rep = string.rep local math_randomseed = math.randomseed local debug_traceback = debug.traceback local function _dump_value(v) if type(v) == "string" then v = "\"" .. v .. "\"" end return tostring(v) end --- dump 输出table的内容 --@param value 输出的table --@string desciption 调试信息格式 --@int nesting 输出时的嵌套层级,默认为 15 --@usage local t = {key = "xxx"} --dump(t) local function dump(value, desciption, nesting) if type(nesting) ~= "number" then nesting = 10 end local lookup = {} local result = {} local traceback = string.split(debug_traceback("", 2), "\n") local str = "- dump from: " .. string.trim(traceback[3]); local function _dump(value, desciption, indent, nest, keylen) desciption = desciption or "" local spc = "" if type(keylen) == "number" then spc = string_rep(" ", keylen - string_len(_dump_value(desciption))) end if type(value) ~= "table" then result[#result +1 ] = table_format("%s%s%s = %s", indent, _dump_value(desciption), spc, _dump_value(value)) elseif lookup[tostring(value)] then result[#result +1 ] = table_format("%s%s%s = *REF*", indent, _dump_value(desciption), spc) else lookup[tostring(value)] = true if nest > nesting then result[#result +1 ] = table_format("%s%s = *MAX NESTING*", indent, _dump_value(desciption)) else result[#result +1 ] = table_format("%s%s = {", indent, _dump_value(desciption)) local indent2 = indent.." " local keys = {} local keylen = 0 local values = {} for k, v in pairs(value) do if k~="___message" then keys[#keys + 1] = k local vk = _dump_value(k) local vkl = string_len(vk) if vkl > keylen then keylen = vkl end values[k] = v end end if isSort == true then table.sort(keys, function(a, b) if type(a) == "number" and type(b) == "number" then return a < b else return tostring(a) < tostring(b) end end) end for i, k in ipairs(keys) do _dump(values[k], k, indent2, nest + 1, keylen) end result[#result +1] = table_format("%s}", indent) end end end _dump(value, desciption, "- ", 1) str = str .. "\n" .. table.concat(result, "\n") print(str) end return dump; ================================================ FILE: utils/dump_to_file.lua ================================================ --[[-- 序列化lua table @module dump_to_file @author iwiniwin Date 2019-11-15 19:20:39 Last Modified by iwiniwin Last Modified time 2020-01-16 13:42:17 ]] local M = {}; local table_format = string.format local string_len = string.len local string_rep = string.rep ---数列化table 报错到文件 --@string t 表 --@string tabName 表名 --@string path 保存目录,可选 --@retrurn tableStr function M.serialize(t, tabName,path) local function dump(value, desciption, nesting) local lookup = {} local result = {} if type(nesting) ~= "number" then nesting = 100 end local function _dump_value(v) if type(v) == "string" then v = string.format("%q", v) end if type(v) == "function" or type(v) == "userdata" then v = string.format("%q", tostring(v)) end -- if type(v) == "userdata" then -- v = string.format("%q", tostring(v)) -- end -- if type(v) == "number" then -- v = string.format("%.2f",v) -- 如果是小数,保留小数点后两位 -- if math.floor(v) < v then -- v = string.format("%.2f",v) -- end -- end return tostring(v) end local function _dump_key(v) if type(v) == "number" then v = "[" .. v .. "]" end return v end local function _dump(value, desciption, indent, nest, keylen) desciption = desciption or "" local spc = "" if type(keylen) == "number" then spc = string_rep(" ", keylen - string_len(_dump_value(desciption))) end if type(value) ~= "table" then result[#result +1 ] = table_format("%s%s%s = %s,", indent, _dump_key(desciption), spc, _dump_value(value)) elseif lookup[tostring(value)] then result[#result +1 ] = table_format("%s%s%s = '*REF*%s',", indent, desciption, spc,tostring(value)) else lookup[tostring(value)] = true if nest > nesting then result[#result +1 ] = table_format("%s%s = '*MAX NESTING*',", indent, desciption) else result[#result +1 ] = table_format("%s%s = {", indent, _dump_key(desciption)) local indent2 = indent.." " local keys = {} local keylen = 0 local values = {} for k, v in pairs(value) do if k~="___message" then keys[#keys + 1] = k local vk = _dump_value(k) local vkl = string_len(vk) if vkl > keylen then keylen = vkl end values[k] = v end end -- table.sort(keys, function(a, b) -- if type(a) == "number" and type(b) == "number" then -- return a < b -- else -- return tostring(a) < tostring(b) -- end -- end) for i, k in ipairs(keys) do _dump(values[k], k, indent2, nest + 1, keylen) end result[#result +1] = table_format("%s},", indent) end end end _dump(value, desciption, "", 1) result[1] = "{"; result[#result] = "}"; local ret = "" for i, line in ipairs(result) do ret = ret .. line .. "\n"; end return ret; end tabName = tabName or "ret" local str = "do local " .. tabName .. " =\n"..dump(t) .. string.format("\nreturn %s end",tabName); -- local path = System.getStorageTempPath() .. tabName .. ".lua" local filePath = tabName .. ".lua" if path then filePath = path .. "/" .. filePath end M.writefile(str,filePath) return str; end function M.writefile(str, file) --os.remove(file); local file =io.open(file,"w"); if file then file:write(str); file:close(); end end function M.dump_to_file(t,tabName,path) return M.serialize(t,tabName,path) end return M; ================================================ FILE: utils/memory_monitor.lua ================================================ --[[-- lua内存泄漏检测工具 @module MemoryMonitor @author iwiniwin Date 2019-11-15 19:20:39 Last Modified by iwiniwin Last Modified time 2020-01-16 13:42:34 ]] --[[ lua内存泄漏检测工具 原理:弱表中的引用是弱引用,不会导致对象的引用计数发生变化 即如果一个对象只有弱引用指向它,那么gc会自动回收该对象的内存 ]] --[[ Lua运行了一个垃圾收集器来收集所有死对象来完成自动内存管理的工作 Lua实现了一个增量标记-扫描收集器。它使用间歇率和步进倍率这两个数字来控制垃圾收集循环,都是以百分数为单位(例如:值100在内部表示1) 间歇率控制收集器在开启新的循环前要等待多久,增大这个值将减少收集器的积极性。当这个值比100小的时候,收集器在开始新的循环前不会有等待, 设置这个值为200就会让收集器等到总内存使用量达到之前的两倍时才开始新的循环 步进倍率控制着收集器运行速度相对于内存分配速度的倍率,增大这个值不仅会让收集器更加积极,还会增加每个增量步骤的长度。不要把这个值设置 的小于100,那样的话收集器就工作的太慢了以至于永远都干不完一个循环。默认值是200,表示收集器以内存分配的两倍速工作 collectgarbage("collect") : 做一次完整的垃圾收集循环 collectgarbage("count") : 以k字节数为单位返回Lua使用的总内存数,这个值有小数部分,所以只需要乘上1024就能得到Lua使用的准确字节数 collectgarbage("restart") : 重启垃圾收集器的自动运行 collectgarbage("setpause") : 将arg设为收集器的间歇率,并返回间歇率的前一个值 collectgarbage("setstepmul") : 将arg设为收集器的步进倍率,并返回步进倍率的前一个值 collectgarbage("step") : 单步运行垃圾收集器,步长大小由arg控制,传入0时,收集器步进一步,传入非0值,收集器收集相当于Lua分配这么多()内存的工作。如果垃圾收集器结束一个循环将返回true collectgarbage("stop") : 停止垃圾收集器的运行。在调用重启前,收集器只会因显示的调用运行 ]] -- 监控间隔配置(单位:秒) local MonitorConfig = { -- 内存泄漏监控间隔 memLeakInterval = 1, } local MemoryMonitor = {} function MemoryMonitor:ctor( ... ) -- 内存泄漏弱引用表 self.__memLeakTable = {} -- mode字段可以取 k, v, kv 分别表示table中的 key, value,是弱引用的, kv就是二者的组合 -- 对于一个table,任何情况下,只要它的key或者value中的一个被gc,那么这个key-value pair就从表中移除了 setmetatable(self.__memLeakTable, {__mode = "kv"}) -- 内存泄漏监控器 self.__memLeakMonitor = nil self:start() end -- 开始检测 function MemoryMonitor:start( ... ) self.__memLeakMonitor = self:__mem_leak_monitoring() end --[[ 把一个表或者对象添加到内存检测工具中,如果该表或者对象不存在外部引用,则说明释放干净 否则内存泄漏工具会输出日志 @table t 观察的对象 表 @string tName 表的别名 @usage local memoryMonitor = new(MemoryMonitor) memoryMonitor:add_to_leak_monitor(self, "xx模块") ]] function MemoryMonitor:add_to_leak_monitor( t, tName ) if not self.__memLeakMonitor then return end assert("string" == type(tName), "invalid params") -- 必须以名字+地址的方式作为键值 -- 内存泄漏经常是一句代码多次分配出内存而忘了回收,因此tName经常是相同的 local name = string.format("%s@%s", tName, tostring(t)) if nil == self.__memLeakTable[name] then self.__memLeakTable[name] = t end end -- 更新弱表信息 function MemoryMonitor:update( dt ) dt = dt or 10 if self.__memLeakMonitor then self.__memLeakMonitor(dt) end end function MemoryMonitor:__mem_leak_monitoring( ... ) local monitorTime = MonitorConfig.memLeakInterval local interval = MonitorConfig.memLeakInterval local str = nil return function( dt ) interval = interval + dt if interval >= monitorTime then interval = interval - monitorTime -- 强制调用gc collectgarbage("collect") collectgarbage("collect") collectgarbage("collect") collectgarbage("collect") local flag = false -- 打印当前内存泄漏监控表中依然存在(没有被释放)的对象信息 str = "存在以下内存泄漏:" for k,v in pairs(self.__memLeakTable) do str = str .. string.format(" \n%s = %s", tostring(k), tostring(v)) flag = true end str = str .. "\n请仔细检查代码!!!" if flag then print(str) end end end end return MemoryMonitor --[[ -- TODO 待研究情况 print("--------------分隔符--------------") a = {} local b = {xxx = "xxx"} a.b = b memoryMonitor:add_to_leak_monitor(b, "b") a = nil -- 此时b仍然没有被释放掉 -- 可能是由于b是local变量,不仅有a在引用b,可能lua的堆栈也在对其引用,导致无法被释放。 -- 可以对比上面在函数里定义b的区别 memoryMonitor:update() --]] ================================================ FILE: utils/profiler.lua ================================================ --[[-- lua性能分析工具 @module profiler @author iwiniwin Date 2019-11-15 19:20:39 Last Modified by iwiniwin Last Modified time 2020-01-16 13:45:50 ]] --[[ debug.getinfo(level, arg) : 返回一个包含函数信息的table level表示函数调用的层级,表示要输出哪个函数的信息 arg是一个字符串,其中每个字符代表一组字段,用于指定希望获取那些信息,可以是"n","S","I","u","f","L"中的一个或组合 n : 表示name(函数名)和namewhat(函数类型,field, upvalue, global) S : 表示source(函数所属文件名), linedefined(函数定义起始行号), lastlinedefined(函数定义结束行号), what(函数类型,Lua, C, main), short_src(函数所属文件名,source的短版本) l : 表示currentline(上级函数被调用的行号) u : 表示nups(函数的upvalue值的个数) f : 表示func(函数本身) L : 表示activelines(一个包含行号的table,可理解为该函数运行的代码的行号) debug.sethook(hook, mask, count) : 将一个函数作为钩子函数设入。字符串mask以及数字count决定了钩子将在何时调用 掩码是由下列字符组合成的字符串 "c" : 每当lua调用一个函数时,调用钩子 "r" : 每当lua从一个函数内返回时,调用钩子 "l" : 每当lua进入新的一行时,调用钩子 当count值大于0的时候,每执行完count数量的指令后就会触发钩子 ]] -- package.path = package.path .. ";..\\?.lua;" -- require("_load") local EMPTY_TIME = "0.0000" -- Detect empty time, replace with tag below local emptyToThis = "~" local timeWidth = 7 local relaWidth = 6 local callWidth = 10 local divider = ""; local formatOutput = ""; local formatFunTime = "%04.4f" local formatFunRelative = "%03.1f" local formatFunCount = "%"..(callWidth-1).."i" local formatHeader = "" local scale = 1; local function charRepetition(n, character) local s = {} character = character or " " for _ = 1, n do table.insert(s,character) end return table.concat(s) end local Profiler = {} --[[ 创建一个性能分析工具对象 @string variant 性能分析模式 "call" or "time" @usage local profiler = new_profiler("call") profiler:start() -- do something profiler:stop() profiler:dump_report_to_file("profile.txt") ]] local function new_profiler( variant ) if Profiler.running then print("Profiler already running") return end variant = variant or "time" if variant ~= "time" and variant ~= "call" then print("Profiler method must be 'time' or 'call'") return end local newprof = {} for k,v in pairs(Profiler) do newprof[k] = v end newprof.variant = variant return newprof end --[[ 启动性能分析,核心是利用debug.sethook对函数调用进行钩子 每次只能启动一个 ]] function Profiler:start( ... ) if Profiler.running then return end Profiler.running = self self.caller_cache = {} self.callstack = {} self.start_time = os.clock() if self.variant == "time" then elseif self.variant == "call" then -- 因为垃圾回收会导致性能分析下降严重,所以先放缓垃圾回收 self.setpause = collectgarbage("setpause") self.setstepmul = collectgarbage("setstepmul") collectgarbage("setpause", 300) collectgarbage("setstepmul", 5000) self.coroutine_create = coroutine.create self.coroutines = {} coroutine.create = function(...) local co = self.coroutine_create(...) table.insert(self.coroutines, co) debug.sethook(co,profiler_hook_wrapper_by_call, "cr") return co end debug.sethook(profiler_hook_wrapper_by_call, "cr") else error("Profiler method must be 'time' or 'call'") end end --[[ 停止性能分析 ]] function Profiler:stop( ... ) if Profiler.running ~= self then -- 如果没有启动则没有任何效果 return end self.end_time = os.clock() if self.coroutine_create then coroutine.create = self.coroutine_create self.coroutine_create = nil end -- 停止性能分析 debug.sethook(nil) if self.variant == "call" then -- 还原之前的垃圾回收设置 collectgarbage("setpause", self.setpause) collectgarbage("setstepmul", self.setstepmul) end collectgarbage("collect") collectgarbage("collect") Profiler.running = nil end --[[ 钩子函数入口 ]] function profiler_hook_wrapper_by_call( action ) if Profiler.running == nil then debug.sethook(nil) end Profiler.running:analysis_call_info(action) end --[[ 分析函数调用信息 @string action 函数调用类型 action return tail return ]] function Profiler:analysis_call_info( action ) -- 获取当前的调用信息,注意该函数有一定的损耗 -- 0表示当前函数,即getinfo,1表示上一层调用即analysis_call_info,2表示再上一层,即profiler_hook_wrapper_by_call, 3即客户函数 local caller_info = debug.getinfo(3, "Slfn") if caller_info == nil then return end local last_caller = self.callstack[1] if action == "call" then -- 进入函数,标记堆栈 local this_caller = self:get_func_info_by_cache(caller_info) this_caller.parent = last_caller this_caller.clock_start = os.clock() this_caller.count = this_caller.count + 1 table.insert(self.callstack, 1, this_caller) else table.remove(self.callstack, 1) -- 移除顶部堆栈,有可能粗发连续触发return if action == "tail return" then return end local this_caller = self.caller_cache[caller_info.func] if this_caller == nil then return end -- 计算本次函数调用时长 this_caller.this_time = os.clock() - this_caller.clock_start -- 该函数累加调用时间 this_caller.time = this_caller.time + this_caller.this_time -- 更新父类信息 if this_caller.parent then local func = this_caller.func -- 更新父类中存储的该子函数的调用次数 this_caller.parent.children[func] = (this_caller.parent.children[func] or 0) + 1 -- 更新父类中存储的该子函数的总调用时间 this_caller.parent.children_time[func] = (this_caller.parent.children_time[func] or 0) + this_caller.this_time if caller_info.name == nil then -- 统计无名函数调用时间 this_caller.parent.unknow_child_time = this_caller.parent.unknow_child_time + this_caller.this_time else -- 统计有名函数调用时间 this_caller.parent.name_child_time = this_caller.parent.name_child_time + this_caller.this_time end end end end --[[ 获取缓存里的函数信息 @info 函数调用信息debug.getinfo返回的数据 ]] function Profiler:get_func_info_by_cache( info ) local func = info.func local ret = self.caller_cache[func] if ret == nil then ret = {} ret.func = func ret.count = 0 -- 调用次数 ret.time = 0 -- 时间 ret.unknow_child_time = 0 --没有名字的函数的调用时间 ret.name_child_time = 0 -- 有名字的函数的调用时间 ret.children = {} ret.children_time = {} ret.func_info = info self.caller_cache[func] = ret end return ret end --格式化成表格样式 function Profiler:format_header(ordering,lines,totalTime) local TABL_REPORTS = {}; local maxFileLen = 0; local maxFuncLen = 0; for i,func in ipairs(ordering) do local record = self.caller_cache[func] local reportInfo = { count = record.count, timer = record.time, src = record.func_info.short_src, name = record.func_info.name or "unknow", linedefined = record.func_info.linedefined, what = record.func_info.what, source = record.func_info.source; } reportInfo.src = self:pretty_name(func,true); --计算最长的名字 if string.len(reportInfo.src) > maxFileLen and reportInfo.count > 0 then maxFileLen = string.len(reportInfo.src) + 1; end if string.len(reportInfo.name) > maxFuncLen and reportInfo.count > 0 then maxFuncLen = string.len(reportInfo.name) + 1; end table.insert(TABL_REPORTS,reportInfo); end if maxFileLen>=99 then --必须如此处理,不然会报错越界 maxFileLen = 99; end -- if maxFuncLen>100 then -- maxFuncLen = 100; -- end -- print(maxFileLen,"maxFileLen") formatOutput = "| %-"..maxFileLen.."s: %-"..maxFuncLen.."s: %-"..timeWidth.."s: %-"..relaWidth.."s: %-"..callWidth.."s|\n" -- dump(formatOutput) formatHeader = string.format(formatOutput, "FILE", "FUNCTION", "TIME", "%", "Call count") divider = charRepetition(#formatHeader-1, "-").."\n" table.insert(lines, "\n"..divider) table.insert(lines, formatHeader) table.insert(lines, divider) local totalCount = 0; for i,reportInfo in ipairs(TABL_REPORTS) do if reportInfo.count > 0 and reportInfo.timer <= totalTime then local count = string.format(formatFunCount, reportInfo.count) local timer = string.format(formatFunTime, reportInfo.timer) local relTime = string.format(formatFunRelative, (reportInfo.timer / totalTime) * 100) if timer == EMPTY_TIME then timer = emptyToThis relTime = emptyToThis end local outputLine = string.format(formatOutput, reportInfo.src,reportInfo.name, timer, relTime, count) table.insert(lines, outputLine) totalCount = totalCount + reportInfo.count; end end table.insert(lines, divider) table.insert(lines, "\n\n") table.insert(lines, 2,"Total call count spent in profiled functions: " .. totalCount.. "\n\n") end --[[-- 生成报表table @return table 报表 @return number 性能分析总时间 @usage local new_profiler = import("bos.core.profiler") local profiler = new_profiler("call") profiler:start(); -- do something profiler:stop(); profiler:report(); ]] function Profiler:report() local lines = {}; table.insert(lines,[[Lua Profile output created by profiler.lua. author: iwiniwin ]]) table.insert(lines, "\n\n" ) local total_time = self.end_time - self.start_time table.insert(lines, 1,"Total time spent in profiled functions: " .. string.format("%5.3g",total_time) .. "s\n\n") -- This is pretty awful. local terms = {} if self.variant == "time" then elseif self.variant == "call" then terms.capitalized = "Call" terms.single = "call" terms.pastverb = "called" local ordering = {} for func,record in pairs(self.caller_cache) do table.insert(ordering, func) end table.sort( ordering, function(a,b) return self.caller_cache[a].time > self.caller_cache[b].time end ) --生成头部表格信息 self:format_header(ordering,lines,total_time); for i,v in ipairs(ordering) do local func = ordering[i] local record = self.caller_cache[func] if record.count and record.count > 0 then --- 标记数量大于0的 local thisfuncname = " " .. self:pretty_name(func) .. " " if string.len( thisfuncname ) < 42 then thisfuncname = string.rep( "-", math.floor((42 - string.len(thisfuncname))/2 )) .. thisfuncname thisfuncname = thisfuncname .. string.rep( "-", 42 - string.len(thisfuncname) ) end --单个函数的总时间减去子函数的时间,获得自身的时间 local timeinself = record.time - (record.unknow_child_time + record.name_child_time) if timeinself < 0 then timeinself = 0; end local children = record.unknow_child_time+record.name_child_time if children > record.time then children = record.time end timeinself = timeinself * scale; table.insert(lines, string.rep( "-", 19 ) .. thisfuncname .. string.rep( "-", 19 ) .. "\n" ) table.insert(lines, terms.capitalized.." count: " .. string.format( "%4d", record.count ) .. "\n" ) table.insert(lines, "Time spend total: " .. string.format( "%4.4f", record.time * scale) .. "s\n" ) table.insert(lines, "Time spent in children: " .. string.format("%4.4f",(children) * scale) .. "s\n" ) table.insert(lines, "Time spent in self: " .. string.format("%4.4f", timeinself) .. "s\n" ) -- Report on each child in the form -- Child called n times and took a.bs local added_blank = 0 for k,v in pairs(record.children) do if added_blank == 0 then table.insert(lines, "\n" ) -- extra separation line added_blank = 1 end table.insert(lines, "Child " .. self:pretty_name(k) .. string.rep( " ", 41-string.len(self:pretty_name(k)) ) .. " " .. terms.pastverb.." " .. string.format("%6d", v) ) table.insert(lines, " times. Took " .. string.format("%4.5f", record.children_time[k] * scale ) .. "s\n" ) end table.insert(lines, "\n" ) -- extra separation line end end end table.insert(lines, [[ END ]] ) return lines,total_time end --[[-- 输出报表到文件 @tparam table self Profiler对象 @tparam string outfile 文件名称 @return number 本次总共花费时间 @usage local new_profiler = import("bos.core.profiler") local profiler = new_profiler("call") profiler:start(); -- do something profiler:stop(); profiler:dump_report_to_file("path"); ]] function Profiler.dump_report_to_file(self,outfile) local outfile = io.open(outfile, "w+" ) local lines, total_time= self:report() for i,v in ipairs(lines) do outfile:write(v) end outfile:flush() outfile:close() return total_time end --[[ 美化名称,输出可以看懂的信息 @tparam function func 函数 @boolean force 是否强制 ]] function Profiler:pretty_name(func,force) -- Only the data collected during the actual -- run seems to be correct.... why? local info = self.caller_cache[func].func_info local name = "" if info.what == "Lua" and force then name = "L:" .. info.short_src ..":" .. info.linedefined return name; end if info.what == "Lua" then name = "L:" end if info.what == "C" then name = "C:" end if info.what == "main" then name = " :" end if info.name == nil then name = name .. "<"..tostring(func) .. ">" else name = name .. info.name end if info.source then name = name .. "@" .. info.source else if info.what == "C" then name = name .. "@?" else name = name .. "@" end end name = name .. ":" -- if info.what == "C" then -- name = name .. "unknow line" -- else -- name = name .. info.linedefined -- end name = name .. info.linedefined return name end return new_profiler