对可抢占和非抢占脚本引擎的思考

 

一、脚本语言的可抢占能力

根据脚本引擎核心的抢占能力,脚本引擎大概可分三类:

1. 第一类脚本,它的语句是细粒度的,而且如果它没有完成所有命令,就不能被宿主抢占(脚本不能被打断)。

例如Python,它提供的PyRun_SimpleString是不可抢占(除非它自己退出,或在脚本运行于一个后台线程内)

    2. 第二类脚本,它实际上是宏命令集合(相当于描述或配置文件)。它的执行过程可以被宿主打断。

    例如XML,它的执行(实际上是解析)速度很快。脚本既可以一次性地向宿主传递数据就马上退出,也可以以单步的形式在脚本解析和宿主执行之间轮流切换。

    3. 第三类脚本,它们的虚拟机核心部分是可抢占的,即脚本自身可以暂停或挂起自己,通过退出虚拟机把控制权交还宿主。等待宿主完成某些事情后重新恢复脚本的执行。

    例如Lua,允许使用一种叫“协程”的机制,并且提供一个API叫lua_resume,使Lua和宿主的执行流能有尽可能多的机会交错切换(详细可以参考这个链接(日文):http://marupeke296.com/LUA_No3_Coroutine.html)。由于可抢占的优势,Lua可以轻松处理事件驱动的系统。只要它有机会挂起自己,就能避免“程序无响应”的情况(前提是脚本执行的时间粒度足够小)。

 

二、如何让非抢占脚本引擎与消息循环共存

如上所述,脚本的可抢占能力与实时事件的处理能力是相关联的。如果脚本不需要花太多的时间(不处理鼠标事件),那么能否被抢占是无需考虑的。但万一脚本需要花很多时间(需要处理鼠标事件),而且脚本不可抢占,那么程序就可能困死在脚本中,没有时间处理窗口事件。

问题是:如果选择的脚本语言是单线程(同一时间只能执行一段脚本)且不可抢占(阻塞的),如何让它和实时系统(窗口系统)共存。

我认为解决方法有以下几种:

1. 多线程

由于脚本的执行是同步的,与异步的窗口事件处理不兼容,可以显式地创建一个后台线程,让脚本的执行独立于窗口系统,通过线程间的共享内存进行通信。需要考虑多线程的数据竞争问题。

2. 在脚本内处理窗口系统的消息循环

由于Windows的窗口处理实际上是多线程的,可以让主线程的消息循环在脚本内执行,使窗口事件的处理不被阻塞。虽然这种方法没有显式创建线程,但原理实际上和方法1相同。

3. 与可抢占的脚本语言混合使用

使用多于一种脚本引擎来操纵程序。需要考虑不同脚本状态机之间的数据共享问题。

4. 把一个脚本分拆成多个脚本

让窗口事件处理(如鼠标事件)的逻辑单独放在一个脚本文件。不过如果脚本状态机不是全局的,还需要留意状态数据的共享问题。

我觉得第2种办法是最优雅的,因为它不需要太复杂的机制。不过这么做的话,脚本既要处理逻辑,还要处理底层。

 

三、简单地在Python脚本中嵌入Win32消息循环

我尝试用第2种解决方法在Win32窗口程序内嵌入Python 2.2.2脚本(模仿一个日本游戏引擎KAVG的做法)。

大概写法如下(注意,这里忽略脚本的错误信息输出和窗口输入处理,而且用PyRun_SimpleString不太好,仅供参考)

 

脚本引擎部分:

 

 

#include <Python.h>
#include <windows.h>
#include "script.h"
#include "mainframe.h"

static PyObject *trace(PyObject *self, PyObject *args)
{
    char* input;
    if (!PyArg_ParseTuple(args, "s", &input))
	{
		return NULL;
	}
	OutputDebugString(input);
	OutputDebugString("\n");
	return PyInt_FromLong(0);
}

static PyObject *foo(PyObject *self, PyObject *args)
{
	return PyInt_FromLong(42L);
}

static PyObject *peekMsg(PyObject *self, PyObject *args)
{
	return PyInt_FromLong(MainFrameMainLoop());
}

void PyInit_SAVG(void)
{
	PyObject *m;
	static PyMethodDef SAVG_methods[] = {
		//{"foo", (PyCFunction)foo, METH_NOARGS, "Return the meaning of everything."},
		{"trace", (PyCFunction)trace, METH_VARARGS, "Output debug info for debugging."},
		{"peekMsg", (PyCFunction)peekMsg, METH_NOARGS, "Window message loop"},
		{NULL, NULL}
	};
	PyImport_AddModule("SAVG");
	m = Py_InitModule("SAVG", SAVG_methods);
	PyModule_AddStringConstant(m, "SAVG_VERSION", SAVG_VERSION);
}

static int script_init(void)
{
	int ret;
	ret = PyRun_SimpleString(
		"import SAVG\n"
		"SAVG.trace(\"Script environment is initializing...\")\n"
		"SAVG.trace(\"SAVG_VERSION is %s\" % SAVG.SAVG_VERSION)\n"
	);
	if(ret == -1)
	{
		OutputDebugString("error on script_init\n");
		return 0;
	}
	return 1;
}

static void script_main(void)
{
	int ret;
	ret = PyRun_SimpleString(
		"while 1:\n"
		"	if SAVG.peekMsg():\n"
		"		break\n"
	);
	if(ret == -1)
	{
		OutputDebugString("error on script_main\n");
	}
}

//NOTE:This function is in WinMain (Main Thread)
int runScript(void)
{
	Py_SetProgramName("SimpleScriptSystem");
	Py_Initialize();
	PyInit_SAVG();
	if(script_init())
	{
		script_main();
	}
	Py_Finalize();
	return 0;
}

 

 

主窗口(部分代码):

 

 

int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
	UNREFERENCED_PARAMETER(hPrevInstance);
	UNREFERENCED_PARAMETER(lpCmdLine);
	MyRegisterClass(hInstance);
	if(!InitInstance(hInstance, nCmdShow))
	{
		return FALSE;
	}
	//TODO: Using script engine to dispatch message,
	//or the main window will have no response!!!
	//As follow:
	//	while(1)
	//	{
	//		if(MainFrameMainLoop())
	//			break;
	//	}
	runScript();
	//FIXME:
	//the return value of program should be : 
	//(int) msg.wParam;
	return 0; 
}

int MainFrameMainLoop(void) 
{
	MSG msg;
	if(!GetMessage(&msg, 0, 0, 0))
	{
		return 1;
	}
	if(!TranslateAccelerator(msg.hwnd, NULL, &msg)) 
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	return 0;
}

 

 

原本执行GetMessage的循环被Python脚本接管了,所以即使script_main没有执行完,窗口系统也不会失去响应。

此时Python脚本不能直接处理输入事件,只能在peekMsg之后轮询输入缓冲(上面的代码没有实现此功能)。

 

四、总结

用类似Python的不可抢占脚本语言处理实时事件是可以的,关键是让脚本内的微观操作尽快完成,但不需要让整个脚本尽快完成。在山穷水尽的时候,还可以依靠操作系统的多线程能力。

对于win32消息循环,Python脚本可以通过C扩展来避免使用协程。

 

五、参考资料

1. Lua組み込み編:その3 コルーチンで状態遷移をLuaで制御

http://marupeke296.com/LUA_No3_Coroutine.html

2. Lua下实现抢占式多线程

http://blog.codingnow.com/2011/08/lua_52_multithreaded.html

3. 游戏引擎脚本系统(二)

http://www.blogjava.net/tianlinux/archive/2007/06/01/121434.html

 

 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值