• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[Solved] optimizing code; T32, structs...

Status
Not open for further replies.
Level 3
Joined
Jan 7, 2010
Messages
37
**SOLVED**


I'm currently figuring out how T32 works and have trouble with optimizing my code.

I wrote a simple sequence ability where one action is done after the other always with a few milliseconds delay, like this:

JASS:
call Action => start Timer0
Timer0 expires => call DoAttack => start Timer1
Timer1 expires => call StartProjectile; OnFinish => start Timer2
Timer2 expires => call DoRetreat (end)

The ability I have works fine. Just the coding is meh...

I used this as a basis:

JASS:
scope test initializer dotest
    private struct teststruct
        private integer endTick
        private method periodic takes nothing returns nothing
            call BJDebugMsg(I2S(this))
            
            if this.endTick == T32_Tick then // no more this.ticksLeft stuff and incrementing.
                call this.stopPeriodic() // never follow with a .startPeriodic() call
                call this.destroy()      // of any sort (when in the .periodic method).
            endif
        endmethod
        implement T32x
        static method create takes real duration returns thistype
            local thistype this = thistype.allocate()
            set this.endTick = T32_Tick + R2I(duration / T32_PERIOD)
            return this
        endmethod
    endstruct
    private function dotest takes nothing returns nothing
        // Creates structs which display their integer id 32 times a second for x seconds.
        call teststruct.create(3.0).startPeriodic()
        call teststruct.create(5.0).startPeriodic()
    endfunction
endscope

and got this (simplified):

JASS:
library
	//...
	private function DoRetreat takes BSMove s returns nothing
		//...
	endfunction
	
	//=================================================
	//		TIMER 2
	//=================================================
	private struct RangedAttack_Timer2
		private method periodic takes nothing returns nothing 
				//...
				if this.endTick == T32_Tick then
					call DoRetreat(bs)
				//...
		endmethod
	endstruct
	//=================================================
	
	private function OnFinish takes projectile p returns nothing
		call RangedAttack_Timer2.create(TIMER_DUR2, s).startPeriodic()
	endfunction 

	private function StartProjectile takes BSMove s returns nothing
		call OnFinish(...)
	endfunction
	
	//=================================================
	//		TIMER 1
	//=================================================
	private struct RangedAttack_Timer1
		private method periodic takes nothing returns nothing 
			//...
			if this.endTick == T32_Tick then
				call StartProjectile(bs)
			//...
		endmethod
	endstruct
    //=================================================
	
    private function DoAttack takes BSMove s returns nothing
        call RangedAttack_Timer1.create(TIMER_DUR1, s).startPeriodic()
    endfunction
	
	//=================================================
	//		TIMER 0
	//=================================================
    private struct RangedAttack_Timer0
        private method periodic takes nothing returns nothing  
				//...
				if this.endTick == T32_Tick then
					call DoAttack(bs)
				//...
        endmethod
    endstruct
	//=================================================

    function Action takes BSMove s returns nothing
        call RangedAttack_Timer0.create(TIMER_DUR0, s).startPeriodic()
    endfunction
endlibrary

So how could I do this better? Like using only one Timer struct...

**UPDATE**
for completion the complete trigger
JASS:
library AttackRange initializer Init requires MovementSystem, SoundTools, UnitDataList

    globals
        private constant real SPEED     = 3000.
        private constant real TIMER_DUR1 = 0.1  // attack delay
        private constant real TIMER_DUR2 = 0.2
        private constant string SOUND_CHARGE = "Abilities\\Weapons\\CannonTowerMissile\\CannonTowerMissileLaunch1.wav"
        
        private location loc = Location(0.00,0.00)
    endglobals
    
    //***********************************************************************
    
    private function DoRetreat takes BSMove s returns nothing
        call MoveReturn(s)
    endfunction

    private struct RangedAttack_Timer2
        private integer endTick
        private BSMove bs
     
        private method periodic takes nothing returns nothing            
            if this.endTick == T32_Tick then // no more this.ticksLeft stuff and incrementing.
                call DoRetreat(bs)
                set this.bs = 0
                call this.stopPeriodic() // never follow with a .startPeriodic() call
                call this.destroy()      // of any sort (when in the .periodic method).
            endif
        endmethod
        
        implement T32x
        
        static method create takes real duration, BSMove s returns thistype
        //static method create takes real duration returns thistype
            local thistype this = thistype.allocate()
            set this.bs = s
            set this.endTick = T32_Tick + R2I(duration / T32_PERIOD)
            return this
        endmethod
    endstruct   
    
    

    private function OnFinish takes projectile p returns nothing
    local BSMove s = BSM_Array[GetUnitId(p.sourceUnit)]
        call SetUnitTimeScale(s.Unit, .5)
        call RangedAttack_Timer2.create(TIMER_DUR2, s).startPeriodic() 
        call p.terminate()
        set s = 0
    endfunction 
    
    
    private function OnLoop takes projectile p returns nothing
    endfunction 

    private function StartProjectile takes BSMove s returns nothing
        local unit       u = s.Unit
        local real       ux = GetUnitX(u)
        local real       uy = GetUnitY(u)
        local real       tarx = s.TargetX
        local real       tary = s.TargetY
        local real       ang  = Atan2((tary - uy),(tarx - ux))
        local real       dist = DistanceBetweenPoints(Location(tarx,tary),Location(ux,uy))
        local projectile proj = 0
        
        call MoveLocation(loc,ux,uy)
        //---------------------------------------------------------
        //set proj = projectile.create(ux,uy,GetUnitFlyHeight(u) + 150.00 + GetLocationZ(loc),ang)
        set proj = projectile.create(ux,uy,GetUnitFlyHeight(u) + GetProjHeight(s.Unit),ang)
        
        set proj.sourceUnit          = s.Unit
        set proj.owningPlayer        = GetOwningPlayer(u)
        set proj.effectPath          = GetProjEffect(s.Unit)   // Caster Projectile
        //set proj.damageDealt         = 0.
        set proj.scaleSize           = 1.
        set proj.zOffset             = 50.00        // Unit Projectile Offset?
        set proj.timedLife           = dist / SPEED   // Unit Projectile Speed?

        set proj.allowExpiration     = true
        set proj.allowDeathSfx       = true
        set proj.allowArcAnimReset   = true
        set proj.allowUnitCollisions = false
        set proj.allowDestCollisions = false
        
        set proj.onExpire            = OnFinish
        //set proj.onFinish            = OnFinish
        set proj.onLoop              = OnLoop

        call MoveLocation(loc,tarx,tary)
        
        call proj.projectNormal(tarx,tary,50.00 + GetLocationZ(loc),SPEED)
        //---------------------------------------------------------
        set u = null
    endfunction
        
        
    private struct RangedAttack_Timer1
        private integer endTick
        private BSMove bs
     
        private method periodic takes nothing returns nothing        
            if this.endTick == T32_Tick then // no more this.ticksLeft stuff and incrementing.
                call StartProjectile(bs)
                set this.bs = 0
                call this.stopPeriodic() // never follow with a .startPeriodic() call
                call this.destroy()      // of any sort (when in the .periodic method).
            endif
        endmethod
        
        implement T32x
        
        static method create takes real duration, BSMove s returns thistype
        //static method create takes real duration returns thistype
            local thistype this = thistype.allocate()
            set this.bs = s
            set this.endTick = T32_Tick + R2I(duration / T32_PERIOD)
            return this
        endmethod
    endstruct
    
    private function DoAttack takes BSMove s returns nothing
    local unit u = s.Unit
        call IssueImmediateOrder(u, "stop")
        call SetUnitAnimation(u, "stand")
        call SetUnitTimeScale(u, 2.)
        call SetUnitAnimation(u, "attack")
        call QueueUnitAnimation(u, "ready")
        call RangedAttack_Timer1.create(TIMER_DUR1, s).startPeriodic()
        //-------------------------------------------
        set u = null
    endfunction

    private struct RangedAttack_Timer0
        private integer endTick
        private BSMove bs
     
        private method periodic takes nothing returns nothing            
            if this.endTick == T32_Tick then // no more this.ticksLeft stuff and incrementing.
                call DoAttack(bs)
                set this.bs = 0
                call this.stopPeriodic() // never follow with a .startPeriodic() call
                call this.destroy()      // of any sort (when in the .periodic method).
            endif
        endmethod
        
        implement T32x
        
        static method create takes BSMove s returns thistype
        //static method create takes real duration returns thistype
        local real duration = s.Duration + 0.1
        local thistype this = thistype.allocate()
            set this.bs = s
            set this.endTick = T32_Tick + R2I(duration / T32_PERIOD)
            return this
        endmethod
    endstruct
    

    function abilRangeAttack takes BSMove s returns nothing
        call SimpleSoundOnUnit(SOUND_CHARGE, s.Unit)
        call MoveForward(s)
        call RangedAttack_Timer0.create(s).startPeriodic()
    endfunction

    //===========================================================================
    private function Init takes nothing returns nothing
    local trigger trg = CreateTrigger(  )
    endfunction
endlibrary
 
Last edited by a moderator:
With T32, *technically* you can do multiple timings per struct, but it is a little complicated. You would need to make use of "phases", or "stages". Basically, your struct would be set-up like this:
JASS:
struct Example
    integer phase
    real endTick

    method doFirstPhaseStuff takes nothing returns nothing
    endmethod
  
    method doSecondPhaseStuff takes nothing returns nothing
        // projectile movement or whatever
    endmethod

    method doThirdPhaseStuff takes nothing returns nothing
    endmethod

    private method periodic takes nothing returns nothing 
        if phase == 1 then
            call this.doFirstPhaseStuff()
        elseif phase == 2 then
            call this.doSecondPhaseStuff()
        elseif phase == 3 then
            call this.doThirdPhaseStuff()
        endif
    endmethod
    implement T32x

    static method create takes real duration returns thistype
        local thistype this = thistype.allocate()
        set this.endTick = T32_Tick + R2I(duration / T32_PERIOD)
        set this.phase = 1 // start off at phase 1
        return this
    endmethod
endstruct

Now, how does this work? You start off at phase 1, and it will evaluate the function for that phase (doFirstPhaseStuff). The code for doFirstPhaseStuff should have an endpoint (e.g. T32_Tick == this.endTick), and in that end point, it should set the new endTick and set a new phase:
JASS:
method doFirstPhaseStuff takes nothing returns nothing
    // periodic actions
    if T32_Tick == this.endTick then
        /* update the tick to be 5 seconds from now */
        set this.endTick = T32_Tick + R2I(5 / T32_PERIOD)
        /* start phase 2 */
        set this.phase = 2
    endif
endmethod

As such, when periodic is next evaluated, it'll do phase 2 instead, and call those actions. In phase 2, you'd also have some end point, and shift it to phase 3. Finally, in phase 3, you'll have an endpoint, but that will be the point where you call .stopPeriodic() and destroy the struct instance.

------

Overall, it is a pretty bizarre method, but it works. I haven't really seen it done before tbh. I usually just ended up using multiple structs, but that is kind of wasteful. I personally kind of like this method.

But if you don't really agree with it, then you can't go wrong with TimerUtils. With it, you can just directly call a timer onto a method, such as this:
JASS:
struct Example
    static method anyStaticMethod takes nothing returns nothing
        local thistype this = GetTimerData(GetExpiredTimer())
        if /* some end condition */ then
            call ReleaseTimer(GetExpiredTimer())
            call this.destroy()
        endif
    endmethod

    static method create takes nothing returns thistype
        local thistype this = thistype.allocate()
        call TimerStart(NewTimerEx(this), 0.03125, true, function thistype.anyStaticMethod)
    endmethod
endstruct

And if you wanted to use multiple, then you would just start another timer and pass the same instance to it (don't destroy it until you've finished all your timer actions).
 
Level 3
Joined
Jan 7, 2010
Messages
37
@PurgeandFire thanks a lot! the phase method was exactly what I was looking for :O but I can't give +rep to you :'(

@Dalvengyr yeah, I know that I leak there :D This is still work in progress and I was too lazy to fix it yet ^^. CreateTrigger() also still from a template :D problem is already solved!
 
Status
Not open for further replies.
Top