• 🏆 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!
  • 🏆 Hive's 6th HD Modeling Contest: Mechanical is now open! Design and model a mechanical creature, mechanized animal, a futuristic robotic being, or anything else your imagination can tinker with! 📅 Submissions close on June 30, 2024. Don't miss this opportunity to let your creativity shine! Enter now and show us your mechanical masterpiece! 🔗 Click here to enter!

[Solved] MUI Structs

Status
Not open for further replies.
Hi.

I want to make a timed effect struct that allows you to create a special effect at a target location and having it removed a few seconds later. This is what I have so far:
JASS:
struct TimedEffect extends array
    effect sfx
    timer tmr
   
    static method RemoveEffect takes nothing returns nothing
        //call DestroyEffect(sfx)
    endmethod
   
    method AddEffect takes string path, real x, real y, real tm returns effect
        set sfx = AddSpecialEffect(path, x, y)
        call TimerStart(tmr, tm, false, function thistype.RemoveEffect)
        return sfx
    endmethod
endstruct

The problem is that timers can only call static methods that takes and returns nothing. How can I tell method RemoveEffect which instance to remove?

Thanks for your time.
 
Thanks, but now I have encountered another problem. here is the code:
JASS:
struct TimedEffect extends array
    effect sfx
    timer tmr
 
    static method RemoveEffect takes nothing returns nothing
        local thistype this = LoadInteger(htble, 0, GetHandleId(GetExpiredTimer()))
        call DestroyEffect(.sfx)
    endmethod
 
    method AddEffect takes string path, real x, real y, real tm returns effect
        set sfx = AddSpecialEffect(path, x, y)
        call SaveInteger(htble, 0, GetHandleId(tmr), this)
        call TimerStart(tmr, tm, false, function thistype.RemoveEffect)
        return sfx
    endmethod
endstruct

Example:
call AddEffect("units\\human\\Peasant\\Peasant",0,0,3)

I get the error saying that AddEffect() is undeclared. If I use "TimedEffect.AddEffect() then the compiler complains that AddEffect() is not a static method. Making AddEffect() static makes it unable to use the non-static timer. If my timer is static then saving the timer's handle in a hash is pointless. How can I create a new TimedEffect instance?

I also tried this:
local TimedEffect sfx
call sfx.AddEffect(...)

that compiles but prevents all my triggers from running
 
Last edited:
It is a non-static method which means it it a reserved function for your struct instances. When ever you have a created "TimedEffect" you can call it with it.
For exampe:

JASS:
local TimedEffect this = allocate()
call this.AddEffect("units\\human\\Peasant\\Peasant",0,0,3)

Other way it would not make sense and otherwise you could not use "this" inside your method. "this" refers to the instance which calls the method.

Also ensure everything is initialisized like the timer and hashtable.
 
Like this?
JASS:
    local TimedEffect this = allocate()
    call this.AddEffect("units\\human\\Peasant\\Peasant.mdl",-520,0,3)

I get an error saying undeclared function allocate().


note I added TimerUtils as Chaosy suggested. Here is the updated code:

JASS:
struct TimedEffect extends array
    effect sfx
    timer tmr
  
    static method RemoveEffect takes nothing returns nothing
        local thistype this = GetTimerData(GetExpiredTimer())
        call DestroyEffect(.sfx)
        call ReleaseTimer(GetExpiredTimer())
    endmethod
  
    method AddEffect takes string path, real x, real y, real tm returns effect
        set sfx = AddSpecialEffect(path, x, y)
        set tmr = NewTimer()
        call SetTimerData(tmr, this)
        call TimerStart(tmr, tm, false, function thistype.RemoveEffect)
        return sfx
    endmethod
endstruct
 
The static method create() is the constructor of a struct. If called, it returns a new struct instance.
So it must return something that is from type thistype / aka the struct.

So if you call create() you will get a new instance.

But you also have the chance to write your own create method and give it some parameters for example.
Inside the create method, to get a new instance for you struct, you can use the allocate() function which returns a integer that will be used for your thistype.

It may look like:

JASS:
static method create takes nothing returns thistype
    local thistype this = allocate()
    return this
endmethod

You can add some parameters:

JASS:
static method create takes string path, real x, real y returns thistype
    local thistype this = allocate()
    set this.sfx = set sfx = AddSpecialEffect(path, x, y)
    return this
endmethod

Now you want to get your instance inside the callback so you attach it also to a timer:
JASS:
static method callback takes nothing returns nothing
    local thistype this = GetTimerData(GetExpiredTimer())
    call DestroyEffect(this.sfx)
    call ReleaseTimer(GetExpiredTimer())
    call this.destroy()
endmethod

static method create takes string path, real x, real y returns thistype
    local thistype this = allocate()
    set this.sfx = set sfx = AddSpecialEffect(path, x, y)
    call TimerStart(NewTimerEx(this), 5, false, function thistype.callback)
    return this
endmethod

^The destroy method is the opposite of create. When you don't need your instance anymore you call the destroy method with it.
Inside it will call the deallocate() functon to free your instance again.
Similar to the create method you also can write your own destroy method. This comes handy if you want to clear some stuff there, like this:


JASS:
method destroy takes nothing returns nothing
    call this.deallocate()
    set this.sfx = null
endmethod

static method callback takes nothing returns nothing
    local thistype this = GetTimerData(GetExpiredTimer())
    call DestroyEffect(this.sfx)
    call ReleaseTimer(GetExpiredTimer())
    call this.destroy()
endmethod

static method create takes string path, real x, real y returns thistype
    local thistype this = allocate()
    set this.sfx = set sfx = AddSpecialEffect(path, x, y)
    call TimerStart(NewTimerEx(this), 5, false, function thistype.callback)
    return this
endmethod

Recgonize that method destroy() is a non-static method which means you only can call it with your struct instances.
And this makes perfect sense, since this method is only for destroying your struct instance and nothing else.
 
Thanks for your effort IcemanBo. I would have ended up with a lot of leaks if you just left me there. I can't find the NewTimerEx() function. I guess it only assigns a value immediately to the timer upon creation. Anyway I edited my struct as well as applying the changes as Flux suggested. Here is the code :)

JASS:
struct TimedEffect
  
    effect sfx
  
    method destroy takes nothing returns nothing
        call DestroyEffect(.sfx)
        call ReleaseTimer(GetExpiredTimer())
        call this.deallocate()
        set this.sfx = null
    endmethod
  
    private static method callback takes nothing returns nothing
        local thistype this = GetTimerData(GetExpiredTimer())
        call this.destroy()
    endmethod
  
    static method create takes string path, real x, real y, real tm returns thistype
        local thistype this = thistype.allocate()
        local timer tmr = NewTimer()
        set sfx = AddSpecialEffect(path, x, y)
        call SetTimerData(tmr, this)
        call TimerStart(tmr, tm, false, function thistype.callback)
        return this
    endmethod
endstruct
 

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,207
One could drive it with a single static timer if one used an accompanying time based priority queue. You insert the effect instances into the priority queue at the times they are scheduled for. You get the timer to wait until the soonest event from the priority queue. On timer expiry the top of the queue is poped, destroyed and the timer waits until the new top. This has very good scaling as priority queues can be as fast as log2(n) for insertion/removal and is likely how JASS timers are implemented internally.

Needlessly complex I know but thought I should post the idea.
 
Level 24
Joined
Aug 1, 2013
Messages
4,657
JASS:
       local timer tmr = NewTimer()
       call SetTimerData(tmr, this)
=
JASS:
       local timer tmr = NewTimerEx(this)

@Dr Super Good
I tried to make that, but it only works nice if you use triggers to execute instead of code.
EDIT:
JASS:
//! runtextmacro NewLinkedList("timerSetList")
struct TimerSet
  
    private timer t
  
    private static integer array l_data
    private static real array l_timestamp
    private static trigger array l_trigger
  
    private static method onExpire takes nothing returns nothing
        local thistype this = GetTimerData(GetExpiredTimer())
        local integer id = timerSetList.first[this]
      
        call TriggerEvaluate(.l_trigger[id])
        call timerSetList.removeIndex(id)
        set .l_data[id] = 0
        set .l_timestamp[id] = 0
        set .l_trigger[id] = null
      
        if timerSetList.first[this] > 0 then
            call TimerStart(.t, .l_timestamp[timerSetList.first[this]] - Time.getElapsedGameTime(), false, function thistype.onExpire)
        endif
    endmethod
    
    public method removeTimer takes integer id returns boolean
        local boolean isFirst = timerSetList.first[this] == id
       
        if timerSetList.removeIndexChecked(this, id) then
            set .l_data[id] = 0
            set .l_timestamp[id] = 0
            set .l_trigger[id] = null
           
            if isFirst then
                if timerSetList.first[this] > 0 then
                    call TimerStart(.t, .l_timestamp[timerSetList.first[this]] - Time.getElapsedGameTime(), false, function thistype.onExpire)
                else
                    call PauseTimer(.t)
                endif
            endif
            return true
        endif
        return false
    endmethod
    
    public method newTimer takes integer data, real duration, trigger trig returns integer
        local integer id
        local real timestamp = Time.getElapsedGameTime() + duration
        local integer lId = 0
        local integer nId = timerSetList.last[this]
        local boolean isFirst = false
      
        loop
            if nId == 0 then
                set id = timerSetList.insertIndex(this, lId)
                set isFirst = true
                exitwhen true
            endif
            set lId = nId
            set nId = timerSetList.previous[nId]
          
            if .l_timestamp[lId] <= timestamp then
                set id = timerSetList.insertIndexEx(this, lId)
                exitwhen true
            endif
        endloop
      
        set .l_data[id] = data
        set .l_timestamp[id] = timestamp
        set .l_trigger[id] = trig
      
        if isFirst then
            call TimerStart(.t, duration, false, function thistype.onExpire)
        endif
      
        return id
    endmethod
  
    public static method create takes nothing returns thistype
        local thistype this = .allocate()
        set .t = NewTimerEx(this)
        return this
    endmethod
  
endstruct
 
Last edited:
Status
Not open for further replies.
Top