[vJASS] edit: Seems to be solved. Will repost if not Retro (Time rewind) variant causes progressivly worse lag

edit: I've seemingly solved my issue: it seems to have been an unrelated bug

Hello fellow modders,

I am having an issue which I suspect is caused by one of the following libraries. As the title says, something about these two code snippets is causing lag that gets progressivly worse as the game goes on. It seems to affect the host less compared to other players thought that might just be because I have an exellent computer. Note that some of the trigger comments are out dated since I modified Retro slightly from its original version. I changed Retro to use UnitEvent (linked here: GUI Unit Event v2.5.2.0 ) and linked here among other small changes: The original Retro can be found here:

This library is always active:

JASS:
//TESH.scrollpos=193
//TESH.alwaysfold=0
library Retro
//******************************************
//    ___  ___  ___  ___  ___
//   /__/ /__   /   /__/ /  /
//  /  | /__   /   /  \ /__/
//                          Created by Bribe
//******************************************
//
//
/*  Requirements
*  ¯¯¯¯¯¯¯¯¯¯¯¯
*
*  Description
*  ¯¯¯¯¯¯¯¯¯¯¯
*  Retro provides data retrieval and callback operators for use in simulating a time-travel effect for units.
*  While there is no promise that this is an easy system to implement, I will try to label everything as easy
*  to read and as simple as I can.  If you have any questions, just ask.
*
*  To Know
*  ¯¯¯¯¯¯¯
*  On its own, the retro struct indexes every unit that passes through AutoEvents. It records a unit's data
*  during each lapse of RETRO_TIMEOUT and saves it all in a three-dimensional hashtable. Simple array values
*  record the current-moment's data aspects: x, y, z and, if you desire, facing angle, HP and MP.
*
*  To allocate your own struct and have access to Retro's excellent callback features, the struct(s) you use
*  must extend array and implement the module named RETRO. This module uses allocate and deallocate as method
*  names to regulate those events.  You are welcome to call allocate/deallocate freely as they have built-in
*  foolproofs. <deallocate> is also automatically called when a unit is loaded into a transport or dies.
*
*  You can only instanciate for a given unit once per struct-type.  You can create numerous structs which all
*  implement the RETRO module.
*
*  Events
*  ¯¯¯¯¯¯
    method onLoop           -> called during each iteration of RETRO_TIMEOUT.
    method onLoopForward    -> called for only one struct per iteration, if it's in forward-mode.
    method onLoopReverse    -> called for only one struct per iteration, if it's in reverse-mode.

    method onStart  ->  if you specify a "capture" real value, this will be called after that time has expired.
    method onFinish ->  is called by the deallocate method. deallocate is called automatically if you specify a
                        real value for "capture" or "initiate". It's basically your "onDestroy" method.
*
*  Syntax Example
*  ¯¯¯¯¯¯¯¯¯¯¯¯¯¯
    struct timeStruct extends array
  
        method onFinish takes nothing returns nothing
            call BJDebugMsg(GetUnitName(.subject)+"'s stats:\n")
            call BJDebugMsg("X-coordinate is "+R2S(.x))
            call BJDebugMsg("Y-coordinate is "+R2S(.y))
            call BJDebugMsg("Z-coordinate is "+R2S(.z))
            call BJDebugMsg("facing is "+R2S(.ang))
            call BJDebugMsg("hit points are "+R2S(.hp))
            call BJDebugMsg("mana points are "+R2S(.mp)+"\n")
        endmethod
  
        implement RETRO
      
        static method create takes unit newSubject returns thistype
            local thistype this = thistype.allocate(newSubject, true, false)
            return this
        endmethod
      
        static method onInit takes nothing returns nothing
            local thistype this = thistype.create(newSubject)
            call this.initiate(0.03)
        endmethod
      
    endstruct
*
*
*/
  
    globals
        // Values are in seconds;
        constant    real        RETRO_TIMEOUT       = 0.03125   /* This value is a tribute to Jesus4Lyf's helpful Timer32 */
        constant    real        RETRO_MEMORY_MAX    = 10.0      /* The hashtable will deallocate data past this threshold */
      
        // Static-if controllers;
        private     constant    boolean     BREAKS_CHANNEL      = true      /* <true> uses SetUnitPosition for smoother motion */
        private     constant    boolean     RECORD_FACING       = false      /* If you're an efficiency-freak and don't care, set to false */
        private     constant    boolean     RECORD_HP           = true      /* Can save the health-points of units */
        private     constant    boolean     RECORD_MP           = true     /* Can save the mana-points of units */
    endglobals
  
    private module Validate
        static method create takes unit whichSubject returns retro
      
            if BlzIsUnitInvulnerable(whichSubject) and not IsUnitType(whichSubject,UNIT_TYPE_STRUCTURE) then
                return 0
            else
                return GetUnitUserData(whichSubject)
            endif
          
        endmethod   // Anything that returns 0 will under no condition be added to the system.
    endmodule
  
    globals
        private     hashtable   retro_memory    = InitHashtable()  /* Serves as a three-dimensional array */
        private     trigger     retro_launch    = CreateTrigger()  /* So this system only needs one timer */
        private     trigger     retro_expire    = CreateTrigger()  /* RETRO uses 5 fewer handles this way */
        constant    integer     RETRO_ERASER    = R2I(RETRO_MEMORY_MAX/RETRO_TIMEOUT)
        private     keyword     RetroInitializer
    endglobals
  
    struct retro extends array
      
        private retro prev
        private retro next
      
        readonly unit subject
        readonly integer save
      
        real x
        real y
        real z
      
        static if RECORD_FACING then    // Static ifs for maximum efficiency.
            real ang
        endif
        static if RECORD_HP then
            real hp
        endif
        static if RECORD_MP then
            real mp
        endif
      
        implement Validate
      
        method destroy takes nothing returns nothing
            debug call BJDebugMsg("Error: You cannot manually destroy a retro struct.")
        endmethod
      
        private static integer tick = 0
      
        private static method periodic takes nothing returns nothing
            local retro this = retro(0).next
            set retro.tick = retro.tick + 1
            if (retro.tick == 4) then
                set retro.tick = 0
                loop
                    exitwhen this == 0
                    set .save = .save + 1
                  
                    set .x = GetUnitX(.subject)
                    set .y = GetUnitY(.subject)
                    set .z = GetUnitFlyHeight(.subject)
                    call SaveReal(retro_memory, this * 6 + 0, .save, .x)
                    call SaveReal(retro_memory, this * 6 + 1, .save, .y)
                    call SaveReal(retro_memory, this * 6 + 2, .save, .z)
                  
                    static if RECORD_FACING then
                        set .ang = GetUnitFacing(.subject)
                        call SaveReal(retro_memory, this * 6 + 3, .save, .ang)
                    endif
                    static if RECORD_HP then
                        set .hp = GetWidgetLife(.subject)
                        call SaveReal(retro_memory, this * 6 + 4, .save, .hp)
                    endif
                    static if RECORD_MP then
                        set .mp = GetUnitState(.subject, UNIT_STATE_MANA)
                        call SaveReal(retro_memory, this * 6 + 5, .save, .mp)
                    endif
                  
                    if (.save >= RETRO_ERASER) then
                        call RemoveSavedReal(retro_memory, this * 6 + 0, .save - RETRO_ERASER)
                        call RemoveSavedReal(retro_memory, this * 6 + 1, .save - RETRO_ERASER)
                        call RemoveSavedReal(retro_memory, this * 6 + 2, .save - RETRO_ERASER)
                        static if RECORD_FACING then
                            call RemoveSavedReal(retro_memory, this * 6 + 3, .save - RETRO_ERASER)
                        endif
                        static if RECORD_HP then
                            call RemoveSavedReal(retro_memory, this * 6 + 4, .save - RETRO_ERASER)
                        endif
                        static if RECORD_MP then
                            call RemoveSavedReal(retro_memory, this * 6 + 5, .save - RETRO_ERASER)
                        endif
                    endif
                    set this = .next
                endloop
            endif
          
            // Fire things;
            call TriggerEvaluate(retro_launch)
        endmethod
      
        private static method onInit takes nothing returns nothing
            call TimerStart(CreateTimer(), RETRO_TIMEOUT, true, function retro.periodic)
        endmethod
      
        static retro removing
      
        private static method remove takes nothing returns nothing
            local retro this = retro.create(udg_UDexUnits[udg_UDex])
            //call BJDebugMsg(GetUnitName(udg_UDexUnits[udg_UDex]))
            if .subject == udg_UDexUnits[udg_UDex] then
                //call BJDebugMsg("subject")
                set .prev.next = .next
                set .next.prev = .prev
              
                call FlushChildHashtable(retro_memory, this * 6 + 0)
                call FlushChildHashtable(retro_memory, this * 6 + 1)
                call FlushChildHashtable(retro_memory, this * 6 + 2)
                static if RECORD_FACING then
                    call FlushChildHashtable(retro_memory, this * 6 + 3)
                endif
                static if RECORD_HP then
                    call FlushChildHashtable(retro_memory, this * 6 + 4)
                endif
                static if RECORD_MP then
                    call FlushChildHashtable(retro_memory, this * 6 + 5)
                endif
              
                // Fire things;
                set retro.removing = this
                call TriggerEvaluate(retro_expire)
              
                set .subject = null
            endif
        endmethod
      
        private static method add takes nothing returns nothing
            local retro this = retro.create(udg_UDexUnits[udg_UDex])
            if this != 0 then
                set .subject = udg_UDexUnits[udg_UDex]
                set .save = 0
                set retro(0).next.prev = this
                set this.next = retro(0).next
                set retro(0).next = this
                set this.prev = retro(0)
            endif
        endmethod
      
        implement RetroInitializer
       
    endstruct
  
    private module RetroInitializer
        private static method onInit takes nothing returns nothing
            local trigger enter_t = CreateTrigger()
            local trigger exit_t = CreateTrigger()
            call TriggerRegisterVariableEvent( enter_t, "udg_UnitIndexEvent", EQUAL, 1.0 )
            call TriggerAddAction( enter_t, function retro.add )
            //call OnUnitIndexed(retro.add)
            call TriggerRegisterVariableEvent( exit_t, "udg_UnitIndexEvent", EQUAL, 2.0 )
            call TriggerAddAction( exit_t, function retro.remove )
           // call OnUnitDeindexed(retro.remove)
               call TriggerRegisterVariableEvent( enter_t, "udg_CargoEvent", EQUAL, 2.0 )
            //call OnUnitLoad(retro.remove)
            call TriggerRegisterVariableEvent( exit_t, "udg_CargoEvent", EQUAL, 1.0 )
            //call OnUnitUnload(retro.add)
        endmethod
    endmodule
  
    native UnitAlive takes unit id returns boolean
  
    module RETRO    /* May only be implemented in <extends array> structs */
      
        boolean noLoops     /* To toggle a method on/off that gets called <every single> loop */
        boolean noExtras    /* To toggle the extra methods on/off (called for one instance per loop) */
      
        private thistype prev
        private thistype next
    
        private boolean active
        private boolean reverse
        private boolean captured
      
        private real timeLeft
        private real timePend
        private integer iSave
      
        static if RECORD_FACING then
            method operator ang takes nothing returns real  // Delegate ang from retro
                return retro(this).ang
            endmethod
            method operator ang= takes real r returns nothing
                set retro(this).ang = r
            endmethod
        endif
        static if RECORD_HP then
      
            boolean restoreGoodHP
            boolean restoreBadHP
            boolean restoreAnyHP
          
            method operator hp takes nothing returns real   // Delegate hp from retro
                return retro(this).hp
            endmethod
            method operator hp= takes real r returns nothing
                set retro(this).hp = r
            endmethod
          
        endif
        static if RECORD_MP then
      
            boolean restoreGoodMP
            boolean restoreBadMP
            boolean restoreAnyMP
          
            method operator mp takes nothing returns real   // Delegate mp from retro
                return retro(this).mp
            endmethod
            method operator mp= takes real r returns nothing
                set retro(this).mp = r
            endmethod
          
        endif
      
        method operator x takes nothing returns real        // Delegate x
            return retro(this).x
        endmethod
        method operator x= takes real r returns nothing
            set retro(this).x = r
        endmethod
          
        method operator y takes nothing returns real        // Delegate y
            return retro(this).y
        endmethod
        method operator y= takes real r returns nothing
            set retro(this).y = r
        endmethod
          
        method operator z takes nothing returns real        // Delegate z
            return retro(this).z
        endmethod
        method operator z= takes real r returns nothing
            set retro(this).z = r
        endmethod
          
        method operator subject takes nothing returns unit  // Readonly subject
            return retro(this).subject
        endmethod
      
        method deallocate takes nothing returns nothing
            if .active then
                set .active = false
                set .reverse = false
                set .captured = false
                set .prev.next = .next
                set .next.prev = .prev
                static if BREAKS_CHANNEL then
                    call SetUnitPathing(.subject, true)
                endif
                static if thistype.onFinish.exists then
                    call .onFinish()
                else
                    call BJDebugMsg("No onFinish found")
                endif
            endif
        endmethod
      
        method initiate takes real duration returns nothing
            set .iSave = retro(this).save
            set .timeLeft = .timeLeft + duration
            set .reverse = true
            static if BREAKS_CHANNEL then
                call SetUnitPathing(.subject, false)
            endif
        endmethod   /* <duration> is the time the unit will be in reverse */
      
        method capture takes real duration returns nothing
            set .timeLeft = .timeLeft + duration
            set .captured = true
            if .reverse then
                set .reverse = false
                static if BREAKS_CHANNEL then
                    call SetUnitPathing(.subject, true)
                endif
            endif
        endmethod   /* <duration> is the time the unit will be in forward. Reverse is 1/4 of that */
      
        method collapse takes nothing returns nothing
            if not .reverse and .captured then
                set .timeLeft = (.timePend / 4)
                set .iSave = retro(this).save
                set .timePend = 0.00
                set .captured = false
                set .reverse = true
                static if thistype.onStart.exists then
                    call .onStart()
                endif
                static if BREAKS_CHANNEL then
                    call SetUnitPathing(.subject, false)
                endif
            endif
        endmethod
      
        private static method handler takes nothing returns boolean
            local thistype array choices
            local thistype this = thistype(0).next
            local thistype rand = 0
            local real r
            loop
                exitwhen (this == 0)
              
                if .reverse then
                  
                    set .x = LoadReal(retro_memory, this * 6 + 0, .iSave)
                    set .y = LoadReal(retro_memory, this * 6 + 1, .iSave)
                    set .z = LoadReal(retro_memory, this * 6 + 2, .iSave)

                    if GetUnitMoveSpeed(.subject) > 0.0 then
                  
                        static if BREAKS_CHANNEL then
                            call SetUnitPosition(.subject, .x, .y)
                        else
                            call SetUnitX(.subject, .x)
                            call SetUnitY(.subject, .y)
                        endif
                        call SetUnitFlyHeight(.subject, .z, 0.00)
                    endif
                  
                    static if RECORD_FACING then
                        set .ang = LoadReal(retro_memory, this * 6 + 3, .iSave)
                        call SetUnitFacing(.subject, .ang)
                    endif
                    static if RECORD_HP then 
                        set r = GetWidgetLife(.subject)
                        set .hp = LoadReal(retro_memory, this * 6 + 4, .iSave)
                        if .restoreAnyHP or (.restoreGoodHP and r < .hp - 1) or (.restoreBadHP and r > .hp + 1) then
                            call SetWidgetLife(.subject, RMaxBJ(1.0, .hp))
                            if .hp > 0 and GetHeroProperName(.subject) !=null and not UnitAlive(.subject) then
                                call ReviveHero(.subject,.x,.y,false)
                                if GetLocalPlayer()==GetOwningPlayer(.subject) then
                                    call SetCameraPosition(.x, .y)
                                    call ClearSelection()
                                    call SelectUnit(.subject,true)
                                endif
                            endif
                        endif
                    endif
                    static if RECORD_MP then
                        set r = GetUnitState(.subject, UNIT_STATE_MANA)
                        set .mp = LoadReal(retro_memory, this * 6 + 5, .iSave)
                        if .restoreAnyMP or (.restoreGoodMP and r < .mp + 1) or (.restoreBadMP and r > .mp + 1) then
                            call SetUnitState(.subject, UNIT_STATE_MANA, .mp)
                        endif
                    endif
                  
                    set .iSave = .iSave - 1
                    set .timeLeft = .timeLeft - RETRO_TIMEOUT
                    if (.timeLeft <= 0.00 or .iSave <= 0) then
                        call .deallocate()
                    endif
                  
                elseif .captured then
                    set .timePend = .timePend + RETRO_TIMEOUT
                    if (.timePend >= .timeLeft) then
                        call .collapse()
                    endif
                endif
              
                static if thistype.onLoop.exists then
                    if not .noLoops then
                        call .onLoop()
                    endif
                endif
                if not .noExtras then
                    set rand:choices = this
                    set rand = rand + 1
                endif
                set this = .next
            endloop
          
            if (rand > 0) then
                set rand = choices[GetRandomInt(0, rand - 1)]
                if rand.reverse then
                    static if thistype.onLoopReverse.exists then
                        call rand.onLoopReverse()
                    endif
                else
                    static if thistype.onLoopForward.exists then
                        call rand.onLoopForward()
                    endif
                endif
            endif
          
            return false
        endmethod
      
        static method allocate takes unit theSubject, boolean wantLoop, boolean wantLoopEx returns thistype
            local thistype this = retro.create(theSubject)
          
            if this == 0 or retro(this).save == 0 then
                return 0
            elseif .active then
                call .deallocate()
            endif
          
            set .active   = true
            set .noLoops  = not wantLoop
            set .noExtras = not wantLoopEx
            set .timeLeft = 0.00
            set .timePend = 0.00
          
            static if RECORD_HP then
                set .restoreGoodHP = false
                set .restoreBadHP = false
                set .restoreAnyHP = false
            endif
            static if RECORD_MP then
                set .restoreGoodMP = false
                set .restoreBadMP = false
                set .restoreAnyMP = false
            endif
          
            set thistype(0).next.prev = this
            set this.next = thistype(0).next
            set thistype(0).next = this
            set this.prev = thistype(0)
          
            return this
        endmethod
      
        private static method onRemoval takes nothing returns boolean
            if thistype(retro.removing).active then
                call thistype(retro.removing).deallocate()
            endif
            return false
        endmethod
      
        private static method onInit takes nothing returns nothing
            call TriggerAddCondition(retro_launch, Condition(function thistype.handler))    // Enables the handler event
            call TriggerAddCondition(retro_expire, Condition(function thistype.onRemoval))  // Enables the onRemoval event
        endmethod
      
    endmodule
  
    module RetroTimerModule
        private static method onInit takes nothing returns nothing
            call TriggerAddCondition(retro_launch, Condition(function thistype.handler))    // Basically for RetroFade
        endmethod
    endmodule
  
endlibrary

However, the lag only seems to happen when this spell is cast during the game.

JASS:
library Rewind requires Retro
  
    private struct rewindv2 extends array
    // Retro structs must <extend array>
      
  
        effect attach   // This effect gets attached to the unit for the duration of the spell.
      
        method onFinish takes nothing returns nothing
            call DestroyEffect(.attach)
        endmethod   // <onFinish> is the <onDestroy> method. You don't need to call <deallocate> from it.
             
        implement RETRO // The module is below <onFinish> and <onLoopReverse> while above <allocate>
             
        static method Rewind takes unit u, boolean b returns boolean
            local thistype this

         
            // <allocate> and <deallocate> are provided by the RETRO module.
            set this = allocate(u, false, true)
        
            // <allocate> will return <0> if a unit is invalid; I advise checking that before any continuation;
            if this > 0 then
            /*
             *  <initiate> immediately launches a unit back through time for the
             *  specified duration.  This won't do anything for units created in
             *  the same instant; they haven't had time to record some movement.
             */
                call .initiate(3.00)
           
                // The unit you're manipulating is referenced as <.subject>
                set .attach = AddSpecialEffectTarget("Abilities\\Spells\\Human\\slow\\slowtarget", .subject, "chest")
              
                // <.restoreGoodHP> will revive the unit if its former HP was greater.
                set .restoreGoodHP = b
              
            /*
             *  <.restoreGoodMP> will do the same, but for MP. Un-commenting the line will provide a syntax
             *  error, as I have the Retro constant boolean RECORD_MP set to <false>, preventing the variable
             *  from even being compiled.
             */
                set .restoreGoodMP = b
            endif
            return false
        endmethod
      
    static method condition takes nothing returns boolean
            return GetSpellAbilityId() == AID
    endmethod
      
        static method onCast takes nothing returns nothing
            local unit caster = GetTriggerUnit()
        local unit temp
            local group targets = CreateGroup()

            call GroupEnumUnitsInRange(targets, GetUnitX(caster),GetUnitY(caster), 2000.0, null)
        loop
            set temp = FirstOfGroup(targets)
                exitwhen temp == null
            if IsUnitAlly(temp, GetOwningPlayer(caster)) and not BlzIsUnitInvulnerable(temp) and temp!=caster and not IsUnitType(temp,UNIT_TYPE_STRUCTURE) then
            call UnitResetCooldown(temp)
            call rewindv2.Rewind(temp,true)
        endif
               call GroupRemoveUnit(targets, temp)
        endloop
            call GroupEnumUnitsInRange(targets, GetUnitX(caster),GetUnitY(caster), 2000.0, null)
        loop
            set temp = FirstOfGroup(targets)
                exitwhen temp == null
            if IsUnitEnemy(temp, GetOwningPlayer(caster)) and not BlzIsUnitInvulnerable(temp) and not IsUnitType(temp, UNIT_TYPE_MAGIC_IMMUNE) and not IsUnitType(temp,UNIT_TYPE_STRUCTURE) then
            call rewindv2.Rewind(temp,false)
        endif
               call GroupRemoveUnit(targets, temp)
        endloop
        call DestroyGroup(targets)
            set targets =null
            set caster = null
            set temp = null
        endmethod
    static integer AID = 'A54I'
        private static method onInit takes nothing returns nothing
        local trigger t = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_SPELL_EFFECT )
            call TriggerAddCondition( t, Condition( function thistype.condition ) )
            call TriggerAddAction( t, function thistype.onCast )
    endmethod
      
        /*
         *  Note the <onLoop>, <onLoopForward> and <onStart> methods are nowhere in sight. They are optional,
         *  and so are <onLoopReverse> and <onFinish>
         *
         *  Final Notes
         *  ¯¯¯¯¯¯¯¯¯¯¯
         *  ~ You should only implement an event-method if you have a use for it.
         *  ~ You don't have to call <destroy> on a RETRO-implemented struct unless you wish to call your onFinish method.
         */
       
    endstruct
  
endlibrary
 
Last edited:
Top