• 🏆 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!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[System] DamageType (StructuredDD Extension)

Cokemonkey11

Spell Reviewer
Level 29
Joined
May 9, 2006
Messages
3,531
Hey guys,

Sorry for the double post. I'd like to direct everyone who has some interest in DD systems' attention to some data I've gathered over the last few days.

Why?

My intention with these tests is to understand the performance differences between some features and design choices used in some popular DD systems.

Some major differences include:

  • Handler implementation: In two of the tested scripts, handler functions are added to a stack as "callback" functions. In Nestharus' System, handlers are added by extending a set of parent scripts.
  • Physical/Spell Detection: In two of the systems, the client can recognize the damage type in scope, but in StructuredDD, one must implement the DamageType extension for the same functionality.
  • SET_MAX_LIFE ability: In two of the systems, deathblows can be prevented using a SET_MAX_LIFE ability. In StructuredDD, no extension or feature exists.
  • In two of the systems, damage event triggers are restructured periodically, but in Nestharus' system, UnitIndexer is used to recognize allocation/deallocation in comparatively deferred scope.

How?

The scripts vary in many ways, but I've used a "greatest common factor" to provide a baseline test. The baseline test creates a single unit, and then damages it with 0 damage n times every millisecond. We measure the FPS change as n increases and that's all there is to it.

In the DDS comparison scripts, we disable any restructuring scripts (Set the refresh interval very high). Then we add an empty onDamage handler and compare the new FPS.


The Data

Comparison of DD Systems

This set of data is a comparison between iterations per millisecond and client frames per second. As everyone should be aware, high frame rates are desirable, as well as one of our (current) best performance measurements, since system timers are not usable at the moment.


JASS:
scope stressTest initializer i
    globals
        private unit u
        private integer kIPS=0
    endglobals
    
    private function p takes nothing returns nothing
        local integer j=0
        loop
            exitwhen j>=kIPS
            call UnitDamageTarget(u,u,0.,true,true,ATTACK_TYPE_NORMAL,DAMAGE_TYPE_NORMAL,WEAPON_TYPE_WHOKNOWS)
            set j=j+1
        endloop
    endfunction
    
    private function c takes nothing returns boolean
        set kIPS=kIPS+1
        call DisplayTimedTextToPlayer(Player(0),0.,0.,1.,I2S(kIPS))
        return false
    endfunction
    
    private function i takes nothing returns nothing
        local trigger t=CreateTrigger()
        set u=CreateUnit(Player(0),'hfoo',0.,0.,0.)
        call TimerStart(CreateTimer(),1./1000.,true,function p)
        call TriggerRegisterPlayerEvent(t,Player(0),EVENT_PLAYER_END_CINEMATIC)
        call TriggerAddCondition(t,Condition(function c))
        set t=null
    endfunction
endscope


Here you can see that we're simply damaging a unit n times per millisecond. In the DDS tests, we simply apply an empty onDamage handler and observe the difference.

Commentary

There's a few things you can infer from the data.

  • DDS Systems are expensive. Even in the lightest of the three systems, the same FPS was achieved in only about ~40% the number of iterations.
  • Handlers are not so comparatively expensive as we expected. StructuredDD+DamageType received comparable performance to the other two systems which get damagetype in scope.
  • SET_MAX_HP Ability is not so expensive as expected. Since DamageType analysis needs a table instance to deal delayed damage already, SET_MAX_HP can be coupled quite nicely without a huge hit to performance.

Limitations of this Data

This information is far from complete, and certainly isn't enough on its own to declare any certain design decision as bad.

Here's a list of (obvious) things that would need further testing to understand:

  • These tests don't consider the performance difference when an increased number of units are indexed
  • These tests don't consider the performance difference when an increased number of onDamage handlers are added to the stack
  • These tests don't consider the performance difference when rebuilding triggers.

To finish off, I'd like to say that the point of this was to recognize that:

  1. Coupling of SET_MAX_HP with DamageType utilities is reasonable in relation to performance.
  2. Separation of DamageType utilities and Damage Detection is useful, but perhaps not obligatory, in regards to performance.

Edit: Forgot one important thing!

Specs:
Intel Core i5-2410M @ 2.3GHz
6GB DDR3-SDRAM
64-bit Windows 7
 

Attachments

  • baseLineStressTest001.w3x
    19.9 KB · Views: 67
  • lfhStressTest001.w3x
    31.6 KB · Views: 99
  • nestharusStressTest001.w3x
    62 KB · Views: 75
  • sDDstressTest001.w3x
    23.9 KB · Views: 58
  • sddStressTestWithDamageType001.w3x
    32.3 KB · Views: 104
Last edited:
Level 31
Joined
Jul 10, 2007
Messages
6,306
Yes, DDS needs an update for 1 performance boost for spell damage.


The update will increase it's performance by 50% for spell damage.

TriggerRefresh also needs an update, which will help improve DDS speed even more.

There is one other thing I can apply, making timers only run when they have to, which will maximize its speed =).

edit
I'll perform the updates today.

edit
Also, looking_for_help's isn't reliable. He doesn't handle nested events at all -.-. Handling nested events adds some overhead.
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
edit
Also, looking_for_help's isn't reliable. He doesn't handle nested events at all -.-. Handling nested events adds some overhead.

Huh? What do you mean with this? My system uses the function UnitDamageTargetEx to allocate damage from running damage events.

Btw, awsome post @Cokemonkey11

Edit:

There is one other thing I can apply, making timers only run when they have to, which will maximize its speed =).

Why would you run a timer at all if it doesn't have to?
 
Last edited:
Level 31
Joined
Jul 10, 2007
Messages
6,306
You kinda cheated when testing my DDS plugin pack.

You compared StructureDD with no damage type to DDS with everything enabled. If you had disabled Damage Event Modification and Archetype, it woulda been totally fair as they woulda both had the same features. Obviously, the DDS plugin pack with everything enabled is going to have more overhead than StructureDD, just like StructureDD with your damage type thing has more overhead than StructureDD by itself.

You can freely disable whatever plugins you want, lol.

I was also working on possibly catching the end of a damage event with a unit state event for life, but that didn't pan out due to lack of reliability for reals (won't always fire).

edit
In case you were wondering, DDS basic Damage Event plugin is literally just this (updated one)

JASS:
                local UnitIndex prevTarget = targetId_p
                local UnitIndex prevSource = sourceId_p
                local real prevDamage = damage_p

                if (0 == GetEventDamage()) then
                    return
                endif
                
                set targetId_p = GetUnitUserData(GetTriggerUnit())
                set sourceId_p = GetUnitUserData(GetEventDamageSource())
                set damage_p = GetEventDamage()

                call ANY.fire()

                set targetId_p = prevTarget
                set sourceId_p = prevSource
                set damage_p = prevDamage

You'd be hard pressed to beat that

And if you wanted to drop the extra stuff, it'd be this call ANY.fire()

DDS with no plugins is just a damage event response with nothing in it, lol. The only thing that'd compare to that would be an empty trigger condition on a trigger with a damage event registered to it.

edit
Your test also didn't cover multiple damage in same instant, nested damage, and spell vs physical, and damage cases (kill but cancel, kill, regular), and multiple on damage functions (4+)
 
Last edited:

Cokemonkey11

Spell Reviewer
Level 29
Joined
May 9, 2006
Messages
3,531
You kinda cheated when testing my DDS plugin pack.

You compared StructureDD with no damage type to DDS with everything enabled. If you had disabled Damage Event Modification and Archetype, it woulda been totally fair as they woulda both had the same features. Obviously, the DDS plugin pack with everything enabled is going to have more overhead than StructureDD, just like StructureDD with your damage type thing has more overhead than StructureDD by itself.

I didn't know you could disable plugins with DDS. Also, check the graph again. I compared both (notice StructuredDD+DamageType is also there). Also, read my whole post again. You've misunderstood the intention of these tests.

edit
In case you were wondering, DDS basic Damage Event plugin is literally just this (updated one)

JASS:
                local UnitIndex prevTarget = targetId_p
                local UnitIndex prevSource = sourceId_p
                local real prevDamage = damage_p

                if (0 == GetEventDamage()) then
                    return
                endif
                
                set targetId_p = GetUnitUserData(GetTriggerUnit())
                set sourceId_p = GetUnitUserData(GetEventDamageSource())
                set damage_p = GetEventDamage()

                call ANY.fire()

                set targetId_p = prevTarget
                set sourceId_p = prevSource
                set damage_p = prevDamage

You'd be hard pressed to beat that

I've done my tests with what I was interested in. If you want to back up any new claims you should do your own tests.

edit
Your test also didn't cover multiple damage in same instant, nested damage, and spell vs physical, and damage cases (kill but cancel, kill, regular), and multiple on damage functions (4+)

I've mentioned all of this already. You should seriously read my entire post. Also, I kind of knew you didn't read it already, since your first reply was completely irrelevant.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
eh nvm, I didn't see the *obvious* keyword, lol, my bad

Had to read it 3x before I saw that word

edit
Here are my results with updated DDS plugins so far =). I also benched all of your stuff and lfh's stuff.

This is the code I used (just changed how to register for each thing)
JASS:
struct Benchmark extends array
    private static constant integer COUNT = 0
    private static unit u
    private static timer o
    private static real maxLife

    private static method benchmark takes nothing returns nothing
        local integer i = COUNT
        
        call SetWidgetLife(u, maxLife)
        
        loop
            exitwhen 0 == i
            set i = i - 1
            
            call UnitDamageTarget(u, u, 1, true, false, null, DAMAGE_TYPE_UNIVERSAL, null)
        endloop
    endmethod
    
    private static method stop takes nothing returns boolean
        call PauseTimer(o)
        return false
    endmethod

    private static method onInit takes nothing returns nothing
        local trigger t = CreateTrigger()
        
        call TriggerRegisterPlayerEvent(t, Player(0), EVENT_PLAYER_END_CINEMATIC)
        call TriggerAddCondition(t, Condition(function thistype.stop))
        
        set u = CreateUnit(Player(0), 'hfoo', 0, 0, 270)
        set maxLife = GetUnitState(u, UNIT_STATE_MAX_LIFE)
        
        set o = CreateTimer()
        call TimerStart(o, .001, true, function thistype.benchmark)
        
        call PanCameraToTimed(64000, 64000, 0)
    endmethod
    
    private static method onDamage takes nothing returns nothing
    endmethod

    implement DDS
endstruct

And results (haven't updated archetype yet, soo)
Code:
Physical Damage Detection Limit: 9
StructureDD Limit: 77
StructureDD Damage Type Limit: 23
DDS Damage Event Limit: 38
DDS Damage Event Modification Limit: 28

If I just do Trigger Regfresh, that matches StructureDD. I didn't realize StructureDD was essentially a trigger refresher, lol.

So lfh's is by far in last place atm. We'll see how mine does when I get archetype in : \. However, archetype will be little overhead now, so it should only go down a few frames, maybe 25 =).
 
Last edited:
Level 31
Joined
Jul 10, 2007
Messages
6,306
call PanCameraToTimed(64000, 64000, 0)

Maximize fps so that no graphics are being rendered. If it's in the top right corner, all you see is black mask.

The limit is until it drops to 0/1, or some other very low value. If it holds at like 10, I still push it further. Physical Damage Detection slowly dropped from 45 down to 1. I thought it was stable at 30, but then it started dropping again.

Limit score is the score at which it drops.


Also, your stats w/ archetype make me nervous. I was hoping modify/archetype would neatly couple and cause little difference :s

Copy my Archetype algorithm and you'll have as little overhead as possible. It got a score of 19.

JASS:
                    /*
                    *   Calculate spell resistance
                    */
                    call DisableTrigger(Trigger(targetId_p).parent.trigger)
                    
                        set life = GetWidgetLife(u)
                        set scale = GetUnitState(u, UNIT_STATE_MAX_LIFE)
                        call SetWidgetLife(u, scale)
                        call UnitDamageTarget(u, u, -scale/2, false, false, null, DAMAGE_TYPE_UNIVERSAL, null)
                        set scale = 2*(scale - GetWidgetLife(u))/scale
                        if (scale > 1) then
                            set damageOriginal = -damageOriginal*scale
                        else
                            set damageOriginal = -damageOriginal
                        endif
                        call SetWidgetLife(u, life)
                    
                    call EnableTrigger(Trigger(targetId_p).parent.trigger)
 

Cokemonkey11

Spell Reviewer
Level 29
Joined
May 9, 2006
Messages
3,531
Copy my Archetype algorithm and you'll have as little overhead as possible. It got a score of 19.

JASS:
                    /*
                    *   Calculate spell resistance
                    */
                    call DisableTrigger(Trigger(targetId_p).parent.trigger)
                    
                        set life = GetWidgetLife(u)
                        set scale = GetUnitState(u, UNIT_STATE_MAX_LIFE)
                        call SetWidgetLife(u, scale)
                        call UnitDamageTarget(u, u, -scale/2, false, false, null, DAMAGE_TYPE_UNIVERSAL, null)
                        set scale = 2*(scale - GetWidgetLife(u))/scale
                        if (scale > 1) then
                            set damageOriginal = -damageOriginal*scale
                        else
                            set damageOriginal = -damageOriginal
                        endif
                        call SetWidgetLife(u, life)
                    
                    call EnableTrigger(Trigger(targetId_p).parent.trigger)

That's not what I was talking about in terms of coupling.

Anyway, I've included this as an option finally ^^

Also, updated the script including the API. Now DamageType will function properly with the newest version of StructuredDD.
 
Level 10
Joined
Sep 19, 2011
Messages
527
To improve a little bit you should cache some results (although this would be better to do it in StructuredDD).

Also:

call TriggerRegisterEnterRegion(addBracer,reg,null)

Why are you not using null (filter)? If I'm not wrong, you can remove one of the auxiliar functions and use just one :).

request/question:
Will you make an extension to block damage? ºwº
Oh nvm, there is Shield xD.

off:
Hive, why did you remove "@" from the jass code tag :\
 
Last edited:

Cokemonkey11

Spell Reviewer
Level 29
Joined
May 9, 2006
Messages
3,531
To improve a little bit you should cache some results (although this would be better to do it in StructuredDD).

Improve what? Performance? Cache how? hashtables?

I don't think that would help.

Also:

call TriggerRegisterEnterRegion(addBracer,reg,null)

Why are you not using null (filter)? If I'm not wrong, you can remove one of the auxiliar functions and use just one :).

You're right, I'm not sure what I was thinking here. Especially considering the fact that I'm the author of this - http://www.hiveworkshop.com/forums/...on-using-first-group-loop-enumeration-223140/

I'll update this when I get a chance.
 

Cokemonkey11

Spell Reviewer
Level 29
Joined
May 9, 2006
Messages
3,531
No no, just a cupple of global variables so, instead of calling functions to get the source/target/etc. you just use those cached values ;).

But again, this should be part of StructuredDD.

Right, StructuredDD doesn't use any handler itself for performance reasons.

I could make some variables like

JASS:
readonly static unit damageSource
readonly static unit triggerUnit
readonly static real damage

which I could apply to StructuredDD (if I added a handler to it), or to DamageType.

In the first case, the performance would certainly decrease. In the second, the performance gain would be too negligible to constitute such an unintuitive and unnecessary API change.

People who are used to making their own basic damage detection systems already understand what the GetEventDamage(), GetTriggerUnit(), and GetEventDamageSource() natives do. I feel that introducing something new would only degrade the experience.
 
Top