- Joined
- Oct 9, 2019
- Messages
- 295
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:
www.hiveworkshop.com
This library is always active:
However, the lag only seems to happen when this spell is cast during the game.
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:
[System] Retro (time travel)
The coolest system on the market, Retro logs a unit's status for any amount of time, then restores them to the unit instant by instant. Stats include: x, y, z coordinates, optional facing, optional HP and optional MP. If you want more, ask, this system is very flexible and can easily handle...

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: