Desync Frames Problem

Status
Not open for further replies.
I made a Map called Pokemon World, in this map I got the great idea to use frames...

And it's really confusing.

One of my system that I put below create a transition with a music (like in game boy pokemon)

it work in 3 phases
1\ cover the screen of hidden frames ans store them into an array in a struct
2\ show a random frame periodically until they are all shown
3\ destroy all frames

And this desync
For no reason (i test with 2 laptops on same bnet account in local network, because one of the is not connected)
It desync after a random number of test sometimes 2, 3, 4 or 8 ?!?

I put the map as attached if someone want to test it
In the test map Press "Esc" to launch a transition

Here is the code, I commented it to make it clear:

I called that transition random because it cover the screen randomly like a chess board.
vJASS:
scope IntroBattle
  globals
    private constant real MIN_SCREEN_X = -0.135
    private constant real MAX_SCREEN_X = 0.935
    private constant real MIN_SCREEN_Y = 0
    private constant real MAX_SCREEN_Y = 0.6

    // INTRO DURATION
    private constant real MAX_DURATION = 2.7

    private constant string FRAME_TEXTURE = "UI\\Widgets\\EscMenu\\Human\\human-options-menu-background.blp"

    // THESES NAMES ARE GENERATED IN FDF file
    private constant string FRAME_NAME = "IntroBattleFh-"                     // IntroBattleFh-1  IntroBattleFh-2  IntroBattleFh-3 ....
    private constant string FRAME_TEXTURE_NAME = "IntroBattleFhTexture-"      // IntroBattleFhTexture-1  IntroBattleFhTexture-2 ...

    // USEFUL TO GENERATE FRAMES NAMES (nbr of frames names in the fdf file)
    // it loops in the code the generated name num is re-set to 1 when it goes over the limit
    private constant integer MAX_FRAME_NUM = 500

    // INTRO RANDOM (DAMIER SPAWN RANDOM)
    // private constant integer RANDOM_NBR_COLS = 4
    // private constant integer RANDOM_NBR_LINE = 3
    private constant integer RANDOM_NBR_COLS = 20
    private constant integer RANDOM_NBR_LINE = 12

    // VARIABLE USED TO SET THE ARRAY LENGTH
    private constant integer RANDOM_ARRAY_SIZE = 245

    // VARIABLES SET IN the "initIntrosBattle" function
    // INTERVAL BETWEEN EACH APPARITION
    private real RANDOM_INTERVAL

    // FRAMES DIMENTIONS
    private real RANDOM_FH_WIDTH
    private real RANDOM_FH_HEIGHT

    // END INTRO RANDOM

    private integer NBR_FRAMES
    private integer CURRENT_FRAME_NUM
    private real SCREEN_WIDTH
    private real SCREEN_HEIGHT

  endglobals

private function getFrameName takes nothing returns string
  return FRAME_NAME + I2S(CURRENT_FRAME_NUM)
endfunction

private function getFrameTextureName takes nothing returns string
  return FRAME_TEXTURE_NAME + I2S(CURRENT_FRAME_NUM)
endfunction

function showFrameForPlayers takes framehandle fh, player P, player P1 returns nothing
  call BlzFrameSetVisible(fh, (P == GetLocalPlayer() or P1 == GetLocalPlayer()))
endfunction

private struct Random
  static Random array R
  static integer RT = 0
  static timer T = null

  player P
  player P1
  real duration
  integer context
  // the array where the hidden remaining frames are stored
  framehandle array hiddenFh [RANDOM_ARRAY_SIZE]
  // the number of remaining hidden frames
  integer nbrFrames
  boolean done

  private method clearAllFrames takes nothing returns nothing
    local integer I = 0

    loop
      exitwhen I >= RANDOM_ARRAY_SIZE
      set hiddenFh[I] = null
      set I = I + 1
    endloop

  endmethod

  private method onDestroy takes nothing returns nothing
    call .clearAllFrames()

    // A Struct that list framehandles and player to destroye all of them
    call Frame.destroyForPlayer(.P)

    set .P = null
    set .P1 = null

  endmethod

  private method spawnFrameAtLoc takes framehandle parent, real xTopLeft, real yTopLeft returns nothing
    local framehandle fh = BlzCreateSimpleFrame(getFrameName(), parent, .context)
    local framehandle textureFrame = BlzGetFrameByName(getFrameTextureName(), .context)

    // function that calculates XMax, XMin, YMax, YMin of the frame
    // move it with top corner point
    // and resize the frame
    call setFramePos(fh, xTopLeft, yTopLeft, RANDOM_FH_WIDTH, RANDOM_FH_HEIGHT)

    call BlzFrameSetTexture(textureFrame, FRAME_TEXTURE, 0, true)

    // BY DEFAULT THEY ARE ALL HIDDEN
    call BlzFrameSetVisible(fh, false)

    set .nbrFrames = .nbrFrames + 1
    set .hiddenFh[.nbrFrames] = fh

    // when the frames are shown they are stored into a structure that will destroy them at the end of the intro
    call Frame.addFrame(.P, fh, xTopLeft, yTopLeft, true)

    set NBR_FRAMES = NBR_FRAMES + 1

    set fh = null
    set textureFrame = null

  endmethod

  // FRAMES ARE SUMMONED BY LINE
  private method spawnLine takes framehandle parent, integer lineNum returns nothing
    local real XTopLeft = MIN_SCREEN_X
    local real YTopLeft = MAX_SCREEN_Y - ((lineNum - 1) * RANDOM_FH_HEIGHT)
    local integer colNum = 0

    loop
      set colNum = colNum + 1
      set CURRENT_FRAME_NUM = CURRENT_FRAME_NUM + 1

      call .spawnFrameAtLoc(parent, XTopLeft, YTopLeft)

      set XTopLeft = XTopLeft + RANDOM_FH_WIDTH

      if CURRENT_FRAME_NUM >= MAX_FRAME_NUM then
        set CURRENT_FRAME_NUM = 0
      endif

      exitwhen colNum >= RANDOM_NBR_COLS
    endloop

  endmethod

  // USED TO COVER LE SCREEN BY HIDDEN FRAME TO INIT
  private method createFrames takes nothing returns nothing
    local framehandle parent = BlzGetOriginFrame(ORIGIN_FRAME_GAME_UI, 0)
    local integer lineNum = 0

    loop
      set lineNum = lineNum + 1

      call .spawnLine(parent, lineNum)

      exitwhen lineNum >= RANDOM_NBR_LINE
    endloop

    set parent = null

  endmethod

  // SHOW A RANDOM FRAME
  private method showRandomFrame takes nothing returns nothing
    local integer num = GetRandomInt(1, .nbrFrames)
    local framehandle fh = .hiddenFh[num]

    call showFrameForPlayers(fh, .P, .P1)

    set .hiddenFh[num] = .hiddenFh[.nbrFrames]
    set .hiddenFh[.nbrFrames] = null
    set .nbrFrames = .nbrFrames - 1

    set fh = null

  endmethod

  // CHECK IF SOME FRAME ARE STILL HIDDEN
  private method manageShowFrame takes nothing returns nothing
    if .nbrFrames > 0 then
      call .showRandomFrame()
    else
      set .done = true
    endif

  endmethod

  private method initTable takes nothing returns nothing
    local integer I = 0

    loop
      exitwhen I >= RANDOM_ARRAY_SIZE
      set hiddenFh[I] = null
      set I = I + 1
    endloop

  endmethod

  static method update takes nothing returns nothing
    local Random r
    local integer I = 0

    loop
      set I = I + 1
      set r = .R[I]

      set r.duration = r.duration + RANDOM_INTERVAL

      call r.manageShowFrame()

      if r.done then
        call r.destroy()

        set .R[I] = .R[.RT]
        set .RT = .RT - 1
        set I = I - 1
      endif

      exitwhen I >= .RT
    endloop

    if .RT <= 0 then
      call PauseTimer(.T)
      set .RT = 0
    endif

  endmethod

  static method addRandom takes player P, player P1 returns nothing
    local Random r = Random.allocate()

    set r.P = P
    set r.P1 = P1
    set r.context = GetPlayerId(P) + 3
    set r.duration = 0
    set r.nbrFrames = 0
    set r.done = false

    // A method to be sure that the bug is not due to the array
    call r.initTable()

    // An init methos that creates all the frames hidden
    call r.createFrames()

    set .RT = .RT + 1
    set .R[.RT] = r

    if .RT == 1 then
      // The timer will show to the target players the given frames
      call TimerStart(.T, RANDOM_INTERVAL, true, function Random.update)
    endif

  endmethod
endstruct

function startIntroForPlayers takes player P, player P1 returns nothing
  call Random.addRandom(P, P1)
endfunction

// INITIALIZATION OF INTROS VARIABLES
function initIntrosBattle takes nothing returns nothing
  local integer nbrCells = 0

  set SCREEN_WIDTH = MAX_SCREEN_X - MIN_SCREEN_X
  set SCREEN_HEIGHT = MAX_SCREEN_Y - MIN_SCREEN_Y
  set NBR_FRAMES = 0

    // "RANDOM" called like that because the screen will be randomly covered by frames over the time
    set nbrCells = RANDOM_NBR_COLS * RANDOM_NBR_LINE
    set RANDOM_FH_WIDTH = SCREEN_WIDTH / RANDOM_NBR_COLS
    set RANDOM_FH_HEIGHT = SCREEN_HEIGHT / RANDOM_NBR_LINE
    set RANDOM_INTERVAL = MAX_DURATION / (nbrCells + 1)
    set Random.T = CreateTimer()

  set CURRENT_FRAME_NUM = 0

endfunction

endscope

I really hope someone can help me.
I really lke that map, I put already a lot of effort in there, and I think it can have a good entertainment potential.
I would be really sad to abandon that project for a warcraft 3 technical limitation... or watever
Sorry for my bad english

I add too a few logs files generated by blizzard
 

Attachments

  • LAPTOP_Desync.txt
    96.6 KB · Views: 10
  • Intro.w3m
    29.1 KB · Views: 11
Last edited:

Uncle

Warcraft Moderator
Level 69
Joined
Aug 10, 2018
Messages
7,276
I would avoid doing anything locally besides adjusting visibility. I haven't had any desyncs with frames following this strict rule.

I assume this destroys the frame locally for the specified player:
vJASS:
call Frame.destroyForPlayer(.P)
I doubt that's safe.

Does this work?
vJASS:
function showFrameForPlayers takes framehandle fh, player P, player P1 returns nothing
  call BlzFrameSetVisible(fh, (P == GetLocalPlayer() or P1 == GetLocalPlayer()))
endfunction

I always relied on something like this:
vJASS:
function showFrameForPlayer takes framehandle fh, player p, boolean b returns nothing
if b then
  if GetLocalPlayer() == p then
     call BlzFrameSetVisible(fh, true)
  endif
else
  call BlzFrameSetVisible(fh, false)
endfunction
endif
 
Last edited:
Thanks for your answer

Here is the code of the Frame System:
It store the frames for every players, and destroy it for everyone too. The player variable is only used to know the owner of each frame (the player that sees it on screen).

vJASS:
struct Frame
  static Frame array F
  static integer FT = 0

  player P
  framehandle f

  // attributes to get coordinates of the frame
  real x
  real y

  // attributes to get position of the frame
  integer row
  integer col

  // this attribute is used by the struct to know if the frame should be destroyed when the struct is destroy
  // because some of them have parents that I already destroy
  boolean toRemove

  private method onDestroy takes nothing returns nothing
    if .toRemove then
      call destroyFrame(.f)
    endif

    set .P = null
    set .f = null

  endmethod

  // we just check if the frame is displayed by the player and destroy it.
  static method destroyForPlayer takes player P returns nothing
    local integer I = 1
    local Frame f

    loop
      exitwhen I > .FT
      set f = .F[I]

      if f.P == P then
        call f.destroy()

        set .F[I] = .F[.FT]
        set .FT = .FT - 1
        set I = I - 1
      endif

      set I = I + 1
    endloop

  endmethod

  static method getFrameByHandle takes framehandle fr returns Frame
    local Frame frameToReturn = 0
    local integer I = 1
    local Frame f

    loop
      exitwhen I > .FT

      set f = .F[I]

      if f.f == fr then
        set I = .FT

        set frameToReturn = f
      endif

      set I = I + 1
    endloop

    return frameToReturn

  endmethod

  static method addFrameWithCoords takes player P, framehandle fr, real x, real y, integer row, integer col, boolean toRemove returns nothing
    local Frame f = Frame.allocate()

    set f.P = P
    set f.f = fr
    set f.x = x
    set f.y = y
    set f.row = row
    set f.col = col
    set f.toRemove = toRemove

    set .FT = .FT + 1
    set F[.FT] = f

  endmethod

  static method addFrame takes player P, framehandle fr, real x, real y, boolean toRemove returns nothing
    local Frame f = Frame.allocate()

    set f.P = P
    set f.f = fr
    set f.x = x
    set f.y = y
    set f.row = 0
    set f.col = 0
    set f.toRemove = toRemove

    set .FT = .FT + 1
    set F[.FT] = f

  endmethod
endstruct

You must know that in my map I have a globals variable called LOCAL_PLAYER that store the GetLocalPlayer()
So it is different for each client. Do you think that can cause problem?

Do we need to destroy every frames that is displayed, if not it causes a memory leak or justr destroying the parent is enough?

Do you thing large array of frames can cause memory problems that crash the game. I've watch on the memory of my computer, it grows with frames but it does not gros once the code is finnish. So I think there is no memory leak.... what do you think?
The code crash when I display a 4x3 grid that cover the screen I think warcraft 3 can handle 12 big frames on screen...

vJASS:
// LOCAL_PLAYER variable is a global boolean that store the value of GetLocalPlayer()
function showFrameForPlayers takes framehandle fh, player P, player P1 returns nothing
  call BlzFrameSetVisible(fh, (P == LOCAL_PLAYER or P1 == LOCAL_PLAYER))
endfunction

function showFrame takes framehandle fh, player P returns nothing
  call BlzFrameSetVisible(fh, P == LOCAL_PLAYER)
endfunction

I used theses function and it works in game....
I don't know if this function used too many times causes the crash, I've tried with an "if" block inside
and I still have the bug.

I always hide all frames for everyone with "call BlzFrameSetVisible(fh, false)"
then I show it for the player.
Do you think multiple visibility order can produce a crash, when they are fired too close from each other?

Last question: do you think frames can be seen to multiple players? like EveryOne, OnlyOne or None.. Do you think it is not the fact that 2 players can see the frame that can cause the desync sometimes?

Thanks for your precious help. I'm kind of lost with all that desyncs...
 
Last edited:

Uncle

Warcraft Moderator
Level 69
Joined
Aug 10, 2018
Messages
7,276
I'll be honest I really don't know too much about what you can and can't do. All I can tell you is:

1) I don't use your global GetLocalPlayer() method. Instead, I use what I'm doing in that showFrameForPlayer() function I showed you.
2) I hide frames by default and show them to specific players using If GetLocalPlayer() == player.
3) I create individual frames for each player most of the time, even when it could be unnecessary.
4) I NEVER Destroy frames.

Following these rules I never experience a frame-related desync. You'd be very surprised how many frames you can create for each player. Reusing them by toggling visibility seems perfectly fine instead of destroying/recreating.

But there's always the chance that I'm being unnecessarily cautious in certain cases.
 
You never have any frame desync?!? what a coding GOD!! :thumbs_up:

I created the LOCAL_PLAYER variable because GetLocalPlayer() leaks no?

In my case I create dynamic list and I need some frame names to link the frames to code elements... I didn't see a func that allow us to rename a Frame.

In my intro system I will try to hide the frames at the end instead of destroying them.

Have you got an idea of how many frames maximum that can be created in game? before it crashes.

Thank you man for your help. I have new ideas.
 

Uncle

Warcraft Moderator
Level 69
Joined
Aug 10, 2018
Messages
7,276
You never have any frame desync?!? what a coding GOD!! :thumbs_up:

I created the LOCAL_PLAYER variable because GetLocalPlayer() leaks no?

In my case I create dynamic list and I need some frame names to link the frames to code elements... I didn't see a func that allow us to rename a Frame.

In my intro system I will try to hide the frames at the end instead of destroying them.

Have you got an idea of how many frames maximum that can be created in game? before it crashes.

Thank you man for your help. I have new ideas.
I imagine you can have 1000s of frames without issues.
Here's some UI info that MAY be dated: Random desyncs from custom UI?
 
Status
Not open for further replies.
Top