| 08-01-2007, 07:24 PM | #1 |
There is a bug in a spell I've made that I can't seem to fix. I think it's best if I explain the spell first. The idea is that the hero that casts it creates a sandstorm (hence the name). I use mana shield as a dummy ability (which does nothing), catch the order and use it to run the casting function. This function creates a periodic timer which updates the sandstorm. In the timerfunction, 10 effects are created each iteration. I use 2 different effects: the sandstorm and the sandwall. The sandstorm are little effects of sand around the hero, 9 in total, which disappear after 2 seconds. The sandwall is a big sandexplosion that dissipates after 5 seconds. After the effects have been created, I gather all the units around the hero in a group using a certain condition (it just checks if the unit is NOT the hero). OK so we have that group, then I store the location of the sandwall on the timer and iterate through all the past stored sandwall locations. The if/else is used so that when the runtime of the timer exceeds the time a single sandwall exists, that that sandstorm's location is no longer looped through. I gather all the units around a sandstorm's location and put them in the big 'slowgroup' if they are not already in it. Then when I'm done with that, I slow all the units in the slowgroup and store it on the timer (the group). That group is put in the oldslowgroup local at the start of the iteration and those which are in that group, but not in the new slowgroup are unslowed. The last part of the function which should be understood is that when sandstorm is deactivated, the timer has to keep running until the last sandwall has disappeared. That is why I use the 'stopruntimes' integer and sandstorming boolean (that is also used for something else, but that's not of any importance). When it is stopped, the timerfunction doesn't create effects, but keeps on slowing and unslowing. OK that should give you some direction when reading the code. Now for the problem. When I run this function, I get some red lines that tell me how many groups have been created and how many are undestroyed. Naturally a lot of groups have been created but I (think I've) made sure that the function has no leaking groups. This seems to be the case, since the debug says that there are very few undestroyed groups and all of which are not in use by the updatefunction. Now, there are 2 things going on here. When I run the ability for like..5+ seconds, it starts to lag. It doesn't seem to have anything to do with the amound of effects created, because when I set it so that it creates 5x as many effects, there is no more lag than usual. The weird thing is however that when I switch sandstorm off (the effects created are still there), the lag disappears completely in under a second. I have no idea why this is happening. The second strange thing is that when the hero which is sandstorming approaches a unit for the first time, the screen freezes for about 2 seconds. After that, it never happens again. Not even when I switch sandstorm off/on and approach a unit again. This would seem to have something to do with the slowing/cursing part of the skill. Since the hero is always present when casting sandstorm, but is not grouped because of the condition, it seems that when casting the cursing spell for the first time causes lag...? I've used dummy casters and spells in a lot of different abilities, but never ever got this much lag. Do you have any ideas? I've tried to be as thorough as I can but I can imagine I left crucial information out. I hope you can help me. I've included the map as well, the hero in question is the Crypt Lord. Pay no attention to the other heroes, since more than half of them still posess some bugs Thanks in advance. JASS://SandStorm================================================================================== constant function Sandstorm takes nothing returns integer local integer i = 'A01W' return i endfunction constant function SandstormCurseBuff takes nothing returns integer local integer i = 'B016' return i endfunction function SandstormTargetSlowRemove takes nothing returns nothing local unit target = GetEnumUnit() local real slow = GetHandleReal(target, "sandstormslow") call Msg("Slow removed on target, sandstormslow on target was " + R2S(slow)) call UnitRemoveAbility(target, SandstormCurseBuff()) call SetUnitMoveSpeed(target, GetUnitMoveSpeed(target)+slow) set target = null endfunction function SandstormCancel takes nothing returns nothing local unit hero = GetTriggerUnit() local timer sandt = GetHandleTimer(hero, "sandt") local group slowgroup call Msg("SandStormCancel runs") if sandt == null then call Msg("OMG WHERE IS TEH TIMERZ!?!/11") //obvious debug endif call SetHandleBoolean(hero, "sandstorming", false) call TriggerSleepAction(5) set slowgroup = GetHandleGroup(sandt, "slowgroup") call PauseTimer(sandt) call DestroyTimer(sandt) call ForGroup(slowgroup, function SandstormTargetSlowRemove) call DestroyGroup(slowgroup) set slowgroup = null set hero = null set sandt = null endfunction function SandstormTargetSlow takes nothing returns nothing local unit target = GetEnumUnit() local unit hero = udg_tempunit local real slow = udg_tempreal local integer abillevel = R2I(I2R(GetUnitAbilityLevel(hero, Sandstorm())) / GetHeroLevel(hero) * 10) call Msg(I2S(abillevel)) call Msg("SandstormTargetSlow runs") if (GetUnitAbilityLevel(target, udg_buffs[36]) == 0) then call CastWidgetAbility(GetOwningPlayer(hero), 36, abillevel, target, 0.0) call SetUnitMoveSpeed(target, GetUnitMoveSpeed(target) - slow) call SetHandleReal(target, "sandstormslow", slow) call Msg("Target slowed") endif set target = null set hero = null endfunction function SandstormSlowCondition takes nothing returns boolean local unit target = GetFilterUnit() local unit hero = udg_tempunit local boolean validtarget set validtarget = TargetTypeConditionEasy(target, hero, 0,0,0,0,0,0,0,0,0,0,2) call Msg("SandstormSlowCondition runs") if validtarget then call Msg("Valid target found") endif return validtarget endfunction function SandwallGroupAdd takes nothing returns nothing local unit target = GetEnumUnit() local group swgroup = udg_tempgroup call Msg("SandwallGroupAdd runs") if not IsUnitInGroup(target, swgroup) then call GroupAddUnit(swgroup, target) call Msg("Unit added to sandwallslowgroup") endif set udg_tempgroup = swgroup set swgroup = null endfunction function SandstormUpdate takes nothing returns nothing local timer sandt = GetExpiredTimer() local unit hero = GetHandleUnit(sandt, "hero") local real herox = GetWidgetX(hero) local real heroy = GetWidgetY(hero) local real sandslow = I2R(GetUnitAbilityLevel(hero, Sandstorm())) / I2R(GetHeroLevel(hero)) * 500.0 local real dt = GetHandleReal(sandt, "sanddt") local integer timesrun = GetHandleInt(sandt, "timesrun") local integer stoptimesrun = GetHandleInt(sandt, "stoptimesrun") local real stoptime = stoptimesrun * dt local real runtime = timesrun * dt local effect sandstorm local real sandstormdelay = 2 local effect sandwall local real sandwalldelay = 5 local real sandwallx local real sandwally local real sandwallradius = 350 local real effdist = 80 local group closeslowgroup = CreateGroup() local group sandwallslowgroup = CreateGroup() local group oldslowgroup = GetHandleGroup(sandt, "slowgroup") local group slowgroup = CreateGroup() local group tempswgroup = CreateGroup() local integer a = 1 local integer i = 0 local integer debugint call Msg("SandstormUpdate runs") if oldslowgroup == null then call Msg("oldslowgroup is null at start of update") endif set timesrun = timesrun + 1 call SetHandleInt(sandt, "timesrun", timesrun) set runtime = timesrun * dt if GetHandleBoolean(hero, "sandstorming") then //Creating all the effects set sandwall = AddSpecialEffect("war3mapImported\\SandAura.MDX", herox, heroy) call DestroyEffectWait(sandwall, sandwalldelay) set sandstorm = AddSpecialEffect("Objects\\Spawnmodels\\Undead\\ImpaleTargetDust\\ImpaleTargetDust.mdl", herox, heroy) call DestroyEffectWait(sandstorm, sandstormdelay) loop exitwhen i == 8 set sandstorm = AddSpecialEffect("Objects\\Spawnmodels\\Undead\\ImpaleTargetDust\\ImpaleTargetDust.mdl", herox + effdist * Cos(i * 0.25 * bj_PI) , heroy + effdist * Sin(i * 0.25 * bj_PI)) call DestroyEffectWait(sandstorm, sandstormdelay) set i = i + 1 endloop call Msg("Sandstorm effects created") //Storing the location of this iteration's sandwall call SetHandleReal(hero, "sandwallx" + I2S(timesrun), herox) call SetHandleReal(hero, "sandwally" + I2S(timesrun), heroy) //Collecting the units around the hero that have to be slowed set udg_tempunit = hero call GroupEnumUnitsInRange(closeslowgroup, herox, heroy, effdist+30, Condition(function SandstormSlowCondition)) call Msg("closeslowgroup made") else set stoptimesrun = stoptimesrun + 1 call SetHandleInt(sandt, "stoptimesrun", stoptimesrun) set stoptime = stoptimesrun * dt endif //Filling the group of units around the sandwalls if runtime > sandwalldelay then set a = R2I((runtime - sandwalldelay)/dt) call Msg("runtime > sandwalldelay") call Msg("stoptimesrun is " + I2S(stoptimesrun)) set debugint = 0 loop set debugint = debugint + 1 exitwhen a > timesrun - stoptimesrun call Msg("groupadd loops loc # " + I2S(a)) set sandwallx = GetHandleReal(hero, "sandwallx" + I2S(a)) set sandwally = GetHandleReal(hero, "sandwally" + I2S(a)) call Msg("sandwallx is " + R2S(sandwallx) + ", sandwally is " + R2S(sandwally)) set udg_tempunit = hero call GroupEnumUnitsInRange(tempswgroup, sandwallx, sandwally, sandwallradius, Condition(function SandstormSlowCondition)) set udg_tempgroup = sandwallslowgroup call ForGroup(tempswgroup, function SandwallGroupAdd) call GroupClear(tempswgroup) set sandwallslowgroup = udg_tempgroup set a = a + 1 endloop call Msg("# of loops was.."+I2S(debugint)) else call Msg("runtime <= sandwalldelay") loop exitwhen a > timesrun - stoptimesrun call Msg("groupadd loops loc # " + I2S(a)) set sandwallx = GetHandleReal(hero, "sandwallx" + I2S(a)) set sandwally = GetHandleReal(hero, "sandwally" + I2S(a)) call Msg("sandwallx is " + R2S(sandwallx) + ", sandwally is " + R2S(sandwally)) set udg_tempunit = hero call GroupEnumUnitsInRange(tempswgroup, sandwallx, sandwally, sandwallradius, Condition(function SandstormSlowCondition)) set udg_tempgroup = sandwallslowgroup call ForGroup(tempswgroup, function SandwallGroupAdd) call GroupClear(tempswgroup) set sandwallslowgroup = udg_tempgroup set a = a + 1 endloop endif call Msg("groupadd loop finished") call GroupAddGroup(closeslowgroup, slowgroup) call GroupAddGroup(sandwallslowgroup, slowgroup) set udg_tempunit = hero set udg_tempreal = sandslow call ForGroup(oldslowgroup, function SandstormTargetSlowRemove) call ForGroup(slowgroup, function SandstormTargetSlow) call SetHandleHandle(sandt, "slowgroup", slowgroup) call DestroyGroup(closeslowgroup) call DestroyGroup(sandwallslowgroup) call DestroyGroup(oldslowgroup) call DestroyGroup(tempswgroup) set closeslowgroup = null set sandwallslowgroup = null set oldslowgroup = null set slowgroup = null set tempswgroup = null set sandt = null set hero = null set sandwall = null set sandstorm = null endfunction function SandstormCast takes nothing returns nothing local unit hero = GetTriggerUnit() local timer sandt = CreateTimer() local timer sandslowt = CreateTimer() local real sanddt = 0.3 call Msg("SandstormCast runs") call SetHandleHandle(sandt, "hero", hero) call SetHandleHandle(hero, "sandt", sandt) call SetHandleBoolean(hero, "sandstorming", true) call SetHandleReal(sandt, "sanddt", sanddt) call TimerStart(sandt, sanddt, true, function SandstormUpdate) set hero = null set sandt = null set sandslowt = null endfunction |
| 08-01-2007, 07:34 PM | #2 |
I am not sure if you should call functions with waits in a periodic timer. Here is a general solution for you: 1. Install newGen 2. Put all spell data in struct 3. attach the struct to a timer using zillion handles on a timer can only confuse you and make it easy to make mistakes. |
| 08-01-2007, 07:39 PM | #3 |
I am already using newgen and the functions that are being called with waits use executefunc(). Besides that.. I don't find anything confusing about the amount of handles used here. Isn't that a bit offtopic? |
| 08-01-2007, 07:52 PM | #4 |
Actually it is not off topic, you asked for a bright ideas remember See the thing is this, when you pass handles directly if you have 10 handles you have to do 10 set functions on a timer call SetHandleHandle(sandt, "hero", hero) call SetHandleHandle(hero, "sandt", sandt) call SetHandleBoolean(hero, "sandstorming", true ... and then declare 10 local variables and then do 10 get functions and by the time you complete you code has 3 pages and you havent even started to code the spell. If on the other hand you use structs, you just fill the struct in init function set struct to a timer in periodic get struct from a timer and tada.. you can use all 10 variables without problems, witch is more it will be a LOT faster than using gamecache 10 times in every period So bottom line: smaller code = easier to find errors faster code = no lag All you have to do is forget about handle vars and start using structs. All the pros do it.. so think about it. |
| 08-01-2007, 08:57 PM | #5 |
I guess you have a point. I don't mind the coding at all, but I can't argue that it's not easier using a struct. I'm just used to using the jasscraft syntax checker and the structs get compiled into 'normal' code anyway.. still a bit faster yes, but not even close to causing this kind of lag. The readability of the code is perfect for me, even more so if I throw in some comments. However I suppose I could get used to the structs as well and it's easier to read for other people. So I think I'll agree with you on this one :) Not that big a deal though, since there are only like.. 4-5 variables being stored. And yes, you were right about the bright ideas. Sincerest apologies for my rudeness. p.s. I have been coding jass for less than 2 weeks now, still trying to figure out the style I want. |
| 08-01-2007, 09:07 PM | #6 |
Thanks for apologizing it makes a world a better place. Also I would like to say that it is sometimes easier to write your spells down on paper, not the code of course, but a sketch of where things should go. It really helps with spells involving geometry. |
| 08-01-2007, 09:16 PM | #7 |
First some comments: JASS:constant function Sandstorm takes nothing returns integer local integer i = 'A01W' return i endfunction JASS:constant function Sandstorm takes nothing returns integer return 'A01W' endfunction About your problem, it's similar to one situation that happens to me with one spell that which manages too much effects, groups and effects which are variable in amount. the problem is that you have to abuse of game cache and in some way or another, the type casting tends to fail, in a random way. why this happens?? At the moment I don't know. My solution: use vJASS and the beautiful structs. |
| 08-01-2007, 09:32 PM | #8 | |
Quote:
I do. when you store handles to a game cache without keeping the original handle in game memory after some time the game will recycle that handle number and when you return the handle from memory it will point to a location that is now occupied by another handle. And as we all now 2 things cannot be at the same place in the same time unless they are subatomic pi-mesons That is why people say I2H is a nono, and that is another reason why using structs is better than handle vars, structs are simply integers, they are not handles, therefore they cannot get garbled. (I said garbled because I didn't want to say fucked up) |
| 08-01-2007, 09:50 PM | #9 | |||
Quote:
Hmm yeah thanks for the tip but I already do that :) Quote:
I seem to recall that that didn't work for the constant function, but I believe you. About the second you said.. that isn't really of much help to me. You're just saying that I use gamecache too much. I don't really have any evidence that I'm overloading it.. I have other spells that use it just as much and there are no problems with misplaced pointers or whatever. I've tested that dilligently with messages. Furthermore, from what I know of structs, they don't hold that big an advantage over using the gamecache that it could solve this problem. Besides, the lag is really strange. Maybe you should try my testmap (it's the crypt lord) Quote:
I know this and I know it's an explanation for the fucking up of handle vars, but there's still no reason I can assume that that's the problem here :/ I use the handle var system throughout the entire map with abilities that are more complex than this one. I won't argue with you that the handlevar system has its limits and that structs could be better, but I still have no reason to think that that's what's causing it... there are no misplaced variables or wrong pointers, it just lags... as if its leaking, but it's not leaking. The reason I'm saying this is that even though it may be worth a shot if all else fails, I hope you can continue to think of a different cause/solution besides using structs... it's just unlikely that it will solve anything |
| 08-01-2007, 10:09 PM | #10 | |
Quote:
I've solved my problem learning structs, with them I was able to simplify the spell greatly, and now I access directly the handles instead calling them indirectly by type casting (I2H nightmare). I'll check your spell this night (I'm at the office right now) and I'll see if I can help you in any way. |
| 08-01-2007, 10:32 PM | #11 | |
Quote:
I hope you will look carefully through my code, because I feel pretty confident that I coded it cleanly. They aren't just random bugs, they are very recognizable, distinct amounts of lag. I really doubt it's as simple as saying "it has too many handles and gamecache abuse, use structs". If it was leaking lag (which I had earlier in the construction of this spell), you could give the same argument about my code. I think it's too short-sighted to say that the handlevars system is fucking up, period. If that were true, I should be able to notice it in functionality. Every iteration, every variable is as it should be... It looks and smells like a leak, but I can't find it. What problem did you solve? |
| 08-01-2007, 10:48 PM | #12 | |
Quote:
It is not handle var's fault, it is blizzard's they didn't really expect the ref counter on handles to be compatible with things like i2h so things mess up. .. Recipe for chaos - Creating and destroying timers. - Attaching things to timers and loading them using i2h (or equivalents) - Using executefunc in a timer function so it has waits. - Destroying the timer somewhere in between. In short you are doing a lot of things that cause issues, it is not your fault and as one of the guys that first used handle vars must say that it is not handle vars' fault either, but global arrays tend to resist more against these things than handle vars, and structs use global arrays, so ... |
| 08-01-2007, 10:59 PM | #13 |
... use Jass New Jen Pack now and enjoy the pleasure of structs :) |
| 08-01-2007, 11:20 PM | #14 | |
Quote:
I guess I can't get around the fact that chaos can ensue when you code stuff my way. However, could you be a bit more concrete? If only for my learning process. Do you have any idea what 'these things' are or what exactly goes wrong when doing the things you described above. I understand that one could label it as 'risky' business because everything that involves timing is error-prone and hard to debug, but I honestly don't understand what could go wrong in this situation. Perhaps you could take a look at my map and see what kind of lag I am talking about? I am curious if you'll keep your opinion about what's causing this. Again, I am convinced that this could be a more messy way of coding than using structs, but still.. I don't get it. It can't be a pointer problem, it doesn't look like a leaking problem. The cancelling of the sandstorm ability destroys the timer and the group and somehow fixes all the lag almost instantly. That about rules out the leaking possibility and would lead me to believe that, over time, the amount of work that has to be done each iteration is too big. But I've checked with messages how much he does (the amount of GroupEnumUnitsInRange with the sandwall's locations) and it doesn't increase past a certain limit. I guess it's just curiosity now (especially since I use this structure throughout my entire map and have always been able to fix timing issues or storing issues). Maybe there's also still a part of me that believes in this system and doesn't think the structs will fix it :) One final question: "global arrays tend to resist more against these things than handle vars". What things? General weirdo functionality bugs? Lagging issues? Pointer problems? Timing problems? I would like to thank everyone for their time btw :) A lot of responses in only a few hours. |
| 08-02-2007, 07:14 AM | #15 |
See the reason none of us can be more specific about what is your problem is simply because we don't know. And it is impossible to know, bugs connected with I2H are just too elusive and even if all the code seems to work sometimes it does not. If you are interested why exactly is it so random here is the answer: The thing that fucks up handles is handle recycler (garbage collector) and it works in a separate thread from your code. That means that it can do stuff to handles at the same time as your code (and since these 2 thread are not synchronized ...) As for the lag: gamecache is sensitive to order in witch you restore variables from it. Also note that gamecache is actually a file on your hard disk. when you do stuff like call SetHandleBoolean(hero, "sandstorming", true) he writes that somewhere in file, sandstorming | true and when you do a get he needs to find a line in file that has "sandstorming" and return the value to you. wc3 is accessing hard disc for other reasons also, if for example heroes or effects or whatever is not preloaded the first time you use it in a game wc3 will access the hard disk to read that data from your map (no it does not load the whole map at startup) So to conclude, I don't know what exactly is wrong with your spell, and frankly I don't wanna know, I am trying to forget handle vars. Learn structs - * it will be easier to make spells, * it will not lag * and when you have a problem you will get help more likely |
