wc3campaigns
WC3C Homepage - www.wc3c.netUser Control Panel (Requires Log-In)Engage in discussions with other users and join contests in the WC3C forums!Read one of our many tutorials, ranging in difficulty from beginner to advanced!Show off your artistic talents in the WC3C Gallery!Download quality models, textures, spells (vJASS/JASS), systems, and scripts!Download maps that have passed through our rigorous approval process!

Go Back   Wc3C.net > Tutorials > JASS/AI scripts tutorials
User Name
Password
Register Rules Get Hosted! Chat Pastebin FAQ and Rules Members List Calendar



Reply
 
Thread Tools Search this Thread
Old 02-09-2007, 02:39 PM   #1
Vexorian
Free Software Terrorist
 
Vexorian's Avatar


Technical Director
 
Join Date: Apr 2003
Posts: 14,905

Submissions (37)

Vexorian has a reputation beyond repute (1060)Vexorian has a reputation beyond repute (1060)Vexorian has a reputation beyond repute (1060)Vexorian has a reputation beyond repute (1060)Vexorian has a reputation beyond repute (1060)Vexorian has a reputation beyond repute (1060)Vexorian has a reputation beyond repute (1060)

Hero Contest #3 - 2nd Place

Default vJass for spell makers (Moving from handle vars to structs)

This tutorial is about using new technologies... vJass is a set of extensions to Jass it specially adds structs and global declaration freedom, what are these features and how to use it are things that are included in this tutorial.

If you are a handle vars user you would like this tutorial since I am gonna update a spell from old, rusty handle vars to structs and will also specify exactly why you should stop using handle vars (it is also valid for attacheable vars, which are actually the same thing)

Requirements
- JassHelper is a proof of concept compiler for the vJass extension that works inside grimoire or WEHelper (although wehelper currently is having issues with patch 1.21) An easy way to get it is from the Jass NewGen pack.

In order to use the extensions you need to save the map using (grimoire or wehelper)+JassHelper, so it processes the script and translates it to valid Jass code.

The spell
The spell we will port to vJass is optical flare, an starcraft inspired spell that reduces sight radius of units. It is a very old spell I made in my beginning days...

It is convenient for this tutorial because the complex is simple: Just remove the unit's sight range and then periodically move a dummy unit with some minimal sight range to the position of the unit. It is also an special spell that may also need some global variables.

Collapse JASS:
//**********************************************************************************************
//*
//* Optical Flare
//* ¯¯¯¯¯¯¯¯¯¯¯¯¯
//*     Requires:
//*          - The Pool Class functions
//*          - The Handle Variables functions
//*          - The Optical Flare Ability
//*          - The Optical Flare Buff
//*          - The Optical Flare Sight Destruction Ability
//*          - The Flare Vision unit type
//*          - This Trigger (make sure it points to the right rawcodes)
//*     Note:
//*          - If you have any custom detector ability in your map, you have to add it to the
//*            SetupDetectionPool function at the end of this configuration section.
//*     
//**********************************************************************************************

//==============================================================================================
// Optical Flare Configuration
//
constant function OpticalFlare_SpellId takes nothing returns integer
    return 'A007' //// The Rawcode of the Optical Flare Ability In your map
endfunction

constant function OpticalFlare_BuffId takes nothing returns integer
    return 'B000' //// The Rawcode of the Optical Flare Buff In your map
endfunction

constant function OpticalFlare_SightDestructorSpellId takes nothing returns integer
    return 'A006' //// The Rawcode of the Optical Flare Sight Destructor ability In your map
endfunction

constant function OpticalFlare_FakeSightUnit takes nothing returns integer
    return 'n000' //// The Rawcode of the Optical Flare Vision unit type In your map
endfunction

constant function OpticalFlare_MissileSpeed takes nothing returns integer
    return 1500 //// The missile speed, should be exactly the same as the one used by the ability
endfunction

function SetupDetectionPool takes integer spells returns nothing

    call PoolAddItem(spells,'Agyv') //True Sight (Flying Machine)
    call PoolAddItem(spells,'Atru') //True Sight (Shade)
    call PoolAddItem(spells,'Adtg') //True Sight (Neutral 1)
    call PoolAddItem(spells,'ANtr') //True Sight (Neutral 2)
    call PoolAddItem(spells,'Adts') //Magic Sentry
    call PoolAddItem(spells,'Adt1') //Detector (Sentry Ward)
    call PoolAddItem(spells,'Abdt') //Burrow Detection (fliers)

 //If you have any custom ability that allows passive detection, you must add a line for it
 // Example:

    call PoolAddItem(spells,'A008') //Creep Detection Ability

 // Remove the example from your map, or this ability would remove an ability it is not
 // supposed to remove.

endfunction

//===================================================================================================
function OpticalFlareDetectDetector takes unit u, integer p returns integer
 local integer n=CountItemsInPool(p)
 local integer i
    loop
        exitwhen n==0
        set i=PoolGetItem(p,n)
        if GetUnitAbilityLevel(u,i)>0 then
            return i
        endif
        set n=n-1
    endloop

 return 0
endfunction

function OpticalFlare_GetUnit takes timer t, string s returns unit
    return GetHandleHandle(t,s)
endfunction

function OpticalFlare_Timer takes nothing returns nothing
 local timer t=GetExpiredTimer()
 local unit b=OpticalFlare_GetUnit(t,"b")
 local unit sh=OpticalFlare_GetUnit(t,"sh")
 local real x=GetHandleReal(t,"x")
 local real y=GetHandleReal(t,"y")
 local real nx=GetUnitX(b)
 local real ny=GetUnitY(b)
 local real a=ModuloReal( Atan2(ny-y,nx-x) , 2*bj_PI)
 local real d= SquareRoot(Pow(x-nx,2) +Pow(y-ny,2))
    if ModuloReal(a+bj_PI/4,bj_PI*2)<=bj_PI then
        set d=d*40
    endif
    call SetUnitPosition(sh,nx+d*Cos(a),ny+d*Sin(a) )
    call SetHandleReal(t,"x",nx)
    call SetHandleReal(t,"y",ny)
    call SetUnitFacing(sh,GetUnitFacing(b))
    call SetUnitFlyHeight(sh, GetUnitFlyHeight(b)+120,0)
 set t=null
 set b=null
 set sh=null
endfunction

function OpticalFlare_Effect takes unit b, integer l returns nothing
 local real ac=GetUnitAcquireRange(b)
 local unit sh=CreateUnit( GetOwningPlayer(b), OpticalFlare_FakeSightUnit(), GetUnitX(b), GetUnitY(b), 0)
 local integer abi=OpticalFlareDetectDetector(b, GetStoredInteger(InitGameCache("opticalflare"),"opt","pool") )
 local timer t=CreateTimer()
    call SetUnitPathing(sh,false)
    call UnitRemoveAbility(b, abi )
    call UnitAddAbility(b, OpticalFlare_SightDestructorSpellId() )
    call UnitMakeAbilityPermanent(b,true,OpticalFlare_SightDestructorSpellId())
    call UnitMakeAbilityPermanent(b,true,OpticalFlare_BuffId())
    call SetHandleHandle(t,"b",b)
    call SetHandleHandle(t,"sh",sh)
    call TimerStart(t,0.01,true, function OpticalFlare_Timer )
    loop
        exitwhen IsUnitDeadBJ(b)
        call TriggerSleepAction(0)
        exitwhen not UnitHasBuffBJ(b, OpticalFlare_BuffId() )
    endloop
    call UnitRemoveAbility(b, OpticalFlare_SightDestructorSpellId() )
    call UnitAddAbility(b, abi )
 call RemoveUnit(sh)
 call FlushHandleLocals(t)
 call DestroyTimer(t)
 set t=null
 set sh=null
endfunction

function Trig_Optical_Flare_Actions takes nothing returns nothing
 local unit u=GetTriggerUnit()
 local unit blind=GetSpellTargetUnit()
 local integer l=GetUnitAbilityLevel(u,GetSpellAbilityId() )
    call PolledWait(  SquareRoot(Pow(GetUnitX(u)-GetUnitX(blind),2) + Pow(GetUnitY(u)-GetUnitY(blind),2)  ) / OpticalFlare_MissileSpeed() )
    call OpticalFlare_Effect(blind,l)
 set u=null
 set blind=null
endfunction

function Trig_Optical_Flare_Conditions takes nothing returns boolean
    return GetSpellAbilityId() == OpticalFlare_SpellId()
endfunction

function InitTrig_Optical_Flare takes nothing returns nothing
 local integer i=CreatePool()
    call StoreInteger(InitGameCache("opticalflare"),"opt","pool",i)
    call SetupDetectionPool(i)
    set gg_trg_Optical_Flare = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_Optical_Flare, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( gg_trg_Optical_Flare, Condition( function Trig_Optical_Flare_Conditions ) )
    call TriggerAddAction( gg_trg_Optical_Flare, function Trig_Optical_Flare_Actions )
endfunction


I will take some moments to explain what the spell is doing. The first part are some constant functions a pre JESP header:

SpellId :This is the id of the spell that causes the trigger to run
BuffId :We need to know the buff id so the trigger effects of the spell are dispellable.
SightDestructorSpellId:It is an ability that removes sight range of the unit you add it to. (Sight range bonus ability tweaked to add negative sight range instead)
FakeSightUnit:This is the unit with an small visibility radius we are gonna move periodically to the position of the unit.

SetupDetectionPool:Unfortunately, detection abilities make units immune to negative sight range bonuses... So we need to remove them, this is a configuration function and it uses pools to add the detector abilities...

Then the actual code:

OpticalFlareDetectDetector: This is a search and return function, using that one pool will check whether a unit has or not a detector ability and in that case returns its ability. It would be odd to expect a unit to have 2 or more detector abilities.
OpticalFlare_GetUnit: This spell is using an old HandleVars version that didn't have specific functions like GetHandleUnit , you had to make your own typecasters periodically, it eventually made me change the handlevars to attacheable vars which had more complete get functions, some others also updated HandleVArs.
OpticalFlare_Timer: The stuff to do whenever the timer expires, this is where we noticed that just moving the unit to the position of the unit wasn't enough, so we also detect if the unit is moving and predict the movement of the unit, the math in this funciton could be a lot better but it is out of the scope of this tutorial. Notice the extensive usage of HandleVars.
OpticalFlare_Effect: This function triggers when the spell is casted, it does the dirty job setting up some stuff and whathing for the buff expiration then removes the unit.
Trig_Optical_Flare_Actions: It is the one that calls _Effect after waiting some time because of the missile, again many things could improve this was a very old spell...

_Conditions:I won't bother explaining this:
InitTrig... :Here you may notice something and it is the fairly odd way we are using gamecache and the pools. In fact, the current design of the spell is terribly flawed because back then I didn't want to make global variables since they were a mess to add. It is currently leaking some stuff each 0.01 seconds! We later discovered that InitGameCache causes memory leaks and should only be used once.

The first update
Now that we analized this spell we notice what is the priority: fix the leak.

We could go the easy way and make a gamecache variable to prevent the InitGameCache, but it seems that it is not the real problem, aren't we doing too much effort for an integer? (pool id) In fact, do we really need the pool? Isn't the dynamism of pools better for things that need to be dynamic? So I would guess that a better thing to do is to replace all the pool stuff with an array. Besides we also need an integer variable. And these things are configuration stuff used by all the instances of optical flare, so it would be convenient that they were global variables.

But then how do you add global variables? Long ago the only way was with the variable dialog in GUI, which took some time and was limited, besides of making the spell harder to copy since copying a Jass trigger to another map does not copy the global variables it uses.

But vJass allows declaration of globals by globals block in a way similar to common.j/blizzard.j/war3map.j globals.

It is like this:

Collapse JASS:
globals
    type name
    type2 name2 = value
endglobals

type works in the same way it works in local variable declarations, and the rest is the same as well....

So, we can be happy now since we are gonna add a global array to replace that whole pool and gamecache stuff that was leaky.

We need these globals:
Collapse JASS:
globals
    integer array DetectorSpells
    integer array DetectorSpellsN=0
endglobals

This requires modiffications of 3 functions, the setup one in which the user adds the detector spells, the one that returns the id of a detector ability if it is found and the init function that calls the setup one.

Collapse JASS:
function SetupDetectionAbilities returns nothing

    set DetectorSpells[1]='Agyv' //True Sight (Flying Machine)
    set DetectorSpells[2]='Atru' //True Sight (Shade)
    set DetectorSpells[3]='Adtg' //True Sight (Neutral 1)
    set DetectorSpells[4]='ANtr' //True Sight (Neutral 2)
    set DetectorSpells[5]='Adts' //Magic Sentry
    set DetectorSpells[6]='Adt1' //Detector (Sentry Ward)
    set DetectorSpells[7]='Abdt' //Burrow Detection (fliers)

    // set DetectorSpellsN=7 //normally these 7 spells  are needed, uncomment this line if they are just 7

 //If you have any custom ability that allows passive detection, you must add a line for it
 // Example:

    set DetectorSpells[8]='A008' //Creep Detection Ability
    set DetectorSpellsN=8 //We added 8 detector spells to the array, so let's specify that number here.

 // Remove the example for your map (also update the count), or this ability would remove an ability it is not
 // supposed to remove.

endfunction

A typo was fixed. Notice that this time we are using the arrays, it might be a little less intuitive for the user but this has got a work around I will specify later. This function no longer needs any arguments, so we removed the argument as well.

Collapse JASS:
function OpticalFlareDetectDetector takes unit u returns integer
 local integer i
 local integer n=DetectorSpellsN
    loop
        exitwhen n==0
        set i=DetectorSpells[n]
        if GetUnitAbilityLevel(u,i)>0 then
            return i
        endif
        set n=n-1
    endloop
 return 0
endfunction

We do not need that p argument either. Also updated the part of OpticalFlare_Effect that called it:
local integer abi=OpticalFlareDetectDetector(b )
Finally the Init function looks much cleaner:
Collapse JASS:
function InitTrig_Optical_Flare takes nothing returns nothing
    call SetupDetectionAbilities()
    set gg_trg_Optical_Flare = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_Optical_Flare, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( gg_trg_Optical_Flare, Condition( function Trig_Optical_Flare_Conditions ) )
    call TriggerAddAction( gg_trg_Optical_Flare, function Trig_Optical_Flare_Actions )
endfunction

Replacing the handle vars
The obvious update was done, but now we have an emeny left, the HandleVars calls.

We just have to take a look at OpticalFlare_Timer and OpticalFlare_Effect to see how HandleVars intensive this spell currently is.

What's wrong with handle vars? you may ask.
  • Handle vars are too many function calls: H2I, I2S(), (Get)Store(d)(type), function calls in Jass are slower than they should.
  • HandleVars use gamecache: Gamecache is - even when you just use the natives - slow. When you use handlevars you are mostly just storing/reading some integer and real variables, an operation that currently is not really worth the time spent by the gamecache natives. Don't get me wrong, gamecache is very useful in some other situations but in this one it was found not to be the best way.
  • HandleVars are return bug intensive, there is no issue with this as long as you code properly all the time, otherwise many problems arise when you trust too much in storing references to a handle and then transforming those references into variables. Besides of the ackledged gamecache bug (That is actually a handle stack bug)

    There are issues that happen when you use natives on flawed references (ie: a unit that no longer exists that you got solely from handlevars), If you do not take care of properly cleaning all references from handlevars to an object when you remove the object you might end up causing some havoc
  • HandleVars are string-based for fields.
  • HandleVars are actually confusing, you would have noticed a hard time when learning Jass spells, it is because HandleVars and copies of that approach make code look like spaghetty.

The alternative chosen by this tutorial are structs why?
  • Structs only need 2 function calls: one to be "created" and one to be "destroyed", otherwise, when setting/getting values from an struct there is no extra function call, in fact you are just reading an index of an array variable!
  • Since they use actual arrays to store values and references to objects, the objects are still "protected" since they still have a reference pointing to it and the engine knows about it, so it is a little safer. And you certainly won't have as many issues from getting a removed unit from an struct as from getting a removed unit from HandleVars.
  • And instead of just letting you use the array directly, making your code kind of complicated they use an even friendlier syntax.

Are structs the ultimate medicine? No, they aren't:
  • Structs have an instances limit, 8190 instances per struct type, this is not a problem most of the times (namely it is not a problem at all for spells), but sometimes it is problematic.
  • Structs are less dynamic than handle vars, you are not using strings as labels anymore but fixed names for fields that you have to declare, this makes everything less flexible. This is most of the times actually a good thing (the whole OOP theory is based on the idea that limiting yourself is good (please don't take this as a fact=)) but it might be a problem for some applications.
  • There is no structs<->handle relationship, this means structs don't work the way handlevars do, you don't have an struct per handle where you set variables and stuff, so you need a workaround for getting a handle's struct.

In the case of spells, structs are fairly perfect as long as you figure out a way to attach an struct to a handle. The limit is not meaningful since you would hardly need 8190 instances of an spell, in fact it is hard already to have 100 instances of an spell...

So, what exactly are structs?
Do you know what an array is? An array is a group of variables with the same name that are indexed. Now think of an alternative to an array, instead of grouping the variables as indexes of an array, you group them by name.

So you have some kind of variable that holds a group of variables, subvariables, and they got their own name.

For instance take a point, a point in the 2D world is a pair of values, x and y. If you had a variable of type

point it would have 2 parts: x and y.

In a perfect world you would be able to tell Jass that you want to have variables of type point, and be able to operate their x or y values separatedly.

Like:

Collapse JASS:
function DoSomething takes nothing returns point
 local point p=point.create()
     set p.x=4.5
     set p.y=12.3
 return p
endfunction

And you easily made a function that returns 2 values x and y, inside the point "variable", from now we will call the super variable an struct. Of course, Jass does not allow you to declare these new types. But vJass does...

But how would you declare it? What's the best way? You need to tell the compiler 2 things: name of the new type and the "subvariables" it holds. The syntax we chose is like this:

Collapse JASS:
struct point
    real x
    real y
endstruct

You may have noticed this if you read the JassHelper readme after downloading it.

So these struct types may hold any combination of "subvariables" inside! We will from now call these "subvariables" members or fields from now.

The final note is that in the example, the p variable is not actually holding a point but a reference to a point. Structs, like handles are always pointer-like.

In this case, what do we need?
An inspection to _Effect and _Timer brings some conclussions: The Handlevars used are 4: A unit b (unit affected by the buff), A unit sh (the dummy), and reals x,y (the previous position of the unit).

When we are moving from handlevars to structs we would expect to have the same functionality, what we would like here is to attach certain struct to the timer, this struct should hold those 4 values.

Collapse JASS:
struct flaredata
    unit b  //Unit affected by the buff
    unit sh //The dummy unit
    real x  //]
    real y  ///Previous Position
endstruct

Creating an struct
Creating an object of an struct type is really easy, you just need to use (objectname).create(), for example for this flaredata object we use flaredata.create()

How do you attach and struct to a handle?
This is the tricky part is it? It really isn't. references to structs are interchangeable with integers (in a similar way that handle references are... ...just easier)

For instance, will take the easiest way here and just use GetHandleInt / SetHandleInt , HandleVars aren't really a big performance hit when used once, nor they are a threat when used to store native types that are not typecasted references, of course, there are plenty of alternatives to attach a single integer to a handle, and there are some that are specialized towards the handle type. Plenty of the alternatives are much faster than HandleVars, but this is just an example so we'll use HandleVars.

To store an struct reference you just need to use the single store function that takes an integer but use the struct.

You can then assign an integer to an struct variable to load it. It is also recommended you use the structname() typecast operator since it is a more valid coding...

It has becamo hard to explain with vague words, so I'll just post code, it should be really easy to notice how this works:

Collapse JASS:
struct flaredata
    unit b  //Unit affected by the buff
    unit sh //The dummy unit
    real x  //]
    real y  ///Previous Position
endstruct

function OpticalFlare_Timer takes nothing returns nothing
 local timer t=GetExpiredTimer()
 local flaredata dat= flaredata( GetHandleInt(t,"dat") ) //notice the typecast operator,
                                                         //in this case it is not necessary but it just makes the 
                                                         //code look better
 local real nx=GetUnitX(dat.b)
 local real ny=GetUnitY(dat.b)
 local real a=ModuloReal( Atan2(ny-dat.y , nx-dat.x ) , 2*bj_PI) //we do not need them as locals anymore
 local real d= SquareRoot(Pow(dat.x-nx,2) +Pow(dat.y-ny,2))
    if ModuloReal(a+bj_PI/4,bj_PI*2)<=bj_PI then
        set d=d*40
    endif
    call SetUnitPosition(dat.sh,nx+d*Cos(a),ny+d*Sin(a) )

    set dat.x=nx //notice how setting thing actually uses the usual set syntax
    set dat.y=ny

    call SetUnitFacing(    dat.sh,GetUnitFacing( dat.b ))
    call SetUnitFlyHeight( dat.sh, GetUnitFlyHeight( dat.b )+120,0)
 set t=null
endfunction

function OpticalFlare_Effect takes unit b, integer l returns nothing
 local real ac=GetUnitAcquireRange(b)
 local unit sh=CreateUnit( GetOwningPlayer(b), OpticalFlare_FakeSightUnit(), GetUnitX(b), GetUnitY(b), 0)
 local integer abi=OpticalFlareDetectDetector(b )
 local timer t=CreateTimer()
 local flaredata dat = flaredata.create()
    call SetUnitPathing(sh,false)
    call UnitRemoveAbility(b, abi )
    call UnitAddAbility(b, OpticalFlare_SightDestructorSpellId() )
    call UnitMakeAbilityPermanent(b,true,OpticalFlare_SightDestructorSpellId())
    call UnitMakeAbilityPermanent(b,true,OpticalFlare_BuffId())

    set dat.x=0. //struct fields aren't automatically initialized to 0, unlike handle vars
    set dat.y=0. // It is also possible to make them initialize to a default value inside the struct block
    set dat.b=b
    set dat.sh=sh

    call SetHandleInt(t,"dat",dat) //Here we are attaching the struct object to the handle
    call TimerStart(t,0.01,true, function OpticalFlare_Timer )
    loop
        exitwhen IsUnitDeadBJ(b)
        call TriggerSleepAction(0)
        exitwhen not UnitHasBuffBJ(b, OpticalFlare_BuffId() )
    endloop
    call UnitRemoveAbility(b, OpticalFlare_SightDestructorSpellId() )
    call UnitAddAbility(b, abi )
 call RemoveUnit(sh)
 call FlushHandleLocals(t)
 call DestroyTimer(t)
 call flaredata.destroy(dat) //We need to dispose struct objects once they are not used anymore
 set t=null
 set sh=null
endfunction


Yes, you need to destroy structs, just like you need to flush handle vars. But don't think structs leak, they just use space that is ready for them, destroy is simply allowing the id to be recycled. If you just created and created struct types and forgot to destroy you would easily get to the 8190 instances limit since instances you are not using are not freed.

You must accept that the code looks better without all those GetHandleHandle and etc, if it doesn't look better to you at least be glad to know that this is much faster, if anything we reduced the GetHandle** calls done every 0.01s per flared unit to 1 (down from 4).

Shouldn't a better version get rid of all gamecache usage? We could use an alternative method to store the struct's integer id for the handle. But this spell allows us to use another method, one that eliminates the need of attaching a timer, we just need to abuse globals again...

A single timer
What we are currently doing (and is always done, a lot) Is, for each instance of the spell, create a timer, attach info and then make the timer expire each 0.01 seconds, then when the buff ends, destroy the timer and the info.

This means that there are as many timers for as many instances of the spell. Do we really need them?

In the case of this spell, the order in which each timer expires does not matter, the only thing that matters is to process the active buffs each 0.01 seconds.

Instead of making 100 timers to process 100 active instances, since what really matters is to have 100 instances and having 100 timers is not actually needed, we could just use a single timer and force it to process the 100 instances.

It is easier than it sounds, we should only add each active instance to a global array and then when this unique timer expires go through the whole array and process each of the indexes.

Adding stuff to an array is not difficult, you just keep some element number variable and just increase it after assigning last position to the new element.
Take a look to the expire event, it just processes the data, besides of getting the data from the expired timer.

Collapse JASS:
globals
    flaredata  array OpticalFlare_Ar
    integer OpticalFlare_total = 0
    timer OpticalFlare_timer=CreateTimer()
endglobals

WE got all we need, an array of flaredata objects, an integer that tells us the total and a timer that will expire when necessary.

Now, we need to handle this timer with some intelligence, it should effectively be paused when there are no active instances. And periodically expire each 0.01 seconds whenever there is at least once.

Starting the timer when necessary is easy, just check at the moment of adding an element to the array, if the number of elements was 0 then the timer was innactive and it is necessary to enable it.

However pausing the timer when there are no active buffs anymore is a little harder. First of all, we need to remove flaredata objects from the array at the time they are destroyed. Else we would have an array full of invalid objects.

We could go to the moment in which flaredata.destroy() gets called, and remove the element from the array there. But it is a little problematic to find the index of the currently being destroyed object.

Something better would be to stop destroying the flaredata objects from that function, and instead do it by request in the expiration event function.

OpticalFlare_Effect requires the right to request destruction (and removal) of a flaredata object.

The destruction request should probably just be a field in the flaredata struct, we could add a destroyplease boolean field to it:

Collapse JASS:
struct flaredata
    unit b  //Unit affected by the buff
    unit sh //The dummy unit
    real x  //]
    real y  ///Previous Position
    
    boolean destroyplease = false
endstruct

Notice the = false, it determines a default value automatically assigned to the new object when you use create().
Function OpticalFlare_Effect, should now just set that field to true at the moment it wants an object destroyed. Instead of calling destroy on it.

The expiration event would loop through all the supposedly active buffs, if it finds that for any of them destroyplease is true it should destroy the object and remove it it from the array.

In case the number of elements is 0 after processing the elements in the array, it should pause the timer again.

The current function that processes each flaredata object is OpticalFlare_Timer we should modiffy it to do the processing inside a loop and also handle the destruction.

Collapse JASS:
function OpticalFlare_Timer takes nothing returns nothing
 local integer i=0
 local flaredata dat
 local real nx
 local real ny
 local real a
 local real d

    loop
        exitwhen i==OpticalFlare_total
        set dat= OpticalFlare_Ar[i]
        if (dat.destroyplease) then
             //This is tricky.
             // We already have the ith flaredata referenced by dat, so we
             // will first remove it from the array, since the order does not matter
             // we can simply move the last flaredata to this position and decrease
             // the total

             set  OpticalFlare_Ar[i]= OpticalFlare_Ar[ OpticalFlare_total - 1]
             set OpticalFlare_total=OpticalFlare_total-1

             //now we are free to destroy the dat
             call dat.destroy() //another way to destroy struct objects.

             //If we don't substract i, it will skip the new object we just moved
             // to the ith position (we are increasing i later)
             set i=i-1

        else //Notice how this ressembles the original expire function
              set nx=GetUnitX(dat.b)
              set ny=GetUnitY(dat.b)
              set a=ModuloReal( Atan2(ny-dat.y , nx-dat.x ) , 2*bj_PI)
              set d= SquareRoot(Pow(dat.x-nx,2) +Pow(dat.y-ny,2))

              if ModuloReal(a+bj_PI/4,bj_PI*2)<=bj_PI then
                  set d=d*40
              endif
              call SetUnitPosition(dat.sh,nx+d*Cos(a),ny+d*Sin(a) )
              set dat.x=nx
              set dat.y=ny
              call SetUnitFacing(    dat.sh,GetUnitFacing( dat.b ))
              call SetUnitFlyHeight( dat.sh, GetUnitFlyHeight( dat.b )+120,0)
        endif
        set i=i+1
    endloop

    if (OpticalFlare_total==0) then
        // let's pause the timer, it is not needed anymore
        call PauseTimer(OpticalFlare_timer)
    endif

endfunction

WE now have to modiffy the _Effect function, since it is currently creating a timer and attaching stuff to it. We need to make it just add the new flaredata object to the array, start the timer if necessary and then set destroyplease to true instead of destroying the flaredata.

Collapse JASS:
function OpticalFlare_Effect takes unit b, integer l returns nothing
 local real ac=GetUnitAcquireRange(b)
 local unit sh=CreateUnit( GetOwningPlayer(b), OpticalFlare_FakeSightUnit(), GetUnitX(b), GetUnitY(b), 0)
 local integer abi=OpticalFlareDetectDetector(b )
 local flaredata dat = flaredata.create()
    call SetUnitPathing(sh,false)
    call UnitRemoveAbility(b, abi )
    call UnitAddAbility(b, OpticalFlare_SightDestructorSpellId() )
    call UnitMakeAbilityPermanent(b,true,OpticalFlare_SightDestructorSpellId())
    call UnitMakeAbilityPermanent(b,true,OpticalFlare_BuffId())

    set dat.x=0. //struct fields aren't automatically initialized to 0, unlike handle vars
    set dat.y=0. // It is also possible to make them initialize to a default value inside the struct block
    set dat.b=b
    set dat.sh=sh

    if(OpticalFlare_total==0) then //There are no elements in the array so the timer is inactive
        call TimerStart(OpticalFlare_timer,0.01,true,function OpticalFlare_Timer) //restart it
    endif

    set OpticalFlare_total=OpticalFlare_total+1 //increase the number of elements
    set OpticalFlare_Ar[ OpticalFlare_total-1 ] = dat //add the flaredata to the array, notice that these are arrays
                                                      //that begin with the [0] index...
    loop
        exitwhen IsUnitDeadBJ(b)
        call TriggerSleepAction(0)
        exitwhen not UnitHasBuffBJ(b, OpticalFlare_BuffId() )
    endloop
    call UnitRemoveAbility(b, OpticalFlare_SightDestructorSpellId() )
    call UnitAddAbility(b, abi )

 call RemoveUnit(sh)
 set dat.destroyplease = true //Will send a signal to the timer function so it destroys this object...
 set sh=null
endfunction

We just got rid of the creation of timers, attaching stuff, destroying timers, flushing handle vars and all those annoyances , in exchange of some array operations.

And that's it! The spell has been modiffied to use structs in a clean way instead of HandleVars, there is a huge speed gain in the 0.01 seconds timer. This spell used to cause the game a heart attack in my computer when there were 11 units affected by optical flare. Now you wouldn't notice a drop in performance even with 24 units affected with it, imagine how good it would be if we increased the timer to 0.04seconds from 0.01? That would allow a lot of units to be affected by this buff.

One thing is left to do: change the header to requires JassHelper, instead of requires PoolClass and HandleVars...

There is one thing pending and it is that the configuration function that sets the detector abilities is not as friendly as before. A second part of this tutorial in which we take the spell one step further by using scopes and methods will come soon.
__________________
Zoom (requires log in)Wc3 map optimizer 5.0
Someone should fix .wav sound in this thing.
Zoom (requires log in)JassHelper 0.A.2.A
Turns your simple code into something that is complicated enough to work.
Faster != more useful
Vexorian is offline   Reply With Quote
Sponsored Links - Login to hide this ad!
Old 02-13-2007, 07:12 PM   #2
The)TideHunter(
SpeakerGames.com
 
The)TideHunter('s Avatar
 
Join Date: Mar 2006
Posts: 1,328

Submissions (1)

The)TideHunter( is a jewel in the rough (160)The)TideHunter( is a jewel in the rough (160)

Send a message via MSN to The)TideHunter(
Default

Great tutorial, sounds interesting, i'm gonna go try some stuff out with this now.
__________________
Big plans...
The)TideHunter( is offline   Reply With Quote
Old 02-13-2007, 07:31 PM   #3
Rising_Dusk
Obscurity, the Art


Projects Director
Project Leader: OD
 
Join Date: Feb 2006
Posts: 9,727

Submissions (27)

Rising_Dusk has a reputation beyond repute (1192)Rising_Dusk has a reputation beyond repute (1192)Rising_Dusk has a reputation beyond repute (1192)Rising_Dusk has a reputation beyond repute (1192)Rising_Dusk has a reputation beyond repute (1192)Rising_Dusk has a reputation beyond repute (1192)Rising_Dusk has a reputation beyond repute (1192)Rising_Dusk has a reputation beyond repute (1192)Rising_Dusk has a reputation beyond repute (1192)

Hero Contest #3 - 1st PlaceApproved Map: Desert of ExileApproved Map: Advent of the ZenithHero Contest #2 - 1st PlaceHero Contest - Third place>

Send a message via AIM to Rising_Dusk Send a message via MSN to Rising_Dusk
Default

Okay...
Well I forced myself to read it.
I think I'll give it a go...

Nice job Vex.
It kinda sorta made sense to me.

I had an issue following at one point --
Collapse JASS:
             //This is tricky.
             // We already have the ith flaredata referenced by dat, so we
             // will first remove it from the array, since the order does not matter
             // we can simply move the last flaredata to this position and decrease
             // the total

             set  OpticalFlare_Ar[i]= OpticalFlare_Ar[ OpticalFlare_total - 1]
             set OpticalFlare_total=OpticalFlare_total-1
This part lost me.
What are you doing at this point?
The comments don't really register with my mind.
__________________

Last edited by Rising_Dusk : 02-13-2007 at 07:31 PM.
Rising_Dusk is offline   Reply With Quote
Old 02-13-2007, 07:47 PM   #4
Vexorian
Free Software Terrorist
 
Vexorian's Avatar


Technical Director
 
Join Date: Apr 2003
Posts: 14,905

Submissions (37)

Vexorian has a reputation beyond repute (1060)Vexorian has a reputation beyond repute (1060)Vexorian has a reputation beyond repute (1060)Vexorian has a reputation beyond repute (1060)Vexorian has a reputation beyond repute (1060)Vexorian has a reputation beyond repute (1060)Vexorian has a reputation beyond repute (1060)

Hero Contest #3 - 2nd Place

Default

[x][y][z][a]n=4
0123

i = 1
we want to remove ith element.

Easiest way so far is to place the last element in that position and then just reduce the number of elements

[x][a][z]n=4
__________________
Zoom (requires log in)Wc3 map optimizer 5.0
Someone should fix .wav sound in this thing.
Zoom (requires log in)JassHelper 0.A.2.A
Turns your simple code into something that is complicated enough to work.
Faster != more useful
Vexorian is offline   Reply With Quote
Old 02-16-2007, 04:14 AM   #5
zergleb
User
 
zergleb's Avatar
 
Join Date: Jul 2005
Posts: 88

zergleb has little to show at this moment (4)

Default

OK if I understand this right this right here

Collapse JASS:
    set nx=GetUnitX(dat.b)
    set ny=GetUnitY(dat.b)
    set a=ModuloReal( Atan2(ny-dat.y , nx-dat.x ) , 2*bj_PI)
    set d= SquareRoot(Pow(dat.x-nx,2) +Pow(dat.y-ny,2))

    if ModuloReal(a+bj_PI/4,bj_PI*2)<=bj_PI then
        set d=d*40
    endif
    call SetUnitPosition(dat.sh,nx+d*Cos(a),ny+d*Sin(a) )

Is a complex way to move dat.sh(The dummy unit) to the position of the unit that it is affecting (dat.b) now I would never claim to be a better programmer than you (Not even close) but why not just do this?

Collapse JASS:
    call SetUnitX(dat.sh, GetUnitX(dat.b))
    call SetUnitY(dat.sh, GetUnitY(dat.b))

and then you could just ignore saving the last position of the unit. Obviously you know of such methods so I think I can safely assume I'm wrong, I guess I'm just asking to be taught. Maybe there is something about optical flare I don't understand. but all I can see that really affects the game is SetUnitPostion. Is it trying to set the 2 units to the same spot?

Very nice tutorial btw. The rest of it was very clear and helpful to me and explained in a way that made perfect sense and taught me some things that are very useful.

Last edited by zergleb : 02-16-2007 at 04:29 AM.
zergleb is offline   Reply With Quote
Old 02-16-2007, 01:09 PM   #6
Vexorian
Free Software Terrorist
 
Vexorian's Avatar


Technical Director
 
Join Date: Apr 2003
Posts: 14,905

Submissions (37)

Vexorian has a reputation beyond repute (1060)Vexorian has a reputation beyond repute (1060)Vexorian has a reputation beyond repute (1060)Vexorian has a reputation beyond repute (1060)Vexorian has a reputation beyond repute (1060)Vexorian has a reputation beyond repute (1060)Vexorian has a reputation beyond repute (1060)

Hero Contest #3 - 2nd Place

Default

you should have tested yourself, there is some delay in the update of fog of war, if you move the dummy directly the flared unit will become invisible while moving.

So it is better to try to move the dummy where the unit would be after some time, kind of predicting where it is going . Nevermind the math there , it is terrible, there is a better way using vector theory.
__________________
Zoom (requires log in)Wc3 map optimizer 5.0
Someone should fix .wav sound in this thing.
Zoom (requires log in)JassHelper 0.A.2.A
Turns your simple code into something that is complicated enough to work.
Faster != more useful
Vexorian is offline   Reply With Quote
Old 02-16-2007, 03:37 PM   #7
zergleb
User
 
zergleb's Avatar
 
Join Date: Jul 2005
Posts: 88

zergleb has little to show at this moment (4)

Default

Oh yeah i knew there was a .40 update interval, I just didn't know the math well enough to know it was going infront of the unit. I wonder if you could just run a timer every .4 seconds and have the timer start at .38 seconds so it changes the unit position .02 seconds before the vision change? but I guess that is not for this thread.

but I understand now thanks.

Last edited by zergleb : 02-16-2007 at 03:37 PM.
zergleb is offline   Reply With Quote
Old 02-17-2007, 02:39 AM   #8
Chuckle_Brother
Oh for the sake of fudge
 
Chuckle_Brother's Avatar


Respected User
 
Join Date: Dec 2005
Posts: 782

Submissions (2)

Chuckle_Brother will become famous soon enough (53)Chuckle_Brother will become famous soon enough (53)

Send a message via ICQ to Chuckle_Brother Send a message via AIM to Chuckle_Brother Send a message via MSN to Chuckle_Brother Send a message via Yahoo to Chuckle_Brother
Default

Excellent stuff. My only issue with it is the decided lack of arrays within a struct, but still exceedingly useful.

Good stuff.
__________________
"...you play a mean banjo"
Chuckle_Brother is offline   Reply With Quote
Old 02-17-2007, 11:29 AM   #9
Vexorian
Free Software Terrorist
 
Vexorian's Avatar


Technical Director
 
Join Date: Apr 2003
Posts: 14,905

Submissions (37)

Vexorian has a reputation beyond repute (1060)Vexorian has a reputation beyond repute (1060)Vexorian has a reputation beyond repute (1060)Vexorian has a reputation beyond repute (1060)Vexorian has a reputation beyond repute (1060)Vexorian has a reputation beyond repute (1060)Vexorian has a reputation beyond repute (1060)

Hero Contest #3 - 2nd Place

Default

Making structs have arrays directly would ruin polymorphism, I don't think it is worth it. With dynamic array members it is possible to "have arrays" anyways

Collapse JASS:
library test
    private type intarray extends integer array [100]

    struct bbbb
          intarray ar
          int top=0
 
          method new takes nothing returns nothing
                set ar=intarray.create()
          endmethod
          method onDestroy takes nothing returns nothing
                call ar.destroy()
          endmethod
    endstruct
endlibrary

function errs takes nothing returns nothing
 local bbbb a=bbbb.create()
     set bbbb.ar[5]=34
endfunction

Eventually, in a next version declaring

Collapse JASS:
struct bbb
    integer array ar [100] //this syntax may be changed
    integer top=0
endstruct

Will do all of above automatically (except the library thing)
__________________
Zoom (requires log in)Wc3 map optimizer 5.0
Someone should fix .wav sound in this thing.
Zoom (requires log in)JassHelper 0.A.2.A
Turns your simple code into something that is complicated enough to work.
Faster != more useful
Vexorian is offline   Reply With Quote
Old 02-17-2007, 02:36 PM   #10
Chuckle_Brother
Oh for the sake of fudge
 
Chuckle_Brother's Avatar


Respected User
 
Join Date: Dec 2005
Posts: 782

Submissions (2)

Chuckle_Brother will become famous soon enough (53)Chuckle_Brother will become famous soon enough (53)

Send a message via ICQ to Chuckle_Brother Send a message via AIM to Chuckle_Brother Send a message via MSN to Chuckle_Brother Send a message via Yahoo to Chuckle_Brother
Default

That is just purely awesome.

But something I didn't get from here, but I assume to be the case, will all members of a struct object automatically be destroyed/nulled/removed/etc as fits the requirements for that object?
__________________
"...you play a mean banjo"
Chuckle_Brother is offline   Reply With Quote
Old 02-17-2007, 02:41 PM   #11
Vexorian
Free Software Terrorist
 
Vexorian's Avatar


Technical Director
 
Join Date: Apr 2003
Posts: 14,905

Submissions (37)

Vexorian has a reputation beyond repute (1060)Vexorian has a reputation beyond repute (1060)Vexorian has a reputation beyond repute (1060)Vexorian has a reputation beyond repute (1060)Vexorian has a reputation beyond repute (1060)Vexorian has a reputation beyond repute (1060)Vexorian has a reputation beyond repute (1060)

Hero Contest #3 - 2nd Place

Default

I would have to make a garbage collector or a ref counter for that, and I seriously wouldn't like to do it.

Most of the destroying is easy when you have an onDestroy method.

Nulling members is not really necessary, the struct's id is recycled and eventually the members will get assigned again.
__________________
Zoom (requires log in)Wc3 map optimizer 5.0
Someone should fix .wav sound in this thing.
Zoom (requires log in)JassHelper 0.A.2.A
Turns your simple code into something that is complicated enough to work.
Faster != more useful
Vexorian is offline   Reply With Quote
Old 02-17-2007, 04:06 PM   #12
Chuckle_Brother
Oh for the sake of fudge
 
Chuckle_Brother's Avatar


Respected User
 
Join Date: Dec 2005
Posts: 782

Submissions (2)

Chuckle_Brother will become famous soon enough (53)Chuckle_Brother will become famous soon enough (53)

Send a message via ICQ to Chuckle_Brother Send a message via AIM to Chuckle_Brother Send a message via MSN to Chuckle_Brother Send a message via Yahoo to Chuckle_Brother
Default

Excellent.
__________________
"...you play a mean banjo"
Chuckle_Brother is offline   Reply With Quote
Old 02-17-2007, 09:57 PM   #13
PipeDream
Moderator
 
PipeDream's Avatar


Code Moderator
 
Join Date: Feb 2006
Posts: 1,405

Submissions (6)

PipeDream is a glorious beacon of light (463)PipeDream is a glorious beacon of light (463)PipeDream is a glorious beacon of light (463)PipeDream is a glorious beacon of light (463)

Default

The structs aren't inlined (at least conceptually) in vjass, it's always a pointer-they never live constrained inside a scope. I think you only need three things for the desired behavior: something to stop assignment to that var (but reading is of course ok, if dangerous), a constructor syntax with ": varname(params)" and automatic entry in onDestroy. Hopefully the same syntax would let you constrain the struct instance to a function scope.

It could make things a little cleaner-one less line where you use it, of course structs get even more verbose and the language more complex so it might be a bad trade.
__________________
PipeDream is offline   Reply With Quote
Old 02-19-2007, 09:40 PM   #14
Lordy
User
 
Lordy's Avatar
 
Join Date: May 2006
Posts: 72

Submissions (1)

Lordy has little to show at this moment (7)

Send a message via MSN to Lordy
Default

Hey all,

I'm afraid that I'm still somewhat in the dark about vJass and structs.
vJass is an extension to Jass I understand. But how does this work? Wc3 maps can only consist of jass right? So maybe you guys have made a vJass compiler that compiles vJass into Jass so that it can be inserted into a wc3 map. Then vJass only makes the programming easier but the code not more efficient. Or does vJass work in another way?
Also I couldn't gather what functionality vJass adds. For one it adds structs, but what are structs exactly? I know it is a variable that can contain other variables. But is that the full functionality of structs? The code I read seemed to have some functions contained inside a struct: "bbbb.Create(); bbbb.onDestroy()", so I'm curious about that. Besides the structs I don't know what functionality vJass adds.

Thanks for answering. I don't mind if you just link me to some thread that I don't know about that contains the right information.

Lordy.
__________________
Being against ALL BJ is like being against ALL code written by other people.
Lordy is offline   Reply With Quote
Old 02-19-2007, 11:46 PM   #15
Vexorian
Free Software Terrorist
 
Vexorian's Avatar


Technical Director
 
Join Date: Apr 2003
Posts: 14,905

Submissions (37)

Vexorian has a reputation beyond repute (1060)Vexorian has a reputation beyond repute (1060)Vexorian has a reputation beyond repute (1060)Vexorian has a reputation beyond repute (1060)Vexorian has a reputation beyond repute (1060)Vexorian has a reputation beyond repute (1060)Vexorian has a reputation beyond repute (1060)

Hero Contest #3 - 2nd Place

Default

vJass cannot be faster than Jass, fact is that for the dynamic stuff that we used to use handle vars, arrays are a good alternative, but writing everything in terms of arrays and pointers (thus acquiring the level of dynamism we want) is a really low level way of doing it and generates some of the most horrible and repetitive Jass code. vJass simply saves the hassle of doing so.


It is not faster than Jass but it is surely faster than gamecache, and the way it works makes it easier to use. Unlike handling the stuff manually which would be more difficult than gamecache, specially considering the fact that it is a huge annoyance to actually declare a global variable without a preprocessor.
__________________
Zoom (requires log in)Wc3 map optimizer 5.0
Someone should fix .wav sound in this thing.
Zoom (requires log in)JassHelper 0.A.2.A
Turns your simple code into something that is complicated enough to work.
Faster != more useful
Vexorian is offline   Reply With Quote
Reply


Thread Tools Search this Thread
Search this Thread:

Advanced Search

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

vB code is On
Smilies are On
[IMG] code is On
HTML code is Off


All times are GMT. The time now is 10:34 AM.


Donate

Affiliates
The Hubb http://bylur.com - Warcraft, StarCraft, Diablo and DotA Blog & Forums The JASS Vault Clan WEnW Campaign Creations Clan CBS GamesModding Flixreel Videos

Powered by vBulletin (Copyright ©2000 - 2014, Jelsoft Enterprises Ltd).
Hosted by www.OICcam.com
IT Support and Services provided by Executive IT Services