找回密码
 点一下
查看: 10697|回复: 24

小小地解释下为什么不让jass初学者使用bj_wantDestroyGroup的写法~~

[复制链接]
发表于 2008-4-18 19:13:52 | 显示全部楼层 |阅读模式
为什么我一直不让jass初学者使用set bj_wantDestroyGroup = true ?

一来~~手动销毁单位组在我看来只有更正式更具可读性更易理解~~二来呢这东西看似方便~~本质上还是DestroyGroup()~~而且无端增加多余的代码~~所以比直接销毁单位组效率低得多~~最关键的问题是~这玩意儿存在不少的害人之bug

BUG在哪里?全局变量冲突。

让我们来看看set bj_wantDestroyGroup = true的实现原理


打开Blizzard.j搜索bj_wantDestroyGroup字样
[jass]
function IsUnitGroupDeadBJ takes group g returns boolean
    // If the user wants the group destroyed, remember that fact and clear
    // the flag, in case it is used again in the callback.
    local boolean wantDestroy = bj_wantDestroyGroup //这里
    set bj_wantDestroyGroup = false //这里

    set bj_isUnitGroupDeadResult = true
    call ForGroup(g, function IsUnitGroupDeadBJEnum)

    // If the user wants the group destroyed, do so now.
    if (wantDestroy) then  //这里
        call DestroyGroup(g)  //这里
    endif
    return bj_isUnitGroupDeadResult
endfunction

function IsUnitGroupEmptyBJ takes group g returns boolean
    // If the user wants the group destroyed, remember that fact and clear
    // the flag, in case it is used again in the callback.
    local boolean wantDestroy = bj_wantDestroyGroup //这里
    set bj_wantDestroyGroup = false //这里

    set bj_isUnitGroupEmptyResult = true
    call ForGroup(g, function IsUnitGroupEmptyBJEnum)

    // If the user wants the group destroyed, do so now.
    if (wantDestroy) then //这里
        call DestroyGroup(g) //这里
    endif
    return bj_isUnitGroupEmptyResult
endfunction


function ForGroupBJ takes group whichGroup, code callback returns nothing
    // If the user wants the group destroyed, remember that fact and clear
    // the flag, in case it is used again in the callback.
    local boolean wantDestroy = bj_wantDestroyGroup //这里
    set bj_wantDestroyGroup = false //这里

    call ForGroup(whichGroup, callback)

    // If the user wants the group destroyed, do so now.
    if (wantDestroy) then //这里
        call DestroyGroup(whichGroup) //这里
    endif
endfunction

function GroupAddGroup takes group sourceGroup, group destGroup returns nothing
    // If the user wants the group destroyed, remember that fact and clear
    // the flag, in case it is used again in the callback.
    local boolean wantDestroy = bj_wantDestroyGroup //这里
    set bj_wantDestroyGroup = false //这里

    set bj_groupAddGroupDest = destGroup
    call ForGroup(sourceGroup, function GroupAddGroupEnum)

    // If the user wants the group destroyed, do so now.
    if (wantDestroy) then //这里
        call DestroyGroup(sourceGroup) //这里
    endif
endfunction

function GroupRemoveGroup takes group sourceGroup, group destGroup returns nothing
    // If the user wants the group destroyed, remember that fact and clear
    // the flag, in case it is used again in the callback.
    local boolean wantDestroy = bj_wantDestroyGroup //这里
    set bj_wantDestroyGroup = false //这里

    set bj_groupRemoveGroupDest = destGroup
    call ForGroup(sourceGroup, function GroupRemoveGroupEnum)

    // If the user wants the group destroyed, do so now.
    if (wantDestroy) then //这里
        call DestroyGroup(sourceGroup) //这里
    endif
endfunction


function GroupPickRandomUnit takes group whichGroup returns unit
    // If the user wants the group destroyed, remember that fact and clear
    // the flag, in case it is used again in the callback.
    local boolean wantDestroy = bj_wantDestroyGroup //这里
    set bj_wantDestroyGroup = false //这里

    set bj_groupRandomConsidered = 0
    set bj_groupRandomCurrentPick = null
    call ForGroup(whichGroup, function GroupPickRandomUnitEnum)

    // If the user wants the group destroyed, do so now.
    if (wantDestroy) then //这里
        call DestroyGroup(whichGroup) //这里
    endif
    return bj_groupRandomCurrentPick
endfunction

function CountUnitsInGroup takes group g returns integer
    // If the user wants the group destroyed, remember that fact and clear
    // the flag, in case it is used again in the callback.
    local boolean wantDestroy = bj_wantDestroyGroup //这里
    set bj_wantDestroyGroup = false //这里

    set bj_groupCountUnits = 0
    call ForGroup(g, function CountUnitsInGroupEnum)

    // If the user wants the group destroyed, do so now.
    if (wantDestroy) then //这里
        call DestroyGroup(g) //这里
    endif
    return bj_groupCountUnits
endfunction
[/jass]

以上是所有可以应用bj_wantDestroyGroup的函数~~可见bj_wantDestroyGroup的作用原理是~用局部变量wantDestroy记录bj_wantDestroyGroup的值~~然后将bj_wantDestroyGroup设为false~~当函数运行完毕以后再判断wantDestroy是否为真~~为真则销毁单位组~~

也许你看完以后会说~~它明明是用局部变量保存的~~哪里冲突了?~~

hmmm~~我当然没有说冲突发生在函数体以内~~实际上~~在另外一个地方~~单是盯着上面的源码看可能看一个钟头也想不到~~但是你在实际应用中不可避免地会碰到~~

考查以下几个实际例子~~
[trigger]
XXX
    Events
        Player - Player 1 (Red) skips a cinematic sequence
    Conditions
    Actions
        Custom script:   set bj_wantDestroyGroup=true
        Set i = (Number of units in (Units of type Footman))
[/trigger]
[trigger]
YYY
    Events
        Player - Player 1 (Red) skips a cinematic sequence
    Conditions
    Actions
        Set UG = (Units owned by Player 2 (Blue))
        Custom script:   set bj_wantDestroyGroup=true
        Unit Group - Pick every unit in (Random 4 units from UG) and do (Actions)
            Loop - Actions
                Unit - Change ownership of (Picked unit) to Player 1 (Red) and Change color
[/trigger]
以上2个触发都是比较常见的BUG触发实例~~

显然触发XXX想要的结果是在屏幕上输出地图上步兵的个数~~而由于会生成一个地图上所有步兵集合的单位组~~于是在前面加了一条Custom script:   set bj_wantDestroyGroup=true ~~意图在输出完成后销毁这个单位组~~

而YYY想要的结果显然是随机选出属于玩家2的4个单位~~然后将它们交给玩家1并改变颜色~~考虑到"随机的4个单位"也是个多余的单位组~~于是自然就会想用set bj_wantDestroyGroup=true来销毁之~~

我们可以在地图上放上12个玩家2的步兵进行测试~~理论上按下esc后应该显示12~~并且其中有4个被转移给玩家1~~

不过~~事实是~~运行地图按下esc后~~屏幕上显示的是0~~而玩家2的步兵也一个都没有转移所属~~

现在我们就来展示其中的bug~~

将上面2个触发的动作转为jass

[jass]
function Trig_XXX_Actions takes nothing returns nothing
    set bj_wantDestroyGroup=true
    set udg_i = CountUnitsInGroup(GetUnitsOfTypeIdAll('hfoo'))
    call DisplayTextToForce( GetPlayersAll(), I2S(udg_i) )
endfunction
[/jass]

[jass]
function Trig_YYY_Func004A takes nothing returns nothing
    call SetUnitOwner( GetEnumUnit(), Player(0), true )
endfunction

function Trig_YYY_Actions takes nothing returns nothing
    set udg_UG = GetUnitsOfPlayerAll(Player(1))
    set bj_wantDestroyGroup=true
    call ForGroupBJ( GetRandomSubGroup(4, udg_UG), function Trig_YYY_Func004A )
endfunction
[/jass]
也许一时半会还看不出问题~~那么现在开始~~想象自己是单步调试机~~我们开始执行XXX触发器的动作了~~

先运行第一条代码~~全局变量bj_wantDestroyGroup值被设为真~~

运行第二条代码~~我们先运行CountUnitsInGroup()么?~~不对~~先得计算参数GetUnitsOfTypeIdAll('hfoo')~~那么就要跳入函数GetUnitsOfTypeIdAll()进行计算了~~该函数是一个BJ函数~~那么我们列出代码~~可以看出该函数的作用原理是遍历所有玩家~~然后将每个玩家的步兵组g分别投入到result中~~最后返回result~~不过我们现在还是继续单步执行比较好~~
[jass]
function GetUnitsOfTypeIdAll takes integer unitid returns group
    local group   result = CreateGroup()
    local group   g      = CreateGroup()
    local integer index

    set index = 0
    loop
        set bj_groupEnumTypeId = unitid
        call GroupClear(g)
        call GroupEnumUnitsOfPlayer(g, Player(index), filterGetUnitsOfTypeIdAll)
        call GroupAddGroup(g, result)

        set index = index + 1
        exitwhen index == bj_MAX_PLAYER_SLOTS
    endloop
    call DestroyGroup(g)

    return result
endfunction
[/jass]
继续~~我们加快点速度~~创建单位组result,创建单位g,创建局部整型变量index作为循环变量,设index初值,循环开始~~

给全局变量bj_groupEnumTypeId赋值~~用来作为枚举过滤~~清空g~~再开始将Player(0)的所有步兵装入单位组g~~

下面就是关键了~~放慢速度~~

call GroupAddGroup(g, result)

将单位组g整个装入result单位组~~

我们继续跳入GroupAddGroup()这个函数~~如果你记性不错~~那么会记得之前我列出的所有应用bj_wantDestroyGroup的BJ函数中~~已经包括了该函数的源码在内了~~不过这里为了展示方便~~不妨多copy一遍~~

[jass]
function GroupAddGroup takes group sourceGroup, group destGroup returns nothing
    // If the user wants the group destroyed, remember that fact and clear
    // the flag, in case it is used again in the callback.
    local boolean wantDestroy = bj_wantDestroyGroup
    set bj_wantDestroyGroup = false

    set bj_groupAddGroupDest = destGroup
    call ForGroup(sourceGroup, function GroupAddGroupEnum)

    // If the user wants the group destroyed, do so now.
    if (wantDestroy) then
        call DestroyGroup(sourceGroup)
    endif
endfunction
[/jass]
好了~~我们将2个单位组g和result作为参数传递给GroupAddGroup()的参数sourceGroup和destGroup~~为GroupAddGroup()创建局部变

布尔量wantDestroy~~值为bj_wantDestroyGroup~~而现在我们检查我们大脑中记忆体中的bj_wantDestroyGroup的值罢~~好吧~~是true~~那么wantDestroy自然也被赋值为true了~~

赋值完成后~~bj_wantDestroyGroup的使命似乎就完成了~~于是我们将其设为false~~注意我这里用“似乎”~~一般来说~~稍微有点基础的同学看到这里就应该已经完全明白问题在哪里了~~不过我们不妨继续调试~~

于是继续~~我们加快点速度~~接下去是枚举sourceGroup也就是前一个函数中的单位组g~~将其中所有单位逐一地添加给单位组bj_wantDestroyGroup也就是前一个函数中的result单位组~~

然后判断wantDestroy为真~~于是我们调用DestroyGroup()来将sourceGroup也就是前一个函数中的g销毁~~

遇到endfunction跳出函数~~回到前一个函数GetUnitsOfTypeIdAll()继续执行~~

现在循环变量index自增为1~~而没有达到跳出循环条件(等于bj_MAX_PLAYER_SLOTS~~也就是等于16)~~于是循环继续~~

继续给bj_groupEnumTypeId~~不过可惜~~当我们执行到GroupClear(g)的时候~~嘿嘿g不是已经被销毁了么?~~

那么下一步~~将Player(1)的所有步兵都添加给g也毫无意义~~不过我们现在是调试机~~乖乖地做一个向不存在的单位组添加单位的操作罢~~

下一步将g中所有单位装入result~~既然g不存在~~那么就是白装~~result中依然还是只有上一次添加的单位~~

继续~~循环~~加快速度~~反正接下去所有15次循环全部都是一样结果~~什么也没有给result添加~~所以整个循环下来~~只有第一次成功地执行了单位添加~~也就是仅仅加入了玩家1的所有步兵~~现在你知道为何这个触发会在屏幕上显示0了~~因为我们只给玩家2放了12个步兵~~

bug就这样完了?~~才不是呢~~继续调试~~

销毁g~~毫无意义的操作~~

遇到return跳出函数~~并返回result~~就这个例子来说~~就是返回玩家1的所有步兵~~(要记得我们的最初目的是返回地图上所有的步兵~~而不单单是玩家1的)

回到触发器XXX的动作函数Trig_XXX_Actions()~~开始执行CountUnitsInGroup()~~

于是我们再copy一遍CountUnitsInGroup()的源码~~

[jass]
function CountUnitsInGroup takes group g returns integer
    // If the user wants the group destroyed, remember that fact and clear
    // the flag, in case it is used again in the callback.
    local boolean wantDestroy = bj_wantDestroyGroup
    set bj_wantDestroyGroup = false

    set bj_groupCountUnits = 0
    call ForGroup(g, function CountUnitsInGroupEnum)

    // If the user wants the group destroyed, do so now.
    if (wantDestroy) then
        call DestroyGroup(g)
    endif
    return bj_groupCountUnits
endfunction
[/jass]
将之前的result单位组赋给CountUnitsInGroup()的参数g~~跳入函数~~

跳入函数后第一步~~将bj_wantDestroyGroup的值赋给局部变量wantDestroy~~检查我们脑内的记忆体~~发现bj_wantDestroyGroup已经被设为false了~于是wantDestroy的值也是假~~

加快速度~~将全局变量bj_wantDestroyGroup的值再设为假~~bj_groupCountUnits的值设为0~~枚举单位组g~~也就result~~计算单位总数~~在这个例子里由于玩家1一个步兵也没有~~所以结果自然是0~~

继续~~检查wantDestroy~~假~~于是不销毁result单位组~~返回单位组单位总数~~也就是0~~

跳回到回到触发器XXX的动作函数Trig_XXX_Actions()~~执行写屏操作~~将0显示在屏幕上~~



好了~~现在让我们回过头去看看这一句set bj_wantDestroyGroup=true给这个触发带来了多少bug~~

第一~~本应该返回所有的步兵~~却只返回了玩家1的步兵~~

第二~~本应该将(所有步兵)这个计数用的临时单位组销毁~~结果却根本没有销毁~~




XXX的问题是这样~~那么触发器YYY呢?~~

相比这次也不用像刚才那样详细地调试了~~

单只就列出关键部分即可~~
[jass]
function Trig_YYY_Actions takes nothing returns nothing
    set udg_UG = GetUnitsOfPlayerAll(Player(1))
    set bj_wantDestroyGroup=true //关键
    call ForGroupBJ( GetRandomSubGroup(4, udg_UG), function Trig_YYY_Func004A ) //跳入函数GetRandomSubGroup()
endfunction

function GetRandomSubGroup takes integer count, group sourceGroup returns group
    local group g = CreateGroup()

    set bj_randomSubGroupGroup = g
    set bj_randomSubGroupWant  = count
    set bj_randomSubGroupTotal = CountUnitsInGroup(sourceGroup) //跳入函数CountUnitsInGroup()

    if (bj_randomSubGroupWant <= 0 or bj_randomSubGroupTotal <= 0) then
        return g //g已经被销毁!
    endif

    set bj_randomSubGroupChance = I2R(bj_randomSubGroupWant) / I2R(bj_randomSubGroupTotal)
    call ForGroup(sourceGroup, function GetRandomSubGroupEnum)
    return g //g已经被销毁!
endfunction

function CountUnitsInGroup takes group g returns integer
    // If the user wants the group destroyed, remember that fact and clear
    // the flag, in case it is used again in the callback.
    local boolean wantDestroy = bj_wantDestroyGroup //关键
    set bj_wantDestroyGroup = false //关键

    set bj_groupCountUnits = 0
    call ForGroup(g, function CountUnitsInGroupEnum)

    // If the user wants the group destroyed, do so now.
    if (wantDestroy) then
        call DestroyGroup(g) //关键,计数用的单位组g已经被销毁。
    endif
    return bj_groupCountUnits
endfunction

function ForGroupBJ takes group whichGroup, code callback returns nothing
    // If the user wants the group destroyed, remember that fact and clear
    // the flag, in case it is used again in the callback.
    local boolean wantDestroy = bj_wantDestroyGroup //bj_wantDestroyGroup已经为假
    set bj_wantDestroyGroup = false

    call ForGroup(whichGroup, callback)  //跳入Trig_YYY_Func004A()~~执行枚举动作

    // If the user wants the group destroyed, do so now.
    if (wantDestroy) then
        call DestroyGroup(whichGroup)  //由于wantDestroy为假~~没有销毁!
    endif
endfunction

function Trig_YYY_Func004A takes nothing returns nothing
    call SetUnitOwner( GetEnumUnit(), Player(0), true ) //实际上单位组已经被销毁~~因此一个单位也无法被改变所属~~
endfunction
[/jass]

好了~~总结下来~~这小小的几句动作也出现了二个大BUG~~

第一~~随机出来的4个步兵组成的单位组已经被销毁~~所以枚举该组内单位~~并改变他们所属的玩家成了一句空话~~

第二~~本来打算销毁这4个单位组成的临时单位组~~却没有正确地销毁~~



看了以上两个小例子~~你应该对bj_wantDestroyGroup的废材之处有了更多了解了~~如果你还是jass新手~~希望你从现在起就杜绝这种写法以免养成坏习惯~~乖乖地使用DestroyGroup()这个很有前途的写法罢~~

BUGTest.w3x

18 KB, 下载次数: 41

评分

参与人数 1威望 +20 收起 理由
狡猾的兔子 + 20 辛苦了。

查看全部评分

 楼主| 发表于 2008-4-18 19:28:05 | 显示全部楼层
什么?

你说我让新手不要用这东西~~是不是就是说等你们高手了再用呢?~~才怪~~

以后如果你民遇到还在用这玩意儿的自封高手~~就可以优越地对他们说~~

"嘛~~竟然还在用这种东西~~这年头连jass初学者都抛弃这废物啦~~"
回复

使用道具 举报

发表于 2008-4-18 19:35:23 | 显示全部楼层
hahahahaha~
回复

使用道具 举报

发表于 2008-4-18 19:38:44 | 显示全部楼层
其实完全没有必要把某个重要的结论放在后面  
如果在
<BUG在哪里?全局变量冲突。>
这句前面加上
<1个动作操作了2个组 导致bj_wantDestroyGroup不正确运转>
那么看起来就没那么头昏了

用bj_wantDestroyGroup也是多年前看了一篇DaneXX写的繁体的 關於記憶體泄露XX    的文章.....
回复

使用道具 举报

发表于 2008-4-18 20:08:03 | 显示全部楼层
最好的办法就是那个EnumGroup什么什么的,也别用了。
[codes=jass]
unit u
group your_familly
loop
        set you=FirstOfGroup(your_familly)
        exitwhen you==null
        call Kill(you)
        call GroupRemoveUnit(your_familly,you)
endloop
call DestroyGroup(your_familly)
set your_familly=null
[/codes]
回复

使用道具 举报

发表于 2008-4-18 20:12:21 | 显示全部楼层
<下次删我的帖子时,一定要给一个短信啊,扣分扣威望都可以,不要减少我的发帖量的>
回复

使用道具 举报

发表于 2008-4-18 20:22:56 | 显示全部楼层
确实太长老
回复

使用道具 举报

发表于 2008-4-18 20:23:35 | 显示全部楼层
引用第5楼朱朱于2008-04-18 20:12发表的  :
<下次删我的帖子时,一定要给一个短信啊,扣分扣威望都可以,不要减少我的发帖量的>

有什么区别吗....
回复

使用道具 举报

发表于 2008-4-18 21:15:51 | 显示全部楼层
这问题素不会用jass的人清单位组必须使用的方法,你叫人不用,太残忍了。
回复

使用道具 举报

发表于 2008-4-18 21:19:15 | 显示全部楼层
使用单位组变量记录单位组,用完后手动删除。
回复

使用道具 举报

 楼主| 发表于 2008-4-18 21:31:53 | 显示全部楼层
引用第8楼thody于2008-04-18 21:15发表的  :
这问题素不会用jass的人清单位组必须使用的方法,你叫人不用,太残忍了。
既然不会用jass~~当然更容易出bug咯~~因为他们都根本不了解个中原理和过程~~所以不会觉得这两个动作有什么问题~~

而且既然不会jass~~那么让他们用bj_wantDestroyGroup和让他们用DestroyGroup()又有什么区别呢?~~
回复

使用道具 举报

发表于 2008-4-18 22:31:51 | 显示全部楼层
我都很小心的用BJ函数
回复

使用道具 举报

发表于 2008-4-18 23:16:20 | 显示全部楼层


不用bj_wantDestroyGroup,用DestroyGroup()的话呢

是不是先设置单位组变量

然后删除单位组?

那样的话,如果地图多处要删除单位组呢?

可不可以都使用一个单位组变量?

就像多个技能同时使用一个临时点变量一样?
回复

使用道具 举报

发表于 2008-4-19 00:37:12 | 显示全部楼层
引用第12楼hmmm于2008-04-18 23:16发表的  :


不用bj_wantDestroyGroup,用DestroyGroup()的话呢

是不是先设置单位组变量
.......
LS说得不错[s:179]
回复

使用道具 举报

发表于 2008-4-19 02:55:35 | 显示全部楼层
引用第3楼乱穿马甲于2008-04-18 19:38发表的  :
用bj_wantDestroyGroup也是多年前看了一篇DaneXX写的繁体的 關於記憶體泄露XX    的文章.....
.......
原来是OX叔叔写的....现在被MD大人打败了...
回复

使用道具 举报

发表于 2008-4-19 19:18:56 | 显示全部楼层
引用第14楼OrS于2008-04-19 02:55发表的  :

原来是OX叔叔写的....现在被MD大人打败了...
不是OX写的把,虽然2个人首字母都是D,不过应该很容易分辨出来
回复

使用道具 举报

发表于 2008-4-19 19:23:54 | 显示全部楼层
Danexx=蛋叉=OX=蛋X=O叉=叔叔=007
Danny=蛋Y,素台湾的,所以繁体的话大概是蛋Y写的
回复

使用道具 举报

发表于 2008-4-19 20:46:04 | 显示全部楼层
你不说我都忘记有这个东西了~~

万年苦工飘国~~
回复

使用道具 举报

发表于 2008-4-19 20:49:34 | 显示全部楼层
话说~~这个应该是DY写的~~不是DX写的~~
回复

使用道具 举报

发表于 2008-4-19 21:35:00 | 显示全部楼层
你居然在三分钟之后才发现这个事实,果然是何等师太的阴谋啊。
回复

使用道具 举报

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

本版积分规则

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

GMT+8, 2024-5-4 02:20 , Processed in 0.742324 second(s), 25 queries .

Powered by Discuz! X3.5

© 2001-2023 Discuz! Team.

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