[Snippet] Dummy Reuser

Level 14
Joined
Nov 18, 2007
Messages
1,084
This snippet is intended to allow different unit-type dummy units to be recycled.

The documentation is inside the code. A test-map is also attached with a simple spell showing the recycling in action.

JASS:
//==========================================================================================
// DummyReuser v2.01 by watermelon_1234
//******************************************************************************************
// A textmacro for structs designed to reuse/recycle dummy units of any type.
//
// This is mostly aimed for reusing dummy units with models that can play multiple animations,
// allowing a specific animation to be played unlike special effects which can only play
// birth, standing, or death animations consistently.
//##########################################################################################
//
// Instructions:
//  To first use this, you must declare some kind of struct. Once you've declared the struct,
//  just add the following line:
//
//  //! runtextmacro DummyReuser("DUMMY_UNIT_TYPEID")
//
//  where DUMMY_UNIT_TYPEID is the unit-type id of the dummy.
//
// API:
//  * static method get takes player owner, real x, real y, real facing returns unit *
//  Returns a dummy unit that you can use for a player at map coordinates x and y.
//  Note that facing is in degrees like CreateUnit.
//
//  * static method release takes unit u returns nothing *
//  Releases a dummy unit for recycling.
//
//##########################################################################################
//
// Importing:
//  1. Copy this trigger.
//  2. Configure the system to your liking
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
// Credits:
//  - azlier for improvements on my earlier code
//  - Bribe for more improvements
//  - PurgeandFire111 for showing me how to make structs efficient, though it no longer applies
//  - Anachron for CustomAura which gave me an idea to use a textmacro for this
//
//==========================================================================================

library DummyReuser

    globals
        // The owner for the dummy unit when it's in storage
        private constant player     DUMMY_STORAGE_OWNER     = Player(15) 
        // Max number of dummies to be stored
        private constant integer    DUMMY_MAX_COUNT         = 18       
    endglobals  
    
//==========================================================================================
//                              END OF CONFIGURATION
//==========================================================================================
    
    // Prepares a dummy unit for use and returns it
    public function Get takes unit u, player owner, real x, real y, real facing returns unit
        call SetUnitOwner(u, owner, true)
        call SetUnitFacing(u, facing)              
        call PauseUnit(u, false)
        call ShowUnit(u, true)   
        if UnitRemoveAbility(u, 'Aloc') then
            call UnitAddAbility(u, 'Aloc')
        endif
        call SetUnitX(u, x)
        call SetUnitY(u, y)
        return u
    endfunction
    
    // Returns true if a dummy unit was stored
    public function Store takes unit u, integer id, integer count returns boolean
        // Safety check to prevent silly mistakes
        if not IsUnitType(u, UNIT_TYPE_DEAD) and GetUnitTypeId(u) == id and count < DUMMY_MAX_COUNT then
            call ShowUnit(u, false) // Using ShowUnit as that's the most consistent way of hiding any dummy unit
            call SetUnitOwner(u, DUMMY_STORAGE_OWNER, false)          
            call PauseUnit(u, true) 
            return true   
        endif
        debug call BJDebugMsg("DummyReuser: Removed dummy unit either due to max limit or wrong dummy unit")
        call RemoveUnit(u)
        return false
    endfunction
endlibrary

//! textmacro DummyReuser takes DUMMY_UNIT_TYPEID
    private static unit array dr_stack
    private static integer dr_count = 0    
    
    static method get takes player owner, real x, real y, real facing returns unit
        if dr_count == 0 then
            return CreateUnit(owner, $DUMMY_UNIT_TYPEID$, x, y, facing)
        endif        
        set dr_count = dr_count - 1   // The stack is zero-based 
        return DummyReuser_Get(dr_stack[dr_count], owner, x, y, facing)
    endmethod

    static method release takes unit u returns nothing 
        if DummyReuser_Store(u, $DUMMY_UNIT_TYPEID$, dr_count) then  
            set dr_stack[dr_count] = u
            set dr_count = dr_count + 1
        endif
    endmethod
//! endtextmacro
v2.01

  • Made the textmacro produce less code
  • No longer stores dummy in a different place
  • Globals are no longer public
  • Added constant that can specify a max number of dummy units that can be recycled
  • Supports dummy units with Locust (again)

v2.00

  • New way of getting and recycling dummy units.
  • No longer supports dummy units that don't have Locust.
  • Updated test map to use xemissile instead of xeprojectile.
  • No longer uses UnitAlive.

v1.01b


  • Used a more efficient way of allocating and deallocating the struct.

v1.01a


  • No longer uses onDestroy.
  • Macro uses static if for locust.

v1.01

  • Changed system to template for structs recycling dummy units.

v1.00

  • Released.


Additional Credits:

  • Vexorian for xe system, TimerUtils, and BoundSentinel
  • EotA by DarnYak whose spell inspired the test-map one.
 

Attachments

  • DummyReuserTest.w3x
    42.5 KB · Views: 195
  • DummyReuserTest2.01.w3x
    44.4 KB · Views: 129
Last edited:
Level 8
Joined
Oct 3, 2008
Messages
367
This just seems... overcomplicated.

And why would you EVER need more than one dummy unit type?

I think something like this might work better.

JASS:
library DummyRecycler

globals
    private constant integer DUMMY_TYPE = 'hfoo'
    //The unit type that dummies should utilize.
    
    private          player HOLDER_PLAYER = Player(15)
    //The player that holds dummies when they are not in use.
endglobals

struct RecycledDummy

    unit unit
    
    static method create takes player p, real x, real y, real facing returns thistype
        local thistype this = thistype.allocate()
        if unit == null then
            set unit = CreateUnit(p, DUMMY_TYPE, x, y, facing)
        else
            call ShowUnit(unit, true)
            call PauseUnit(unit, false)
            call SetUnitOwner(unit, p, true)
            call SetUnitX(unit, x)
            call SetUnitY(unit, y)
            call SetUnitFacing(unit, facing)
        endif
        return this
    endmethod
    
    method destroy takes nothing returns nothing
        call ShowUnit(unit, true)
        call PauseUnit(unit, true)
        call SetUnitOwner(unit, HOLDER_PLAYER, false)
    endmethod
    
endstruct

endlibrary

Here's an example of use.

JASS:
function Icthys takes nothing returns nothing
    local RecycledDummy d = RecycledDummy.create(Player(0), 0, 0, 0)
    call IssueTargetOrder(d.unit, "blah", GetTriggerWidget()) //Or something...
    //...
    //And later:
    call d.destroy()
endfunction
 
Level 14
Joined
Nov 18, 2007
Messages
1,084
The main reason I made this was when I wanted to have "special effects" play a certain animation; the only reason stopping me from using special effects is that they can't play animations. An example of this is the spell I provided in the test-map.

Your idea looks much better than mine though.
Maybe I'll try to make it so reuser can be extended to provide the multiple unit type support? Or something like that.
 
Level 14
Joined
Nov 18, 2007
Messages
1,084
Well if you guys don't seem to find any use for this then feel free to graveyard it.
I just wish I had these opinions presented in this thread before I put this here.

Edit:
Just before I completely give up on this, how about this?

JASS:
library NewDummyReuser
    globals
        public constant player DUMMY_STORAGE_OWNER     = Player(15) 
        // Coordinates for the storage of all dummy units
        public constant real   DUMMY_STORAGE_X         = 0
        public constant real   DUMMY_STORAGE_Y         = 0  
    endglobals    
endlibrary

//! textmacro NewDummyReuser
    unit dummy 

    static method create takes player owner, real x, real y, real facing returns thistype
        local thistype this = thistype.allocate()
        if .dummy == null then
            set .dummy = CreateUnit(owner,DUMMY_UNIT_TYPEID,x,y,facing)
        else
            call SetUnitOwner(.dummy,owner,true)
            call SetUnitFacing(.dummy,facing)
            call SetUnitX(.dummy,x)
            call SetUnitY(.dummy,y)
            call PauseUnit(.dummy,false)
            call ShowUnit(.dummy,true)
        endif
        return this
    endmethod

    method onDestroy takes nothing returns nothing
        if GetWidgetLife(.dummy) > 0.405 then
            call SetUnitOwner(.dummy,NewDummyReuser_DUMMY_STORAGE_OWNER,false)
            call SetUnitX(.dummy,NewDummyReuser_DUMMY_STORAGE_X)
            call SetUnitY(.dummy,NewDummyReuser_DUMMY_STORAGE_Y)
            call PauseUnit(.dummy,true)
        else
            set .dummy = null
        endif
    endmethod
//! endtextmacro
To use it, you need to create a struct and then just have //! runtextmacro NewDummyReuser() inside the struct.
In addition, you'll need to have a global constant integer DUMMY_UNIT_TYPEID to define what unit type you're using.
The script would basically be a template for a struct that can recycle dummy units.
It's not really mine anymore though since it's basically a copy of azlier's struct.
 
Last edited:
Level 38
Joined
Sep 26, 2009
Messages
8,477
UnitAlive far out-performs GetWidgetLife / IsUnitType.

busterkomo, when using GetWidgetLife it should be checked against 0.405; once that value is reached or overshot, the unit is registered as 'dead' by the game.

The problem with recycling dummies is that the unit-facing is not instant. I should write a script that collects dummies and stores them according to various facing-angles. The more dummies are in the stack, the more acute the angles would get.
 
Level 18
Joined
Feb 4, 2009
Messages
1,315
having multiple dummy types is really important
you can't recycle special effects but they leak a lot of memory if destroyed (at least it was like that when gexxo @ inwarcraft.de made a few tests years ago)
so if you want to have different special effects and don't want to leak memory having different dummy types is the only way
also it is faster to just hide/show dummys instead of creating and destroying special effects
i'd base it of special effect string instead of unit id though
(other things are dummys with different movement types, flying units fuck up on cliff edges etc.)

and if you really want to destroy special effects you will have to add a delay before putting the dummy back on the stack because special effects take a few seconds to be destroyed
so if you allocate a new dummy it might still have the old dying special effect to it
another case would be the special effect death animation not shown properly

the facing can be solved with SetUnitLookAt and an appropiate dummy
 
Level 14
Joined
Nov 18, 2007
Messages
1,084
onDestroy is wicked and makes it so you can't run this in an array struct.
So just change onDestroy to destroy and deallocate the struct there?

The macro shouldn't request .allocate(), either, it should have its own allocator.
I'm not really sure what you want here; do you want me to make a new static method that's not create?
 
Level 14
Joined
Nov 18, 2007
Messages
1,084
I think more along the lines of...

static method releaseDummy takes unit whichUnit returns nothing
// stuff
endmethod

static method newDummy takes <args> returns unit
// stuff
endmethod
I'm sorry but I'm still failing to understand what you want me to do. :\

The current recycling method depends on struct instances being allocated.
Do you want me to try doing this a different way?

Also I tried updating the code with the other suggestions.
 
Bribe is probably referring to making it look like this:
JASS:
//! textmacro DummyReuser takes DUMMY_UNIT_TYPEID,HAS_LOCUST
    private static integer instanceCount = 0
    private static thistype recycle = 0
    private thistype recycleNext
    unit dummy 

    static method create takes player owner, real x, real y, real facing returns thistype
        local thistype this 
        if recycle == 0 then //allocation
            set instanceCount = instanceCount + 1
            set this          = instanceCount
        else
            set this = recycle
            set recycle = recycle.recycleNext
        endif
        if .dummy == null then
            set .dummy = CreateUnit(owner,$DUMMY_UNIT_TYPEID$,x,y,facing)
        else
            call SetUnitOwner(.dummy,owner,true)
            call SetUnitFacing(.dummy,facing)
            call SetUnitX(.dummy,x)
            call SetUnitY(.dummy,y)
            call PauseUnit(.dummy,false)
            call ShowUnit(.dummy,true)
            static if $HAS_LOCUST$ then
                if UnitRemoveAbility(.dummy,'Aloc') then
                    call UnitAddAbility(.dummy,'Aloc')
                endif
            endif
        endif
        return this
    endmethod

    method destroy takes nothing returns nothing
        if UnitAlive(.dummy) then
            call SetUnitOwner(.dummy,DummyReuser_DUMMY_STORAGE_OWNER,false)
            call SetUnitX(.dummy,DummyReuser_DUMMY_STORAGE_X)
            call SetUnitY(.dummy,DummyReuser_DUMMY_STORAGE_Y)
            call PauseUnit(.dummy,true)
            call ShowUnit(.dummy,false)
        else
            set .dummy = null
        endif
        set recycleNext = recycle //deallocation
        set recycle = this
    endmethod
//! endtextmacro

In regards to this tutorial:
http://www.hiveworkshop.com/forums/...ls-280/coding-efficient-vjass-structs-187477/

It is slightly more efficient. It always negligible, but in this case it is pretty nice since dummies are usually created often. :)
 
Level 14
Joined
Nov 18, 2007
Messages
1,084
Bribe is probably referring to making it look like this:
JASS:
//! textmacro DummyReuser takes DUMMY_UNIT_TYPEID,HAS_LOCUST
    private static integer instanceCount = 0
    private static thistype recycle = 0
    private thistype recycleNext
    unit dummy 

    static method create takes player owner, real x, real y, real facing returns thistype
        local thistype this 
        if recycle == 0 then //allocation
            set instanceCount = instanceCount + 1
            set this          = instanceCount
        else
            set this = recycle
            set recycle = recycle.recycleNext
        endif
        if .dummy == null then
            set .dummy = CreateUnit(owner,$DUMMY_UNIT_TYPEID$,x,y,facing)
        else
            call SetUnitOwner(.dummy,owner,true)
            call SetUnitFacing(.dummy,facing)
            call SetUnitX(.dummy,x)
            call SetUnitY(.dummy,y)
            call PauseUnit(.dummy,false)
            call ShowUnit(.dummy,true)
            static if $HAS_LOCUST$ then
                if UnitRemoveAbility(.dummy,'Aloc') then
                    call UnitAddAbility(.dummy,'Aloc')
                endif
            endif
        endif
        return this
    endmethod

    method destroy takes nothing returns nothing
        if UnitAlive(.dummy) then
            call SetUnitOwner(.dummy,DummyReuser_DUMMY_STORAGE_OWNER,false)
            call SetUnitX(.dummy,DummyReuser_DUMMY_STORAGE_X)
            call SetUnitY(.dummy,DummyReuser_DUMMY_STORAGE_Y)
            call PauseUnit(.dummy,true)
            call ShowUnit(.dummy,false)
        else
            set .dummy = null
        endif
        set recycleNext = recycle //deallocation
        set recycle = this
    endmethod
//! endtextmacro
In regards to this tutorial:
http://www.hiveworkshop.com/forums/...ls-280/coding-efficient-vjass-structs-187477/

It is slightly more efficient. It always negligible, but in this case it is pretty nice since dummies are usually created often. :)
Oh wow, that's really interesting. I'll have to look into that more to actually understand it.

Thanks! =D

Edit: Updated with that code.
 
Level 18
Joined
Feb 4, 2009
Messages
1,315
later I might suggest a bit more complex recycling, having to do with unit-facing which, to my knowledge, no one else has tried before.

because it's not a problem
http://www.wc3c.net/showthread.php?t=105830
(I am posting this thread all over the hive all the time x)
I also posted a working dummy somewhere on hive so you don't have to make one yourself

but a problem is this:
destroying a special effect leaks much more than creating and removing a unit
so it would make sense to recycle these instead (or dummys with effects attached to them)
the create-function could take an additional parameter which would be the effect string or unit type
just make sure the dummy can rest for a few seconds so it can finish its death animation if you go for the second option
 
so it would make sense to recycle these instead (or dummys with effects attached to them)
the create-function could take an additional parameter which would be the effect string or unit type
just make sure the dummy can rest for a few seconds so it can finish its death animation if you go for the second option

The problem is that most effects cannot be recycled. Most have a default single loop instead of repeating, so that when they finish their animation, they are done until you recreate them. You may say that you can play the animation again, but the thing is that you are setting the dummy unit's animation, not the effect attached to it. :( The only other solution is dummies with the effect of the model already, which can work but is map-specific. D;

destroying a special effect leaks much more than creating and removing a unit

Since when does destroying it leak memory? :eek:
 
Level 18
Joined
Feb 4, 2009
Messages
1,315
The only other solution is dummies with the effect of the model already, which can work but is map-specific. D;

that's the second option I was talking about
maybe implement both so you have advantages of both?

Since when does destroying it leak memory? :eek:

a long time ago someone named gexo made a few benchmarks at inwarcraft.de

SetUnitLookAt has its flaws as that thing pointed out. The problem is that you need to use unit-facing for homing.

I don't know if SetUnitLookAt handles unit-z-height properly but if it does it would cut the requirement of calculating the z-angle if you make an invisible missile one frame ahead of the visible missile

but if it does not it does not matter
what about combining both SetUnitAnimationByIndex for frequent z-angle changes and SetUnitLookAt for less frequent x/y-angle changes?
 
Level 9
Joined
May 27, 2009
Messages
494
hmm seems dead though but i'd like to ask why some dummy models especially models like this:
Units\Demon\Infernal\InfernalBirth.mdl or rework of that model seems to bug, when I try to recycle those dummies, their model effect (or the falling thingy) will not be shown or in easy cases, it won't play its animation but in normal create unit function, it would play its animation though.

is it advisable to recycle this kinds of units?
 
hmm seems dead though but i'd like to ask why some dummy models especially models like this:
Units\Demon\Infernal\InfernalBirth.mdl or rework of that model seems to bug, when I try to recycle those dummies, their model effect (or the falling thingy) will not be shown or in easy cases, it won't play its animation but in normal create unit function, it would play its animation though.

is it advisable to recycle this kinds of units?

That is because for those kinds of dummies, they only show their birth animation upon creation. You can still recycle those kinds of dummies, but when you use it, just use call SetUnitAnimation(dummy, "birth") instead of recreating it.
 
Level 38
Joined
Sep 26, 2009
Messages
8,477
I like this approach, it doesn't require the user to write a struct, keeps the generated code extremely short, has a "max dummies" limit and supports units who do not have locust:

JASS:
library DummyReuser
/*
    Example use:
    
        //! runtextmacro DummyReuser("Footman", "'hfoo'")
        ...
        local unit myFootman = Footman_Get(player, x, y, facing)
        ...
        call Footman_Release(myFootman)
*/
    globals
        //The owner for the dummy unit when it's in storage
        private player p = Player(15)
        //The max amount of dummies which can be stored per type
        private constant integer MAX = 16
    endglobals
    
    function DummyReuser_Get takes player owner, unit u returns unit
        call SetUnitOwner(u, owner, true)
        call SetUnitFacing(u, facing)
        call SetUnitX(u, x)
        call SetUnitY(u, y)
        call PauseUnit(u, false)
        call ShowUnit(u, true)
        if GetUnitAbilityLevel(u, 'Aloc') > 0 then
            call UnitRemoveAbility(u, 'Aloc')
            call UnitAddAbility(u, 'Aloc')
        endif
        return u
    endfunction
    
    function DummyReuser_Release takes unit u, integer id, integer n returns boolean
        //Safety check to protect users from themselves
        if not IsUnitType(u, UNIT_TYPE_DEAD) and GetUnitTypeId(u) == id and n < MAX then
            call SetUnitOwner(u, p, false)
            call PauseUnit(u, true)
            call ShowUnit(u, false) // Using ShowUnit as that's the most consistent way of hiding any dummy unit
            return true
        endif
        debug call BJDebugMsg("[DummyReuser] Removing dummy unit!")
        call RemoveUnit(u)
        return false
    endfunction
    
//! textmacro DummyReuser takes NAME, DUMMY_UNIT_TYPEID
scope $NAME$
    globals
        private unit array u
        private integer n = 0
    endglobals
    
    public function Get takes player owner, real x, real y, real facing returns unit
        if n == 0 then
            return CreateUnit(owner, $DUMMY_UNIT_TYPEID$, x, y, facing)
        endif
        set n = n - 1
        return DummyReuser_Get(owner, u[n])
    endfunction
    
    public function Release takes unit which returns nothing
        if DummyReuser_Release(which, $DUMMY_UNIT_TYPEID$, n) then
            set u[n] = which
            set n = n + 1
        endif
    endfunction
endscope
//! endtextmacro
    
endlibrary
 
Level 14
Joined
Nov 18, 2007
Messages
1,084
JASS:
        if GetUnitAbilityLevel(u, 'Aloc') > 0 then
            call UnitRemoveAbility(u, 'Aloc')
            call UnitAddAbility(u, 'Aloc')
        endif
->
JASS:
        if UnitRemoveAbility(u, 'Aloc') then            
            call UnitAddAbility(u, 'Aloc')
        endif
That's an interesting approach, though I'm not too sure if I like how the functions are named (though that can probably be easily modified with the textmacro).

Not sure if I'll actually switch from a struct to a scope, but I will eventually update with a limit for dummy units recycled and dividing the functions up like you did.

EDIT:
Updated to 2.01, mostly following Bribe's improvements.
 
Last edited:
Level 31
Joined
Jul 10, 2007
Messages
6,307
Ok, I now see what you mean by the playing a certain animation, but all standard wc3 effects only have one animation (when it is standing). For other effects, you typically only need the standing animation and the destroy animation.

Let's say that you wanted to create an explode effect (play its animation and so on)
set effect = AddSpecialEffect("Objects\\Spawnmodels\\Human\\HumanLargeDeathExplode\\HumanLargeDeathExplode.mdl", GetStartLocationX(0), GetStartLocationY(0))

If you want to move it around, then you could just as easily add that effect to a standard dummy unit and move that dummy unit around.

The only time I see this ever being used is when the map maker did their design incorrectly.

Sure, you could have one model that had 1000 different animations on it and then you could just pick whatever animation you wanted and then you could have another unit with another 1000 different animations and then you could play those animations whenever, but it's just smarter to use 1 model per effect.


In short, I don't see this ever being used for anything as its only use occurs when the design of the map was done poorly :\. You don't need a unit handle to create an effect. Wc3 has effect handles for that, lol.
 
Level 14
Joined
Nov 18, 2007
Messages
1,084
That method doesn't seem like a good idea in terms of ease.
It requires more work in general since the user has to know how to edit models. It's also a hassle if he wants to play different multiple animations from one model because he would have to import one model for each animation he wants to use. The method would also force him to edit and import standard models again that have a specific animation he wants to play.
 
That method doesn't seem like a good idea in terms of ease.
It requires more work in general since the user has to know how to edit models. It's also a hassle if he wants to play different multiple animations from one model because he would have to import one model for each animation he wants to use. The method would also force him to edit and import standard models again that have a specific animation he wants to play.
Again, when would you ever need to run a specific animation of an sfx? special effect models are made in a way that you don't ever need that. The only exception is when you want to use unit model as an sfx. But in this case you can just use dummy units in the first place.

It simply makes no sense to have a system that uses dummy units instead of special effects right from the start. You should avoid using dummyunits whenever you can. Creating and destroying a special effects is a hundred times faster than creating or manipulating a dummy unit. Even moving an dummy unit alone takes much more speed than just using a special effect.
If you need to access a certain animation directly (which should almost never be the case in standard situations), you can always use a dummy tailor-made for this specific situation. I don't really see a reason why you would need a system for that.
 
Level 29
Joined
Mar 10, 2009
Messages
5,016
Some kind of a struct? whats that, i dont know jass xD

so how do we use this?

it means like this...
JASS:
struct test
    //! runtextmacro DummyReuser("'A000'")
endstruct
'A000' is a raw code of the dummy by pressing CTRL+D in the object editor...

coz it will compile like this...
JASS:
function s__test_get takes player owner,real x,real y,real facing returns unit
        if s__test_dr_count == 0 then
            return CreateUnit(owner, 'A000', x, y, facing)
        endif
        set s__test_dr_count=s__test_dr_count - 1 // The stack is zero-based
        return DummyReuser_Get(s__test_dr_stack[s__test_dr_count] , owner , x , y , facing)
    endfunction

    function s__test_release takes unit u returns nothing
        if DummyReuser_Store(u , 'A000' , s__test_dr_count) then
            set s__test_dr_stack[s__test_dr_count]=u
            set s__test_dr_count=s__test_dr_count + 1
        endif
    endfunction

and called/used like this;
local unit u = test.get(GetTriggerPlayer(), 0, 0, 0)
 
Level 5
Joined
May 2, 2015
Messages
109
I tried to make this system a year ago and I failed, and in this system it did the same problem that make system a failure.

Here the problem. I create a dummy with revive effect and I rotated it to 90 deg. When I reuse it, the dummy takes time to face to another location.

Hope you will find a way out of this.
 
Top