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

Dummy Recycler v1.25

vJASS version

JASS version (works with default/vanilla World Editor)


JASS:
library DummyRecycler /*
  
//                      DummyRecycler v1.25
//                          by Flux
// 
//  A system that recycles dummy units while considering their facing angle.
//  It can be used as attachment dummies for visual effects or as dummy caster.
//
//  Why is recycling a unit important?
//      Because creating a unit is is one of the slowest function in the game
//      and there are reports that will always leave a permanent tiny bit of
//      memory (0.04 KB).
//      On average, retrieving a pending Dummy is approximately 4x faster compared
//      to creating a new one and recycling a Dummy compared to removing it is
//      approximately 1.3x faster.
//      Furthermore, if you're using a lot of "Unit has entered map" events,
//      using this system will even result to even more better performance
//      because retrieving Dummy units does not cause that event to run.


    */ requires /*
       nothing
      
    */ optional Table/*
        if not found, this system will use a hashtable. Hashtables are limited to
        255 per map.
      
    */ optional WorldBounds /*
        if not found, this system will initialize its own Map Boundaries.
//
//
//  Features:
//
//    -- Dummy Sharing
//        When a Dummy List gets low on unit count, it will borrow Dummy Units
//        from the Dummy List with the highest unit count. The transfer is not
//        instant because the shared Dummy Unit has to turn to the appropriate
//        angle of its new Dummy List before it can be recycled.
//        See BORROW_REQUEST.
//
//    -- Self-balancing recycling algorithm
//        Recycled Dummy Units will be thrown to the List having the least number
//        of Dummy Units.
//
//    -- Recycling least used
//        Allows recycling a Dummy from the Dummy List with the highest
//        unit count. It is useful when the facing angle of the Dummy Unit
//        does not matter.
//        See GetRecycledDummyAnyAngle.
//
//    -- Self-adaptation
//        When there are no free Dummy Units from a Dummy List, it will end up creating
//        a new unit instead but that unit will be permanently added as a Dummy
//        Unit to be recycled increasing the overall total Dummy Unit count.
//
//    -- Count control
//        Allows limiting the overall number of Dummy Units.
//        See MAX_DUMMY_COUNT.
//
//    -- Delayed Recycle
//        Allows recycling Dummy Units after some delay to allocate time for the
//        death animation of Special Effects to be seen.
//        See DummyAddRecycleTimer.
//
// ******************************************************************
// ***************************** API: *******************************
// ******************************************************************
//
//  function GetRecycledDummy takes real x, real y, real z, real facing returns unit
//      - Retrieve an unused Dummy Unit from the List.
//      - The equivalent of CreateUnit.
//      - To use as a Dummy Caster, follow it with PauseUnit(dummy, false).
//
//  function GetRecycledDummyAnyAngle takes real x, real y, real z returns unit
//      - Use this function if the facing angle of the Dummy doesn't matter to you.
//      - It will return a unit from the list having the highest number of unused Dummy Units.
//      - To use as a Dummy Caster, follow it with PauseUnit(dummy, false).
//
//  function RecycleDummy takes unit u returns nothing
//      - Recycle the Dummy unit for it to be used again later.
//      - The equivalent of RemoveUnit.
//
//  function DummyAddRecycleTimer takes unit u, real time returns nothing
//      - Recycle the Dummy unit after a certain time.
//      - Use this to allocate time for the the death animation of an effect attached to the
//        Dummy Unit to finish..
//      - The equivalent of UnitApplyTimedLife.
//
//  function ShowDummy takes unit u, boolean flag returns nothing
//      - Shows/hides Dummy Unit without conflicting with the Locust ability.
//
//--------------------
//      CREDITS
//--------------------
//  Bribe - for the MissileRecycler (vJASS) where I got this concept from
//       http://www.hiveworkshop.com/forums/jass-resources-412/system-missilerecycler-206086/
//        - for the optional Table
//       http://www.hiveworkshop.com/forums/jass-resources-412/snippet-new-table-188084/
//  Vexorian - for the Attachable and Pitch Animation Model (dummy.mdx)
//       http://www.wc3c.net/showthread.php?t=101150
//  Maker and IcemanBo - for the unit permanent 0.04 KB memory leak of units.
//       http://www.hiveworkshop.com/forums/trigger-gui-editor-tutorials-279/memory-leaks-263410/
//  Nestharus - for the data structure
//       http://www.hiveworkshop.com/forums/2809461-post7.html
//            - for the optional WorldBounds
//       http://githubusercontent.com/nestharus/JASS/master/jass/Systems/WorldBounds/script.j

// =============================================================== //
// ====================== CONFIGURATION ========================== //
// =============================================================== */

    globals
        //The rawcode of the Dummy Unit
        private constant integer DUMMY_ID = 'dumi'
      
        //The owner of the Dummy Unit
        private constant player OWNER = Player(14)
      
        //The number of indexed angle. The higher the value the:
        // - Lesser the turning time for the Dummy Units.
        // - Higher the total number of Dummy Units created at Map Initialization.
        //          Recommended Value: 10 (Max difference of 18 degrees)
        private constant integer ANGLES_COUNT = 10
      
        //The number of Dummy units per ANGLES_COUNT. The higher the value the:
        // - Higher the number of units that can be recycled per angle, when
        //   no more units are in queue, the system will resort to use CreateUnit.
        // - Higher the total number of Dummy Units created at Map Initialization.
        //    Recommended Value: 3 to 5 (for less overhead in Map Loading Screen)
        private constant integer STORED_UNIT_COUNT = 3
      
        //The maximum number of Dummy units that can exist. When the system resort
        //to using CreateUnit, the unit will be permanently added to the Dummy
        //List. To avoid spamming Dummy Units and having too much free Dummy
        //Units to allocate, the maximum number of Dummy Units is capped.
        //               Recommended Value: 80 to 120
        private constant integer MAX_DUMMY_COUNT = 100
      
        //When a certain angle have less than BORROW_REQUEST units in its list,
        //it will start to borrow Dummy Units from the list with the highest
        //Dummy Unit count.
        //      Recommended Value: Half of maximum STORED_UNIT_COUNT
        private constant integer BORROW_REQUEST = 5
      
        //It will only return a Dummy if the current dummy is close
        //to it's appropriate facing angle. This is to avoid returning
        //a Dummy which is still turning to face it's list angle.
        private constant real ANGLE_TOLERANCE = 10.0
      
        //An additional option to automatically hide recycled dummy units in the
        //corner of the map camera bounds
        private constant boolean HIDE_ON_MAP_CORNER = true
    endglobals
  
    //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)
        call ShowDummy(bj_lastCreatedUnit, true)
    //! endtextmacro
// =============================================================== //
// ==================== END CONFIGURATION ======================== //
// =============================================================== //
  
  
    globals
        private integer dummyCount = ANGLES_COUNT*STORED_UNIT_COUNT
        private real array angle
        private integer array count
        private integer array countHead
        private integer array countNext
        private integer array countPrev
        private integer array next
        private integer array prev
        private unit array dummy
        private integer upper
        private integer lower
        private integer lastInstance
        private constant real FACING_OFFSET = 180.0/ANGLES_COUNT
    endglobals
  
    static if HIDE_ON_MAP_CORNER and not LIBRARY_WorldBounds then
        private module BoundsInit
      
            readonly static real x
            readonly static real y
          
            private static method onInit takes nothing returns nothing
                local rect map = GetWorldBounds()
                set thistype.x = GetRectMaxX(map)
                set thistype.y = GetRectMaxY(map)
                call RemoveRect(map)
                set map = null
            endmethod
          
        endmodule
      
        private struct Bounds extends array
            implement BoundsInit
        endstruct
    endif
  
    private module M
      
        static if LIBRARY_Table then
            static Table tb
        else
            static hashtable hash = InitHashtable()
        endif
      
        private static method onInit takes nothing returns nothing
            local real add = 360.0/ANGLES_COUNT
            local real a = 0
            local integer this = ANGLES_COUNT
            local integer head = 0
            local integer cHead = JASS_MAX_ARRAY_SIZE - 1   //avoid allocation collision
            local integer i = R2I(MAX_DUMMY_COUNT/ANGLES_COUNT + 0.5)
            set upper = STORED_UNIT_COUNT
            set lower = STORED_UNIT_COUNT
            static if LIBRARY_Table then
                set tb = Table.create()
            endif
            //Initialize countHeads
            loop
                exitwhen i < 0
                set countNext[cHead] = cHead
                set countPrev[cHead] = cHead
                set countHead[i] = cHead
                set cHead = cHead - 1
                set i = i - 1
            endloop
            set cHead = countHead[STORED_UNIT_COUNT]  //All heads will be inserted here initially
            //Create the Dummy units
            loop
                exitwhen a >= 360
                //Initialize head
                set next[head] = head
                set prev[head] = head
                set count[head] = STORED_UNIT_COUNT
                set angle[head] = a
                //Insert head in the Count List
                set countNext[head] = cHead
                set countPrev[head] = countPrev[cHead]
                set countNext[countPrev[head]] = head
                set countPrev[countNext[head]] = head
                set i = 0
                loop
                    exitwhen i >= STORED_UNIT_COUNT
                    //Queued Linked List
                    set next[this] = head
                    set prev[this] = prev[head]
                    set next[prev[this]] = this
                    set prev[next[this]] = this
                    static if HIDE_ON_MAP_CORNER then
                        static if LIBRARY_WorldBounds then
                            set dummy[this] = CreateUnit(OWNER, DUMMY_ID, WorldBounds.maxX, WorldBounds.maxY, a)
                        else
                            set dummy[this] = CreateUnit(OWNER, DUMMY_ID, Bounds.x, Bounds.y, a)
                        endif
                    else
                        set dummy[this] = CreateUnit(OWNER, DUMMY_ID, 0, 0, a)
                    endif
                    call PauseUnit(dummy[this], true)
                    static if LIBRARY_Table then
                        set tb[GetHandleId(dummy[this])] = this
                    else
                        call SaveInteger(hash, GetHandleId(dummy[this]), 0, this)
                    endif
                    set this = this + 1
                    set i = i + 1
                endloop
                set head = head + 1
                set a = a + add
            endloop
            set lastInstance = this
        endmethod
      
    endmodule
  
    private struct S extends array
        implement M
    endstruct
  
    private function GetHead takes integer facing returns integer
        if facing < 0 or facing >= 360 then
            set facing = facing - (facing/360)*360
            if facing < 0 then
                set facing = facing + 360
            endif
        endif
        return R2I((facing*ANGLES_COUNT/360.0))
    endfunction
  
    function ShowDummy takes unit u, boolean flag returns nothing
        if IsUnitHidden(u) == flag then
            call ShowUnit(u, flag)
            if flag and GetUnitTypeId(u) == DUMMY_ID then
                call UnitRemoveAbility(u, 'Aloc')
                call UnitAddAbility(u, 'Aloc')
            endif
        endif
    endfunction
  
    function GetRecycledDummy takes real x, real y, real z, real facing returns unit
        local integer head = GetHead(R2I(facing + FACING_OFFSET))
        local integer this = next[head]
        local integer cHead
      
        //If there are Dummy Units in the Queue List already facing close to the appropriate angle
        if this != head and RAbsBJ(GetUnitFacing(dummy[this]) - angle[head]) <= ANGLE_TOLERANCE then
            //Remove from the Queue List
            set next[prev[this]] = next[this]
            set prev[next[this]] = prev[this]
            //For double free protection
            set next[this] = -1
            //Unit Properties
            set bj_lastCreatedUnit = dummy[this]
            call SetUnitX(bj_lastCreatedUnit, x)
            call SetUnitY(bj_lastCreatedUnit, y)
            call SetUnitFacing(bj_lastCreatedUnit, facing)
            call SetUnitFlyHeight(bj_lastCreatedUnit, z, 0)
            //! runtextmacro DUMMY_UNIT_RESET()
            //Update Count and Bounds
            set count[head] = count[head] - 1
          
            //------------------------------------------------
            //                 Unit Sharing
            //------------------------------------------------
            if count[head] < BORROW_REQUEST and count[countNext[countHead[upper]]] > count[head] then
                set count[head] = count[head] + 1
                set this = next[countNext[countHead[upper]]]
                call SetUnitFacing(dummy[this], angle[head])
                //Remove
                set next[prev[this]] = next[this]
                set prev[next[this]] = prev[this]
                //Add to the Current List
                set next[this] = head
                set prev[this] = prev[head]
                set next[prev[this]] = this
                set prev[next[this]] = this
                set head = countNext[countHead[upper]]
                set count[head] = count[head] - 1
            endif
          
            //---------------------------
            //Update Count Lists
            //---------------------------
            //Remove from the current Count List
            set countNext[countPrev[head]] = countNext[head]
            set countPrev[countNext[head]] = countPrev[head]
            //Add to the new Count List
            set cHead = countHead[count[head]]
            set countNext[head] = cHead
            set countPrev[head] = countPrev[cHead]
            set countNext[countPrev[head]] = head
            set countPrev[countNext[head]] = head
          
            //---------------------------
            //  Update Bounds
            //---------------------------
            set cHead = countHead[upper]
            if countNext[cHead] == cHead then
                set upper = upper - 1
            endif
            if count[head] < lower then
                set lower = count[head]
            endif
        else
            set bj_lastCreatedUnit = CreateUnit(OWNER, DUMMY_ID, x, y, facing)
            call PauseUnit(bj_lastCreatedUnit, true)
            call SetUnitFlyHeight(bj_lastCreatedUnit, z, 0)
            if dummyCount < MAX_DUMMY_COUNT then
                set this = lastInstance
                //For double free protection
                set next[this] = -1
                set dummy[this] = bj_lastCreatedUnit
                static if LIBRARY_Table then
                    set S.tb[GetHandleId(bj_lastCreatedUnit)] = this
                else
                    call SaveInteger(S.hash, GetHandleId(bj_lastCreatedUnit), 0, this)
                endif
                set lastInstance = lastInstance + 1
            endif
            set dummyCount = dummyCount + 1
        endif

        return bj_lastCreatedUnit
    endfunction
  
    function RecycleDummy takes unit u returns nothing
        static if LIBRARY_Table then
            local integer this = S.tb[GetHandleId(u)]
        else
            local integer this = LoadInteger(S.hash, GetHandleId(u), 0)
        endif
        local integer head
        local integer cHead
      
        //If the unit is a legit Dummy Unit
        if this > 0 and next[this] == -1 then
            //Find where to insert based on the list having the least number of units
            set head = countNext[countHead[lower]]
            set next[this] = head
            set prev[this] = prev[head]
            set next[prev[this]] = this
            set prev[next[this]] = this
            //Update Status
            call SetUnitFacing(u, angle[head])
            call PauseUnit(u, true)
            call SetUnitOwner(u, OWNER, false)
            static if HIDE_ON_MAP_CORNER then
                static if LIBRARY_WorldBounds then
                    call SetUnitX(u, WorldBounds.maxX)
                    call SetUnitY(u, WorldBounds.maxY)
                else
                    call SetUnitX(u, Bounds.x)
                    call SetUnitY(u, Bounds.y)
                endif
            else
                call SetUnitScale(u, 0, 0, 0)
                call SetUnitVertexColor(u, 0, 0, 0, 0)
            endif
            set count[head] = count[head] + 1
          
            //---------------------------
            //    Update Count Lists
            //---------------------------
            //Remove
            set countNext[countPrev[head]] = countNext[head]
            set countPrev[countNext[head]] = countPrev[head]
            //Add to the new Count List
            set cHead = countHead[count[head]]
            set countNext[head] = cHead
            set countPrev[head] = countPrev[cHead]
            set countNext[countPrev[head]] = head
            set countPrev[countNext[head]] = head
          
            //---------------------------
            //  Update Bounds
            //---------------------------
            set cHead = countHead[lower]
            if countNext[cHead] == cHead then
                set lower = lower + 1
            endif
            if count[head] > upper then
                set upper = count[head]
            endif
        elseif this == 0 then
            call RemoveUnit(u)
        debug elseif next[this] != -1 then
            debug call BJDebugMsg("|cffffcc00[DummyRecycler]:|r Attempted to recycle a pending/free Dummy Unit.")
        endif
      
    endfunction
  
    private function Expires takes nothing returns nothing
        local timer t = GetExpiredTimer()
        local integer id = GetHandleId(t)
        static if LIBRARY_Table then
            call RecycleDummy(S.tb.unit[id])
            call S.tb.unit.remove(id)
        else
            call RecycleDummy(LoadUnitHandle(S.hash, id, 0))
            call FlushChildHashtable(S.hash, id)
        endif
        call DestroyTimer(t)
        set t = null
    endfunction

    function DummyAddRecycleTimer takes unit u, real time returns nothing
        local timer t = CreateTimer()
        static if LIBRARY_Table then
            set S.tb.unit[GetHandleId(t)] = u
        else
            call SaveUnitHandle(S.hash, GetHandleId(t), 0, u)
        endif
        call TimerStart(t, time, false, function Expires)
        set t = null
    endfunction
  
    function GetRecycledDummyAnyAngle takes real x, real y, real z returns unit
        return GetRecycledDummy(x, y, z, angle[countNext[countHead[upper]]])
    endfunction
  
    // runtextmacro DUMMY_DEBUG_TOOLS()
  
endlibrary
JASS:
//                      DummyRecycler v1.25
//                          by Flux
// 
//  A system that recycles dummy units while considering their facing angle.
//  It can be used as attachment dummies for visual effects or as dummy caster.
//
//  Why is recycling a unit important?
//      Because creating a unit is is one of the slowest function in the game
//      and there are reports that will always leave a permanent tiny bit of
//      memory (0.04 KB).
//      On average, retrieving a pending Dummy is approximately 4x faster compared
//      to creating a new one and recycling a Dummy compared to removing it is
//      approximately 1.3x faster.
//      Furthermore, if you're using a lot of "Unit has entered map" events,
//      using this system will even result to even more better performance
//      because retrieving Dummy units does not cause that event to run.
//
//
//  Features:
//
//    -- Dummy Sharing
//        When a Dummy List gets low on unit count, it will borrow Dummy Units
//        from the Dummy List with the highest unit count. The transfer is not
//        instant because the shared Dummy Unit has to turn to the appropriate
//        angle of its new Dummy List before it can be recycled.
//        See BORROW_REQUEST.
//
//    -- Self-balancing recycling algorithm
//        Recycled Dummy Units will be thrown to the List having the least number
//        of Dummy Units.
//
//    -- Recycling least used
//        Allows recycling a Dummy from the Dummy List with the highest
//        unit count. It is useful when the facing angle of the Dummy Unit
//        does not matter.
//        See GetRecycledDummyAnyAngle.
//
//    -- Self-adaptation
//        When there are no free Dummy Units from a Dummy List, it will end up creating
//        a new unit instead but that unit will be permanently added as a Dummy
//        Unit to be recycled increasing the overall total Dummy Unit count.
//
//    -- Count control
//        Allows limiting the overall number of Dummy Units.
//        See MAX_DUMMY_COUNT.
//
//    -- Delayed Recycle
//        Allows recycling Dummy Units after some delay to allocate time for the
//        death animation of Special Effects to be seen.
//        See DummyAddRecycleTimer.
//
// ******************************************************************
// ***************************** API: *******************************
// ******************************************************************
//
//  function GetRecycledDummy takes real x, real y, real z, real facing returns unit
//      - Retrieve an unused Dummy Unit from the List.
//      - The equivalent of CreateUnit.
//      - To use as a Dummy Caster, follow it with PauseUnit(dummy, false).
//
//  function GetRecycledDummyAnyAngle takes real x, real y, real z returns unit
//      - Use this function if the facing angle of the Dummy doesn't matter to you.
//      - It will return a unit from the list having the highest number of unused Dummy Units.
//      - To use as a Dummy Caster, follow it with PauseUnit(dummy, false).
//
//  function RecycleDummy takes unit u returns nothing
//      - Recycle the Dummy unit for it to be used again later.
//      - The equivalent of RemoveUnit.
//
//  function DummyAddRecycleTimer takes unit u, real time returns nothing
//      - Recycle the Dummy unit after a certain time.
//      - Use this to allocate time for the the death animation of an effect attached to the
//        Dummy Unit to finish..
//      - The equivalent of UnitApplyTimedLife.
//
//  function ShowDummy takes unit u, boolean flag returns nothing
//      - Shows/hides Dummy Unit without conflicting with the Locust ability.
//
//--------------------
//      CREDITS
//--------------------
//  Bribe - for the MissileRecycler (vJASS) where I got this concept from
//       http://www.hiveworkshop.com/forums/jass-resources-412/system-missilerecycler-206086/
//        - for the optional Table
//       http://www.hiveworkshop.com/forums/jass-resources-412/snippet-new-table-188084/
//  Vexorian - for the Attachable and Pitch Animation Model (dummy.mdx)
//       http://www.wc3c.net/showthread.php?t=101150
//  Maker and IcemanBo - for the unit permanent 0.04 KB memory leak of units.
//       http://www.hiveworkshop.com/forums/trigger-gui-editor-tutorials-279/memory-leaks-263410/
//  Nestharus - for the data structure
//       http://www.hiveworkshop.com/forums/2809461-post7.html
//            - for the optional WorldBounds
//       http://githubusercontent.com/nestharus/JASS/master/jass/Systems/WorldBounds/script.j

// =============================================================== //
// ====================== CONFIGURATION ========================== //
// =============================================================== //

//The rawcode of the Dummy Unit
constant function Dummy_Rawcode takes nothing returns integer
    return 'dumi'
endfunction

//The owner of the Dummy Unit
constant function Dummy_Owner takes nothing returns player
    return Player(14)
endfunction

//The number of indexed angle. The higher the value the:
// - Lesser the turning time for the Dummy Units.
// - Higher the total number of Dummy Units created at Map Initialization.
//          Recommended Value: 10 (Max difference of 18 degrees)
constant function Dummy_Angles takes nothing returns integer
    return 10
endfunction

//The number of Dummy units per Dummy_Angle. The higher the value the:
// - Higher the number of units that can be recycled per angle, when
//   no more units are in queue, the system will resort to use CreateUnit.
// - Higher the total number of Dummy Units created at Map Initialization.
//    Recommended Value: 3 to 5 (for less overhead in Map Loading Screen)
constant function Dummy_StoredUnits takes nothing returns integer
    return 3
endfunction

//The maximum number of Dummy units that can exist. When the system resort
//to using CreateUnit, the unit will be permanently added to the Dummy
//List. To avoid spamming Dummy Units and having too much free Dummy
//Units to allocate, the maximum number of Dummy Units is capped.
//               Recommended Value: 80 to 120
constant function Dummy_MaxCount takes nothing returns integer
    return 100
endfunction

//When a certain angle have less than BORROW_REQUEST units in its list,
//it will start to borrow Dummy Units from the list with the highest
//Dummy Unit count.
//      Recommended Value: Half of maximum Dummy_StoredUnits
constant function Dummy_BorrowRequest takes nothing returns integer
    return 5
endfunction

//It will only return a Dummy if the current dummy is close
//to it's appropriate facing angle. This is to avoid returning
//a Dummy which is still turning to face it's list angle.
constant function Dummy_AngleTolerance takes nothing returns real
    return 10.0
endfunction

//An additional option to automatically hide recycled dummy units in the
//corner of the map camera bounds
constant function Dummy_HideOnMapCorner takes nothing returns boolean
    return true
endfunction

// =============================================================== //
// ==================== END CONFIGURATION ======================== //
// =============================================================== //

function Dummy_GetHead takes integer facing returns integer
    if facing < 0 or facing >= 360 then
        set facing = facing - (facing/360)*360
        if facing < 0 then
            set facing = facing + 360
        endif
    endif
    return R2I((facing*Dummy_Angles()/360.0))
endfunction

function ShowDummy takes unit u, boolean flag returns nothing
    if IsUnitHidden(u) == flag then
        call ShowUnit(u, flag)
        if flag and GetUnitTypeId(u) == Dummy_Rawcode() then
            call UnitRemoveAbility(u, 'Aloc')
            call UnitAddAbility(u, 'Aloc')
        endif
    endif
endfunction

function GetRecycledDummy takes real x, real y, real z, real facing returns unit
    local integer head = Dummy_GetHead(R2I(facing + 180.0/Dummy_Angles()))
    local integer this = udg_Dummy_Next[head]
    local integer countHead
  
  
    //If there are Dummy Units in the Queue List already facing the appropriate angle
    if this != head and RAbsBJ(GetUnitFacing(udg_Dummy_Unit[this]) - udg_Dummy_Angle[head]) <= Dummy_AngleTolerance() then
        //Remove from the Queue List
        set udg_Dummy_Next[udg_Dummy_Prev[this]] = udg_Dummy_Next[this]
        set udg_Dummy_Prev[udg_Dummy_Next[this]] = udg_Dummy_Prev[this]
        //For double free protection
        set udg_Dummy_Next[this] = -1
        //Unit Properties
        set bj_lastCreatedUnit = udg_Dummy_Unit[this]
        call SetUnitX(bj_lastCreatedUnit, x)
        call SetUnitY(bj_lastCreatedUnit, y)
        call SetUnitFacing(bj_lastCreatedUnit, facing)
        call SetUnitVertexColor(bj_lastCreatedUnit, 255, 255, 255, 255)
        call SetUnitFlyHeight(bj_lastCreatedUnit, z, 0)
        call ShowDummy(bj_lastCreatedUnit, true)
        //------------------------------------------------
        //       Comment out resets you don't need
        //------------------------------------------------
        call SetUnitScale(bj_lastCreatedUnit, 1, 0, 0)
        call SetUnitAnimationByIndex(bj_lastCreatedUnit, 90)
        //Update Count and Bounds
        set udg_Dummy_Count[head] = udg_Dummy_Count[head] - 1
      
        //------------------------------------------------
        //                 Unit Sharing
        //------------------------------------------------
        if udg_Dummy_Count[head] < Dummy_BorrowRequest() and udg_Dummy_Count[udg_Dummy_CountNext[udg_Dummy_CountHead[udg_Dummy_Upper]]] > udg_Dummy_Count[head] then
            set udg_Dummy_Count[head] = udg_Dummy_Count[head] + 1
            //Take an instance from the UpperBound list
            set this = udg_Dummy_Next[udg_Dummy_CountNext[udg_Dummy_CountHead[udg_Dummy_Upper]]]
            call SetUnitFacing(udg_Dummy_Unit[this], udg_Dummy_Angle[head])
            //Remove
            set udg_Dummy_Next[udg_Dummy_Prev[this]] = udg_Dummy_Next[this]
            set udg_Dummy_Prev[udg_Dummy_Next[this]] = udg_Dummy_Prev[this]
            //Add to the Current List
            set udg_Dummy_Next[this] = head
            set udg_Dummy_Prev[this] = udg_Dummy_Prev[head]
            set udg_Dummy_Next[udg_Dummy_Prev[this]] = this
            set udg_Dummy_Prev[udg_Dummy_Next[this]] = this
            set head = udg_Dummy_CountNext[udg_Dummy_CountHead[udg_Dummy_Upper]]
            set udg_Dummy_Count[head] = udg_Dummy_Count[head] - 1
        endif
        //---------------------------
        //Update Count Lists
        //---------------------------
        //Remove from the current Count List
        set udg_Dummy_CountNext[udg_Dummy_CountPrev[head]] = udg_Dummy_CountNext[head]
        set udg_Dummy_CountPrev[udg_Dummy_CountNext[head]] = udg_Dummy_CountPrev[head]
        //Add to the new Count List
        set countHead = udg_Dummy_CountHead[udg_Dummy_Count[head]]
        set udg_Dummy_CountNext[head] = countHead
        set udg_Dummy_CountPrev[head] = udg_Dummy_CountPrev[countHead]
        set udg_Dummy_CountNext[udg_Dummy_CountPrev[head]] = head
        set udg_Dummy_CountPrev[udg_Dummy_CountNext[head]] = head
      
        //---------------------------
        //  Update Bounds
        //---------------------------
        set countHead = udg_Dummy_CountHead[udg_Dummy_Upper]
        if udg_Dummy_CountNext[countHead] == countHead then
            set udg_Dummy_Upper = udg_Dummy_Upper - 1
        endif
        if udg_Dummy_Count[head] < udg_Dummy_Lower  then
            set udg_Dummy_Lower = udg_Dummy_Count[head]
        endif
    else
        set bj_lastCreatedUnit = CreateUnit(Dummy_Owner(), Dummy_Rawcode(), x, y, facing)
        call PauseUnit(bj_lastCreatedUnit, true)
        call SetUnitFlyHeight(bj_lastCreatedUnit, z, 0)
        if udg_Dummy_UnitCount < Dummy_MaxCount() then
            set this = udg_Dummy_LastInstance
            set udg_Dummy_Next[this] = -1
            set udg_Dummy_Unit[this] = bj_lastCreatedUnit
            call SaveInteger(udg_Dummy_Hashtable, GetHandleId(bj_lastCreatedUnit), 0, this)
            set udg_Dummy_LastInstance = udg_Dummy_LastInstance + 1
        endif
        set udg_Dummy_UnitCount = udg_Dummy_UnitCount + 1
    endif

    return bj_lastCreatedUnit
endfunction

function GetRecycledDummyAnyAngle takes real x, real y, real z returns unit
    return GetRecycledDummy(x, y, z, udg_Dummy_Angle[udg_Dummy_CountNext[udg_Dummy_CountHead[udg_Dummy_Upper]]])
endfunction


function RecycleDummy takes unit u returns nothing
    local integer this = LoadInteger(udg_Dummy_Hashtable, GetHandleId(u), 0)
    local integer head
    local integer countHead
  
    //If the unit is a legit Dummy Unit
    if this > 0 and udg_Dummy_Next[this] == -1 then
        //Find where to insert based on the list having the least number of units
        set head = udg_Dummy_CountNext[udg_Dummy_CountHead[udg_Dummy_Lower]]
        set udg_Dummy_Next[this] = head
        set udg_Dummy_Prev[this] = udg_Dummy_Prev[head]
        set udg_Dummy_Next[udg_Dummy_Prev[this]] = this
        set udg_Dummy_Prev[udg_Dummy_Next[this]] = this
        //Update Status
        call SetUnitFacing(u, udg_Dummy_Angle[head])
        call PauseUnit(u, true)
        call SetUnitOwner(u, Dummy_Owner(), false)
        if Dummy_HideOnMapCorner() then
            call SetUnitX(u, udg_Dummy_X)
            call SetUnitY(u, udg_Dummy_Y)
        else
            call SetUnitVertexColor(u, 0, 0, 0, 0)
        endif
        set udg_Dummy_Count[head] = udg_Dummy_Count[head] + 1
      
        //---------------------------
        //    Update Count Lists
        //---------------------------
        //Remove
        set udg_Dummy_CountNext[udg_Dummy_CountPrev[head]] = udg_Dummy_CountNext[head]
        set udg_Dummy_CountPrev[udg_Dummy_CountNext[head]] = udg_Dummy_CountPrev[head]
        //Add to the new Count List
        set countHead = udg_Dummy_CountHead[udg_Dummy_Count[head]]
        set udg_Dummy_CountNext[head] = countHead
        set udg_Dummy_CountPrev[head] = udg_Dummy_CountPrev[countHead]
        set udg_Dummy_CountNext[udg_Dummy_CountPrev[head]] = head
        set udg_Dummy_CountPrev[udg_Dummy_CountNext[head]] = head
      
        //---------------------------
        //  Update Bounds
        //---------------------------
        set countHead = udg_Dummy_CountHead[udg_Dummy_Lower]
        if udg_Dummy_CountNext[countHead] == countHead then
            set udg_Dummy_Lower = udg_Dummy_Lower + 1
        endif
        if udg_Dummy_Count[head] > udg_Dummy_Upper then
            set udg_Dummy_Upper = udg_Dummy_Count[head]
        endif
    elseif this == 0 then
        //User tries to recycle an invalid unit, remove the unit instead
        call RemoveUnit(u)
    endif
  
endfunction

function Dummy_TimerExpires takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local integer id = GetHandleId(t)
    call RecycleDummy(LoadUnitHandle(udg_Dummy_Hashtable, id, 0))
    call FlushChildHashtable(udg_Dummy_Hashtable, id)
    call DestroyTimer(t)
    set t = null
endfunction

function DummyAddRecycleTimer takes unit u, real time returns nothing
    local timer t = CreateTimer()
    call SaveUnitHandle(udg_Dummy_Hashtable, GetHandleId(t), 0, u)
    call TimerStart(t, time, false, function Dummy_TimerExpires)
    set t = null
endfunction

function InitTrig_DummyRecycler_JASS takes nothing returns nothing
    local integer id = Dummy_Rawcode()
    local player owner = Dummy_Owner()
    local integer unitCount = Dummy_StoredUnits()
    local real add = 360.0/Dummy_Angles()
    local real angle = 0
    local integer this = Dummy_Angles()
    local integer head = 0
    local integer countHead = JASS_MAX_ARRAY_SIZE - 1   //avoid allocation collision
    local integer i = R2I(Dummy_MaxCount()/Dummy_Angles() + 0.5)
    local rect r
    set udg_Dummy_Hashtable = InitHashtable()
    set udg_Dummy_Upper = unitCount
    set udg_Dummy_Lower = unitCount
    //Create Map Bounds
    if Dummy_HideOnMapCorner() then
        set r = GetWorldBounds()
        set udg_Dummy_X = GetRectMaxX(r)
        set udg_Dummy_Y = GetRectMaxY(r)
        call RemoveRect(r)
        set r = null
    endif
    //Initialize countHeads
    loop
        exitwhen i < 0
        set udg_Dummy_CountNext[countHead] = countHead
        set udg_Dummy_CountPrev[countHead] = countHead
        set udg_Dummy_CountHead[i] = countHead
        set countHead = countHead - 1
        set i = i - 1
    endloop
    set countHead = udg_Dummy_CountHead[unitCount]  //All heads will be inserted here initially
    //Create the Dummy units
    loop
        exitwhen angle >= 360
        //Initialize head
        set udg_Dummy_Next[head] = head
        set udg_Dummy_Prev[head] = head
        set udg_Dummy_Count[head] = unitCount
        set udg_Dummy_Angle[head] = angle
        //Insert head in the Count List
        set udg_Dummy_CountNext[head] = countHead
        set udg_Dummy_CountPrev[head] = udg_Dummy_CountPrev[countHead]
        set udg_Dummy_CountNext[udg_Dummy_CountPrev[head]] = head
        set udg_Dummy_CountPrev[udg_Dummy_CountNext[head]] = head
        set i = 0
        loop
            exitwhen i >= unitCount
            //Queued Linked List
            set udg_Dummy_Next[this] = head
            set udg_Dummy_Prev[this] = udg_Dummy_Prev[head]
            set udg_Dummy_Next[udg_Dummy_Prev[this]] = this
            set udg_Dummy_Prev[udg_Dummy_Next[this]] = this
            //The actual unit
            set udg_Dummy_Unit[this] = CreateUnit(owner, id, udg_Dummy_X, udg_Dummy_Y, angle)
            call PauseUnit(udg_Dummy_Unit[this], true)
            call SaveInteger(udg_Dummy_Hashtable, GetHandleId(udg_Dummy_Unit[this]), 0, this)
            set this = this + 1
            set i = i + 1
        endloop
        set head = head + 1
        set angle = angle + add
    endloop
    set udg_Dummy_LastInstance = this
    set udg_Dummy_UnitCount = Dummy_Angles()*unitCount
endfunction

// runtextmacro DUMMY_DEBUG_TOOLS_JASS()


EXAMPLE OF USING THE SYSTEM

  • Demo in GUI
  • Events
  • Player - Player 1 (Red) types a chat message containing gui as An exact match
  • Conditions
  • Actions
  • -------- ----------------------- --------
  • -------- for MUIness --------
  • -------- ----------------------- --------
  • Custom script: local effect e
  • Custom script: local unit u
  • -------- ----------------------- --------
  • -------- Retrieved a recycled dummy, use (Last Created Unit) to assign it to a variable --------
  • -------- ----------------------- --------
  • Custom script: call GetRecycledDummy(0, 0, 0, udg_Demo_Angle)
  • Set Demo_Unit = (Last created unit)
  • -------- ^Though no unit creation actually takes place --------
  • -------- ----------------------- --------
  • -------- Create the Effect --------
  • -------- ----------------------- --------
  • Special Effect - Create a special effect attached to the origin of Demo_Unit using abilities\weapons\WyvernSpear\WyvernSpearMissile.mdl
  • -------- ----------------------- --------
  • -------- In the name of MUI --------
  • -------- ----------------------- --------
  • Custom script: set e = bj_lastCreatedEffect
  • Custom script: set u = udg_Demo_Unit
  • -------- ----------------------- --------
  • -------- Change size to make the demo more visible --------
  • -------- ----------------------- --------
  • Animation - Change Demo_Unit's size to (200.00%, 200.00%, 200.00%) of its original size
  • -------- ----------------------- --------
  • Game - Display to (All players) the text: ([code=jass] Retrieved a Dummy with Attached Effect where facing = + (String(Demo_Angle)))
  • -------- ----------------------- --------
  • -------- Increase the Angle to make the next dummy face a different angle --------
  • -------- ----------------------- --------
  • Set Demo_Angle = (Demo_Angle + 4.50)
  • Wait 8.00 seconds
  • Custom script: call DestroyEffect(e)
  • Custom script: call DummyAddRecycleTimer(u, 2.0)





Changelog:
v1.00 - [6 April 2016]
- Initial Release

v1.10 - [8 April 2016]
- No longer comes in 'Basic' and 'Advance' versions. 'Basic' version is removed.
- Changed API name, removed the "Dummy_" prefix.
- Improved the data structure, all operations are now O(1).
- Recycling a unit is now the equivalent of removing a unit, thus users should use AddRecycleTimer to allow the death time of the attached effect to show.

v1.11 - [9 April 2016]
- Excess recycled units are no longer removed, but permanently added to the list.
- GetRecycledUnit will no longer return units that are still turning.
- Added a vJASS version since it is different from Bribe's MissileRecycler.

v1.12 - [21 April 2016]
- Added double free protection.
- Fixed real-type variable comparison bug.

v1.13 - [9 May 2016]
- Renamed API, uses the word 'dummy' instead of 'unit'.
- Replaced "boolean caster" with "real z" as input argument.
- Recycling the dummy no longer changes the position of the dummy.

v1.20 - [16 May 2016]
- Fixed Angle Difference calculation bug when facing is 0 degrees and list angle is 360 degrees ends up creating new units.
- Fixed a bug in GetRecycledDummyAnyAngle. Fixed by angle[countNext[upper]] -> angle[countNext[countHead[upper]]]
- Added a demo in GetRecycledDummyAnyAngle.
- Improved DummyDisplay debugging tool.
- Fixed unstable list when a list reaches a zero count. Fixed by exitwhen i == 0 -> exitwhen i < 0 on //Initialize countHeads.
- Extra Dummy units are no longer permanently added and are removed instead to avoid having too much free Dummy units in the list, counteract spamming and to give better count control.

v1.21 - [17 May 2016]
- Re-added the feature where extra Dummy Units created are permanently added to the Dummy List.
- Added a parameter to set the maximum number of recyclable Dummy Units.
- Dummy Sharing will no longer requires the sharer (list with higher dummy count) to meet a condition. In other words, the ENABLE_SHARE/Dummy_EnableShare() parameter was removed.
- Optimized angle difference calculation, avoiding costly trigonometric and inverse trigonometric functions.
- Rephrased double free protection debug message.

v1.22 - [21 May 2016]
- Added an additional feature to move recycled units in the corner of the Map Camera Bounds.

v1.23 - [22 July 2016]
- Properly cleaned optional Table.
- Removed unnecessary PauseUnit upon retrieval.
- Added a lua Object Merger so that users can generate the dummy unit without copying from the test map.

v1.24 - [14 August 2016]
- vJASS version now uses a Module initializer.
- Improved description a bit.

v1.25 - [14 December 2016]
- Added ShowDummy function which allows you to show/hide a Dummy without problems.
- Retrieved Dummy Units are automatically shown by default even if they were hidden previously.



Keywords:
reduce, reuse, recycle, dummy
Contents

Dummy Recycler v1.25 (Map)

Reviews
3rd May 2016 Your resource has been reviewed by BPower. In case of any questions or for reconfirming the moderator's rating, please make use of the Quick Reply function of this thread. Review: Highly recommended for projectile and fx...

Moderator

M

Moderator

3rd May 2016

General Info

Your resource has been reviewed by BPower.
In case of any questions or for reconfirming the moderator's rating,
please make use of the Quick Reply function of this thread.
Review:

Highly recommended for projectile and fx utility systems or
for anyone who needs a lot of dummy units for his/her code.

The option to share units among unit lists is very neat.
I ran a couple of stress tests and everything seems to be in perfect order.

In the vJass version the onInit function could be placed into a module initializer.

Troubleshooting:

  • Nothing

Review changelog:
  1. -
 
Level 33
Joined
Apr 24, 2012
Messages
5,113
You can still make the DummyRecycler without using a hashtable :D

Overall, I think this should still be based on Bribe's Recycler :(

[edit]
GetRecycledDummy is much better than Dummy_GetRecycledUnit, same goes to RecycleDummy :D

also, I think the recycle delay should be the same for all dummies, i mean, declare a time constant, then apply that to all recycled dummies

Also, Allow the users to disable their Unit Indexers to prevent dummy indexing.
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
You can still make the DummyRecycler without using a hashtable :D
How (but still being O(1))? Anyway, it doesn't matter much because hashtables are O(1).

Overall, I think this should still be based on Bribe's Recycler :(
I honestly doesn't understand how Bribe's work. Didn't also bother to have a detailed look at it. Also, where's the fun in that if I'm just gonna copy someone else's design? I like thinking my own design.


GetRecycledDummy is much better than Dummy_GetRecycledUnit, same goes to RecycleDummy :D
Agree, will change that in the next update.

also, I think the recycle delay should be the same for all dummies, i mean, declare a time constant, then apply that to all recycled dummies
Actually, the AddRecycleTimer is just a bonus feature. Users will mostly just recycle the Dummy instantly. It will still show effect being destroyed because the Dummy's are not moved anywhere when recycled.

Also, Allow the users to disable their Unit Indexers to prevent dummy indexing.
I don't use Unit Indexer so I don't know how to do that.
 

Attachments

  • DummyRecylerSharing.jpg
    DummyRecylerSharing.jpg
    158.4 KB · Views: 1,137
  • DummyRecylerBasics.jpg
    DummyRecylerBasics.jpg
    262.5 KB · Views: 1,040
Last edited:
Level 31
Joined
Jul 10, 2007
Messages
6,306
This does exactly what Bribe's does.

A good one will always throw the unit into the least populated angle in O(1).


I'll give you a hint on how to do this.

list[1] = angles with a population of 1
list[2] = angles with a population of 2
list[3] = angles with a population of 3
list[x] = angles with a population of x


at most, only 3 lists may be active at a time. You would have a lower bound and an upper bound. Whenever an angle goes below the lower bound for population, it takes from the upper bound. When the upper bound becomes empty, the upper and lower bound are shifted down. As things get put in, then lower bound angles get populated first as they are the most sparse. When the lower bound becomes empty, the lower bound and upper bound are shifted up. This is how my recycler works and how you can achieve O(1) recycling while optimally throwing units into the most sparse angles : ).



You'll also need the following

list[90] = units facing direction 90
list[270] = units facing direction 270
list[x] = units facing direction x


Angles are added to the first set of lists. Units are added to the second set of lists. You'll also need angle counts.


count[angle] = value (how many units are in the list for an angle)





This is the very best way to do unit recycling. Bribe has yet to implement this. It's only in my library, which is broken : p.
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
A good one will always throw the unit into the least populated angle in O(1).
However, when a Recycled Unit is thrown to the list with the least number, they will be able to see the Dummy Unit turning while it gets destroyed. Actually, that reminds me, this system currently throw recycled units to it's original list, where it should throw it to the list nearest to the current angle of the recycled unit so that the dummy will not be seen turning.

list[1] = angles with a population of 1
list[2] = angles with a population of 2
list[3] = angles with a population of 3
list[x] = angles with a population of x
This is still not O(1) because what if list[1] is empty? Then it will go to the next until it reaches a non-empty list?
Example, all list contains 5 units. Then it will go to list[1] then list[2] then list[3] up to list[5].


Currently, it's worst case scenario (DummyRecyclerEx) is O(n) and best case is O(1).
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
The below is a snapshot of what the data structure looks like. If you were to retrieve a unit from angle 45, it'd be pushed into 0, which is below the lower bound. Because the upper bound is not empty, it would take from the upper bound and push angle 45 back to a count of 1. Angle 90 would be pushed down from 2 to 1.

0 would never go to -1 because a unit is not popped off of a list unless that list isn't empty and has a ready unit.

When recycling a unit, you'd always throw it into an angle in the lower bound. This keeps everything as evenly distributed as possible.

When the upper bound becomes empty, bounds go down by 1. When the lower bound becomes empty, bounds go up by 1. For bounds, you only need the lower bound variable.

attachment.php




Hope this makes things more clear : )


If you still don't understand, I can try doing an animation ^_^
 

Attachments

  • Drawing1.jpg
    Drawing1.jpg
    77.5 KB · Views: 2,257
Last edited:
Level 22
Joined
Feb 6, 2014
Messages
2,466
Thanks Nestharus! That makes it more clear.
No need to animate but if you want to, feel free to do it.

When the upper bound becomes empty, bounds go down by 1. When the lower bound becomes empty, bounds go up by 1. For bounds, you only need the lower bound variable.
Though I disagree on this part, it should be thrown to the closest angle it is currently facing then the lower bound will take a unit from the upper bound. Else, when a unit facinf 80 degrees is recycled and the lower bound is 270 degrees, the recycled unit will be seen turning as the effect attached to it destroyed.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
because when it's recycled, it will already not have any effects on it...

that's like calling something like RemoveUnit and complaining that the unit is visible after removing it. It doesn't even make sense >.<.


If your recycling doesn't work like RemoveUnit, you've got some serious problems.


Sending to the nearest angle doesn't provide any benefit because the recycle method should work in the same way RemoveUnit does. Once you call it, the unit vanishes immediately. I think good recycling will move the unit to the edge of the bounds of the map so that it's not visible.


Recycle != KillUnit. Recycle = RemoveUnit : ).



Now if you want to show the death animation on top of that, then you recycle on a delay.


Not moving the unit can have unintended consequences in a map as it may interact with other elements of the map. For example, unit in range.
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
Updated.
- No longer comes in 'Basic' and 'Advance' versions. 'Basic' version is removed.
- Changed API name, removed the "Dummy_" prefix.
- Improved the data structure, all operations are now O(1).
- Recycling a unit is now the equivalent of removing a unit, thus users should use AddRecycleTimer to allow the death time of the attached effect to show.
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
You should recycle newly created units too. Shouldn't remove "excess" units. The data structure can adapt to new units being put into it.

Okay, and on that part, it should not have been KillUnit but RemoveUnit. But it doesn't matter anyway since I'll change it.

Do you think I also should add a safety mechanism on recycling unit. Because currently, let's say I recycle a unit facing 0 degree and it is thrown to 180 degrees (with empty list) so it is now starting to turn. However while turning, I retrieved a unit from the 180 degrees list but the only unit in the list is still turning. So should I only add it when the unit is completely turning already and the action should have created a new unit instead? However, it would require calculation based on turning speed and angle difference.
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
if GetUnitFacing == angle, return unit
otherwise create unit

How the fuck did I fail to think that? :)
Thanks!


Update:
- Excess recycled units are no longer removed, but permanently added to the list.
- GetRecycledUnit will no longer return units that are still turning.
- Added a vJASS version since it is different from Bribe's MissileRecycler.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
Just some nitpicking.
Wouldn't it be better to name the functions GetRecycledDummy and RecycleDummy?

Also I would suggest you enable fly height manipulation by adding crow form to dummy units.
Then again it maybe would be good to add a z parameter to GetRecycledDummy.
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
Congrats :D

will switch from MissileRecycler to this
Thanks! glad you found it useful.

Wouldn't it be better to name the functions GetRecycledDummy and RecycleDummy?
Because the word 'unit' is only 4 letters while 'dummy' is 5 letters so I go with the shorter one.

Also I would suggest you enable fly height manipulation by adding crow form to dummy units.
Then again it maybe would be good to add a z parameter to GetRecycledDummy.
Hmm, the way I use dummy units is I set the movement type to fly so I can directly manipulate fly height without 'Arav'. A z parameter like Bribe's is also good, but the reason I did not add it at first is I tend to use 0 for it in a lot of my coding experience. I thought people experience the same. Nevertheless, I'll add it as soon as I have WC3 again.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
Personally I would prefer a function name change from

GetRecycledUnit() --> GetRecycledDummy()
simply because what we recycle are dummies.

GetRecycledUnit() could be used for a system that recycles any unit type.

function GetRecycledDummy takes real x, real y, real z, real face returns unit
function RecycleDummy takes unit dummy returns nothing

Code from the missile system I'm developing.
JASS:
    private function GetDummyUnit takes real x, real y, real z, real face returns unit    
        static if LIBRARY_MissileRecycler then
            return GetRecycledMissile(x, y, z, face) // Inlines.
        elseif LIBRARY_DummyRecycler then
            set tempUnit = GetRecycedUnit(x, y, false, face)
        elseif LIBRARY_xedummy and xedummy.new.exists then
            set tempUnit = xedummy.new(Missile_NEUTRAL_PLAYER, x, y, face)
        elseif LIBRARY_Dummy and Dummy.create.exists then
            set tempUnit = Dummy.create(x, y, face).unit
        else
            set tempUnit = CreateUnit(Missile_NEUTRAL_PLAYER, Missile_DUMMY_UNIT_TYPE_ID , x, y, face)
            call SetUnitX(tempUnit, x)
            call SetUnitY(tempUnit, y)
            call UnitAddAbility(tempUnit, ABILITY_ID_LOCUST)
            call PauseUnit(tempUnit, true)
        endif
        call UnitAddAbilityToFly(tempUnit)
        call SetUnitFlyHeight(tempUnit, z, 0.)
        return tempUnit
    endfunction

function DummyAddRecycleTime ???

I dislike the isCaster boolean. I think dummies should be paused by default.
Caster systems can still unpause them if required.
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
Is it ok if I remove it now, won't it have backward compatibility issue? On the otherhand, a simple 'Searchb+ Replace will fix that'. . Since I'm also coding on the Zephyr, I'm also starting to get irritated by the continuous false input argument. Like 90% of the time it is false.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
Beautiful :)

One more thing.

What is if I need really a lot of dummies at once, but maybe only once. Let's say 350 dummies within 2 seconds.
That is far beyond the per angle stored dummies number.

Then I recycle all dummies. Wouldn't it be clever to recycle only the max number of dummies
as predefined in the globals ANGLES*COUNT_PER_ANGLE and remove the rest from the game?
 
Last edited:
Level 31
Joined
Jul 10, 2007
Messages
6,306
Remember that even removing a dummy unit from the game has a memory leak cost associated with it. You could do removing extraneous dummy units over time to get down to an average, but I personally think that not removing any dummy units is the best choice.

Perhaps Flux could include options for how to handle dummy units.

Option 1: Remove all above preload max.
Option 2: Remove over time to an average.
Option 3: Remove none.
 
For normal practical cases it won't ever matter actually I believe, to bother about removing dummies.
In theory I might imagine it to be a cool saftefy mechanism, if the amount of unused dummies is extremly high, and unused.

If there should be a solution for it, maybe a seperated function could be used.
GetDummyOnce ... something alike, to get a dummy which should not be recylced, and which won't be registered as a removal from the dummy stack.
So the user has to remove it by it's own instead of adding it to the stack.
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
Working on my Zephyr Contest and found a bug. I should have tested this thoroughly. Anyways it's fixed now.

Then I recycle all dummies. Wouldn't it be clever to recycle only the max number of dummies
as predefined in the globals ANGLES*COUNT_PER_ANGLE and remove the rest from the game?
Done. It turns out, I need to initialize each possible count and if I don't remove excess dummy units, it would be unpredictable what counts to initialize so I end up removing it instead so it now removes excess units.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
Working on my Zephyr Contest and found a bug. I should have tested this thoroughly. Anyways it's fixed now.


Done. It turns out, I need to initialize each possible count and if I don't remove excess dummy units, it would be unpredictable what counts to initialize so I end up removing it instead so it now removes excess units.

I disagree with this thoroughly. Please give users the option.


I'm personally the kind of user that sets dummy preloading to 0, or some extremely low value. I let the system adapt to the map. That makes your system nothing but overhead for me now.
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
Updated!
Changes:
- Re-added the feature where extra Dummy Units created are permanently added to the Dummy List.
- Added a parameter to set the maximum number of recyclable Dummy Units.
- Dummy Sharing will no longer requires the sharer (list with higher dummy count) to meet a condition. In other words, the ENABLE_SHARE/Dummy_EnableShare() parameter was removed.
- Optimized angle difference calculation, avoiding costly trigonometric and inverse trigonometric functions.
- Rephrased double free protection debug message.

I did some test where 2 Dummy units are recycled every 0.1 seconds with little variation in facing angle (+- 30 degrees)
Condition: Initial number of dummy units was 30 (10 Angles, 3 units per angle)
The first test, the number of recycled units was 80 and the number of created units (due to empty list or unready dummy units) was 76.

In the second test, the number of recycled units was 121 and the number of created units (due to empty list or unready dummy units) was 35. Since in the first test, a lot of dummy units were created, it increases the number of recyclable dummy units that's why it was able to recycle more dummy units.
 

Deleted member 219079

D

Deleted member 219079

I've my own dummy recycler, it has textmacro that uses ObjectMerger to create dummy object. Maybe you can have that here? Makes implementation easier.
JASS:
library Dummy requires AIDS // http://www.thehelper.net/threads/advanced-indexing-data-storage.116539/
    //////////////////////////////////////////////////////////////////////
    //                                                                  //
    // Dummy v1.0.0                                                     //
    // by jondrean                                                      //
    //                                                                  //
    // Implementation:                                                  //
    //  1. Download model: http://www.wc3c.net/showthread.php?p=1007469 //
    //  2. Save map once having correct input for below textmacro       //
    //  3. Comment out below textmacro                                  //
    //                                                                  //
    ///! runtextmacro DUMMY_OBJECT("dummy.mdl")                         //
    //                                                                  //
    // Methods:                                                         //
    // Dummy                                                            //
    //  .get(real x,real y)->unit                   // effects          //
    //  .getEx(real x,real y,player owner)->unit    // casting          //
    //  .getA(real x,real y,real a)->unit           // missiles         //
    //  .release(unit u)                            // effects          //
    //  .releaseEx(unit u)                          // casting          //
    //  .releaseA(unit u)                           // missiles         //
    //  .unit->unit                                 // instant casts    //
    //                                                                  //
    // I'm not the author of the dummy model or idea of dummy systems   //
    //                                                                  //
    //////////////////////////////////////////////////////////////////////
   
    private keyword Init
   
    struct Dummy extends array
       
        private static unit o
       
        private static integer m=-1
        private static unit array u
       
        private static unit U
        static method operator unit takes nothing returns unit
            return U
        endmethod
       
        static method get takes real x,real y returns unit
            if(m==-1)then
                set o=CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE),'udum',x,y,0)
                call UnitAddAbility(o,'Arav')
                call UnitRemoveAbility(o,'Arav')
                return o
            endif
            call SetUnitX(u[m],x)
            call SetUnitY(u[m],y)
            set m=m-1
            return u[m+1]
        endmethod
        static method release takes unit o returns nothing
            call SetUnitFlyHeight(o,0,0)
            call SetUnitScale(o,1,1,1)
            set m=m+1
            set u[m]=o
        endmethod
       
        static method getEx takes real x,real y,player p returns unit
            if(m==-1)then
                set o=CreateUnit(p,'e002',x,y,0)
                call UnitAddAbility(o,'Arav')
                call UnitRemoveAbility(o,'Arav')
                return o
            endif
            call SetUnitX(u[m],x)
            call SetUnitY(u[m],y)
            call SetUnitOwner(u[m],p,false)
            set m=m-1
            return u[m+1]
        endmethod
        static method releaseEx takes unit o returns nothing
            call SetUnitOwner(o,Player(PLAYER_NEUTRAL_PASSIVE),false)
            set m=m+1
            set u[m]=o
        endmethod
       
        private thistype n //next
        private static thistype array h //head
        static method getA takes real x,real y,real a returns unit
            local integer i=R2I(a/45)
            local thistype n=h[i]
            if(n==0)then
                set o=CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE),'e002',x,y,a)
                call UnitAddAbility(o,'Arav')
                call UnitRemoveAbility(o,'Arav')
                return o
            endif
            set h[i]=n.n
            call SetUnitX(GetIndexUnit(n),x)
            call SetUnitY(GetIndexUnit(n),y)
            call SetUnitFacing(GetIndexUnit(n),a)
            return GetIndexUnit(n)
        endmethod
        static method releaseAById takes thistype u returns nothing
            local integer i=R2I(GetUnitFacing(GetIndexUnit(u))/45)
            call SetUnitFacing(GetIndexUnit(u),i*45+22.5)
            set u.n=h[i].n
            set h[i]=u
        endmethod
        static method releaseA takes unit u returns nothing
            debug if(GetUnitUserData(u)==0)then
            debug   call BJDebugMsg("|cffff0000Dummy.releaseA ERROR: Removed/unindexed unit given")
            debug   return
            debug endif
            call releaseAById(GetUnitId(u))
        endmethod
       
        implement Init
       
    endstruct
   
    private module Init
       
        static method onInit takes nothing returns nothing
            set U=CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE),'e002',0,0,0)
            call UnitAddAbility(U,'Arav')
            call UnitRemoveAbility(U,'Arav')
        endmethod
       
    endmodule
   
    // ---------------
   
    //! textmacro DUMMY_OBJECT takes MODELPATH
        //! externalblock extension=lua ObjectMerger $FILENAME$
            //! i setobjecttype("units")
            //! i createobject("ewsp","udum")
            //! i makechange(current,"uabi","Aloc")
            //! i makechange(current,"uble","0")
            //! i makechange(current,"ucbs","0")
            //! i makechange(current,"ucpt","0")
            //! i makechange(current,"umdl","$MODELPATH$")
            //! i makechange(current,"ulpz","0")
            //! i makechange(current,"uprw","0")
            //! i makechange(current,"ushu","")
            //! i makechange(current,"umvt","")
            //! i makechange(current,"ucol","0")
            //! i makechange(current,"ufle","0")
            //! i makechange(current,"ufoo","0")
            //! i makechange(current,"uhom","1")
            //! i makechange(current,"urac","commoner")
            //! i makechange(current,"usid","0")
            //! i makechange(current,"usin","0")
            //! i makechange(current,"upgr","")
            //! i makechange(current,"utyp","_")
            //! i makechange(current,"unam","Dummy")
        //! endexternalblock
    //! endtextmacro
   
endlibrary
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
I've my own dummy recycler, it has textmacro that uses ObjectMerger to create dummy object. Maybe you can have that here? Makes implementation easier.
JASS:
library Dummy requires AIDS // http://www.thehelper.net/threads/advanced-indexing-data-storage.116539/
    //////////////////////////////////////////////////////////////////////
    //                                                                  //
    // Dummy v1.0.0                                                     //
    // by jondrean                                                      //
    //                                                                  //
    // Implementation:                                                  //
    //  1. Download model: http://www.wc3c.net/showthread.php?p=1007469 //
    //  2. Save map once having correct input for below textmacro       //
    //  3. Comment out below textmacro                                  //
    //                                                                  //
    ///! runtextmacro DUMMY_OBJECT("dummy.mdl")                         //
    //                                                                  //
    // Methods:                                                         //
    // Dummy                                                            //
    //  .get(real x,real y)->unit                   // effects          //
    //  .getEx(real x,real y,player owner)->unit    // casting          //
    //  .getA(real x,real y,real a)->unit           // missiles         //
    //  .release(unit u)                            // effects          //
    //  .releaseEx(unit u)                          // casting          //
    //  .releaseA(unit u)                           // missiles         //
    //  .unit->unit                                 // instant casts    //
    //                                                                  //
    // I'm not the author of the dummy model or idea of dummy systems   //
    //                                                                  //
    //////////////////////////////////////////////////////////////////////
  
    private keyword Init
  
    struct Dummy extends array
      
        private static unit o
      
        private static integer m=-1
        private static unit array u
      
        private static unit U
        static method operator unit takes nothing returns unit
            return U
        endmethod
      
        static method get takes real x,real y returns unit
            if(m==-1)then
                set o=CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE),'udum',x,y,0)
                call UnitAddAbility(o,'Arav')
                call UnitRemoveAbility(o,'Arav')
                return o
            endif
            call SetUnitX(u[m],x)
            call SetUnitY(u[m],y)
            set m=m-1
            return u[m+1]
        endmethod
        static method release takes unit o returns nothing
            call SetUnitFlyHeight(o,0,0)
            call SetUnitScale(o,1,1,1)
            set m=m+1
            set u[m]=o
        endmethod
      
        static method getEx takes real x,real y,player p returns unit
            if(m==-1)then
                set o=CreateUnit(p,'e002',x,y,0)
                call UnitAddAbility(o,'Arav')
                call UnitRemoveAbility(o,'Arav')
                return o
            endif
            call SetUnitX(u[m],x)
            call SetUnitY(u[m],y)
            call SetUnitOwner(u[m],p,false)
            set m=m-1
            return u[m+1]
        endmethod
        static method releaseEx takes unit o returns nothing
            call SetUnitOwner(o,Player(PLAYER_NEUTRAL_PASSIVE),false)
            set m=m+1
            set u[m]=o
        endmethod
      
        private thistype n //next
        private static thistype array h //head
        static method getA takes real x,real y,real a returns unit
            local integer i=R2I(a/45)
            local thistype n=h[i]
            if(n==0)then
                set o=CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE),'e002',x,y,a)
                call UnitAddAbility(o,'Arav')
                call UnitRemoveAbility(o,'Arav')
                return o
            endif
            set h[i]=n.n
            call SetUnitX(GetIndexUnit(n),x)
            call SetUnitY(GetIndexUnit(n),y)
            call SetUnitFacing(GetIndexUnit(n),a)
            return GetIndexUnit(n)
        endmethod
        static method releaseAById takes thistype u returns nothing
            local integer i=R2I(GetUnitFacing(GetIndexUnit(u))/45)
            call SetUnitFacing(GetIndexUnit(u),i*45+22.5)
            set u.n=h[i].n
            set h[i]=u
        endmethod
        static method releaseA takes unit u returns nothing
            debug if(GetUnitUserData(u)==0)then
            debug   call BJDebugMsg("|cffff0000Dummy.releaseA ERROR: Removed/unindexed unit given")
            debug   return
            debug endif
            call releaseAById(GetUnitId(u))
        endmethod
      
        implement Init
      
    endstruct
  
    private module Init
      
        static method onInit takes nothing returns nothing
            set U=CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE),'e002',0,0,0)
            call UnitAddAbility(U,'Arav')
            call UnitRemoveAbility(U,'Arav')
        endmethod
      
    endmodule
  
    // ---------------
  
    //! textmacro DUMMY_OBJECT takes MODELPATH
        //! externalblock extension=lua ObjectMerger $FILENAME$
            //! i setobjecttype("units")
            //! i createobject("ewsp","udum")
            //! i makechange(current,"uabi","Aloc")
            //! i makechange(current,"uble","0")
            //! i makechange(current,"ucbs","0")
            //! i makechange(current,"ucpt","0")
            //! i makechange(current,"umdl","$MODELPATH$")
            //! i makechange(current,"ulpz","0")
            //! i makechange(current,"uprw","0")
            //! i makechange(current,"ushu","")
            //! i makechange(current,"umvt","")
            //! i makechange(current,"ucol","0")
            //! i makechange(current,"ufle","0")
            //! i makechange(current,"ufoo","0")
            //! i makechange(current,"uhom","1")
            //! i makechange(current,"urac","commoner")
            //! i makechange(current,"usid","0")
            //! i makechange(current,"usin","0")
            //! i makechange(current,"upgr","")
            //! i makechange(current,"utyp","_")
            //! i makechange(current,"unam","Dummy")
        //! endexternalblock
    //! endtextmacro
  
endlibrary

Hmmm, adding an Object Merger to make the Dummy Object automatic is not a bad idea, offers some utilities to users. But I'll probably put it in a separate trigger though.

By the way, I just found out that I've been removing the unit handle from the table incorrectly this whole time, it should be S.tb.unit.remove(id) instead of S.tb.remove(id) thus it is bound to have another update after I try Nestharus' Memory Analysis on it.
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
Now that I finally got the capability to benchmark things, here's the benchmark result of this resource:
Numbers shows how much time (in microseconds) the function takes.
SpeedTest Result.JPG


EDIT:
Attached the map I used for testing.
 

Attachments

  • Removing Unit.jpg
    Removing Unit.jpg
    140.3 KB · Views: 76
  • Recycling Unit.jpg
    Recycling Unit.jpg
    161.7 KB · Views: 80
  • Creating Unit.jpg
    Creating Unit.jpg
    150.5 KB · Views: 109
  • Retrieving Unit.jpg
    Retrieving Unit.jpg
    157.8 KB · Views: 81
  • DummyRecyclerv1.24 SpeedTest.w3x
    48.3 KB · Views: 63
Level 22
Joined
Feb 6, 2014
Messages
2,466
Updated for safety purposes.
v1.25 - [14 December 2016]
- Added ShowDummy function which allows you to show/hide a Dummy without problems.
- Retrieved Dummy Units are automatically shown by default even if they were hidden previously.


Personal experience: I thought the system was bugged because sometimes the dummy is not shown but it turns out that I had hidden it (ShowUnit) in another library and forgot to unhide it before recycling so now retrieved Dummy units are shown the moment they are retrieved.


Side note: Woah, almost 4k downloads.
 
Level 37
Joined
Jul 22, 2015
Messages
3,485
Still one of my favorite systems in the database :3 if I may make a recommendation, I personally think most maps should have 3 different types of dummy units: one with no animation name (small), another with "medium", and a last one with "large". I never realized how much of a visual impact it has on scaled effects (via SetUnitScale()). Would there be a way for you to integrate this into your system? As in allowing me to decide whether I want a small, medium, or large dummy?
 
Top