26 KiB
LuaPluginManage
总述
整体功能
实现Lua插件的管理功能,功能整体包括如下部分:
- 状态机的管理及维护:功能包括状态机的创建、删除、初始化、获取运行状态等;
- 注册函数的管理功能:C将函数注册至Lua环境中,并可在Lua中进行调用;提供函数检查功能,为保证调用过程安全可靠,在传入过程中对安全状态进行检查;
- 数据的管理功能:C将数据注册至Lua环境中,如全局使用的字符串、版本信息、运行环境等;
- 插件的管理功能:将一个Lua插件加载至插件管理中,与C插件一致,将插件注册至stellar-plugin_manage及session_plugin_manage中;
- 数据转化:可以将Lua中的数据与C中的数据进行安全的数据转换,用于在C中暂存Lua中保存的数据,并可以对该数据进行修改、维护;
状态机(state)
状态机用于保存Lua运行虚拟机在运行过程中所有的环境表、注册表、堆栈、数据段、上下文等数据。Lua状态机是Lua运行过程中对外暴露的唯一数据结构,其余数据结构均已经在Lua内部隐藏,所有对Lua的操作均需要通过改变状态机的状态来实现。一个Lua状态机可以理解为一个Lua的线程,该状态机和C的线程一样,拥有独立的内存空间。
创建:安全的新建一个空的状态机
删除:释放一个状态机,删除其内部所有数据占用的内存并释放一个状态机实例
初始化:状态机初始化过程中加载运行时必要的Lua组件,并根据配置文件将各个Lua插件加载在状态机中
获取运行错误*:获取状态机运行过程中的错误
获取运行状态*:获取状态机运行状态,包括运行时间、加载插件数量、运行耗时等
函数(function)
向Lua内部注册一个函数,可供Lua程序在运行过程中调用。如向lua中传入数据结构时,可通过注册函数的方式向Lua提供数据的获取方法。或通过注册函数向Lua提供其他模块的外部接口。
注册:向Lua状态机中注册一个格式要求符合lpm_cbinding_function类型的函数,该函数可供Lua脚本调用
删除:从Lua状态机中删除一个C注册的函数
安全检查*:提供一个标准化的定义方式,在Lua插件编写过程中可通过该定义方式得知运行过程中存在的注册函数名称、参数类型等,防止在调用过程中出错;在Lua脚本加载前对其内部调用的C函数进行安全性检查,检查如传入参数个数、参数类型等
自动化函数翻译*:通过特定方法能够自动将一些C函数转换为Lua的注册函数格式
数据(data)
向Lua内部注册一个函数,可供Lua脚本在运行过程中使用。如向Lua中传入运行程序版本、运行环境信息、系统信息等。
注册:向状态机中注册一个全局变量,变量类型可以为数字、字符串;如需注册一个数据结构需同时注册该数据结构的成员获取方法
删除:从状态机中删除一个全局变量
数据范式化*:将一段Lua数据范式化为一段json或其他特定格式,方便输出或在其他程序中调用
插件(plugin)
插件为一整个Lua功能模块,与C中的插件含义相同。
插件加载:将一个插件加载至插件管理器中
插件卸载:从插件管理器中卸载一个插件
会话插件加载:将一个会话插件加载至session_plugin_manage中
会话插件卸载:从session_plugin_manage中删除一个会话插件
注册会话插件:此功能仅在lua端暴露,不在C接口中。在插件加载过程中可注册会话插件
订阅会话消息:此功能仅在lua端暴露,不在C接口中。在插件加载过程中可订阅一个会话中特定id的会话消息
处理会话消息:处理会话过程中的数据
模块(model)
模块为加载Lua的一个通用文件,模块与配置项一一对应,将一个配置项加载为一个插件管理器中的模块 模块加载:向插件管理器中插入一个新的模块 由于模块没有唯一标识,在模块插入后不支持单独卸载,在插件管理器整体退出过程中会卸载所有插入的模块
代码块(chunk)
代码块可以是一段Lua代码、一个Lua文件或一个Lua函数。在注册过程中,在状态机中加载一段lua代码,并将其通过引用方式确定其一个引用编号,调用过程中通过引用编号在状态机中调用一段代码。参数及返回值的传递通过数据模块进行。目前支持的数据类型包括数字、字符串、table、userdata。 代码块执行:执行一段代码,并获取其执行结果
数据转换*
将Lua数据翻译为C中可读可写可持久化保存的数据,也可以将一个由Lua转换为C数据得到的序列化数据转换为Lua中的数据结构 Lua转换至C:将Lua数据序列化成为C中一段内存数据 C转换至Lua:将C中数据反序列化为Lua中一段数据
接口
外部接口
| 函数名称 | 参数 | 返回值 | 功能 |
|---|---|---|---|
| lua_plugin_manage_init | struct stellar * st int specific_count struct lua_config_specific *specifics |
创建的lua插件管理器实例指针 | 根据配置文件创建一个lua插件管理器实例,并在函数内完成配置加载及初始化过程 |
| lua_plugin_manage_exit | struct lua_plugin_manage_schema * lua_plug_mgr | void | 清理一个lua插件管理器实例,清除内部所有数据 |
| lua_plugin_manage_load_one_specific | struct lua_plugin_manage_schema *schema struct lua_config_specific *specific |
0成功,其他失败 | 在lua插件管理器中根据配置注册一个新的插件 |
内部接口
| 函数名称 | 参数 | 返回值 | 功能 |
|---|---|---|---|
| lua_cbinding_functions | lua_State *state | 0表示成功,其他表示失败,返回值大于0时表示注册失败的元素个数 | 向lua状态机中注册函数,函数定义在全局变量lua_bind_functions中 |
| lua_cbinding_datas | lua_State *state | 0表示成功,其他表示失败,返回值大于0时表示注册失败的元素个数 | 向lua状态机中注册数据,所有注册数据在状态机中作为全局变量存在,所有注册数据保存在lua_bind_datas全局变量中 |
| lua_cdata_push_stack | lua_State *state struct lua_cdata *cdata |
0表示成功,其他表示失败 | 将一个data结构入栈 |
| lua_cdata_pop_stack | lua_State *state struct lua_cdata *cdata |
0表示成功,其他表示失败 | 从栈中弹出一个元素,并保存在data结构中,类型限制见函数实现部分 |
| lua_cdata_destory | struct lua_cdata *cdata | void | 销毁一个data结构,只有string类型需要调用此函数,其他的情况直接释放即可 |
| lua_context_new | lua_State *state | 创建出的context实例 | 在状态机中生成一个context |
| lua_context_push_stack | struct lua_context *context | 0表示成功,其他表示失败 | 将一个context入栈 |
| lua_context_free | struct lua_context *context | void | 释放一个context |
C注册函数
| 函数名称 | 参数 | 返回值 | 功能 |
|---|---|---|---|
| lpm_ctx_new_func | struct session *sess void *plugin_env |
在该session中创建的context实例 | session创建时调用 |
| lpm_ctx_free_func | struct session *sess void *sess_ctx void *plugin_env |
void | session销毁时调用 |
| lpm_message_free_func | struct session *sess void *msg void *msg_free_arg |
void | 在消息释放过程中调用的函数 |
| lpm_on_session_msg_func | struct session *sess int topic_id const void *msg void *sess_ctx void *plugin_env |
void | 触发消息时调用 |
代码块
| 函数名称 | 参数 | 返回值 | 功能 |
|---|---|---|---|
| lua_chunk_execute | lua_State *state int ref_id int pcount struct lua_cdata *params int rcount struct lua_cdata *returns |
int | 调用并执行一个Lua代码块,该代码段需要提前生成一个引用ID,通过该引用ID进行执行 |
Lua注册函数
| 函数名称 | 参数 | 返回值 | 功能 |
|---|---|---|---|
| lua_plugin_manage_session_regist | lua_State * state | 0无需处理返回值,1需处理返回值 | lua中调用该函数注册插件,注册完成后调用方式为调用plugin_manage.register |
| lua_session_get_type | lua_State * state | 1有返回值 | 获取sessiontype |
| lua_mq_create_topic | lua_State *state | 1有返回值,0处理失败或无返回值 | 创建一个新的topic |
| lua_mq_get_topic_id | lua_State *state | 1有返回值,0处理失败或无返回值 | 根据topic名称获取某个topic的编号 |
| lua_mq_update_topic | lua_State *state | 1有返回值,0处理失败或无返回值 | 更新一个topic,更新其释放函数及释放私有数据 |
| lua_mq_destory_topic | lua_State *state | 1有返回值,0处理失败或无返回值 | 销毁一个topic |
| lua_mq_subscribe_topic | lua_State *state | 1有返回值,0处理失败或无返回值 | 订阅一个topic,根据plugin编号订阅 |
| lua_mq_topic_is_active | lua_State *state | 1有返回值,0处理失败或无返回值 | 判断topic当前的活跃状态 |
| lua_mq_publish_message | lua_State *state | 1有返回值,0处理失败或无返回值 | 在topic中发布一条消息 |
| lua_mq_ignore_message | lua_State *state | 1有返回值,0处理失败或无返回值 | 在会话中忽略一个消息 |
| lua_mq_unignore_message | lua_State *state | 1有返回值,0处理失败或无返回值 | 在会话中取消忽略一个消息 |
总体设计
初始化
---
title: lua_plugin_manage_init
---
flowchart TD;
start(["开始"]);
createschema["calloc新的schema"];
initmessagemq["初始化消息队列"];
getthreadnum["获取线程数量"];
createstate["根据线程数量创建对应状态机"];
getmodelnum["获取需要加载的模块数量"];
createmodel["根据模块数量预分配内存"];
finishload{"所有状态机完成初始化"};
initstate["初始化状态机"];
finishstate{"状态机完成所有模块加载"};
loadspecific["状态机加载一个模块"];
callload["调用该模块加载函数"];
finish(["结束"]);
start --> createschema
createschema --> initmessagemq
initmessagemq --> getthreadnum
getthreadnum --> createstate
createstate --> getmodelnum
getmodelnum --> createmodel
createmodel --> finishload
finishload --> |N|initstate
finishload --> |Y|finish
initstate --> finishstate
finishstate --> |N|loadspecific
finishstate --> |Y|finishload
loadspecific --> callload
callload --> finishstate
- 创建一个新的schema实例
- 将配置文件中配置信息加载至schema实例中暂存
- 在schema中按照线程数量创建状态机,并完成初始化
退出
---
title: lua_plugin_manage_exit
---
flowchart TD;
start(["开始"]);
freestate["依次释放所有状态机"];
freemodel["释放所有模块"];
freemessage["释放所有消息队列"];
finish(["结束"]);
start --> freestate
freestate --> freemodel
freemodel --> freemessage
freemessage --> finish
功能模块
各功能结构之间关系
---
title: 各结构体之间依赖关系
---
erDiagram
lua_plugin_manage_schema {
stellar st
int state_count
lua_State[] state
int model_count
lua_model[] model
int mq_count
array message_mq_array
}
函数及全局变量注册
- 如果传入参数中包含space_name,则将函数在Lua中注册为space_name.func_name形式
- 如果参数中没有space_name,则将函数在Lua中注册为func_name形式
- 删除过程中只是将该函数的指针在Lua中修改为nil,并不会真正删除,需要等Lua下一次自动垃圾回收完成后才会真正完成删除
- 数据管理与函数管理流程相同,不同之处在于函数删除时会检查Lua中数据类型是否为function,但是在全局变量删除时不会做该校验
数据结构
/* 需要注册至lua中的函数 */
struct lua_binding_function
{
/* 注册函数原型 */
lua_CFunction binding_function;
/* 注册至lua中的函数名称 */
char *binding_function_name;
/* 注册至lua中的命名空间名称 */
char *binding_function_space_name;
};
/* 需要注册至lua状态机中的数据 */
struct lua_binding_data
{
/* 注册的数据类型 */
enum DATATYPE binding_data_type;
/* 注册数数据值 */
char *binding_data_value;
/* 注册的数据名称 */
char *binding_data_name;
/* 注册至lua中的命名空间名称 */
char *binding_data_space_name;
};
函数
| 函数名称 | 参数 | 返回值 | 功能 |
|---|---|---|---|
| lua_cbinding_function | lua_State *state lua_CFunction bind_function const char *function_name const char *space_name |
0表示成功,其他表示失败 | 向lua状态机中注册一个函数 |
| lua_cbinding_function_remove | lua_State *state const char *function_name const char *space_name |
0表示成功,其他表示失败 | 从状态机中移除一个已经完成注册的函数 |
| lua_cbinding_data | lua_State *state struct lua_binding_data *data |
0表示成功,其他表示失败 | 向lua状态机中注册一段数据,该数据在状态机中作为全局变量存在 |
| lua_cbinding_data_remove | lua_State *state const char *data_name const char *space_name |
0表示成功,其他表示失败 | 从状态机中删除一个已经注册的数据,从状态机中删除该全局变量的引用 |
---
title: 函数注册 lua_cbinding_function
---
flowchart TD;
start(["开始"]);
paramcheck{"参数检查"};
checkspace{"参数中是否有space_name"};
getspace["状态机中获取space_name"];
spacecheck{"space_name是否存在"};
spacetablecheck{"space_name类型检查,是否为table类型"};
createspace["创建space_name"];
funccheck{"func_name是否存在"};
createfunc["注册函数"];
addfunc["func_name加入管理列表中"]
finish(["结束"]);
start --> paramcheck
paramcheck --> |Y|checkspace
paramcheck --> |N|finish
checkspace --> |Y|getspace
checkspace --> |N|funccheck
getspace --> spacecheck
spacecheck --> |Y|spacetablecheck
spacecheck --> |N|createspace
createspace --> funccheck
spacetablecheck --> |N|finish
spacetablecheck --> |Y|funccheck
funccheck --> |N|createfunc
funccheck --> |Y|finish
createfunc --> addfunc
addfunc --> finish
---
title: 函数卸载 lua_cbinding_function_remove
---
flowchart TD;
start(["开始"]);
paramcheck{"参数检查"};
funccheck{"func_name是否在管理列表中"}
checkspace{"参数中是否有space_name"};
spacecount{"space_name中是否仅有一个元素"}
spacedelete["删除space_name"]
funcdelete["删除func_name"]
functype{"类型是否为LUA_TFUNCTION"}
deletefunc["管理列表中删除func_name"]
finish(["结束"]);
start --> paramcheck
paramcheck --> |Y|funccheck
paramcheck --> |N|finish
funccheck --> |N|finish
funccheck --> |Y|checkspace
checkspace --> |Y|spacecount
checkspace --> |N|funcdelete
spacecount --> |Y|spacedelete
spacedelete --> funcdelete
spacecount --> |N|funcdelete
funcdelete --> functype
functype --> |Y|deletefunc
functype --> |N|finish
deletefunc --> finish
---
title: 数据注册 lua_cbinding_data
---
flowchart TD;
start(["开始"]);
paramcheck{"参数检查"};
checkspace{"参数中是否有space_name"};
getspace["状态机中获取space_name"];
spacecheck{"space_name是否存在"};
spacetablecheck{"space_name类型检查,是否为table类型"};
createspace["创建space_name"];
datacheck{"data_name是否存在"};
createdata["注册数据"];
adddata["data_name加入管理列表中"]
finish(["结束"]);
start --> paramcheck
paramcheck --> |Y|checkspace
paramcheck --> |N|finish
checkspace --> |Y|getspace
checkspace --> |N|funccheck
getspace --> spacecheck
spacecheck --> |Y|spacetablecheck
spacecheck --> |N|createspace
createspace --> funccheck
spacetablecheck --> |N|finish
spacetablecheck --> |Y|datacheck
datacheck --> |N|createdata
datacheck --> |Y|finish
createdata --> adddata
adddata --> finish
---
title: 数据卸载 lua_cbinding_data_remove
---
flowchart TD;
start(["开始"]);
paramcheck{"参数检查"};
datacheck{"data_name是否在管理列表中"}
checkspace{"参数中是否有space_name"};
spacecount{"space_name中是否仅有一个元素"}
spacedelete["删除space_name"]
datadelete["删除data_name"]
deletedata["管理列表中删除data_name"]
finish(["结束"]);
start --> paramcheck
paramcheck --> |Y|datacheck
paramcheck --> |N|finish
datacheck --> |N|finish
datacheck --> |Y|checkspace
checkspace --> |Y|spacecount
checkspace --> |N|datadelete
spacecount --> |Y|spacedelete
spacedelete --> datadelete
spacecount --> |N|datadelete
datadelete --> deletedata
deletedata --> finish
Lua与C数据转换
- 根据不同的数据类型调用不同的Lua原生函数进行出入栈操作;
- context本质是一个预分配并完成引用关联的lua-table,在lua状态机中可以不受限制的使用;
- 后续可能会根据数据类型进行lua和C的内存转换,当前不支持内存共享使用;
- lua元素在出栈过程中,table类型的变量只创建引用并返回引用编号;
数据结构
struct lua_cdata
{
enum DATATYPE cdata_type;
union
{
int cdata_bool;
int cdata_int;
double cdata_num;
char *cdata_string;
/* table暂时只有plugin_env场景下使用, 暂时使用索引进行操作 */
int cdata_table;
void *cdata_pointer;
struct lua_context *cdata_context;
};
};
struct lua_context
{
lua_State *context_state;
int context_ref_id;
};
状态机管理
- 每个线程中创建一个状态机,将该线程中加载的所有插件全部注册在该状态机中;
函数
| 函数名称 | 参数 | 返回值 | 功能 |
|---|---|---|---|
| thread_state_init | int thread_id | 创建成功的状态机指针 | 创建一个状态机,并完成基础信息初始化 |
---
title: thread_state_init
---
flowchart TD;
start(["开始"]);
paramcheck{"参数检查"};
newstate["创建状态机"];
bindfunc["绑定函数"];
binddata["绑定全局变量"];
setthread["设置线程ID全局变量"];
finish(["结束"]);
start --> paramcheck
paramcheck --> |Y|newstate
praamcheck --> |N|finish
newstate --> bindfunc
bindfunc --> binddata
binddata --> setthread
setthread --> finish
插件管理
整体原则:每一个Lua插件在stellar的插件管理器中为一个单独的插件,插件在C插件管理器中共用同一个函数,在调用lua插件函数时根据插件的编号及注册使用的plugin_env查找插件的实际lua函数并完成调用;
数据结构
/* 保存Lua插件信息 */
struct lua_plugin
{
/* 注册完成后得到的插件ID */
int plugin_id;
/* context_new函数在状态机中的引用值 */
int ctx_new_ref;
/* context_free函数在状态机中的引用值 */
int ctx_free_ref;
/* 该插件中订阅的topic */
UT_array *sub_topic_array;
};
模块管理
数据结构
代码块管理
函数
| 函数名称 | 参数 | 返回值 | 功能 |
|---|---|---|---|
| lua_chunk_execute | lua_State *state int ref_id int pcount struct lua_cdata *params int rcount struct lua_cdata *returns |
0运行成功,其他表示运行失败 | 调用一个lua代码块 |
---
title: lua_chunk_execute
---
flowchart TD;
start(["开始"]);
paramcheck{"参数检查"};
getref["根据refid加载函数"];
pushparam["传入参数"];
execute["调用函数"];
popreturn["取出参数"];
finish(["结束"]);
start --> paramcheck
paramcheck --> |Y|getref
paramcheck --> |N|finish
getref --> pushparam
pushparam --> execute
execute --> popreturn
popreturn --> finish
配置管理
数据结构
/* 根据配置文件加载过程中保存插件信息的临时结构 */
struct lua_config_specific
{
/* 插件需要使用的文件名 */
char *config_specific_file;
/* 插件名称 */
// char *config_specific_name;
/* 加载插件需要调用的函数名称 */
char *config_specific_load_func;
/* 卸载插件需要调用的函数名称 */
char *config_specific_unload_func;
};
C注册函数
| 函数名称 | 参数 | 返回值 | 功能 |
|---|---|---|---|
| lpm_ctx_new_func | struct session *sess void *plugin_env |
在该session中创建的context实例 | session创建时调用 |
| lpm_ctx_free_func | struct session *sess void *sess_ctx void *plugin_env |
void | session销毁时调用 |
---
title: lpm_ctx_new_func
---
flowchart TD;
start(["开始"]);
paramcheck{"参数检查"};
getpluginid["获取pluginid"];
searchid["在pluginenv中查找该pluginid"];
idcheck{"找到该id对应的插件"};
newcontext["创建新的context"];
execute["调用该插件实际的lua函数"];
finish(["结束"]);
start --> paramcheck
paramcheck --> |Y|getpluginid
paramcheck --> |N|finish
getpluginid --> searchid
searchid --> idcheck
idcheck --> |Y|newcontext
idcheck --> |N|finish
newcontext --> execute
execute --> finish
---
title: lpm_ctx_free_func
---
flowchart TD;
start(["开始"]);
paramcheck{"参数检查"};
getpluginid["获取pluginid"];
searchid["在pluginenv中查找该pluginid"];
idcheck{"找到该id对应的插件"};
execute["调用该插件实际的lua函数"];
finish(["结束"]);
start --> paramcheck
paramcheck --> |Y|getpluginid
paramcheck --> |N|finish
getpluginid --> searchid
searchid --> idcheck
idcheck --> |Y|execute
idcheck --> |N|finish
execute --> finish
Lua注册函数
| 函数名称 | 参数 | 返回值 | 功能 |
|---|---|---|---|
| lua_plugin_manage_session_regist | lua_State * state | 0无需处理返回值,1需处理返回值 | lua中调用该函数注册插件,注册完成后调用方式为调用plugin_manage.register |
---
title: lua_plugin_manage_session_regist
---
flowchart TD;
start(["开始"]);
paramcheck{"参数数量检查"};
paramtypecheck{"参数类型检查"};
getpluginenv["获取plugin_env"];
getfreeref["将ctx_free函数创建引用"];
getnewref["将ctx_new函数创建引用"];
getstellar["获取stellar"];
regist["调用stellar_session_plugin_register"];
insertplugin["将插件id与函数对应关系插入plugin_env"];
returnid["返回pluginid"];
finish(["结束"]);
start --> paramcheck
paramcheck --> |Y|paramtypecheck
paramcheck --> |N|finish
paramtypecheck --> |Y|getpluginenv
paramtypecheck --> |N|finish
getpluginenv --> getfreeref
getfreeref --> getnewref
getnewref --> getstellar
getstellar --> regist
regist --> insertplugin
insertplugin --> returnid
returnid --> finish
安全性
重名的情况
在Lua中并不会校验是否存在重名变量、重名函数等,出现重名的变量或函数会直接将旧的数据覆盖。
自定义数据冲突
自定义数据如果出现重名的情况,以先注册的为准,后续的函数或数据在注册中会报错
插件数据冲突
插件中的函数在初始化过程中会生成引用ID,在Lua中被强引用的数据即使出现重名情况也不会被删除覆盖,调用过程中根据引用ID进行调用,函数重名不会产生影响
插件中的全局变量在初始化过程中可以通过提供给Lua的接口将全局变量注册成为一个状态机中的全局变量,该全局变量与自定义数据全局变量的维护及保存逻辑一致,后续注册插件过程中如果出现与当前全局变量冲突的数据会导致插件注册失败
自定义数据与插件数据冲突
自定义数据优先级高于插件数据,插件数据注册过程中如果与已存在的数据冲突会注册失败
使用限制
- 在加载的Lua插件中尽量不使用全局变量,否则可能出现由于加载顺序问题导致全局变量被覆盖,使结果不符合预期的情况;可以将需要全局保存的数据注册为状态机中的全局变量,或将需要全局保存的数据写入env变量中解决此问题。