雪辉 发表于 2020-9-27 14:36

罗技Lua语言脚本,发一个"多线程"框架

本帖最后由 雪辉 于 2020-9-27 14:40 编辑

众所周知,罗技宏在业务逻辑上是单线程的,对于部分宏的开发,主要体现在以下两个问题上:
      1.宏开始后无法干预
                按下g键后,宏开始运行,按下第二次g键时,若宏没有停止,则第二次g按键会被挂起,直到前一次宏退出时,才会执行此次g按键。这就导致了,在宏运行的过程中,不能直接的使用g按键对其进行干涉,做不到宏的'随开随停';
      2.逻辑流错综复杂
                举个例子,对于奶僧来说,进本后需要一直保持真言和黑人的释放,而在定点后需要打定点的宏,此时的两宏并发,在逻辑上非常简单, 但在实现上就比较麻烦了,两段代码糅杂在一起,非常混乱。这就是两条逻辑流交错时,逻辑流(双线)与开发流(单线)不一致,导致代码变得复杂冗余、高耦合。
那么有什么解决办法么?
以上两点都是单线程的弊病,那么自然可以想到,把程序变成多线程不就行了嘛~
可惜,罗技是不支持多线程的,多线程是基于cpu不断的切换上下文,而达到宏观上的线程并发,不是发生在代码层面的,所以无法通过代码去实现真正的多线程,这时候就需要曲线救国一下了
先提两个知识点:
1.轮询:
      什么是轮询呢,顾名思义就是循环着去询问消息队列,是否有事情做。前文提到过,在用户按下第二次g键后,需要等待前一次宏的运行完毕才能接着运行。轮询的处理则是将一次宏的时间分割成多段,每次进来只运行一个较短的轮询时间就退出,这样用户的命令就可以通过轮询的间隙发出来。
2.协程:
      协程运作不同于多线程,他是发生在代码层面的(也就是说即使lua不支持协程运作,也可以通过代码去实现),协程中记录一条完整的逻辑流,协程之间的切换就行互调方法一样简单、高效,宏开发者只需要将不同的逻辑流写到不同的协程中去,则只需要关心‘在什么时候切换’,而不需要关心‘怎么切换’的问题了。

说完两个知识点,下面就要说说所谓的‘多线程’实现了
其实运用轮询和协程实现并发的宏框架目前是有的,不过暗黑的宏作者很少有人用,目前此框架拥有这以下两个问题,导致其只能实现简单的连 点器功能,并不普适。
1.在协程中写Sleep函数
      在协程中写Sleep函数并不会让协程放弃系统资源,Sleep函数依旧会让整个程序停下来等他,这就使得在轮询的低时间高频率的背景下,不能做循环节较长的宏,多协程并发时,时间不精准。
2.每次轮询都遍历一遍待办任务表
      这个是很消耗资源的事情,直接导致了轮询时间不能设置的太短,宏的灵敏性不够,其实并不需要去遍历整个消息队列,只需要找到时间最接近的任务去执行就行了。

那么这两个问题又怎么去解决呢
1.将Sleep函数完全抽离到主线程里来,所有的逻辑流交于协程去运作,协程运作到sleep函数时将其挂起,并向消息队列中插入一则(当前时间+需sleep时间)的任务,等待时间到时将其唤醒继续运行。
2.目前消息队列的数据模型只是lua原生的表,我在框架中重新构建了一个插排队列的数据模型,任务插入时排序,每次轮询时从队列首端(时间最近)取值。


引入框架
1.下载压缩包解压到本地
https://ok.166.net/forum/d3/forum/202003/21/004915voewywppyxqy763e.jpg.thumb.jpg
2.打开罗技脚本编辑器,在第一行引入框架
dofile('刚刚下载的压缩包中lua文件的绝对路径')
https://ok.166.net/forum/d3/forum/202003/21/005202m3lllaaanacqqlba.png.thumb.jpg
注意此处的斜杠要使用正斜杠(/)
至此,框架已经引入完毕,接下来就可以正常使用了








https://ok.166.net/forum/d3/forum/202003/24/205226j41bsxqn85zn95g8.png.thumb.jpg
如图,实现了按下鼠标左键运行v_run1宏,输出系统运行时间。抬起鼠标左键停止输出。
1.EnablePrimaryMouseButtonEvents(true)函数,开启鼠标左键的编程,部分系统不能使用。
2.main函数多了两个参数
第一个参数控制抬起或按下(MOUSE_BUTTON_PRESSED/MOUSE_BUTTON_RELEASED),默认值为按下,可直接填写nil。
第二个参数控制开启或停止(start/end)默认值为start_end(若开即关,若关即开)


https://ok.166.net/forum/d3/forum/202003/21/151551gjxzzvthhjlvh8x6.jpg.thumb.jpg
如图,实现了单击g4键位,循环输出1-10。
因为并非循环运行的宏,则不适用于queue_ctrl控制器,这里使用了run控制器
main函数的第一个参数直接传递run,第2、3个参数依旧是绑定键位、按下/抬起
第四个参数因为此宏不涉及停止,则直接传递需要运行函数名即可。
再举一例:
https://ok.166.net/forum/d3/forum/202003/21/151559w0qwqkh0u8tvmskh.jpg.thumb.jpg
如图,添加了一个终止所有正在运行的宏的方法。
list:empty()清空任务队列里等待执行的所有任务。
则实现点击g5键,强制终止所有正在运行的宏。

目前list:empty方法无法弹起使用press方法按下的键位,框架中集成了all_stop方法,可以删除队列的同时弹起press方法按下的键位.
all_stop方法允许传入一个参数,为线程对象,可定向关闭对应线程的序列

另外,在宏里也可以使用run函数直接执行其他宏
https://ok.166.net/forum/d3/forum/202003/21/154508cxh8gbxxag7gu0hp.jpg.thumb.jpg
如图,在v_run1中用run函数调用v_run2方法。
那么这样和直接调用v_run2有什么区别呢?
直接调用,宏会先去执行v_run2结束后再回来执行v_run1
而使用run函数调用,v_run1和v_run2是同步进行的(如图,同时输出1-5)




下面介绍一个框架中比较基础也比较核心的控制器——自旋器。
自旋器,顾名思义指反复的去做一件相同的事。宏本身就是为了替代人进行一些机械性的操作而存在的,而自旋器则是执行这些简单而机械的反复操作的控制器
1.此控制器是一个单独线程,所以无需担心和正常的宏出现逻辑上的冲突。
2.可以实时的获知此控制器的核心参数,此核心参数可以自定义 (比如一个计时用的自旋器,可以实时获得所记时间)
首先是自旋器所要运行的函数https://ok.166.net/forum/d3/forum/202003/28/231526ty5yk7k10huyk1wm.jpg.thumb.jpg
如图,执行函数实现的是按一下q键,此函数和以往的执行函数不同在于有参数和返回值。
此返回值由玩家定义,返回一个控制器的核心参数。我这里要记录一共执行了几次q按键,故返回次数。
而函数的参数则是上一次运行的返回值,这样就完成了一个每次进入后次数+1的实现。
参数和返回值可以不填
https://ok.166.net/forum/d3/forum/202003/28/231527e533r2vgggr3vo11.jpg.thumb.jpg
spin函数有四个参数(要重复执行的函数,重复执行的时间间隔,传入的参数初始值,自旋中止时执行的函数)
会返回一个控制器
https://ok.166.net/forum/d3/forum/202003/28/231527ukal8vfa1b6lzc6i.jpg.thumb.jpg
将控制器的开启和中止分别绑定到g4和g5上。
如此,则用自旋控制器实现了一个简单的连点 器的作用。
注意,自旋函数中尽量不要使用sleep函数。
因为每一次执行的自旋函数是一个单独的不可分割的元 操作,即每一次进入函数后必须执行完才能被终止,里面加入了sleep函数后,可能会导致宏无法立即停止。

自旋器由于其独立线程,可以在后台静默开启执行一些插入式的操作。
例如:

https://ok.166.net/forum/d3/forum/202003/28/231528p70dh0zdz5uuwiet.jpg.thumb.jpg
在刚刚的基础上,添加了一个监听器,监听刚刚的test_spin自旋控制器,若其执行到5次,则中断。
这种保持原函数独立性的同时,用外界的新函数去干预其原本逻辑的行为称为插入式。就像一个插件,需要时插上去,不需要可以关掉,不影响原本的逻辑流。
如图按下了5次q之后被监听器中断。
1.test_spin('status')返回该自旋器的运行状态(true/false)
2.test_spin('get')获取该自旋器的核心参数,图中为次数。
3.A and B or C格式为lua中的三目运算符,指若A为真,则返回B否则返回C(有一些小瑕疵)
注意,test_spin('end')仅仅是中断自旋器,再重新调用后次数会从6开始继续计数。
那么,如果想每次点击g4后自旋器重新计数怎么办呢?
https://ok.166.net/forum/d3/forum/202003/28/231528bcg0sg88gcfzgww9.jpg.thumb.jpg
写一个重置的函数绑定到g4即可。
如图,输出5次q被终止后,再次开启,重新计数到5次后被终止。

最后再给一个实例,是我经常用到的自旋器实现

https://ok.166.net/forum/d3/forum/202003/28/231528im0pk1a2g116f6ne.jpg.thumb.jpg
此自旋器实现了一个后台静默执行的元素戒轮转。
开启自旋后,无论在宏的任何地方调用 ys_spin("get") 方法都可以直接获取到元素戒信息,无需再花费过多的逻辑去根据运行时间计算。




下面所介绍的控制器是这个框架中最重要的两个控制器之一,多线程连 点器。这个控制器运用了我比较喜欢的数组式编程,让逻辑代码完全抽离,代码本身十分干净,可以说很多不懂编程的小白都能看懂,甚至模仿写出自己的宏。
所谓连 点器是指以固定的频率去重复点击某些按键。市面上大多数罗技宏因为本身单线程,会导致宏的不精准,而为了保证宏的精准性,常常会将延迟进行最小公倍数计算,比如某些跑马天谴宏,点一下跑马要录十几个天谴等等。而本框架的特色就是多线程,让每个键位之间保持独立,互不干扰,多条逻辑流并发,保证了连 点器的精准。
https://ok.166.net/forum/d3/forum/202004/11/135459rjg7roycj85owcxz.png.thumb.jpg
如图,实现了分别1,2,3,4秒连点q,w,e,r。
写法为模仿图中写一个二维数组,第一个参数为要按的键位,第二个参数为间隔
将数组放入clicks方法,生成一个控制器。
然后将控制器绑到按键上,即可完成。
https://ok.166.net/forum/d3/forum/202004/11/135451lu34kbjb3xj3u4u8.png.thumb.jpg
如果想要控制键位按下和抬起的时间,可以在数组的第三个参数中写
如图,我想执行宏时按住强制站立键(shift)就将shift键位的间隔调到一个比较大的值即可。


之前介绍了一下多线程连 点器的基本使用功能,在此对于此控制器进行功能补充,如果各位所写的宏有基本方法无法完成的需求,可以来这一层看一看。
1.拦截器
比如我们要实行组合键的需求,例如ctrl+q
https://ok.166.net/forum/d3/forum/202004/19/012431mxxlaz5x33s3lmax.png.thumb.jpg
拦截器函数写在数组的第四格(前三格为‘所按键位’,‘按键间隔’,‘按下与抬起之间的间隔’)
拦截器函数会在这个键位被按下前触发,如图则在按键前插入一个ctrl按键
拦截器函数如果写返回值且为true,会触发拦截按键,并循环访问拦截器,直到返回false为止。
https://ok.166.net/forum/d3/forum/202004/19/012424tax1xlcrunrbl3v3.jpg.thumb.jpg
如图是一个法师黑人(r)与一技能(q)的例子
逻辑为:48s点一次黑人,黑人持续时间内隔0.6s打次一技能。
这个拦截器函数判断黑人上次点击时间,若不在20s内则拦截10ms(函数的第二个返回值为拦截器轮询间隔,不填则默认为按键间隔 即默认数组第二格,请勿填0及以下,会死循环)
拦截器函数可写一个参数,此参数为连 点器本身(图中get),get(2)表示数组中第二个按键上一次运行的时间点。
下面给个例子。
https://ok.166.net/forum/d3/forum/202004/19/012437nry8e80lglqt5gb3.jpg.thumb.jpg
这个例子是上面那个黑人与一技能的扩充版,加入了元素戒校验。
宏的上半部分为之前写的元素戒自旋器,下半部分为刚刚的黑人宏。
多加了一个位于29行的拦截器,保证黑人的开启时间在电0s-2s
这个例子也是一个比较古老的bd——维尔冰封球的宏的一部分。

2.非按键函数
此连 点器的第一个参数并非绑定按键,也可以使用函数。下面举个小例子
https://ok.166.net/forum/d3/forum/202004/19/012443hnmccvzvt5ttcvss.jpg.thumb.jpg

这个例子把一个自旋器装进了连 点器中去,数组中写了三个参数,都是这个自旋控制器,但意义不太一样。
第一个为执行函数,自旋器可以通过直接传入‘start’运行,
第二个为终止函数,自旋器可以通过直接传入‘end’终止,
第三个为get函数,即用于拦截器函数中的get()所返回的状态,自旋器也是通过传入‘get’获取核心参数。
所以自旋器可以三个都直接传入本身来保持正常运行。
因为连 点器本身的特性,即使是加入非按键函数一般也是自旋器,故在这里只介绍自旋器的方法。
注意:如果加入的是非按键函数,则没有第四个参数,即这个函数无法通过拦截器拦截

CSGO01 发表于 2020-9-27 16:38

mjx 发表于 2021-4-29 14:19

mjx 发表于 2021-4-29 09:51
罗技 2021.3.5164版本的G HUB报错   spring.lua:134: attempt to index a nil value (global 'coroutine' ...

解决了换了罗技游戏软件就可以了

美汁源 发表于 2020-9-27 15:34

玩穿越火线可以用不

bachelor66 发表于 2020-9-27 15:46

mark,留着慢慢看                                 

雪辉 发表于 2020-9-27 16:26

美汁源 发表于 2020-9-27 15:34
玩穿越火线可以用不

可以用宏的,CF的弹道貌似是固定方位的

peroperotina 发表于 2020-11-12 02:34

看了一半, 有点懵懂, 感谢分享.

不过一个zip文件怎么会被chrome警告有风险了呢...

coolloog 发表于 2021-4-27 17:22

留着慢慢看   

mjx 发表于 2021-4-29 09:51

罗技 2021.3.5164版本的G HUB报错   spring.lua:134: attempt to index a nil value (global 'coroutine')
有没有什么办法解决呢?

www445599 发表于 2021-7-8 00:26

CSGO01 发表于 2020-9-27 16:38
你是凯恩那个人??

大佬求求你给个csgo宏G102 LUA文件
页: [1] 2 3
查看完整版本: 罗技Lua语言脚本,发一个"多线程"框架