一、脚本语言的可抢占能力
根据脚本引擎核心的抢占能力,脚本引擎大概可分三类:
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
相关推荐
scramble4gpuscramble4gpu是用来在抢占显卡的脚本,主要是在实验室显卡资源紧张且自己亟需使用显卡的情况下,使用该脚本自动抢占一个或多个显卡。建议将scramble4gpu.py更改为train.py;防止被打...依赖numpytorch ...
世界杯程序,全部源码均已进行严格测试,可以直接运行!
Melang是单线程中抢先调度协程的脚本语言。 仅在UNIX / Linux上受支持。 Melang已经支持MySQL 8.0,但是最新MySQL C客户端库是不稳定的。 因此,如果尝试连接无法访问的地址,即使程序可能不会崩溃,也会发生缓冲区...
该脚本是由shell语言编写完成,主要用于实现一键部署graylog-sidecar组件,其中包含...下载该脚本后可根据自身环境的服务器大小,来动态调整cgroup资源限制,使得该组件能以更优的方式运行。不会发生抢占系统资源现象。
在多维光通信/光处理和非线性/纳米光子器件/新型存储器件/计算与存储高效融合/智能存储等基础前沿研究中取得原创性成果,掌握自主知识产权,突破封锁,抢占先机实现弯道超越。 在能量光电子领域,面向我国能源结构...
4、因为各沙箱对全局环境有读权限,各沙箱可以共享全局环境,避免lua基础库和公共脚本的重复加载。5、在调度的过程中通过lua hook的方式实现任务的抢占式调度。Build && Testcd Cellular && make./testcase
看过这篇文章的朋友,会注意到我是在 Gitlab-Runner服务器上自动部署的站点,本次我们结合ssh部署到远程机器(将CI服务器和部署服务器分离,避免资源抢占)。 SSH免密登陆 还是那句话,CI/CD实质是将我们手动集成、...
与性能相关的工具和脚本的集合 schedtime-更好的类似于time(1)的工具 schedtime执行程序并打印运行时统计信息。 它的工作方式类似于time(1)命令,只是不打印诸如系统和用户时间之类的高级细节,而schedtime则...
3:脚本:价值百万的一分钟脚本思路 part2拍摄篇 4:器材:90%的人不知道的手机隐藏视频功能 5:快进:延时摄影、让普通的视频变大片 6:构图:电影感视频这样拍 7:运镜:案例拆解、分分钟学会高级运镜 8:对标...
MCGS昆仑通态的触摸屏的脚本串口驱动 DIM strTmp as STRING '临时字符变量 DIM strData as STRING ' '定义HEXE协议格式命令收发帧使用变量 dim SendByteArr(0) as byte '存放发送命令字节数组 dim RecByteArr(0...
勇芳_鼠标精灵 因一女孩(亲戚)玩网游的需要,特为其做的,大家有需要的也可用一用。 使用说明: 点“录制”,就记录你用鼠标对...简简单单、不抢占电脑,这是本软件的开发目的,想要强大的功能请找其他的软件。
使用说明: 点“录制”,就记录你用鼠标对窗口的操作 点“回放”,将你录制的鼠标动作重复地对窗口操作 “误差”选项,有些网络游戏...简简单单、不抢占电脑,这是本软件的开发目的,想要强大的功能请找其他的软件
使用说明: 点“录制”,就记录你用鼠标对窗口的操作 点“回放”,将你录制的鼠标动作重复地对窗口操作 “误差”选项,有些网络游戏...简简单单、不抢占电脑,这是本软件的开发目的,想要强大的功能请找其他的软件。
内容操作系统登录可抢占的虚拟机用于从本地爆发的混合集群节点编址配置步骤混合群集中的用户和组多集群/联盟故障排除 Google Cloud Platform中的独立群集提供的脚本可用于在Google Cloud Platform中创建独立集群。...
使用说明:1.打开程序2.输入网页点打开,登陆网站3.按A在游戏上移动鼠标分析出要点的位置4.写成脚本5.点运行1.2版自动记忆软件上次的位置和窗口上次的大小,方便不用改动上次的坐标修正已知错误非程序激活下不抢占热键
使用说明:1.打开程序2.输入网页点打开,登陆网站3.按A在游戏上移动鼠标分析出要点的位置4.写成脚本5.点运行1.2版自动记忆软件上次的位置和窗口上次的大小,方便不用改动上次的坐标修正已知错误非程序激活下不抢占热键
另一个愚蠢的脚本项目过于具体,并非全部可重用。 但是,这不是所有伟大的事物都是如何开始的吗?你如何运行这个东西? :thinking_face: 将sample.env复制/重命名为.env 指定您要分析的GITHUB_OWNER和GITHUB_REPO ...
其中两个实时脚本逐行显示这些函数的步骤结果,第三个“demo.mlx”绘制了给定数据包抢占和替换概率的 AoI 和 PAoI 的累积分布函数,这些概率由滑块控制。 用法: 输入和输出的描述在函数文件中给出。 1) 第一个...
spyder是anaconda自带的一款IDE,... 其中的原因,笔者猜测是spyder多开的一个控制台实际上是多开的一个python进程,并且可能在某些对用户隐藏的控制台层面以某种方式加了进程锁,而控制台进程优先抢占到了进程锁,只
多脚本执行调度,防止多个不同脚本抢占前台 RunningQueueDispatcher 封装了支持多脚本锁的 LockableStorage,阻塞写入并返回写入成功与否,达到锁互斥的目的 封装了基于文本、ID控件正则查找工具 WidgetUtils,支持...