• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.

Desync when calling Preloader() locally

Level 2
Joined
Apr 19, 2022
Messages
8
I have been sitting in the editor of version 1.26 for a very long time, but now I decided to switch to the functionality of Reforge, but to my surprise, when saving a map in Reforge, a desynchronization occurs in this place, what has changed in the new versions of Warcraft, because when saving a map in 1.26 everything works? Help fix the desynchronization

Code causing desynchronization

JASS:
  if GetLocalPlayer()==p then
          call Preloader("save2\\Multiplayer\\DungeonRunners\\"+udg_Name[GetPlayerId(p)+1]+".pld")
        endif


JASS:
function Trig_InitialPlayersLoad_Actions takes nothing returns nothing
    local integer i=0
    local player p
    loop
      exitwhen i>15
      set udg_Player[i]=Player(i)
      set i=i+1
    endloop
    set udg_ColoredName[1] = ( ( "|c00FF0000" + GetPlayerName(Player(0)) ) + "|r" )
    set udg_ColoredName[2] = ( ( "|c000000FF" + GetPlayerName(Player(1)) ) + "|r" )
    set udg_ColoredName[3] = ( ( "|c0000FFC2" + GetPlayerName(Player(2)) ) + "|r" )
    set udg_ColoredName[4] = ( ( "|c004B005D" + GetPlayerName(Player(3)) ) + "|r" )
    set udg_ColoredName[5] = ( ( "|c00FFFF00" + GetPlayerName(Player(4)) ) + "|r" )
    set udg_ColoredName[6] = ( ( "|c00FF8B00" + GetPlayerName(Player(5)) ) + "|r" )
    set udg_ColoredName[7] = ( ( "|c0000C800" + GetPlayerName(Player(6)) ) + "|r" )
    set udg_ColoredName[8] = ( ( "|c00FF52AE" + GetPlayerName(Player(7)) ) + "|r" )
    set udg_ColoredName[9] = ( ( "|c00888888" + GetPlayerName(Player(8)) ) + "|r" )
    set udg_ColoredName[10] = ( ( "|c009FC4FF" + GetPlayerName(Player(9)) ) + "|r" )
    set udg_ColoredName[11] = ( ( "|c00004400" + GetPlayerName(Player(10)) ) + "|r" )
    set udg_ColoredName[12] = ( ( "|c00543200" + GetPlayerName(Player(11)) ) + "|r" )
    set i=1
    loop
        exitwhen i>12
        set udg_Name[i] = GetPlayerName(udg_Player[i-1])
        set i=i+1
    endloop
    set i=0
    loop
      exitwhen i>11
      set p=udg_Player[i]
     // call NameLoad(p)
      if GetPlayerController(p)==MAP_CONTROL_USER and GetPlayerSlotState(p)==PLAYER_SLOT_STATE_PLAYING then
        if GetLocalPlayer()==p then
          call Preloader("save2\\Multiplayer\\DungeonRunners\\"+udg_Name[GetPlayerId(p)+1]+".pld")
        endif
      endif
      set i=i+1
    endloop
endfunction

//===========================================================================
function InitTrig_InitialPlayersLoad takes nothing returns nothing
    set gg_trg_InitialPlayersLoad = CreateTrigger()
    call TriggerAddAction( gg_trg_InitialPlayersLoad, function Trig_InitialPlayersLoad_Actions )
endfunction
 
Two things to watch out for:
  1. This thread (Known causes of desync) seems to suggest that certain player controller/player slot-state checks can cause desyncs (I haven't personally verified this, but it may be worth testing to see if that is related since you have GetPlayerController() and GetPlayerSlotState()).
  2. Could you give an example of the file you're preloading? jassdoc says that Preloader returns asynchronous values, so depending on what it contains and how you use those results, you can end up disconnecting players:
Code:
@note If you use `Preloader` to load some values into your map, these values
are very likely to be different for each player (since the player might not 
even have local files enabled), so treat them as async values.

I assume you're using a save/load system, so if you're loading a string that you'll then decode into data, you'll want to make sure you synchronize that whole string across all players before handling it.
 
Level 2
Joined
Apr 19, 2022
Messages
8
Yes, you are right! I use the save loading system, finally found someone who understands this topic. I think the problem is in data synchronization, but I can't figure out what to do?

I tried to remove GetLocalPlayer, desynchronization did not happen, but the hero will only load as the last player, I may be wrong that the problem is in GetLocalPlayer, but I would really like to fix it

Here is my file: How to synchronize data?

JASS:
function PreloadFiles takes nothing returns nothing

    call PreloadStart()
    call Preload( "" )
call SetPlayerTechMaxAllowed(Player(15),1,7)
call SetPlayerTechMaxAllowed(Player(14),1,1)
  //" )
    call Preload( "" )
call SetPlayerTechMaxAllowed(Player(15),2,536877998)
call SetPlayerTechMaxAllowed(Player(14),2,1)
  //" )
    call Preload( "" )
call SetPlayerTechMaxAllowed(Player(15),3,536870992)
call SetPlayerTechMaxAllowed(Player(14),3,1)
  //" )
    call Preload( "" )
call SetPlayerTechMaxAllowed(Player(15),4,536870922)
call SetPlayerTechMaxAllowed(Player(14),4,1)
  //" )
    call Preload( "" )
call SetPlayerTechMaxAllowed(Player(15),5,536870922)
call SetPlayerTechMaxAllowed(Player(14),5,1)
  //" )
    call Preload( "" )
call SetPlayerTechMaxAllowed(Player(15),6,1630548276)
call SetPlayerTechMaxAllowed(Player(14),6,1)
  //" )
    call Preload( "" )
call SetPlayerTechMaxAllowed(Player(15),7,1630549057)
call SetPlayerTechMaxAllowed(Player(14),7,1)
  //" )
    call Preload( "" )
call SetPlayerTechMaxAllowed(Player(15),8,1630548293)
call SetPlayerTechMaxAllowed(Player(14),8,1)
  //" )
    call Preload( "" )
call SetPlayerTechMaxAllowed(Player(15),9,536870912)
call SetPlayerTechMaxAllowed(Player(14),9,1)
  //" )
    call Preload( "" )
call SetPlayerTechMaxAllowed(Player(15),10,536870928)
call SetPlayerTechMaxAllowed(Player(14),10,1)
  //" )
    call Preload( "" )
call SetPlayerTechMaxAllowed(Player(15),11,536870924)
call SetPlayerTechMaxAllowed(Player(14),11,1)
  //" )
    call Preload( "" )
call SetPlayerTechMaxAllowed(Player(15),12,536870927)
call SetPlayerTechMaxAllowed(Player(14),12,1)
  //" )
    call Preload( "" )
call SetPlayerTechMaxAllowed(Player(15),13,536870912)
call SetPlayerTechMaxAllowed(Player(14),13,1)
  //" )
    call Preload( "" )
call SetPlayerTechMaxAllowed(Player(15),14,1747988549)
call SetPlayerTechMaxAllowed(Player(14),14,1)
  //" )
    call Preload( "" )
call SetPlayerTechMaxAllowed(Player(15),15,1764766298)
call SetPlayerTechMaxAllowed(Player(14),15,1)
  //" )
    call Preload( "" )
call SetPlayerTechMaxAllowed(Player(15),16,1821875853)
call SetPlayerTechMaxAllowed(Player(14),16,2)
  //" )
    call Preload( "" )
call SetPlayerTechMaxAllowed(Player(15),17,1837996702)
call SetPlayerTechMaxAllowed(Player(14),17,2)
  //" )
    call Preload( "" )
call SetPlayerTechMaxAllowed(Player(15),18,1764766537)
call SetPlayerTechMaxAllowed(Player(14),18,1)
  //" )
    call Preload( "" )
call SetPlayerTechMaxAllowed(Player(15),19,1805426317)
call SetPlayerTechMaxAllowed(Player(14),19,2)
  //" )
    call Preload( "" )
call SetPlayerTechMaxAllowed(Player(15),20,1764766531)
call SetPlayerTechMaxAllowed(Player(14),20,1)
  //" )
    call Preload( "" )
call SetPlayerTechMaxAllowed(Player(15),21,536870921)
call SetPlayerTechMaxAllowed(Player(14),21,1)
  //" )
    call Preload( "" )
call SetPlayerTechMaxAllowed(Player(15),22,536870912)
call SetPlayerTechMaxAllowed(Player(14),22,1)
  //" )
    call Preload( "" )
call SetPlayerTechMaxAllowed(Player(15),23,536870912)
call SetPlayerTechMaxAllowed(Player(14),23,1)
  //" )
    call Preload( "" )
call SetPlayerTechMaxAllowed(Player(15),24,536870917)
call SetPlayerTechMaxAllowed(Player(14),24,1)
  //" )
    call Preload( "" )
call SetPlayerTechMaxAllowed(Player(15),25,536870913)
call SetPlayerTechMaxAllowed(Player(14),25,1)
  //" )
    call Preload( "" )
call SetPlayerTechMaxAllowed(Player(15),26,536870912)
call SetPlayerTechMaxAllowed(Player(14),26,1)
  //" )
    call Preload( "" )
call SetPlayerTechMaxAllowed(Player(15),27,536870912)
call SetPlayerTechMaxAllowed(Player(14),27,1)
  //" )
    call Preload( "" )
call SetPlayerTechMaxAllowed(Player(15),28,536870912)
call SetPlayerTechMaxAllowed(Player(14),28,1)
  //" )
    call Preload( "" )
call SetPlayerTechMaxAllowed(Player(15),29,536870912)
call SetPlayerTechMaxAllowed(Player(14),29,1)
  //" )
    call Preload( "" )
call SetPlayerTechMaxAllowed(Player(15),30,536870912)
call SetPlayerTechMaxAllowed(Player(14),30,1)
  //" )
    call Preload( "" )
call SetPlayerTechMaxAllowed(Player(15),31,536870912)
call SetPlayerTechMaxAllowed(Player(14),31,1)
  //" )
    call Preload( "" )
call SetPlayerTechMaxAllowed(Player(15),32,536870912)
call SetPlayerTechMaxAllowed(Player(14),32,1)
  //" )
    call Preload( "" )
call SetPlayerTechMaxAllowed(Player(15),33,536870912)
call SetPlayerTechMaxAllowed(Player(14),33,1)
  //" )
    call Preload( "" )
call SetPlayerTechMaxAllowed(Player(15),34,536870912)
call SetPlayerTechMaxAllowed(Player(14),34,1)
  //" )
    call Preload( "" )
call SetPlayerTechMaxAllowed(Player(15),35,536870912)
call SetPlayerTechMaxAllowed(Player(14),35,1)
  //" )
    call Preload( "" )
call SetPlayerTechMaxAllowed(Player(15),36,536870912)
call SetPlayerTechMaxAllowed(Player(14),36,1)
  //" )
    call Preload( "" )
call SetPlayerTechMaxAllowed(Player(15),37,536870912)
call SetPlayerTechMaxAllowed(Player(14),37,1)
  //" )
    call Preload( "" )
call SetPlayerTechMaxAllowed(Player(15),38,536870912)
call SetPlayerTechMaxAllowed(Player(14),38,1)
  //" )
    call Preload( "" )
call SetPlayerTechMaxAllowed(Player(15),39,536870912)
call SetPlayerTechMaxAllowed(Player(14),39,1)
  //" )
    call Preload( "" )
call SetPlayerTechMaxAllowed(Player(15),40,536870912)
call SetPlayerTechMaxAllowed(Player(14),40,1)
  //" )
    call Preload( "" )
call SetPlayerTechMaxAllowed(Player(15),41,536870912)
call SetPlayerTechMaxAllowed(Player(14),41,1)
  //" )
    call Preload( "" )
call SetPlayerTechMaxAllowed(Player(15),42,536870912)
call SetPlayerTechMaxAllowed(Player(14),42,1)
  //" )
    call Preload( "" )
call SetPlayerTechMaxAllowed(Player(15),43,536870912)
call SetPlayerTechMaxAllowed(Player(14),43,1)
  //" )
    call Preload( "" )
call SetPlayerTechMaxAllowed(Player(15),44,536870912)
call SetPlayerTechMaxAllowed(Player(14),44,1)
  //" )
    call Preload( "" )
call SetPlayerTechMaxAllowed(Player(15),45,536870912)
call SetPlayerTechMaxAllowed(Player(14),45,1)
  //" )
    call Preload( "" )
call SetPlayerTechMaxAllowed(Player(15),46,536870912)
call SetPlayerTechMaxAllowed(Player(14),46,1)
  //" )
    call Preload( "" )
call SetPlayerTechMaxAllowed(Player(15),47,536870912)
call SetPlayerTechMaxAllowed(Player(14),47,1)
  //" )
    call Preload( "" )
call SetPlayerTechMaxAllowed(Player(15),48,536870912)
call SetPlayerTechMaxAllowed(Player(14),48,1)
  //" )
    call Preload( "" )
call SetPlayerTechMaxAllowed(Player(15),49,536870912)
call SetPlayerTechMaxAllowed(Player(14),49,1)
  //" )
    call Preload( "" )
call SetPlayerTechMaxAllowed(Player(15),50,536870912)
call SetPlayerTechMaxAllowed(Player(14),50,1)
  //" )
    call Preload( "" )
call SetPlayerTechMaxAllowed(Player(15),51,536870912)
call SetPlayerTechMaxAllowed(Player(14),51,1)
  //" )
    call Preload( "" )
call SetPlayerTechMaxAllowed(Player(15),52,536870913)
call SetPlayerTechMaxAllowed(Player(14),52,1)
  //" )
    call Preload( "" )
call SetPlayerTechMaxAllowed(Player(15),53,1082523648)
call SetPlayerTechMaxAllowed(Player(14),53,1)
  //" )
    call Preload( "" )
call SetPlayerTechMaxAllowed(Player(15),54,172935768)
call SetPlayerTechMaxAllowed(Player(14),54,2)
  //" )
    call Preload( "" )
call SetPlayerTechMaxAllowed(Player(15),55,1226180937)
call SetPlayerTechMaxAllowed(Player(14),55,1)
  //" )
    call Preload( "" )
call SetPlayerTechMaxAllowed(Player(15),56,536870917)
call SetPlayerTechMaxAllowed(Player(14),56,1)
  //" )
    call Preload( "" )
call SetPlayerTechMaxAllowed(Player(15),57,441079156)
call SetPlayerTechMaxAllowed(Player(14),57,2)
  //" )
    call Preload( "" )
call SetPlayerTechMaxAllowed(Player(15),58,536870995)
call SetPlayerTechMaxAllowed(Player(14),58,1)
  //" )
    call Preload( "" )
call SetPlayerTechMaxAllowed(Player(15),59,536871167)
call SetPlayerTechMaxAllowed(Player(14),59,1)
  //" )
    call Preload( "" )
call SetPlayerTechMaxAllowed(Player(15),602,1636804050)
call SetPlayerTechMaxAllowed(Player(14),602,1)
  //" )
    call Preload( "" )
endfunction
function recyclebin takes nothing returns nothing
//" )
    call PreloadEnd( 0.0 )

endfunction
 
Do you have a link to the save/load system you're using? Or are you able to share the code that "loads" the data? Chances are that that is where the desync occurs, since that is where the asynchronous data actually gets "used" to do something meaningful (e.g. creating a hero, granting them stats/items, etc.).

If you want to do a bit of reading, I strongly recommend reading up on these two tutorials:
These will give you an overview of why save/load systems are set up the way they are. At a high level, the saving process goes as follows:
  1. You gather all the data you want to save
  2. Encode it into a string (or a set of integers in your case)
  3. Save that data into a .pld file
However, the way it is stored in the .pld file is important. Preloader(...) allows us to run that local file as a script, but in order to get "data" from it, we need to run functions that store that data somewhere. That's why most preload files will look something like this:
JASS:
function PreloadFiles takes nothing returns nothing

    call PreloadStart()
    call Preload( "" )
call BlzSetAbilityTooltip(1097690227, "Blood Mage (Hale Magefire)
B:2|RtHe@`QWE(>e(s4&XYPy", 1)
//" )
    call Preload( "" )
endfunction
function a takes nothing returns nothing
 //" )
    call PreloadEnd( 0.0 )

endfunction

As mentioned in the File I/O tutorial linked above, there are a bunch of different natives you can use to store the saved data so you can read it back. In your case, it seems like the author was using SetPlayerTechMaxAllowed to store integers.

So once you have it saved, at a high-level, the loading process goes as follows:
  1. You run the preload file via Preloader(...), which will then populate the data somewhere you can read back
  2. You can then read the data back into some variable locally (e.g. BlzGetAbilityTooltip for strings, or GetPlayerTechMaxAllowed in your case for integers)
  3. Once you have that data locally, you'll need to sync the data to all other players before using it for logic
This strategy largely hasn't changed at a high-level. The main differences with reforged is that we can now use BlzSetAbilityTooltip to store the data from the .pld file and then BlzGetAbilityTooltip to read it back in our map's code. And then as for syncing, the main difference in reforged is that we have fancy new sync natives to use:

So typically, you'll use BlzSendSyncData("SOME_PREFIX", myStringData) to sync some data, and then BlzTriggerRegisterPlayerSyncEvent to "use" it once that data has become synchronized across all players.

I'm guessing the system you're using already syncs the data back in some way (likely using one of the methods mentioned in Lizreu's tutorial), so if you share that logic I can give some recommendations.
 
Level 2
Joined
Apr 19, 2022
Messages
8
It is surprising that when saving in 1.26, there were no such problems, but in Reforge there is already an additional "mandatory" synchronization.

In the attachment DATA MANAGER, but it is only the basis from which the auto SaveLoad system was written. (It is unlikely that this is relevant to watch)

So typically, you'll use BlzSendSyncData("SOME_PREFIX", myStringData) to sync some data, and then BlzTriggerRegisterPlayerSyncEvent to "use" it once that data has become synchronized across all players.

Based on the material I read, I don't have synchronization via BlzSendSyncData and BlzTriggerRegisterPlayerSyncEvent, which means that desynchronization occurs.
Tell me, based on the provided code, where should I insert the new Blz functions for synchronization?

Here is my download code:
JASS:
function LoadPlayerTalentTable takes player p returns nothing
    local unit u=PlayerCharacter(p)
    local integer i=TALANTS
    loop
      exitwhen i==0
      if udg_SaveInt[21+i]<0 or udg_SaveInt[21+i]>10  then
        set udg_SaveInt[21+i]=0
      endif
      call SetUnitTalentLevel(u,i,udg_SaveInt[21+i])
      //call BJDebugMsg (I2S(21+i)+"="+I2S(udg_SaveInt[21+i]))
      set i=i-1
    endloop
    set u=null
    
endfunction

function CheckNumbers takes nothing returns boolean
    local integer lvl=udg_SaveInt[2]/200+1
    local integer i=0
    local integer i2
    local integer i3
    if udg_SaveInt[2]>200*udg_Const_CurMaxLevel then
      return false
    endif
    if udg_SaveInt[3]+udg_SaveInt[4]+udg_SaveInt[5]>28+lvl*2 then
      return false
    endif
    if udg_SaveInt[10]>GetHeroMSL(lvl) or udg_SaveInt[11]>GetHeroMSL(lvl) or udg_SaveInt[12]>GetHeroMSL(lvl) or udg_SaveInt[13]>GetHeroMSL(lvl) then
      return false
    endif
    if udg_SaveInt[10]+udg_SaveInt[11]+udg_SaveInt[12]+udg_SaveInt[13]-lvl/5>lvl then
      return false
    endif
    loop
      exitwhen i>4
      set i2=i+1
      loop
        exitwhen i2>5
        if udg_SaveInt[15+i]!=0 and udg_SaveInt[15+i]==udg_SaveInt[15+i2] then
          return false
        endif
        set i2=i2+1
      endloop
      set i=i+1
    endloop
    set i=22
    set i2=0
    loop
      exitwhen i>51
      set i2=i2+udg_SaveInt[i]
      set i=i+1
    endloop
    if i2>lvl/5 then
      return false
    endif
    set i=udg_SaveInt[22]+udg_SaveInt[23]+udg_SaveInt[24]
    set i2=udg_SaveInt[25]+udg_SaveInt[26]+udg_SaveInt[27]
    set i3=udg_SaveInt[28]+udg_SaveInt[29]+udg_SaveInt[30]
    if (i<5 and i2+i3>0) or (i2<5 and i3>0) then
      return false
    endif
    set i=udg_SaveInt[32]+udg_SaveInt[33]+udg_SaveInt[34]
    set i2=udg_SaveInt[35]+udg_SaveInt[36]+udg_SaveInt[37]
    set i3=udg_SaveInt[38]+udg_SaveInt[39]+udg_SaveInt[40]
    if (i<5 and i2+i3>0) or (i2<5 and i3>0) then
      return false
    endif
    set i=udg_SaveInt[42]+udg_SaveInt[43]+udg_SaveInt[44]
    set i2=udg_SaveInt[45]+udg_SaveInt[46]+udg_SaveInt[47]
    set i3=udg_SaveInt[48]+udg_SaveInt[49]+udg_SaveInt[50]
    if (i<5 and i2+i3>0) or (i2<5 and i3>0) then
      return false
    endif
    if udg_SaveInt[6]=='AIsr' or udg_SaveInt[7]=='AIsr' or udg_SaveInt[8]=='AIsr' or udg_SaveInt[9]=='AIsr' then
      return false
    endif
    return true
endfunction

function DestroyThem takes nothing returns nothing
  local integer i=1
  call FlushGameCache(udg_cache)
  loop
    exitwhen i>udg_SaveSize
    if i==60 then
        set i=602
    endif
    call SetPlayerTechMaxAllowed(Player(15),i,0)
    call SetPlayerTechMaxAllowed(Player(14),i,0)
    if i==602 then
        set i=udg_SaveSize
    endif
    set i=i+1
  endloop
endfunction

function ReadInteger takes integer Offset,player p returns nothing
  set udg_SyncInteger=Offset
  set udg_SyncPlayer=p
  call ExecuteFunc("ReadInteger_f")
endfunction

function ReadInteger_f3 takes nothing returns nothing
  local trigger t=GetTriggeringTrigger()
  local integer id=GetHandleId(t)
  local player p=LoadPlayerHandle(udg_h,id,1)
  local integer offset=LoadInteger(udg_h,id,2)
  call SaveInteger(udg_h,100+GetPlayerId(p),offset,GetStoredInteger(udg_cache,I2S(offset),I2S(GetPlayerId(p))))
  call FlushChildHashtable(udg_h,id)
  call DestroyTrigger(t)
  set t=null
endfunction


function ReadInteger_f2 takes nothing returns nothing
  local trigger t=GetTriggeringTrigger()
  local integer id=GetHandleId(t)
  local player p=LoadPlayerHandle(udg_h,id,1)
  local integer offset=LoadInteger(udg_h,id,2)
  local integer a
  call TriggerSyncStart()
  if GetLocalPlayer()==p then
      call SyncStoredInteger(udg_cache,I2S(offset),I2S(GetPlayerId(p)))
  endif
  call TriggerSyncReady()
  set a=GetStoredInteger(udg_cache,I2S(offset),I2S(GetPlayerId(p)))
  call TriggerClearActions(t)
  call TriggerAddAction(t,function ReadInteger_f3)
  call TriggerRegisterTimerEvent(t,1.,false)
  set t=null
endfunction

function ReadInteger_f takes nothing returns nothing
  local player p=udg_SyncPlayer
  local integer a=udg_SyncInteger
  local integer offset=udg_SyncInteger
  local string sid=I2S(GetPlayerId(p))
  local string soffset=I2S(offset)
  local trigger t=CreateTrigger()
  local integer id=GetHandleId(t)
  if GetLocalPlayer()==p then
    if GetPlayerTechMaxAllowed(udg_Player[14] , offset) == 2 then
      set a = - GetPlayerTechMaxAllowed(udg_Player[15] , offset)
    elseif GetPlayerTechMaxAllowed(udg_Player[14] , offset) == 1 then
      set a = GetPlayerTechMaxAllowed(udg_Player[15] , offset)
    elseif GetPlayerTechMaxAllowed(udg_Player[14] , offset) == 4 then
      set a = -2147483648
    else
      set a = 0
    endif
    call StoreInteger(udg_cache , soffset ,sid, a)
  endif
  call TriggerSyncStart()
  if GetLocalPlayer() == p then
      call SyncStoredInteger(udg_cache,soffset,sid)
  endif
  call TriggerSyncReady()
  set a=GetStoredInteger(udg_cache,soffset,sid)
  call SavePlayerHandle(udg_h,id,1,p)
  call SaveInteger(udg_h,id,2,offset)
  call TriggerAddAction(t,function ReadInteger_f2)
  call TriggerRegisterTimerEvent(t,1.,false)
  set t=null
endfunction

function CheckCode takes player p returns boolean
if udg_SaveInt[602]==-536870912 then
   // call BJDebugMsg("602=52"+I2S(udg_SaveInt[602])+"="+I2S(CheckSum1()))
        set udg_SaveInt[602]=udg_SaveInt[52]
    endif
    
    if udg_SaveInt[602]!=CheckSum1() then
      call BJDebugMsg("602="+I2S(udg_SaveInt[602])+"="+I2S(CheckSum1()))
      return false
    endif
    if udg_SaveInt[53]!=CheckSum2() then
    call BJDebugMsg("53="+I2S(udg_SaveInt[53])+"="+I2S(CheckSum2()))
      return false
    endif
    if udg_SaveInt[54]!=CheckSum3() then
    call BJDebugMsg("54="+I2S(udg_SaveInt[54])+"="+I2S(CheckSum3()))
      return false
    endif
    if udg_SaveInt[55]!=CheckSum4(p) then
    call BJDebugMsg("55="+I2S(udg_SaveInt[55])+"="+I2S(CheckSum4(p)))
      return false
    endif
    if udg_SaveInt[56]!=CheckSum5(p) then
    call BJDebugMsg("56="+I2S(udg_SaveInt[56])+"="+I2S(CheckSum5(p)))
      return false
    endif
    if udg_SaveInt[57]!=CheckSum6(p) then
    call BJDebugMsg("57="+I2S(udg_SaveInt[57])+"="+I2S(CheckSum6(p)))
      return false
    endif
    return true
endfunction

function LoadCharacter takes nothing returns nothing
    local player p=udg_SaveP
    local integer id=AdvancedModel(udg_SaveInt[14])
    local unit u
    local integer pid=1+GetPlayerId(p)
    local integer i=1
    if udg_SaveInt[1]==1 then
      set udg_SaveInt[58]=0
    endif
    if not CheckNumbers() then
      return
    endif
    set u=CreateUnit(p,id,GetPlayerStartLocationX(p),GetPlayerStartLocationY(p),0.)
    if GetUnitAbilityLevel(u,'AIsr')!=1 then
      call KillUnit(u)
      call RemoveUnit(u)
      set i=0
      if GetLocalPlayer()==p then
        set i=pid
      endif
      call Player(i)
      return
    endif
    call SaveInteger(udg_h,100+GetPlayerId(p),-1,udg_SaveInt[14])
    set udg_Character[pid]=u
    call SetHeroXP(u,udg_SaveInt[2],false)
    call SaveInteger(udg_h,GetHandleId(u),99,udg_SaveInt[3]+udg_SaveInt[4]+udg_SaveInt[5])
    call SetHeroStat(u,0,udg_SaveInt[3])
    call SetHeroStat(u,1,udg_SaveInt[4])
    call SetHeroStat(u,2,udg_SaveInt[5])
    if udg_SaveInt[6]==0 then
        if udg_SaveInt[7]!=0 then
          set udg_SaveInt[6]=udg_SaveInt[7]
          set udg_SaveInt[10]=udg_SaveInt[11]
          set udg_SaveInt[7]=0
          set udg_SaveInt[11]=0
        else
            if udg_SaveInt[8]!=0 then
              set udg_SaveInt[6]=udg_SaveInt[8]
              set udg_SaveInt[10]=udg_SaveInt[12]
              set udg_SaveInt[8]=0
              set udg_SaveInt[12]=0
            else
                if udg_SaveInt[9]!=0 then
                  set udg_SaveInt[6] = udg_SaveInt[9]
                  set udg_SaveInt[10] = udg_SaveInt[13]
                  set udg_SaveInt[9] = 0
                  set udg_SaveInt[13] = 0
                endif
            endif
        endif
    endif
    if udg_SaveInt[7]==0 then
        if udg_SaveInt[8]!=0 then
          set udg_SaveInt[7]=udg_SaveInt[8]
          set udg_SaveInt[11]=udg_SaveInt[12]
          set udg_SaveInt[8]=0
          set udg_SaveInt[12]=0
        else
            if udg_SaveInt[9]!=0 then
              set udg_SaveInt[7]=udg_SaveInt[9]
              set udg_SaveInt[11]=udg_SaveInt[13]
              set udg_SaveInt[9]=0
              set udg_SaveInt[13]=0
            endif
        endif
    endif
    if udg_SaveInt[8]==0 and udg_SaveInt[9]!=0 then
      set udg_SaveInt[8]=udg_SaveInt[9]
      set udg_SaveInt[12]=udg_SaveInt[13]
      set udg_SaveInt[9]=0
      set udg_SaveInt[13]=0
    endif
    set udg_Spell1[pid]=udg_SaveInt[6]
    set udg_Spell2[pid]=udg_SaveInt[7]
    set udg_Spell3[pid]=udg_SaveInt[8]
    set udg_Spell4[pid]=udg_SaveInt[9]
    set udg_Spell1LVL[pid]=udg_SaveInt[10]
    set udg_Spell2LVL[pid]=udg_SaveInt[11]
    set udg_Spell3LVL[pid]=udg_SaveInt[12]
    set udg_Spell4LVL[pid]=udg_SaveInt[13]
    set udg_QuestLevel[pid]=udg_SaveInt[21]
    set udg_RunnerSymbol[pid]=udg_SaveInt[59]
    call UnitAddItemToSlotById(u,udg_SaveInt[15],0)
    call UnitAddItemToSlotById(u,udg_SaveInt[16],1)
    call UnitAddItemToSlotById(u,udg_SaveInt[17],2)
    call UnitAddItemToSlotById(u,udg_SaveInt[18],3)
    call UnitAddItemToSlotById(u,udg_SaveInt[19],4)
    call UnitAddItemToSlotById(u,udg_SaveInt[20],5)
    call GroupAddUnit(udg_Characters,u)
                                //call BJDebugMsg("Download Hero="+GetUnitName(FirstOfGroup(udg_Characters)))
    call LoadPlayerTalentTable(p)
    call SaveInteger(udg_h,GetHandleId(u),19,GetHeroLevel(u))
    call SaveInteger(udg_h,GetHandleId(u),20,GetHeroXP(u))
    //loop
    //    exitwhen i>udg_SaveSize
    //    set udg_SaveInt[i]=0
    //    set i=i+1
    //endloop
    call UnitAddAbility(u,UnbindSpell(udg_Spell1[pid],1))
    call SetUnitAbilityLevel(u,UnbindSpell(udg_Spell1[pid],1),udg_Spell1LVL[pid])
    call UnitAddAbility(u,UnbindSpell(udg_Spell2[pid],2))
    call SetUnitAbilityLevel(u,UnbindSpell(udg_Spell2[pid],2),udg_Spell2LVL[pid])
    call UnitAddAbility(u,UnbindSpell(udg_Spell3[pid],3))
    call SetUnitAbilityLevel(u,UnbindSpell(udg_Spell3[pid],3),udg_Spell3LVL[pid])
    call UnitAddAbility(u,UnbindSpell(udg_Spell4[pid],4))
    call SetUnitAbilityLevel(u,UnbindSpell(udg_Spell4[pid],4),udg_Spell4LVL[pid])
    call SaveInteger(udg_h,100+GetPlayerId(p),58,udg_SaveInt[58]+1)
    call SetPlayerMaxHeroesAllowed(0,p)
    call SuspendHeroXP(u,true)
    call TriggerSleepAction(0.)
    //set udg_SaveP=GetOwningPlayer(u)
    if GetLocalPlayer()==p then
      call SelectUnitSingle(u)
      call PanCameraTo(GetUnitX(u),GetUnitY(u))
    endif
    set udg_Character[pid]=u
    set u=null
    //set udg_CharLoaded=true
endfunction

function ReadThem takes nothing returns nothing
  local timer t=GetExpiredTimer()
  local integer id=GetHandleId(t)
  local player p=LoadPlayerHandle(udg_h,id,1)
  local integer i=LoadInteger(udg_h,id,2)
  local integer n=LoadInteger(udg_h,id,3)
  call ReadInteger(i,p)
  if i==602 then
            set i=n
  endif
  if i==60 then
            set i=601
  endif
  if i>=n then
    call FlushChildHashtable(udg_h,id)
    call DestroyTimer(t)
  else
    call SaveInteger(udg_h,id,2,i+1)
  endif
  set t=null
endfunction

function LoadAllPlayers_f takes nothing returns nothing
  local player p=udg_SaveP
  local timer t=CreateTimer()
  local integer id=GetHandleId(t)
  call SavePlayerHandle(udg_h,id,1,p)
  call SaveInteger(udg_h,id,2,1)
  call SaveInteger(udg_h,id,3,udg_SaveSize)
  call TimerStart(t,0.03,true,function ReadThem)
  call ReadInteger(3333,p)
  set t=null
endfunction

function LoadAllPlayers takes nothing returns nothing
    local integer q
    local integer n
    local integer i2=0
    loop
      exitwhen i2>11
      set udg_SaveP=udg_Player[i2]
      if GetPlayerController(udg_SaveP)==MAP_CONTROL_USER and GetPlayerSlotState(udg_SaveP)==PLAYER_SLOT_STATE_PLAYING then
        call LoadAllPlayers_f()
      endif
      set i2=i2+1
    endloop
endfunction

function GetPlayerSaveInt takes player p returns nothing
  local integer i=1
  local integer id=100+GetPlayerId(p)
  loop  
    exitwhen i>udg_SaveSize
    if i==60 then
            set i=602
          endif
    if i<60 or i==602 then//
        set udg_SaveInt[i]=LoadInteger(udg_h,id,i)-536870912
    endif//
    if i==602 then
            set i=udg_SaveSize
          endif
    set i=i+1
  endloop
  set udg_SaveInt[1]=udg_SaveInt[1]+536870912
endfunction

function LoadPlayerHero takes player p returns nothing
    local integer i=1
    local integer q
    local integer n
    call GetPlayerSaveInt(p)
    set q=udg_SaveInt[1]
    if q<1 or q>udg_SaveVersion then
      if q>udg_SaveVersion then
        call ErrorMsg(p,"Your character is from a newer version.")
        set udg_SaveInt[1]=-1
      endif
      return
    endif
    if not CheckCode(p) then
      call ErrorMsg(p,"Your save is corrupted.")
      set i=1
      loop
          exitwhen i>udg_SaveSize
          if i==60 then
            set i=602
          endif
          if i<60 or i==602 then//
            set udg_SaveInt[i]=0
            endif//
          set i=i+1
      endloop
      return
    endif
endfunction



function LoadThem_f takes nothing returns nothing
  local trigger t=GetTriggeringTrigger()
  local integer id=GetHandleId(t)
  local integer i=LoadInteger(udg_h,id,1)
  local player p
  local boolean b=false
  if i>11 then
    call DisableTrigger(t)
    set t=null
    call DestroyThem()
    return
  endif
  call SaveInteger(udg_h,id,1,i+1)
  set p=udg_Player[i]
  set udg_SaveP=udg_Player[i]
  if GetPlayerController(p)==MAP_CONTROL_USER and GetPlayerSlotState(p)==PLAYER_SLOT_STATE_PLAYING then
    set udg_CharLoaded=false
    call LoadPlayerHero(p)
    if (udg_SaveInt[1]==0) then
       set udg_Character[i]=CreateUnit(p,'ewsp',GetRectCenterX(gg_rct_SelectBody),GetRectCenterY(gg_rct_SelectBody),0.)
       set TextTag[1]=CreateTextTagLocBJ( "|c00FFFF00Hero talents, chosen every 5 levels\n(Available from hero level 5)|r", GetRectCenter(gg_rct_Hint1), 0, 10, 100, 100, 0.00, 0 )
       set TextTag[2]=CreateTextTagLocBJ("|c00FFFF00Ability Books|r", GetRectCenter(gg_rct_Hint4), 0, 10, 100, 100, 100, 0 )
       set TextTag[3]=CreateTextTagLocBJ("|c00FFFF00For passing the level, experience is given, and after passing the level, items are played.\n\nIf you did not have time to choose your abilities, do not worry, you can choose them after passing the level.|r", GetRectCenter(gg_rct_Hint5), 0, 10, 100, 100, 100, 0 )
       set TextTag[4]=CreateTextTagLocBJ("|c00FFFF00You will receive experience and items after completing the level.|r", GetRectCenter(gg_rct_Hint6), 0, 10, 100, 100, 100, 0 )
       if GetLocalPlayer()==p then
         call PanCameraTo(GetRectCenterX(gg_rct_SelectBody),GetRectCenterY(gg_rct_SelectBody))
         call SelectUnitSingle(gg_unit_ntav_0000)
         call DisplayTimedTextToPlayer(p,0,0,30,"|c00FFFF00Choose a hero model in the tavern.\nIt is important that all heroes are the same, they are just models.\nDuring the process, you will be able to customize the hero for yourself.|r")
       endif
       set udg_CharLoaded=true
    else
      if udg_SaveInt[1]!=-1 then
        call ExecuteFunc("LoadCharacter")
      endif
    endif
  endif
  set t=null
endfunction

function LoadThem takes nothing returns nothing
    local integer i=1
    local trigger t=CreateTrigger()
    local player p
    local integer temp
    loop
      exitwhen i>12
      set p=Player(i-1)
      set temp=LoadInteger(udg_h,100+GetPlayerId(p),3333)
      if temp==udg_FMVersion then
        set udg_Advanced[1+GetPlayerId(p)]=true
        call DisplayTextToPlayer(p,0,0,"|c00ff0000ВНИМАНИЕ:|r The file module has been successfully connected.")
      elseif temp!=0 and temp<udg_FMVersion then
        call DisplayTextToPlayer(p,0,0,"|c00ff0000ВНИМАНИЕ:|r The file module is outdated and not connected. Download a new one from anufis.ucoz.ru")
      elseif temp!=0 and temp>udg_FMVersion then
        call DisplayTextToPlayer(p,0,0,"|c00ff0000ВНИМАНИЕ:|r The file module for the card is of a newer version and is not connected.")
      endif
      set i=i+1
    endloop
    call SaveInteger(udg_h,GetHandleId(t),1,0)
    call TriggerAddAction(t,function LoadThem_f)
    call TriggerRegisterTimerEvent(t,4.,true)
    set t=null
endfunction

function Trig_LoadEverybody_Actions takes nothing returns nothing
    local trigger t=CreateTrigger()
    call LoadAllPlayers()
    call TriggerAddAction(t,function LoadThem)
    call TriggerRegisterTimerEvent(t,1.+0.03*59,false)
    set t=null
endfunction

//===========================================================================
function InitTrig_LoadEverybody takes nothing returns nothing
    set gg_trg_LoadEverybody = CreateTrigger(  )
    call TriggerRegisterTimerEventSingle( gg_trg_LoadEverybody, 1.00 )
    call TriggerAddAction( gg_trg_LoadEverybody, function Trig_LoadEverybody_Actions )
endfunction
 

Attachments

  • data manager v1.7 (2).w3x
    22.6 KB · Views: 3
That code is pretty hard to read (especially with all the magic numbers), but at a very high-level, you would want to make the following updates:

Step 1: Update the old gamecache logic to use BlzSendSyncData​


You'll want to replace SyncStoredInteger with BlzSendSyncData.

For example, this is the critical code in ReadInteger_f that is responsible for "preparing" the data to be synced:
JASS:
  if GetLocalPlayer()==p then
    if GetPlayerTechMaxAllowed(udg_Player[14] , offset) == 2 then
      set a = - GetPlayerTechMaxAllowed(udg_Player[15] , offset)
    elseif GetPlayerTechMaxAllowed(udg_Player[14] , offset) == 1 then
      set a = GetPlayerTechMaxAllowed(udg_Player[15] , offset)
    elseif GetPlayerTechMaxAllowed(udg_Player[14] , offset) == 4 then
      set a = -2147483648
    else
      set a = 0
    endif
    call StoreInteger(udg_cache , soffset ,sid, a)
  endif
  call TriggerSyncStart()
  if GetLocalPlayer() == p then
      call SyncStoredInteger(udg_cache,soffset,sid)
  endif
  call TriggerSyncReady()
  set a=GetStoredInteger(udg_cache,soffset,sid)

Instead, you'll want to remove all the gamecache logic and all the TriggerSync... calls (those pause the thread until it is finished). And instead of storing the integer "a" in the game cache and doing a SyncStoredInteger call, you'd put a BlzSendSyncData line there instead:
JASS:
  local string syncPrefix = sid + "-" + soffset // this prefix should be unique so your sync handlers don't collide with one another
  if GetLocalPlayer()==p then
    if GetPlayerTechMaxAllowed(udg_Player[14] , offset) == 2 then
      set a = - GetPlayerTechMaxAllowed(udg_Player[15] , offset)
    elseif GetPlayerTechMaxAllowed(udg_Player[14] , offset) == 1 then
      set a = GetPlayerTechMaxAllowed(udg_Player[15] , offset)
    elseif GetPlayerTechMaxAllowed(udg_Player[14] , offset) == 4 then
      set a = -2147483648
    else
      set a = 0
    endif
    call BlzSendSyncData(syncPrefix, I2S(a))
  endif

You can convert the integer to a string since BlzSendSyncData only accepts strings. As for the prefix, it can really be anything--but you'll want it to be something reasonably unique since you're syncing a lot of different values per player. You'll just want to make sure you use the same prefix in your trigger event (see the next step).

Step 2: Update the TriggerSyncReady() and timer logic to use BlzTriggerRegisterPlayerSyncEvent​


Currently, the code is using TriggerSyncStart and TriggerSyncReady to "pause" the thread until all pending sync operations complete (i.e. it is waiting for the SyncStoredInteger to finish). Then, just in case, it seems like there is a trigger timer set up to wait an additional 1 second before doing anything with the data:
JASS:
  // In ReadInteger_f
  call TriggerAddAction(t,function ReadInteger_f2)
  call TriggerRegisterTimerEvent(t,1.,false)

You would want to replace that with BlzTriggerRegisterPlayerSyncEvent:
JASS:
    call TriggerAddAction(t, function ReadInteger_f2)
    call BlzTriggerRegisterPlayerSyncEvent(t, p, syncPrefix, false)

Next, it looks like "ReadInteger_f2" attempts to synchronize the data one more time--maybe just as a precaution? And then it finally gets read back in "ReadInteger_f3". Instead of doing an extra sync call, I would recommend just collapsing the logic into one function:
JASS:
// Replace ReadInteger_f2 and ReadInteger_f3 with something like this
function ReadInteger_f2 takes nothing returns nothing
  local trigger t = GetTriggeringTrigger()
  local integer id = GetHandleId(t)
  local player p = GetTriggerPlayer()
  local integer offset = LoadInteger(udg_h, id, 2)
  local string syncedString = BlzGetTriggerSyncData()
  local integer syncedInteger = S2I(syncedString)
  call SaveInteger(udg_h, 100+GetPlayerId(p), offset, syncedInteger)
  call FlushChildHashtable(udg_h,id)
  call DestroyTrigger(t)
  set t = null
endfunction

Step 3: Consider optimizing your sync logic​

The code might work as it is after the changes in step 1 and step 2, but there is still a lot of room for improvement. It looks like your code is currently trying to synchronize up to 60 (?) values really fast for every player in parallel. I wonder if that is triggering the warden to force a disconnect, especially since Blizz made changes to the netcode in 1.28+.

Optimization 1: Sync Large Strings Instead of Many Integers​

In reality, the best way to handle this is to probably use a different save/load system that can encode all those integers into a long string. That way you can sync one value across all players (or a few values if the string is long), decode it, and then do all your processing--which makes the synchronization logic much simpler.

If you wanted to do a hacky version of that, you could always convert your integers to strings, and append them using a delimiter, e.g.: "7|536877998|536870922|536870922|...". And then you would split that into more strings if it gets too long (or you could compress the string). That way you can do fewer sync calls overall.

Optimization 2: Skip Syncing Values of 0​

A second optimization would be to skip syncing if the player has no meaningful data to sync. For example, if "a" is 0--I would skip sending the sync data. When you go to read it later (via LoadInteger), it returns 0 anyway if nothing is saved there--so you'd have the same result.

So you would just add a simple guard:
JASS:
if a != 0 then
  call BlzSendSyncData("S1", I2S(a))
endif

You would still want the rest of the code in the function to complete though, so make sure you don't return or anything. Otherwise that local player wouldn't register the trigger event whereas all other players would, and that'd trigger a desync. The only goal here is to reduce the number of BlzSendSyncData calls. And this will definitely help for the cases where players don't have any saved data to use.

Optimization 3: Increase the sync interval​

Currently, you use a timer to sync values every 0.03 seconds (to call "ReadThem"). You can try increasing that a bit to reduce the overall number of syncs sent per second. That might reduce the chance that the Warden/engine gets upset with the number of values you're trying to sync. You could try something like 0.1 seconds if you're okay with your players waiting 6-10 seconds for the hero to load.

At the very least, if you're still getting disconnects, it might be worth trying to see if that helps.

Optimization 4: Sync player data sequentially​

Currently, you loop through each player and trigger their sync in parallel (in "LoadAllPlayers"). If you still run into issues, you might want to consider loading them one after the other. That can be quite complex to do reliably without changing a lot of your code, but one simple way is to just use a timer to space out the sync. That way, you only have 1-2 players trying to send sync data at a given time. Something like this:
JASS:
globals
  integer currentPlayerSyncIndex = 0
endglobals

function GetNextSyncPlayerIndex takes nothing returns integer
  loop
    exitwhen currentPlayerSyncIndex > 11
    set udg_SaveP = udg_Player[currentPlayerSyncIndex]
    if GetPlayerController(udg_SaveP) == MAP_CONTROL_USER and GetPlayerSlotState(udg_SaveP) == PLAYER_SLOT_STATE_PLAYING then
      return currentPlayerSyncIndex
    endif
    set currentPlayerSyncIndex = currentPlayerSyncIndex + 1
  endloop
  return currentPlayerSyncIndex
endfunction

function LoadNextPlayer takes nothing returns nothing
  local integer nextPlayerIndex = GetNextSyncPlayerIndex()
 
  // Check if we're all done looping through all active players
  if nextPlayerIndex > 11 then
    call DestroyTimer(GetExpiredTimer())
    return
  endif

  set udg_SaveP = udg_Player[nextPlayerIndex]
  call LoadAllPlayers_f()

  // Start from the next player index on the next tick of the timer
  set currentPlayerSyncIndex = currentPlayerSyncIndex + 1
endfunction

function LoadAllPlayers takes nothing returns nothing
  // Adjust the interval (2) to space out how long before we sync the next player
  call TimerStart(CreateTimer(), 2, false, function LoadNextPlayer)
endfunction



Hopefully that works out for you! This code is all pseudocode in my post, so I haven't tested it or compiled it--you'll probably need to make adjustments to fit your system exactly.
 
Level 2
Joined
Apr 19, 2022
Messages
8
I either didn't understand you or we're looking at the wrong place.

I disabled the load trigger, but I still get desynchronization when calling Preloader() locally.

So before we move on to your recommendation, we need to solve the desynchronization that occurs at the beginning of startup, before the call a second later.

I really want you to help me. I can send you the map personally, I see you're very smart in this area.

JASS:
  if GetLocalPlayer()==p then
          call Preloader("save2\\Multiplayer\\DungeonRunners\\"+udg_Name[GetPlayerId(p)+1]+".pld")
        endif
 
Smeto said:
I disabled the load trigger, but I still get desynchronization when calling Preloader() locally.

So before we move on to your recommendation, we need to solve the desynchronization that occurs at the beginning of startup, before the call a second later.

Oh, that is good to know. Then yeah, I agree we should look earlier.

You can attach the map via the pastebin:

Then you can send me a private message with the link. If you include instructions on how to reproduce the desync (e.g. do I need to type "-load" in the map or anything), then I can try to run it on my computer using that save file that you posted earlier. Are you able to reproduce the desync locally using two Warcraft III clients? (e.g. through LAN) Or do you only get the desync when playing on battle.net?
 
Top