HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

Stop a struct's method onDestroy?

02-06-2009, 03:02 PM#1
Opossum
I've been working on some sort of physics system/map but I'm currently stuck with this problem:
I already made it possible to apply a force on the object i want to accelerate but usually forces aren't permanent so i added this method "applyforcetimed":
Collapse JASS:
        method applyforcetimed takes real amount, real horAngle, real verAngle, real duration returns nothing            
            call this.applyforce(amount, horAngle, verAngle)
            call TriggerSleepAction(duration)
            call this.applyforce(-amount, horAngle, verAngle)
        endmethod
It just "removes" the force again after a given time by reversing the original force.
As you might have already noticed the problem with this is that if the struct instance is destroyed and a new one is created, that new one will take over this old instance.
If that happens during the TriggerSleepAction part the newly created struct will be accelerated by a force that wasn't supposed to be applied to that instance.

So somehow I have to stop that method to work when the struct is destroyed. Adding a timer as a struct member that will be paused onDestroy might be possible but that would mean that i can only apply one force at a time to the object.

Here's the rest of the struct if needed:
Collapse JASS:
    struct object
        unit ent
        real vx = 0
        real vy = 0
        real vz = 0
        real ax = 0
        real ay = 0
        real az = 0
        real m
        timer mov = CreateTimer()
        
        private method onDestroy takes nothing returns nothing
            call RemoveUnit(this.ent)
            
            call FlushHandleLocals(this.mov)
            call PauseTimer(this.mov)
            call DestroyTimer(this.mov)
        endmethod
        
        private method applyforcevectorial takes real fx, real fy, real fz returns nothing
            set this.ax = this.ax+fx/this.m
            set this.ay = this.ay+fy/this.m
            set this.az = this.az+fz/this.m
        endmethod
    
        method applyforce takes real amount, real horAngle, real verAngle returns nothing
            local real fz = Sin(bj_DEGTORAD*verAngle)*amount
            local real fhor = Cos(bj_DEGTORAD*verAngle)*amount
            local real fx = Cos(bj_DEGTORAD*horAngle)*fhor
            local real fy = Sin(bj_DEGTORAD*horAngle)*fhor
            
            call this.applyforcevectorial(fx, fy, fz)
        endmethod
        
        method applyforcetimed takes real amount, real horAngle, real verAngle, real duration returns nothing            
            call this.applyforce(amount, horAngle, verAngle)
            call TriggerSleepAction(duration)
            call this.applyforce(-amount, horAngle, verAngle)
        endmethod
        
        private static method move takes nothing returns nothing
            local object movo = GetHandleInt(GetExpiredTimer(), "object")
            
            set movo.vx = movo.vx+movo.ax
            set movo.vy = movo.vy+movo.ay
            set movo.vz = movo.vz+movo.az
            
            call SetUnitX(movo.ent, GetUnitX(movo.ent)+movo.vx)
            call SetUnitY(movo.ent, GetUnitY(movo.ent)+movo.vy)
            call SetUnitZ(movo.ent, GetUnitZ(movo.ent)+movo.vz)
        endmethod
        
        static method create takes player whichPlayer, integer unitId, real m, real x, real y, real z, real face returns object
            local object creo = object.allocate()
            
            set creo.m = m
            
            set creo.ent = CreateUnit(whichPlayer, unitId, 0, 0, face)
            call UnitAddAbility(creo.ent, 'Arav')
            call UnitRemoveAbility(creo.ent, 'Arav')
            call SetUnitPathing(creo.ent, false)
            call SetUnitX(creo.ent, x)
            call SetUnitY(creo.ent, y)
            call SetUnitZ(creo.ent, z)
            call SetHandleInt(creo.ent, "object", creo)
            
            call SetHandleInt(creo.mov, "object", creo)
            call TimerStart(creo.mov, 0.01, true, function object.move)
            
            return creo
        endmethod
    
    endstruct
P.S: I know I probably shouldn't destroy timers. I guess I'll switch to one of those recycling systems later.
P.P.S: Any additional criticism may be welcome.
02-06-2009, 04:30 PM#2
C2H3NaO2
You can make a list of forces, and to everyone you save how long it should be there. On every tick(move) you always decrease the time and test if it is <= 0. If it is <= 0 you remove it.

A smarter version version is to use the time of a timer that runs all the time. So you calculate everything with that time, that you have not to decrease anything.

@code: You should make it use only a single timer. So you have a array of all objects. Every 0.035 (or whatever) seconds you iterate over all of them an call their .move (in that case it is not necessary to make move static).

You will have to inform you about how to manage all the data. But it is pretty easy if you know how. Just look over some resources.
02-06-2009, 06:47 PM#3
Opossum
Alright... thanks very much for your help

My current code looks like this now and it's working:
Collapse JASS:
library Object initializer Init
    globals
        private timer Timer = CreateTimer()
        private integer ObjectCount = 0
        private object array Object
    endglobals

    struct phforce
        real fx
        real fy
        real fz
        real tleft
        
        static method create takes real fx, real fy, real fz, real duration returns phforce
            local phforce f = phforce.allocate()
            
            set f.fx = fx
            set f.fy = fy
            set f.fz = fz
            set f.tleft = duration
            
            return f
        endmethod
    endstruct

    struct object
        unit ent
        real vx = 0
        real vy = 0
        real vz = 0
        //real ax = 0
        //real ay = 0
        //real az = 0
        real m
        real r
        phforce array f[31]
        integer ForceCount = 0
        //timer mov = CreateTimer()
        
        method onDestroy takes nothing returns nothing
            local integer i = 0
            
            call RemoveUnit(this.ent)
            
            loop
                exitwhen Object[i] == this
                set i = i+1
            endloop            
            loop
                exitwhen i == ObjectCount
                set Object[i] = Object[i+1]
                set i = i+1
            endloop
            set ObjectCount = ObjectCount-1
                
            //call FlushHandleLocals(this.mov)
            //call PauseTimer(this.mov)
            //call DestroyTimer(this.mov)
        endmethod
        
        //method applyforcevectorial takes real fx, real fy, real fz returns nothing
        //    set this.ax = this.ax+fx/this.m
        //    set this.ay = this.ay+fy/this.m
        //    set this.az = this.az+fz/this.m
        //endmethod
    
        //method applyforce takes real magnitude, real horAngle, real verAngle returns nothing
        //    local real fz = Sin(bj_DEGTORAD*verAngle)*magnitude
        //    local real fhor = Cos(bj_DEGTORAD*verAngle)*magnitude
        //    local real fx = Cos(bj_DEGTORAD*horAngle)*fhor
        //    local real fy = Sin(bj_DEGTORAD*horAngle)*fhor
        //    
        //    call this.applyforcevectorial(fx, fy, fz)
        //endmethod
        
        method applyforcetimed takes real magnitude, real horAngle, real verAngle, real duration returns nothing
            local real fz = Sin(bj_DEGTORAD*verAngle)*magnitude
            local real fhor = Cos(bj_DEGTORAD*verAngle)*magnitude
            local real fx = Cos(bj_DEGTORAD*horAngle)*fhor
            local real fy = Sin(bj_DEGTORAD*horAngle)*fhor
            
            set this.f[this.ForceCount] = phforce.create(fx, fy, fz, duration)
            set this.ForceCount = this.ForceCount+1
            
            //call this.applyforce(magnitude, horAngle, verAngle)
            //call TriggerSleepAction(duration)
            //call this.applyforce(-magnitude, horAngle, verAngle)
        endmethod
        
        //static method move takes nothing returns nothing
        //    local object movo = GetHandleInt(GetExpiredTimer(), "object")
        //    
        //    set movo.vx = movo.vx+movo.ax
        //    set movo.vy = movo.vy+movo.ay
        //    set movo.vz = movo.vz+movo.az
        //    
        //    call SetUnitX(movo.ent, GetUnitX(movo.ent)+movo.vx)
        //    call SetUnitY(movo.ent, GetUnitY(movo.ent)+movo.vy)
        //    call SetUnitZ(movo.ent, GetUnitZ(movo.ent)+movo.vz)
        //endmethod
        
        //method move takes nothing returns nothing
        //    set this.vx = this.vx+this.ax
        //    set this.vy = this.vy+this.ay
        //    set this.vz = this.vz+this.az
        //    
        //    call SetUnitX(this.ent, GetUnitX(this.ent)+this.vx)
        //    call SetUnitY(this.ent, GetUnitY(this.ent)+this.vy)
        //    call SetUnitZ(this.ent, GetUnitZ(this.ent)+this.vz)
        //endmethod
        
        static method create takes player whichPlayer, integer unitId, real m, real r, real x, real y, real z, real face returns object
            local object creo = object.allocate()
            
            set creo.m = m
            set creo.r = r
            
            set Object[ObjectCount] = creo
            set ObjectCount = ObjectCount+1
            
            set creo.ent = CreateUnit(whichPlayer, unitId, 0, 0, face)
            call UnitAddAbility(creo.ent, 'Arav')
            call UnitRemoveAbility(creo.ent, 'Arav')
            call SetUnitPathing(creo.ent, false)
            call SetUnitX(creo.ent, x)
            call SetUnitY(creo.ent, y)
            call SetUnitZ(creo.ent, z)
            call SetHandleInt(creo.ent, "object", creo)
            
            //call SetHandleInt(creo.mov, "object", creo)
            //call TimerStart(creo.mov, 0.01, true, function object.move)
            
            return creo
        endmethod
    
    endstruct
    
    private function LoopThroughObjects takes nothing returns nothing
        local integer i = 0
        local integer j
        
        //Looping through objects
        call BJDebugMsg("--total object count: "+I2S(ObjectCount))
        loop
            exitwhen i >= ObjectCount
            set j = 0
            //Looping through Forces
            call BJDebugMsg("----working on object "+I2S(i))
            loop
                exitwhen j >= Object[i].ForceCount
                call BJDebugMsg("------total force count: "+I2S(Object[i].ForceCount))
                if Object[i].f[j].tleft > 0 then
                    //Accelerating
                    call BJDebugMsg("--------accelerating object "+I2S(i)+" using force "+I2S(j))
                    set Object[i].vx = Object[i].f[j].fx / Object[i].m + Object[i].vx
                    set Object[i].vy = Object[i].f[j].fy / Object[i].m + Object[i].vy
                    set Object[i].vz = Object[i].f[j].fz / Object[i].m + Object[i].vz
                    set Object[i].f[j].tleft = Object[i].f[j].tleft - 0.01
                else
                    //Removing Force from the list
                    call BJDebugMsg("--------destroying force "+I2S(j)+" of object "+I2S(i))
                    call Object[i].f[j].destroy()
                    loop
                        exitwhen j >= Object[i].ForceCount
                        set Object[i].f[j] = Object[i].f[j+1]
                        set j = j+1
                    endloop
                    set Object[i].ForceCount = Object[i].ForceCount-1
                endif
                set j = i+1
            endloop
            
            //Moving           
            call SetUnitX(Object[i].ent, GetUnitX(Object[i].ent)+Object[i].vx)
            call SetUnitY(Object[i].ent, GetUnitY(Object[i].ent)+Object[i].vy)
            call SetUnitZ(Object[i].ent, GetUnitZ(Object[i].ent)+Object[i].vz)
            
            set i = i+1
        endloop
    endfunction
    
    private function Init takes nothing returns nothing
        call TimerStart(Timer, 0.01, true, function LoopThroughObjects)
    endfunction
    
endlibrary

It's just really laggy at already 3-5 objects featuring one force each... Ofc I could reduce the timer frequency but when I think about what I was actually going to add to this system (electric fields + charged objects, elastic collision) I guess it's a better idea to scrap that whole idea.
Nature is a hell of an algorithm... wow
02-06-2009, 07:04 PM#4
Veev
You should really use 0.03125 (I think this is the smallest possible) or 0.04 for the timer... Anything smaller than that is way too fast. You might even be able to get away with 0.05 or 0.06, it all depends...
02-06-2009, 07:16 PM#5
Opossum
Well still it gets laggy at about 10 objects on my comp and that one's not too old.

Adding multiple forces or even collision (periodic 3D-distance checking yawrr) would totally fuck any compy...

WC3 just isn't a 3D game...
02-06-2009, 07:24 PM#6
Earth-Fury
Quote:
Originally Posted by Opossum
Well still it gets laggy at about 10 objects on my comp and that one's not too old.

Adding multiple forces or even collision (periodic 3D-distance checking yawrr) would totally fuck any compy...

WC3 just isn't a 3D game...
Bullshit. Grim001 made a physics engine that handles at least a dozen different forces for hundreds of objects, and deterministic collision detection for spheical objects.(Which means no arrow-through-paper problem.) (Including with the terrain.) And, it didn't lag my old POS computer.

HINDYhat also made a less awesome engine, but actually released it: http://wc3campaigns.net/showthread.php?t=98089

Drop by the WC3C IRC server (use the java chat applet because im too lazy to find the IP because the URL doesn't work) if you want to talk fast and hard about how that's possible in WC3.
02-06-2009, 09:17 PM#7
Anitarf
This is why we don't use TriggerSleepAction. Well, one of the many reasons why we don't use it anyway.

If you want a large number of physics objects running smoothly you'll probably have to write unreadable code.
02-06-2009, 09:24 PM#8
Earth-Fury
Quote:
Originally Posted by Anitarf
This is why we don't use TriggerSleepAction. Well, one of the many reasons why we don't use it anyway.
The list of reasons is so long that a far more motivated person would still be too lazy to type it up. But a quick summary is:

A thread sleep is not the proper way to implement sane time-based code

Quote:
Originally Posted by Anitarf
If you want a large number of physics objects running smoothly you'll probably have to write unreadable code.
Not with function inlining. Sure, you'll still end up with a 200+ line function with nested logic so deep it will make your head spin, but it's not that hard to read if the code uses decent conventions. (good names, uniform indentation, etcetera.)

Now, hard to understand, yes. But what physics simulator isn't? :) If you're smart enough to understand the math needed to implement it, you're likely smart enough to understand the logic.
02-07-2009, 11:24 AM#9
Opossum
Well thanks all :>
I never liked TriggerSleepAction either and I knew that code wouldn't work but basically it helped me to demonstrate what I was aiming at [/excuses].

Anyway thanks for your help. I might take a look at the SEE and try to learn something from it :)
02-07-2009, 12:25 PM#10
Vexorian
Quote:
You should really use 0.03125 (I think this is the smallest possible)
It isn't.
Quote:
or 0.04 for the timer... Anything smaller than that is way too fast.
In gamecache era maybe, right now It seems even 0.02 is good.

Quote:
You might even be able to get away with 0.05 or 0.06, it all depends...
Actually no, even 0.04 looks bad when the object is fast. With 0.06 the only case in which it will look barely ok is if the model was a single ribbon emitter.
02-07-2009, 08:31 PM#11
Strilanc
You need to flag the struct so that if it is destroyed and recreated you will know.

One possibility is to add a location variable called flag, which is removed and nulled when destroy is called, and created when the struct is created. When you run your method, assign a local to the flag location, wait, and compare it to the current flag location. If it differs, the struct has been destroyed since you last looked at it.
02-07-2009, 09:15 PM#12
Captain Griffen
Or you could just use an incrementing counter int, starting at one and set ++ each time it changes (NB: may break horribly if you have 2^31 instances of the struct throughout the course of the game!!!).
02-07-2009, 11:51 PM#13
Anitarf
Or, if you use a timer, you could store it in the struct itself and release it in the onDestroy method.
02-08-2009, 01:50 AM#14
Strilanc
Quote:
Originally Posted by Anitarf
Or, if you use a timer, you could store it in the struct itself and release it in the onDestroy method.

That could cause problems if the same struct is used in a spell twice, since one would overwrite the other. The safest bet is a value which is constant for exactly the object's lifetime, such as a flag handle or the destruction counter griffen suggested.

... This is why I liked garbage collectors.
02-08-2009, 06:46 PM#15
Anitarf
Quote:
Originally Posted by Strilanc
That could cause problems if the same struct is used in a spell twice, since one would overwrite the other.
Obviously you'd add in a safety check to see if a timer is already running for the struct?