| 04-01-2006, 03:36 AM | #1 |
This tutorial assumes you know JASS and the basics of fixing memory leaks. If you are new to JASS, read Vexorian's tutorial. If you are new to fixing memory leaks, read Blade.dk's tutorial. This is an attempt to consolidate everything I know about the "local var not nulled leak bug" and provide a reference, as I frequently observe some confusion about it. Disclaimer: I'm not a programmer. Handles Units, locations and everything else that isn't a string, real, integer, code or boolean are handles. A handle is a 4B integer with a little type safety so that you don't try things like local location l = GetTriggerPlayer(), which would be meaningless. The handle indexes an array of structures. Each structure contains a pointer to the unit, location or other object along with a reference count. Whenever you assign a handle to a variable, warcraft finds the structure and increments the reference count. When you reassign that variable, the count is decremented. In theory, when there are no more local or global variables with that small piece of data's address and the object it points to has been removed, it should deallocate itself. Unfortunately, blizzard never finished this functionality. Even worse, they left a bug in: When a variable goes out of scope, that is, when a function that had local variables returns, the reference does not decrement! If you try to destroy or remove a handle, that small piece of data will not be deallocated until the reference count drops to zero. This will never happen if you don't work around that bug. Whenever something is never going to be destroyed, you do not need to worry about this bug. For example, in an AoS map, the heroes will likely never be removed, so setting to null will not fix any leak. Similarly, multiboards aren't likely to be and players can't be destroyed. The Work arounds: Set to Null JASS:function MyTriggerAction takes nothing returns nothing local location unitloc = GetUnitLoc(GetTriggerUnit()) //Do stuff call RemoveLocation(unitloc) set unitloc = null endfunction Note that it does not matter that you set the variable to null, only that you change its value. Thus, a global var does not need to be nulled, as it will soon be changed which has the same effect as nullification. Wrapper Functions - discovered by Cubasis JASS:function SomeFunc_helper takes location l returns location //Do stuff with l return l //Remember to deallocate it later endfunction function SomeFunc takes nothing returns location return SomeFunc_helper(GetUnitLoc(GetTriggerUnit()) endfunction Integers Handles are just integers. If we could cast them into an integer and back out somehow, we would be home free, because as long as the handle never goes into a variable of handle type, the reference count won't increment. However, we lose type safety, so we must be careful. Fortunately, type casting was figured out a long time ago through the infamous return bug. JASS:function H2I takes handle h returns integer return h return 0 endfunction function SomeFunc takes nothing returns location local location unitloc = GetUnitLoc(GetTriggerUnit()) local integer iunitloc = H2I(unitloc) //Do stuff with unitloc set unitloc = null return iunitloc //Don't forget to remove return null endfunction Alternatively, there is an old trick for using local variables in GUI: First, declare a point unitloc. Then, at the top of your trigger, add action Custom script - local location udg_unitloc When you do this, you can select unitloc from the GUI drop down list, but the local version will take preference. However, you can only do this once before something strange happens: Every overwritten local variable will share the same space! For the C heads, it behaves like a union. JASS:function MyTriggerAction takes nothing returns location local integer udg_A = H2I(GetUnitLoc(GetTriggerUnit())) //abuse return bug local location udg_unitloc //Do stuff with udg_unitloc return udg_unitloc endfunction JASS:function MyTriggerAction takes nothing returns location local integer udg_A local location udg_unitloc = GetUnitLoc(GetTriggerUnit()) local integer B = udg_A //abuse global/local bug //Do stuff with udg_unitloc set udg_unitloc = null //Choice A - abuse global/local bug set udg_A = B return udg_unitloc //Choice B - abuse return bug // return B // return null endfunction Globals and GUI Since we don't actually need to set a variable to null, but only change its value, if we could use globals for everything, we would also be home free, as long as we make sure to reuse the same space whenever possible. We have global arrays, all we need is to give each scope a private workspace within them. This method is particulary useful for GUI code where, in practice, you can only have one local variable-this method gets you as many as you can pack into the variable editor. Theoretically you could even scope variables inside loops. JASS://=================================================== // Temp Memory hack // Paste this code into the custom script section // Variables needed: // integer array udg_Stack_Nodes // integer udg_Stack_Heap // integer udg_Stack_Top with initial value -1 // whatever object such as location array udg_tempPoint //======================================================== function AllocateTemp takes nothing returns integer local integer p = udg_Stack_Heap set udg_Stack_Heap = udg_Stack_Heap + 1 if(p > 8191) then call BJDebugMsg("Too many active GUI triggers! Use game cache for a heap") endif return p endfunction function GetTempSpace takes nothing returns integer local integer p if(udg_Stack_Top == -1) then //Check if stack is empty set p = AllocateTemp() //Grab a new cell for our collection else //Do a regular stack pop set p = udg_Stack_Top set udg_Stack_Top = udg_Stack_Nodes[udg_Stack_Top] endif return p endfunction //Regular stack push function ReleaseTempSpace takes integer p returns nothing set udg_Stack_Nodes[p] = udg_Stack_Top set udg_Stack_Top = p endfunction Trigger: sometrig
![]() Conditions
![]() Actions
![]() ![]() Custom script: local integer udg_mem = GetTempSpace()
![]() ![]() Set tempPoint[mem] = (Target of current camera view)
![]() ![]() Wait 5.00 seconds
![]() ![]() Cinematic - Ping minimap for (All players) at tempPoint[mem] for 1.00 seconds
![]() ![]() Custom script: call RemoveLocation(udg_tempPoint[udg_mem])
![]() ![]() Custom script: call ReleaseTempSpace(udg_mem) JASS:function PingCamera takes nothing returns nothing local integer mem = GetTempSpace() set udg_tempPoint[mem] = GetCameraTargetPositionLoc() call TriggerSleepAction(5.) call PingMinimap(GetLocationX(udg_tempPoint[mem]),GetLocationY(udg_tempPoint[mem]),5.) call RemoveLocation(udg_tempPoint(mem)) call ReleaseTempSpace(mem) endfunction Caveats Timers: Attempting to set timers to null will eventually result in catastrophe. Instead, use one of the other methods. The function wrapper method is tried and true: JASS:function callback_core takes timer t returns nothing //Get handle vars, play with timer //t does not need to be set to null because it never lives in a local variable if(you want) then call PauseTimer(t) call DestroyTimer(t) endif endfunction function callback takes nothing returns nothing call callback_core(GetExpiredTimer()) endfunction function inittimer_core takes timer t returns nothing //Do usual init stuff here call TimerStart(t,period,true,function callback) endfunction function inittimer takes nothing returns nothing call inittimer_core(CreateTimer()) endfunction The best way to work around the set to null problem is to avoid ever needing to destroy things. Timers, fortunately, are reusable, so we can keep freed ones on a stack just like the multi-locals in gui trick. JASS:globals timer array udg_timerstack integer udg_ntimer = 0 endglobals function NewTimer takes nothing returns timer if udg_ntimer == 0 then return CreateTimer() else set udg_ntimer = udg_ntimer-1 return udg_timerstack[udg_ntimer] endif endfunction function ReleaseTimer takes timer t returns nothing if udg_ntimer == 8190 then call BJDebugMsg("Warning- you need 8191 timers at once!?") call DestroyTimer(t) else set udg_timerstack[udg_ntimer] = t set udg_ntimer = udg_ntimer + 1 endif endfunction |
| 04-01-2006, 04:25 AM | #2 |
thanks this was very educational, and helped me learn a lot more about how to prevent leaks through custom scripting |
| 04-01-2006, 05:29 AM | #3 |
Informative for those trying to understand the WHY of the whole nulling/H2I and back functions. Really makes me wish they had actually give us this functionality in the first place, true typecasting and an address-of operator would be a heavenly blessing, but thats getting into my huge wishlist of stuff they SHOULD have allowed for. |
| 04-01-2006, 06:40 AM | #4 |
Good, I think people should know that using handles with timers and then nulling the timers can cause weird glitches and bugs. |
| 04-01-2006, 01:35 PM | #5 |
im still not clear, do you not null the timers? or any local that is used by the local handle vars? or what? |
| 04-01-2006, 01:45 PM | #6 |
You shouldnt null timers that you're using any handles on. |
| 04-01-2006, 02:04 PM | #7 |
Has anyone been able to figure out the exact conditions that cause the bug when nulling timers with handles attached? Because I've done that plenty of times and never had any problems. |
| 04-01-2006, 02:21 PM | #8 |
PipeDream, a good tutorial, but the code examples are not appropriate in some cases: when you are explaining how to return a local without a leak, your examples don't return anything, they just destroy the location in the end which makes little sense in the context of what you're explaining. Edit: Moved to the tutorial submission forum. |
| 04-01-2006, 03:36 PM | #9 |
blu_da_noob, it happens when nulling the timer, and doing the same on other triggers with other abilities and so. And also, it doesnt happen in the first time, but rather after alot of casting times |
| 04-01-2006, 04:16 PM | #10 |
wasnt it found that if you wait a few seconds after the function is ran that it doesnt happen, ex. JASS:call PauseTimer(t) call FlushHandleLocals(t) call DestroyTimer(t) call TriggerSleepAction(5) set t = null |
| 04-01-2006, 06:12 PM | #11 |
was that a question or a fact? *confused* |
| 04-01-2006, 06:40 PM | #12 |
Actually : - You must pause timers before destroying them. - If you find bugs with your spell, there is a way to prevent leaks and lose the risk, old:function blahblah takes nothing returns nothing local timer t=CreateTimer() // ... set t=null endfunction new:function blahblah_sub takes timer t returns nothing // ... endfunction function blahblah takes nothing returns nothing call blahblah_sub(t) endfunction It is leak-free and also bug-free. waiting before setting the timer to null is not bullet proof. --- remove the *** in the first sample and I'll approve it |
| 04-01-2006, 06:47 PM | #13 | |
Quote:
How many times is a lot? I have always nulled my timers and never experienced problems, so unless the abilities have be used a few hundred times atleast to experience bugs, I have never had problems with them. I've never taken any of those measures, Vex, and I've never had any problems. I guess I'll start using them for safety's sake. |
| 04-01-2006, 06:52 PM | #14 |
it is related with the use of game cache and return bug exploiters, I made thousands of spells and have only seen it twice |
| 04-01-2006, 08:48 PM | #15 |
Thank you for the feedback. I will add more complete examples. Vex: thanks for the confirmation on function wrappers - Removed *** from the first example (Vexorian) - Changed a couple examples to demonstrate returning with out leaking (Anitarf) |
