找回密码
 点一下
查看: 2930|回复: 14

[研究之三]关于动态函数调用(ExecuteCode式功能)的实现[更新悲剧的Patch1]

[复制链接]
发表于 2009-6-18 23:04:41 | 显示全部楼层 |阅读模式
预备部分
一、
众所周知的,0秒触发的timer,其code执行至少是在调用者函数完成之后(典型例子是制作完美的免伤技能)

二、
即使一个没有停顿(TriggerSleepAction)的函数,也可能在执行native函数期间执行其他代码,两个例子是UnitDamage...函数和Issue...Order函数在执行时会立刻触发伤害事件和发布命令事件,并立即执行相关触发(没细究是否还有其他) 。
就是说了解到native层所知的代码细节并不能保证按函数体顺次执行,从而对于力求无bug的coding,任何非唯一性全局变量的实际使用都是不妥的

三、
如果函数中有TriggerExecute,如果执行的是一个三无触发(无sleep execute/wait),肯定会等触发执行结束再继续调用者函数;至于非三无触发以及TriggerExecuteWait,经查阅了zyl910的触发器执行研究 V1.1之后大体得到的结论是...Wait会等到结束完返回而...不会,同时它俩还受到TriggerWaitOnSleeps(调用者函数, boolean)的影响,详细见链接)
另一点(这个在本文无用),用多个TriggerAddCondtion不会短路(不知道什么叫短路的,搜索"and or 短路表达式",T里的and/or是不短路的,因为bj里把它们写成了函数)

四、
在TriggerEvaluate()和TriggerExecute(Wait)()函数执行期间,GetTriggeringTrigger()返回的是被执行条件/动作所属的触发(被调用者),但GetTriggerUnit()之类可以正确返回调用者触发的相关数据

五、
要转化为boolexpr的function,可以写成return nothing,会return false

前三点可以很容易验证:

[codes=jass]
function Timer takes nothing returns nothing
call BJDebugMsg("timer expired")
endfunction

function Trigger takes nothing returns nothing
call BJDebugMsg("trigger execute")
endfunction

function CustomScript takes nothing returns nothing
local trigger t=CreateTrigger()
call TimerStart(CreateTimer(),0,false,function Timer)
call BJDebugMsg("timer func end")
call TriggerAddAction(t,function Trigger)
call TriggerExecute(t)
call BJDebugMsg("trigger func end")
set t=null
endfunction
[/codes]

[trigger]
单位 - 命令 血魔法师 0001 <预设> 对 血魔法师 0001 <预设> 造成 10.00 点伤害(是 攻击伤害, 不是远程攻击) 攻击类型: 法术 伤害类型: 普通 装甲类型: 无
游戏 - 对 玩家1(红色) 在屏幕位移(0.00,0.00)处显示文本: dmg func end
单位 - 对 血魔法师 0001 <预设> 发布 移动 命令到坐标:(0.00,0.00)
游戏 - 对 玩家1(红色) 在屏幕位移(0.00,0.00)处显示文本: order func end
自定义代码: call CustomScript()
[/trigger]

以及两个触发,分别响应伤害事件和命令发布事件,动作是显示"dmg"和"order"

执行结果为:

dmg
dmg func end
order
order func end
timer func end
trigger execute
trigger func end
timer expired


正文~函数动态调用

第一个方法是ExecuteFunc(string),除此之外都要和code变量扯上关系(code->boolexpr,code/boolexpr->trigger),于是我想到了五种方法(enum类的肯定抛弃,timer抛弃):

1 - ExecuteFunc(string)
2 - TriggerAddAction() - TriggerExecute(Wait)()
3 - TriggerAddCondition() - TriggerEvaluate()
4 - ForGroup(only1unitGroup,code)
5 - ForForce(only1playerForce,code)

首先是测试效率(使用大数暴力测试法),进行了很多次,大致结论就是1~2 < 3~4~5,原因估计是1和2支持等待(朴素地想,多个功能就多分代价么),1还要检索字符串(编译完成后的执行码里是没有函数名的);另外就是有少数几次ForGroup出现了不稳定的变慢(可能和地图上多了个单位有关)

然后经测试,5个均支持多线程调用(就是一个function未完成调用同一个function)

不需要实际参数的情况:
由于不需要唯一标示绑定数据,所有方法都可以提前固化所用“工具”(trigger/group/force)

推荐使用ForForce和ForGroup(数据结构应该更简单的force在一个元素的实测中并没有体现),游戏初始化时固化force/group和其中的一个unit/player,随时调用即可(ForForce在Patch1提到bug)
至于TriggerEvaluate的问题是,由预备之二,你不好保证Condition(code)执行期间执行另一个TriggerAddCondition,而如果每次都CreateTrigger,开销就大多了

不过由于即将而来的1.23b彻底KO了C2I,所以需要用传递“函数变量”的时候如果不能传参而只能是通过hashtable,就只能用string和ExecuteFunc了。。555(Patch1内想到了用boolexpr的方法)

需要实际参数的情况:
在追求理想的情况下,任何不具备唯一标识特性的句柄都不能用来绑定数据(所以我们一般用CreateTrigger()/CreateTimer())(Patch1更新了另一种不需要绑定数据的方法)
在这里,ForForce()期间无法获取标示(玩家是固定的),ForGroup需要用CreateGroup+CreateUnit+GetEnumUnit,就远不如TriggerEvaluate法了(可以获得GetTriggeringTrigger,见预备之四),所以用哪个也就很明显了(除非那个unit真的有用处,否则CreateUnit显然比CreateTrigger开销大多了,暴力测试时如果每次都Create,trigger是卡,unit直接死)
当然如果父调用者trigger/timer之类就具有唯一标识特性,那就好说了,因为除trigger法的GetTriggeringTrigger,其他GetTriggerXX或GetExpiredTimer都可以正确获取

评分

参与人数 1威望 +5 收起 理由
血戮魔动冰 + 5

查看全部评分

发表于 2009-6-19 00:05:12 | 显示全部楼层
支持LZ的研究。
我觉得enum类在一些情况下是可以选择的,比如一个触发会有“触发单位”时。
我的帖子http://bbs.islga.org/read-htm-tid-28200.html中就使用了GroupEnumUnitsInRange()来实现演示中的效果。
当然,演示中存在LZ在第二条中所说的潜在的bug,是懒得使用数组所致,等1.23b之后来修正吧。
回复

使用道具 举报

发表于 2009-6-19 01:25:14 | 显示全部楼层
好像eff之前研究的?。。。
另一点(这个在本文无用),用多个TriggerAddCondtion不会短路(不知道什么叫短路的,搜索"and or 短路表达式",T里的and/or是不短路的,因为bj里把它们写成了函数)
这点不理解?

多个动作是会一起执行。
而多个条件按你所说 也都会全部执行?
不会因为1个条件返回假 而不执行后续?

那么 如果 1个返回假的条件  和1个返回真的条件。。那结果会运行动作吗?



哦。。测试了
多个触发器条件
触发事件时 每个都会运行判断。
最后结果再来 and
回复

使用道具 举报

发表于 2009-6-19 01:54:49 | 显示全部楼层
短路和不短路的差别主要在于检查不检查吧?(发明短路这东西主要是为了节省计算机资源,70年代开发C的时候计算机速度,哔------)
回复

使用道具 举报

发表于 2009-6-19 02:24:53 | 显示全部楼层
TriggerExecute和TriggerEvaluate是vJass里实现多态、function interface的基础

假如你是用vJass的,而且用到了多态或者function interface,那么你已经在用TriggerExecute和TriggerEvaluate了
回复

使用道具 举报

发表于 2009-6-19 07:38:47 | 显示全部楼层
一个问题………………也是最主要的………………
LZ你所说的普通状态下最快的方法是什么啊!!!
…………………………………………看来和我一样有点讲不清的味道呢…………
支持研究~~~
回复

使用道具 举报

发表于 2009-6-19 10:14:20 | 显示全部楼层
TriggerExecute《---这个函数会强制挂起当前触发器实例的进程,然后另外启动一个东西去执行那个Trigger,并且在那个Trigger结束后恢复当前触发器实例的进程(你仍然可以取到GetTriggerUnit,估计做了特殊处理)

实际上,这个函数会将进程挂起0.0015秒左右

因为Condition貌似是没有自己的实体的,所以不能被挂起,因此你如果在Condition里面使用TriggerExecute会导致崩溃。
-----------------------------------------------
大概结构是这样的
【条件的唯一实体】
【触发器模板】+【事件】---》【单个触发器实体(携带数据)】
回复

使用道具 举报

发表于 2009-6-19 10:16:58 | 显示全部楼层
所以触发器可以用Wait而Condition不能
回复

使用道具 举报

发表于 2009-6-19 19:09:32 | 显示全部楼层
学习下
回复

使用道具 举报

 楼主| 发表于 2009-6-19 20:12:19 | 显示全部楼层
引用第5楼血戮魔动冰于2009-06-19 07:38发表的  :
一个问题………………也是最主要的………………
LZ你所说的普通状态下最快的方法是什么啊!!!
…………………………………………看来和我一样有点讲不清的味道呢…………
支持研究~~~


就在写这一部分的时候,我发现了一个bug:如果对同一个force进行enum嵌套,则内层的boolexpr会覆盖外层(下次执行有效),而且如果是counted类,所有的counted计数会叠加而导致更不正确的结果,简单来说就是enum相关数据(boolexpr/integer count)和force是唯一绑定从而对多重enum出现bug。
测试代码:
[codes=jass]
function Watch takes string s returns nothing
    call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,0.01,s)
endfunction

function see takes nothing returns nothing
call Watch(I2S(GetPlayerId(GetEnumPlayer())))
endfunction

function filter2 takes nothing returns boolean
call Watch("filter2")
call ForForce(udg_FuncForce,function see)
return true
endfunction

function filter1 takes nothing returns boolean
call Watch("filter1")
if udg_i==0 then
call ForceEnumPlayersCounted(udg_FuncForce,Condition(function filter2),7)
set udg_i=1
endif
call ForForce(udg_FuncForce,function see)
call Watch("filter1end")
return true
endfunction

function TestCall takes nothing returns nothing
call ForceEnumPlayers(udg_FuncForce,Condition(function test))
call Watch("enumAllEnd")
call ForForce(udg_FuncForce,function see)
endfunction
[/codes]
查阅资料后才发现,原来ForForce就存在这种bug……而GroupEnumUnits...Counted压根几乎无法正常工作……

所以……使用boolexpr暂存的话,老老实实用CreateTrigger-TriggerEvaluate吧……


一、可以获得code值的情况(考虑到1.23b无法动态暂存code)
使用ForGroup(only1unitGroup,code),单位组初始化时预置好即可(虽然对于只执行一次的ForForce应该不存在code覆盖bug的影响,但还是算了吧……)

二、无法获得code值的情况
将code转化为boolexpr暂存,并使用CreateTrigger() - TriggerAddCondition() - TriggerEvaluate() - DestroyTrigger()
由研究之一,应该这样排泄就够了

三、传递参数与返回值
昨晚突然想到,虽然由于预备之二,我们不好在函数执行期间使用全局变量,但窃以为在不涉及call的函数初始进入和最终返回阶段,使用全局变量传值(set)是可以的,就是说可以这么写:

[codes=jass]
function 被执行函数 takes nothing returns xxxx
    local xxxx para1=udg_p1
    local xxxx para2=udg_p2
    //...
    //call ...执行部分,不再涉及udg_xx
    set udg_returnValue=localReturnValue
    //set =null
    return udg_returnValue
endfunction

function 调用者函数 takes ... ... returns ...
    local xxxx value
    set udg_p1=参数一
    set udg_p2=参数二
  //...
    set value=ExecuteCode(被执行函数)
  //...
endfunction
[/codes]

这样的好处就是不用gamecache/hashtable绑定数据了,也就不用考虑唯一标识的问题,唯一的麻烦应该就是要准备各种类型的全局变量(不过常用的也就是integer real unit等大概十种吧,谁让jass不支持重载和模板呢)


累死了,暂时就这些……
回复

使用道具 举报

 楼主| 发表于 2009-6-19 20:22:10 | 显示全部楼层
靠……一个弱智行为让我自己毁了刚写好的长文,只留下了楼上……

崩溃了……

暂时实在没精力补写了……

简单说来就是

一、用enum类实现code功能不太好,加上EnumUnits...Counted不能正确工作,Force类有bug

二、CJ函数And()、Or()、Not()具有短路效果,但需要对生成的boolexpr排泄(Condition/Filter(code)类的不用),一个例子是更简洁的施法触发:
[codes=jass]
function TriggerSpellJudge takes nothing returns boolean
    return GetSpellAbilityId()==GetStoredIntegerEx(GetTriggeringTrigger(),"abil")
endfunction

function CreateUnitSpellTrigger takes unit u,integer abilid,integer eventtype,code c returns nothing
    local trigger t=CreateTrigger()
    call StoreIntegerEx(t,"abil",abilid)
    call TriggerRegisterUnitEvent(t,u,ConvertUnitEvent(289+eventtype))
    call TriggerAddCondition(t,And(Condition(function TriggerSpellJudge),Condition(c))
endfunction
[/codes]

三、在条件里使用TriggerExecute貌似不会引起崩溃,间接使用等待也不会,类似TriggerWaitOnSleeps


先这样,哭了……
回复

使用道具 举报

发表于 2009-6-20 08:38:23 | 显示全部楼层
TriggerEvaluate()《---这个显然会开启新的线程,我这里在condition中执行这个是会崩溃的。
回复

使用道具 举报

发表于 2009-6-20 08:42:58 | 显示全部楼层
function SetBoolexprResult takes nothing returns nothing
    set SYS_BoolexprResult = true
endfunction

function ExecuteBoolexpr takes boolexpr be returns boolean
    set SYS_BoolexprResult = false
    call EnumDestructablesInRect(SYS_Rect_ForBoolexpr,be,function SetBoolexprResult)
    return SYS_BoolexprResult
endfunction
回复

使用道具 举报

 楼主| 发表于 2009-6-20 18:07:42 | 显示全部楼层
[codes=jass]
function test takes nothing returns nothing
    call BJDebugMsg(I2S(udg_i))
    if udg_i==0
        set udg_i=1
        call TriggerEvaluate(udg_t)
        set udg_i=0
    endif
    call BJDebugMsg("end")
endfunction

function TestCall takes nothing returns nothing
    local trigger t=CreateTrigger()
    call TriggerRegisterPlayerEvent(t,Player(0),EVENT_PLAYER_END_CINEMATIC)
    call TriggerAddCondition(t,Condition(function test))
    set udg_t=CreateTrigger()
    call TriggerAddCondition(udg_t,Condition(function test))
endfunction
[/codes]
我这个执行没有问题,每按一次esc输出0/1/end/end
难道是我1.23的问题?不像啊

这个也没问题
[codes=jass]
function test takes nothing returns nothing
    call BJDebugMsg(I2S(udg_i))
    if udg_i==0
        set udg_i=1
        call TriggerEvaluate(udg_t)
        set udg_i=0
    endif
    call BJDebugMsg("end")
endfunction

function testcond takes nothing returns nothing
    call GroupEnumUnitsInRect(CreateGroup(),GetWorldBounds(),Condition(function test))
endfunction

function TestCall takes nothing returns nothing
    local trigger t=CreateTrigger()
    call TriggerRegisterPlayerEvent(t,Player(0),EVENT_PLAYER_END_CINEMATIC)
    call TriggerAddCondition(t,Condition(function test))
    set udg_t=CreateTrigger()
    call TriggerAddCondition(udg_t,Condition(function testfunc))
endfunction
[/codes]
PS GetWorldBounds()会泄露
回复

使用道具 举报

发表于 2009-6-20 18:34:24 | 显示全部楼层
不知道,我好像都是用120的。

确实记得某个情况会引起崩溃
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 点一下

本版积分规则

Archiver|移动端|小黑屋|地精研究院

GMT+8, 2024-11-23 21:47 , Processed in 0.089102 second(s), 22 queries .

Powered by Discuz! X3.5

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表