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

FirstOfGroup2

Status
Not open for further replies.
Level 13
Joined
Nov 7, 2014
Messages
571
I think this is an easy workaround for the problem discussed here, which has "bitten" at least few people before.

JASS:
library FirstOfGroup2 initializer init

// Notes:
//     Iterating the native group type (which seems to be a doubly-linked list of units) using
//     the FirstOfGroup native is faster than using ForGroup (which uses a callback function and GetEnumUnit())
//     or at least its more convenient because one can see/use the local variables that are in scope.
//     But it has a small caveat which is that when units get removed (either explicitly with the
//     RemoveUnit native or implicitly when they die and after a while they decay) they persist in groups
//     that they were added to as "ghosts" for which FirstOfGroup returns null but ForGroup skips over.

//     What FirstOfGroup2 does is to detect when a "ghost" is returned by FirstOfGroup and then clears the
//     group of all the "ghosts" using the GroupRefresh function. We can detect if FirstOfGroup returns a ghost
//     or not by adding a non-hero unit to the group (with GroupAddUnit) and test if FirstOfGroup returns
//     that unit or a "ghost", if it returns the unit that we added then we've reached the end of the group,
//     otherwise if it returns null (a "ghost") then we GroupRefresh the group, remove the unit that we added
//     and return FirstOfGroup.
//     The reason we use a non-hero unit is because GroupAddUnit adds heros to the "head" of
//     the doubly-linked list but it adds non-hero units to the "tail" of the list.

//     It only makes sense to use FirstOfGroup2 instead of FirstOfGroup when iterating groups
//     that could have "ghosts"; it doesn't make sense to use FirstOfGroup2 when we
//     GroupEnumUnits* and then immediately iterate the group with FirstOfGroup because
//     in doing so the group becomes empty.

// Credits:
//     Captain Griffen (GroupRefresh): http://www.wc3c.net/showthread.php?t=101810

globals
    private constant integer DUMMY_ID = 'hpea' // can't be a hero! =)
    private constant real DUMMY_HOME_X = -2048.0
    private constant real DUMMY_HOME_Y = 2048.0
endglobals

globals
    private unit dummy
endglobals

private function init takes nothing returns nothing
    set dummy = CreateUnit(Player(15), DUMMY_ID, DUMMY_HOME_X, DUMMY_HOME_Y, 270.0)
    call UnitAddAbility(dummy, 'Aloc') // unselectable
    call UnitAddAbility(dummy, 'Avul') // invulnerable
    call PauseUnit(dummy, true)
    call ShowUnit(dummy, false)
endfunction

globals
    private boolean clear_ghosts
    private group gg
endglobals
private function group_enum takes nothing returns nothing
    if clear_ghosts then
        call GroupClear(gg)
        set clear_ghosts = false
    endif
    call GroupAddUnit(gg, GetEnumUnit())
endfunction
private function group_refresh takes group g returns nothing
    set clear_ghosts = true
    set gg = g
    call ForGroup(gg, function group_enum)
    if clear_ghosts then
        // group had only ghosts
        call GroupClear(g)
    endif
endfunction

globals
    private unit result
endglobals
function FirstOfGroup2 takes group g returns unit
    set result = FirstOfGroup(g)
    if result != null then
        return result
    endif

    call GroupAddUnit(g, dummy)
    if FirstOfGroup(g) == dummy then
        call GroupRemoveUnit(g, dummy)
        set result = null
        return null
    endif

    call group_refresh(g)
    call GroupRemoveUnit(g, dummy)
    return FirstOfGroup(g)
endfunction

endlibrary
 
Nice info with tail and head-- always wondered if there are default mechanics.
You could add a IsUnitGhost function for the lolz. ^)^
And the user maybe also could just choose to call FoG2 or just always calls the clean function and uses FoG(1) ?
I don't think we have such thing in hive resources, when we don't find such code, we should add it to our database.
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
How about automatic group refresher?
JASS:
library AutoGroupRefresh /*


    */requires /*

    */Table    /*
    */UnitDex


    private module AllocList

        readonly thistype prev
        readonly thistype next
        private thistype recycler
        private static thistype node = 0

        static method allocate takes nothing returns thistype
            debug if thistype(0).recycler == 0 then
                debug call BJDebugMsg("|CFFFFCC00[AllocList]|R |CFFFF0000Attempt to allocate more than 8190 instances.|R")
                debug return 0
            debug endif
            set node = thistype(0).recycler
            set thistype(0).recycler = node.recycler
            debug set node.recycler = -1
            set node.next = 0
            set node.prev = thistype(0).prev
            set node.prev.next = node
            set thistype(0).prev = node
            return node
        endmethod

        method deallocate takes nothing returns nothing
            local thistype prev = .prev
            local thistype next = .next
            debug if .recycler != -1 then
                debug call BJDebugMsg("|CFFFFCC00[AllocList]|R |CFFFF0000Attempt to double-free instance " + I2S(this) + ".|R")
                debug return
            debug endif
            set .recycler = thistype(0).recycler
            set thistype(0).recycler = this
            set prev.next = next
            set next.prev = prev
        endmethod

        private static method onInit takes nothing returns nothing
            set thistype(8190).recycler = 0
            loop
                set node.recycler = node + 1
                set node = node + 1
                exitwhen node == 8190
            endloop
        endmethod

    endmodule

    private struct Group extends array

        group group
        static Table table
        private static group tempGroup
        private static group refreshGroup
        private static boolean refreshFlag

        implement AllocList

        static method create takes nothing returns thistype
            local thistype this = allocate()
            set .group = CreateGroup()
            set table[GetHandleId(.group)] = this
            return this
        endmethod

        method destroy takes nothing returns nothing
            call .deallocate()
            call table.remove(GetHandleId(.group))
            call DestroyGroup(.group)
            set .group = null
        endmethod

        private static method onDeindex takes nothing returns nothing
            local thistype this = thistype(0).next
            loop
                exitwhen this == 0
                if IsUnitInGroup(GetIndexedUnit(), .group) then
                    call GroupRemoveUnit(.group, GetIndexedUnit())
                endif
                set this = .next
            endloop
        endmethod

        private static method onInit takes nothing returns nothing
            local code c = function thistype.onDeindex
            set table = Table.create()
            call OnUnitDeindex(c)
        endmethod

    endstruct


    // Use this instead of CreateGroup() to create a group that
    // automatically removes "ghost units"
    function NewGroup takes nothing returns group
        return Group.create().group
    endfunction

    // Use this for groups created by NewGroup(), well any group
    // actually
    function ReleaseGroup takes group g returns nothing
        local Group this = Group.table[getHandleId(g)]
        if this != 0 then
            call this.destroy()
        else
            call DestroyGroup(g)
        endif
    endfunction


endlibrary
 
Level 13
Joined
Nov 7, 2014
Messages
571
How about automatic group refresher?
I suppose that would work as well although I think its slightly less elegant ;P.

JASS:
// The way IsUnitInGroup and GroupRemoveUnit find their unit is probably the same
// so there's no need to do it 2 times, i.e only call GroupRemoveUnit instead?
if IsUnitInGroup(GetIndexedUnit(), .group) then
    call GroupRemoveUnit(.group, GetIndexedUnit())
endif

JASS:
// GhostFreeGroupCreate() might be a better name for this function =)
function NewGroup takes nothing returns group
    return Group.create().group
endfunction
 
Status
Not open for further replies.
Top