Cokemonkey11
Spell Reviewer
- Joined
- May 9, 2006
- Messages
- 3,575
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:
API
No more explanation necessary. If you want to fiddle with the tweaker settings, you're on your own
The script
Installation
Change Log
2014.01.10 - Initial submission to Hive: Jass Resources: Submissions
Special Thanks:
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
JASS:
//*
//* 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
- Copy the dummy unit from the test map
- Copy this script in its entirety into an empty trigger (custom script) and name it DummyUnitStack.
- (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.
Attachments
Last edited: