HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

Passing Things Using Structs

03-01-2007, 02:59 AM#1
emjlr3
I seem to be, not having a hard time with, but a hard time finding away around using game cache while using structs, for ex.

I saw in the Caster System, when Vex recycled casters after a certain time, thus when I rewrote my CS, using structs, I did it in the same way:

Collapse JASS:
function EndCasterTimed_Child takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local end_caster dat = GetData(t)
    
    call EndCaster(dat.u,dat.abil)
    call dat.destroy()
    call EndTimer(t)

    set t = null
endfunction
function EndCasterTimed takes unit u, integer abil, real time returns nothing
    local timer t = GetTimer()
    local end_caster dat = end_caster.create()
    
    set dat.u = u
    set dat.abil = abil
    call SetData(t,dat) 
    call TimerStart(t,time,false,function EndCasterTimed_Child)
    
    set t = null    
endfunction

GetData/SetData == SetCSData/GetCSData
now to me this seems like it will leak the stored integer each time it is done, since the data is never cleared, and again, it uses Game Cache

another example, when creating a local trigger and using it:

Collapse JASS:
struct movement_struct
    trigger trig
    triggeraction ta 
    unit u   
endstruct 

function Movement_Ability_Knockback takes nothing returns nothing
    local movement_struct dat = GetTableInt(I2S(CS_H2I(GetTriggeringTrigger())),"data")    
    
    // Do Stuff
endfunction
function Movement_Ability_Start takes unit u returns nothing
    local movement_struct dat = movement_struct.create() 
    
    set dat.trig = CreateTrigger()
    set dat.ta = TriggerAddAction(dat.trig,function Movement_Ability_Knockback)
    set dat.u = u
    call TriggerRegisterUnitInRange(dat.trig,u,Movement_Area,null)
    call SetTableInt(I2S(CS_H2I(dat.trig)),"data",dat)   
endfunction

the only way I could see to get the struct back in the trigger was to store the integer onto the trigger, which again, needs game cache

am I missing something here and is there a better way to go about doing such things?
03-01-2007, 03:35 AM#2
wantok
For the first question you could do something like this:

Collapse JASS:
globals
    endcaster array endcaster_ar
    timer endcaster_timer = CreateTimer()
    integer endcaster_count = 0
endglobals
    

struct endcaster
    unit caster
    integer time = 100 //however many timer expirations you want it to last
    
    method time takes nothing returns nothing
        set this.time = this.time - 1
    endmethod
endstruct

function endcaster_timer_callback takes nothing returns nothing
    local endcaster dat
    local integer i
    
    loop
        exitwhen i = endcaster_count
        set dat = endcaster_ar[i]
        call dat.time()
        if dat.time == 0 then
            //remove caster actions here
            set endcaster_ar[i] = endcaster_count - 1
            set endcaster_count = endcaster_count - 1
            call dat.destroy()
        else
            set i = i + 1
        endif
    endloop
    if endcaster_count == 0 then
        call PauseTimer(endcaster_timer)
    endif
endfunction

function AddCasterToStruct takes unit u returns nothing
    local endcaster dat = endcaster.create()
    if endcaster_count == 0 then
        call TimerStart(endcaster_timer, 1, TRUE, function endcaster_timer_callback)
    endif
    set dat.caster = u
    set endcaster_ar[endcaster_count]
    set endcaster_count = endcaster_count + 1
endfunction

For the second question, why are you creating the struct in that function, why not create it in the movement ability knockback function instead? You can then just add it to an array or alternatively you can attach it to the unit using its custom value.
03-01-2007, 04:21 AM#3
emjlr3
i know of your method for the first one, however it was a more of a is there any other way question, since i don't want to have to do that for every spell that I make with timers

for the second, what is wrong with where I created the struct? how else would I store the unit and trigger action, so I could get the unit in the function and the trigger action at a later date so I could remove the leak?

and again, I do now want to have to go through that whole array method for every spell that I make

*Keep in mind that is not the whole trigger, nor is neither function complete with what it has, I just left the parts significant to the question
03-01-2007, 04:42 AM#4
grim001
CSData does not use gamecache unless the H2I of the handle exceeds the limit of 16000ish, and if you wanted to avoid it you could just add in more variables for it to store things in.

and for your second example why not just do SetCSData on the trigger and use GetCSData on the triggering trigger?

I just had an idea for a CSData type system that doesn't even need to use H2I to associate any type of thing with a handle... I should make it...
03-01-2007, 04:56 AM#5
wantok
Well if you don't want to do the array thing, I guess your stuck using gamecache, or in certain instances using a unit's custom value.

You shouldn't need to make any more arrays to recycle casters though.
03-01-2007, 04:59 AM#6
grim001
why would he be stuck using gamecache or custom value?
03-01-2007, 05:06 AM#7
wantok
I should say that those are the only easy ways that I personally know of to attach a struct to something. Although if CSData also does not use Gamecache unless necessary as you say, then that is another way. I'm not familiar with Vex's Caster System.

Maybe there are other ways? I'd certainly like to know them if there are.
03-01-2007, 05:16 AM#8
emjlr3
Collapse JASS:
function SetData takes handle h, integer v returns nothing
    local integer i=CS_H2I(h)-0x100000

    if (i>=8191) then
        call StoreInteger(cscache,"csdata",I2S(i),v)
    else
        set cs_array[i]=v
    endif
endfunction
function GetData takes handle h returns integer
    local integer i=CS_H2I(h)-0x100000

    if (i>=8191) then
        return GetStoredInteger(cscache,"csdata",I2S(i))
    endif
    return cs_array[i]
endfunction

your right, I don't know what I was thinking, it only uses GC if you have used SetData on 8192 things if I am getting this correctly

regardless, and I may be wrong, but it seems that storing the things with GC or to an array would still take up the same amount of memory, no?

and i think it would make sense to be able to remove the data once you have no need of it anymore, though I may be off my hinges here

with that being said, I do see what you mean about me using CSData instead of tables how I did, would make more sense, heh

in anycase, it seems that the only method, other then that whole array dealy with the 1 timer, to move structs around is to use CSData, am I right?

and I imagine using custom values of units would make it non MUI
03-01-2007, 05:31 AM#9
grim001
well, an array already consumes memory when it's created, so filling it with numbers should not make any difference in memory usage (unless I am mistaken). either way this is an infinitesimal amount of space to be "leaked" by an integer. if you really wanted to you could do SetCSData(h, 0) to set the thing back to zero, but I don't think that would make a difference.

CSData is forced to use gamecache if the H2I of the object you are associating the integer with is greater than 8190. if you have handles of 8191 or higher in your map (many maps do) you can add an extra storage variable in there.

overall i think that the method described in vexorian's tutorial using the Optical Flare as an example is the best method for your typical spell. global array of structs and global integer to know how many times you need to loop, one timer to process every instance of the spell. there is actually no need for associating the struct with any specific handle as long as you have a list of the structs to iterate through.
03-03-2007, 12:45 AM#10
wantok
I wrote a textmacro to make it a lot faster to use the array method.
Collapse JASS:
//! textmacro StructStuff takes NAME, STRUCTNAME, STRUCTARRAY, GLOBAL, TIMER, FUNCTION
//! scope $NAME$
    public function Recycle takes $STRUCTNAME$ dat, integer i returns nothing
        set $STRUCTARRAY$[i] = $STRUCTARRAY$[$GLOBAL$ - 1]
        set $GLOBAL$ = $GLOBAL$-1
        call dat.destroy()
    endfunction

    public function Add2StructAr takes $STRUCTNAME$ dat, real period returns nothing
        
        if $GLOBAL$ == 0 then    
            call TimerStart($TIMER$, period, TRUE, function $FUNCTION$)
        endif
        set $STRUCTARRAY$[$GLOBAL$] = dat
        set $GLOBAL$ = $GLOBAL$ + 1
        
    endfunction
//! endscope
//! endtextmacro

//! runtextmacro StructStuff("EC","endcaster","endcaster_ar","endcaster_count","endcaster_timer","endcaster_timer_callback")
Now you don't have to change the array values and all that stuff for each spell. You can use it for any other structs you will be looping through on a global timer. For each struct you will only have to do this:
Collapse JASS:
//! runtextmacro StructStuff("newname","newstruct","newstructarray","newglobal","newtimer","new timer callback functin")

Here it is for the endcaster struct:
Collapse JASS:

globals
    endcaster array endcaster_ar
    timer endcaster_timer = CreateTimer()
    integer endcaster_count = 0
endglobals
    

struct endcaster
    unit caster
    integer time = 100 //however many timer expirations you want it to last
    
    method sub takes nothing returns nothing
        set this.time = this.time - 1
    endmethod
endstruct



function endcaster_timer_callback takes nothing returns nothing
    local endcaster dat
    local integer i
    
    loop
        exitwhen i == endcaster_count
        set dat = endcaster_ar[i]
        call dat.sub()
        if dat.time == 0 then
            //remove caster actions here
           call EC_Recycle(dat, i)
            
        else
            set i = i + 1
        endif
    endloop
    if endcaster_count == 0 then
        call PauseTimer(endcaster_timer)
    endif
endfunction


function createcaster takes unit u returns nothing
    local endcaster dat = endcaster.create()
    set dat.caster = u
    call EC_Add2StructAr(dat, 1)
endfunction

//! textmacro StructStuff takes NAME, STRUCTNAME, STRUCTARRAY, GLOBAL, TIMER, FUNCTION
//! scope $NAME$
    public function Recycle takes $STRUCTNAME$ dat, integer i returns nothing
        set $STRUCTARRAY$[i] = $STRUCTARRAY$[$GLOBAL$ - 1]
        set $GLOBAL$ = $GLOBAL$-1
        call dat.destroy()
    endfunction

    public function Add2StructAr takes $STRUCTNAME$ dat, real period returns nothing
        
        if $GLOBAL$ == 0 then    
            call TimerStart($TIMER$, period, TRUE, function $FUNCTION$)
        endif
        set $STRUCTARRAY$[$GLOBAL$] = dat
        set $GLOBAL$ = $GLOBAL$ + 1
        
    endfunction
//! endscope
//! endtextmacro

//! runtextmacro StructStuff("EC","endcaster","endcaster_ar","endcaster_count","endcaster_timer","endcaster_timer_callback")

When I say this is faster, I don't mean it will run faster. I just mean its a lot less coding to do.

Edit: Actually this won't work the way it is right now. The textmacro needs to be above the functions, since they call its functions but it also needs to be below the callback function since one of the textmacro functions calls it. The solution would be to split it into two textmacros, one for each function, or to not start the timer in the textmacro.
03-03-2007, 04:15 AM#11
grim001
textmacros are indeed useful
03-03-2007, 01:53 PM#12
wantok
You can throw the globals in the textmacro too so you don't have to set them up for each struct.

Edit: Greatly simplified the textmacro.

Collapse JASS:
//! textmacro StructStuff takes STRUCTNAME

    globals
       $STRUCTNAME$ array $STRUCTNAME$_ar
       timer $STRUCTNAME$_timer = CreateTimer()
       integer $STRUCTNAME$_count = 0
    endglobals
    
    function Recycle_$STRUCTNAME$ takes $STRUCTNAME$ dat, integer i returns nothing
        set $STRUCTNAME$_ar[i] = $STRUCTNAME$_ar[$STRUCTNAME$_count - 1]
        set $STRUCTNAME$_count = $STRUCTNAME$_count-1
        call dat.destroy()
    endfunction

    
    function StopTimer_$STRUCTNAME$ takes nothing returns nothing
        if $STRUCTNAME$_count == 0 then
            call PauseTimer($STRUCTNAME$_timer)
        endif
    endfunction

//! endtextmacro

//! runtextmacro StructStuff("endcaster")

//! textmacro StartTimer takes STRUCTNAME

    function Add2Ar_$STRUCTNAME$ takes $STRUCTNAME$ dat, real period returns nothing
        
        if $STRUCTNAME$_count == 0 then    
            call TimerStart($STRUCTNAME$_timer, period, TRUE, function $STRUCTNAME$_timer_callback)
        endif
        set $STRUCTNAME$_ar[$STRUCTNAME$_count] = dat
        set $STRUCTNAME$_count = $STRUCTNAME$_count + 1
        
    endfunction

    
//! endtextmacro

//! runtextmacro StartTimer("endcaster")
07-31-2007, 04:21 PM#13
cohadar
Hope it is not too late

http://www.wc3campaigns.net/showthread.php?t=95823
07-31-2007, 04:57 PM#14
emjlr3
I just use CSCache when its needed
07-31-2007, 06:28 PM#15
cohadar
me too = never