UnitRecycler

Codes & Snippets

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

library UnitRecycler /* v1.3b


    |=============|
    | Author: AGD |
    |=============|

    */requires /*

    */ReviveUnit                        /*  https://wc3modding.info/4469/snippet-reviveunit/
    */UnitDex                           /*  http://www.hiveworkshop.com/threads/system-unitdex-unit-indexer.248209/
    */optional Table                    /*  https://wc3modding.info/4611/snippet-new-table/
    */optional TimerUtils               /*  https://wc3modding.info/5352/hibrid-timerutils/
    */optional RegisterPlayerUnitEvent  /*  https://wc3modding.info/4907/snippet-registerplayerunitevent/

    This system is important because CreateUnit() is one of the most processor-intensive function in
    the game and there are reports that even after they are removed, they still leave some bit of memory
    consumption (0.04 KB) on the RAM. Therefore it would be very helpful if you can minimize unit
    creation or so. This system also allows you to recycle dead units to avoid permanent 0.04 KB memory
    leak for each future CreateUnit() call.                                                                 */

//! novjass

    [Credits]
        Aniki - For suggesting ideas on further improvements


    |-----|
    | API |
    |-----|

        function GetRecycledUnit takes player owner, integer rawCode, real x, real y, real facing returns unit/*
            - Returns unit of specified ID from the stock of recycled units. If there's none in the stock that
              matched the specified unit's rawcode, it will create a new unit instead
            - Returns null if the rawcode's unit-type is a hero or non-existent

      */function GetRecycledUnitEx takes player owner, integer rawCode, real x, real y, real facing returns unit/*
            - Works similar to GetRecycledUnit() except that if the input rawcode's unit-type is a hero, it will
              be created via CreateUnit() instead
            - You can use this as an alternative to CreateUnit()

      */function RecycleUnit takes unit u returns boolean/*
            - Recycles the specified unit and returns a boolean value depending on the success of the operation
            - Does nothing to hero units

      */function RecycleUnitEx takes unit u returns boolean/*
            - Works similar to RecycleUnit() except that if <u> is not recyclable, it will be removed via
              RemoveUnit() instead
            - You can use this as an alternative to RemoveUnit()

      */function RecycleUnitDelayed takes unit u, real delay returns nothing/*
            - Recycles the specified unit after <delay> seconds

      */function RecycleUnitDelayedEx takes unit u, real delay returns nothing/*
            - Works similar to RecycleUnitDelayed() except that it calls RecycleUnitEx() instead of RecycleUnit()

      */function UnitAddToStock takes integer rawCode returns boolean/*
            - Creates a unit of type ID and adds it to the stock of recycled units then returns a boolean value
              depending on the success of the operation

*///! endnovjass

    //CONFIGURATION SECTION


    globals

/*      The owner of the stocked/recycled units
*/      private constant player OWNER               = Player(15)

/*      Determines if dead units will be automatically recycled
        after a delay designated by the <constant function
        DeathTime below>
*/      private constant boolean AUTO_RECYCLE_DEAD  = true

/*      Error debug message prefix
*/      private constant string ERROR_PREFIX        = "|CFFFF0000Operation Failed: "

    endglobals

    /* The delay before dead units will be recycled in case AUTO_RECYCLE_DEAD == true */
    static if AUTO_RECYCLE_DEAD then
        private constant function DeathTime takes unit u returns real
            /*if <condition> then
                  return someValue
              elseif <condition> then
                  return someValue
              endif                 */
            return 8.00
        endfunction
    endif

    /* When recycling a unit back to the stock, these resets will be applied to the
       unit. You can add more actions to this or you can delete this textmacro if you
       don't need it.                                                                       */
        //! textmacro_once UNIT_RECYCLER_RESET
            call SetUnitScale(u, 1, 0, 0)
            call SetUnitVertexColor(u, 255, 255, 255, 255)
            call SetUnitFlyHeight(u, GetUnitDefaultFlyHeight(u), 0)
        //! endtextmacro


    //END OF CONFIGURATION

    /*==== Do not do changes below this line if you're not so sure on what you're doing ====*/
    native UnitAlive takes unit u returns boolean

    globals
        private keyword S
        private integer count = 0
        private real unitCampX
        private real unitCampY
        private integer array stack
        private boolean array stacked
    endglobals

    private function GetIndex takes integer rawCode returns integer
        static if LIBRARY_Table then
            local integer i = S.table.integer[rawCode]
            if i == 0 then
                set count = count + 1
                set S.table.integer[rawCode] = count
                set i = count
            endif
        else
            local integer i = LoadInteger(S.hash, -1, rawCode)
            if i == 0 then
                set count = count + 1
                call SaveInteger(S.hash, -1, rawCode, count)
                set i = count
            endif
        endif
        return i
    endfunction

    static if DEBUG_MODE then
        private function Debug takes string msg returns nothing
            call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "|CFFFFCC00[Unit Recycler]|R " + msg)
        endfunction
    endif

    function GetRecycledUnit takes player owner, integer rawCode, real x, real y, real facing returns unit
        local integer i
        if not IsHeroUnitId(rawCode) then
            set i = GetIndex(rawCode)
            if stack[i] == 0 then
                set bj_lastCreatedUnit = CreateUnit(owner, rawCode, x, y, facing)
                debug call Debug(GetUnitName(bj_lastCreatedUnit) + " stock is empty, creating new " + GetUnitName(bj_lastCreatedUnit))
            else
                static if LIBRARY_Table then
                    set bj_lastCreatedUnit = S.hash[i].unit[stack[i]]
                else
                    set bj_lastCreatedUnit = LoadUnitHandle(S.hash, i, stack[i])
                endif
                set stacked[GetUnitId(bj_lastCreatedUnit)] = false
                call PauseUnit(bj_lastCreatedUnit, false)
                call SetUnitOwner(bj_lastCreatedUnit, owner, true)
                call SetUnitPosition(bj_lastCreatedUnit, x, y)
                call SetUnitFacing(bj_lastCreatedUnit, facing)
                set stack[i] = stack[i] - 1
                debug call Debug("Retrieving " + GetUnitName(bj_lastCreatedUnit) + " from stock")
            endif
            debug if bj_lastCreatedUnit == null then
                debug call Debug(ERROR_PREFIX + "Specified unit-type does not exist")
            debug endif
        else
            debug call Debug(ERROR_PREFIX + "Attemp to retrieve a hero unit")
            return null
        endif
        return bj_lastCreatedUnit
    endfunction

    function GetRecycledUnitEx takes player owner, integer rawCode, real x, real y, real facing returns unit
        if not IsHeroUnitId(rawCode) then
            return GetRecycledUnit(owner, rawCode, x, y, facing)
        endif
        debug call Debug("Cannot retrieve a hero unit, creating new unit")
        return CreateUnit(owner, rawCode, x, y, facing)
    endfunction

    function RecycleUnit takes unit u returns boolean
        local integer rawCode = GetUnitTypeId(u)
        local integer uDex = GetUnitId(u)
        local integer i
        if not IsHeroUnitId(rawCode) and not stacked[uDex] and u != null then
            set i = GetIndex(rawCode)
            if not UnitAlive(u) and not ReviveUnit(u) then
                debug call Debug(ERROR_PREFIX + "Unable to recycle unit: Unable to revive dead unit")
                return false
            endif
            set stacked[uDex] = true
            call PauseUnit(u, true)
            call SetUnitOwner(u, OWNER, true)
            call SetUnitX(u, unitCampX)
            call SetUnitY(u, unitCampY)
            call SetUnitFacing(u, 270)
            call SetWidgetLife(u, GetUnitState(u, UNIT_STATE_MAX_LIFE))
            call SetUnitState(u, UNIT_STATE_MANA, GetUnitState(u, UNIT_STATE_MAX_MANA))
            //! runtextmacro optional UNIT_RECYCLER_RESET()
            set stack[i] = stack[i] + 1
            static if LIBRARY_Table then
                set S.hash[i].unit[stack[i]] = u
            else
                call SaveUnitHandle(S.hash, i, stack[i], u)
            endif
            debug call Debug("Successfully recycled " + GetUnitName(u))
            return true
        debug else
            debug if stacked[uDex] then
                debug call Debug(ERROR_PREFIX + "Attempt to recycle an already recycled unit")
            debug elseif u == null then
                debug call Debug(ERROR_PREFIX + "Attempt to recycle a null unit")
            debug else
                debug call Debug(ERROR_PREFIX + "Attempt to recycle a hero unit")
            debug endif
        endif
        return false
    endfunction

    function RecycleUnitEx takes unit u returns boolean
        if not RecycleUnit(u) then
            call RemoveUnit(u)
            debug call Debug("Cannot recycle the specified unit, removing unit")
            return false
        endif
        return true
    endfunction

    //! textmacro DELAYED_RECYCLE_TYPE takes EX
    private function RecycleTimer$EX$ takes nothing returns nothing
        local timer t = GetExpiredTimer()
        static if LIBRARY_TimerUtils then
            call RecycleUnit$EX$(GetUnitById(GetTimerData(t)))
            call ReleaseTimer(t)
        else
            local integer key = GetHandleId(t)
            static if LIBRARY_Table then
                call RecycleUnit$EX$(S.hash[0].unit[key])
                call S.hash[0].remove(key)
            else
                call RecycleUnit$EX$(LoadUnitHandle(S.hash, 0, key))
                call RemoveSavedHandle(S.hash, 0, key)
            endif
            call DestroyTimer(t)
        endif
        set t = null
    endfunction

    function RecycleUnitDelayed$EX$ takes unit u, real delay returns nothing
        static if LIBRARY_TimerUtils then
            call TimerStart(NewTimerEx(GetUnitId(u)), delay, false, function RecycleTimer$EX$)
        else
            local timer t = CreateTimer()
            static if LIBRARY_Table then
                set S.hash[0].unit[GetHandleId(t)] = u
            else
                call SaveUnitHandle(S.hash, 0, GetHandleId(t), u)
            endif
            call TimerStart(t, delay, false, function RecycleTimer$EX$)
            set t = null
        endif
    endfunction
    //! endtextmacro

    //! runtextmacro DELAYED_RECYCLE_TYPE("")
    //! runtextmacro DELAYED_RECYCLE_TYPE("Ex")

    function UnitAddToStock takes integer rawCode returns boolean
        local unit u
        local integer i
        if not IsHeroUnitId(rawCode) then
            set u = CreateUnit(OWNER, rawCode, unitCampX, unitCampY, 270)
            if u != null then
                set i = GetIndex(rawCode)
                call SetUnitX(u, unitCampX)
                call SetUnitY(u, unitCampY)
                call PauseUnit(u, true)
                set stacked[GetUnitId(u)] = true
                set stack[i] = stack[i] + 1
                static if LIBRARY_Table then
                    set S.hash[i].unit[stack[i]] = u
                else
                    call SaveUnitHandle(S.hash, i, stack[i], u)
                endif
                debug call Debug("Adding " + GetUnitName(u) + " to stock")
                return true
            debug else
                debug call Debug(ERROR_PREFIX + "Attemp to stock a null unit")
            endif
            set u = null
        debug else
            debug call Debug(ERROR_PREFIX + "Attemp to stock a hero unit")
        endif
        return false
    endfunction

    static if AUTO_RECYCLE_DEAD then
        private function OnDeath takes nothing returns nothing
            local unit u = GetTriggerUnit()
            if not IsUnitType(u, UNIT_TYPE_HERO) and not IsUnitType(u, UNIT_TYPE_STRUCTURE) then
                call RecycleUnitDelayed(u, DeathTime(u))
            endif
            set u = null
        endfunction
    endif

    private module Init

        static if LIBRARY_Table then
            static TableArray hash
            static Table table
        else
            static hashtable hash = InitHashtable()
        endif

        private static method onInit takes nothing returns nothing
            local rect bounds = GetWorldBounds()
            static if AUTO_RECYCLE_DEAD then
                static if LIBRARY_RegisterPlayerUnitEvent then
                    static if RPUE_VERSION_NEW then
                        call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_DEATH, function OnDeath)
                    else
                        call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_DEATH, function OnDeath)
                    endif
                else
                    local trigger t = CreateTrigger()
                    local code c = function OnDeath
                    local integer i = 16
                    loop
                        set i = i - 1
                        call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_DEATH, null)
                        exitwhen i == 0
                    endloop
                    call TriggerAddCondition(t, Filter(c))
                endif
            endif
            static if LIBRARY_Table then
                set hash = TableArray[0x2000]
                set table = Table.create()
            endif
            // Hides recycled units at the top of the map beyond reach of the camera
            set unitCampX = 0.00
            set unitCampY = GetRectMaxY(bounds) + 1000.00
            call RemoveRect(bounds)
            set bounds = null
        endmethod

    endmodule

    private struct S extends array
        implement Init
    endstruct


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