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

[Snippet] Unit Recycler

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
UNIT RECYCLER v1.4.1


This system allows you to recycle units. This can be helpful to Hero Defense maps like AoS, Dota, and similar maps which
requires periodic spawning/creation of certain units. This is also helpful in other cases as well. See the attached map below for the demo.
[Since version 1.4] This system also takes into consideration the facing angles of units.


Note (Please Read):
- This only supports non-hero units and units which leave a corpse (those units whose death type is "Can raise, does decay"). So if you want to make a unit recyclable, set its death type in the Object Editor to "Can raise, does decay".
- When retrieving/recycling a unit, this does not fire a unit index/deindex event, but it will if you did not copy the DETECT_LEAVE_ABILITY from the UnitDex library into your map although not doing so would cause problems to this system.


Script
JASS:
library UnitRecycler /* v1.4.1 https://www.hiveworkshop.com/threads/286701/


    */requires /*

    */ReviveUnit                        /*  https://www.hiveworkshop.com/threads/186696/
    */Table                             /*  https://www.hiveworkshop.com/threads/188084/
    */optional RegisterPlayerUnitEvent  /*  https://www.hiveworkshop.com/threads/250266/
    */optional ErrorMessage             /*  https://github.com/nestharus/JASS/blob/master/jass/Systems/ErrorMessage/main.j


    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.

    As of v1.4, the system now takes into consideration the facing angle of units - like most dummy
    recycling systems do. But it is recommended to use DummyRecyclers for dummy units since they are
    specially made for such uses, and use this UnitRecycler for the other non-dummy units.


    [Credits]
        AGD - Author
        Aniki - For suggesting ideas on further improvements
        Other guys in the warcraft modding community - for discovering the permanent memory leak from units

    *///! novjass

    |-----|
    | 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
    /*
        Angle group count per unit type
        If you don't need the precision in unit facing angles, you should set this
        value to 1
        (Note: The maximum number of different kinds of unit type that you can use
        with this system is limited to <8190/ANGLE_COUNT - 1> )                         */
        private constant integer ANGLE_COUNT = 8

    endglobals

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

    /* Filters units allowed for recycling                                              */
    private function UnitTypeFilter takes unit u returns boolean
        return not IsUnitIllusion(u) and not IsUnitType(u, UNIT_TYPE_SUMMONED)
    endfunction

    /* 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 module if you
       don't need it.                                                                   */
    private module UnitRecyclerResets
        call SetUnitScale(u, 1, 0, 0)
        call SetUnitVertexColor(u, 255, 255, 255, 255)
        call SetUnitFlyHeight(u, GetUnitDefaultFlyHeight(u), 0)
    endmodule


    //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 constant real ANGLE_INTERVAL = 360.00/ANGLE_COUNT
        private constant real HALF_INTERVAL = ANGLE_INTERVAL/2.00
        private real unitCampX
        private real unitCampY
    endglobals

    private struct List extends array

        unit unit
        thistype recycler
        thistype prev
        thistype next
        debug static Table stocked

        static constant method operator head takes nothing returns thistype
            return 0
        endmethod

        method stockUnit takes unit u returns nothing
            local thistype node = head.recycler
            local thistype last = this.prev
            set head.recycler = node.recycler
            set this.prev = node
            set last.next = node
            set node.prev = last
            set node.next = this
            set node.unit = u
            call PauseUnit(u, true)
            call SetUnitX(u, unitCampX)
            call SetUnitY(u, unitCampY)
            debug set stocked.boolean[GetHandleId(u)] = true
        endmethod

        method addUnit takes unit u, real angle returns boolean
            if u != null and not IsUnitType(u, UNIT_TYPE_HERO) and UnitTypeFilter(u) then
                if not UnitAlive(u) and not ReviveUnit(u) then
                    static if LIBRARY_ErrorMessage then
                        debug call ThrowWarning(true, "UnitRecycler", "addUnit()", "thistype", GetHandleId(u), "Unable to recycle unit: Unable to revive dead unit")
                    endif
                    return false
                endif
                call this.stockUnit(u)
                call SetUnitFacing(u, angle)
                call SetUnitOwner(u, OWNER, true)
                call SetWidgetLife(u, GetUnitState(u, UNIT_STATE_MAX_LIFE))
                call SetUnitState(u, UNIT_STATE_MANA, GetUnitState(u, UNIT_STATE_MAX_MANA))
                implement optional UnitRecyclerResets
                return true
            endif
            return false
        endmethod

        method getUnit takes player owner, integer id, real x, real y, real angle returns unit
            local thistype first
            local thistype next
            local real facing
            local real deltaAngle
            if not IsHeroUnitId(id) then
                set first = this.next
                set deltaAngle = RAbsBJ(GetUnitFacing(first.unit) - angle)
                if deltaAngle > 180.00 then
                    set deltaAngle = 360.00 - deltaAngle
                endif
                if first == this or deltaAngle > HALF_INTERVAL then
                    set bj_lastCreatedUnit = CreateUnit(owner, id, x, y, angle)
                else
                    set bj_lastCreatedUnit = first.unit
                    set first.unit = null
                    set next = first.next
                    set next.prev = this
                    set this.next = next
                    call SetUnitOwner(bj_lastCreatedUnit, owner, true)
                    call SetUnitPosition(bj_lastCreatedUnit, x, y)
                    call SetUnitFacing(bj_lastCreatedUnit, angle)
                    call PauseUnit(bj_lastCreatedUnit, false)
                    debug call stocked.boolean.remove(GetHandleId(bj_lastCreatedUnit))
                endif
                return bj_lastCreatedUnit
            endif
            return null
        endmethod

        static method init takes nothing returns nothing
            local thistype this = 0
            set thistype(8190).recycler = 0
            loop
                set this.recycler = this + 1
                set this = this + 1
                exitwhen this == 8190
            endloop
            debug set stocked = Table.create()
        endmethod

    endstruct

    private struct UnitRecycler extends array

        private static Table rawCodeIdTable
        private static Table timerTable
        private static integer rawCodeCount = 0
        private static integer array position
        private static integer array stackSize
        private static integer array indexStack
        private static List array head

        private static method getRawCodeId takes integer rawCode returns integer
            local integer i = rawCodeIdTable[rawCode]
            if i == 0 then
                set rawCodeCount = rawCodeCount + 1
                set rawCodeIdTable[rawCode] = rawCodeCount
                set i = rawCodeCount
            endif
            return i
        endmethod

        private static method getHead takes integer id, integer index returns List
            local List this = head[id*ANGLE_COUNT + index]
            if this == 0 then
                set this = List.head.recycler
                set List.head.recycler = this.recycler
                set this.prev = this
                set this.next = this
                set head[id*ANGLE_COUNT + index] = this
            endif
            return this
        endmethod

        private static method getListIndex takes integer id returns integer
            if stackSize[id] == 0 then
                if position[id] < ANGLE_COUNT - 1 then
                    set position[id] = position[id] + 1
                    return position[id]
                endif
                set position[id] = 0
                return 0
            endif
            set stackSize[id] = stackSize[id] - 1
            return indexStack[id*ANGLE_COUNT + stackSize[id]]
        endmethod

        static method stock takes integer rawCode returns boolean
            local integer id
            local integer index
            local unit u
            if not IsHeroUnitId(rawCode) then
                set id = getRawCodeId(rawCode)
                set index = getListIndex(id)
                set u = CreateUnit(OWNER, rawCode, 0.00, 0.00, index*ANGLE_INTERVAL)
                if u != null and not IsUnitType(u, UNIT_TYPE_HERO) and UnitTypeFilter(u) then
                    call getHead(id, index).stockUnit(u)
                    set u = null
                    return true
                endif
            endif
            return false
        endmethod

        static method add takes unit u returns boolean
            local integer id = getRawCodeId(GetUnitTypeId(u))
            local integer index = getListIndex(id)
            return getHead(id, index).addUnit(u, index*ANGLE_INTERVAL)
        endmethod

        static method get takes player owner, integer rawCode, real x, real y, real angle returns unit
            local integer id = getRawCodeId(rawCode)
            local integer index = R2I(angle/ANGLE_INTERVAL)
            if angle - ANGLE_INTERVAL*index > ANGLE_INTERVAL/2.00 then
                if index < ANGLE_COUNT - 1 then
                    set index = index + 1
                else
                    set index = 0
                endif
            endif
            set indexStack[id*ANGLE_COUNT + stackSize[id]] = index
            set stackSize[id] = stackSize[id] + 1
            return getHead(id, index).getUnit(owner, rawCode, x, y, angle)
        endmethod

        static method delayedRecycle takes nothing returns nothing
            local timer t = GetExpiredTimer()
            local integer key = GetHandleId(t)
            call add(timerTable.unit[key])
            call timerTable.unit.remove(key)
            call DestroyTimer(t)
            set t = null
        endmethod
        static method delayedRecycleEx takes nothing returns nothing
            local timer t = GetExpiredTimer()
            local integer key = GetHandleId(t)
            call add(timerTable.unit[key])
            call timerTable.unit.remove(key)
            call DestroyTimer(t)
            set t = null
        endmethod

        static method addDelayed takes unit u, real delay, code callback returns nothing
            local timer t = CreateTimer()
            set timerTable.unit[GetHandleId(t)] = u
            call TimerStart(t, delay, false, callback)
            set t = null
        endmethod

        static method init takes nothing returns nothing
            local rect bounds = GetWorldBounds()
            // Hide 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
            set rawCodeIdTable = Table.create()
            set timerTable = Table.create()
        endmethod

    endstruct

    /*========================================================================================================*/

    function GetRecycledUnit takes player owner, integer rawCode, real x, real y, real facing returns unit
        static if DEBUG_MODE and LIBRARY_ErrorMessage then
            call UnitRecycler.get(owner, rawCode, x, y, facing)
            call ThrowError(bj_lastCreatedUnit == null, "UnitRecycler", "GetRecycledUnit()", "", 0, "Specified unit type does not exist")
            call ThrowError(IsHeroUnitId(rawCode), "UnitRecycler", "GetRecycledUnit()", GetUnitName(bj_lastCreatedUnit), 0, "Specified unit type is a hero")
            return bj_lastCreatedUnit
        else
            return UnitRecycler.get(owner, rawCode, x, y, facing)
        endif
    endfunction

    function GetRecycledUnitEx takes player owner, integer rawCode, real x, real y, real facing returns unit
        if not IsHeroUnitId(rawCode) then
            return UnitRecycler.get(owner, rawCode, x, y, facing)
        endif
        static if LIBRARY_ErrorMessage then
            debug call ThrowWarning(true, "UnitRecycler", "GetRecycledUnitEx()", "", 0, "Cannot retrieve a hero unit, creating new unit")
        endif
        return CreateUnit(owner, rawCode, x, y, facing)
    endfunction

    function RecycleUnit takes unit u returns boolean
        static if LIBRARY_ErrorMessage then
            debug call ThrowError(List.stocked.boolean[GetHandleId(u)], "UnitRecycler", "RecycleUnit()", GetUnitName(u), 0, "Attempted to recycle an already recycled unit")
            debug call ThrowWarning(u == null, "UnitRecycler", "RecycleUnit()", "", 0, "Attempted to recycle a null unit")
            debug call ThrowWarning(IsHeroUnitId(GetUnitTypeId(u)), "UnitRecycler", "RecycleUnit()", GetUnitName(u), 0, "Attempted to recycle a hero unit")
            debug call ThrowWarning(not UnitTypeFilter(u), "UnitRecycler", "RecycleUnit()", GetUnitName(u), 0, "Attempted to recycle an invalid unit type")
        endif
        return UnitRecycler.add(u)
    endfunction

    function RecycleUnitEx takes unit u returns boolean
        static if LIBRARY_ErrorMessage then
            debug call ThrowError(List.stocked.boolean[GetHandleId(u)], "UnitRecycler", "RecycleUnitEx()", GetUnitName(u), 0, "Attempted to recycle an already recycled unit")
            debug call ThrowWarning(u == null, "UnitRecycler", "RecycleUnitEx()", "", 0, "Attempted to recycle a null unit")
            debug call ThrowWarning(not UnitTypeFilter(u), "UnitRecycler", "RecycleUnitEx()", GetUnitName(u), 0, "Attempted to recycle an invalid unit type")
        endif
        if not UnitRecycler.add(u) then
            call RemoveUnit(u)
            static if LIBRARY_ErrorMessage then
                debug call ThrowWarning(u != null, "UnitRecycler", "RecycleUnitEx()", GetUnitName(u), 0, "Cannot recycle the specified unit, removing unit")
            endif
            return false
        endif
        return true
    endfunction

    function RecycleUnitDelayed takes unit u, real delay returns nothing
        static if LIBRARY_ErrorMessage then
            debug call ThrowError(List.stocked.boolean[GetHandleId(u)], "UnitRecycler", "RecycleUnitDelayed()", GetUnitName(u), 0, "Attempted to recycle an already recycled unit")
            debug call ThrowWarning(u == null, "UnitRecycler", "RecycleUnitDelayed()", "", 0, "Attempted to recycle a null unit")
            debug call ThrowWarning(IsHeroUnitId(GetUnitTypeId(u)), "UnitRecycler", "RecycleUnitDelayed()", GetUnitName(u), 0, "Attempted to recycle a hero unit")
            debug call ThrowWarning(not UnitTypeFilter(u), "UnitRecycler", "RecycleUnitDelayed()", GetUnitName(u), 0, "Attempted to recycle an invalid unit type")
        endif
        call UnitRecycler.addDelayed(u, delay, function UnitRecycler.delayedRecycle)
    endfunction

    function RecycleUnitDelayedEx takes unit u, real delay returns nothing
        static if LIBRARY_ErrorMessage then
            debug call ThrowError(List.stocked.boolean[GetHandleId(u)], "UnitRecycler", "RecycleUnitDelayedEx()", GetUnitName(u), 0, "Attempted to recycle an already recycled unit")
            debug call ThrowWarning(u == null, "UnitRecycler", "RecycleUnitDelayedEx()", "", 0, "Attempted to recycle a null unit")
            debug call ThrowWarning(not UnitTypeFilter(u), "UnitRecycler", "RecycleUnitDelayedEx()", GetUnitName(u), 0, "Attempted to recycle an invalid unit type")
        endif
        call UnitRecycler.addDelayed(u, delay, function UnitRecycler.delayedRecycleEx)
    endfunction

    function UnitAddToStock takes integer rawCode returns boolean
        static if LIBRARY_ErrorMessage then
            debug local unit u = CreateUnit(OWNER, rawCode, 0, 0, 0)
            debug call ThrowWarning(u == null, "UnitRecycler", "UnitAddToStock()", "", 0, "Attempted to stock a non-existent unit type")
            debug call ThrowWarning(IsHeroUnitId(rawCode), "UnitRecycler", "UnitAddToStock()", GetUnitName(u), 0, "Attempted to stock a hero unit")
            debug call ThrowWarning(not UnitTypeFilter(u), "UnitRecycler", "UnitAddToStock()", GetUnitName(u), 0, "Attempted to stock an invalid unit type")
            debug call RemoveUnit(u)
            debug set u = null
        endif
        return UnitRecycler.stock(rawCode)
    endfunction

    /*========================================================================================================*/

    private module Init
        private static method onInit takes nothing returns nothing
            call init()
        endmethod
    endmodule

    private struct Initializer extends array

        static if AUTO_RECYCLE_DEAD then
            private static method onDeath takes nothing returns nothing
                local unit u = GetTriggerUnit()
                static if LIBRARY_ErrorMessage then
                    debug call ThrowError(List.stocked.boolean[GetHandleId(u)], "UnitRecycler", "", GetUnitName(u), 0, "A unit in stock has been killed!")
                endif
                if UnitTypeFilter(u) and not IsUnitType(u, UNIT_TYPE_HERO) and not IsUnitType(u, UNIT_TYPE_STRUCTURE) then
                    call RecycleUnitDelayedEx(u, DeathTime(u))
                endif
                set u = null
            endmethod

            private static method autoRecycler takes nothing returns nothing
                static if AUTO_RECYCLE_DEAD then
                    static if LIBRARY_RegisterPlayerUnitEvent then
                        call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_DEATH, function thistype.onDeath)
                    else
                        local trigger t = CreateTrigger()
                        local code c = function thistype.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))
                        set t = null
                    endif
                endif
            endmethod
        endif

        private static method init takes nothing returns nothing
            call List.init()
            call UnitRecycler.init()
            static if AUTO_RECYCLE_DEAD then
                call autoRecycler()
            endif
            call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "|CFFFFCC00UnitRecycler|R library is ready!")
        endmethod
        implement Init

    endstruct

    static if DEBUG_MODE and LIBRARY_ErrorMessage then
        private function DisplayError takes unit removedUnit returns nothing
            call ThrowError(List.stocked.boolean[GetHandleId(removedUnit)], "UnitRecycler", "RemoveUnit()", GetUnitName(removedUnit), 0, "Attempted to remove a stocked unit")
        endfunction

        hook RemoveUnit DisplayError
    endif


endlibrary



JASS:
struct SpawnCreepsPeriodic extends array

    private static constant real SPAWN_INTERVAL = 30.00
    private static constant integer ARMY_COUNT = 5
    // some other vars

    private static method spawn takes nothing returns nothing
        local integer id = 0
        local integer i
        loop
            set i = 0
            loop
                set i = i + 1
                call IssueTargetOrder(GetRecycledUnit(Player(id), creepType[id], PlayerX[id], PlayerY[id], 0), "attack", ENEMY_BASE[id])
                exitwhen i == ARMY_COUNT
            endloop
            set id = id + 1
            exitwhen id == bj_MAX_PLAYER_SLOTS
        endloop
    endmethod

    private static method prepareStock takes integer count returns nothing
        local integer id = 0
        local integer i
        loop
            set i = 0
            loop
                set i = i + 1
                call UnitAddToStock(creepType[id])
                exitwhen i == count
            endloop
            set id = id + 1
            exitwhen id == bj_MAX_PLAYER_SLOTS
        endloop
    endmethod

    private static method onInit takes nothing returns nothing
        call prepareStock(30)
        call TimerStart(CreateTimer(), SPAWN_INTERVAL, true, function thistype.spawn)
    endmethod

endstruct



v1.4.1
- Fixed a possible compile error
- Updated dependencies (RegisterPlayerUnitEvent)

v1.4
- Recoded most part of the system
- Added a new feature where the system takes into consideration the facing angle of the units - like what some dummy recyclers do
- Added a more detailed debug messages
- Changed the system's data structure (Now uses queues to accommodate the new feature)
- Other changes

v1.3c
- Removed unnecessary debug messages
- Added a new function for users to filter the types of units not valid for recycling, by default it restricts summoned and illusion units
- Other changes

v1.3b
- Recycled units are now hidden with an offset from the map bounds instead of the camera bounds
- GetRecycledUnit() is now set to bj_lastCreatedUnit instead of U
- Added a configuration for the users where they can specify unit reset actions whenever a unit is recycled
- Now uses TableArray instead of a HashTable
- Optimized some parts of the script
- Other changes

v1.3
- Added a new API function GetRecycledUnitEx() and RecycleUnitDelayedEx()
- Changed some variable namings
- Updated/changes some debug messages
- Other changes

v1.2c
- Replaced RegisterAnyUnitEvent and Mag's RegisterPlayerUnitEvent with Bannar's RegisterPlayerUnitEvent
- Updated dependencies in the demo map
- Added a new API function RecycleUnitEx()
- Simplified one nested if statements using 'and'
- Other changes

v1.2b
- Added RegisterPlayerUnitEvent as an optional requirement
- RecycleUnit() now checks if a unit is dead before recycling - if it is, it revives it first
- Automatic recycling of dead units is now configurable
- Dead units recycling delay now has a better configuration i.e. a constant function that returns real instead of a constant global variable
- GetRecycledUnit() and UnitAddToStock() now checks first if the unitid entered is a heroid before proceeding further instead of removing the unit if the created unit is a hero
- The delayed death recycler now uses RecycleUnitDelayed() instead of a TriggerSleepAction()
- Other changes

v1.2
- Added TimerUtils as an optional requirement
- Function RecycleUnitDelayed() now returns nothing
- Now uses the native UnitAlive instead of IsUnitType(unit, UNIT_TYPE_DEAD)

v1.1b
- Implemented table as an optional requirement
- Fixed an error regarding indexing units' rawcodes

v1.1
- Removed the fake death and the bounty simulator library, but instead uses Revive Unit
- No longer uses ShowUnit but instead hides the unit stock at the top of the map beyond the reach of camera
- Removed the function OnUnitRecycleInit
- Now uses module initializer
- Removed the optional RawCodeIndexer

v1.0c
- Added an oprional library requirement named RawCodeIndexer
- Added some debug messages for debug mode
- Added an API for registering a fake death event

v1.0b
- Made the UnitIndexerGUI library required rather than optional
- Added an API that enables users to add their custom initialization
- Some fixes
- Minor changes

v1.0
- First Upload
 

Attachments

  • Unit Recycler v1.3b.w3x
    56.3 KB · Views: 218
  • Unit Recycler v1.3c.w3x
    56.6 KB · Views: 219
  • Unit Recycler v1.4.w3x
    50.3 KB · Views: 217
  • Unit Recycler v1.4.1.w3x
    44.9 KB · Views: 116
Last edited:
Level 13
Joined
Nov 7, 2014
Messages
571
Hm...
JASS:
function have_you_tried_this takes nothing returns nothing
    local unit u

    call UnitAddToStock('hfoo') // Footman
    call UnitAddToStock('hfoo')
    call UnitAddToStock('hfoo')

    call UnitAddToStock('ogru') // Grunt
    call UnitAddToStock('ogru')

    set u = GetRecycledUnit(Player(0), 'hfoo', 0.0, 0.0, 270.0)
    call BJDebugMsg(GetUnitName(u)) // Grunt??
endfunction

You probably want a stack per kind of unit ('hfoo', 'ogru', etc.), not... whatever it is that you got now.
 
PurgeandFire wrote this library. [Snippet] ReviveUnit
I think I would feel better using this over making a FakeDeath event. It's so specific and not compatible with any other systems that the user needs to build all his death code triggers around this system.
A demo with examples would help. :)
Ensure your target group -- is it jassers or gui users? It seems not very clear because jass users actually tend to use jass indexers and not use udg_variables also. And gui user don't usually registers "code" functions for usage.
I don't feel this is currently a real UnitRecycler. PurgeandFire's seems a bit more. This system actually only fakes everthing and just hides the unit until usage again. I'm not sure if I'm a fan of this concept.
I fail to see the usefulness of add a unit to stock. The system somwhoe tries not to create new units.

Your system basicly shares the same goal as this [Snippet] CreepRespawn (Unit Recycling).
I'm not sure if both is needed. How ever I personaly prefer to have a stack over a forced timed reviving. What do you, or other members think?
 
Ah k, I see. ^^ But ehm the one linked to TriggerHappy's is also under "Submissions" at the moment, not approved.
But from my view your both codes currently aim a similar goal with a different approach as far as I read and understood the codes.
This one uses a stack with, and asks the user to add/remove from the stack per function calls, in TriggerHappy's there is an automated behaviour with respawning and the user has to define only the reviving duration.

I'm uncertain if both methods are really needed, and if what I said is correctly I actually prefer having the stack. But what I not prefer in this stack system is that "fake death", as it's not compatible with regular systems that work with unit deaths, but needs addons and more caring for to work correctly.
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
UPDATED to v1.1

- Removed the fake death and the bounty simulator library
- But instead uses Revive Unit
- No longer uses ShowUnit but instead hides the unit stock at the top of the map beyond the reach of camera
- Removed the function OnUnitRecycleInit
- Now uses module initializer
- Removed the optional RawCodeIndexer
 
This native might be useful a bit:
constant native IsHeroUnitId takes integer unitId returns boolean
to not to create an invalid stock unit in first place, instead of removing it after creation.

What's exactly the purpose of:
function UnitAddToStock takes integer ID returns boolean
what I mean, it does not make a difference if the user just directly uses the GetRecycledUnit function in the moment he needs one, or?

JASS:
    private function OnDeath takes nothing returns nothing
        local unit u = GetTriggerUnit()
        if not IsHero(u) then
            call TriggerSleepAction(DEATH_TIME)
            if ReviveUnit(u) and RecycleUnit(u) then
            endif
        endif
        set u = null
    endfunction
TriggerSleepAction is not acceptable for such purposes, because it's not accurate, and it also does not wait if a client is for example lagging in a multiplayer game.
Timers is the way to go... or could not just be this called (?) function RecycleUnitDelayed takes unit u, real delay returns nothing
But furthermore, I'm not sure the auto recycle does need to be forced for the user. It may be handy, but I think it can be also good that the user can disable the auto-recylcing option after a constant duration, if he wants to handle it individualy by himself.
For example he could set the duration to -1, or so, and you might check it with a static if, if you need to create an death trigger for it, or not.
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
What's exactly the purpose of:
function UnitAddToStock takes integer ID returns boolean
what I mean, it does not make a difference if the user just directly uses the GetRecycledUnit function in the moment he needs one, or?
Yes you can directly do GetRecycledUnit() if you want no hassle but if there no units of that type in the stock yet, it will create one which means more probable lag since CreateUnit() is probably heavier while using UnitAddToStock() on the other hand reduces a nice amount of lag. Imagine it like preloading a number of units at map init so that GetRecycledUnit() can go smoother.

About the others, yes, will do.
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
UPDATED to v1.2b
- Added RegisterPlayerUnitEvent as an optional requirement
- RecycleUnit() now checks if a unit is dead before recycling - if it is, it revives it first
- Automatic recycling of dead units is now configurable
- Dead units recycling delay now has a better configuration i.e. a constant function that returns real instead of a constant global variable
- GetRecycledUnit() and UnitAddToStock() now checks first if the unitid entered is a heroid before proceeding further instead of removing the unit if the created unit is a hero
- The delayed death recycler now uses RecycleUnitDelayed() instead of a TriggerSleepAction()
- Other changes
 
Level 13
Joined
Nov 7, 2014
Messages
571
Could rewrite this
JASS:
    if LoadInteger(S.hash, 0, rawCode) == 0 then
        set count = count + 1
        call SaveInteger(S.hash, 0, rawCode, count)
    endif
    return LoadInteger(S.hash, 0, rawCode)

to something like this (1 less hashtable call):
JASS:
set i = LoadInteger(S.hash, 0, rawCode)
if i == 0 then
    set count = count + 1
    call SaveInteger(S.hash, 0, rawCode, count)
    set i = count
endif
return i

And similarly this:
JASS:
if S.hash[0].integer[rawCode] == 0 then
    set count = count + 1
    set S.hash[0].integer[rawCode] = count
endif
return S.hash[0].integer[rawCode]

to something like this (4 less hashtable calls):
JASS:
set i = S.hash[rawCode]
if i == 0 then
    set count = count + 1
    set S.hash[rawCode] = count
    set i = count
endif
return i


You are already using a hashtable, why have the UnitDex requirement just to store a boolean
set stacked[GetUnitId(U)] = false and 1 call to GetUnitById, when it can easily be done with the hashtable.


When recycling a unit you might consider doing something like this (scroll down to the macro):
JASS:
    //Every time a new dummy unit is retrieved, it will apply this resets
    //If it is redundant/you dont need it, remove it.
    //! textmacro DUMMY_UNIT_RESET
        call SetUnitScale(bj_lastCreatedUnit, 1, 0, 0)
        call SetUnitVertexColor(bj_lastCreatedUnit, 255, 255, 255, 255)
        call SetUnitAnimationByIndex(bj_lastCreatedUnit, 90)
    //! endtextmacro


JASS:
// Hides recycled units at the top of the map beyond reach of the camera
set unitCampX = 0.00
set unitCampY = GetCameraBoundMaxY() + 1000.00
Some maps adjust the camera bounds during gameplay. Could use the GetWorldBounds instead.
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
You are already using a hashtable, why have the UnitDex requirement just to store a boolean
set stacked[GetUnitId(U)] = false
and 1 call to GetUnitById, when it can easily be done with the hashtable.
Hmm, perhaps I will make UnitDex optional?
I'd still use array if possible over hashtable.

EDIT:
Making UnitDex optional would make the code ugly. I'll just leave it as is.
 
Last edited:

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
UPDATED to v1.3b

- Recycled units are now hidden with an offset from the map bounds instead of the camera bounds
- GetRecycledUnit() is now set to bj_lastCreatedUnit instead of U
- Added a configuration for the users where they can specify unit reset actions whenever a unit is recycled
- Now uses TableArray instead of a HashTable
- Optimized some parts of the script
- Other changes
 
Level 19
Joined
Dec 12, 2010
Messages
2,069
may I say that's bullshit right here? any properly handled handle get back to recycle and available to use again. you save literally nothing but increase the complexity of people's understanding of handles.

anytime game needs new handle it passes a handle's address to special function. this function checks current Incrementer value, see if it stands at available handle ID and returns this ID. Else it will go through searchgin for next available ID and THEN will consume 4 bytes for saving it. Then Incrementer value stored.

On destroying handle game checks if theres link counter of object == 0. If it is, then handleID being marked as available again, and Incrementer set to this handleID's index.

See the issue? game recycles handles itself. The only valid point is about much-time-consuming "CreateUnit()" native, although testing required, which one actually works faster.
 
may I say that's bullshit right here? any properly handled handle get back to recycle and available to use again
What are you talking? the handleid is not the handle.

The only valid point is about much-time-consuming "CreateUnit()" native
That is all what such systems is about. Just to avoid mass CreateUnit()... not because of id-releasing or not releasing.
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
may I say that's bullshit right here? any properly handled handle get back to recycle and available to use again. you save literally nothing but increase the complexity of people's understanding of handles.

anytime game needs new handle it passes a handle's address to special function. this function checks current Incrementer value, see if it stands at available handle ID and returns this ID. Else it will go through searchgin for next available ID and THEN will consume 4 bytes for saving it. Then Incrementer value stored.

On destroying handle game checks if theres link counter of object == 0. If it is, then handleID being marked as available again, and Incrementer set to this handleID's index.

See the issue? game recycles handles itself. The only valid point is about much-time-consuming "CreateUnit()" native, although testing required, which one actually works faster.
Actually, the purpose of the resource is not to deal with the handleid leak but to have a workaround with the permanent memory leak leftovers from removed units as stated here.
 
Create() is always a very heavy function..

And why you think it's impossible that blizzard doesn't free 100% of used memory for a unit?

I know that, Maker, Troll-Brain (I don't remember exactly all) tested a lot about leaks in past, (I'm not sure anymore where to find all the discussions). Even I personaly have not tested it, I know for sure they did multiple tests with it.

Why are you even starting a toxic discussion that just everything about it is bullshit without making any proof yourself... :S
 
Level 8
Joined
Jul 10, 2008
Messages
353
well been trying to use this, to test its efficiency vs mass created units, but it bugs somehow. I dont know which is at fault either this or the indexer but i can't recycle more than 1 units of the same id... is this how its suppose to work?!?

I think its the indexer tho.

Also this:
JASS:
        call SetUnitScale(bj_lastCreatedUnit, 1, 0, 0)
        call SetUnitVertexColor(bj_lastCreatedUnit, 255, 255, 255, 255)
        call SetUnitAnimationByIndex(bj_lastCreatedUnit, 90)
Are never restored, i had to it manually.
 
Last edited:

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
well been trying to use this, to test its efficiency vs mass created units, but it bugs somehow. I dont know which is at fault either this or the indexer but i can't recycle more than 1 units of the same id... is this how its suppose to work?!?

I think its the indexer tho.
May I ask if you copied the DETECT_LEAVE ability of UnitDex into your map?

Also this:
JASS:
        call SetUnitScale(bj_lastCreatedUnit, 1, 0, 0)
        call SetUnitVertexColor(bj_lastCreatedUnit, 255, 255, 255, 255)
        call SetUnitAnimationByIndex(bj_lastCreatedUnit, 90)
Are never restored, i had to it manually.
Tested it and it works fine on me. Can you show us the code?
 
Level 2
Joined
Feb 23, 2017
Messages
9
It feels like you do a lot of duplicate work in UnitAddToStock and RecycleUnit. I imagine, with only minor edits, you should be able to simplify UnitAddToStock to be something like:

JASS:
function UnitAddToStock takes integer id returns boolean
    return RecycleUnit(CreateUnit(OWNER, id, unitCampX, unitCampY, 270))
endfunction

This may not be optimal (you'll do some extra work with setting unit position, state and all the steps in UNIT_RECYCLER_RESET), but it makes the library simpler.
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
Updated to v1.4
- Recoded most part of the system
- Added a new feature where the system takes into consideration the facing angle of the units - like what some dummy recyclers do
- Added a more detailed debug messages
- Changed the system's data structure (Now uses queues to accommodate the new feature)
- Other changes
 
Level 20
Joined
May 16, 2012
Messages
635
Using this system with a damage detection system is buggy. When damaging units that have been recycled, they are healed instead of damaged (looking_for_help PDD). I'm not sure if it's an issue with this system or the damage system, i'm guessing that the problem is related with unit registration from the damage engine when a unit enters the map, and since the recycled units never leave it they bug it out. If the system creator or anyone have any solution to this problem, please share it.
 

NEL

NEL

Level 6
Joined
Mar 6, 2017
Messages
113
This library gave me an error if I don't have ErrorMessage lib even it's an optional.

upload_2019-2-6_13-38-29.png
 
  • Like
Reactions: AGD
Level 4
Joined
May 19, 2020
Messages
319
I personally have never seen any "palpable" complaints about units or evidence of actual damage that has been done to a map's performance, after all, there are always much larger factors to cause "visible" damage or whether units can contribute, they're just insects to worry about compared to other problems.
But even though many here at Hive feel that recycling a unit isn't demonstrably necessary even on larger or longer-lasting maps. There are some users, even the more technical ones, who still insist on emphasizing that unit creation is one of the slower functions of the game and that for each new unit created, a small piece of permanent memory is preserved.
And within everything I have understood, this memory usage would be something that does not depend on repairs due to leaks or removals.
If 0.04 KB is a really proven fact for permanence in the playingmap. Recycling unit maybe becomes a real solution only for maps with intensive consumption of units, the question would be, In a map without any kind of leakage..:
  • 1) From how many units created by Spawns on triggers or trained by a Player, would the game start to feel damage or decisive overload?
  • 2) Will pre-placed in WE units be considered within this memory congestion?
  • 3) Units created, but for "non-playable" (disabled) Players for dispute map, would they be considered part of permanent memory as well?

I was interested in this system, after all, use maps with about 3K units simultaneously active (on average), there is rare scarcity of resources or production and checks are constantly made for Re-spawns, if the map is even longer for hours, this amount it may be too abundant to the point where there really is one day that excess memory. I haven't noticed any problems yet, but I'm afraid... lol
I've checked other similar systems like:
[Snippet] ReviveUnit

But all these systems are always JASS, although this is possibly better or at least more complete and also more up to date...:
  • 4) I wonder if there are no possibilities to convert or adapt it for use through the GUI? I confess that I am one more clumsy in the knowledge of JASS/LUA's. There would be no possibilities to guide us about how it works through Events calls by GUI-triggers... I want to make requests for recycling dead units, with the purpose of respawning in so many seconds in some region of the map. Note: To whom it May concern... I know my case can be solved easily by adding reincarnation for the drives I want to recycle, just using the system
    "Unit Event" (by Bribe), but wanted something more practical.
 
Last edited:
Top