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