• 🏆 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!
  • 🏆 Hive's 6th HD Modeling Contest: Mechanical is now open! Design and model a mechanical creature, mechanized animal, a futuristic robotic being, or anything else your imagination can tinker with! 📅 Submissions close on June 30, 2024. Don't miss this opportunity to let your creativity shine! Enter now and show us your mechanical masterpiece! 🔗 Click here to enter!

[Crash] Custom UI & desync issues

Status
Not open for further replies.
Level 5
Joined
Jun 12, 2018
Messages
148
Hivers, time has come for the community to understand how to synchronize data to get each player his own customized UI :ogre_rage:

I'm doing a unit selection panel (check picture provided) and I'd like it to work separetely for each player. It's running fine in solo but I will get automatic desyncs in MP when someone use the buttons.

I've isolated the display parts into GetLocalPlayer() condition block and the only thing I'm doing is to set variables in these blocks to check their value after so remaining actions are executed for everyone.

I've ran a test setting a local integer to get the id of the clicked button inside a GetLocalPlayer() block and use his value after it, this leaded to a crash for every other player.

Then I've stumbled upon this thread trying to understand how I could synchronize my data. I've ran the same test with my local integer but this time with a gamecache and synchronize call methods, it also leaded to a crash.

According to @Taysen, one must call the BlzGetFrameByName function at least once for every players before using it in GetLocalPlayer() blocks, so I did an "anti-desync" function that just use this method upon every frame I got in my unit panel to avoid desyncs.

Now I must confess I feel a bit blocked, here the code involved in the process. ID_XXX are just global integers declared in the same script file.
I hope I can solve it asap because my alpha release is nearly finished :)

Thanks !

Callback function when a frame is clicked inside the unit panel (pressing the "roll" button will create a desync issue)


JASS:
function SelectionUIButtonClick takes nothing returns nothing
    local framehandle frameClicked = BlzGetTriggerFrame()
    local player trigPlayer = GetTriggerPlayer()
    local integer playerIndex = GetConvertedPlayerId(trigPlayer)
    local integer unitTypeClicked
    local integer goldCost
    local integer buttonId = NO_ID
 
    if GetLocalPlayer() == trigPlayer then
        if ( frameClicked == BlzGetFrameByName("UnitSelectionUIOption_0", 0) ) then
            set unitTypeClicked = LoadInteger(udg_hashRandomUnits, playerIndex, 1)
            set goldCost = S2I(BlzFrameGetText(BlzGetFrameByName("UnitSelectionUIAmountText_0" ,0)))
            set buttonId = ID_SELECT_UNIT
        elseif ( frameClicked == BlzGetFrameByName("UnitSelectionUIOption_1", 0) ) then
            set unitTypeClicked = LoadInteger(udg_hashRandomUnits, playerIndex, 2)
            set goldCost = S2I(BlzFrameGetText(BlzGetFrameByName("UnitSelectionUIAmountText_1" ,0)))
            set buttonId = ID_SELECT_UNIT
            ....
        elseif ( frameClicked == BlzGetFrameByName("UnitSelectionUICloseButton", 0) ) then
            call BlzFrameSetVisible(BlzGetFrameByName("UnitSelectionUI", 0), false)
        elseif ( frameClicked == BlzGetFrameByName("UnitSelectionUIReRollButton", 0) ) then
            set buttonId = ID_ROLL
            call StoreInteger(GetUICache(), "btnCache" + I2S(GetPlayerId(trigPlayer)), "id", buttonId)
            call SyncStoredInteger(GetUICache(), "btnCache" + I2S(GetPlayerId(trigPlayer)), "id")
        elseif ( frameClicked == BlzGetFrameByName("UnitSelectionUILockButton", 0) ) then
            set buttonId = ID_LOCK
        endif
    endif
  
    //Syncs up data
    call TriggerSyncReady()
    set buttonId = GetStoredInteger(GetUICache(), "btnCache" + I2S(GetPlayerId(trigPlayer)), "id")
 
    //Do actions for everyone according to the clicked button
    if buttonId == ID_ROLL then
        call IssueImmediateOrder( udg_DRAFTERS[playerIndex], "channel" )
    elseif buttonId == ID_SELECT_UNIT then
        call CreateUnitInStash(unitTypeClicked, playerIndex, trigPlayer)
        call AdjustPlayerStateBJ( -goldCost, trigPlayer, PLAYER_STATE_RESOURCE_GOLD )
        if GetLocalPlayer() == trigPlayer then
            call BlzFrameSetVisible(frameClicked, false)
        endif  
    endif
    set trigPlayer = null
    set frameClicked = null
endfunction

GetUICache method

JASS:
function GetUICache takes nothing returns gamecache
  if udg_CacheUI == null then
    call FlushGameCache(InitGameCache("uiCache"))
    set udg_CacheUI = InitGameCache("uiCache")
  endif
  return udg_CacheUI
endfunction
 

Attachments

  • customuiroll.png
    customuiroll.png
    513.9 KB · Views: 102
Last edited:
Level 12
Joined
Jan 30, 2020
Messages
875
OMG !
I mean I am just starting to learn custom UI (thanks again Taysen) but what I know for sure is that your triggers have no chance not to desync.

How do you want the game to keep in sync if you SET GLOBAL variables INSIDE GetLocalPlayer block ?????

This is the only thing that won't desync in your code :

JASS:
        if GetLocalPlayer() == trigPlayer then
            call BlzFrameSetVisible(frameClicked, false)
        endif

You have to change values outside of the Local Blocks, once inside you can use anything that only alters display (more or less).
Everything that impacts the game (and integer values being a serious one) for everyone should never have async values.

At least that is what my 16 years old knowledge about desyncs tells me, but unless things have drastically changed on this very subject, you cannot expect your code not to desync.



EDIT : Note that you can also change selection for the local player only without any risk of desync.

About your different custom UIs for every player, it might be easier than you thnk :

Set all variables outside the local player block, and when it comes to display the UI, use the proper variables : SOLVED :) - and you won't even need to try to sync things or use cache.





EDIT 2 : Just wanted to show you something very simple I made thanks to Taysen advice on UI : sliders

Here is my final trigger. Note that this works for all players : they all have their own camera zoomed out to the value they chose with the slider, and this does not desync :

JASS:
function UpdateCamera takes nothing returns nothing
    local player ThePlayer=GetTriggerPlayer()
    local real Distance=BlzGetTriggerFrameValue()
   
    if (ThePlayer==GetLocalPlayer()) then
        call SetCameraField(CAMERA_FIELD_TARGET_DISTANCE, Distance, 0)
    endif
    set ThePlayer=null
endfunction


function InitTrig_Camera_Controls takes nothing returns nothing
    local trigger CameraControl=CreateTrigger()
    local framehandle VSlider = BlzCreateFrameByType( "SLIDER", "Distance", BlzGetOriginFrame(ORIGIN_FRAME_GAME_UI,0), "QuestMainListScrollBar", 0 )
    local framehandle SliderToolTip =  BlzCreateFrameByType("TEXT", "SliderTitle", BlzGetOriginFrame(ORIGIN_FRAME_GAME_UI, 0), "", 0)
    local integer Color = BlzConvertColor(255, 255, 255, 0) // Change values of alpha, red, green and blue for another custom color

    // Set parametees for the Slider...
    // call BlzFrameSetVertexColor(VSlider, Color) // does not work
    call BlzFrameSetSize(VSlider, 0.018, 0.076)
    call BlzFrameSetAbsPoint(VSlider, FRAMEPOINT_BOTTOMLEFT, 0.20, 0.026)
    call BlzFrameSetMinMaxValue(VSlider, 600, 4000)
    call BlzFrameSetValue(VSlider, 2500)
    call BlzFrameSetStepSize(VSlider, 50)

    // ... and for the SliderToolTip
    call BlzFrameSetTextColor(SliderToolTip, Color)
    call BlzFrameSetText(SliderToolTip, "USE THIS SLIDER TO CONTROL THE CAMERA ZOOM")
    call BlzFrameSetScale(SliderToolTip, 1.60)
    call BlzFrameSetAbsPoint(SliderToolTip, FRAMEPOINT_BOTTOMLEFT, 0.22, 0.16)
    call BlzFrameSetTooltip(VSlider, SliderToolTip)

    // Set Initial camera distance for all players
    call SetCameraField(CAMERA_FIELD_TARGET_DISTANCE, 2500, 0)
   
    // Add event and callback to trigger for the Slider
    call BlzTriggerRegisterFrameEvent(CameraControl, VSlider, FRAMEEVENT_SLIDER_VALUE_CHANGED)
    call TriggerAddAction(CameraControl, function UpdateCamera)

    // nulling
    set VSlider=null
    set CameraControl=null
endfunction

I hope this will help you more than my previous explanation attempts
 
Last edited:
Level 5
Joined
Jun 12, 2018
Messages
148
Ay, it seems I've copy/pasted the code too quickly, you're right the first call to create the global was inside this GetLocalPlayer() block -.-

Seems problem is solved, I have no desync anymore when using a local gamecache variable inside the function !!

Many thanks Macadamia :)
 
Level 12
Joined
Jan 30, 2020
Messages
875
Sorry I was updating while you typed your answer, please read my second edit, I am sure this will help you.

I mean the same way as users have their own slider when there is in fact only one, I am sure there is a way to do things in a similar matter to obtain many more UI elements having distinct aspects without using game cache.

This said if you made it work....
 
Level 5
Joined
Jun 12, 2018
Messages
148
Hey Macadamia,

Thanks for the detailed answer, I'm also learning custom UI behavior and I'm also progressing fast thanks to Taysen !

The issue was fixed with the gamecache but I've experienced other desync issues with my UI elements so I decided to clean things up. I've managed to do something simple and efficient (and sync safe).

Here's the updated code :
JASS:
function SelectionUIButtonClick takes nothing returns nothing
    local framehandle frameClicked = BlzGetTriggerFrame()
    local player trigPlayer = GetTriggerPlayer()
    local integer playerIndex = GetConvertedPlayerId(trigPlayer)
    local integer frameIndex
    local integer goldCost
    local integer unitId
    local boolean isUnitSelected = false
 
    if ( frameClicked == BlzGetFrameByName("UnitSelectionUIOption_0", 0) ) then
        set frameIndex = 0
        set isUnitSelected = true
    elseif ( frameClicked == BlzGetFrameByName("UnitSelectionUIOption_1", 0) ) then
        set frameIndex = 1
        set isUnitSelected = true                    
    elseif ( frameClicked == BlzGetFrameByName("UnitSelectionUIOption_2", 0) ) then
        set frameIndex = 2
        set isUnitSelected = true        
    elseif ( frameClicked == BlzGetFrameByName("UnitSelectionUIOption_3", 0) ) then
        set frameIndex = 3
        set isUnitSelected = true            
    elseif ( frameClicked == BlzGetFrameByName("UnitSelectionUIOption_4", 0) ) then
        set frameIndex = 4
        set isUnitSelected = true        
    elseif ( frameClicked == BlzGetFrameByName("UnitSelectionUICloseButton", 0) ) then
        call HideSelectionUI(trigPlayer)
    elseif ( frameClicked == BlzGetFrameByName("UnitSelectionUIReRollButton", 0) ) then
       call IssueImmediateOrder( udg_DRAFTERS[playerIndex], "channel" )
    elseif ( frameClicked == BlzGetFrameByName("UnitSelectionUILockButton", 0) ) then
        //todo
    endif
    
    //If a unit is clicked, create it at the player stash
    if isUnitSelected then
        set unitId = LoadInteger( udg_hashPlayerUI[playerIndex], frameIndex, udg_UI_FIELD_UNIT_TYPE )
        set goldCost = LoadInteger( udg_hashPlayerUI[playerIndex], frameIndex, udg_UI_FIELD_UNIT_TYPE )
        call CreateUnitInStash( unitId, playerIndex, trigPlayer )
        call AdjustPlayerStateBJ( -goldCost, trigPlayer, PLAYER_STATE_RESOURCE_GOLD )
        if GetLocalPlayer() == trigPlayer then
            call BlzFrameSetVisible( frameClicked, false )
        endif    
    endif
    set trigPlayer = null
    set frameClicked = null
endfunction
 
Level 12
Joined
Jan 30, 2020
Messages
875
Hey Macadamia,

Thanks for the detailed answer, I'm also learning custom UI behavior and I'm also progressing fast thanks to Taysen !

The issue was fixed with the gamecache but I've experienced other desync issues with my UI elements so I decided to clean things up. I've managed to do something simple and efficient (and sync safe).

Here's the updated code :
JASS:
function SelectionUIButtonClick takes nothing returns nothing
    local framehandle frameClicked = BlzGetTriggerFrame()
    local player trigPlayer = GetTriggerPlayer()
    local integer playerIndex = GetConvertedPlayerId(trigPlayer)
    local integer frameIndex
    local integer goldCost
    local integer unitId
    local boolean isUnitSelected = false
 
    if ( frameClicked == BlzGetFrameByName("UnitSelectionUIOption_0", 0) ) then
        set frameIndex = 0
        set isUnitSelected = true
    elseif ( frameClicked == BlzGetFrameByName("UnitSelectionUIOption_1", 0) ) then
        set frameIndex = 1
        set isUnitSelected = true                   
    elseif ( frameClicked == BlzGetFrameByName("UnitSelectionUIOption_2", 0) ) then
        set frameIndex = 2
        set isUnitSelected = true       
    elseif ( frameClicked == BlzGetFrameByName("UnitSelectionUIOption_3", 0) ) then
        set frameIndex = 3
        set isUnitSelected = true           
    elseif ( frameClicked == BlzGetFrameByName("UnitSelectionUIOption_4", 0) ) then
        set frameIndex = 4
        set isUnitSelected = true       
    elseif ( frameClicked == BlzGetFrameByName("UnitSelectionUICloseButton", 0) ) then
        call HideSelectionUI(trigPlayer)
    elseif ( frameClicked == BlzGetFrameByName("UnitSelectionUIReRollButton", 0) ) then
       call IssueImmediateOrder( udg_DRAFTERS[playerIndex], "channel" )
    elseif ( frameClicked == BlzGetFrameByName("UnitSelectionUILockButton", 0) ) then
        //todo
    endif
   
    //If a unit is clicked, create it at the player stash
    if isUnitSelected then
        set unitId = LoadInteger( udg_hashPlayerUI[playerIndex], frameIndex, udg_UI_FIELD_UNIT_TYPE )
        set goldCost = LoadInteger( udg_hashPlayerUI[playerIndex], frameIndex, udg_UI_FIELD_UNIT_TYPE )
        call CreateUnitInStash( unitId, playerIndex, trigPlayer )
        call AdjustPlayerStateBJ( -goldCost, trigPlayer, PLAYER_STATE_RESOURCE_GOLD )
        if GetLocalPlayer() == trigPlayer then
            call BlzFrameSetVisible( frameClicked, false )
        endif   
    endif
    set trigPlayer = null
    set frameClicked = null
endfunction

I like that much better !!!! Code looks clean and understandable.

I just realize we all seem to use locals for custom UI elements, question is ..... how do you hide them on demand if we don't use globals ?Like you know in victory / defeat or cinematic contexts...
 
Level 5
Joined
Jun 12, 2018
Messages
148
I just realize we all seem to use locals for custom UI elements, question is ..... how do you hide them on demand if we don't use globals ?Like you know in victory / defeat or cinematic contexts...

I'm using this kind of simple function to hide a frame for the local player :
JASS:
function HideDescriptionButton takes player whichPlayer returns nothing
    if GetLocalPlayer() == whichPlayer then
        call BlzFrameSetVisible(BlzGetFrameByName("ScriptDialogButton", 9), false)
    endif
endfunction

So basically you just have to type the code below to hide a frame for everyone
Code:
call BlzFrameSetVisible(BlzGetFrameByName(MY_FRAME_NAME, CONTEXT_ID), false)
 
Status
Not open for further replies.
Top