请选择 进入手机版 | 继续访问电脑版

GA地精研究院

 找回密码
 立即注册
查看: 6268|回复: 24

[文献资料] 【环保大师】教你“环保”(排泄+提高效率+一点小建议)

[复制链接]
发表于 2009-1-7 12:49:48 | 显示全部楼层 |阅读模式
(被自己忍无可忍的和谐掉了)

一.排泄
    排泄,即避免内存泄漏的发生,而内存泄露则是指什么呢?
    内存泄露 = 程序 (创建实例 和 变量指针) 占用了内存,但是你却不释放它,且造成了这块内存的不可释放性(在程序结束以前,这块内存无法被访问、读取/存入数据,即无法操作这块内存)。这样导致 这块内存 成了废内存(用不了那还不废了 ~_~ ),虽然这没有对电脑造成什么【不可回复性的物理性损伤】,但是如果不断的执行导致内存泄露的代码,那么内存的开销将越来越大,直到再也无法申请到新内存以供给新的实例和变量指针,接下来就是程序强制退出。
    你们可能看不懂吧~~(一大堆东西谁不烦那~)。其实呢,简单的放在魔兽里来说,就是: 你创建了N个【点】(Location)实例(它们都占用内存),却没有删除它们,使用的内存越来越大,最后,因为没有更多的内存,魔兽卡掉了~~(仅仅是举个例子)。


            (1) 一个典型的“点”【Location】泄露:


    这是一个典型的“点”泄露trigger,大家看看。
[trigger]Location
    事件
        单位 - 任意单位 死亡
    条件
    动作
        单位 - 创建 1 个 步兵 给 玩家1(红色) 在 ((触发单位) 的位置) ,面向角度为 默认建筑朝向 度
[/trigger]
    这个泄露就在 ((触发单位) 的位置) 上。为什么呢?因为 (XXXXX 的位置) 是这样的: 创建一个 X坐标 为 单位X坐标 Y坐标 为 单位Y坐标 的 “点”。而这个点(即一个实例) 则传递给 (创建单位)。 然后,它没有被(清除点)【RemoveLocation】给清除(即没有释放这个点的内存),也没有一个变量指针指向它(造成这个点的内存的不可访问性),这样就形成了内存泄露。
    修改错误的方法是这样的:
[trigger]Location
    事件
        单位 - 任意单位 死亡
    条件
    动作
        设置 loc = ((触发单位) 的位置)
        单位 - 创建 1 个 步兵 给 玩家1(红色) 在 loc ,面向角度为 默认建筑朝向 度
        点 - 清除 loc
        自定义代码: set udg_loc = null
[/trigger]
    这回是弄了个变量(其实是变量指针),把它指向为这个点实例((触发单位) 的位置)(这样就使这个点实例占用的内存有可释放性)。然后,引用这个变量指针指向的 点, 创建单位。然后,再引用这个变量指针指向的点, 用清除点【RemoveLocation】, 删除这个点, 并释放这个点占用的内存。最后,释放这个变量指针的内存(设置为空,即null)
    当然,几乎每个人都可能犯这样的可爱又引人发笑的错误:
[trigger]Location
    事件
        单位 - 任意单位 死亡
    条件
    动作
        单位 - 创建 1 个 步兵 给 玩家1(红色) 在 ((触发单位) 的位置) ,面向角度为 默认建筑朝向 度
        点 - 清除 ((触发单位) 的位置)
        自定义代码: set udg_loc = null
[/trigger]
    为什么这是一个错误呢?因为这创建了2个点,一个点是用来创建单位的,而另一个则逗了。创建出来之后,就给删了,什么也没干,还降低效率。

    举一反三,依此类推,什么单位组(group),特殊效果(effect)、闪电效果(lightning)、计时器(timer)…………都需要清除(用DestroyGroup/DestroyTimer/DestroyLightning/DestroyEffect)
    还有一些小技巧。如set bj_wantDestroyGroup = true等,很多人都介绍过,在此就不多说了。


            (2) 局部变量泄露:


    怎么说呢~~,这个局部变量泄漏,其实是局部变量指针在函数执行完前没有清空(指针也要有内存以存储它指向的实例的ID):
[codes=jass]function A takes nothing returns nothing
    local unit u = GetTriggerUnit()
    //some codes here
endfunction[/codes]
    ,而也造成了内存泄露。解决的方法很简单,就是这样:
[codes=jass]function A takes nothing returns nothing
    local unit u = GetTriggerUnit()
    //some codes here
    set u = null
endfunction[/codes]
    设置为空(null)即可。
    造成局部变量泄漏的人为原因有二:一是不知道有这么个泄露,二是忘记在函数【执行】完前设置为空(set XXX = null)。
    当然,这样的情况也请注意:
[codes=jass]function A takes nothing returns nothing
    local unit u = GetTriggerUnit()
    //some codes here
    if GetUnitLifePercent( u ) > 100.0 then
        return
    endif
    //some codes here
    set u = null
endfunction[/codes]
    需要在return前也清空局部变量指针:
[codes=jass]function A takes nothing returns nothing
    local unit u = GetTriggerUnit()
    //some codes here
    if GetUnitLifePercent( u ) > 100.0 then
        set u = null
        return
    endif
    //some codes here
    set u = null
endfunction[/codes]
    小技巧:大家最好在函数的开头声明局部变量时,就在函数最后写上释放局部变量的代码,这样也不太会忘记。


            (3) 全局变量“泄露”:


    可能看帖的大多数人都只知道局部变量泄漏,而不知道全局变量“泄露”。主要呢是因为全局变量“泄露”并不是不可恢复性的。(其实这是GA 某个人发的贴,我才知道的。本人在此强烈建议kook大人加这个人(精华)或(威望+10)。)
    魔兽的变量全是指针,局部变量是,全局变量也是。所以他们在用完后都应设置为空。全局变量和局部变量的不同在于,全局变量可以重复使用,而局部变量则在函数结束之后,除非是被设置为空,否则魔兽并不会自动删除它们。但是如果全局变量没有设置为空,它则会一直占用内存用来指向一个错误数据,直至重新给它赋值(重新指向一个实例)。(PS:貌似在这点上,玻璃渣的所有地图都没有对大家起到示范作用。)解决方法也很简单,将变量设置为空就可以了。
[trigger]Location
    事件
        单位 - 任意单位 死亡
    条件
    动作
        单位 - 创建 1 个 步兵 给 玩家1(红色) 在 ((触发单位) 的位置) ,面向角度为 默认建筑朝向 度
        点 - 清除 ((触发单位) 的位置)
        自定义代码: set udg_loc = null  (!!!)
[/trigger]


            (4) GameCache“泄露”:


    这个泄露几乎对魔兽没有影响,因为这个东西占用的内存实在是太小了。说白了就是吃力不讨好,但是为了真正的环保,大家还是努力吧。就是在GameCache里存储数据,但在他们失效后,不清空他们,就形成泄露。用来清除的就是这些函数,相信大家都会用吧:
[codes=jass]
FlushStoredMission
FlushStoredInteger
FlushStoredBoolean
FlushStoredString
FlushStoredUnit
FlushStoredReal
[/codes]
    在这里,顺便说一下HaveStoredXXXX和GetStoredXXXX != 0/null/false 的区别。
    HaveStoredXXXX的意思是:这里是否有数据( 【没有存储过/已用FlushStoredXXXX清空】 时 为 true)
    而GetStoredXXXX! = 0/null/false 只是对比存储的数据(如果没有数据则返回0/null/false)是否为 0/null/false, 不是有没有数据的意思。
    现已给出测试代码(本人编译):
[codes=jass]function TestForHaveStoredXXXXAndGetStoredXXXX takes nothing returns nothing
    local gamecache gc = null
    call FlushGameCache( InitGameCache( thegc.w3v ) )
    set gc = InitGameCache( thegc.w3v )
    if HaveStoredBoolean( gc, a, b ) then
        call BJDebugMsg( 1:True! )
    else
        call BJDebugMsg( 1:False! )
    endif
    call StoreBoolean( gc, a, b, false )
    if HaveStoredBoolean( gc, a, b ) then
        call BJDebugMsg( 2:True! )
    else
        call BJDebugMsg( 2:False! )
    endif
    call FlushStoredBoolean( gc, a, b )
    if HaveStoredBoolean( gc, a, b ) then
        call BJDebugMsg( 3:True! )
    else
        call BJDebugMsg( 3:False! )
    endif
    call FlushGameCache( gc )
    set gc = null
endfunction[/codes]输出:
1:False
2:True
3:False


            (5) **** String“泄露” ****:


注:此为JASS班毕业以上学历(熟练使用Return Bug+GameCache+Timer)者的课程,有兴趣就看看,没兴趣就别难为自己了。

    String“泄露”是很早就被众多JASSer所“知道”,但却没有意识到的一种对内存可重复使用但不可释放的“泄露”。
    (所白了就是使用string)
    魔兽所有的string都具有 【不可释放性】 但极为有趣的是,string还具有 【可重复使用性】,这跟全局变量很相像。在魔兽中,所有string全要用变量储存它,且一旦申请到内存,就无法释放。但是如果在函数A里使用了A,在函数B里又使用了A,那么这回只是引用A,而不是再创建一个A并占用内存。
    实例:
[codes=jass]function A takes nothing returns nothing
    call BJDebugMsg( A )
endfunction

function B takes nothing returns nothing
    call BJDebugMsg( B )
    call BJDebugMsg( A )
endfunction

function Init takes nothing returns nothing
    call A()
    //此时【创建】了A
    call B()
    //此时【创建】了B
    //此时【引用】了A
endfunction[/codes]
这段话请尽量理解。
注1:魔兽给string分配内存的方式是:已使用的string,达到了25个,就再申请25个string的内存,直到这次分配的内存被新的25个string充填,就再次申请25个内存(步骤循环)………………
注2:魔兽string数量的上限是10万条(再多虽不会强制退出,但每一次引用/创建string将会把人的电脑卡死:速度:3帧/s)
注3:一条string最多860字符(1汉字 = 3字符)
    string基础
    ① 字符串连接+: a+b
    就是引用/创建a和b,再引用/创建ab。
    ② 字符串截断SubString: SubString( ab, 0, 1 )
    就是引用/创建ab,再引用/创建a
    ③ 字符串大小写转换StringCase: StringCase( ab, true )
    就是引用/创建ab, 再引用/创建AB
    ④ I2S: I2S( 100 )
    就是引用/创建100
    ⑤ R2S: R2S( 123.4567 )
    就是引用/创建123.457(请注意,R2S只有小数点后3位数的转换且会四舍五入)
    ⑥ R2SW: R2SW( 123.4567, 1, 3 )
    就是引用/创建123.457
    注:R2SW参数说明:1.要转换的实数 2.宽度(貌似没有用) 3.精度



    几乎所有的在编译阶段无法估计数量的string泄露都是这个东东产生的:
    I2S( H2I( <a handle value> ) )
    为什么呢?因为每一个handle都有自己独一无二的Handle编号(其实是地址),而这个东东就是把这个编号转换成string,这无疑是一场灾难!
    假设:一个地图里【出现】过1000个单位,那么就会有 小于或等于1000个string被创建,他们都是10XXXX的形式。而且这种string在游戏中除了使用在GameCache读取数据时被引用以外,是不可能被正常使用的。所以,我更愿意叫他们为“灰色的黄金”。
注1:参考正常使用string范畴:1000条中/英文信息+“0”~“100”(非小数)
注2:参考隐性使用string范畴:在游戏初始化/开始XXX秒时,创建的Handle的地址的string。
注3:参考尽量避免string范畴1:在游戏进行时,创建Handle并转换地址为string。
注4:参考尽量避免string范畴2:-   “--  ”“--- ”“----”不应写成这样:
[codes=jass]function A takes nothing returns nothing
    //some codes here
    loop
        set i = i + 1
        exitwhen i > 4
        set s = s + -
    endloop
    //some codes here
endfunction[/codes]
最好使用全局变量数组:
[codes=jass]
globals
    string S[3]
endglobals

function InitS takes nothing returns nothing
    set S[0] = -   
    set S[1] = --  
    set S[2] = ---
    set S[3] = ----
    //此函数在地图开始时初始化就可以了。
endfunction
[/codes]

    既然我们在前面都说了要尽量避免使用H2S,但是,ReturnBug+GameCache要是没了它,估计就没得玩了。那怎么办呢?
    我在这里给你们一个10000年也不会处Bug的方法,大家要记住了!:


            (6) **** 改进的H2S(指针算术) ****:


[codes=jass]globals
    integer const_InitHandleNumber = 0
endglobals

function H2I takes handle h returns integer
    return h
    return 0
endfunction

function InitHandleNumber takes nothing returns nothing
    local lighting l = Lighting( 0, 0, 0, 0 )
    set const_InitHandleNumber = H2I(l )
    call RemoveLighting( l )
    set l = null
endfunction
//此函数要在地图初始化时运行。

function H2S takes handle h returns string
    return I2S( H2I( h ) - const_InitHandleNumber )
endfunction[/codes]
    这是我改进后的H2S。原理是用需要转换的Handle的地址再减去地图初始化时创建的Location的地址再I2S。
    比如在运行InitHandleNumber之后,你再创建一个Timer,输出H2S( < The Timer > ),你会发现输出的是0。
    这样的话至少把地图初始化后创建的一部分的Handle的地址转换成string时,得到的是“0”~“100”的正常适用范围。

    这个技术被人们称为“指针算术”。
    当然,还有另一种从根本上避免使用H2S但同时又能继续像GameCache存储数据的方式。
PS:如果使用lighting的话。
这样改H2I也可以:
[codes=jass]
function H2I takes handle h returns integer
    return h
    return 0
endfunction
function H2I_Debug takes handle h returns integer
    return H2I( h ) - const_InitHandleNumber
endfunction[/codes]
其实这样没多大用………………


            (7) **** 利用全局变量数组替代GameCache+H2S ****:


[codes=jass]globals
    integer    g_UnitDataNumber = 0
    trigger array g_UnitDamageTrigger
    triggeraction array g_UnitDamageTriggerAction
endglobals

function createUnitData takes unit u returns integer
    local trigger t = CreateTrigger()
    set g_UnitDataNumber = g_UnitDataNumber + 1
    set g_UnitDamageTrigger[ g_UnitDataNumber ] = t
    call TriggerRegisterUnitEvent( t, u, EVENT_UNIT_DAMAGED )
    set g_UnitDamageTriggerAction[ g_UnitDataNumber ] = TriggerAddAction( t, function XXXXXXXXXXX )
    set t = null
    return g_UnitDataNumber
endfunction

function InitUnitData takes unit u returns nothing
    call SetUnitUserData( u, createUnitData( u ) )
endfunction

function RemoveUnitData takes unit u returns nothing
    local integer id = GetUnitUserData( u )
    local trigger t = null
    if id != 0 then
        set t = g_UnitDamageTrigger[ id ]
        call TriggerRemoveAction( t, g_UnitDamageTriggerAction[ id ] )
        call DestroyTrigger( t )
        set g_UnitDamageTrigger[ id ] = null
        set g_UnitDamageTriggerAction[ id ] = null
    endif
    set t = null
endfunction[/codes]
    当然,数组是有使用限制的,每一个变量数组最多只能有8192个元素(即指针),也就是说:XXXXX[0-8191]才是合法的。剩下的[<0]and[>8191]就不行了。
    演示中并没有考虑这个问题。
    要解决这个问题很难。但是————————
    嗯~~~~~上天总是对懒人没有眷顾,但是貌似这次他开恩了~~


            (8) **** 利用VJ中的struct(结构)替代自己的全局变量数组 ****:


    不得不说,VJ实在是绝对的懒人工具。本人现在就深深沉迷于其中…………~~~
    VJ的struct更是将我们绞尽脑汁也较难以解决的全局变量数组变得极为简单了(自己就有创造/释放的功能)~~~~~
    算了不废话,VJ还是各位自己去找找看吧~~我只能说它是个好极了的东东~~我现在只给出演示的VJ型代码
[codes=jass]struct UnitData
    trigger         DamageTrigger
    triggeraction   DamageTriggerAction
   
    static method create takes unit u returns UnitData
        local UnitData temp = UnitData.allocate()
        local trigger t = CreateTrigger()
        set temp.DamageTrigger = t
        call TriggerRegisterUnitEvent( t, u, EVENT_UNIT_DAMAGED )
        set temp.DamageTriggerAction = TriggerAddAction( t, function XXXXX )
        set t = null
        call SetUnitUserData( u, temp )
        return temp
    endmethod
   
    method onDestroy takes nothing returns nothing
        call TriggerRemoveAction( this.DamageTrigger, this.DamageTriggerAction )
        call DestroyTrigger( this.DamageTrigger )
        set this.DamageTrigger = null
        set this.DamageAction = null
        call this.destroy()
    endmethod
endstruct

function GetUnitData takes unit u returns UnitData
    return GetUnitUserData( u )
endfunction[/codes]
    当然,这仅仅适用于有SetXXXUserData的Handle(目前为止,只有unit和item有这个成员函数:SetUnitUserData()and SetItemUserData() )


            (9) **** 利用H2I使诸如Trackable或Destruction这些没有SetXXXUserData的Handle使用全局变量数组 ****:


    问题总是有可能解决的。
[codes=jass]globals
    integer Trks_InitHandleNumber = 0
    real array Trks_X
    real array Trks_Y
endglobals

function H2I takes handle h returns integer
    return h
    return 0
endfunction

function InitTrksHandleNumber takes nothing returns nothing
    local location loc = Location( 0, 0 )
    set Trks_InitHandleNumber = H2I( loc )
    call RemoveLocation( loc )
    set loc = null
endfunction
//此函数必须要在地图初始化(且在其他一切创建Handle动作之前)时运行。

function GetTrkId takes trackable trk returns integer
    return H2I( trk ) - Trks_InitHandleNumber
endfunction

function CreateTrackableXL takes string trackableModelPath, real x, real y, real facing returns trackable
    local trackable trk = CreateTrackable( trackableModelPath, x, y, facing )
    local integer id = GetTrkId( trk )
    set Trks_X[ id ] = x
    set Trks_Y[ id ] = y
    return trk
endfunction[/codes]
    这个…………原理和【改进的H2S】基本一样(指针算术)只不过……换了一种方式………………且没有涉及string………………
    *小小的脑力题:
    本人曾经这样想过能不能这样:?
    警告!!下面的代码有Bug!请勿运用于魔兽中!!
[codes=jass]globals
    integer Trks_InitHandleNumber = 0
    trigger array Trks_Trig
    triggeraction array Trks_Action
endglobals

function H2I takes handle h returns integer
    return h
    return 0
endfunction

function InitTrksHandleNumber takes nothing returns nothing
    local location loc = Location( 0, 0 )
    set Trks_InitHandleNumber = H2I( loc )
    call RemoveLocation( loc )
    set loc = null
endfunction
//此函数必须要在地图初始化(且在其他一切创建Handle动作之前)时运行。

function GetTrkId takes trackable trk returns integer
    return ( H2I( trk ) - Trks_InitHandleNumber ) / 4
    //注意,这里/4是因为创建一个trackable还要创建trigger,triggeraction,event总共4个handle,所以这里/4就可以了。
endfunction

function CreateTrackableXL takes string trackableModelPath, real x, real y, real facing, code action, boolean IsHit returns trackable
    local trackable trk = CreateTrackable( trackableModelPath, x, y, facing )
    local integer id = GetTrkId( trk )
    local trigger t = CreateTrigger()
    set Trks_Trig[ id ] = t
    if IsHit then
        call TriggerRegisterTrackableHitEvent( t, trk )
    else
        call TriggerRegisterTrackableTrackEvent( t, trk )
    endif
    set Trks_Action[ id ] = TriggerAddAction( t, action )
    set t = null
    return trk
endfunction[/codes]
    恩~~~~最后本人发现貌似这种方法不可行,很容易导致数据覆盖,所以就没有用。
    问题就出在GetTrkId的/4上了。
    想知道为什么吗?(*^__^*) 嘻嘻……自己想想吧
    想出来的奖励:
    恭喜你,能想出这道题的人,凭你的能力你在GA应该能拿到12+声望了~~~~
    答案在下面:


答案:
很简单,举个例子来说,你先运行InitTrksHandleNumber(),然后创建了2个别的handle,接着又用CreateTrackableXL()创建了一个trackable<trkA>及他的附属trigger,triggeraction,event。再把那两个handle中后面创建的那个删了。然后你再用CreateTrackableXL()创建了一个trackable<trkB>及他的附属trigger,triggeraction,event。恩…………这时你就会发现有了数据覆盖了……………………
TrkA的地址-Trks_InitHandleNumber = 2
TrkB的地址-Trks_InitHandleNumber = 1
2/4 = 1
1/4 = 1
一个实例:
[codes=jass]globals
    integer Trks_InitHandleNumber = 0
    trigger array Trks_Trig
    triggeraction array Trks_Action
    real array Trks_Facing
endglobals

function H2I takes handle h returns integer
    return h
    return 0
endfunction

function InitTrksHandleNumber takes nothing returns nothing
    local location loc = Location( 0, 0 )
    set Trks_InitHandleNumber = H2I( loc )
    call RemoveLocation( loc )
    set loc = null
endfunction
//此函数必须要在地图初始化(且在其他一切创建Handle动作之前)时运行。

function GetTrkId takes trackable trk returns integer
    return ( H2I( trk ) - Trks_InitHandleNumber ) / 4
    //注意,这里/4是因为创建一个trackable还要创建trigger,triggeraction,event总共4个handle,所以这里/4就可以了。
endfunction

function CreateTrackableXL takes string trackableModelPath, real x, real y, real facing, code action, boolean IsHit returns trackable
    local trackable trk = CreateTrackable( trackableModelPath, x, y, facing )
    local integer id = GetTrkId( trk )
    local trigger t = CreateTrigger()
    set Trks_Trig[ id ] = t
    if IsHit then
        call TriggerRegisterTrackableHitEvent( t, trk )
    else
        call TriggerRegisterTrackableTrackEvent( t, trk )
    endif
    set Trks_Action[ id ] = TriggerAddAction( t, action )
    set Trks_Facing[ id ] = facing
    set t = null
    return trk
endfunction

function main takes nothing returns nothing
    local trackable trkA = null
    local trackable trkB = null
    local location temp = null
    call InitTrksHandleNumber()
    call CreateTrackable( , 0, 0, 0 )
    set temp = Location( 0, 0 )
    set trkA = CreateTrackableXL( , 30, 60, 22, null, true )
    call BJDebugMsg(  Before create, TrkA.Facing =  + R2S( Trks_Facing[ GetTrkId( trkA ) ] ) )
    call RemoveLocation( temp )
    set temp = null
    set trkB = CreateTrackableXL( , 30, 60, 11, null, true )
    call BJDebugMsg(  After created, TrkA.Facing =  + R2S( Trks_Facing[ GetTrkId( trkA ) ] ) )
    call BJDebugMsg(  And TrkB.Facing =  + R2S( Trks_Facing[ GetTrkId( trkB ) ] ) )
endfunction
//游戏从这里开始(假定)[/codes]
理论输出:
Before create, TrkA.Facing = 22.000
After created, TrkA.Facing = 11.000
And TrkB.Facing = 11.000


            (10) **** 用unit+全局变量数组+响应单位死亡事件替代Timer ****:


    恩………………想来肯定有很多很多WEer们使用Timer吧。用我的方法虽然不会涉及到string。但是………………整个游戏的handle最高峰的数量没准就>8192呢?那我们前面介绍的方法岂不是很危险………………
    嗯…………现在本人又想出了一个办法,但是…………鱼与熊掌不可兼得……我这个方法不仅效率比Timer低,还有可能与别的什么响应单位XX事件的动作混合。
    原理很简单,unit不是有个生命周期吗?把timer换成 unit(控制时间) + 全局变量数组(储存数据) + 任意单位死亡的trigger(执行动作) 不就行了?当然既然说到了全局变量数组,就没有理由不用VJ的struct…………………………
    实例讲解:XX时间后,创建TimerData.unit_num个TimerData.unit_id在(TimerData.x,TimerData.y)
    首先,是在自定义脚本代码中的代码:
[codes=jass]struct TimerData
    integer unit_id
    integer unit_num
    integer player_id
    real x
    real y
   
    static method create takes integer id. integer num, real x, real y, integer pid returns TimerData
        local TimerData temp = TimerData.allocate()
        set temp.unit_id = id
        set temp.unit_num = num
        set temp.x = x
        set temp.y = y
        set temp.player_id = pid
        return temp
    endmethod
   
    method onDestroy takes nothing returns nothing
        set this.unit_id = 0
        set this.unit_num = 0
        set this.x = 0.0
        set this.y = 0.0
        set this.player_id = 0
        call this.destroy()
    endmethod
   
endstruct

function StartTimer takes real time returns nothing
    local unit u = CreateUnit( Player( 13 ), 'hZZZ', 0.0, 0.0, 0.0 )
    local TimerData TD = TimerData.create( 'hZZO', 5, 0.0, 0.0, 0 )
    call ShowUnit( u, false )
    call UnitApplyTimedLife( u, 'BHwe', time )
    call SetUnitUserData( u, TD )
    set u = null
endfunction[/codes]
    然后是在响应单位死亡的trigger里的代码:
[codes=jass]function Trig________________u_Conditions takes nothing returns boolean
    return GetUnitTypeId(GetTriggerUnit()) == 'hfoo'
endfunction

function Trig________________u_Actions takes nothing returns nothing
    local unit u = GetTriggerUnit()
    local TimerData TD = GetUnitUserData( u )
    local integer i = 0
    loop
        set i = i + 1
        exitwhen i > TD.unit_num
        set bj_lastCreatedUnit = CreateUnit( Player( TD.player_id ), TD.unit_id, TD.x, TD.y, 0.0 )
    endloop
    call TD.OnDestroy()
    call RemoveUnit( u )
    set u = null
endfunction

//===========================================================================
function InitTrig________________u takes nothing returns nothing
    set gg_trg________________u = CreateTrigger(  )
    call TriggerRegisterPlayerUnitEventSimple( gg_trg________________u, Player(13), EVENT_PLAYER_UNIT_DEATH )
    call TriggerAddCondition( gg_trg________________u, Condition( function Trig________________u_Conditions ) )
    call TriggerAddAction( gg_trg________________u, function Trig________________u_Actions )
endfunction[/codes]
    最后补充一下,这种东西只适合于不循环的Timer。虽然可循环的Timer也可用这个做,但是效率会大大降低………………而且像延时复活小兵的RPG的系统。一般来说。其实用一个不断循环的Timer即可(理论:每0.5秒减少地图中所有已死亡单位的一个值0.5,然后如果这个值小于0,则复活单位)


            (11) 几个普通点的节省string的小技巧:


    嗯~~总之~~………………我不得不承认………………除非是在GameCache+H2S用得极为习惯的情况下,积累了大量的魔兽经验,量变引发质变,才有可能真正理解我说的是什么意思(其实就是对GameCache+H2S发牢骚)。
    下面几个小技巧则是基本没什么技术含量的。但是会比较管用。
                Ⅰ【Real2Integer2String】
    这个想理解也很简单,就是用I2S(R2I(XXXX))替代R2S(XXXX)。这是为什么呢?
    主要是因为R2S会把实数的小数点后3位也转换成string。举个最简单的例子:转换 单位的生命百分比 为字符串。则可能有:
    “0.000”~“100.000”共100001个string会出现。
    但是如果用:转换 转换 单位的生命百分比 为整数 为字符串。则可能有:
    “0”~“100”共101个个string会出现。
                Ⅱ【分离TextTag法】
    我做【全屏装备栏】时发现的一个技巧,就是将要显示包含数字的文字分离开,这样前面的文字算一个string,后面的数值再单算string,而不是前面的文字、后面数值也算string、结果两者连结起来还要算string。
    比如这样一个东东:
[codes=jass]function ShowStr takes nothing returns nothing
    local integer value = GetRandomInt( 1, 100 )
    local texttag tt = CreateTextTag()
    call SetTextTagText( tt, 装备增加力量: + I2S( value ), 0.023 )
    call SetTextTagPos( tt, 0.0, 0.0, 10.0 )
    call SetTextTagColor( tt, 200, 100, 122, 255 )
    set tt = null
endfunction[/codes]
    为了避免涉及更多的string。我们可以这么做:
[codes=jass]function ShowStr takes nothing returns nothing
    local integer value = GetRandomInt( 1, 100 )
    local texttag tt_1 = CreateTextTag()
    local texttag tt_2 = CreateTextTag()
    call SetTextTagText( tt_1, 装备增加力量:, 0.023 )
    call SetTextTagPos( tt_1, 0.0, 0.0, 10.0 )
    call SetTextTagColor( tt_1, 200, 100, 122, 255 )
   
    call SetTextTagText( tt_2, I2S( value ), 0.023 )
    call SetTextTagPos( tt_2, 10.0, 0.0, 10.0 )
    call SetTextTagColor( tt_2, 200, 100, 122, 255 )
   
    set tt_1 = null
    set tt_2 = null
endfunction[/codes]
    嗯~~~可能每次Show一下Str可能都要创建两个texttag。所以我们简化一下:
[codes=jass]globals
    constant texttag Str_tt = CreateTextTag()
endglobals

function Init takes nothing returns nothing
    call SetTextTagText( Str_tt, 装备增加力量:, 0.023 )
    call SetTextTagPos( Str_tt, 0.0, 0.0, 10.0 )
    call SetTextTagColor( Str_tt, 200, 100, 122, 255 )
    call SetTextTagVisibility( Str_tt, false )
endfunction

function ShowStr takes nothing returns nothing
    local integer value = GetRandomInt( 1, 100 )
    local texttag tt_2 = CreateTextTag()
    call SetTextTagVisibility( Str_tt, true )

   
    call SetTextTagText( tt_2, I2S( value ), 0.023 )
    call SetTextTagPos( tt_2, 10.0, 0.0, 10.0 )
    call SetTextTagColor( tt_2, 200, 100, 122, 255 )

    set tt_2 = null
endfunction[/codes]
    好了~~~~泄露就此告一段落。现在我们再来看看怎样让我们的效率提高些吧~~~








二.提高效率
    编了很多系统,也积累了不少提高效率的经验。发现很多提高效率的方法,但大多数都是视情况而定,才能提高效率。真正的不应该做的事情很少。而严禁使用的更少………………
    其实也有人发不过关于提高效率的贴,但是那贴只介绍了不涉及到纯Jass/编程领域的优化方法。


            (1) 严禁:递归:


    恩………………在我看来,使用递归并不是什么可怕的事情,但是,在正规地图甚至于发布在网上的演示/系统,如果使用递归(尤其是用了递归还不告诉别人的),这就有点…………。其实想不用递归也很简单。使用loop当函数体,全局变量当函数参数即可。诸如:
[codes=jass]function Loop takes unit u, real v returns real
    local real oldlife = GetUnitState( u, UNIT_STATE_LIFE )
    local real newlife = oldlife - v
    if newlife < 0.0 then
        return GetUnitX( u )
    else
        call SetUnitX( u, GetUnitX( u ) + v )
    endif
    return Loop( u, v )
endfunction

function main takes nothing returns nothing
    call BJDebugMsg( Unit.X =  + R2S( Loop( CreateUnit( Player( 0 ), 'hfoo', 0.0, 0.0, 0.0 ), 10.0 ) ) )
endfunction[/codes]
    完全可以这样,不使用递归:
[codes=jass]globals
    real g_x
    boolean g_b
endglobals

function Loop takes unit u, real v returns nothing
    local real oldlife = GetUnitState( u, UNIT_STATE_LIFE )
    local real newlife = oldlife - v
    if newlife < 0.0 then
        set g_b = true
    else
        call SetUnitX( u, GetUnitX( u ) + v )
    endif
    set g_x = GetUnitX( u )
endfunction

function main takes nothing returns nothing
    local unit u = CreateUnit( Player( 0 ), 'hfoo', 0.0, 0.0, 0.0 )
    loop
        call Loop( u, 10.0 )
        exitwhen g_b
    endloop
    call BJDebugMsg( Unit.X =  + R2S( g_x ) )
    set g_b = false
    set g_x = 0.0
    set u = null
endfunction[/codes]
    思想就是利用loop,把原递归函数需要传递给自己的参数/返回的值变成全局变量,每一次调用函数都将数据写入全局变量,覆盖上一次的数据,再让下一次调用使用这些数据即可。注意:在“伪”递归完成后,请将所有的有关于这个“伪”递归的变量清空,否则会引起Bug。
    递归的可怕性你可以去算法区找关于雪花函数的贴,看看你的电脑能挺过多少次递归。
    当然,不能用这个方法替代递归的,那就放弃做这个作品吧,毕竟使用递归在正规游戏中是会引起lag的(大多数玩家最讨厌的就是这个情况)。


            (2) Timer三节:


    1.N个Timer 2 一个 Timer
    这个方法其实很简单,就是将多个Timer替换成一个不断循环的Timer。
    下面是复活死尸系统中一小段截取:
[codes=jass]function B takes nothing returns nothing
    local timer ti = GetExpiredTimer()
    local unit u = GHU( ti, TheUnit )
    call CreateUnit( GetOwningPlayer(u), GetUnitTypeId(u), GetUnitX(u), GetUnitY(u), GetUnitFacing(u) )
    set u = null
    call PauseTimer(ti)
    call DestroyTimer(ti)
    set ti = null
endfunction

function A takes nothing returns nothing
    local timer ti = CreateTimer()
    call SHH( ti, TheUnit, GetTriggerUnit() )
    call TimerStart( ti, 10.0, false, function B )
    set ti = null
endfunction[/codes]
    我们可以改成:
[codes=jass]globals
    timer DeathTimer = CreateTimer()
    group DeathGroup = CreateGroup()
endglobals

function C takes nothing returns nothing
    local unit u = GetEnumUnit()
    call SetUnitUserData( GetTriggerUnit(), GetUnitUserData( u ) - 1 )
    if GetUnitUserData( u ) <= 0 then
        call CreateUnit( GetOwningPlayer(u), GetUnitTypeId(u), GetUnitX(u), GetUnitY(u), GetUnitFacing(u) )
        call GroupRemoveUnit( DeathGroup, u )
        call RemoveUnit( u )
    endif
    set u = null
endfunction

function B takes nothing returns nothing
    call ForGroup( DeathGroup, function C )
endfunction

function Init takes nothing returns nothing
    call TimerStart( DeathTimer, 1.0 ,true, function B )
endfunction

function A takes nothing returns nothing
    call SetUnitUserData( GetTriggerUnit(), 10 )
    call GroupAddUnit( DeathGroup, GetTriggerUnit() )
endfunction[/codes]
    这样的话,在地图中死亡单位数量特别多的时候,这个算法会比刚刚的算法的效率要好很多。

    2.尽量少用循环间隔小于0.01的循环性Timer
    很简单,如题,避免使用这种Timer就可以,尽量把他的循环间隔调的大一点,比如:
[codes=jass]call TimerStart( ti, 0.02, true, function XXXXXX )[/codes]
    明显比下面的效率高了2倍。
[codes=jass]call TimerStart( ti, 0.01, true, function XXXXXX )[/codes]

    3.Timer不用时就暂停他
    这其实是直觉的判断………………(我一直在怀疑魔兽关于时间控制很可能是共用一个0.001循环的timer,也就是不管创建出多少不执行函数的Timer,效率不会增加多少)我是觉得这会影响到效率。
    废话少说,给出实例:
[codes=jass]globals
    timer DeathTimer = CreateTimer()
    group DeathGroup = CreateGroup()
endglobals

function C takes nothing returns nothing
    local unit u = GetEnumUnit()
    call SetUnitUserData( GetTriggerUnit(), GetUnitUserData( u ) - 1 )
    if GetUnitUserData( u ) <= 0 then
        call CreateUnit( GetOwningPlayer(u), GetUnitTypeId(u), GetUnitX(u), GetUnitY(u), GetUnitFacing(u) )
        call GroupRemoveUnit( DeathGroup, u )
        call RemoveUnit( u )
    endif
    set u = null
endfunction

function B takes nothing returns nothing
    if ( FirstOfGroup(DeathGroup) == null ) then
        call PauseTimer( DeathTimer )
        return
    endif
    call ForGroup( DeathGroup, function C )
endfunction

function Init takes nothing returns nothing
    call TimerStart( DeathTimer, 1.0 ,true, function B )
endfunction

function A takes nothing returns nothing
    if ( FirstOfGroup(DeathGroup) == null ) then
        call Init()
    endif
    call SetUnitUserData( GetTriggerUnit(), 10 )
    call GroupAddUnit( DeathGroup, GetTriggerUnit() )
endfunction[/codes]
    (就是上面的改进版本罢了)


            (3) 触发器动作写条件里去:


    如题,相信会Jass的,就知道这一点吧…………………………


            (4) 抛弃GameCache的储存方式,使用全局变量数组/struct:


    ………………不得不说的,事实证明,GameCache储存数据的速度确实没有全局变量数组/struct快,所以说,快使用【全局变量数组/struct】呼呼哈咦!


            (5) 特殊的函数替换:


    很简单,比如:SetUnitPosition换成SetUnitX+SetUnitY…………………………效率会很高。


            (6) 使用宏:


    一个VJASS的特权:宏。
    一般来说,一个你肯定不会超过一行的函数,都改成宏写,效率会有些许提升。
    比如:
[codes=jass]function H2S takes handle h returns string
    return I2S(H2I(h))
endfunction[/codes]
    你就可以写成:
[codes=jass]//! define H2S(h) I2S(H2I(h))[/codes]
    这主要是提高了一次调用函数的效率


        好了……………………总算写完了………………万里长征总算结束了…………………


    最后声明:可能会有错的地方,也可能有跟大家思想有冲突的地方。如果大家有异议,可以在下面回帖告诉我,我会尽量改正错误/与你进行深度探讨。
    最后请求:…………如果可以的话…………把这贴设成精华吧…………要不然我就要泪奔+跳楼了&_& &_&
    5,700多中文字啊…………………………还不算Jass呢&_&

【环保大师】教你“环保”(排泄+提高效率+一点小建议).txt

32 KB, 下载次数: 92

文字版

恶声恶气 该用户已被删除
发表于 2009-1-7 13:06:48 | 显示全部楼层
提示: 作者被禁止或删除 内容自动屏蔽
回复 支持 反对

使用道具 举报

发表于 2009-1-7 13:08:53 | 显示全部楼层
很长....
帮顶个...
好像不适合新手学习
投诉LS灌水+抢我SF
回复 支持 反对

使用道具 举报

发表于 2009-1-7 13:29:35 | 显示全部楼层
引用楼主血戮魔动冰于2009-01-07 12:49发表的 【环保大师】教你“环保”(排泄+提高效率+一点小建议) :
这是我改进后的H2S。原理是用需要转换的Handle的地址再减去地图初始化时创建的Location的地址再I2S。

.......

你确定这样不会死掉?~
每种数据占的handle位都不一样的说
http://www.islga.org/bbs/read.php?tid=362
回复 支持 反对

使用道具 举报

 楼主| 发表于 2009-1-7 13:34:32 | 显示全部楼层
………………………………
不管Handle址怎么变。他都具有唯一性。
所以不管怎样,我的H2S是应该没有BUG的(I2S支持负数哦!)
当然,H2I减Handle的址就不行了
PS:用lighting不就行了
已改。
回复 支持 反对

使用道具 举报

发表于 2009-1-7 13:39:22 | 显示全部楼层
地毯。。。关于string的泄露第一次听说。。
RemoveLocation之后应该不许设null了吧?
string泄露对游戏影响很小吧?否则GameCache就不会那么流行了   每秒钟生成20个string 每个string平均20个字符每分钟2400字节,游戏一个小时8640000字节的内存占用也就是8M
回复 支持 反对

使用道具 举报

发表于 2009-1-7 13:44:52 | 显示全部楼层
string泄露不严重的,而且大多是可以重复利用的,并且就算string很多也只影响string的相关函数,对游戏其他方面没有影响。

RemoveLocation之后需要设null
回复 支持 反对

使用道具 举报

发表于 2009-1-7 14:00:02 | 显示全部楼层
写完老啊~占位。。。参观学习。。。
回复 支持 反对

使用道具 举报

发表于 2009-1-7 14:02:26 | 显示全部楼层
貌似高亮又乱码了?
RemoveLocation之后的set null参见句柄与变量的区分分析
http://www.islga.org/bbs/read.php?tid=15486 的14楼
或者更进一步的直接看这里http://www.islga.org/bbs/read.php?tid=10851

string泄露对游戏影响的确不大,主要因为string句柄占用独立的内存空间;即使到极端的情况,比如上百万的string造成的效率低下,如果这时没有直接对string的操作(加入新字符串或引用已有字符串),游戏都可以正常进行,但只要涉及上述操作症状就会表现出来。
还有string是录入后就可重复使用的啦(hash索引),除非是针对的做实验,每秒钟生成上百个新字符的情况在正常地图里是很罕见的
string句柄http://www.islga.org/bbs/read.php?tid=2520

存疑的地方:
游戏中的string的句柄是独立的,比如在pld和ai脚本里就不能引用
在上述地方用GetStoredString的话,得不到储存的string。
但是作为参数的string是能够正常工作的。。
比如GetStoredInteger/boolean/real都没问题。。。这又能说明什么呢
回复 支持 反对

使用道具 举报

发表于 2009-1-7 14:16:46 | 显示全部楼层
注1:魔兽给string分配内存的方式是:已使用的string,达到了25个,就再申请25个string的内存,直到这次分配的内存被新的25个string充填,就再次申请25个内存(步骤循环)………………
以前看个教程(老狼的?)说是256一换。。。?

handle值还是有一点点重复的么~

3楼那篇感觉ms和现在的机制不同了。。。

还素不清楚全局变量的setnull。。。
回复 支持 反对

使用道具 举报

发表于 2009-1-7 14:27:37 | 显示全部楼层
lz的25分配只是举例而已(或者漏打了个6)

其实是稍微错解了lz的用意,lz把原7位的handle剪成小些的数字,在转为string的时候碰上常见的已有字符的概率也大些
回复 支持 反对

使用道具 举报

 楼主| 发表于 2009-1-7 15:28:04 | 显示全部楼层
是这样的。kook真贴心。
PS:乱码修复ing
回复 支持 反对

使用道具 举报

发表于 2009-1-7 15:28:40 | 显示全部楼层
问个问题,所有的handle都是7位的吗?要是那样的话把所有的数据全绑定在一个integer上不就行了么?这个问题我想了很久,虽然感觉上是的.......
回复 支持 反对

使用道具 举报

 楼主| 发表于 2009-1-7 15:30:16 | 显示全部楼层
引用第12楼louter于2009-01-07 15:28发表的  :
问个问题,所有的handle都是7位的吗?要是那样的话把所有的数据全绑定在一个integer上不就行了么?这个问题我想了很久,虽然感觉上是的.......
……………………
看不懂,能再解释一下吗?
回复 支持 反对

使用道具 举报

 楼主| 发表于 2009-1-7 15:32:17 | 显示全部楼层
乱码修复完毕!!
回复 支持 反对

使用道具 举报

发表于 2009-1-7 15:33:08 | 显示全部楼层
就是把所有的handle H2I 之后都是一个7位数字吗?要是的话把所有的数据全都写在一个integer上,不是很好么
回复 支持 反对

使用道具 举报

 楼主| 发表于 2009-1-7 15:35:41 | 显示全部楼层
…………………………………………
能再详细点吗?
(感觉是没多说什么)
回复 支持 反对

使用道具 举报

发表于 2009-1-7 15:42:14 | 显示全部楼层
.......

比如我local了一个unit u,H2I之后是3100257,再local 一个 group,H2I之后是6107825(数字当然是随便写的),然后设定一个全局integer Int = 31002576107825,用的时候再
[jass]
local unit u = I2U(Int / 10^7)
local group g = I2G( Int - (Int / 10^7) * 10^7 )
[/jass]
之类的。

当然是在都是7位的前提下。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2009-1-7 15:54:09 | 显示全部楼层
……………………………………………………
31002576107825
integer有这么强悍的存储功能吗?
回复 支持 反对

使用道具 举报

发表于 2009-1-7 16:47:35 | 显示全部楼层
首先让我们来了解一下String对象。
War3中为String独立开辟一块内存区,初始分配的string地址为0-246,0=null 1="",2一般为地图名称,之后是各个用到的string。未使用的地址被赋值为"(null)"。
当分配的地址不够用时系统自动再分配256位地址,并全部赋值为"(null)"。
所有触发脚本中出现的字符串都会被记录,并无法删除。
相同的字符串不会被再次记录。
例:
设string内存地址1-25已被使用,那么

local string s
set s="a"         //此时地址26被赋值为"a"
set s="b"+"c"     //27="b" 28="c" 29="bc"
set s="bc"        //"bc"已经存在,故不会再被记录

我们可以很方便的通过return bug设计i2s和s2i函数来观察其结果,当运行以上代码后
i2s(27)="b"
i2s(200)="(null)"
而如果想要运行 i2s(300) 则游戏会直接弹出,因为该内存地址还未被分配;如果运行以下代码之后就不会发生弹出事件,因为新的内存区域已经被分配,此时i2s(300)="(null)"

local integer i=0
local string s
loop
set s=I2S(i)
set i=i+1
exitwhen i>250
endloop

好吧,讲这么多应该足够了。现在来看看Cache储存方式2的问题吧。问题就在"ThunderClap"+I2S(h2i(u))上面。
I2S(h2i(u))会产生若干个string对象,而"ThunderClap"+又会产生另外的若干个string对象。
并且我们还会有很多诸如"IceBolt"+I2S(h2i(u)) "Blizzard"+I2S(h2i(tm))之类的东西,于是由此而产生的string数量是很大的,这就是方式2的弊端。此时我们就要重新去度量2者的价值了。

方式1:
使用该方式缓存结构无法被删除,但是游戏缓存项所占内存是很小的,100万条Integer类缓存项才占用60多M内存,使用FlushStoredInteger也能大量减少内存占用。并且对于Timer Trigger等一些对象,即使用该方式存储数据,我们也可以用FlushStoredMission来直接删除缓存类,因为这些东西是不会在两个不同的技能或系统中使用的。所以我们也可以将数据更多的绑定在Timer以及Trigger上,而不是Unit上。
而且很重要的一点,缓存项数量的增加,并不会影响到缓存的操作速度,更不会影响到游戏速度。

方式2:
这种存储方式条理比较清晰,并且在清空数据时会比较方便,但是会产生数量很大的string对象,在测试中创建100万个7位数字转换的string需要占用200多M空间,并且很难让人接受的是,当string数量增加后,对string的操作效率会急剧下降,即使只是对string变量的简单赋值而已。不过值得庆幸的是,string是独自使用一块内存区域的,所以string对象的大量存在对string以外的其它操作并不会有影响。


老狼的经典教程
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

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

GMT+8, 2019-10-21 04:13 , Processed in 0.353567 second(s), 29 queries , Gzip On.

Powered by Discuz! X3.4

© 2001-2017 Comsenz Inc.

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