stop718 发表于 2024-2-29 13:03:05

LUA协程和多任务笔记

最近在学习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 脚本1101   co1=running   co2=suspended   co3=suspended
036CBF54 脚本1202   co1=running   co2=suspended   co3=suspended
036CC01C 脚本2301   co1=suspended   co2=running   co3=suspended
036CBF54 脚本1303   co1=running   co2=suspended   co3=suspended
036CBF54 脚本1404   co1=running   co2=suspended   co3=suspended
036CC0E4 脚本3501   co1=suspended   co2=suspended   co3=running
036CBF54 脚本1505   co1=running   co2=suspended   co3=suspended
036CBF54 脚本1结束    co1=running   co2=suspended   co3=suspended
036CC01C 脚本2602   co1=dead      co2=running   co3=suspended
036CC01C 脚本2结束    co1=dead      co2=running   co3=suspended
036CC0E4 脚本31002    co1=dead      co2=dead      co3=running
036CC0E4 脚本31503    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;
      if (id == uIDEvent)
      {
            lua_State *L = (lua_State*)LuaTimerList->Items;
            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;
}






--C中实现协程
--以脚务器消息接收为例
--编写一个消息处理的函数Lua(伪代码)
function Recv(Recog, Ident, Param, Tag, Series, buf, len)
    --if 定义 and 定义.过滤==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)=='dead' then
            mir.copool=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);
    }
}

页: [1]
查看完整版本: LUA协程和多任务笔记