diff --git a/docs/summaryDesign.md b/docs/summaryDesign.md new file mode 100644 index 0000000..9abbe62 --- /dev/null +++ b/docs/summaryDesign.md @@ -0,0 +1,292 @@ +# LuaPluginManage +## 总述 +### 整体功能 +实现Lua插件的管理功能,功能整体包括如下部分: +1. 状态机的管理及维护:功能包括状态机的创建、删除、初始化、获取运行状态等; +2. 注册函数的管理功能:C将函数注册至Lua环境中,并可在Lua中进行调用;提供函数检查功能,为保证调用过程安全可靠,在传入过程中对安全状态进行检查; +3. 数据的管理功能:C将数据注册至Lua环境中,如全局使用的字符串、版本信息、运行环境等; +4. 插件的管理功能:将一个Lua插件加载至插件管理中,与C插件一致,将插件注册至stellar-plugin_manage及session_plugin_manage中; +5. 数据转化:可以将Lua中的数据与C中的数据进行安全的数据转换,用于在C中暂存Lua中保存的数据,并可以对该数据进行修改、维护; +### 状态机 +状态机用于保存Lua运行虚拟机在运行过程中所有的环境表、注册表、堆栈、数据段、上下文等数据。Lua状态机是Lua运行过程中对外暴露的唯一数据结构,其余数据结构均已经在Lua内部隐藏,所有对Lua的操作均需要通过改变状态机的状态来实现。一个Lua状态机可以理解为一个Lua的线程,该状态机和C的线程一样,拥有独立的内存空间。 +创建:安全的新建一个空的状态机 +删除:释放一个状态机,删除其内部所有数据占用的内存并释放一个状态机实例 +初始化:状态机初始化过程中加载运行时必要的Lua组件,并根据配置文件将各个Lua插件加载在状态机中 +获取运行错误*:获取状态机运行过程中的错误 +获取运行状态*:获取状态机运行状态,包括运行时间、加载插件数量、运行耗时等 +### 函数 +向Lua内部注册一个函数,可供Lua程序在运行过程中调用。如向lua中传入数据结构时,可通过注册函数的方式向Lua提供数据的获取方法。或通过注册函数向Lua提供其他模块的外部接口。 +注册:向Lua状态机中注册一个格式要求符合lpm_cbinding_function类型的函数,该函数可供Lua脚本调用 +删除:从Lua状态机中删除一个C注册的函数 +安全检查*:提供一个标准化的定义方式,在Lua插件编写过程中可通过该定义方式得知运行过程中存在的注册函数名称、参数类型等,防止在调用过程中出错;在Lua脚本加载前对其内部调用的C函数进行安全性检查,检查如传入参数个数、参数类型等 +自动化函数翻译*:通过特定方法能够自动将一些C函数转换为Lua的注册函数格式 +### 数据 +向Lua内部注册一个函数,可供Lua脚本在运行过程中使用。如向Lua中传入运行程序版本、运行环境信息、系统信息等。 +注册:向状态机中注册一个全局变量,变量类型可以为数字、字符串;如需注册一个数据结构需同时注册该数据结构的成员获取方法 +删除:从状态机中删除一个全局变量 +数据范式化*:将一段Lua数据范式化为一段json或其他特定格式,方便输出或在其他程序中调用 +### 插件 +插件为一整个Lua功能模块,与C中的插件含义相同。 +插件加载:将一个插件加载至插件管理器中 +插件卸载:从插件管理器中卸载一个插件 +会话插件加载:将一个会话插件加载至session_plugin_manage中 +会话插件卸载:从session_plugin_manage中删除一个会话插件 +注册会话插件:此功能仅在lua端暴露,不在C接口中。在插件加载过程中可注册会话插件 +订阅会话消息:此功能仅在lua端暴露,不在C接口中。在插件加载过程中可订阅一个会话中特定id的会话消息 +处理会话消息:处理会话过程中的数据 +### 数据转换 +将Lua数据翻译为C中可读可写可持久化保存的数据,也可以将一个由Lua转换为C数据得到的序列化数据转换为Lua中的数据结构 +Lua转换至C:将Lua数据序列化成为C中一段内存数据 +C转换至Lua:将C中数据反序列化为Lua中一段数据 +## 总体设计 +### 初始化 +```mermaid +--- +title: lua plugin manage init +--- +flowchart TD; + + start(["开始"]); + create["创建状态机"]; + bindfunc["绑定函数"]; + binddata["绑定数据"]; + init["读取配置文件并获取插件"] + load["加载插件"]; + finish(["结束"]); + + start --> create + create --> bindfunc + bindfunc --> binddata + binddata --> init + init --> load + load --> finish +``` +1. 创建一个状态机实例,后续所有的运行全部依赖于该状态机; +2. 将自定义的C函数绑定至状态机中,功能详见[C函数管理](#函数管理); +3. 将一些自定义的数据注册至状态机中,功能详见[数据管理](#数据管理); +4. 进行初始化,根据一个配置文件,加载文件中声明需要加载的所有插件。此时,插件仅是在将必要的信息暂存在状态机中; +5. 加载插件,依次调用每一个插件中的load函数,功能详见[插件管理](#插件管理); +### 运行 +运行过程中所有调用均通过现在的stellar中的plugin_manage于session_plugin_manage进行调用,运行过程中不会主动运行。 +### 退出 +```mermaid +--- +title: lua plugin manage exit +--- +flowchart TD; + + start(["开始"]); + unload["依次卸载插件"]; + clean["关闭状态机"]; + finish(["结束"]); + + start --> unload + unload --> clean + clean --> finish +``` +1. 依次调用插件管理中每一个插件的unload函数,功能详见[插件管理](#插件管理); +2. 关闭状态机并释放占用的资源; +## 外部接口 +C端函数: +| 函数名称 | 参数 | 返回值 | 功能 | +| --- | --- | --- | --- | +| lpm_state_instance_create | void | 创建完成的状态机指针,NULL为创建失败 | 创建一个状态机实例。 +| lpm_state_instance_init | struct stellar * st
struct lpm_state * state
const char * filename | 0表示成功,其他表示失败 | 根据一个配置文件对一个状态机实例进行初始化 +| lpm_state_instance_free | struct lpm_state * | int | 释放一个状态机实实例,释放其占用内存 +| lpm_cdata_clean | struct lpm_cdata * | void | 清理一个struct lpm_cdata实例内所有数据,删除其内部数据占用的内存 +| lpm_cbinding_get_params_count | struct lpm_state * state | 传入的参数数量,负数为获取失败 | 该函数用于C函数获取lua在调用C函数时传入的参数个数 +| lpm_cbinding_get_params | struct lpm_state * state
int index
struct lpm_cdata * data | 0表示成功,其他表示失败 | 获取传入C中的具体参数内容,index表示参数下标,data为具体参数内容 +| lpm_cbinding_push_return | struct lpm_state * state
int count
struct lpm_cdata * data | 0表示成功,其他表示失败 | 将C函数运行结果传入Lua中,count表示传入的数据数量,data为具体数据 +| lpm_cbinding_function_register | struct lpm_state * state
lpm_cbinding_function function
const char * func_name
const char * space_name | 0表示成功,其他表示失败 | 将一个C函数注册至Lua中,在Lua中调用方式为space_name.func_name的形式,如果space_name为空时可通过func_name进行调用 +| lpm_cbinding_function_remove | struct lpm_state * state
const char * func_name
const char * space_name | 0表示成功,其他表示失败 | 从Lua状态机中删除一个函数 +| lpm_cdata_register | struct lpm_state * state
struct lpm_cdata * data
const char * data_name
const char * space_name | 0表示成功,其他表示失败 | 将一个C数据传入Lua中,并可作为Lua的全局变量使用,在Lua中使用方式为space_name.data_name,当space_name为空时可直接使用data_name获取该变量的值 +| lpm_cdata_remove | struct lpm_state * state
const char * data_name
const char * space_name | 0表示成功,其他表示失败 | 从Lua状态机中删除一个全局变量 +| lpm_plugin_load | struct stellar * st | 返回插件的私有运行数据 | 提供至stellar中plugin manage的插件注册函数 +| lpm_plugin_unload | void * plugin_env | void | 提供至stellar中plugin manage的插件卸载函数 +| lpm_ctx_new_func | struct session * sess
void * plugin_env | 在该session上插件的私有数据 | 提供至stellar中session plugin manage的函数,在会话创建过程中调用,创建该会话中的插件私有数据 +| lpm_ctx_free_func | struct session * sess
void * sess_ctx
void * plugin_env | void | 提供至stellar中session plugin manage的函数,在会话结束时调用,删除在会话上的插件私有数据 +| lpm_on_session_msg_func | struct session * sess
int topic_id
const void * msg
void * sess_ctx
void * plugin_env | void | 会话中的消息处理函数 +| lpm_trans_data_luatoc | struct lpm_state * state
struct lpm_cdata * data | 0表示成功,其他表示失败 | 将状态机中当前栈顶的数据转换为一个C中的cdata结构,并出栈 +| lpm_trans_data_ctolua | struct lpm_state * state
struct lpm_cdata * data | 0表示成功,其他表示失败 | 将一个cdata结构转为一个Lua中的数据,并将该数据置于状态机栈顶 + +Lua端函数 +## 功能模块 +### 函数管理 +1. 如果传入参数中包含space_name,则将函数在Lua中注册为space_name.func_name形式 +2. 如果参数中没有space_name,则将函数在Lua中注册为func_name形式 +3. 删除过程中只是将该函数的指针在Lua中修改为nil,并不会真正删除,需要等Lua下一次自动垃圾回收完成后才会真正完成删除 +```mermaid +--- +title: function register +--- +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 +``` + +```mermaid +--- +title: 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 +``` +### 数据管理 +与函数管理流程相同,不同之处在于函数删除时会检查Lua中数据类型是否为function,但是在全局变量删除时不会做该校验 +```mermaid +--- +title: data register +--- +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 +``` + +```mermaid +--- +title: 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插件在stellar的插件管理器中为一个单独的插件,每一个session_plugin在stellar的会话插件管理器中也为一个单独的插件 +解决方案: +1. 在状态机整体init过程中将所有插件仅暂时保存在一个链表中,但是不加载至stellar的插件管理器中。每一次调用lpm_plugin_load函数时从插件链表中取出一个节点,并调用该节点中保存的load函数。之后在使用过程中,其plugin_env中已经保存了插件的所有信息,可通过plugin_env分辨是哪一个插件并调用该插件中数据; +2. 在状态机init过程中为插件分配一个lua_plugin_manage_id,在所有函数调用过程中增加一个参数,每次传入参数时携带该lua_plugin_manage_id + +对于会话插件的管理,由于一个插件中可能注册多个会话插件,在会话插件管理上有两种不同的处理方案: +1. plugin_env中维护一个该插件的会话插件列表,在调用Lua会话插件时需要同时传入插件ID,在该列表中查找对应的处理函数进行调用; +2. 会话插件注册过程中复制一个完整的plugin_env,每一个会话插件拥有一个完全独立的plugin_env; +### Lua与C数据转换 +## 数据结构 +```C +struct lpm_cdata { + enum LPM_DATATYPE data_type; + int data_length; + union { + int data_bool; + double data_num; + int data_int; + char * data_string; + struct lpm_ctable * data_table; + void * data_user; + void * data_context; + }; +}; +``` +## 可配置项 + + +## 安全设计 +### 重名的情况 +在Lua中并不会校验是否存在重名变量、重名函数等,出现重名的变量或函数会直接将旧的数据覆盖。 + +自定义数据冲突 +自定义数据如果出现重名的情况,以先注册的为准,后续的函数或数据在注册中会报错 + +插件数据冲突 +插件中的函数在初始化过程中会生成引用ID,在Lua中被强引用的数据即使出现重名情况也不会被删除覆盖,调用过程中根据引用ID进行调用,函数重名不会产生影响 +插件中的全局变量在初始化过程中可以通过提供给Lua的接口将全局变量注册成为一个状态机中的全局变量,该全局变量与自定义数据全局变量的维护及保存逻辑一致,后续注册插件过程中如果出现与当前全局变量冲突的数据会导致插件注册失败 + +自定义数据与插件数据冲突 +自定义数据优先级高于插件数据,插件数据注册过程中如果与已存在的数据冲突会注册失败 diff --git a/example/example.c b/example/example.c new file mode 100644 index 0000000..18cb448 --- /dev/null +++ b/example/example.c @@ -0,0 +1,59 @@ +#include "lua_plugin_manage.h" + +#include + +int session_getid(struct lpm_state * state) +{ + if ( lpm_cbinding_get_params_count(state) != 1 ) + return 0; + struct lpm_cdata data; + lpm_cbinding_get_params(state, 1, &data); + struct session * sess = (struct session *)data.data_user; + lpm_cdata_clean(&data); + int session_id = sess->id; + + data.data_type = LPM_DATATYPE_INT; + lpm_cbinding_push_return(state, 1, &data); + lpm_cdata_clean(&data); + return 1; +} + +int binding_functions(struct lpm_state * state) { + int count = 0; + if ( lpm_cbinding_function_register(state, session_getid, "getid", "session") ) { + count += 1; + } + return count; +} + +int binding_data(struct lpm_state * state) { + int count = 0; + const char * version = "v0.1"; + struct lpm_cdata data; + data.data_type = LPM_DATATYPE_CSTRING; + data.data_length = strlen(version); + data.data_string = strdup(version); + if ( lpm_cdata_register(state, &data, "version", NULL) ) { + count += 1; + } + lpm_cdata_clean(&data); + return count; +} + +int main(int argc, char * argv[]) +{ + struct stellar st; + struct lpm_state * state = lpm_state_instance_create(); + binding_functions(state); + binding_data(state); + lpm_state_instance_init(&st, state, "config.toml"); + + + + + + + + lpm_state_instance_free(state); + return 0; +} \ No newline at end of file diff --git a/include/lua_plugin_manage.h b/include/lua_plugin_manage.h new file mode 100644 index 0000000..1de66e0 --- /dev/null +++ b/include/lua_plugin_manage.h @@ -0,0 +1,113 @@ +/************************************************************************* + > File Name: lua_plugin_manage.h + > Author: + > Created Time: 2024-07 + > Encoding : UTF-8 + ************************************************************************/ + +/************************************************************************* + * version + * [ v0.1 ] + * + ************************************************************************/ +#ifndef LUA_PLUGIN_MANAGE_INCLUDE_H +#define LUA_PLUGIN_MANAGE_INCLUDE_H + +#include "stellar.h" + +struct lpm_state; + +enum LPM_DATATYPE +{ + LPM_DATATYPE_NULL = 0, + LPM_DATATYPE_NIL, + LPM_DATATYPE_BOOL, + LPM_DATATYPE_NUM, + LPM_DATATYPE_INT, + LPM_DATATYPE_CSTRING, + LPM_DATATYPE_CTABLE, + LPM_DATATYPE_CUSER, + LPM_DATATYPE_CONTEXT, + LPM_DATATYPE_END +}; + +struct lpm_cdata; +struct lpm_ctable; + +struct lpm_cdata { + enum LPM_DATATYPE data_type; + int data_length; + union { + int data_bool; + double data_num; + int data_int; + char * data_string; + struct lpm_ctable * data_table; + void * data_user; + void * data_context; + }; +}; + +void lpm_cdata_clean(struct lpm_cdata * data); + +/* 创建一个lua plugin manage状态机实例 */ +/* return lpm_state instance */ +struct lpm_state * lpm_state_instance_create(void); +/* 释放一个lua plugin manage状态机实例 */ +/* 在plugin_manager使用过程中应该将所有已经加载的插件卸载后再调用此函数 */ +/* return 0 - success, other - failed */ +int lpm_state_instance_free(struct lpm_state * state); +/* 根据一个配置文件加载所有配置需要加载的插件 */ +/* return 0 - success, other - failed */ +int lpm_state_instance_init(struct stellar * st, struct lpm_state * state, const char * filename); + +/* 可以注册至lua状态机实例中的C函数原型 */ +/* return 1 - lua调用完成后需要处理返回值, 0 - lua调用完后不需要处理返回值 */ +typedef int (*lpm_cbinding_function)(struct lpm_state *); + +/* 供注册的C函数使用 */ +/* 获取传入的参数数量 */ +/* return 参数数量 */ +int lpm_cbinding_get_params_count(struct lpm_state * state); +/* 获取传入的参数, index为参数的下标, 多个参数只能逐个获取 */ +/* return 0 - success, other - failed */ +int lpm_cbinding_get_params(struct lpm_state * state, int index, struct lpm_cdata * data); +/* 将返回值传入lua, count为返回值数量, data为数据的数组 */ +/* return 0 - success, other - failed */ +int lpm_cbinding_push_return(struct lpm_state * state, int count, struct lpm_cdata * data); + +/* 注册一个C函数function至lua中, 调用方式为space_name.func_name格式, space_name可以为空 */ +/* return 0 - success, other - failed */ +int lpm_cbinding_function_register(struct lpm_state * state, lpm_cbinding_function function, const char * func_name, const char * space_name); +/* 将一个已经注册的function从lua中移除 */ +/* return 0 - success, other - failed */ +int lpm_cbinding_function_remove(struct lpm_state * state, const char * func_name, const char * space_name); + +/* 将一个数据注册至lua中, 作为lua中的全局变量使用 */ +/* return 0 - success, other - failed */ +int lpm_cdata_register(struct lpm_state * state, struct lpm_cdata * data, const char * data_name, const char * space_name); +/* 将一个全局变量从lua中移除 */ +/* return 0 - success, other - failed */ +int lpm_cdata_remove(struct lpm_state * state, const char * data_name, const char * space_name); + +/* +解决方案: +1. 在stellar或状态机中新增一个列表, 调用该函数时每次按顺序从列表中取一个进行调用, 加载完成后每次从列表中删除一个结点 +2. 增加一个参数, 函数原型修改为 +void * lpm_plugin_load(struct stellar * st, int id) +3. 所有的插件与C不同, 在init函数执行过程中完成session_plugin的注册, 插件不再注册至plugin manage中 +*/ +void * lpm_plugin_load(struct stellar * st); +void lpm_plugin_unload(void * plugin_env); + +void * lpm_ctx_new_func(struct session * sess, void * plugin_env); +void lpm_ctx_free_func(struct session * sess, void * sess_ctx, void * plugin_env); + +void lpm_on_session_msg_func(struct session * sess, int topic_id, const void * msg, void * sess_ctx, void * plugin_env); + +/* 将lua栈顶的一个数据转为一个lpm_cdata结构的数据 */ +int lpm_trans_data_luatoc(struct lpm_state * state, struct lpm_cdata * data); +/* 将一个lpm_cdata结构的数据转为一个lua数据并入栈至栈顶 */ +int lpm_trans_data_ctolua(struct lpm_state * state, struct lpm_cdata * data); + +#endif \ No newline at end of file