Lua - 重置脚本状态而无需重新解析

5
我有一个运行Lua脚本的应用程序。每个Lua脚本可能会运行多次。有些脚本甚至可能在按下键时每次都运行。
我希望这些脚本在每次运行之间被“重置”。也就是说,如果用户设置了变量Foo,则下一次运行时该脚本中不应存在Foo,直到用户再次定义它。
问题在于,如果我想要这样的行为,我需要每次创建一个新的lua_State,然后每次打开库,并解析脚本文件,这似乎非常不优化。
加载库可能是相当轻量级的操作(我假设),但解析脚本可能并非如此。
有没有一种方法可以重置Lua脚本的状态(即清除用户代码定义的变量),而无需创建新的lua_State并重新解析整个Lua脚本文件? 我只想让脚本文件在应用程序启动时解析一次,因为它们在运行时不会修改。
谢谢。 :)
编辑:我发现了这个主题,但它没有详细说明如何做到这一点:http://lua-users.org/lists/lua-l/2006-01/msg00493.html 编辑:lua_setfenv似乎与此相关。我会深入挖掘一下。
编辑:似乎在LUA 5.2之后就没有lua_setfenv了。由于我正在使用5.3,我必须设置环境(即名为_ENV的隐藏表格,其中存储变量)才能做到这一点,从而重新加载所有内容,这正是我不想做的...

你可以尝试Lua中使用协程实现,也许可以这样做? - warspyking
3个回答

1
上次我调查时的答案很遗憾是不行的。
您还需要记住,Lua 可能会调用库,这些库可能会打开文件、分配内存等等,任何“重置”都需要处理关闭这些文件、释放该内存等等。
作为重置 Lua 状态的替代方法,您可以简单地组织您的代码,使其不需要重置;这显然需要以特定方式编写 Lua 代码。一种方法是要求您的 Lua 代码完全(或几乎完全)包含在函数中,并为每个操作调用一个或多个函数。函数外部的代码(例如)可能会返回由引用组成的 Lua 表,以调用特定入口点;这只会被调用一次。当调用函数时,它会自动清理自己,包括清理任何库分配的项、打开的文件等等。应避免使用全局变量(除非是常量)。我们成功地使用此方法确保仅解析一次 Lua,确定入口点一次,但可以快速调用相对较小的函数而几乎没有开销。
在评论中,您建议在函数块中词法包装 Lua 代码。我认为这比上述方法更不灵活,并具有以下缺点:
  • 你将失去进行“一次性初始化”的机会(例如从磁盘读取固定常量)。

  • 如果用户插入(例如)不匹配的end ... function B(),那么你就会面临不可预测的问题。

  • 你将限制自己每个Lua状态只有一个入口点。

这意味着Lua代码必须以不同的方式编写(实际上是Lua编码人员按所需形式提供代码)。解决这个问题的一种可能方法是使用固定的框架来完成这个任务,并将要调用的代码require为库。我没有尝试过这种方法。


感谢您的帮助。如果某些库正在分配资源,当删除所有用户变量时,这些资源不应该自动释放/垃圾回收吗? 此外,我可以在运行脚本之前简单地手动删除_ENV中的所有值,并执行lua_settop(0)以重置堆栈吗? - Virus721
@Virus721 - 当然,如果程序有效地重置自己(或者不需要重置),那就可以。我们所做的与此非常相似,在全局级别上,它只是返回一个函数指针(如果我没记错的话——很久以前我看过它),而该函数本身应该以清理自身的方式运行。但我不确定是否有一种从程序外部重置任意 Lua 程序的方法。我认为“删除所有用户变量”的方法行不通,因为您可能需要知道删除它们的顺序。 - abligh
那么你是在告诉我,在解析用户脚本之前将其包装到一个函数中(即 script = "function main() " + userCode + " end"),只要在返回之前正确清除任何分配的资源,就不需要重置脚本了,是吗? - Virus721
1
@Virus721 是的,我也这么认为。这就是我们采取的方法,只不过代码已经被包装了。 - abligh
好的,我会首先尝试这个。谢谢你的帮助。如果你将这个解决方法添加到你的答案中,我会接受它。 - Virus721
显示剩余4条评论

1
如果您想确保每次脚本调用时lua_State相同,您还可以尝试以下方法(适用于我的情况):
  • 将自定义内存分配器传递给lua_newstate,该分配器从内存池中分配内存
  • 在解析脚本之后,在第一次“运行”之前,在其他某个内存位置创建内存池的备份
  • 在每次“运行”之后,将内存池从备份恢复到原始位置
请注意,这仅照顾了完全包含在Lua数据结构中的资源,并且不涉及从Lua或Lua库以任何方式引用的“外部”资源(例如文件描述符、userdata等)。
因此,在我的情况下,我还通过替换全局表来限制脚本编写者的能力,只提供被认为对上述用法安全的操作。
使用此方法,重置基本上归结为每次“运行”后的一些 memcpy 调用,因此需要复制用于您的脚本的Lua结构所使用的内存所需的时间。

0

你不能清除lua_State吗?删除所有线程和手动设置的全局变量。你可能需要将用户环境与全局环境分开。


“用户环境”是否与lua_State分离? - Virus721
有一个全局环境,其中包含'table'和'print'等内容。根据实现方式,这也是用户环境。如果用户执行a = 123,则全局环境中会有一个名为_a_的字段。您可以在C端(我不知道如何)或Lua端将其分离,使用简单的getfenv/setfenv(或_ENV),创建一个新环境,并将元字段__index指向旧环境。 - EinsteinK

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接