Hiho,
in order to solve some critical virtual memory based fatal errors with my line tower wars I tried to search for several solutions and tested the map for hidden memory leaks.
Since I haven't found any fixable memory leak within my whole codes I came up with something I have read ages ago about units which says that every instanciated unit will leave a leak after it gets destroyed or even removed from the game. This leak is not preventable for us, mappers.
That's why I have started to think about a solution for this. I have to minimize the amount of unit creations - I had to code a unit recycling system. This would solve all my unit allocation problems ... perhaps. However, a problem was that I had to allocate tousand of units because there is no functionality for a unit type swap ... wrong. With chaos ability and a good unit database I was able to fix this problem, so every recyclable unit type in my map can be converted dynamically to any other recyclable unit type with the chaos ability, this worked fine.
However, tests provided me with some strange results. I have tested a minor dummy recycling system on a map where I spawn about 500 units per second and remove them again (with and without recycling and with and without unit type swap.)
The results where very weird ...
As you might think without recycling these massive amounts of units created a big memory leak after some time while the units which got recycled (basically hidden and unhidden etc.) created (nearly!!!) no memory leak.
However, with the chaos ability based unit type swap the memory leak was the same as without unit recycling!
I couldn't believe my own tests and started to implement the whole system in my map - Line Tower Wars Evolution ...
And here is the code (but keep in mind that I have not followed the awesome syntax and code writing rules because when I started the map I was a hardcore fanbody for C# syntax style. xDDD - I won't change it now as this system is not intended to be publicly available since it needs ugly unit databases which isn't very handy imo.)
Maybe I have some critical memory leaks within my system which cause these weird memory leak results in my many many tests. I am looking forward for bug reports or memory leaks within this system or performance improvement ideas of you guys. Another thing I haven't mentioned is that this system is awefully slow with an enabled onDamageCondition trigger with massive amounts of units at the same time - it is so slow that I sometimes had about 0.5 fps during the tests ... without it everything performed even better than without the recycling system as I have not to create units all the time which is a very performance critical procedure.
Robbepop
in order to solve some critical virtual memory based fatal errors with my line tower wars I tried to search for several solutions and tested the map for hidden memory leaks.
Since I haven't found any fixable memory leak within my whole codes I came up with something I have read ages ago about units which says that every instanciated unit will leave a leak after it gets destroyed or even removed from the game. This leak is not preventable for us, mappers.
That's why I have started to think about a solution for this. I have to minimize the amount of unit creations - I had to code a unit recycling system. This would solve all my unit allocation problems ... perhaps. However, a problem was that I had to allocate tousand of units because there is no functionality for a unit type swap ... wrong. With chaos ability and a good unit database I was able to fix this problem, so every recyclable unit type in my map can be converted dynamically to any other recyclable unit type with the chaos ability, this worked fine.
However, tests provided me with some strange results. I have tested a minor dummy recycling system on a map where I spawn about 500 units per second and remove them again (with and without recycling and with and without unit type swap.)
The results where very weird ...
As you might think without recycling these massive amounts of units created a big memory leak after some time while the units which got recycled (basically hidden and unhidden etc.) created (nearly!!!) no memory leak.
However, with the chaos ability based unit type swap the memory leak was the same as without unit recycling!
I couldn't believe my own tests and started to implement the whole system in my map - Line Tower Wars Evolution ...
And here is the code (but keep in mind that I have not followed the awesome syntax and code writing rules because when I started the map I was a hardcore fanbody for C# syntax style. xDDD - I won't change it now as this system is not intended to be publicly available since it needs ugly unit databases which isn't very handy imo.)
JASS:
library CreepRecycling requires GroupUtils
struct CreepRecycling extends array
private static constant real MIN_LIFE = .405
private static constant integer CREEP_AURA_DEFENSE = 'B000'
private static constant integer CREEP_AURA_OFFENSE = 'B001'
private static constant integer CREEP_AURA_SPEED = 'B002'
private static constant integer TOWER_AURA_SLOW = 'B003'
private static constant integer TOWER_AURA_CURSED = 'B00A'
private static constant integer BUILDER_AURA_HEADWIND = 'B008'
private static constant integer DUMMY_CREEP_ID = 'n007'
private static constant real BOUNTY_PERCENTAGE = 0.1
private static group recycled
private static integer countRecycled
private static trigger detectDamageTrigger
private static unit currentCreep
private static key UNIT_TYPE_ID_KEY
private static method RemoveEnum takes nothing returns nothing
call RemoveUnit(GetEnumUnit())
endmethod
public static method Clear takes nothing returns nothing
set thistype.countRecycled = 0
call ForGroup(thistype.recycled, function thistype.RemoveEnum)
call GroupClear(thistype.recycled)
call DestroyGroup(thistype.recycled)
call DisableTrigger(thistype.detectDamageTrigger)
call TriggerClearActions(thistype.detectDamageTrigger)
call TriggerClearConditions(thistype.detectDamageTrigger)
endmethod
private static method CreateBountyTexttag takes unit whichUnit, integer bounty, player killer returns nothing
// Var Init
local texttag tt = CreateTextTag()
local string text = "+" + I2S(bounty)
// Create Visuals
call SetTextTagText(tt, text, 0.024)
call SetTextTagPos(tt, GetUnitX(whichUnit) - 16.0, GetUnitY(whichUnit), 0.0)
call SetTextTagColor(tt, 255, 220, 0, 255)
call SetTextTagVelocity(tt, 0.0, 0.03)
call SetTextTagVisibility(tt, GetLocalPlayer() == killer)
call SetTextTagFadepoint(tt, 2.0)
call SetTextTagLifespan(tt, 3.0)
call SetTextTagPermanent(tt, false)
// Clear
set text = null
set tt = null
endmethod
private static method RemoveBuffs takes unit creep returns nothing
call UnitRemoveAbility(creep, thistype.CREEP_AURA_DEFENSE)
call UnitRemoveAbility(creep, thistype.CREEP_AURA_OFFENSE)
call UnitRemoveAbility(creep, thistype.CREEP_AURA_SPEED)
call UnitRemoveAbility(creep, thistype.TOWER_AURA_SLOW)
call UnitRemoveAbility(creep, thistype.TOWER_AURA_CURSED)
call UnitRemoveAbility(creep, thistype.BUILDER_AURA_HEADWIND)
endmethod
private static method GetTypeSwapAbility takes integer creepId returns integer
//call BJDebugMsg("GetTypeSwapAbility - name = " + GetAbilityName(LoadInteger(GameGlobals.Hashtable, thistype.UNIT_TYPE_ID_KEY, creepId)))
return LoadInteger(GameGlobals.Hashtable, thistype.UNIT_TYPE_ID_KEY, creepId)
endmethod
private static method RemoveTypeSwapAbility takes unit creep returns nothing
//call BJDebugMsg("RemoveTypeSwapAbility - name = " + GetUnitName(creep))
call UnitRemoveAbility(creep, thistype.GetTypeSwapAbility(GetUnitTypeId(creep)))
endmethod
private static method SetUnitTypeId takes unit creep, integer creepId returns nothing
call UnitAddAbility(creep, thistype.GetTypeSwapAbility(creepId))
//call BJDebugMsg("SetUnitTypeId(" + GetUnitName(creep) + ", " + I2S(creepId))
endmethod
public static method Get takes player owner, integer creepId, real posX, real posY, real facingAngle returns unit
if thistype.countRecycled <= 0 then
//set thistype.currentCreep = CreateUnit(owner, creepId, posX, posY, facingAngle)
set thistype.currentCreep = CreateUnit(owner, Creep.DummyCreep.Id, posX, posY, facingAngle)
call thistype.AddCreep(thistype.currentCreep)
call thistype.SetUnitTypeId(thistype.currentCreep, creepId)
//call BJDebugMsg("CreepRecycling - Get - created new")
else
set thistype.countRecycled = thistype.countRecycled - 1
set thistype.currentCreep = FirstOfGroup(thistype.recycled)
call GroupRemoveUnit(thistype.recycled, thistype.currentCreep)
call SetUnitOwner(thistype.currentCreep, owner, true)
call thistype.SetUnitTypeId(thistype.currentCreep, creepId)
call SetUnitX(thistype.currentCreep, posX)
call SetUnitY(thistype.currentCreep, posY)
call SetUnitFacing(thistype.currentCreep, facingAngle)
call PauseUnit(thistype.currentCreep, false)
call ShowUnit(thistype.currentCreep, true)
call SetUnitInvulnerable(thistype.currentCreep, false)
//call BJDebugMsg("CreepRecycling - Get - recycled" + " - count = " + I2S(thistype.countRecycled))
endif
return thistype.currentCreep
endmethod
public static method Recycle takes unit creep returns nothing
call SetWidgetLife(creep, 9999999.)
call SetUnitInvulnerable(creep, true)
call thistype.RemoveBuffs(creep)
call thistype.RemoveTypeSwapAbility(creep)
call PauseUnit(creep, true)
call ShowUnit(creep, false)
call SetUnitOwner(creep, Player(PLAYER_NEUTRAL_AGGRESSIVE), false)
call GroupAddUnit(thistype.recycled, creep)
call SetUnitX(creep, 0.)
call SetUnitY(creep, 0.)
set thistype.countRecycled = thistype.countRecycled + 1
//call BJDebugMsg("CreepRecycling - Recycle - " + GetUnitName(creep) + " - count = " + I2S(thistype.countRecycled))
endmethod
private static method AddCreep takes unit creep returns nothing
call TriggerRegisterUnitEvent(thistype.detectDamageTrigger, creep, EVENT_UNIT_DAMAGED)
//call BJDebugMsg("CreepRecycling - AddCreep - " + GetUnitName(creep))
endmethod
private static method onDamageCondition takes nothing returns boolean
// Var Init
local unit killed
local real damage = GetEventDamage()
local Gamer owner
local Creep creep
// Initial Check
if damage > 0. then
return false
endif
// Check & Execute
set killed = GetTriggerUnit()
if GetWidgetLife(killed) - damage <= thistype.MIN_LIFE then
set owner = Gamer.GetOwner(GetEventDamageSource())
set creep = Creep.GetInstance(killed)
call thistype.CreateBountyTexttag(killed, creep.Bounty, owner.Controller)
call thistype.Recycle(killed)
call owner.AddGold(creep.Bounty)
endif
// Clear
set killed = null
// Default Return
return false
//return GetEventDamage() > 0. and GetWidgetLife(GetTriggerUnit()) - GetEventDamage() <= thistype.MIN_LIFE
endmethod
//private static method onDamageAction takes nothing returns nothing
//call thistype.Recycle(GetTriggerUnit())
//call thistype.CreateBountyTexttag(GetTriggerUnit(), GetOwningPlayer(GetEventDamageSource()))
//endmethod
private static method InitDummyAbility takes integer creepId, integer abilityId returns nothing
call SaveInteger(GameGlobals.Hashtable, thistype.UNIT_TYPE_ID_KEY, creepId, abilityId)
endmethod
private static method InitDummyAbilities takes nothing returns nothing
// Tier 1
call thistype.InitDummyAbility('n00E', 'S000') // Sheep
call thistype.InitDummyAbility('n00C', 'S001') // Wolf
call thistype.InitDummyAbility('u003', 'S002') // Skeleton
call thistype.InitDummyAbility('u000', 'S003') // Acolyte
call thistype.InitDummyAbility('n005', 'S004') // Corruped Treant
call thistype.InitDummyAbility('h00U', 'S005') // Swordsman
call thistype.InitDummyAbility('n000', 'S006') // Chaos Grunt
call thistype.InitDummyAbility('n00A', 'S007') // Vile Temptress
call thistype.InitDummyAbility('u001', 'S008') // Shade
call thistype.InitDummyAbility('u00A', 'S009') // Shade (-na)
call thistype.InitDummyAbility('n00F', 'S00A') // Mud Golem
call thistype.InitDummyAbility('h01B', 'S00B') // Demolition Machine
call thistype.InitDummyAbility('h00W', 'S00C') // Draenei (-ns)
call thistype.InitDummyAbility('n008', 'S00D') // Rot Golem
// Tier 2
call thistype.InitDummyAbility('h01H', 'S00E') // Knight
call thistype.InitDummyAbility('o000', 'S00F') // Tauren
call thistype.InitDummyAbility('n002', 'S00G') // Troll
call thistype.InitDummyAbility('n003', 'S00H') // Harpy
call thistype.InitDummyAbility('n001', 'S00I') // Gnoll (-na)
call thistype.InitDummyAbility('h01I', 'S00J') // Siege Engine
call thistype.InitDummyAbility('h00X', 'S00K') // Siege Engine (-ns)
call thistype.InitDummyAbility('n006', 'S00L') // Faceless Void
call thistype.InitDummyAbility('n009', 'S00M') // Magnataur
call thistype.InitDummyAbility('n004', 'S00N') // Dragonspawn
call thistype.InitDummyAbility('u002', 'S00O') // Banshee
call thistype.InitDummyAbility('u00B', 'S00P') // Banshee (-na)
call thistype.InitDummyAbility('n00B', 'S00Q') // Wendigo
call thistype.InitDummyAbility('n00D', 'S00R') // Hellbeast
call thistype.InitDummyAbility('h01M', 'S00S') // Phoenix
call thistype.InitDummyAbility('h00Y', 'S00T') // Felhound (-na + -ns)
// Tier 3
call thistype.InitDummyAbility('u008', 'S00U') // Brutal Ghoul
call thistype.InitDummyAbility('u00C', 'S00V') // Armored Ghoul (-ns)
call thistype.InitDummyAbility('n00H', 'S00W') // Satyr
call thistype.InitDummyAbility('u006', 'S00X') // Cryptfiend
call thistype.InitDummyAbility('o002', 'S00Y') // Orcish Shaman
call thistype.InitDummyAbility('o001', 'S00Z') // Void Walker
call thistype.InitDummyAbility('h02E', 'S010') // Spellbreaker
call thistype.InitDummyAbility('u007', 'S011') // Necromancer
call thistype.InitDummyAbility('h021', 'S012') // Gryphon Rider
call thistype.InitDummyAbility('h00Z', 'S013') // Owlbear (-na)
call thistype.InitDummyAbility('u004', 'S014') // Abomination
call thistype.InitDummyAbility('e000', 'S015') // Mountain Giant
call thistype.InitDummyAbility('e002', 'S016') // Mountain Giant (-ns)
call thistype.InitDummyAbility('u009', 'S017') // Hell Skeleton
call thistype.InitDummyAbility('u005', 'S018') // Frost-Wyrm
call thistype.InitDummyAbility('u00D', 'S019') // Mathog (-na)
endmethod
public static method Init takes nothing returns nothing
call thistype.InitDummyAbilities()
set thistype.recycled = NewGroup()
set thistype.countRecycled = 0
set thistype.detectDamageTrigger = CreateTrigger()
call TriggerAddCondition(thistype.detectDamageTrigger, Condition(function thistype.onDamageCondition))
//call TriggerAddAction(thistype.detectDamageTrigger, function thistype.onDamageAction)
endmethod
endstruct
endlibrary
Maybe I have some critical memory leaks within my system which cause these weird memory leak results in my many many tests. I am looking forward for bug reports or memory leaks within this system or performance improvement ideas of you guys. Another thing I haven't mentioned is that this system is awefully slow with an enabled onDamageCondition trigger with massive amounts of units at the same time - it is so slow that I sometimes had about 0.5 fps during the tests ... without it everything performed even better than without the recycling system as I have not to create units all the time which is a very performance critical procedure.
Robbepop