Critic my (bad) projectile system

Narks

Vastly intelligent whale-like being from the stars
Reaction score
90
I'm working on a projectile system, because other projectile systems I am simply not familiar with, and they do not suit my needs. I am aware this is unfinished - but honestly, I have no idea if I'm going in the right direction.

My ultimate goal, is to make a projectile system, where you just specify projectile target, speed, friction, homing, model, damage, etc., and it launches the projectile and you don't have to worry about it. (although if there is a way I can incorporate having the projectile call a function on collision or on tick or something, that would be cool too).

JASS:
library NarksProjectiles initializer Init requires Common 

globals
    private constant integer DUMMY_ID       = 'pZZZ'
endglobals

struct Projectile
    unit Unit 
    private effect Model
    
    real bearing
    real velocity
    real lifetime
    static method altCreate takes player p returns Projectile
        local Projectile pr = Projectile.create()
        set pr.Unit = CreateUnit(p,DUMMY_ID,0,0,0)
        call SetUnitHeight(pr.Unit, 128)
        set pr.bearing = 0
        set pr.velocity = 0
        set pr.lifetime = 0
        return pr
    endmethod
    method onDestroy takes nothing returns nothing
        set this.Unit = null
        call DestroyEffect(Model)
    endmethod
    method tick takes nothing returns nothing
        if GetUnitState(.Unit, UNIT_STATE_LIFE) <= 0 then
            call .destroy()
            return
        endif
        call SetUnitXY(this.Unit, GetUnitX(.Unit) + .velocity * Cos(bearing * bj_DEGTORAD), GetUnitY(.Unit) + .velocity * Sin(bearing * bj_DEGTORAD))
    endmethod
    method setModel takes string s returns nothing
        if Model != null then
            call DestroyEffect(Model)
        endif
        set Model = AddSpecialEffectTarget(s, .Unit, "origin")
    endmethod
endstruct

globals
    private timer Timer 
    
    private integer Top = 0
    private Projectile array arProj
endglobals


private function Update takes nothing returns nothing 
    local integer i = Top
    debug call BJDebugMsg("Top is " + I2S(Top))
    
    loop
        exitwhen i == 0
        if arProj<i>.Unit == null then
            // Projectile is dead, lets dispose of it.
            if i == Top then
                // Projectile was on top, just lower the top count.
                set Top = Top - 1
            else
                // Projectile was not on top, lets fill the hole.
                set arProj<i> = arProj[Top]
                set Top = Top - 1
                // Now lets move the replaced projectile.
                call arProj<i>.tick()
            endif
        else
            // Projectile is alive, lets work with it.
            call arProj<i>.tick()
        endif

        set i = i - 1
    endloop
endfunction

private function Unit2Projectile takes unit u, real nvx, real nvy returns nothing
    //set Top = Top + 1
    //set arProj[Top] = Projectile.create()
    //set arProj[Top].Unit = u
    //set arProj[Top].vx = nvx
    //set arProj[Top].vy = nvy
endfunction



function CreateProjectile takes player p returns Projectile
    set Top = Top + 1
    set arProj[Top] = Projectile.altCreate(p)
    
    return arProj[Top]
endfunction

private function Test takes nothing returns nothing
    local Projectile p = CreateProjectile(Player(0))
    call p.setModel(&quot;Abilities\\Weapons\\Mortar\\MortarMissile.mdl&quot;)
    set p.bearing = GetRandomReal(0,360)
    set p.velocity = GetRandomReal(0,15)
endfunction

private function Init takes nothing returns nothing
    debug call TimerStart(CreateTimer(), 1, true, function Test)
    
    set Timer = CreateTimer()
    call TimerStart(Timer, 0.01, true, function Update)
endfunction

endlibrary</i></i></i></i>


Alright, my first round of questions.
1). How do I check if a struct has been allocated? You can see I am checking if the unit is null to see if the struct has been destroyed, but I'm sure there is a proper way.
 

Weep

Godspeed to the sound of the pounding
Reaction score
400
2). How do I make a unit unselectable, but still targetable by attacks (ie. using triggers to order a unit to attack it, phoenix fire, immolation etc.) - I plan to make certain projectiles interceptable by automatic weapon systems.
Add and then remove Locust from it. Note: this will be partially undone if the unit is hidden and unhidden again.
 

Narks

Vastly intelligent whale-like being from the stars
Reaction score
90
Cool.

I think I will need to incorporate a way to recycle projectiles, too...
 

Nestharus

o-o
Reaction score
84
I think I will need to incorporate a way to recycle projectiles, too...

That would be good, but it would be bad if you had a lot of different unit types =). If you just had one type of projectile, then : p.


Ok, so just from looking at this. When dealing with stuff like this, you want to minimize the impact of each projectile that way you can have more projectiles on the map at one given time without lag : ).

This means gets rid of a lot of the methods and just put them in the calling method.

JASS:

    method tick takes nothing returns nothing
        if GetUnitState(.Unit, UNIT_STATE_LIFE) &lt;= 0 then
            call .destroy()
            return
        endif
        call SetUnitXY(this.Unit, GetUnitX(.Unit) + .velocity * Cos(bearing * bj_DEGTORAD), GetUnitY(.Unit) + .velocity * Sin(bearing * bj_DEGTORAD))
    endmethod


Same with functions ; ).

SetUnitX and SetUnitY is the right way to go : D, good job.

JASS:

call TimerStart(Timer, 0.01, true, function Update)


Should be .03125, or 32 times a second. You want your timers to run as little as possible without any noticeable difference. .03125 is a fantastic value for this : ).


Good job with the indexed array.


JASS:

struct Projectile


If you really care about optimization, then this should be
JASS:

struct Projectile extends array


The reason is because vJASS likes to add some extra code and triggers =).


JASS:

static method altCreate takes player p returns Projectile
        local Projectile pr = Projectile.create()
        set pr.Unit = CreateUnit(p,DUMMY_ID,0,0,0)
        call SetUnitHeight(pr.Unit, 128)
        set pr.bearing = 0
        set pr.velocity = 0
        set pr.lifetime = 0
        return pr
    endmethod


I suggest you create your own struct stack : ). Also Projectile.create() turns into Projectile.allocate() in this case. Change altCreate to create and yea... inline allocate via using your own stack.

JASS:

set pr.bearing = 0
        set pr.velocity = 0
        set pr.lifetime = 0


pointless ;p. The only thing that might want to be reset is lifetime, but the other two things are set by the user aren't they? Next, you should be able to take x, y, z coords and be able to determine the bearing from that. Finally, you might want to do two types of projectiles, that is homing and non-homing.

JASS:

call SetUnitXY(this.Unit, GetUnitX(.Unit) + .velocity * Cos(bearing * bj_DEGTORAD), GetUnitY(.Unit) + .velocity * Sin(bearing * bj_DEGTORAD))


Very bad ><. Don't use Cos and Sin over and over again. They are very slow natives because of all the calculations involved. With newer CPUs, it does do a lot of these calculations on the CPU, but I don't know if this works with wc3. The bearing never changes right? Just store the values...

JASS:

set .x = .x + .changeX
set .y = .y + .changeY
call SetUnitX(.unit, .x)
call SetUnitY(.unit, .y)


.change would be the initial Cos and Sin
.x would be current unit x. Initially this is GetUnitX.



I'm actually working on a super highly optimized projectile system =), so I've dealt with a lot of this ; p. Was helping a guy named Sevion with his and that's how my little project kind of started : D.


Oh, and if you want homing, compare .targetX, .targetY, .targetZ to GetUnitX(.target), GetUnitY(.target), etc ^_-. If they aren't equal, then there is a 99% chance that the angle has changed ;p. The idea here is to minimize calculations.

With plain projectiles, it's the very simple thing I showed above-
JASS:

set .x = .x + .changeX
set .y = .y + .changeY
call SetUnitX(.unit, .x)
call SetUnitY(.unit, .y)


With homing ones, this is the goal, but you sometimes need to change the angle and unit facing =), so you want to minimize the uses of calculating the angle (Atan2), and calculating the new translations (Cos, Sin).

If you take these things to heart, you'll have the most optimized possible system you can have : D, meaning you'll maximize the amount of projectiles you can have on your map without any lag ^^.
 

Narks

Vastly intelligent whale-like being from the stars
Reaction score
90
This means gets rid of a lot of the methods and just put them in the calling method.
Do you mean I should try to avoid as many function calls as possible (when the timer ticks), or remove methods from my struct?

I suggest you create your own struct stack : ). Also Projectile.create() turns into Projectile.allocate() in this case. Change altCreate to create and yea... inline allocate via using your own stack.
I don't understand what you are saying here. I have not used structs in vJASS really, until now.

Very bad ><. Don't use Cos and Sin over and over again. They are very slow natives because of all the calculations involved. With newer CPUs, it does do a lot of these calculations on the CPU, but I don't know if this works with wc3. The bearing never changes right? Just store the values...
I plan to make homing missiles, so the bearings can change. However with non-homing I'll fix doing the calculations over and over, but is there a better way of calculating the horizontal and vertical components?

ty for the help btw
 

Romek

Super Moderator
Reaction score
963
> Don't use Cos and Sin over and over again. They are very slow natives because of all the calculations involved.
Have you tested this?
I've heard numerous times that Cos and Sin are amongst the fastest natives.
They're not calculated when the natives are called either, it's just a read from a database.
 

Nestharus

o-o
Reaction score
84
I edited some of my post, so read new parts.

Do you mean I should try to avoid as many function calls as possible (when the timer ticks), or remove methods from my struct?

Remove as many calls as possible. If you were using cJASS, you could use definitions, which is what I do a lot of the time ^_-.

I don't understand what you are saying here. I have not used structs in vJASS really, until now.

new:
if structRecycle != 0
newStruct = structStack[--structRecycle]
else
newStruct = ++structIndex

destroy:
structStack[structRecycle++] = destroyedStruct

EDIT
> Don't use Cos and Sin over and over again. They are very slow natives because of all the calculations involved.
Have you tested this?
I've heard numerous times that Cos and Sin are amongst the fastest natives.
They're not calculated when the natives are called either, it's just a read from a database.

Haven't tested no. It's traditional for trigonometric calculations to be slow is all ^_-, so I assumed ;p. Anyhow, minimizing calls to them is still a very positive thing. That's how I managed to get my little homing projectile project to where it was. There are still quite a few optimizations that can be done, so I expect that the final result will be doing 1200+ homing projectiles on map without any lag : ). Currently it does around 900.
 

Narks

Vastly intelligent whale-like being from the stars
Reaction score
90
new:
if structRecycle != 0
newStruct = structStack[--structRecycle]
else
newStruct = ++structIndex

destroy:
structStack[structRecycle++] = destroyedStruct

Can you explain this a little more? What is structRecycle, structIndex, and destroyedStruct?
 

Nestharus

o-o
Reaction score
84
They are absolutely nothing, just arbitrary names i chose to show you the model. Name them whatever you want.

newStruct is always going to be your new struct in your create method

destroyedStruct is always this in your destroy method =).

JASS:

struct MyStruct extends array
    private static integer structRecycleIndex = 0
    private static integer structIndex = 0
    private static thistype newStruct
    private static integer array structStack

    public static method create takes nothing returns thistype
        if thistype.structRecycleIndex != 0 then
            set thistype.structRecycleIndex = thistype.structRecycleIndex - 1
            set thistype.newStruct = thistype.structRecycleIndex
        else
            set thistype.newStruct = thistype.structIndex
            set thistype.structIndex = thistype.structIndex + 1
        endif
        //do your ini stuff or w/e
        return thistype.newStruct
    endmethod

    //if using cJASS and you don&#039;t have leaks to clean up, you might as well inline this
    public method destroy takes nothing returns nothing
        thistype.structStack[thistype.structRecycleIndex] = this
        thistype.structRecycleIndex = thistype.structRecycleIndex + 1
    endmethod
endstruct



Now does it make sense? I was using C Syntax when showing you since I don't like typing too much, but this is in JASS syntax now ; )


Now, there is a system that does all of this for you with a module, but it's written in cJASS :p.

Oh, if you want to extend on structs that extend arrays, just use a delegate of that struct-

JASS:

public static delegate MyStruct myStruct


This will work with all methods and what not. If you want to use specific instances, you can also make it non-static and set it to a value, but this by itself will point you to the right methods and variables in the background =). From there, you can use the myStruct create methods and what not and just work off of its own collections.

JASS:

struct MyStruct2 extends array
    public static delegate MyStruct myStruct

    public static method create takes nothing returns thistype
        thistype.newStruct = myStruct.create()
        //ini stuff
        return thistype.newStruct
    endmethod
endstruct


call MyStruct2.create()
call MyStruct2.destroy()

both would work perfectly and they'd use the collection in MyStruct =).


When it comes to optimizing vJASS, I know all the tricks ;p. I even know how to have interfaces without trigger evaluations ;d
 

Vexorian

Why no custom sig?
Reaction score
187
> Don't use Cos and Sin over and over again. They are very slow natives because of all the calculations involved.
Have you tested this?
I've heard numerous times that Cos and Sin are amongst the fastest natives.
They're not calculated when the natives are called either, it's just a read from a database.
getting rid of Cos/Sin/Squareroot/ function calls. It is the same "speed" meme we've been using for years. But in practice it is irrelevant as heck.

Make sure to code your system correctly, and that it works correctly, good optimization = making surei t doesn't leak memory . Anything more than that should only come if you actually notice there are speed issues with your system, try with the number of missiles you expect in your map, and check if it is fast enoug hor if it needs fixing.

If you do have speed problems, that's good start by making sure you are not doing operations you don't need. In some cases you can get rid of Cos/Sin and replace it by arithmetic, but make sure you know what you are doing. Some guy following that "Sin is slow" mantra, changed to arithmetic that was far slower and more complicated (which equals bugged) than just using Sin.

Don't fall into Function call hysteria either, optimizing calls is only going to make an important change in VERY few situations, otherwise it will just make your code harder to update , not worth it...
 

Nestharus

o-o
Reaction score
84
JASS:

if GetUnitState(.Unit, UNIT_STATE_LIFE) &lt;= 0 then


Should be GetWidgetLife or w/e.


And to Vexorian... if you could forcefully inline functions in vJASS, it'd stay just as easy to maintain, which is why I end up using cJASS : ).


Oh yes, and for your own struct stack, I just wrote something that's quite easy to use and will do it with little effort-

JASS:

struct C extends array {
    implement Collection

    public thistype create() {
        new Collection //inlined!
    }

    public void destroy() {
        destroy Collection //inlined!
    }
}


Now wouldn't that be cool? And if you were just doing that for your methods, you wouldn't even need a create or destroy-

JASS:

new Collection
destroy Collection
new Collection(structName)
destroy Collection(var) //for destroying specific instances within a struct
destroy Collection(structName, var)
new Collection(structName, var) //var will be set to the new collection


Just as maintainable I think : ). But again, this goes back to the debate of being able to forcefully inline code =P.


Oh, and for extending structs-
JASS:

Extend(structName)


Only way to extend structs that extend arrays is through the use of delegates, so that just creates a private delegate in the background ; ). From there, you wouldn't be using collections in the extending struct, rather you'd be getting instances from the previous struct and setting the delegate to them. You could also create a create method or w/e so that you can do layered extensions ; ).


But now we're really getting into serious optimization practices, which can be very hindering. I would never recommend doing stuff like this in compiled programming languages. I'd only do this in interpretive languages, like JASS : P.
 

Narks

Vastly intelligent whale-like being from the stars
Reaction score
90
Well, I'm not trying to super-optimize my system (obviously I don't want any memory leaks or stuff that eats up system resources like cake). My main goal is pretty much to learn new things - this thread has given me a lot to investigate (like what a struct stack is) - which hopefully I can apply to other languages (*cough* sc2 *cough*).

It's also to learn some of the other features of vJASS I haven't touched (which is pretty much everything except free global declaration).

Chances are I won't even use this system, although maybe if I get good at this, I'll release something useful.


Also if I want to recycle projectiles, what do I need to do to the unit itself? I am thinking it needs to be hidden, but then what about things like group enums that pick all units in an area?
 

Nestharus

o-o
Reaction score
84
Some of the things I touched upon are from my own stuff, for example the Extend function and collections. Those aren't part of vJASS, lol.

As for recycling units... think of a struct stack. Look at the code I've already shown you and it'll come to you. Just SetUnitX and UnitY to somewhere far away, take them out of the system, and put them into a stack. When you get a new one, get one from the stack if the stack isn't empty, otherwise make a brand new one ; ).
 

Jesus4Lyf

Good Idea™
Reaction score
397
Hey Nestharus:
>if you could forcefully inline functions in vJASS, it'd stay just as easy to maintain
JASS:
globals
    integer MyKi=0
endglobals
function GiveUsAKey takes nothing returns integer
    set MyKi=MyKi+1
    return MyKi
endfunction
// Try force inlining AddMeUp:
function AddMeUp takes integer i return integer
    return i+i
endfunction
function AddNewKeys takes nothing returns integer
    return AddMeUp(GiveUsAKey())
endfunction

Of course, AddNewKeys returns 2. Unless you force inline AddMeUp, which would make it return 3. This is why it is not easier to maintain (because it doesn't necessarily work) and it isn't a good idea.

Now, if you write a manual allocator like Lyncor did with Lightning and want to inline that, by all means. But please stop asking it to be a vJass feature. It is useless because you can do it anyway. If it was made a feature, it would probably be misused like above.

This is the only time I wish to demonstrate this.
 

Nestharus

o-o
Reaction score
84
Ok, back to the actual topic (let's try and keep side talk to a minimum, that's what PMs are for ^^)

Well, I'm not trying to super-optimize my system (obviously I don't want any memory leaks or stuff that eats up system resources like cake). My main goal is pretty much to learn new things - this thread has given me a lot to investigate (like what a struct stack is) - which hopefully I can apply to other languages (*cough* sc2 *cough*).

That's great Narks : ). However, with projectile systems a key concern is the number of projectiles you can have on a map at the same time without any lag : ).

When you start doing homing projectiles, it gets a little more complicated ^_-.

Because you might have a large amount of projectiles on the map (imagine 1200+ units on the map all firing projectiles 3x a second, this is very realistic for a wc3 custom RTS map like foots), you need to keep supreme optimization in mind. Would you want your system to lag the map hardcore with those 3600+ projectiles? Medium? Zero?

When dealing with systems that can become extremely large scale like projectiles or timers, optimization is vital ^_-. If you look through a lot of physics engines, the main thing they brag about is how many objects they can have going through the engine at one time without slowing down a machine : D.


Of course we get back to the common saying-
minimize coupling
maximize cohesion

And so this is where you, as the architect, have to become creative by writing extremely optimum code and keeping it extremely maintainable ; ).

Would you rather use a projectile system that lagged at 2000 units, or 4000 given they both have the same exact interface. I'd use the 4000 def : D.


Perhaps rather than working on a serious large scale system like this that requires immense optimization, you should make a spell that doesn't ; ). That's where most people start out : P.

Actually, a very good practice would be making something like a linked list. That would teach you how memory works in general and how programming languages operate ^_^.

Keep up your endeavor mate, you'll get there : ).
 

Nestharus

o-o
Reaction score
84
You can forget everything Nestharus said and use T32 instead.

If you'd like to learn things, I recommend checking out the code there. It's nice...

Using T32 would def being easier than going through all of this trouble, and the performance T32 would yield would be almost the exact same performance of all of this extra work. Using T32 would be the smart thing to do ; ).
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Staff online

      Members online

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top