• 🏆 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!

[vJASS] EVENT_PLAYER_UNIT_ISSUED_ORDER memory leak issue

Status
Not open for further replies.
Level 6
Joined
Oct 23, 2011
Messages
182
My map has been having a lot of fatal errors due to 'out of memory' errors, and I traced the issue down to a single line of JASS code (in a unit indexing system) that looks something like this:

JASS:
call TriggerRegisterPlayerUnitEvent(CreateTrigger(), Player(12), EVENT_PLAYER_UNIT_ISSUED_ORDER, null)

My map's memory usage increases tremendously as units die when I have a single line of above JASS code.
The above example shouldn't even be doing anything because filter func is null.


Here's a simple example I used to test this issue on a test map
JASS:
scope Test

    native UnitAlive takes unit u returns boolean
  
    struct Test extends array
  
        static group G = null
      
        static method x takes nothing returns nothing
            local unit u
          
            call GroupEnumUnitsInRect(G, WorldBounds.world, null)
            loop
                set u = FirstOfGroup(G)
                exitwhen u == null
                call GroupRemoveUnit(G, u)
              
                if GetUnitTypeId(u) == 'h000' and UnitAlive(u) then
                    call KillUnit(u)
                endif
            endloop
          
            set u = null
        endmethod
      
        static method onInit takes nothing returns nothing
            set G = CreateGroup()
           
            // having this line causes HUGE memory leak!
            call TriggerRegisterPlayerUnitEvent(CreateTrigger(), Player(12), EVENT_PLAYER_UNIT_ISSUED_ORDER, null)

            call TimerStart(CreateTimer(), 0.2, true, function thistype.x)
        endmethod
    endstruct
endscope

Respawn System to remake dummies
JASS:
scope Respawn initializer OnInit

    private struct Data extends array
        implement Alloc
      
        real x
        real y
    endstruct
  
    private function OnExpiration takes nothing returns nothing
        local Data data = ReleaseTimer(GetExpiredTimer())
      
        call CreateUnit(Player(12), 'h000', data.x, data.y, 0)
        call data.deallocate()
    endfunction
  
    private function OnDeath takes nothing returns boolean
        local unit u = GetTriggerUnit()
        local Data data
      
        if GetUnitTypeId(u) == 'h000' then
            set data = Data.allocate()
            set data.x = GetUnitX(u)
            set data.y = GetUnitY(u)
          
            call TimerStart(NewTimerEx(data), 0.1, false, function OnExpiration)
        endif
       
        set u = null
        return false
    endfunction
  
    private function OnInit takes nothing returns nothing
        local trigger trg = CreateTrigger()
      
        call TriggerRegisterPlayerUnitEvent(trg, Player(12), EVENT_PLAYER_UNIT_DEATH, null)
        call TriggerAddCondition(trg, Condition(function OnDeath))
    endfunction

endscope


Any insights on this issue?
 
Last edited:
In the test map you used, how many units are there in the world? Enumerating all the units on the map can be very expensive. If you comment out the line where it registers the event, I assume it still lags out?

As for your respawn system, it seems fine. But often the problem comes from events that fire other events. You might want to add a debug message to every trigger that has an event. For example, in the respawn system, you'd do something like this inside OnDeath:
JASS:
call BJDebugMsg("Respawn trigger fired!")

Do that for every trigger. When a unit dies, see what messages pop up. If you get a ton of messages, go to that trigger and see what the problem is. The issue shouldn't come from that line of code you have, afaik.
 
JASS:
call TriggerRegisterPlayerUnitEvent(CreateTrigger(), Player(12), EVENT_PLAYER_UNIT_ISSUED_ORDER, null)

I wonder about the Neutral Hostile player being considered as a parameter for the above function.

Is it alright if you show what kind of indexing system you're using? I'll test out your sample code and see what's wrong with it.
 
Level 6
Joined
Oct 23, 2011
Messages
182
In the test map you used, how many units are there in the world? Enumerating all the units on the map can be very expensive. If you comment out the line where it registers the event, I assume it still lags out?

As for your respawn system, it seems fine. But often the problem comes from events that fire other events. You might want to add a debug message to every trigger that has an event. For example, in the respawn system, you'd do something like this inside OnDeath:
JASS:
call BJDebugMsg("Respawn trigger fired!")

Do that for every trigger. When a unit dies, see what messages pop up. If you get a ton of messages, go to that trigger and see what the problem is. The issue shouldn't come from that line of code you have, afaik.

Around 30. I had no lag regardless of whether I commented out the event or not. It's just that my Warcraft's memory usage (in task manager) keeps going up with the event registered.

This test was done on a simple map without other systems that use unit events.

I wonder about the Neutral Hostile player being considered as a parameter for the above function.

Is it alright if you show what kind of indexing system you're using? I'll test out your sample code and see what's wrong with it.

On my main map, I used Nestharus' Unit Indexer (an old version).




I've attached a test map with simpler codes (below codes are the only codes in map). My dummy unit is set to 'not resurrectable/does not decay', and map's unit decay times were adjusted to 1 second.

JASS:
scope UnitSpawner

    private struct Test extends array
 
        private static method x takes nothing returns nothing
            local integer i = 0
        
            loop
                set i = i + 1
                call KillUnit(CreateUnit(Player(12), 'hfoo', 0, 0, 0))
                exitwhen i == 10
            endloop
        endmethod
    
        private static method onInit takes nothing returns nothing
            call FogEnable(false)
            call FogMaskEnable(false)
            call TimerStart(CreateTimer(), 0.03, true, function thistype.x)
        endmethod
    endstruct

endscope

JASS:
scope TheLeakCode initializer OnInit

    // enabling this trigger causes HUGE memory leak!
 
    private function OnInit takes nothing returns nothing
        call TriggerRegisterPlayerUnitEvent(CreateTrigger(), Player(12), EVENT_PLAYER_UNIT_ISSUED_ORDER, null)
    endfunction
 
endscope

With 'TheLeakCode' enabled, my warcraft's memory usage increases by steady 0.7mb ish per second. It started from 150mb and reached 300mb in about 3 minutes. With it disabled, it climbs much more slowly at like 0.2~0.3mb ish per second, which is also weird because there shouldn't be a leak to begin with..


Changing the KillUnit() to RemoveUnit() in UnitSpawner produces different results. With that change, leaving 'TheLeakCode' on increases warcraft's memory usage by steady 0.5mb ish per second. Leaving 'TheLeakCode' off increases warcraft's memory usage by 0.05mb ish per second (almost negligible, but the growth seems to be still there)
 

Attachments

  • TheMemoryLeak.w3x
    12.7 KB · Views: 38
I see the problem now.

The dying unit somehow issues some kind of order when dying (or maybe it was spawning), which the trigger somehow picks up.

I haven't tested it yet for the RemoveUnit function but

it is somehow understood that even when you call the RemoveUnit function, it will still leave a permanent leak that cannot (as of now) be cleaned. The proof of such behavior of the unit handle can be seen in the systems to be referenced, such as @Flux's DummyRecycler or @Bribe's MissileRecycler.

Testing it with the RemoveUnit now...


The probable reason why the function
JASS:
call TriggerRegisterPlayerUnitEvent(CreateTrigger(), Player(12), EVENT_PLAYER_UNIT_ISSUED_ORDER, null)
is that the trigger uses a playerunitevent, which extends event and simulates separate unitevent events internally. This leads to a possible conclusion that the playerunitevent type is a wrapper type to the unitevent type, creating more events and registering the trigger to said events.
 
Last edited:
Level 6
Joined
Oct 23, 2011
Messages
182
I see the problem now.

The dying unit somehow issues some kind of order when dying (or maybe it was spawning), which the trigger somehow picks up.

I haven't tested it yet for the RemoveUnit function but

it is somehow understood that even when you call the RemoveUnit function, it will still leave a permanent leak that cannot (as of now) be cleaned. The proof of such behavior of the unit handle can be seen in the systems to be referenced, such as @Flux's DummyRecycler or @Bribe's MissileRecycler.

Testing it with the RemoveUnit now...


The probable reason why the function
JASS:
call TriggerRegisterPlayerUnitEvent(CreateTrigger(), Player(12), EVENT_PLAYER_UNIT_ISSUED_ORDER, null)
is that the trigger uses a playerunitevent, which extends event and simulates separate unitevent events internally. This leads to a possible conclusion that the playerunitevent type is a wrapper type to the unitevent type, creating more events and registering the trigger to said events.

Well, I hope Blizzard can take a look at the unit leak issue then..


when timer is paused memory usage drops down to acceptable level

This did not work for me unfortunately :( Memory usage just stopped increasing when I stopped timer, but no leaks were cleared up.
 
Level 18
Joined
Nov 21, 2012
Messages
835
indeed game only clears up ~20% of the memory after timer paused
but when changed to this:
JASS:
set u = CreateUnit(Player(12), 'hfoo', 0, 0, 0)
call KillUnit(u)
set u=null
instead of in-line version, it cleans more memory (for about ~30%), it looks like KillUnit(CreateUnit(Player(12), 'hfoo', 0, 0, 0)) producec additional leak
 
Status
Not open for further replies.
Top