UnitRecycler

Scripts

9 years
edited 8 years
A useful library which allows you to recycle units (even dead ones, but they must leave a corpse), avoiding yet another permanent 0.04kb memory leak for each future CreateUnit() call.


Script
Code (jass) Select
1
2
library UnitRecycler /* v1.3b
3
4
5
    |=============|
6
    | Author: AGD |
7
    |=============|
8
9
    */requires /*
10
11
    */ReviveUnit                        /*  https://wc3modding.info/4469/snippet-reviveunit/
12
    */UnitDex                           /*  http://www.hiveworkshop.com/threads/system-unitdex-unit-indexer.248209/
13
    */optional Table                    /*  https://wc3modding.info/4611/snippet-new-table/
14
    */optional TimerUtils               /*  https://wc3modding.info/5352/hibrid-timerutils/
15
    */optional RegisterPlayerUnitEvent  /*  https://wc3modding.info/4907/snippet-registerplayerunitevent/
16
17
    This system is important because CreateUnit() is one of the most processor-intensive function in
18
    the game and there are reports that even after they are removed, they still leave some bit of memory
19
    consumption (0.04 KB) on the RAM. Therefore it would be very helpful if you can minimize unit
20
    creation or so. This system also allows you to recycle dead units to avoid permanent 0.04 KB memory
21
    leak for each future CreateUnit() call.                                                                 */
22
23
//! novjass
24
25
    [Credits]
26
        Aniki - For suggesting ideas on further improvements
27
28
29
    |-----|
30
    | API |
31
    |-----|
32
33
        function GetRecycledUnit takes player owner, integer rawCode, real x, real y, real facing returns unit/*
34
            - Returns unit of specified ID from the stock of recycled units. If there's none in the stock that
35
              matched the specified unit's rawcode, it will create a new unit instead
36
            - Returns null if the rawcode's unit-type is a hero or non-existent
37
38
      */function GetRecycledUnitEx takes player owner, integer rawCode, real x, real y, real facing returns unit/*
39
            - Works similar to GetRecycledUnit() except that if the input rawcode's unit-type is a hero, it will
40
              be created via CreateUnit() instead
41
            - You can use this as an alternative to CreateUnit()
42
43
      */function RecycleUnit takes unit u returns boolean/*
44
            - Recycles the specified unit and returns a boolean value depending on the success of the operation
45
            - Does nothing to hero units
46
47
      */function RecycleUnitEx takes unit u returns boolean/*
48
            - Works similar to RecycleUnit() except that if <u> is not recyclable, it will be removed via
49
              RemoveUnit() instead
50
            - You can use this as an alternative to RemoveUnit()
51
52
      */function RecycleUnitDelayed takes unit u, real delay returns nothing/*
53
            - Recycles the specified unit after <delay> seconds
54
55
      */function RecycleUnitDelayedEx takes unit u, real delay returns nothing/*
56
            - Works similar to RecycleUnitDelayed() except that it calls RecycleUnitEx() instead of RecycleUnit()
57
58
      */function UnitAddToStock takes integer rawCode returns boolean/*
59
            - Creates a unit of type ID and adds it to the stock of recycled units then returns a boolean value
60
              depending on the success of the operation
61
62
*///! endnovjass
63
64
    //CONFIGURATION SECTION
65
66
67
    globals
68
69
/*      The owner of the stocked/recycled units
70
*/      private constant player OWNER               = Player(15)
71
72
/*      Determines if dead units will be automatically recycled
73
        after a delay designated by the <constant function
74
        DeathTime below>
75
*/      private constant boolean AUTO_RECYCLE_DEAD  = true
76
77
/*      Error debug message prefix
78
*/      private constant string ERROR_PREFIX        = "|CFFFF0000Operation Failed: "
79
80
    endglobals
81
82
    /* The delay before dead units will be recycled in case AUTO_RECYCLE_DEAD == true */
83
    static if AUTO_RECYCLE_DEAD then
84
        private constant function DeathTime takes unit u returns real
85
            /*if <condition> then
86
                  return someValue
87
              elseif <condition> then
88
                  return someValue
89
              endif                 */
90
            return 8.00
91
        endfunction
92
    endif
93
94
    /* When recycling a unit back to the stock, these resets will be applied to the
95
       unit. You can add more actions to this or you can delete this textmacro if you
96
       don't need it.                                                                       */
97
        //! textmacro_once UNIT_RECYCLER_RESET
98
            call SetUnitScale(u, 1, 0, 0)
99
            call SetUnitVertexColor(u, 255, 255, 255, 255)
100
            call SetUnitFlyHeight(u, GetUnitDefaultFlyHeight(u), 0)
101
        //! endtextmacro
102
103
104
    //END OF CONFIGURATION
105
106
    /*==== Do not do changes below this line if you're not so sure on what you're doing ====*/
107
    native UnitAlive takes unit u returns boolean
108
109
    globals
110
        private keyword S
111
        private integer count = 0
112
        private real unitCampX
113
        private real unitCampY
114
        private integer array stack
115
        private boolean array stacked
116
    endglobals
117
118
    private function GetIndex takes integer rawCode returns integer
119
        static if LIBRARY_Table then
120
            local integer i = S.table.integer[rawCode]
121
            if i == 0 then
122
                set count = count + 1
123
                set S.table.integer[rawCode] = count
124
                set i = count
125
            endif
126
        else
127
            local integer i = LoadInteger(S.hash, -1, rawCode)
128
            if i == 0 then
129
                set count = count + 1
130
                call SaveInteger(S.hash, -1, rawCode, count)
131
                set i = count
132
            endif
133
        endif
134
        return i
135
    endfunction
136
137
    static if DEBUG_MODE then
138
        private function Debug takes string msg returns nothing
139
            call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "|CFFFFCC00[Unit Recycler]|R " + msg)
140
        endfunction
141
    endif
142
143
    function GetRecycledUnit takes player owner, integer rawCode, real x, real y, real facing returns unit
144
        local integer i
145
        if not IsHeroUnitId(rawCode) then
146
            set i = GetIndex(rawCode)
147
            if stack[i] == 0 then
148
                set bj_lastCreatedUnit = CreateUnit(owner, rawCode, x, y, facing)
149
                debug call Debug(GetUnitName(bj_lastCreatedUnit) + " stock is empty, creating new " + GetUnitName(bj_lastCreatedUnit))
150
            else
151
                static if LIBRARY_Table then
152
                    set bj_lastCreatedUnit = S.hash[i].unit[stack[i]]
153
                else
154
                    set bj_lastCreatedUnit = LoadUnitHandle(S.hash, i, stack[i])
155
                endif
156
                set stacked[GetUnitId(bj_lastCreatedUnit)] = false
157
                call PauseUnit(bj_lastCreatedUnit, false)
158
                call SetUnitOwner(bj_lastCreatedUnit, owner, true)
159
                call SetUnitPosition(bj_lastCreatedUnit, x, y)
160
                call SetUnitFacing(bj_lastCreatedUnit, facing)
161
                set stack[i] = stack[i] - 1
162
                debug call Debug("Retrieving " + GetUnitName(bj_lastCreatedUnit) + " from stock")
163
            endif
164
            debug if bj_lastCreatedUnit == null then
165
                debug call Debug(ERROR_PREFIX + "Specified unit-type does not exist")
166
            debug endif
167
        else
168
            debug call Debug(ERROR_PREFIX + "Attemp to retrieve a hero unit")
169
            return null
170
        endif
171
        return bj_lastCreatedUnit
172
    endfunction
173
174
    function GetRecycledUnitEx takes player owner, integer rawCode, real x, real y, real facing returns unit
175
        if not IsHeroUnitId(rawCode) then
176
            return GetRecycledUnit(owner, rawCode, x, y, facing)
177
        endif
178
        debug call Debug("Cannot retrieve a hero unit, creating new unit")
179
        return CreateUnit(owner, rawCode, x, y, facing)
180
    endfunction
181
182
    function RecycleUnit takes unit u returns boolean
183
        local integer rawCode = GetUnitTypeId(u)
184
        local integer uDex = GetUnitId(u)
185
        local integer i
186
        if not IsHeroUnitId(rawCode) and not stacked[uDex] and u != null then
187
            set i = GetIndex(rawCode)
188
            if not UnitAlive(u) and not ReviveUnit(u) then
189
                debug call Debug(ERROR_PREFIX + "Unable to recycle unit: Unable to revive dead unit")
190
                return false
191
            endif
192
            set stacked[uDex] = true
193
            call PauseUnit(u, true)
194
            call SetUnitOwner(u, OWNER, true)
195
            call SetUnitX(u, unitCampX)
196
            call SetUnitY(u, unitCampY)
197
            call SetUnitFacing(u, 270)
198
            call SetWidgetLife(u, GetUnitState(u, UNIT_STATE_MAX_LIFE))
199
            call SetUnitState(u, UNIT_STATE_MANA, GetUnitState(u, UNIT_STATE_MAX_MANA))
200
            //! runtextmacro optional UNIT_RECYCLER_RESET()
201
            set stack[i] = stack[i] + 1
202
            static if LIBRARY_Table then
203
                set S.hash[i].unit[stack[i]] = u
204
            else
205
                call SaveUnitHandle(S.hash, i, stack[i], u)
206
            endif
207
            debug call Debug("Successfully recycled " + GetUnitName(u))
208
            return true
209
        debug else
210
            debug if stacked[uDex] then
211
                debug call Debug(ERROR_PREFIX + "Attempt to recycle an already recycled unit")
212
            debug elseif u == null then
213
                debug call Debug(ERROR_PREFIX + "Attempt to recycle a null unit")
214
            debug else
215
                debug call Debug(ERROR_PREFIX + "Attempt to recycle a hero unit")
216
            debug endif
217
        endif
218
        return false
219
    endfunction
220
221
    function RecycleUnitEx takes unit u returns boolean
222
        if not RecycleUnit(u) then
223
            call RemoveUnit(u)
224
            debug call Debug("Cannot recycle the specified unit, removing unit")
225
            return false
226
        endif
227
        return true
228
    endfunction
229
230
    //! textmacro DELAYED_RECYCLE_TYPE takes EX
231
    private function RecycleTimer$EX$ takes nothing returns nothing
232
        local timer t = GetExpiredTimer()
233
        static if LIBRARY_TimerUtils then
234
            call RecycleUnit$EX$(GetUnitById(GetTimerData(t)))
235
            call ReleaseTimer(t)
236
        else
237
            local integer key = GetHandleId(t)
238
            static if LIBRARY_Table then
239
                call RecycleUnit$EX$(S.hash[0].unit[key])
240
                call S.hash[0].remove(key)
241
            else
242
                call RecycleUnit$EX$(LoadUnitHandle(S.hash, 0, key))
243
                call RemoveSavedHandle(S.hash, 0, key)
244
            endif
245
            call DestroyTimer(t)
246
        endif
247
        set t = null
248
    endfunction
249
250
    function RecycleUnitDelayed$EX$ takes unit u, real delay returns nothing
251
        static if LIBRARY_TimerUtils then
252
            call TimerStart(NewTimerEx(GetUnitId(u)), delay, false, function RecycleTimer$EX$)
253
        else
254
            local timer t = CreateTimer()
255
            static if LIBRARY_Table then
256
                set S.hash[0].unit[GetHandleId(t)] = u
257
            else
258
                call SaveUnitHandle(S.hash, 0, GetHandleId(t), u)
259
            endif
260
            call TimerStart(t, delay, false, function RecycleTimer$EX$)
261
            set t = null
262
        endif
263
    endfunction
264
    //! endtextmacro
265
266
    //! runtextmacro DELAYED_RECYCLE_TYPE("")
267
    //! runtextmacro DELAYED_RECYCLE_TYPE("Ex")
268
269
    function UnitAddToStock takes integer rawCode returns boolean
270
        local unit u
271
        local integer i
272
        if not IsHeroUnitId(rawCode) then
273
            set u = CreateUnit(OWNER, rawCode, unitCampX, unitCampY, 270)
274
            if u != null then
275
                set i = GetIndex(rawCode)
276
                call SetUnitX(u, unitCampX)
277
                call SetUnitY(u, unitCampY)
278
                call PauseUnit(u, true)
279
                set stacked[GetUnitId(u)] = true
280
                set stack[i] = stack[i] + 1
281
                static if LIBRARY_Table then
282
                    set S.hash[i].unit[stack[i]] = u
283
                else
284
                    call SaveUnitHandle(S.hash, i, stack[i], u)
285
                endif
286
                debug call Debug("Adding " + GetUnitName(u) + " to stock")
287
                return true
288
            debug else
289
                debug call Debug(ERROR_PREFIX + "Attemp to stock a null unit")
290
            endif
291
            set u = null
292
        debug else
293
            debug call Debug(ERROR_PREFIX + "Attemp to stock a hero unit")
294
        endif
295
        return false
296
    endfunction
297
298
    static if AUTO_RECYCLE_DEAD then
299
        private function OnDeath takes nothing returns nothing
300
            local unit u = GetTriggerUnit()
301
            if not IsUnitType(u, UNIT_TYPE_HERO) and not IsUnitType(u, UNIT_TYPE_STRUCTURE) then
302
                call RecycleUnitDelayed(u, DeathTime(u))
303
            endif
304
            set u = null
305
        endfunction
306
    endif
307
308
    private module Init
309
310
        static if LIBRARY_Table then
311
            static TableArray hash
312
            static Table table
313
        else
314
            static hashtable hash = InitHashtable()
315
        endif
316
317
        private static method onInit takes nothing returns nothing
318
            local rect bounds = GetWorldBounds()
319
            static if AUTO_RECYCLE_DEAD then
320
                static if LIBRARY_RegisterPlayerUnitEvent then
321
                    static if RPUE_VERSION_NEW then
322
                        call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_DEATH, function OnDeath)
323
                    else
324
                        call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_DEATH, function OnDeath)
325
                    endif
326
                else
327
                    local trigger t = CreateTrigger()
328
                    local code c = function OnDeath
329
                    local integer i = 16
330
                    loop
331
                        set i = i - 1
332
                        call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_DEATH, null)
333
                        exitwhen i == 0
334
                    endloop
335
                    call TriggerAddCondition(t, Filter(c))
336
                endif
337
            endif
338
            static if LIBRARY_Table then
339
                set hash = TableArray[0x2000]
340
                set table = Table.create()
341
            endif
342
            // Hides recycled units at the top of the map beyond reach of the camera
343
            set unitCampX = 0.00
344
            set unitCampY = GetRectMaxY(bounds) + 1000.00
345
            call RemoveRect(bounds)
346
            set bounds = null
347
        endmethod
348
349
    endmodule
350
351
    private struct S extends array
352
        implement Init
353
    endstruct
354
355
356
endlibrary
9 years
Oh, nice to know that you already have conceived the concept a long time ago =D Though the two resources have a bit different approach.
Btw, mine does not account for summoned units and structures yet.
8 years
Great, really need a covered space for the equipment other