首页
社区
课程
招聘
[分享]LUA协程和多任务笔记
发表于: 2015-8-27 20:11 9967

[分享]LUA协程和多任务笔记

2015-8-27 20:11
9967
最近在学习lua多任务, 用于外挂游戏服务器消息的处理, 因为脚本消息很多,
每分钟有几十条到上百条, 如果不能多任务并行处理, 会拖慢游戏,也不实用
整理下, 做个笔记, 以免忘了, 代码也是百度来的, 改改能用
lua用5.3.1版, 和我原来的5.1版本有不兼营, 按网上的方法改成支持中文变量
这样挺好的, 代码也不用注释, 取名字方便,也短, 不然如果用英文
背包-->bag 血-->HP 兰-->MP 还好翻译好记
怪物-->monster 人物-->human 就不有点长,不好记
地面物品-->dropeditem 装备-->useitem 狂欢应典活动-->KHQD 太不直观了
xx地图 xxNPC xx任务 就十分难翻译了,
闲话结束, 正文开始
----------------------------------------------

--LUA协程和多任务
--3个同时运行的协程为例
--每隔一定的间隔输出文字, 协程状态
--第1个 隔101毫秒 输出5次
--第2个 隔101毫秒 输出2次
--第3个 隔101毫秒 输出3次
--实现协程的关键是要 延时 = mylib.delay 这个函数
--标准库里没有这个函数 用C扩展为DLL库来使用
--先启用定时器, 然后把协程挂起(yield), 定时器时间到了再唤醒(resume)
--这样就能实现多个任务同时运行
--协程一次只能有1个运行(running), 其它都是在挂起状态(suspended)
--当协程运行结束后, 就是停止状态(dead)
--协程挂起(yield), 唤醒(resume), 应用有一定限制,就是不能在C和LUA间交叉使用
--即在C中挂起的,就要在C中唤醒, Lua中挂起的,也要在LUA中唤醒
--协程的开销不大, 初略估计了下, 开启运行2万多个协程,结束后故意不消毁,内存约增加20多M
--1个协程的开销约1k多内存

--Lua脚本
延时 = mylib.delay

function 显示(a,b)
  print(a,b,'co1='..coroutine.status(co1),'co2='..coroutine.status(co2),'co3='..coroutine.status(co3))
end

function 协程(序号,次数,毫秒)
    for i=1,次数 do
        延时(毫秒) 显示(序号,i*毫秒 )
    end
    显示(序号,'结束' )
end

co1=coroutine.create(协程) coroutine.resume(co1, '脚本1', 5, 101)
co2=coroutine.create(协程) coroutine.resume(co2, '脚本2', 2, 301)
co3=coroutine.create(协程) coroutine.resume(co3, '脚本3', 3, 501)

--运行结果
036CBF54 脚本1  101     co1=running     co2=suspended   co3=suspended
036CBF54 脚本1  202     co1=running     co2=suspended   co3=suspended
036CC01C 脚本2  301     co1=suspended   co2=running     co3=suspended
036CBF54 脚本1  303     co1=running     co2=suspended   co3=suspended
036CBF54 脚本1  404     co1=running     co2=suspended   co3=suspended
036CC0E4 脚本3  501     co1=suspended   co2=suspended   co3=running
036CBF54 脚本1  505     co1=running     co2=suspended   co3=suspended
036CBF54 脚本1  结束    co1=running     co2=suspended   co3=suspended
036CC01C 脚本2  602     co1=dead        co2=running     co3=suspended
036CC01C 脚本2  结束    co1=dead        co2=running     co3=suspended
036CC0E4 脚本3  1002    co1=dead        co2=dead        co3=running
036CC0E4 脚本3  1503    co1=dead        co2=dead        co3=running
036CC0E4 脚本3  结束    co1=dead        co2=dead        co3=running

--延时 = mylib.delay 的C代码 用C++Builder 6环境
--调用delay时,首先创建1个定时器
  int id = SetTimer(NULL, NULL, ms, (TIMERPROC)LuaTimerProc);
--然后保存协程句柄,定时器id到TList LuaTimerList,到定时器回调时有用
  LuaTimerList->Add((void*)L);
  LuaTimerList->Add((void*)id);
--然后,挂起协程
--时间到了,定时器回调函数运行
--因为定时器只用一次就够了, 故销毁之
    KillTimer(hWnd, uIDEvent);
--在TList LuaTimerList找到对应的记录,获得协程句柄,唤醒之
    lua_resume(L, 0, 0);
--注意, 在dll退出时, 记得销毁LuaTimerList, 不然内存略有泄漏
    case DLL_PROCESS_DETACH:
        delete LuaTimerList;
        break;
--在lua中使用方法, (编译后的库名为mylib.dll)
    mylib = require('mylib')
    mylib.delay(100)  --延时100ms
--函数只能在协程内使用, 在主线程使用会出错
--不过没关系, 可以让所有脚本都在协程运行,按下列方式即可
function f()
--------------------
--这里插入要执行的内容
--------------------
end
co=coroutine.create(f)
coroutine.resume(co)

//---------------------------------------------------------------------------
//Lua扩展库测试

#pragma hdrstop

//---------------------------------------------------------------------------

#pragma package(smart_init)
#include <vcl.h>
#include <math.h>
#include <windows.h>
#include "LuaMylib.h"

TList *LuaTimerList;

//void _stdcall LuaTimerProc(int hwnd, int uMsg, int idEvent, int time)
void   CALLBACK   LuaTimerProc(HWND hWnd, UINT nMsg, UINT uIDEvent, DWORD dwTime)
{
    KillTimer(hWnd, uIDEvent);
    for (int i=(LuaTimerList->Count / 2) -1;i>=0; i--)
    {
        UINT id = (UINT)LuaTimerList->Items[i*2+1];
        if (id == uIDEvent)
        {
            lua_State *L = (lua_State*)LuaTimerList->Items[i*2];
            LuaTimerList->Delete(i*2);
            LuaTimerList->Delete(i*2);
            lua_resume(L, 0, 0);
        }
    }
}

static int delay (lua_State *L) {
  if (lua_gettop(L) == 0) return 0;
  int ms = lua_tointeger(L, 1);
  int id = SetTimer(NULL, NULL, ms, (TIMERPROC)LuaTimerProc);
  if (LuaTimerList == NULL )LuaTimerList = new(TList);
  LuaTimerList->Add((void*)L);
  LuaTimerList->Add((void*)id);
  lua_pop(L, 1);//弹出参数
  return lua_yield(L, 0);
}

static const luaL_Reg mylib[] = {
  {"delay",   delay},
  {NULL, NULL}
};

/*
** Open test library
*/
LUALIB_API int luaopen_mylib (lua_State *L) {
    luaL_newlib(L, mylib);
    return 1;
}

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 0
支持
分享
最新回复 (12)
雪    币: 19
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
--C中实现协程
--以脚务器消息接收为例
--编写一个消息处理的函数Lua(伪代码)
function Recv(Recog, Ident, Param, Tag, Series, buf, len)
    --if 定义[Ident] and 定义[Ident].过滤==1 then return end;
    print('收',翻译(Ident,Recog,Param,Series,Tag,len,buf))
    mylib.delay(5000)
    --print('协程结束')
    回收协程()
end

--编写一个消息转发Lua的C函数 相当于Lua下列语句,挂到消息接收上
static _stdcall void SocketRecvHook(int buf, int len)
local co=coroutine.create(recv)
...转化内存数据到变量
coroutine.resume(co, Recog, Ident, Param, Tag, Series, buf, len)
库名.copool=co //必须, 不然协程被当垃圾回收出错, 也可以留在栈里

--如此每收到1个消息就开启1个协程, 所以协程数量增长很快,
--如果协程运行结束, 就要进行回收, 即可在C中编写,也可在LUA中
function 回收协程()
    local 总数=0
    local 回收=0
    for i in pairs(mir.copool) do
        总数 = 总数+1
        if coroutine.status(mir.copool[i])=='dead' then
            mir.copool[i]=nil --垃圾回收器会自动回收该协程
            回收=回收+1
        end
    end
    --if 回收>0 then print('协程总数,回收,剩余=',总数,回收,总数-回收) end
end

--C代码如下
static _stdcall void SocketRecvHook(int buf, int len)
{
    //调用原来的程序
    OrgSocketRecvDecode(buf, len);

    //调用LUA程序
    PTDefaultMessage pm = (PTDefaultMessage)(buf+4);
    //co=coroutine.create(recv)
    lua_getglobal(L, "coroutine");  //--->+1
    lua_getfield(L, -1, "create"); //调用函数coroutine.create//--->+2
    //获得self.recv
    lua_pushlightuserdata(L, (void*)&Key); //压入地址   //--->+3
    lua_gettable(L, LUA_REGISTRYINDEX);//table在栈顶    //--->+4
    lua_getfield(L, -1, "recv"); //self.Recv           //--->+5
    lua_remove(L, -2);//移除table                      //--->+4
    if (lua_isfunction(L, -1))
    {
    lua_call(L, 1, 1);//1个参数,1个结果->co            //--->+2
    //库名.copool=co
    savepool(IntToStr(GetTickCount())+"_R_"+IntToStr(pm->Ident) );
    //coroutine.resume(co,参数)
    lua_getglobal(L, "coroutine");
    lua_getfield(L, -1, "resume"); //调用函数coroutine.resume
    lua_pushvalue(L, -3);//复制co
    lua_pushinteger(L, pm->Recog);//1
    lua_pushinteger(L, pm->Ident);//2
    lua_pushinteger(L, pm->Param);//3
    lua_pushinteger(L, pm->Tag);//4
    lua_pushinteger(L, pm->Series);//5
    lua_pushinteger(L, buf + 16);//6
    lua_pushinteger(L, len - 16);//7
    lua_call(L, 1+7, 0);//1+7个参数,0个结果->co
    lua_pop(L, 3);
    }
    else
    {
        lua_pop(L, 3);
    }
}
2015-8-27 20:12
0
雪    币: 19
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
实际使用效果

0B0E2AE4 收        1107        681879936        16        0        0        0        nil
0B0E3860 收        17=人?        598938656=角色        13=X        16        15=Y        8       
0B0E3EA0 收        638=人?开盾        598938656=角色        13=X        4100        15=Y        8         ?
0B0E44E0 收        17=人?        610322560=角色        10=X        29        18=Y        8       
0B0E4B20 收        657=人?        610322560=角色        0        0        24        0        nil
0B0E5028 收        638=人?开盾        610322560=角色        10=X        7428        18=Y        8       
0B0D907C 收        10=转        427863328=角色        15=X        0=方向        16=Y        8       
0B0D1188 收        11=走        569500128=角色        13=X        3=方向        18=Y        8       
0B0CE928 收        13=跑        536616704=角色        10=X        6=方向        20=Y        8       
0B0D9E7C 收        13=跑        536616704=角色        8=X        5=方向        22=Y        8       
0B0DA53C 收        30=人没        536616704=角色        0        0        0        0        nil
0B0DAB88 收        30=人没        442646400=角色        0        0        0        0        nil
0B0DB1E4 收        17=人?        440465568=角色        8=X        16        21=Y        8       
0B0DB8E8 收        17=人?        598938656=角色        13=X        16        15=Y        8       
0B0DBF88 收        638=人?开盾        598938656=角色        13=X        4100        15=Y        8         ?
0B0DC600 收        10=转        681895344=角色        14=X        4=方向        14=Y        17       
0B0DCD08 收        42=称号        681895344=角色        255        0        0        5        入入
0B0DD3B0 收        10=转        681895344=角色        14=X        4=方向        14=Y        17       
0B0DD9F0 收        2873=人?        681895344=角色        14=X        4=方向        14=Y        0        nil
2015-8-27 20:44
0
雪    币: 19
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
0B0FC5C8 发        3013=自跑        12=X        17=Y        1=方向        0        0        nil
0B0FCC18 发        3010=自转        12=X        17=Y        3=方向        0        0        nil
0B0FD270 发        3013=自跑        12=X        19=Y        4=方向        0        0        nil
0B0FD88C 发        3013=自跑        10=X        21=Y        5=方向        0        0        nil
0B0FDEA8 发        3011=自走        9=X        22=Y        5=方向        0        0        nil
0B0FE4F4 发        3011=自走        9=X        23=Y        4=方向        0        0        nil
0B0FEB44 发        3011=自走        8=X        22=Y        7=方向        0        0        nil
0B0F06B8 发        1033        0        0        0        0        0        nil
0B0F0CF0 发        3011=自走        380=X        301=Y        5=方向        0        0        nil
0B0F1374 发        3013=自跑        382=X        303=Y        3=方向        0        0        nil
0B0F19F8 发        3013=自跑        382=X        305=Y        4=方向        0        0        nil
0B0F2048 发        3011=自走        381=X        305=Y        6=方向        0        0        nil
0B0F2698 发        3013=自跑        381=X        307=Y        4=方向        0        0        nil
0B0C9878 发        3011=自走        381=X        308=Y        4=方向        0        0        nil
0B0E37D4 发        3013=自跑        383=X        310=Y        3=方向        0        0        nil
0B0F7F94 发        3013=自跑        385=X        308=Y        1=方向        0        0        nil
0B0EE3FC 发        3013=自跑        385=X        306=Y        0=方向        0        0        nil
0B0CF590 发        3013=自跑        383=X        304=Y        7=方向        0        0        nil
0B0CFBE8 发        1010        116604992        0        0        0        0        nil
0B0CB988 发        1011=NPC命令        116604992=NPC        0        0        0        8        @goback
0B0C80C0 发        1033        0        0        0        0        0        nil
0B0C86F8 发        1010        117189504        0        0        0        0        nil
0B0D8540 发        1011=NPC命令        117189504=NPC        0        0        0        8        @gostep
0B0D40D8 发        1033        0        0        0        0        0        nil
0B0D46F4 发        3013=自跑        331=X        333=Y        7=方向        0        0        nil
0B0D4D78 发        3013=自跑        329=X        331=Y        7=方向        0        0        nil
0B0D53E4 发        1010        117189504        0        0        0        0        nil
0B0D5A00 发        1011=NPC命令        117189504=NPC        0        0        0        9        @godrink
0B0D607C 发        1033        0        0        0        0        0        nil
0B0DE05C 发        1010        116604992        0        0        0        0        nil
0B0DE678 发        1011=NPC命令        116604992=NPC        0        0        0        9
2015-8-27 20:50
0
雪    币: 19
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
lua的C API
就是操作LUA栈, 很难计算, 不过编好了, 使用起来却时很方便的
我也算不来, 先写好实现的语句, 不管堆栈
运行时记下每处的堆栈变化, 再调整语句, 最后注释掉记录语句
这样比较省力, 也不会出错,如
s += " " + IntToStr(lua_gettop(L));
    某些操作
s += " " + IntToStr(lua_gettop(L));
    某些操作
s += " " + IntToStr(lua_gettop(L));
    某些操作
s += " " + IntToStr(lua_gettop(L));
    某些操作
print(s.c_str()); //打印出堆栈变化情况

在C中调用lua的print如下
static void print(char *msg)
{
    lua_getglobal(::L, "print");
    lua_pushstring(::L, msg);
    lua_call(::L, 1, 0);//1个参数,0个结果
}
2015-8-27 20:52
0
雪    币: 22
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
MARK下!
2015-8-27 21:45
0
雪    币: 8
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
好历害的吧
2015-8-28 00:24
0
雪    币: 19
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
Delphi7 编写LUA 扩展库
因为Delphi7编写东西比较快, 所以做了下试验
C++虽也很好, 不过还不熟, 要边百度边写, 很慢,
网上找来的lua头文件, 是5.1的, 不适合5.3, 先用着
有空再改一个出来

目的: 生成一个mylib.dll的库,包含2个函数xx,yy
先把mylib.dll拷到lua目录, 使用方法:
mylib=require('mylib')
print( mylib.xx() ) -->输出 mylib.xx
print( mylib.yy() ) -->输出 1818
实现方法:
新建DLL工程, 保存为mylib
添加单元,添加以下内容,编译即可

unit lualib_test;

interface
uses
  SysUtils,
  Classes,
  lua, luapas; //lua头文件

type
TMyLib = class
published //所有库函数定义在这里,才能注册到库
  function xx: integer;cdecl;
  function yy: integer;cdecl;
end;

procedure RegisterFunctions(L:plua_State; Cls: TClass);//注册库函数

implementation

function luaopen_mypas(L:plua_State):  Integer; cdecl;
begin
  luapas.lua_newtable(L);
  RegisterFunctions(L, TMyLib);
  result := 1;
end;

exports luaopen_mypas; //导出函数

//注册类的published方法到库, 假设table在栈顶
procedure RegisterFunctions(L:plua_State; Cls: TClass);
type
  PPointer = ^Pointer;
  PMethodRec = ^TMethodRec;

  TMethodRec = packed record
    wSize: Word;
    pCode: Pointer;
    sName: ShortString;
  end;
var
  MethodTable: PAnsiChar;
  MethodRec: PMethodRec;
  wCount: Word;
  nMethod: Integer;
begin
  // Get a pointer to the class's published method table
  MethodTable := PAnsiChar(Pointer(PAnsiChar(Cls) + vmtMethodTable)^);

  if (MethodTable <> Nil) then
  begin
    // Get the count of the methods in the table
    Move(MethodTable^, wCount, 2);

    // Position the MethodRec pointer at the first method in the table
    // (skip over the 2-byte method count)
    MethodRec := PMethodRec(MethodTable + 2);

    // Iterate through all the published methods of this class
    for nMethod := 0 to wCount - 1 do
    begin
      // Add the method name to the lua functions
      // 名称:MethodRec.sName 地址:MethodRec.pcode
      lua_pushcclosure(L, MethodRec.pcode, 0); //地址
      lua_setfield(L, -2, PAnsiChar(AnsiString(MethodRec.sName))); //名称
      // Skip to the next method
      MethodRec := PMethodRec(PAnsiChar(MethodRec) + MethodRec.wSize);
    end;
  end;
end;

{ mylib }

function Tmylib.xx: integer;
begin
  lua_pushstring(self, 'mylib.xx');
  result:=1;
end;

function Tmylib.yy: integer;
begin
  lua_pushinteger(self, 1818);
  result:=1;
end;

end.

如果要添加新的函数
只要在TMyLib类中添加新的函数,并编好实现代码即可,
函数放在published部分可以自动注册
放在其它部分要手动注册, 可以用以下代码
//table在栈顶
lua_pushcclosure(L, 函数地址, 0);
lua_setfield(L, -2, 函数名称);
2015-8-29 02:37
0
雪    币: 8107
活跃值: (1955)
能力值: ( LV8,RANK:122 )
在线值:
发帖
回帖
粉丝
9
学习一下LUA
2015-8-30 12:34
0
雪    币: 19
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
因为原来用的头文件是5.1版的
开始没找到5.3版的, 准备自已编个
前天在坐长途车时, 找到一个
Dennis D. Spreen写好的一个例子
里面是用5.3.0版的, 正好可以用
原文是Delphi XE版的,
稍微改下就能用了
原文有2个文件VerySimple.Lua.Lib.pas, VerySimple.Lua.pas
我只用其中的一个VerySimple.Lua.Lib.pas
都放在压缩包里
上传的附件:
2015-8-30 22:56
0
雪    币: 19
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
外挂在游戏中有许多数据要读取
如果一个个读取很麻烦, 如果绑定为LUA对象就好用多了
下面是以Delhpi TList对象的绑定为例( Delphi 7)

unit unitDelphi;
{ Tlist类 脚本调用
  mir = require('luamir')

  list = mir.tlist(TList的指针)

  count = list.count
  for i = 1, count do
    print( list[i])
  end

  find = list.indexof(list, AItem)

  --或使用:操作符 更直观
  find = list:indexof(AItem)

}

interface
uses
  SysUtils,StrUtils,
  Classes,
  lualib,luapackage;

type TLuaDelphi = class(TLuaPackage)
  private
    function TList__index: integer;cdecl;
    function TList__indexof: integer;cdecl;

  published
    function TList: integer;cdecl;

end;
var
  List:TList;
  i:integer;

  function open_Delphi(L:lua_State):  Integer; cdecl;

implementation

function open_Delphi(L:lua_State):  Integer; cdecl;
begin
  RegisterFunctions(L, TLuaDelphi);

  //用于测试TList
  if List = nil then List := TList.create;
  lua_pushinteger(L, integer(List));
  lua_setfield(L, -2, 'testlist');
  for i:=1 to 10 do begin
    List.Add(pointer(i*100));
  end;
  //
  //Delphi TList类
  luaL_newmetatable(L, 'Delphi.TList');
  lua_pushcclosure(L, @TLuaDelphi.TList__index, 0);
  lua_setfield(L, -2, '__index');

end;

{ TLuaDelphi }

function TLuaDelphi.TList: integer;
var
  str:string;
  p : integer;
begin
  p := lua_tointeger(self,1);
  lua_pushlightuserdata(self, pointer(p));
  luaL_getmetatable(self, 'Delphi.TList');
  lua_setmetatable(self, -2);
  result:=1;
end;

function TLuaDelphi.TList__index: integer; //堆栈List,Index
var
  List: Classes.TList;
  Key : pAnsiChar;
  Index : integer;
begin
  result := 1;
  try
    List := Classes.TList( lua_touserdata(self, 1));
    Key := lua_tostring(self, 2);
    Index := lua_tointeger(self, 2);

    if (index>0) and (index<=List.Count)  then begin
      lua_pushinteger(self, integer(List.Items[Index-1]));
    end else if AnsiStrIComp(Key, 'count')=0 then begin
      lua_pushinteger(self, List.Count);
    end else if AnsiStrIComp(Key, 'indexof')=0 then begin
      lua_pushcclosure(self, pointer(@TLuaDelphi.TList__indexof), 0);
    end else begin
      result := 0;
    end;
  except
    lua_pushstring(self, 'Trace List,Index,Key');
    lua_pushinteger(self, integer(List));
    lua_pushinteger(self, Index);
    lua_pushstring(self, Key);
    error();
    result :=0 ;
  end
end;

//脚本调用 list:indexof(item)
function TLuaDelphi.TList__indexof: integer;
var
  List: Classes.TList;
  Item: Integer;
begin
  try
    List := Classes.TList( lua_touserdata(self, 1));
    Item := lua_tointeger( self, 2);
    lua_pushinteger(self, List.IndexOf(pointer(Item))+1);
    result:=1;
  except
    lua_pushstring(self, 'Trace List,Item');
    lua_pushinteger(self, integer(List));
    lua_pushinteger(self, Item);
    error();
    result := 0;
  end;
end;

end.
2015-8-30 23:10
0
雪    币: 21
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
mark
2015-8-30 23:19
0
雪    币: 117
活跃值: (20)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
jdt
13
必须MARK,看了楼主的DELPHI应用的LUA,学习了思路。谢了。
2015-10-17 22:40
0
游客
登录 | 注册 方可回帖
返回
//