Dismiss Notice
60,000 passwords have been reset on July 8, 2019. If you cannot login, read this.

[System] DummyUnitStack

Discussion in 'JASS Resources' started by Cokemonkey11, Jan 10, 2014.

  1. Cokemonkey11

    Cokemonkey11

    Wurst Reviewer

    Joined:
    May 9, 2006
    Messages:
    3,271
    Resources:
    18
    Tools:
    1
    Maps:
    5
    Spells:
    3
    Tutorials:
    2
    JASS:
    7
    Resources:
    18
    DummyUnitStack

    Preface

    This system is designed to accommodate spell designers who want to use spam-loads of effects, are trying to use dummy casters but static global ones aren't behaving properly, and anything else you can think of for having pre-loaded dummy units on the fly.

    Design Explanation

    A dummy unit stack is a simple concept that has been executed before. Indeed, caching dummy units was a primary design paradigm in Vexorian's xe. But what if you don't want all that extra garbage? DummyUnitStack is as simple as it gets. It preloads you some units, you request them when you want them, and release them when you're done. DummyUnitStack isn't safe - you have to obey some simple rules when you use it, and it isn't perfect - there are some corners cut in its design in order to squeeze every bit of performance out possible.

    The FPS drop associated with unit counts is complex, and depends on many factors including model poly counts, animation complexity, quantity on screen, and quantity on map. By preventing some of these factors we create a ton of units without FPS drop.

    Limitations:

    • This resource requires vJass to compile.
    • This resource is not idiot-proof. You must not release a unit to the stack that doesn't belong there, and you must destroy anything attached to the dummy handle before releasing it yourself. Some attributes like owner, scale, fly height, and position are reset.
    • * The stack design pattern implies that getting a unit handle might have an effect attached to it whose death animation is still playing (you can see this in the test map) - at some point I'd like to write a linked-list implementation which removes the problem, and compare the performance. My instinct is that the linked list would perform at most 10% worse, and the benefits would be worth-while.
    • The unit facing angles are not stored in the system and cannot be changed instantly, so for large effects whose angle is important, this can lead to bad visual effects.

    API

    local unit u = DummyUnitStack.get()


    call DummyUnitStack.release(u)


    No more explanation necessary. If you want to fiddle with the tweaker settings, you're on your own :)

    The script
    Code (vJASS):


    //*
    //* DummyUnitStack is a library designed to give spell developers immediate
    //* access to any number of dummy units at any time by hacking around the
    //* engine limitations associated with CreateUnit() - essentially, all we do
    //* is create units in a deferred manner which not only caches dummies, but
    //* also prevents simultaneous unit instantiations on a more global scale.
    //* In other words, we create a few units, very often, until we have a lot.
    //*
    //* The public API for DummyUnitStack is simple - just get and release:
    //*     local unit u = DummyUnitStack.get()
    //*     call DummyUnitStack.release(u)
    //*
    //* The more complex part is the preloading options available. For most cases
    //* the default options will more than suffice, but if you really want to get
    //* aggressive with your optimizations, you can do some maths yourself and
    //* decide on every limit used in the system.
    //*
    //* Enjoy,
    //*     -Cokemonkey11
    //*
    library DummyUnitStack
        // Just a shim for the API, the struct is not actually instanciated.
        struct DummyUnitStack extends array
       
       
            //*********************************************************************
            //* CUSTOMIZABLE SECTION
            //*********************************************************************
           
            // This bit is important - make sure this refers to the dummy unit
            // in the test map
            private static constant integer DUMMY_UNIT_ID='u000'
           
            // Whenever the preloading process turns on, we set a minimum number
            // of units to load, to prevent turning the system on and off too
            // often - here you can choose the minimum count (default 10)
            private static constant integer MIN_PRELOAD_BLOCK_SIZE=10
           
            // This value is the initialization preload value. Higher numbers
            // will increase map load time, but give the best performance. The
            // ideal value should be something between the number of dummies used
            // in the first few minutes, and the maximum number of dummies used
            // in theory. Additionally, you can turn the feature off entirely.
            // (default 200)
            private static constant integer PRELOAD_INIT_COUNT=200
            private static constant boolean DO_PIC=true
           
            // When a series of dummies are retrived using .get(), the stack size
            // falls and approaches 0 - with this setting, we can turn on the
            // preloader whenever the stack size falls to an arbitrary number.
            // Again, this feature can be turned off. (default 20)
            private static constant integer DYNAMIC_DEFERRED_PRELOAD_COUNT=20
            private static constant boolean DO_DDPC=true
           
            // When the map starts, the preloader turns on right away, preloading
            // more units - this can be tweaked to reduce loading time without
            // losing dummies available at the beginning of the game. (default 20)
            private static constant integer INITIAL_DEFERRED_PRELOAD_COUNT=20
            private static constant boolean DO_IDPC=true
           
            // If a dummy unit is released and the stack exceeds this size, the unit
            // will instead be removed.
            private static constant integer MAX_PRELOADED_UNITS=5000
            private static constant boolean DO_MPU=true
           
            // Here we have another "hack-like" design choice. The developer must
            // specify an (x,y) coordinate pair where dummy units can be moved on
            // release - we do this because there is a bug associaed with ShowUnit()
            // and the locust ability. :(
            private static constant real SAFE_LOC_X=-3580.
            private static constant real SAFE_LOC_Y=3460.
           
            // Normally a not-so-useful tweaking variable, clock period in dummy unit
            // stack is actually quite powerful because we're not relying on any
            // kind of smooth movement for releasing/getting. (default 1./60.)
            private static constant real CLOCK_PERIOD=1./60.
           
            //*********************************************************************
            //* END CUSTOMIZABLE SECTION
            //*********************************************************************
           
           
            private static unit array dummyStack
            private static integer stackIndex=-1
            private static integer deferredTodoCount=0
            private static timer clock=CreateTimer()
           
            //* Private method used for creating a unit by the system
            private static method add takes boolean preloading returns unit
                set bj_lastCreatedUnit = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE),thistype.DUMMY_UNIT_ID,thistype.SAFE_LOC_X,thistype.SAFE_LOC_Y,0.)
                call UnitAddAbility(bj_lastCreatedUnit,'Aloc')
                call UnitAddAbility(bj_lastCreatedUnit,'Arav')
                if preloading then
                    set thistype.stackIndex=thistype.stackIndex+1
                    set thistype.dummyStack[thistype.stackIndex]=bj_lastCreatedUnit
                else
                    return bj_lastCreatedUnit
                endif
                return null
            endmethod
           
            //* Private periodic method used for preloading
            private static method deferredPreloader takes nothing returns nothing
                set thistype.deferredTodoCount=thistype.deferredTodoCount-1
                call thistype.add(true)
                if thistype.deferredTodoCount<1 then
                    call PauseTimer(thistype.clock)
                endif
            endmethod
           
            //* Public method used to get a dummy. Use this at any time, as much
            //* as you want.
            public static method get takes nothing returns unit
                local unit result
                if thistype.stackIndex!=-1 then
                    set thistype.stackIndex=thistype.stackIndex-1
                    set result=thistype.dummyStack[thistype.stackIndex+1]
                else
                    set result=thistype.add(false)
                endif
                static if DO_DDPC then
                    if thistype.stackIndex<(thistype.DYNAMIC_DEFERRED_PRELOAD_COUNT-1) and thistype.deferredTodoCount<1 then
                        set thistype.deferredTodoCount=thistype.MIN_PRELOAD_BLOCK_SIZE
                        call TimerStart(thistype.clock,thistype.CLOCK_PERIOD,true,function thistype.deferredPreloader)
                    endif
                endif
                return result
            endmethod
           
            //* Public method for releasing a dummy. Make sure you don't release
            //* units that don't come from DummyUnitStack. Make sure you destroy
            //* effects and anything else attached the unit because its handle
            //* will be RECYCLED.
            public static method release takes unit u returns nothing
                static if DO_MPU then
                    if thistype.stackIndex>=thistype.MAX_PRELOADED_UNITS then
                        call RemoveUnit(u)
                        return
                    endif
                endif
                call SetUnitFlyHeight(u,0.,0.)
                call SetUnitScale(u,1.,1.,1.)
                call SetUnitOwner(u,Player(PLAYER_NEUTRAL_PASSIVE),true)
                call SetUnitX(u,thistype.SAFE_LOC_X)
                call SetUnitY(u,thistype.SAFE_LOC_Y)
                set thistype.stackIndex=thistype.stackIndex+1
                set thistype.dummyStack[thistype.stackIndex]=u
            endmethod
           
            //* herpderp #programming
            private static method onInit takes nothing returns nothing
                local integer index=1
                static if DO_PIC then
                    loop
                        exitwhen index>thistype.PRELOAD_INIT_COUNT
                        call thistype.add(true)
                        set index=index+1
                    endloop
                endif
                static if DO_IDPC then
                    set thistype.deferredTodoCount=thistype.INITIAL_DEFERRED_PRELOAD_COUNT
                    call TimerStart(thistype.clock,thistype.CLOCK_PERIOD,true,function thistype.deferredPreloader)
                endif
            endmethod
        endstruct
    endlibrary

     


    Installation
    1. Copy the dummy unit from the test map
    2. Copy this script in its entirety into an empty trigger (custom script) and name it DummyUnitStack.
    3. (If necessary) adjust the 'CUSTOMIZABLE SECTION' to your liking.

    Change Log

    2014.01.10 - Initial submission to Hive: Jass Resources: Submissions

    Special Thanks:

    • -Kobas- for creating a map submission template, which turned out useful for system submissions as well.
    • Vexorian for developing JassHelper. Without vJass, I almost certainly would not still be scripting for wc3. He also designed xe and the dummy.mdx used in this map, which I appreciate, etc.
    • The various developers of JNGP including PitzerMike and MindworX. Integrating JassHelper and TESH is a godsend.
     

    Attached Files:

    Last edited: Jan 12, 2014
  2. TriggerHappy

    TriggerHappy

    Code Moderator

    Joined:
    Jun 23, 2007
    Messages:
    3,793
    Resources:
    22
    Spells:
    11
    Tutorials:
    2
    JASS:
    9
    Resources:
    22
    I like all the options like preloading via a timer after map init. :thumbs_up:
     
  3. Anachron

    Anachron

    Joined:
    Sep 9, 2007
    Messages:
    6,220
    Resources:
    66
    Icons:
    49
    Packs:
    2
    Tools:
    1
    Maps:
    3
    Spells:
    9
    Tutorials:
    1
    JASS:
    1
    Resources:
    66
    Yes but it's sad that you don't change the angle of the units.
    So this cannot be used by any missile engine so far.

    You should not forget that it's impossible to make an unit face to a specific angle instantly. It's always with delay. That's why Nes' built his library to calculate which is the unit with the closest angle from what is searched.
     
  4. TriggerHappy

    TriggerHappy

    Code Moderator

    Joined:
    Jun 23, 2007
    Messages:
    3,793
    Resources:
    22
    Spells:
    11
    Tutorials:
    2
    JASS:
    9
    Resources:
    22
  5. Cokemonkey11

    Cokemonkey11

    Wurst Reviewer

    Joined:
    May 9, 2006
    Messages:
    3,271
    Resources:
    18
    Tools:
    1
    Maps:
    5
    Spells:
    3
    Tutorials:
    2
    JASS:
    7
    Resources:
    18
    Yes, I forgot about this limitation - I'll add it to the original post. It can still be used for missile engine (I do), but for large effects it will look quite bad.

    I didn't know Nes made that. Did it perform well? (eg, did nestharus keep making it or get rid of it)

    Yes, dummy.mdl is used in the test map, but this will not allow you to (easily) change the facing angle instantly. Also, the angle of attack animation set is limited to 180 degrees of rotation.
     
  6. Anachron

    Anachron

    Joined:
    Sep 9, 2007
    Messages:
    6,220
    Resources:
    66
    Icons:
    49
    Packs:
    2
    Tools:
    1
    Maps:
    3
    Spells:
    9
    Tutorials:
    1
    JASS:
    1
    Resources:
    66
    Triggerhappy, I am talking about xyAngle, not zAngle.
    Cokemonkey11, Yes, the resource was quite good, that's why it was approved and constantly updated. You can download it from his signature.
     
  7. Maker

    Maker

    Joined:
    Mar 6, 2006
    Messages:
    9,174
    Resources:
    17
    Maps:
    2
    Spells:
    14
    Tutorials:
    1
    Resources:
    17
    You return the units but don't null the locals. Does that not leak?
     
  8. Cokemonkey11

    Cokemonkey11

    Wurst Reviewer

    Joined:
    May 9, 2006
    Messages:
    3,271
    Resources:
    18
    Tools:
    1
    Maps:
    5
    Spells:
    3
    Tutorials:
    2
    JASS:
    7
    Resources:
    18
    Do you mean the release method should have
    set u = null
    at the end, and add method should have
    set u = null
    above return null ? That might be leaking, I'm not sure to be honest.
     
  9. PurgeandFire

    PurgeandFire

    Code Moderator

    Joined:
    Nov 11, 2006
    Messages:
    7,429
    Resources:
    18
    Icons:
    1
    Spells:
    4
    Tutorials:
    9
    JASS:
    4
    Resources:
    18
    It does prevent the handle ID from being recycled (the reference count will continue to go up), but it shouldn't matter since you're recycling the dummies. It only matters when you kill/remove the unit (when MAX_PRELOADED_UNITS is reached). If you want to fix it, you would just use globals for "result" and "u" instead of the locals.
     
  10. Nestharus

    Nestharus

    Joined:
    Jul 10, 2007
    Messages:
    6,146
    Resources:
    8
    Spells:
    3
    Tutorials:
    4
    JASS:
    1
    Resources:
    8
    Dummy organized units by angle and returned the unit for an angle in O(1) ;). The behavior is similar to a minimum binary heap, in which the angle with the least amount of units is at the top, but unlike a binary heap, it does all of its operations in O(1) instead of O(log n).

    However, I'm just happy that people are submitting resources again :)
     
  11. Cokemonkey11

    Cokemonkey11

    Wurst Reviewer

    Joined:
    May 9, 2006
    Messages:
    3,271
    Resources:
    18
    Tools:
    1
    Maps:
    5
    Spells:
    3
    Tutorials:
    2
    JASS:
    7
    Resources:
    18
    Would the following not also suffice?

    Code (vJASS):

            public static method release takes unit u returns nothing
                static if DO_MPU then
                    if thistype.stackIndex>=thistype.MAX_PRELOADED_UNITS then
                        call RemoveUnit(u)
                        set u = null //this line was added
                        return
                    endif
                endif
                call SetUnitFlyHeight(u,0.,0.)
                call SetUnitScale(u,1.,1.,1.)
                call SetUnitOwner(u,Player(PLAYER_NEUTRAL_PASSIVE),true)
                call SetUnitX(u,thistype.SAFE_LOC_X)
                call SetUnitY(u,thistype.SAFE_LOC_Y)
                set thistype.stackIndex=thistype.stackIndex+1
                set thistype.dummyStack[thistype.stackIndex]=u
                set u = null //this line was added
            endmethod
     


    along with

    Code (vJASS):

            private static method add takes boolean preloading returns unit
                local unit u=CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE),thistype.DUMMY_UNIT_ID,thistype.SAFE_LOC_X,thistype.SAFE_LOC_Y,0.)
                call UnitAddAbility(u,'Aloc')
                call UnitAddAbility(u,'Arav')
                if preloading then
                    set thistype.stackIndex=thistype.stackIndex+1
                    set thistype.dummyStack[thistype.stackIndex]=u
                else
                    return u
                endif
                set u = null //this line added
                return null
            endmethod
     


    I'm not really interested in the time complexity of the system (try analyzing DummyUnitStack), I'm more interested in the amortized and maximum comparisons between the system and the baseline (CreateUnit).

    In other words, how much faster is your system, in practice, than CreateUnit() ? How about compared to DummyUnitStack?

    Also, if you use UnitIndexer, it's worthless to me
     
  12. PurgeandFire

    PurgeandFire

    Code Moderator

    Joined:
    Nov 11, 2006
    Messages:
    7,429
    Resources:
    18
    Icons:
    1
    Spells:
    4
    Tutorials:
    9
    JASS:
    4
    Resources:
    18
    For .release(), it is fine. For .add(), it doesn't null it before
    return u
    , so the reference count will go up.
     
  13. Cokemonkey11

    Cokemonkey11

    Wurst Reviewer

    Joined:
    May 9, 2006
    Messages:
    3,271
    Resources:
    18
    Tools:
    1
    Maps:
    5
    Spells:
    3
    Tutorials:
    2
    JASS:
    7
    Resources:
    18
    Are you sure the reference is not simply sent to the parent function? I mean, you're positive this would leak?

    I'm not trying to argue, just trying to understand before I fix it so I don't need to do multiple edits.

    Thanks,
     
  14. Bribe

    Bribe

    Joined:
    Sep 26, 2009
    Messages:
    8,381
    Resources:
    25
    Maps:
    3
    Spells:
    10
    Tutorials:
    3
    JASS:
    9
    Resources:
    25
    Yes, the local variable set to u will leak. Any time a handle is followed by = it will increase the reference count for an agent. Parameters thankfully do not cause this problem.

    You must use a global variable like bj_lastCreatedUnit or something like that.
     
  15. Cokemonkey11

    Cokemonkey11

    Wurst Reviewer

    Joined:
    May 9, 2006
    Messages:
    3,271
    Resources:
    18
    Tools:
    1
    Maps:
    5
    Spells:
    3
    Tutorials:
    2
    JASS:
    7
    Resources:
    18
    Thanks guys, I've updated it.

    Can someone check the release method and verify for me that this won't cause any problems if someone does:

    Code (vJASS):

    call DummyUnitStack.release(u)
    call DestroyEffect(AddSpecialEffectTarget(path,u,"overhead"))
     


    In other words, does the reference passed as an argument to release get nulled, or is it copied inernally?
     
  16. Maker

    Maker

    Joined:
    Mar 6, 2006
    Messages:
    9,174
    Resources:
    17
    Maps:
    2
    Spells:
    14
    Tutorials:
    1
    Resources:
    17
    Like Bribe mentioned, parameters don't need to be nulled.
     
  17. Cokemonkey11

    Cokemonkey11

    Wurst Reviewer

    Joined:
    May 9, 2006
    Messages:
    3,271
    Resources:
    18
    Tools:
    1
    Maps:
    5
    Spells:
    3
    Tutorials:
    2
    JASS:
    7
    Resources:
    18
    So, the line I added in the release method not only doesn't cause problems, but also isn't necessary to prevent a leak. Correct?
     
  18. Bribe

    Bribe

    Joined:
    Sep 26, 2009
    Messages:
    8,381
    Resources:
    25
    Maps:
    3
    Spells:
    10
    Tutorials:
    3
    JASS:
    9
    Resources:
    25
    Yep, it's completely useless to null parameters unless you had set them to something else.
     
  19. Cokemonkey11

    Cokemonkey11

    Wurst Reviewer

    Joined:
    May 9, 2006
    Messages:
    3,271
    Resources:
    18
    Tools:
    1
    Maps:
    5
    Spells:
    3
    Tutorials:
    2
    JASS:
    7
    Resources:
    18
    Okay thanks, I've removed those two lines which nulled the function argument. Everything should be good now.
     
  20. TriggerHappy

    TriggerHappy

    Code Moderator

    Joined:
    Jun 23, 2007
    Messages:
    3,793
    Resources:
    22
    Spells:
    11
    Tutorials:
    2
    JASS:
    9
    Resources:
    22
    I suggest approval in replacement of Dummy Reuser.

    I also think the default value for PRELOAD_INIT_COUNT should be much less, like 50 100.
     
    Last edited: Feb 19, 2014