• 🏆 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] IPool

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
TriggerHappy, for a long time I was the only active moderator in the JASS forum and the users and staff said it was fine for me to approve my own resources given a general consensus that it doesn't suck.

I wasn't aware there were any moderators in the JASS section these days, especially if you look at the submission queue.
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
mag said he is chat mod, because he is too lazy to moderate most likely :D

BPower is here, and I dont know about other mods, yes there are ones like PnF, but he also isnt very active nowadays, and I dont know if he even moredates spell section :D

also yes, if you are(were) the only moderator, your resources would never get approved if you couldnt approve them :D
 
Level 8
Joined
Feb 3, 2013
Messages
277
Herm......
I'm using IPool and while its great, I have come across an issue.
I periodically spawn units using IPool. I also make sure the units in IPool get periodically stronger and weaker units are removed. But as time goes on, IPool gets messed up. When I use the debug print method, it seems fine. But the getItem() is returning values that I've removed!
It doesn't work in other's maps as i've tried...

4g6uk1.png

I just made IPool.print method, read the objectname
The Orange text is that IPool.print()
The White Text is integer ui = IPool.getItem()
The getItem() method is getting ID's that have been removed!
JASS:
        static method onLoop takes nothing returns nothing
            local thistype this = l.first
            local unit u
            local integer i
            local thistype node
            local thistype n
            local integer ui
            
            while (this != l.sentinel)
                if (.active) then
                    set .time = .time - period
                    if (.time <= 0.0) then
                        call GroupEnumUnitsInRange(g, .cenX, .cenY, CAMP_CHECK_AOE, function AliveHeroes)
                        if ((FirstOfGroup(g) == null or not .checkAoe) and (.cc == 0 or not .checkCount)) then
                            set node = .ulist.first
                            while (node != .ulist.sentinel)
                                call node.uPool.print()
                                set i = node.max
                                // LOOK HERE
                                // So basically I'm looping through a list containing multiple IPools
                                // I made it so IPool.print method prints out Object Names instead of the id's
                                // I'm just using the ui integer because I suspect the IPool.getItem() method
                                // returning the wrong values
                                while (i > 0 and .cc < .mc)
                                    set ui = node.uPool.getItem()
                                    call Print(GetObjectName(ui), 5)
                                    set u = CreateUnit(Player(12), ui, GetRandomReal(.minX, .maxX), GetRandomReal(.minY, .maxY), 0.)
                                    set n = .units.enqueue()
                                    set n.u = u
                                    set ht[0][GetHandleId(u)] = this
                                    set ht[1][GetHandleId(u)] = n
                                    set .cc = .cc + 1
                                    set i = i - 1
                                    set u = null
                                endwhile
                                set node = node.next
                            endwhile
                        endif
                        set .time = .ogt
                    endif
                endif
                set this = .next
            endwhile
            
            set updateTime = updateTime - period
            if (updateTime <= 0.0) then
            
                // Periodically update IPool units...
                if (weakCamp.contains(MURGUL_SLAVE_ID)) then
                    call weakCamp.remove(MURGUL_SLAVE_ID)
                    call weakCamp.shiftWeight(MURGUL_CASTER_ID, 1)
                    call weakCamp.add(MAKURA_TIDEBRINGER_ID, 1)
                    call strongCamp.remove(MAKURA_TIDEBRINGER_ID)
                    call strongCamp.add(MURGUL_SEEKER_ID, 1)
                elseif (weakCamp.contains(MURGUL_CASTER_ID)) then
                    call weakCamp.remove(MURGUL_CASTER_ID)
                    call weakCamp.shiftWeight(MURGUL_HUNTER_ID, 1)
                    call weakCamp.add(MAKURA_DWELLER_ID, 1)
                    call strongCamp.remove(MAKURA_DWELLER_ID)
                    call strongCamp.add(MURGUL_TIDERUNNER_ID, 1)
                elseif (weakCamp.contains(MURGUL_HUNTER_ID)) then
                    call weakCamp.remove(MURGUL_HUNTER_ID)
                    call weakCamp.shiftWeight(MAKURA_TIDEBRINGER_ID, 1)
                    call weakCamp.add(MURGUL_SEEKER_ID, 1)
                    call strongCamp.remove(MURGUL_SEEKER_ID)
                    call strongCamp.add(MURGUL_FLESHEATER_ID, 1)
                elseif (weakCamp.contains(MAKURA_TIDEBRINGER_ID)) then
                    call weakCamp.remove(MAKURA_TIDEBRINGER_ID) 
                    call weakCamp.add(MURGUL_TIDERUNNER_ID, 1)
                    call weakCamp.shiftWeight(MAKURA_DWELLER_ID, 1)
                    call strongCamp.remove(MURGUL_TIDERUNNER_ID)
                    call strongCamp.add(SEA_TURTLE_ID, 1)
                elseif (weakCamp.contains(MAKURA_DWELLER_ID)) then
                    call weakCamp.remove(MAKURA_DWELLER_ID)
                    call weakCamp.shiftWeight(MURGUL_SEEKER_ID, 1)
                    call weakCamp.add(MURGUL_FLESHEATER_ID, 1)
                    call strongCamp.remove(MURGUL_FLESHEATER_ID)
                    call strongCamp.add(MAKURA_SNAPPER_ID, 1)
                elseif (weakCamp.contains(MURGUL_SEEKER_ID)) then
                    call weakCamp.remove(MURGUL_SEEKER_ID)
                    call weakCamp.shiftWeight(MURGUL_TIDERUNNER_ID, 1)
                    call weakCamp.add(SEA_TURTLE_ID, 1)
                    call strongCamp.remove(SEA_TURTLE_ID)
                    call strongCamp.add(GARGANTUAN_TURTOISE_ID, 1)
                endif
                
                set updateTime = PHASE_TIME
            endif
            
            set u = null
        endmethod
        
        static method onInit takes nothing returns nothing
            set weakCamp = IPool.create()
                call weakCamp.add(MURGUL_SLAVE_ID, 2)
                call weakCamp.add(MURGUL_CASTER_ID, 1)
                call weakCamp.add(MURGUL_HUNTER_ID, 1)
            set strongCamp = IPool.create()
                call strongCamp.add(MAKURA_TIDEBRINGER_ID, 1)
                call strongCamp.add(MAKURA_DWELLER_ID, 1)
        endmethod

This basically does the same thing as the above, but I just outlined the problem into another fresh new map.
30xagew.png

JASS:
scope Test
    globals
        IPool camp1
        IPool camp2
    endglobals
    
    private struct xe extends array
        implement SharedList
        
        rect r
        
        thistype ulist
        IPool uPool
        integer max
        
        static integer ic = 0
        static trigger t = CreateTrigger()
        static thistype l
        static group g = CreateGroup()
        
        static method killAll takes nothing returns boolean
            call RemoveUnit(GetFilterUnit())
            return false
        endmethod
        
        static method onChat takes nothing returns boolean
            local string str = GetEventPlayerChatString()
            local thistype this
            local thistype node
            local integer id
            local integer i = GetRandomInt(0, ic-1)
            
            if (str == "-upg") then
                if (camp1.contains('hpea')) then
                    call camp1.remove('hpea')
                    call camp1.add('hmtm', 1)
                    call camp2.remove('hmtm')
                    call camp2.add('hgry', 1)
                elseif (camp1.contains('hfoo')) then
                    call camp1.remove('hfoo')
                    call camp1.add('hgyr', 1)
                    call camp2.remove('hgyr')
                    call camp2.add('hmpr', 1)
                elseif (camp1.contains('hkni')) then
                    call camp1.remove('hkni')
                    call camp1.add('hgry', 1)
                    call camp2.remove('hgry')
                    call camp2.add('hsor', 1)
                elseif (camp1.contains('hmtm')) then
                    call camp1.remove('hmtm')
                    call camp1.add('hmpr', 1)
                    call camp2.remove('hmpr')
                    call camp2.add('hmtt', 1)
                elseif (camp1.contains('hgyr')) then
                    call camp1.remove('hgyr')
                    call camp1.add('hsor', 1)
                    call camp2.remove('hsor')
                    call camp2.add('hspt', 1)
                elseif (camp1.contains('hgry')) then
                    call camp1.remove('hgry')
                    call camp1.add('hmtt', 1)
                    call camp2.remove('hmtt')
                    call camp2.add('hdhw',1)
                endif
            elseif (str == "-make") then
                set this = l.first
                while (this != l.sentinel and i > 0)
                    set this = .next
                    set i = i - 1
                endwhile
                
                set node = .ulist.first
                while(node != .ulist.sentinel)
                    set i = node.max
                    call node.uPool.print()
                    while (i > 0)
                        set id = node.uPool.getItem()
                        call BJDebugMsg(GetObjectName(id))
                        call CreateUnit(Player(0), id, GetRectCenterX(.r), GetRectCenterY(.r), 0.)
                        set i = i - 1
                    endwhile
                    set node = node.next
                endwhile
            else
                call GroupEnumUnitsOfPlayer(g, Player(0), function thistype.killAll)
            endif
                return false
        endmethod
        
        static method new takes rect r returns thistype
            local thistype this = l.enqueue()
            
            set .r = r
            set .ulist = create()
            set ic = ic + 1
            
            return this
        endmethod
        
        method add takes IPool uPool, integer max returns nothing
            local thistype node = .ulist.enqueue()
            set node.uPool = uPool
            set node.max = max
        endmethod
        
        static method onInit takes nothing returns nothing
            local thistype this
            local thistype node
            set l = create()
            
            set camp1 = IPool.create()
                call camp1.add('hpea', 1)
                call camp1.add('hfoo', 1)
                call camp1.add('hkni', 1)
            set camp2 = IPool.create()
                call camp2.add('hmtm', 1)
                call camp2.add('hgyr', 1)
                
            set this = new(gg_rct_1)
                call .add(camp1, 3)
                call .add(camp2, 2)
            set this = new(gg_rct_2)
                call .add(camp1, 2)
            set this = new(gg_rct_3)
                call .add(camp1, 2)
                call .add(camp2, 1)
            set this = new(gg_rct_4)
                call .add(camp1, 3)
            set this = new(gg_rct_5)
                call .add(camp1, 3)
                call .add(camp2, 2)
                
            call TriggerRegisterPlayerChatEvent(t, Player(0), "-upg", true)
            call TriggerRegisterPlayerChatEvent(t, Player(0), "-make", true)
            call TriggerRegisterPlayerChatEvent(t, Player(0), "-clear", true)
            call TriggerAddCondition(t, Filter(function thistype.onChat))
        endmethod
    endstruct
endscope
Thanks in advance,..
 
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
Are you using the most up-to-date version of IPool? There was an older version with similar bugs as you describe. I wonder if you might have copied it from another map instead of directly from here on the web site.

I tried evaluating both your script and mine and I couldn't find any cause that an item should remain even though it was removed.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
JASS:
library IPool requires Table, Alloc

private module Init
    private static method onInit takes nothing returns nothing
        set .tar = TableArray[8192]
    endmethod
endmodule
private struct data extends array
    static TableArray tar
    integer int
    integer locks
    integer weight
    implement Alloc
    implement Init
endstruct

    private function Create takes nothing returns data
        local data this = data.allocate()
        set this.locks = 0
        return this
    endfunction
    private function Destroy takes data this returns boolean
        if this.locks == -1 or this == 0 then
            debug call BJDebugMsg("IPool Error: Attempt to double-free instance!")
            return false
        endif
        set this.locks = -1
        call this.deallocate()
        return true
    endfunction

    private function Lock takes data i returns nothing
        if i.locks == -1 then
            debug call BJDebugMsg("IPool Error: Attempt to lock a destroyed instance!")
            return
        endif
        set i.locks = i.locks + 1
    endfunction
    private function Unlock takes data i returns boolean
        if i.locks == -1 then
            debug call BJDebugMsg("IPool Error: Attempt to unlock destroyed instance!")
            return false
        endif
        set i.locks = i.locks - 1
        return i.locks == 0
    endfunction

struct IPool extends array
    
    method operator weight takes nothing returns integer
        return data(this).weight
    endmethod
    private method operator weight= takes integer lbs returns nothing
        set data(this).weight = lbs
    endmethod

    private method operator table takes nothing returns Table
        return data(this).int
    endmethod
    private method operator table= takes Table t returns nothing
        set data(this).int = t
    endmethod

    static method create takes nothing returns thistype
        local thistype this = Create()
        set this.table = Table.create()
        return this
    endmethod
    method flush takes nothing returns nothing
        call this.table.flush()
        call data.tar[this].flush()
        set this.weight = 0
    endmethod

    method destroy takes nothing returns nothing
        if Destroy(this) then
            call this.table.destroy()
            call data.tar[this].flush()
            set this.weight = 0
        endif
    endmethod

    method lock takes nothing returns nothing
        call Lock(this)
    endmethod
    method unlock takes nothing returns nothing
        if Unlock(this) then
            call this.destroy()
        endif
    endmethod

    //One-liner method to get a random item from the pool based on weight
    method getItem takes nothing returns integer
        return this.table[GetRandomInt(0, this.weight -1)]
    endmethod

    method weightOf takes integer value returns integer
        return data.tar[this][value]
    endmethod
    method chanceOf takes integer value returns real //returns between 0. and 1.
        return this.weightOf(value) / (this.weight + 0.) //don't divide by 0 here or else!
    endmethod
    method contains takes integer value returns boolean
        return data.tar[this].has(value)
    endmethod

    method add takes integer value, integer lbs returns nothing
        local Table tb = this.table
        local integer i = this.weight
        if lbs < 1 then
            debug call BJDebugMsg("IPool Error: Tried to add value with invalid weight!")
            return
        endif
        set data.tar[this][value] = data.tar[this][value] + lbs
        set lbs = i + lbs
        loop
            exitwhen i == lbs
            set tb[i] = value //treat this.table as an array
            set i = i + 1
        endloop
    endmethod
    
    method remove takes integer value returns nothing
        local Table tb = this.table
        local Table new
        local integer i = this.weight
        local integer n = 0
        local integer val
        if not this.contains(value) then
            debug call BJDebugMsg("IPool Error: Attempt to remove un-added instance!")
            return
        endif
        set new = Table.create()
        set this.table = new
        loop
            set i = i - 1
            set val = tb[i]
            if val != value then
                set new[n] = val //write to the new Table without gaps
                set n = n + 1
            endif
            exitwhen i == 0
        endloop
        set this.weight = n //lower pool weight
        call tb.destroy() //abandon old Table instance
        call data.tar[this].remove(value) //clear the value's weight now that it's gone
    endmethod

    method copy takes nothing returns thistype
        local thistype new = .create()
        local integer i = this.weight
        local Table tt = this.table
        local Table nt = new.table
        local Table dt = data.tar[new]
        local integer val
        if this.weight == 0 then
            debug call BJDebugMsg("IPool Error: Attempt to copy invalid instance!")
            call new.destroy()
            return 0
        endif
        set new.weight = i
        loop
            set i = i - 1
            exitwhen i == 0
            set val = tt[i]
            set nt[i] = val
            set dt[val] = dt[val] + 1
        endloop
        return new
    endmethod
        
    static if DEBUG_MODE then
        method print takes nothing returns nothing //print the array of the pool
            local string s = "IPool: |cffffcc33"
            local integer i = this.weight
            local Table t = this.table
            loop
                set i = i - 1
                exitwhen i <= 0
                set s = s + "[" + I2S(t[i]) + "]"
            endloop
            call BJDebugMsg(s + "|r")
        endmethod

    endif

endstruct

//New struct to handle deliberately-rare chances
struct SubPool extends array
    
    private IPool iPool //for association if you want it
    private thistype nest //you can nest IPoolMinis for poolception

    //you can change a value's weight via subpoolinstance[value].weight = blah
    //you can also change the entire pool's weight via subpoolinstance.weight = blah.
    method operator weight takes nothing returns integer
        return data(this).weight
    endmethod
    method operator weight= takes integer lbs returns nothing
        set data(this).weight = lbs
    endmethod

    private method operator value takes nothing returns integer
        return data(this).int
    endmethod
    private method operator value= takes integer val returns nothing
        set data(this).int = val
    endmethod

    private thistype next
    private thistype prev

    method operator pool takes nothing returns IPool
        return this.iPool
    endmethod
    method operator pool= takes IPool ip returns nothing
        if ip != 0 then
            call ip.lock()
        endif
        if this.iPool != 0 then
            call this.iPool.unlock()
        endif
        set this.iPool = ip
    endmethod

    static method create takes integer totalWeight returns thistype
        local thistype this = Create()
        set this.next = this
        set this.prev = this //I'm my own best friend
        set this.weight = totalWeight
        return this
    endmethod
    method destroy takes nothing returns nothing
        local thistype curr = this
        if this.next == -1 then
            debug call BJDebugMsg("SubPool Error: Attempt to double-free!")
            return
        endif
        loop
            set curr = curr.next
            call Destroy(curr) //destroy all the things
            exitwhen curr == this
        endloop
        set this.next = -1
        set this.pool = 0
        if this.nest != 0 then
            if data(this.nest).locks == 1 then
                call this.nest.destroy()
            else
                call Unlock(this.nest)
            endif
            set this.nest = 0
        endif
        call data.tar[this].flush()
    endmethod

    method lock takes nothing returns nothing
        call Lock(this)
    endmethod
    method unlock takes nothing returns nothing
        if Unlock(this) then
            call this.destroy()
        endif
    endmethod

    method operator subPool takes nothing returns IPool
        return this.nest
    endmethod
    method operator subPool= takes thistype ip returns nothing
        if this == ip then
            debug call BJDebugMsg("SubPool Error: Don't set a subPool within itself. Use the .copy() method, instead.")
            return
        endif
        if ip != 0 then
            call ip.lock()
        endif
        if this.nest != 0 then
            call this.nest.unlock()
        endif
        set this.nest = ip
    endmethod

    method add takes integer val, integer lbs returns nothing
        local thistype new
        if lbs <= 0 then
            debug call BJDebugMsg("SubPool Error: Don't add a value without weight")
            return
        endif
        if data.tar[this].has(val) then
            set new = data.tar[this][val]
            set new.weight = new.weight + lbs
            return
        endif
        set new = Create()
        set new.prev = this.prev
        set this.prev.next = new
        set this.prev = new
        set new.next = this

        set new.value = val
        set new.weight = lbs
        
        set data.tar[this][val] = new
    endmethod

    method contains takes integer val returns boolean
        return data.tar[this].has(val)
    endmethod
    method operator [] takes integer val returns thistype
        return data.tar[this][val]
    endmethod
    method operator []= takes integer val, integer newWeight returns nothing
        set this[val].weight = newWeight
    endmethod

    method remove takes integer val returns nothing
        local thistype node = this[val]
        if Destroy(node) then
            set node.prev.next = node.next
            set node.next.prev = node.prev
            call data.tar[this].remove(val)
        else
            debug call BJDebugMsg("SubPool Error: Attempt to remove non-added value")
        endif
    endmethod
    method getItem takes nothing returns integer
        local thistype curr = this
        local integer i = GetRandomInt(1, this.weight)
        loop
            set curr = curr.next
            set i = i - curr.weight
            exitwhen i <= 0
        endloop
        if curr == this then
            if this.nest != 0 then
                set i = this.nest.getItem()
            else
                set i = 0
            endif
            if i == 0 and this.pool != 0 then //if no low-probability item could be found...
                set i = this.pool.getItem() //pick a random int from main pool
            endif
        else
            set i = curr.value
        endif
        return i
    endmethod

    method copy takes nothing returns thistype
        local thistype new = .create(this.weight)
        local thistype curr = this
        set new.pool = this.iPool
        set new.subPool = this.nest
        loop
            set curr = curr.next
            exitwhen curr == this
            call new.add(curr.value, curr.weight)
        endloop
        return new
    endmethod

    static if DEBUG_MODE then
        method print takes nothing returns nothing
            local thistype curr = this
            local string s = "SubPool: |cffffcc33"
            loop
                set curr = curr.next
                exitwhen curr == this
                set s = s + "[" + I2S(curr.value) + "]<" + I2S(curr.weight) + ">"
            endloop
            call BJDebugMsg(s + "|r")
            if this.nest != 0 then
                call this.nest.print()
            endif
            if this.iPool != 0 then
                call this.iPool.print()
            endif
        endmethod
    endif
endstruct

endlibrary
 
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
example script:

JASS:
local IPoolMini i = IPoolMini.create()
call i.add('hkni',1,100) //has a 1/100 chance to be picked
call i.add('Hpal',2,53) //has a 2/53 chance to be picked
set i.pool = IPool.create() //FYI multiple IPoolMinis could share the same IPool
call i.pool.add('hpea',6)
call i.pool.add('hfoo',2)
call i.getItem() //if lucky, an item from IPoolMini will be returned. Otherwise,
//the IPool will return one of its items for you. Similarly, 0 will be returned
//if no IPool was preset.
call i.pool.destroy()
call i.destroy() //does not destroy the "pool" member which is why I destroyed it manually
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
Say for example you have an IPool that is used by several IPoolMinis and maybe even standalone. You have to manually create it - so manually destroying it comes with the territory.

You also don't NEED an IPool with each mini pool. What if you only wanted to create something if luck prevailed? Not every creep should drop items, for example.
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
true, didnt think of having shared IPool.

What about making the odds of getting idems higher than certain?

For instance, I add boots of speed as drop to boss with 1 in 3 chance, and then I add boots of lower speed into the boss with 3 in 4 chance. Or even adding 4 items all with 1 in 3 chance. How would the behaviour be then?
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
OK I have changed the API for IPoolMinis slightly but changed how they function somewhat. The probability is now much easier to follow and the removal/getItem methods are now much more efficient.

http://www.hiveworkshop.com/forums/2716745-post67.html

You now assign a total weight for the IPoolMini, and each item that is added has a chance to be picked out of that pre-specified weight. The weight of both the pool and its items can be set arbitrarily, but the weight of the entire pool should never be lower than the sum of the weight of its contents. API is now like this:

JASS:
ipm = IPoolMini.create(1000) //each entry has an x/1000 chance
ipm.add('A001', 10) //has a 1/100 chance to be picked
ipm.add('A007', 100) //has a 1/10 chance to be picked
ipm.add('A036', 1) //will have a 1/1000 chance to be picked
ipm.getItem() //has an 11.1% chance of picking something
ipm.weight = 500 //doubles the chance something will be picked
ipm['A007'] = 200 //Now has a 40% chance to be picked
ipm.pool = myGlobalIPool //still supported of course
 
Last edited:
Level 14
Joined
Dec 12, 2012
Messages
1,007
Say for example you have an IPool that is used by several IPoolMinis and maybe even standalone. You have to manually create it - so manually destroying it comes with the territory.

I think this is pretty dangerous... how can you know when you want to destroy your IPoolMini that its associated IPool is "final" in the sense that there are no references to it elsewhere left? Thats quite an invitation for memory leaks IMO.

I also don't really see the purpose of IPoolMini... Its for having an alternative group of items with different probability than the normal IPool, right? Because the name is quite confusing I think - what exactly makes the IPoolMini "mini"?

Just having different groups within the IPool would achieve the same, but without the need for manual reference tracking and more flexible (API is just exemplary):

JASS:
local IPool pool = IPool.create() 
call pool.add('unt1', 0, 0.05) // Group 0, probability 5%
call pool.add('unt2', 0, 0.07) // Group 0, probability 7%
call pool.add('unt3', 0, 0.10) // Group 0, probability 10%
call pool.add('unt4', 1, 0.50) // Group 1, probability 50%
call pool.add('unt5', 1, 0.30) // Group 1, probability 30%
call pool.getItem() // 22% for group 0 and 80% for group 1, rest is null
call pool.destroy() // Just one destroy
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
Thanks for the feedback!

I am not set on the naming convention of IPoolMini. However, I gave it that name because it enables low-probability of picks without spamming the weight of other items in an IPool. It acts almost the same way that Rising_Dusk's pool does, in that the getItem method has to be done in a loop to get a value. The original reason I wrote IPool was so that the getItem method could be O(1) instead of O(n).

I dig the concept of probability groups as part of the same overall pool, and is something I tried to envision before I made the compromise of mini IPools. I made IPoolMini because someone pointed out to me the risk of thread crashes and the obvious performance hit when adding huge different weights in the IPool. An item with a 1/1000 chance in an IPool would require the other weights to outweigh that item 1000 to 1.

So IPoolMini has all the features of IPool, is more efficient on add/remove, but less efficient on .getItem(). Hence they are 2 separate things.

EDIT: In response to the memory leak concern, I have just added "lock" and "unlock" methods to IPool. I have changed this.pool to a method operator so it locks/unlocks the pool automatically. If a pool was unlocked the same number of times as it was locked, the pool is destroyed. Likewise, if the user chooses to manually lock the pool, then the user will have to manually unlock it.
 
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
By the way, your example can be achieved with this:

JASS:
local IPoolMini pool = IPoolMini.create(100) 
call pool.add('unt1', 5) // probability 5%
call pool.add('unt2', 7) // probability 7%
call pool.add('unt3', 10) // probability 10%
set pool.pool = IPool.create()
call pool.pool.add('unt4', 5) // probability 50%
call pool.pool.add('unt5', 3) // probability 30%
call pool.pool.add(0, 2)
call pool.getItem() // 22% for IPoolMini and 80% for IPool, rest is null
call pool.destroy() // Just one destroy
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
Ok some cool changes now, including renaming IPoolMini to SubPool:

You can now nest SubPools as many times as needed

JASS:
local SubPool pool = SubPool.create(100) 
call pool.add('unt1', 5) // probability 5%
call pool.add('unt2', 7) // probability 7%
call pool.add('unt3', 10) // probability 10%
set pool.subPool = SubPool.create(10)
call pool.subPool.add('unt4', 5) // probability 50%
call pool.subPool.add('unt5', 3) // probability 30%
call pool.getItem() // 22% for first SubPool and 80% for nested SubPool(s).
call pool.destroy() // Just one destroy
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
Alright, I've gone ahead and peppered this with debug messages and fixed all the syntax errors I could find. If anyone has the time to see if this will compile yet, please check it for me.

I finally got my Windows PC back but need to find the charger buried in one of many various boxes

http://www.hiveworkshop.com/forums/2716745-post67.html
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
1. the very first function, instead of having endfunction has endmethod(I tried to type endmethod for functions a lot too :D)

Error: This actually causes the next function to say "unexpected takes"

2. IPool.getItem is marked as returns nothing
Error: Cannot return value from function that returns nothing"

It compiles when both of these are fixed.

Fixed snippet(with only these two changes)

JASS:
library IPool requires Table, Alloc

private module Init
    private static method onInit takes nothing returns nothing
        set .tar = TableArray[8192]
    endmethod
endmodule
private struct data extends array
    static TableArray tar
    integer int
    integer locks
    integer weight
    implement Alloc
    implement Init
endstruct

    private function Create takes nothing returns data
        local data this = data.allocate()
        set this.locks = 0
        return this
    endfunction
    private function Destroy takes data this returns boolean
        if this.locks == -1 or this == 0 then
            debug call BJDebugMsg("IPool Error: Attempt to double-free instance!")
            return false
        endif
        set this.locks = -1
        call this.deallocate()
        return true
    endfunction

    private function Lock takes data i returns nothing
        if i.locks == -1 then
            debug call BJDebugMsg("IPool Error: Attempt to lock a destroyed instance!")
            return
        endif
        set i.locks = i.locks + 1
    endfunction
    private function Unlock takes data i returns boolean
        if i.locks == -1 then
            debug call BJDebugMsg("IPool Error: Attempt to unlock destroyed instance!")
            return false
        endif
        set i.locks = i.locks - 1
        return i.locks == 0
    endfunction

struct IPool extends array
   
    method operator weight takes nothing returns integer
        return data(this).weight
    endmethod
    private method operator weight= takes integer lbs returns nothing
        set data(this).weight = lbs
    endmethod

    private method operator table takes nothing returns Table
        return data(this).int
    endmethod
    private method operator table= takes Table t returns nothing
        set data(this).int = t
    endmethod

    static method create takes nothing returns thistype
        local thistype this = Create()
        set this.table = Table.create()
        return this
    endmethod
    method flush takes nothing returns nothing
        call this.table.flush()
        call data.tar[this].flush()
        set this.weight = 0
    endmethod

    method destroy takes nothing returns nothing
        if Destroy(this) then
            call this.table.destroy()
            call data.tar[this].flush()
            set this.weight = 0
        endif
    endmethod

    method lock takes nothing returns nothing
        call Lock(this)
    endmethod
    method unlock takes nothing returns nothing
        if Unlock(this) then
            call this.destroy()
        endif
    endmethod

    //One-liner method to get a random item from the pool based on weight
    method getItem takes nothing returns integer
        return this.table[GetRandomInt(0, this.weight -1)]
    endmethod

    method weightOf takes integer value returns integer
        return data.tar[this][value]
    endmethod
    method chanceOf takes integer value returns real //returns between 0. and 1.
        return this.weightOf(value) / (this.weight + 0.) //don't divide by 0 here or else!
    endmethod
    method contains takes integer value returns boolean
        return data.tar[this].has(value)
    endmethod

    method add takes integer value, integer lbs returns nothing
        local Table tb = this.table
        local integer i = this.weight
        if lbs < 1 then
            debug call BJDebugMsg("IPool Error: Tried to add value with invalid weight!")
            return
        endif
        set data.tar[this][value] = data.tar[this][value] + lbs
        set lbs = i + lbs
        loop
            exitwhen i == lbs
            set tb[i] = value //treat this.table as an array
            set i = i + 1
        endloop
    endmethod
   
    method remove takes integer value returns nothing
        local Table tb = this.table
        local Table new
        local integer i = this.weight
        local integer n = 0
        local integer val
        if not this.contains(value) then
            debug call BJDebugMsg("IPool Error: Attempt to remove un-added instance!")
            return
        endif
        set new = Table.create()
        set this.table = new
        loop
            set i = i - 1
            set val = tb[i]
            if val != value then
                set new[n] = val //write to the new Table without gaps
                set n = n + 1
            endif
            exitwhen i == 0
        endloop
        set this.weight = n //lower pool weight
        call tb.destroy() //abandon old Table instance
        call data.tar[this].remove(value) //clear the value's weight now that it's gone
    endmethod

    method copy takes nothing returns thistype
        local thistype new = .create()
        local integer i = this.weight
        local Table tt = this.table
        local Table nt = new.table
        local Table dt = data.tar[new]
        local integer val
        if this.weight == 0 then
            debug call BJDebugMsg("IPool Error: Attempt to copy invalid instance!")
            call new.destroy()
            return 0
        endif
        set new.weight = i
        loop
            set i = i - 1
            exitwhen i == 0
            set val = tt[i]
            set nt[i] = val
            set dt[val] = dt[val] + 1
        endloop
        return new
    endmethod
       
    static if DEBUG_MODE then
        method print takes nothing returns nothing //print the array of the pool
            local string s = "IPool: |cffffcc33"
            local integer i = this.weight
            local Table t = this.table
            loop
                set i = i - 1
                exitwhen i <= 0
                set s = s + "[" + I2S(t[i]) + "]"
            endloop
            call BJDebugMsg(s + "|r")
        endmethod

    endif

endstruct

//New struct to handle deliberately-rare chances
struct SubPool extends array
   
    private IPool iPool //for association if you want it
    private thistype nest //you can nest IPoolMinis for poolception

    //you can change a value's weight via subpoolinstance[value].weight = blah
    //you can also change the entire pool's weight via subpoolinstance.weight = blah.
    method operator weight takes nothing returns integer
        return data(this).weight
    endmethod
    method operator weight= takes integer lbs returns nothing
        set data(this).weight = lbs
    endmethod

    private method operator value takes nothing returns integer
        return data(this).int
    endmethod
    private method operator value= takes integer val returns nothing
        set data(this).int = val
    endmethod

    private thistype next
    private thistype prev

    method operator pool takes nothing returns IPool
        return this.iPool
    endmethod
    method operator pool= takes IPool ip returns nothing
        if ip != 0 then
            call ip.lock()
        endif
        if this.iPool != 0 then
            call this.iPool.unlock()
        endif
        set this.iPool = ip
    endmethod

    static method create takes integer totalWeight returns thistype
        local thistype this = Create()
        set this.next = this
        set this.prev = this //I'm my own best friend
        set this.weight = totalWeight
        return this
    endmethod
    method destroy takes nothing returns nothing
        local thistype curr = this
        if this.next == -1 then
            debug call BJDebugMsg("SubPool Error: Attempt to double-free!")
            return
        endif
        loop
            set curr = curr.next
            call Destroy(curr) //destroy all the things
            exitwhen curr == this
        endloop
        set this.next = -1
        set this.pool = 0
        if this.nest != 0 then
            if data(this.nest).locks == 1 then
                call this.nest.destroy()
            else
                call Unlock(this.nest)
            endif
            set this.nest = 0
        endif
        call data.tar[this].flush()
    endmethod

    method lock takes nothing returns nothing
        call Lock(this)
    endmethod
    method unlock takes nothing returns nothing
        if Unlock(this) then
            call this.destroy()
        endif
    endmethod

    method operator subPool takes nothing returns IPool
        return this.nest
    endmethod
    method operator subPool= takes thistype ip returns nothing
        if this == ip then
            debug call BJDebugMsg("SubPool Error: Don't set a subPool within itself. Use the .copy() method, instead.")
            return
        endif
        if ip != 0 then
            call ip.lock()
        endif
        if this.nest != 0 then
            call this.nest.unlock()
        endif
        set this.nest = ip
    endmethod

    method add takes integer val, integer lbs returns nothing
        local thistype new
        if lbs <= 0 then
            debug call BJDebugMsg("SubPool Error: Don't add a value without weight")
            return
        endif
        if data.tar[this].has(val) then
            set new = data.tar[this][val]
            set new.weight = new.weight + lbs
            return
        endif
        set new = Create()
        set new.prev = this.prev
        set this.prev.next = new
        set this.prev = new
        set new.next = this

        set new.value = val
        set new.weight = lbs
       
        set data.tar[this][val] = new
    endmethod

    method contains takes integer val returns boolean
        return data.tar[this].has(val)
    endmethod
    method operator [] takes integer val returns thistype
        return data.tar[this][val]
    endmethod
    method operator []= takes integer val, integer newWeight returns nothing
        set this[val].weight = newWeight
    endmethod

    method remove takes integer val returns nothing
        local thistype node = this[val]
        if Destroy(node) then
            set node.prev.next = node.next
            set node.next.prev = node.prev
            call data.tar[this].remove(val)
        else
            debug call BJDebugMsg("SubPool Error: Attempt to remove non-added value")
        endif
    endmethod
    method getItem takes nothing returns integer
        local thistype curr = this
        local integer i = GetRandomInt(1, this.weight)
        loop
            set curr = curr.next
            set i = i - curr.weight
            exitwhen i <= 0
        endloop
        if curr == this then
            if this.nest != 0 then
                set i = this.nest.getItem()
            else
                set i = 0
            endif
            if i == 0 and this.pool != 0 then //if no low-probability item could be found...
                set i = this.pool.getItem() //pick a random int from main pool
            endif
        else
            set i = curr.value
        endif
        return i
    endmethod

    method copy takes nothing returns thistype
        local thistype new = .create(this.weight)
        local thistype curr = this
        set new.pool = this.iPool
        set new.subPool = this.nest
        loop
            set curr = curr.next
            exitwhen curr == this
            call new.add(curr.value, curr.weight)
        endloop
        return new
    endmethod

    static if DEBUG_MODE then
        method print takes nothing returns nothing
            local thistype curr = this
            local string s = "SubPool: |cffffcc33"
            loop
                set curr = curr.next
                exitwhen curr == this
                set s = s + "[" + I2S(curr.value) + "]<" + I2S(curr.weight) + ">"
            endloop
            call BJDebugMsg(s + "|r")
            if this.nest != 0 then
                call this.nest.print()
            endif
            if this.iPool != 0 then
                call this.iPool.print()
            endif
        endmethod
    endif
endstruct

endlibrary
 
Level 13
Joined
May 11, 2008
Messages
1,198
Anyone care to propose a list of pros and cons for using Bribe's integer pool over Rising Dusk's integer pool?

Here is the other one for reference:
(Yes, some of my comments are in there as questions because I was attempting to understand the library. Yes, I am using it and it works just fine, and I think I configured it so this by the code itself isn't the original as you can clearly see.)
JASS:
library Pool
//******************************************************************************
//* BY: Rising_Dusk
//* 
//* This script gives the user access to general integer pools. A pool is a data
//* structure that allows you to give entries to it a weight. This weight
//* essentially scales how likely certain random accesses to the pool will be
//* returned by the internal .getRandomInt method. Pools can be useful in any
//* number of ways, such as item drops, randomizing enemy encounters, randomly
//* selecting rects weighted by area, and so forth.
//* 
//******************************************************************************
//* 
//* Example usage:
//*    local intpool ip = intpool.create()
//*    call ip.addInt(1, 1.0)
//*    call ip.addInt(2, 0.5)
//*    call ip.getRInt()
//*    call ip.getChance(2)
//*    call ip.getWeight(2)
//*    call ip.removeInt(1)
//*    //or
//8    globals
///    intpool firstip
///    intpool secondip
///    endglobals
//     //later on:
///    set firstip=intpool.create()
//*    call firstip.addInt(1, 1.0)
//*    call firstip.addInt(2, 0.5)
//*    call firstip.getRInt()
//*    call firstip.getChance(2)
//*    call firstip.getWeight(2)
//*    call firstip.removeInt(1)
//*    //etc...
///    //global intpool are usually easier to use because you don't have to keep
///    //creating them over and over again and unless they need to change with
///    //events or time or whatever you only need 1 line to use them.
///    //that is, call nameofintpool.getRInt()
///
//* You will first need to create an intpool as shown above. Once you've done
//* that, you may use the .addInt method to add an entry to the pool with a
//* specific weight. The example above adds 2 integers, one twice as likely to
//* be randomly selected as the other. That means for the above example, the
//* .getRInt method will 66% of the time return 1 and 33% of the time
//* return 2. If you want to remove an entry from an intpool, the .removeInt
//* method is what you will want to use. If you would like to update an entry's
//* weight after already adding it, simply use .addInt again with the new
//* weight.
//* 
//* The .getChance and .getWeight methods are there for convenience. If you are
//* interested in the exact chance of the intpool returning a specific entry,
//* then you should use .getChance to obtain the decimal chance out of 1. If you
//* want to know the weight input for a specific entry, .getWeight will return
//* that for you.
//* 
//* When adding an entry to the intpool with .addInt, the actual magnitude of
//* the weight doesn't matter. What matters is its magnitude relative to the
//* magnitudes of all other entries in the intpool. This means that it is ok to
//* use very large or very small weights and is done at the user's discretion.
//* 

//* It is worth noting that if you use .getRInt on an intpool with no
//* entries, the function will return INTPOOL_NO_ENTRIES, which is about as
//* random an integer as possible so as to avoid people accidentally using it.
//* 
globals
    //These constants can be changed//i think he meant to say can change the value
    private constant integer MAX_INSTANCES         = 2500//8191//current is about 2?
    //i don't understand how the numbers work...
    private constant integer MAX_ENTRIES           = 150//256//current is around 43?
            constant integer INTPOOL_NO_ENTRIES    = 0x672819
            //what is the point? why not return 0?  what is that integer!?
    //Don't change the following global declaration//i changed it lol(the name)
    private hashtable poolht = InitHashtable()
endglobals

struct intpool[MAX_INSTANCES]
    private integer Cnt         = 0
    private real    WeightTotal = 0.
    private integer array Entries[MAX_ENTRIES]
    private real    array Weights[MAX_ENTRIES]
    
    method getWeight takes integer entry returns real
        return Weights[LoadInteger(poolht, integer(this), entry)]
    endmethod
    method getChance takes integer entry returns real
        if WeightTotal > 0. then
            return Weights[LoadInteger(poolht, integer(this), entry)]/WeightTotal
        endif
        return 0.
    endmethod
    method addInt takes integer entry, real weight returns nothing
        local integer in = 0
        if .Cnt == MAX_ENTRIES then
            //Can't hold any more entries
            debug call BJDebugMsg(SCOPE_PREFIX+"Error: .addEntry has reached MAX_ENTRIES")
            return
        elseif weight <= 0. then
            //Zero or negative weights make no sense
            debug call BJDebugMsg(SCOPE_PREFIX+"Error: .addEntry can't take zero or negative weights")
            return
        endif
        set in = LoadInteger(poolht, integer(this), entry)
        if in > 0 then
            //Update old entry
            set .WeightTotal = .WeightTotal - .Weights[in] + weight
            set .Weights[in] = weight
        else
            //Make a new entry
            set .Cnt           = .Cnt + 1
            call SaveInteger(poolht, integer(this), entry, .Cnt)
            set .Entries[.Cnt] = entry
            set .Weights[.Cnt] = weight
            set .WeightTotal   = .WeightTotal + weight
        endif
    endmethod
    method removeInt takes integer entry returns nothing
        local integer in = LoadInteger(poolht, integer(this), entry)
        if in > 0 then
            call RemoveSavedInteger(poolht, integer(this), entry)
            //Remove its entry in the arrays
            set .WeightTotal = .WeightTotal - .Weights[in]
            set .Entries[in] = .Entries[.Cnt]
            set .Weights[in] = .Weights[.Cnt]
            set .Cnt         = .Cnt - 1
        debug else
            debug call BJDebugMsg(SCOPE_PREFIX+"Error: .removeEntry entry doesn't exist")
        endif
    endmethod
    method getRInt takes nothing returns integer
        local real    r = GetRandomReal(0, .WeightTotal)
        local integer c = 0
        if .WeightTotal <= 0. then
            debug call BJDebugMsg(SCOPE_PREFIX+"Error: intpool has no entries")
            return INTPOOL_NO_ENTRIES
        endif
        loop
            set r = r - .Weights[c]
            exitwhen r <= 0
            set c = c + 1
        endloop
        return .Entries[c]
    endmethod
endstruct
endlibrary
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
no useless size limitations(2500 instances), unlimited number of instances.
Also in his system with normal settings you can only have 16 instances, not 2500.

With Bribes, you can actually say a chance, not a weight, so if I say 0.95 it will be 95% chance to be picked. In RD's you only set weight, so at all times you need to recalculate it on your paper to know the actual chance of item being dropped.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
subPool operates in a very similar manner to RD's Pool. IPool is quite different under the hood, in that the .get method is remarkably faster. subPool's .get method is probably faster because it uses integers instead of reals (and is, consequently, more accurate).

A big departure from RD's Pool is that his can only have MAX_INSTANCES/MAX_ENTRIES Pools, whereas IPool can have up to 8190 IPools+subPools+subPool items. Pool will waste a lot of potential storage space by using predefined max_entries whereas subPools are fully-dynamic.

I like RD's Pool. I wanted this resource to be a better implementation of random pools, but I made sure to name it IPool so there aren't API conflicts (like I haphazardly did with NewTable which should've been called Hash).
 
Level 13
Joined
May 11, 2008
Messages
1,198
A big departure from RD's Pool is that his can only have MAX_INSTANCES/MAX_ENTRIES Pools, whereas IPool can have up to 8190 IPools+subPools+subPool items. Pool will waste a lot of potential storage space by using predefined max_entries whereas subPools are fully-dynamic.

I'm not sure if you 2 understood me. I changed it from 8190, see that number in the comment section? Isn't that what it originally was, but I changed it? And I still don't understand that stuff and here I am explaining it to you? I'm too confused.

I only used the integer pool for a couple things. I used it for spawning randomly placed gold mines at the start of the game.
In my initialization spawning units trigger:
JASS:
local player y=Player(PLAYER_NEUTRAL_PASSIVE)
local unit x=CreateUnit( y, 'nwgt', 4864.0, -5760.0, 270.000)//not really relevant what this unit was
local intpool ipgold = intpool.create()
call ipgold.addInt(550000, 0.1)
call ipgold.addInt(250000, 0.6)
call ipgold.addInt(75000, 0.2)
call ipgold.addInt(50000, 2.0)
call ipgold.addInt(150000, 1.0)
call ipgold.addInt(500000000, 0.3)
set x = CreateUnit( y, 'ngol', gx, gy, 270.000 )//exact coordinates were determined by manual placement and documented
call SetResourceAmount(x,ipgold.getRInt())//giving it the number of resources equal to one of the above additions to the pool
And I just planted a series of gold mines like that...there are four different scenarios for gold mine placement, and approximately 55 gold mines for each.
I also made an integer pool for a string array with less than 50 strings in it. And so far, that's it.

What would you envision people would use these pools for, and how taxing is something as simple as what I've done on the systems? And what do you mean by wasting potential storage space? Wouldn't that be why you'd reduce the number in the configuration? Even though I clearly am unsure of what number to reduce it to, are you telling me that since your system is dynamic, there is no need to reduce a configuration number since there is no 'wasted potential storage space'? But what is the benefit to not wasting? Are we referring to eating up less RAM, or what?
 
Last edited:
Level 13
Joined
May 11, 2008
Messages
1,198
well I had no idea, still the system with original values supports only 31 instances of intpool.

This can be used for generating drops from monsters for instance

Hmm...I think I was already generating drops from monsters...it was in my other map though, the one where I lost a lot of data for recently because my computer sucks and I didn't back up the information properly.

Oh yeah so I'm looking at script now and it shows I was using regular item pools. In fact I didn't even make much for items to be dropped. I was getting around to it when I decided that I needed a fewer number of monster types, I had 100 of them at the time. I also decided to revamp the terrain and some other stuff, not sure what else I worked on after this point.

I was using a revival system made by someone else and injected my item pool coding into the library, heh heh.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
OK, IPool and SubPool are ready to meet the world! Before I update the first page, I need to write the documentation. But the functions are there!

I have also attached a demo map since someone was asking for that.

Script:

JASS:
library IPool requires Table, Alloc

private module Init
    private static method onInit takes nothing returns nothing
        set .tar = TableArray[8192]
    endmethod
endmodule
private struct data extends array
    static TableArray tar
    integer int
    integer locks
    integer weight
    implement Alloc
    implement Init
endstruct

    private function Create takes nothing returns data
        local data this = data.allocate()
        set this.locks = 0
        return this
    endfunction
    private function Destroy takes data this returns boolean
        if this.locks == -1 or this == 0 then
            debug call BJDebugMsg("IPool Error: Attempt to double-free instance!")
            return false
        endif
        set this.locks = -1
        call this.deallocate()
        return true
    endfunction

    private function Lock takes data i returns nothing
        if i.locks == -1 then
            debug call BJDebugMsg("IPool Error: Attempt to lock a destroyed instance!")
            return
        endif
        set i.locks = i.locks + 1
    endfunction
    private function Unlock takes data i returns boolean
        if i.locks == -1 then
            debug call BJDebugMsg("IPool Error: Attempt to unlock destroyed instance!")
            return false
        endif
        set i.locks = i.locks - 1
        return i.locks == 0
    endfunction

struct IPool extends array
   
    method operator weight takes nothing returns integer
        return data(this).weight
    endmethod
    private method operator weight= takes integer lbs returns nothing
        set data(this).weight = lbs
    endmethod

    private method operator table takes nothing returns Table
        return data(this).int
    endmethod
    private method operator table= takes Table t returns nothing
        set data(this).int = t
    endmethod

    static method create takes nothing returns thistype
        local thistype this = Create()
        set this.table = Table.create()
        return this
    endmethod
    method flush takes nothing returns nothing
        call this.table.flush()
        call data.tar[this].flush()
        set this.weight = 0
    endmethod

    method destroy takes nothing returns nothing
        if Destroy(this) then
            call this.table.destroy()
            call data.tar[this].flush()
            set this.weight = 0
        endif
    endmethod

    method lock takes nothing returns nothing
        call Lock(this)
    endmethod
    method unlock takes nothing returns nothing
        if Unlock(this) then
            call this.destroy()
        endif
    endmethod

    //One-liner method to get a random item from the pool based on weight
    method getItem takes nothing returns integer
        return this.table[GetRandomInt(0, this.weight -1)]
    endmethod

    method weightOf takes integer value returns integer
        return data.tar[this][value]
    endmethod
    method chanceOf takes integer value returns real //returns between 0. and 1.
        return this.weightOf(value) / (this.weight + 0.) //don't divide by 0 here or else!
    endmethod
    method contains takes integer value returns boolean
        return data.tar[this].has(value)
    endmethod

    method add takes integer value, integer lbs returns nothing
        local Table tb = this.table
        local integer i = this.weight
        if lbs < 1 then
            debug call BJDebugMsg("IPool Error: Tried to add value with invalid weight!")
            return
        endif
        set data.tar[this][value] = data.tar[this][value] + lbs
        set lbs = i + lbs
        set this.weight = lbs //Important
        loop
            exitwhen i == lbs
            set tb[i] = value //treat this.table as an array
            set i = i + 1
        endloop
    endmethod
   
    method remove takes integer value returns nothing
        local Table tb = this.table
        local Table new
        local integer i = this.weight
        local integer n = 0
        local integer val
        if not this.contains(value) then
            debug call BJDebugMsg("IPool Error: Attempt to remove un-added instance!")
            return
        endif
        set new = Table.create()
        set this.table = new
        loop
            set i = i - 1
            set val = tb[i]
            if val != value then
                set new[n] = val //write to the new Table without gaps
                set n = n + 1
            endif
            exitwhen i == 0
        endloop
        set this.weight = n //lower pool weight
        call tb.destroy() //abandon old Table instance
        call data.tar[this].remove(value) //clear the value's weight now that it's gone
    endmethod

    method copy takes nothing returns thistype
        local thistype new = .create()
        local integer i = this.weight
        local Table tt = this.table
        local Table nt = new.table
        local Table dt = data.tar[new]
        local integer val
        if i == 0 then
            debug call BJDebugMsg("IPool Error: Attempt to copy invalid instance!")
            call new.destroy()
            return 0
        endif
        set new.weight = i
        loop
            set i = i - 1
            exitwhen i == 0
            set val = tt[i]
            set nt[i] = val
            set dt[val] = dt[val] + 1
        endloop
        return new
    endmethod
       
    static if DEBUG_MODE then
        method print takes nothing returns nothing //print the array of the pool
            local string s = "IPool: |cffffcc33Weight: "
            local integer i = this.weight
            local Table t = this.table
            set s = s + I2S(i) + "; Indices: "
            loop
                set i = i - 1
                exitwhen i <= 0
                set s = s + "[" + I2S(t[i]) + "]"
            endloop
            call BJDebugMsg(s + "|r")
        endmethod

    endif

endstruct

//New struct to handle deliberately-rare chances
struct SubPool extends array
   
    private IPool iPool //for association if you want it
    private thistype nest //you can nest IPoolMinis for poolception

    //you can change a value's weight via subpoolinstance[value].weight = blah
    //you can also change the entire pool's weight via subpoolinstance.weight = blah.
    method operator weight takes nothing returns integer
        return data(this).weight
    endmethod
    method operator weight= takes integer lbs returns nothing
        set data(this).weight = lbs
    endmethod

    private method operator value takes nothing returns integer
        return data(this).int
    endmethod
    private method operator value= takes integer val returns nothing
        set data(this).int = val
    endmethod

    private thistype next
    private thistype prev

    method operator pool takes nothing returns IPool
        return this.iPool
    endmethod
    method operator pool= takes IPool ip returns nothing
        if ip != 0 then
            call ip.lock()
        endif
        if this.iPool != 0 then
            call this.iPool.unlock()
        endif
        set this.iPool = ip
    endmethod

    static method create takes integer totalWeight returns thistype
        local thistype this = Create()
        set this.next = this
        set this.prev = this //I'm my own best friend
        set this.weight = totalWeight
        return this
    endmethod
    method destroy takes nothing returns nothing
        local thistype curr = this
        if this.next == -1 then
            debug call BJDebugMsg("SubPool Error: Attempt to double-free!")
            return
        endif
        loop
            set curr = curr.next
            call Destroy(curr) //destroy all the things
            exitwhen curr == this
        endloop
        set this.next = -1
        set this.pool = 0
        if this.nest != 0 then
            if data(this.nest).locks == 1 then
                call this.nest.destroy()
            else
                call Unlock(this.nest)
            endif
            set this.nest = 0
        endif
        call data.tar[this].flush()
    endmethod

    method lock takes nothing returns nothing
        call Lock(this)
    endmethod
    method unlock takes nothing returns nothing
        if Unlock(this) then
            call this.destroy()
        endif
    endmethod

    method operator subPool takes nothing returns thistype //need to return thistype and not IPool :P
        return this.nest
    endmethod
    method operator subPool= takes thistype ip returns nothing
        if this == ip then
            debug call BJDebugMsg("SubPool Error: Don't set a subPool within itself. Use the .copy() method, instead.")
            return
        endif
        if ip != 0 then
            call ip.lock()
        endif
        if this.nest != 0 then
            call this.nest.unlock()
        endif
        set this.nest = ip
    endmethod

    method add takes integer val, integer lbs returns nothing
        local thistype new
        if lbs <= 0 then
            debug call BJDebugMsg("SubPool Error: Don't add a value without weight")
            return
        endif
        if data.tar[this].has(val) then
            set new = data.tar[this][val]
            set new.weight = new.weight + lbs
            return
        endif
        set new = Create()
        set new.prev = this.prev
        set this.prev.next = new
        set this.prev = new
        set new.next = this

        set new.value = val
        set new.weight = lbs
       
        set data.tar[this][val] = new
    endmethod

    method contains takes integer val returns boolean
        return data.tar[this].has(val)
    endmethod
    method operator [] takes integer val returns thistype
        return data.tar[this][val]
    endmethod
    method operator []= takes integer val, integer newWeight returns nothing
        set this[val].weight = newWeight
    endmethod

    method remove takes integer val returns nothing
        local thistype node = this[val]
        if Destroy(node) then
            set node.prev.next = node.next
            set node.next.prev = node.prev
            call data.tar[this].remove(val)
        else
            debug call BJDebugMsg("SubPool Error: Attempt to remove non-added value")
        endif
    endmethod
    method getItem takes nothing returns integer
        local thistype curr = this
        local integer i = GetRandomInt(1, this.weight)
        loop
            set curr = curr.next
            set i = i - curr.weight
            exitwhen i <= 0
        endloop
        if curr == this then
            if this.nest != 0 then
                set i = this.nest.getItem()
            else
                set i = 0
            endif
            if i == 0 and this.pool != 0 then //if no low-probability item could be found...
                set i = this.pool.getItem() //pick a random int from main pool
            endif
        else
            set i = curr.value
        endif
        return i
    endmethod

    method copy takes nothing returns thistype
        local thistype new = .create(this.weight)
        local thistype curr = this
        set new.pool = this.iPool
        set new.subPool = this.nest
        loop
            set curr = curr.next
            exitwhen curr == this
            call new.add(curr.value, curr.weight)
        endloop
        return new
    endmethod

    static if DEBUG_MODE then
        method print takes nothing returns nothing
            local thistype curr = this
            local string s = "SubPool: |cffffcc33Instance: " + I2S(this) + ", TotalWeight: " + I2S(this.weight) + ", Indices: "
            if curr.next == this then
                call BJDebugMsg("SubPool is empty!")
                //return
            endif
            loop
                set curr = curr.next
                exitwhen curr == this
                set s = s + "[" + I2S(curr.value) + "]<" + I2S(curr.weight) + ">"
            endloop
            call BJDebugMsg(s + "|r")
            if this.nest != 0 then
                call this.nest.print()
            endif
            if this.iPool != 0 then
                call this.iPool.print()
            endif
        endmethod
    endif
endstruct

endlibrary

JASS:
function TestingIPool takes nothing returns nothing
    local SubPool i = SubPool.create(1000)
    local integer j
    call i.add('Hpal', 1)
    call i.add('hkni', 10)
    call i.add('hpea', 100)
    call i.add('hfoo', 50)
    debug call BJDebugMsg("SubPool GetItem: " + I2S(i.getItem()))
    debug call i.print()
    call i.remove('hpea')
    call i.add('ewsp', 100)
    debug call i.print()
    set i.subPool = SubPool.create(100)
    call i.subPool.add('Amrf', 10)
    call i.subPool.add('Aloc', 10)
    debug call BJDebugMsg("Nested SubPool GetItem: " + I2S(i.subPool.getItem()))
    set i.pool = IPool.create()
    call i.pool.add(9001, 3)
    call i.pool.add(4001, 7)
    call i.pool.add('poop', 10)
    debug call BJDebugMsg("Nested IPool GetItem: " + I2S(i.pool.getItem()))
    debug call i.print()
endfunction

function InitTrig_Pool takes nothing returns nothing
    call TimerStart(CreateTimer(), 0, false, function TestingIPool)
endfunction
 

Attachments

  • IPool Test.w3x
    29.9 KB · Views: 128

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
Perhaps this is a bug:
In my Coconut Party map, I tried to use this in the classic mode to randomize which cannon unit to fire the coconut. Every cannon unit is indexed from 0-3 (there are 4 cannon units). And to make everything fair, I want every unit has the same chance to fire the coconut. So I did this (sorta):
JASS:
// on init
set IndexPool = IPool.create()
call IndexPool.add(0, 1)
call IndexPool.add(1, 1)
call IndexPool.add(2, 1)
call IndexPool.add(3, 1)
set pickCount = 0

// on fire coconut event
local integer i = IndexPool.getItem()

set pickCount = pickCount + 1
call FireCannon(i) // order cannon unit with index i to fire a coconut
// then I call this so that the same unit can't fire the coconut twice
call IndexPool.remove(i)
// then after the pool is empty, I re-add all the indexes
if pickCount == 4 then
    call IndexPool.flush() // I also tried to remove this line but no luck
    call IndexPool.add(0, 1)
    call IndexPool.add(1, 1)
    call IndexPool.add(2, 1)
    call IndexPool.add(3, 1)
    set pickCount = 0
endif

I hope you understand what I tried to achieve. Perhaps that's not exactly how I did it but the strange thing was, it didn't work. It worked fine for the first wave. But after the fourth cannon unit fired, the index pool seem to always return the same index. I didn't know what's wrong. I tried a lot of approaches to re-add the indexes but still unable to solve it until now. I think there is a problem when I re-add the indexes.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
This approach will be easier to manage, since you only have to set the pool contents once during Init:

JASS:
// on init
set MasterPool = IPool.create()
call MasterPool.add(0, 1)
call MasterPool.add(1, 1)
call MasterPool.add(2, 1)
call MasterPool.add(3, 1)
set IndexPool = MasterPool.copy()
set pickCount = 0

// on fire coconut event
local integer i = IndexPool.getItem()

set pickCount = pickCount + 1
call FireCannon(i)
// then after the pool is empty, I re-add all the indexes
if pickCount == 4 then
    call IndexPool.destroy()
    set IndexPool = MasterPool.copy()
    set pickCount = 0
else
    call IndexPool.remove(i)
endif

An old version had an issue with adding and/or removing the value of 0 to an IPool. I couldn't see if this one would have the same problem, but I don't think so. Is your version of IPool up to date from the top post?
 

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
This approach will be easier to manage, since you only have to set the pool contents once during Init:

JASS:
// on init
set MasterPool = IPool.create()
call MasterPool.add(0, 1)
call MasterPool.add(1, 1)
call MasterPool.add(2, 1)
call MasterPool.add(3, 1)
set IndexPool = MasterPool.copy()
set pickCount = 0

// on fire coconut event
local integer i = IndexPool.getItem()

set pickCount = pickCount + 1
call FireCannon(i)
// then after the pool is empty, I re-add all the indexes
if pickCount == 4 then
    call IndexPool.destroy()
    set IndexPool = MasterPool.copy()
    set pickCount = 0
else
    call IndexPool.remove(i)
endif

An old version had an issue with adding and/or removing the value of 0 to an IPool. I couldn't see if this one would have the same problem, but I don't think so. Is your version of IPool up to date from the top post?

Perhaps not. It was two months ago or so :D I will update it and report if the error still exist ;)
 
Top