• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[Spell] Re-saving Destructible in Hashtable not working as intended

Status
Not open for further replies.
Level 12
Joined
May 16, 2020
Messages
660
Hi guys,

This might be a problem on my side, but I don't find the mistake. I'm trying to re-create Dota's Sprout ability in MUI Nature's Prophet.

For some reason this part of the trigger (this is within the trigger at the very bottom) does not re-assign the created trees to the new values:
  • For each (Integer SA_Integer2) from 1 to 8, do (Actions)
    • Loop - Actions
      • Hashtable - Save Handle Of(Load SA_Integer2 of SA_Index in SA_Hashtable.) as SA_Integer2 of SA_Integer in SA_Hashtable.
This results in the trees not being destroyed if the ability is cast twice within the timer. I used this formula successfully twice already for other spells (thanks again @HerlySQR ), but here for some reason it doesn't work. Does anyone see the mistake?

***

  • Sprout Config
    • Events
      • Map initialization
    • Conditions
    • Actions
      • Hashtable - Create a hashtable
      • Set VariableSet SA_Hashtable = (Last created hashtable)
  • Sprout
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
      • (Ability being cast) Equal to Sprout
    • Actions
      • Set VariableSet SA_Index = (SA_Index + 1)
      • Set VariableSet SA_Caster = (Triggering unit)
      • Set VariableSet SA_Counter[SA_Index] = 0
      • Set VariableSet SA_CounterMax[SA_Index] = (20 + (10 x (Level of Sprout for SA_Caster)))
      • -------- --------
      • Set VariableSet SA_Point[1] = (Position of (Target unit of ability being cast))
      • For each (Integer SA_Integer2) from 1 to 8, do (Actions)
        • Loop - Actions
          • Set VariableSet SA_Point[2] = (SA_Point[1] offset by 150.00 towards (45.00 x (Real(SA_Integer2))) degrees.)
          • -------- --------
          • Destructible - Create a Summer Tree Wall at SA_Point[2] facing (Random angle) with scale 1.00 and variation 0
          • Hashtable - Save Handle Of(Last created destructible) as SA_Integer2 of SA_Index in SA_Hashtable.
          • Custom script: call RemoveLocation (udg_SA_Point[2])
      • -------- --------
      • Unit - Create 1 Vision Dummy (500/500 True Sight Flying Vision) for (Owner of SA_Caster) at SA_Point[1] facing Default building facing degrees
      • Unit - Add a (2.00 + (Real((Level of Sprout for SA_Caster)))) second Generic expiration timer to (Last created unit)
      • -------- --------
      • Custom script: call RemoveLocation (udg_SA_Point[1])
      • -------- --------
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • SA_Index Equal to 1
        • Then - Actions
          • Game - Display to (All players) the text: ON
          • Countdown Timer - Start SA_Timer as a Repeating timer that will expire in 0.10 seconds
          • Trigger - Turn on Sprout Loop <gen>
        • Else - Actions
  • Sprout Loop
    • Events
      • Time - SA_Timer expires
    • Conditions
    • Actions
      • For each (Integer SA_Integer) from 1 to SA_Index, do (Actions)
        • Loop - Actions
          • Set VariableSet SA_Counter[SA_Integer] = (SA_Counter[SA_Integer] + 1)
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • SA_Counter[SA_Integer] Greater than or equal to SA_CounterMax[SA_Integer]
            • Then - Actions
              • For each (Integer SA_Integer2) from 1 to 8, do (Actions)
                • Loop - Actions
                  • Destructible - Remove (Load SA_Integer2 of SA_Integer in SA_Hashtable.)
              • -------- --------
              • Set VariableSet SA_Counter[SA_Integer] = SA_Counter[SA_Index]
              • Set VariableSet SA_CounterMax[SA_Integer] = SA_CounterMax[SA_Index]
              • For each (Integer SA_Integer2) from 1 to 8, do (Actions)
                • Loop - Actions
                  • Hashtable - Save Handle Of(Load SA_Integer2 of SA_Index in SA_Hashtable.) as SA_Integer2 of SA_Integer in SA_Hashtable.
              • -------- --------
              • Set VariableSet SA_Index = (SA_Index - 1)
              • Set VariableSet SA_Integer = (SA_Integer - 1)
              • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                • If - Conditions
                  • SA_Index Equal to 0
                • Then - Actions
                  • Game - Display to (All players) the text: OFF
                  • Countdown Timer - Pause SA_Timer
                  • Trigger - Turn off (This trigger)
                • Else - Actions
            • Else - Actions
 
Level 9
Joined
Mar 26, 2017
Messages
376
I think it may be because you have one single global timer, it will overwrite when you start the timer again.

You can save the timer handle in a hashtable, and then retrieve it when the timer expires. This handle id can then be used as a key to appropriately find and remove the trees.
 
Level 9
Joined
Mar 26, 2017
Messages
376
Ah, think I read this code wrong. It is supposed to have a single timer to handle all instances of this spell.

What I believe to be a simpler method is start a single-shot unique timer for each spell cast (that expires at spell duration).
You attach a function to the timer that reads the expiring timer handle id, and can destroy appropriate objects using the handle id as a key. In that case there is no need to have a looping timer, and use count variables and index.

But I can understand if you want to stick to this if it worked well for your other spells. I looked more closely but could not find the issue with this spell. Maybe someone more experienced with these constructions can help you.
 
Level 12
Joined
May 16, 2020
Messages
660
You attach a function to the timer that reads the expiring timer handle id, and can destroy appropriate objects using the handle id as a key. In that case there is no need to have a looping timer, and use count variables and index.

I think I know what you mean, but how would you build "unique" timers? I totally agree that this would be more efficient than checking every 0.1 second.

But yeah, would appreciate if someone else could also check if there is a mistake.
 
Level 12
Joined
Feb 5, 2018
Messages
521
This results in the trees not being destroyed if the ability is cast twice within the timer. I used this formula successfully twice already for other spells (thanks again @HerlySQR ), but here for some reason it doesn't work. Does anyone see the mistake?

This is simply, because you only have one global timer which is overwritten if the ability is cast again before the timer expires.

  • -------- >>>Locals<<< --------
  • Custom script: local real WAIT = 0.10
  • Custom script: local timer TIMER
  • -------- --------
  • Custom script: set TIMER = CreateTimer()
  • Custom script: call TimerStart(TIMER,WAIT,false,null)
  • Custom script: call PolledWait(WAIT)
  • -------- ---Actions here after the timer expires--- --------
  • something something something
  • -------- Remove Leaks --------
  • Custom script: call PauseTimer(TIMER)
  • Custom script: call DestroyTimer(TIMER)
Here is an example what you can do with local timers. It's a quick draft, but should work :)
 
Level 12
Joined
May 16, 2020
Messages
660
@DoomBlade Why is the timer overwritten? The SA_Timer starts only when SA_Index = 1, and keeps going until it is 0. So when casting the ability twice, SA_Index = 2 and shouldn't overwrite the existing timer.

Can I use this template you wrote there only with JASS? And for this I'd need to enter an array, right?
  • Custom script: set TIMER = CreateTimer()
 
Level 12
Joined
Feb 5, 2018
Messages
521
Can I use this template you wrote there only with JASS? And for this I'd need to enter an array, right?

You can write the rest of trigger in gui and no you don't need array. The local timer is never overwritten. :)

Same as with local unit, real, integer, boolean and so on.

You can add more locals at the top of the trigger and instead of using just local timer TIMER, you can create variables and use local timer udg_Timer instead. Then it is easier to reference with triggers using GUI.

So variabless locals are the same as in the example and locals with variables is the same, but you need to put udg_VARIABLE.
 
Level 12
Joined
Feb 5, 2018
Messages
521
Well it's pretty much beyond my knowlegde, but I think I am pretty close :D. You just need to make the angle yourself to make the trees into a circural area around the caster.

Still have some issues here.
1) Trees are not removed after loop, didn't try with hashtable yet.
2) The angle is not set to circucal area around the target.

Maybe if someone can help us finish this, i'll post what I have so far.

It is super close to being complete tho :)

  • Sprout
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
      • (Ability being cast) Equal to Ensnare (Neutral Hostile)
    • Actions
      • -------- >>>Locals<<< --------
      • Custom script: local unit udg_Sprout_Caster = GetTriggerUnit()
      • Custom script: local unit udg_Sprout_Target = GetSpellTargetUnit()
      • Custom script: local real udg_Sprout_Wait = 0.10
      • Custom script: local integer udg_Sprout_Index = 0
      • Custom script: local integer udg_Sprout_Loop = 0
      • Custom script: local integer udg_Sprout_Time = 25
      • Custom script: local timer udg_Sprout_Timer
      • Custom script: local location udg_Sprout_Loc
      • -------- Location --------
      • Set VariableSet Sprout_Loc = (Position of Sprout_Target)
      • -------- Integer --------
      • Custom script: set udg_Sprout_Loop = udg_Sprout_Loop + 1
      • For each (Integer Sprout_Loop) from 1 to 16, do (Actions)
        • Loop - Actions
          • Set VariableSet Sprout_Loc = (Sprout_Loc offset by 150.00 towards 45.00 degrees.)
          • Destructible - Create a Summer Tree Wall at Sprout_Loc facing (Random angle) with scale 1.00 and variation 0
          • Game - Display to (All players) the text: (String(Sprout_Index))
          • Custom script: set udg_Sprout_Index = udg_Sprout_Index + 1
          • Set VariableSet Sprout_Tree[Sprout_Index] = (Last created destructible)
      • -------- Timer --------
      • Custom script: loop
      • Custom script: set udg_Sprout_Timer = CreateTimer()
      • Custom script: call TimerStart(udg_Sprout_Timer,udg_Sprout_Wait,false,null)
      • Custom script: call PolledWait(udg_Sprout_Wait)
      • Custom script: set udg_Sprout_Time = udg_Sprout_Time - 1
      • Game - Display to (All players) the text: (String(Sprout_Time))
      • Custom script: call PauseTimer (udg_Sprout_Timer)
      • Custom script: call DestroyTimer (udg_Sprout_Timer)
      • Custom script: exitwhen udg_Sprout_Time == 0
      • Custom script: call RemoveDestructable( udg_Sprout_Tree[udg_Sprout_Index] )
      • Custom script: set udg_Sprout_Index = 0
      • Custom script: set udg_Sprout_Loop = udg_Sprout_Loop - 1
      • Custom script: set udg_Sprout_Caster = null
      • Custom script: set udg_Sprout_Target = null
      • Custom script: call RemoveLocation (udg_Sprout_Loc)
      • Custom script: endloop

JASS:
function Trig_Sprout_Copy_Conditions takes nothing returns boolean
    if ( not ( GetSpellAbilityId() == 'ACen' ) ) then
        return false
    endif
    return true
endfunction
function Trig_Sprout_Copy_Actions takes nothing returns nothing
    // >>>Locals<<<
    local unit udg_Sprout_Caster = GetTriggerUnit()
    local unit udg_Sprout_Target = GetSpellTargetUnit()
    local real udg_Sprout_Wait = 0.10
    local integer udg_Sprout_Index = 0
    local integer udg_Sprout_Loop = 0
    local integer udg_Sprout_Time = 25
    local timer udg_Sprout_Timer
    local location udg_Sprout_Loc
    // Location
    set udg_Sprout_Loc = GetUnitLoc(udg_Sprout_Target)
    // Integer
    set udg_Sprout_Loop = udg_Sprout_Loop + 1
    set udg_Sprout_Loop = 1
    loop
        exitwhen udg_Sprout_Loop > 16
        set udg_Sprout_Loc = PolarProjectionBJ(udg_Sprout_Loc, 150.00, 45.00)
        call CreateDestructableLoc( 'LTlt', udg_Sprout_Loc, GetRandomDirectionDeg(), 1, 0 )
        call DisplayTextToForce( GetPlayersAll(), I2S(udg_Sprout_Index) )
        set udg_Sprout_Index = udg_Sprout_Index + 1
        set udg_Sprout_Tree[udg_Sprout_Index] = GetLastCreatedDestructable()
        set udg_Sprout_Loop = udg_Sprout_Loop + 1
    endloop
    // Timer
    loop
    set udg_Sprout_Timer = CreateTimer()
    call TimerStart(udg_Sprout_Timer,udg_Sprout_Wait,false,null)
    call PolledWait(udg_Sprout_Wait)
    set udg_Sprout_Time = udg_Sprout_Time - 1
    call DisplayTextToForce( GetPlayersAll(), I2S(udg_Sprout_Time) )
    call PauseTimer (udg_Sprout_Timer)
    call DestroyTimer (udg_Sprout_Timer)
    exitwhen udg_Sprout_Time == 0
    call RemoveDestructable( udg_Sprout_Tree[udg_Sprout_Index] )
    set udg_Sprout_Index = 0
    set udg_Sprout_Loop = udg_Sprout_Loop - 1
    set udg_Sprout_Caster = null
    set udg_Sprout_Target = null
    call RemoveLocation (udg_Sprout_Loc)
    endloop
endfunction
//===========================================================================
function InitTrig_Sprout_Copy takes nothing returns nothing
    set gg_trg_Sprout_Copy = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_Sprout_Copy, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( gg_trg_Sprout_Copy, Condition( function Trig_Sprout_Copy_Conditions ) )
    call TriggerAddAction( gg_trg_Sprout_Copy, function Trig_Sprout_Copy_Actions )
endfunction

EDIT: Added a testmap if anyone is willing to complete or if you want to try to mess with it
 

Attachments

  • Sprout.w3m
    19.9 KB · Views: 29
Last edited:
Level 23
Joined
Jun 26, 2020
Messages
1,838
I was hoping someone give a simple solution no offense but nobody did that, because I didn't get why the indexing didn't work, at the end, if the timer is only to delete the destructibles, the solution is simple:
  • Custom script: local destructible array udg_Tree
  • Set Duration = "Your time"
  • For each integer Int from 1 to 8 do actions
    • Loop - Actions
      • Set TempLoc = "Your point"
      • Destructible - Create tree in TempLoc
      • Set Tree[Int] = (Last created destructible)
      • Custom script: call RemoveLocation(udg_TempLoc)
  • Wait Duration seconds
  • For each integer Int from 1 to 8 do actions
    • Loop - Actions
      • Destructible - Remove Tree[Int]
      • Custom script: set udg_Tree[udg_Int] = null
 
Level 9
Joined
Mar 26, 2017
Messages
376
I'm not sure if I know the exact reason, but if I understood TriggerHappy and DrSuperGood correctly, there is a very small imprecision in waits that can differ across clients. I guess in a case of bad luck where it changes the order of executed code it might result in a difference in object handle Id's that can lead to desync.

In any case in an old map of mine with alot of waits, unfortunately desync occured from time to time. I think it likely was because of the waits. I didn't really use any other desync prone functions in that map. I suppose the more complicated the map, and the more things going on, the bigger chance that a wait causes things to go wrong.
 
Level 23
Joined
Jun 26, 2020
Messages
1,838
I'm not sure if I know the exact reason, but if I understood TriggerHappy and DrSuperGood correctly, there is a very small imprecision in waits that can differ across clients. I guess in a case of bad luck where it changes the order of executed code it might result in a difference in object handle Id's that can lead to desync.

In any case in an old map of mine with alot of waits, unfortunately desync occured from time to time. I think it likely was because of the waits. I didn't really use any other desync prone functions in that map. I suppose the more complicated the map, and the more things going on, the bigger chance that a wait causes things to go wrong.
Hmm, that sounds bad.
 
Level 12
Joined
May 16, 2020
Messages
660
Thanks all.

@DoomBlade I added the circular motion into the testmap, and tried to integrate some of the code which @HerlySQR suggested. But it still doesn't remove the trees properly when multiple casts are done.

@pr114 In DoomBlade's testmap there is a line called "Custom script: call PolledWait(udg_Sprout_Wait)". Isn't this basically the GUI wait?
 

Attachments

  • Sprout v2.w3m
    20.1 KB · Views: 23

Uncle

Warcraft Moderator
Level 63
Joined
Aug 10, 2018
Messages
6,455
It's MUI but has potential to break under extreme circumstances. Repeatedly casting it 1000's of times in a row, with each new cast occurring before the previous cast has expired, could end up reaching the Index limit of 32,768. Will this ever happen? No.
 

Attachments

  • Sprout Uncle.w3m
    19.6 KB · Views: 20

Uncle

Warcraft Moderator
Level 63
Joined
Aug 10, 2018
Messages
6,455
If it's an issue then you can simply use:
  • Custom script: call PolledWait(3.00)
But I see most people use the Wait action in GUI and their maps seem to work fine.

Edit: Here's another version that works even better using Herly's suggestion. No reason to mess around with globals when you can use a local array to contain the trees.
 

Attachments

  • Sprout Uncle 2.w3m
    18.6 KB · Views: 26
Last edited:

Wrda

Spell Reviewer
Level 25
Joined
Nov 18, 2012
Messages
1,864
Wait in this function has a small chance to result in desyncs.
That was just a rumor that happens to be false.
 
Level 9
Joined
Mar 26, 2017
Messages
376
That was just a rumor that happens to be false.
But @TriggerHappy seems to think otherwise?

 
Level 12
Joined
May 16, 2020
Messages
660
If it's an issue then you can simply use:
  • Custom script: call PolledWait(3.00)
But I see most people use the Wait action in GUI and their maps seem to work fine.

Edit: Here's another version that works even better using Herly's suggestion. No reason to mess around with globals when you can use a local array to contain the trees.

Thanks a lot Uncle. I didn't manage to implement this, but now I see what went wrong...

@pr114 @Wrda I guess just to stay on the save side I will use polled waits. I have some other "waits" in my map as well, as sometimes I was forced to add a small wait, otherwise the spell didn't work correctly.
 
Level 9
Joined
Mar 26, 2017
Messages
376
There is no difference in polled wait and regular wait in this regard, because internally they still make use of TriggerSleepAction. The only added value of PolledWait is that the waits do not keep running while the game is paused. Because if it used for certain spells/triggers, players may disrupt the spell effect by pausing the game at the right time. If you intend to use polled wait to reduce desync, I wouldn't bother with it at all.

In all fairness, the chance of a Wait causing desync is quite small (it is dependant on number of players, number of uses of Wait and number of operations happening in the game). In the topic I linked in my earlier post, @Wrda actually admitted there is a risk of desync. So I'm a bit surprised with his post above :D But in that topic he also said he believed the risk to be very small. With which I agree, but let me elaborate.

From my own experiences. I have battled with desync alot. I develop a big strategy map, and I mainly developed in the 1.29-1.31 era. At that time the patches were fairly unstable and desync seemed to be much more common. I developed and playtested tons during this period, and there was a harsh time were it was very common at least a few people fell out during the match. Every single match. (and I didn't use getlocalplayer or the like, just simple GUI)

Now I knew that some desync were attributable to the issue with Gameplay Constants causing desync. But desync happened all throughout the game (even mid and endgame). I cannot be entirely sure, but I think a lot of desync was attributable to Waits. Now I have to remark, my map was a massive strategy game for 12-24 players, had tons of units and used tons of Wait.

I have played the game a bunch recently again in 2021 era, and with 24 player lobbies I still have seen few if any desync. I noticed now for 1.32 patch the problem has been alleviated, and the desync is not so commonplace anymore. Though I don't know if the patch eliminated desync risk from Waits, or just desync from other sources.

Still, if you want to make the map the very best you can. You code it in script instead of GUI (preferably Lua). If you code in lua, it is extremely easy to avoid Waits altogether.

If you either use GUI and JASS, I would understand why it would be prohibitive to eliminate the Waits. As you understand from my story and experiences from other user experiences, the Wait problem is not severe.

If you're unsure, you can always hold out on removing Waits. If your future playtest shows up any desync, you could look further into removing wait after all.
 
Level 12
Joined
May 16, 2020
Messages
660
Thank you! So far I haven't tested my map online at all, so no idea how it will fare online in regards to desyncs (but it's rather a map for my friends and myself anyhow). But gonna write this on a note, you never know when this knowledge will come in handy :)
 

Wrda

Spell Reviewer
Level 25
Joined
Nov 18, 2012
Messages
1,864
In all fairness, the chance of a Wait causing desync is quite small (it is dependant on number of players, number of uses of Wait and number of operations happening in the game). In the topic I linked in my earlier post, @Wrda actually admitted there is a risk of desync. So I'm a bit surprised with his post above :D But in that topic he also said he believed the risk to be very small. With which I agree, but let me elaborate.
Never have I ever said "there's a risk of desync from waits", nor TriggerHappy, nor Dr Super Good. Handle stack corruption isn't the same as desync, perhaps that's what confusing you, and maybe this help clarify something:

I played a map online I made a while back for a few days that uses various systems that destroy triggers -.-. The games would last anywhere from 5 minutes to 4 hours. I'd play about 18 hours a day. I believe I played this map for a total of 4 days.

The only bug I ever ran into in that map was a command for giving gold and lumber to other players on your team. The reason I had bugs with this command was because I didn't put too much safety into it as I was primarily focused on speed... people kept trying to break it by inputting text instead of numbers =p. In games where people didn't attempt to trade, there were never any desyncs or crashes. In all games, there were 0 desyncs.

Now, it's impossible to provide solid proof. When used improperly, you can create handle stack corruption, and so in some cases, destroying triggers and timers will kill the map its inside of. However, in this case, I can assure you that this does not cause handle stack corruption.

Furthermore, why is this not just on a TriggerRegisterTimedEvent over the TriggerRegisterTimerExpireEvent (just winging these). The reason is that a timer is lighter than a trigger and thus slightly faster =). Why? It doesn't use a stack and instead calls a function directly : p.

So in response to this question-

I have observed it enough times to say that in this case I "assume" that there is no handle stack corruption.

Code isn't magic, and if something doesn't make any sense then you either aren't seeing the whole picture or it's not true ^_^. Now, this is just a theory on my part, but I believe the only way for a trigger to remain active even 20 seconds or w/e after it was executed (no sleeps or anything) is if other triggers are running through that period with multiple actions and what not. Now, I have not tested this so I don't know for sure. What I do know is that triggers take precedence when they begin running. When you execute a trigger from another trigger, that executed trigger will run through and then will return to the initial trigger. In this way, if you did this

Trigger 1 executes Trigger 2
Trigger 1 executes Trigger 3
Trigger 2 executes Trigger 4
Trigger 3 executes Trigger 2

Trigger 1 Start
1 -> 2
2 -> 4
2 <- 4
2 <- 1
1 -> 3
1 <- 3
Trigger 1 Continue and End
2 -> 4
4 <- 2
Trigger 2 Continue and End

So I guess if you have multiple actions on a trigger and other triggers begin running on other actions, maybe those other triggers will take precedence, but that makes absolutely no sense ^_^. What makes more sense is that those other triggers get added to the stack and execute one after another as soon as the current trigger finishes =).

Now, when destroying a trigger, a trigger obviously can't run anymore if it's destroyed... so, wc3 will destroy it at the end. If you attempt to destroy it 2x, that will probably cause problems as it frees up the memory 2x (2nd time is null, so it just sends the handle counter back 1 more than it's supposed to go).

Now, apparently people tried this and thought, wth, it only destroyed once (I wasn't one of them so I can't vouch), so people tried a TriggerSleepAction in between. Because the trigger was asleep, it thought the trigger was finish so it cleared out the memory. The function that was on the trigger runs up again (no following functions will run as the trigger is cleared) and then it sees another DestroyTrigger, so it clears it up again (clearing nothing as no trigger exists, but sending the handle counter back 1).

This is the only way I can see handle stack corruption.

Now with timers, when a function on a timer is set to run while one is already running and both destroy that timer, handle stack corruption can occur. If the other one runs after the timer is destroyed, then you don't have handle stack corruption. If the timer is destroyed twice or multiple timers (multiple functions all destroying same timer and running on same timer) then that will obviously cause handle stack corruption : ).

Now, if I am wrong in any of this (a lot of it is just theory), then please feel free to intervene. I don't claim to know the inner workings of wc3, so all of this has only come from logic that has sprung up from tests =).

In finality, i can't see how a trigger that is run once can destroy itself twice ^_^. Playing the map has shown me this concept and I have never seen an example that proves otherwise. The only examples I have ever seen for handle stack corruption is the very specific one I mentioned above, in which you destroy a trigger, put it to sleep, and then destroy it again. I also provided the reasoning as to why the corruption occurs.
I acknowledge the flaws waits have, but the comments on them saying "waits can cause desync" haven't been backed up by anyone. I was unable to locate any post from Dr Super Good or TriggerHappy claiming waits can cause desync and providing evidence for those claims. Even so I highly doubt it does because I've played maps that use a lot of waits constantly and out of all times, there was no desync.
But it's true, it's hard to catch the source of desync if gameplay constants, models, textures, object editor data and local player and blacklisted functions are not used. I don't know if anyone did that experiment or not, but the rumors have been living on and haven't died yet and those two poor functions are taking the blame for everything else :p
 
Level 9
Joined
Mar 26, 2017
Messages
376
Never have I ever said "there's a risk of desync from waits", nor TriggerHappy, nor Dr Super Good. Handle stack corruption isn't the same as desync, perhaps that's what confusing you, and maybe this help clarify something:


I acknowledge the flaws waits have, but the comments on them saying "waits can cause desync" haven't been backed up by anyone. I was unable to locate any post from Dr Super Good or TriggerHappy claiming waits can cause desync and providing evidence for those claims. Even so I highly doubt it does because I've played maps that use a lot of waits constantly and out of all times, there was no desync.
But it's true, it's hard to catch the source of desync if gameplay constants, models, textures, object editor data and local player and blacklisted functions are not used. I don't know if anyone did that experiment or not, but the rumors have been living on and haven't died yet and those two poor functions are taking the blame for everything else :p
Sorry, Wrda, I must have mistunderstood you then.

I assumed in that context, since TriggerSleepAction is run locally and has small imprecisions, it meant having a desync as a consequence.

I don't know at all how the game engine works, but it sounds to me like if there is a chance of a tiny imprecision between clients, it could mean there comes a small chance that the order in which operations are executed are different. And if it's unlucky enough that the wrong 2 operations are mixed up that are critical (such as object creation). This is probably a very rare scenario.
I have to add: I would expect a desync related to gameplay constant to come to surface somewhat early in a game. Namely the first situation the constant is used is encountered. Whereas Wait issues could be a risk throughout the game. Especially late game, if there are more objects on the map (depends on map type).

Based on that knowledge, I believed the desyncs I saw in my own map to be attributable to Wait. Unfortunately there is no way to know it certain. Might have been something else after all.

I'd say this topic is similar to the warnings that player slot dependency in the main function can desync. I haven't read a topic on the Hive where it was proven that they can cause desync, but I'm pretty convinced it IS actually a desync risk. It is known that functions in main run async. That's usually not a problem, but one of the few game states that can be influenced during game loading is the player slot status. If a player disconnects or exits out during game loading. Then, if a player with a slow pc hasn't reached the function that checks slot status, they could have a different outcome than a player with fast pc.

I agree and now see in hindsight my comment 'wait can cause desync' in a topic like this was too strong :p
 
Status
Not open for further replies.
Top