• 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.
  • The Hive's 22nd Icon Contest: Creep Abilities is now concluded, time to vote for your favourite set of icons! Click here to vote!
  • ✅ The POLL for Hive's Texturing Contest #34 is OPEN! Vote for the TOP 3 SKINS! 🔗Click here to cast your vote!
  • ✅ The POLL for Hive's Techtree Contest #20 is OPEN! Vote for the TOP 3 FACTIONS! 🔗Click here to cast your vote!

Frames begin moving with noticeable delay in network mode, even with just 1 player, when FPS exceeds 270.

Level 3
Joined
Apr 19, 2022
Messages
18
I made a frame movement system in vJass. It works perfectly smoothly in single-player mode, but if I launch it alone in a multiplayer game, all the smoothness is lost—even though I have 300+ FPS. However, if I lower the FPS to 270, the movement almost becomes as smooth as in single-player.

This is the first time I’ve encountered a case where visual performance depends on FPS in a multiplayer game, and the lower the FPS, the better the movement looks.

Help me solve the issue: at high FPS (300+), this code performs poorly in multiplayer mode—even when the map is launched with just 1 player.

Setting:

JASS:
globals
    boolean RIGHT_MOUSE_BUTTON_FRAME_DRAG_ENABLE = false
    boolean DRAG_TIMER_ENABLE = true
endglobals

JASS:
library MoveAbleFrame requires World2Screen

globals
    real targetX = 0.0
    real targetY = 0.0
    real currentX = 0.0
    real currentY = 0.0
    real moveSpeed = 0.0125 
    real prevTargetX = 0.0
    real prevTargetY = 0.0
    real deltaX = 0.0
    real deltaY = 0.0
    boolean wasMoving 
    boolean PolledMoving = false
    timer array PlayerTimer
endglobals

struct structMoveAbleFrame
    boolean         Enabled = true
   
    framehandle     Frame = null
    framehandle     PlayerHoveredFrame
    framepointtype  FramePoint
    trigger         TriggerFrameEnter
    triggeraction   TriggerFrameEnterAction
    trigger         TriggerFrameLeave
    triggeraction   TriggerFrameLeaveAction
    trigger         MouseClickTrigger
    triggeraction   MouseClickTriggerAction
    trigger         MouseReleaseTrigger
    triggeraction   MouseReleaseTriggerAction
    trigger         MOUSE_MOVE_Trigger
    triggeraction   MOUSE_MOVE_TriggerAction
endstruct

globals
    structMoveAbleFrame MoveAbleFrame
endglobals

function enable takes player p, boolean flag returns boolean
    //if GetLocalPlayer() == p then
        if flag == null then
            set MoveAbleFrame.Enabled = not MoveAbleFrame.Enabled
        else
            set MoveAbleFrame.Enabled = flag
        endif
    //endif
    return MoveAbleFrame.Enabled
endfunction

function setup takes framehandle frame returns nothing
    call BlzTriggerRegisterFrameEvent(MoveAbleFrame.TriggerFrameEnter, frame, FRAMEEVENT_MOUSE_ENTER)
    call BlzTriggerRegisterFrameEvent(MoveAbleFrame.TriggerFrameLeave , frame, FRAMEEVENT_MOUSE_LEAVE)
endfunction


function startMoving takes framehandle frame, framepointtype framePoint returns nothing
    set MoveAbleFrame.Frame = frame
    if framePoint == null then
       set framePoint = FRAMEPOINT_CENTER
    endif
    set MoveAbleFrame.FramePoint = framePoint
endfunction


function moveFrame takes real x, real y returns nothing
    call BlzFrameClearAllPoints(MoveAbleFrame.Frame)
    call BlzFrameSetAbsPoint(MoveAbleFrame.Frame, MoveAbleFrame.FramePoint, x, y)
endfunction


function GetTriggerPlayerHoveredFrame takes nothing returns nothing
    local real x = BlzGetTriggerPlayerMouseX()
    local real y = BlzGetTriggerPlayerMouseY()
    //if GetLocalPlayer() == GetTriggerPlayer() then
        set MoveAbleFrame.PlayerHoveredFrame = BlzGetTriggerFrame()
    //endif
endfunction

function GetTriggerPlayerHoveredFrameNull takes nothing returns nothing
    //if GetLocalPlayer() == GetTriggerPlayer() then
        set MoveAbleFrame.PlayerHoveredFrame = null
   // endif
endfunction

globals
    location moveLoc = Location(0, 0)
    boolean flag = false
endglobals

function TempFrameSetting takes nothing returns nothing
    call BlzFrameSetEnable(MoveAbleFrame.PlayerHoveredFrame, false)
    call BlzFrameSetLevel(MoveAbleFrame.PlayerHoveredFrame, 9)
    call BlzFrameSetScale(MoveAbleFrame.PlayerHoveredFrame, 0.8)
endfunction

function NormalFrameSetting takes nothing returns nothing
    call BlzFrameSetScale(MoveAbleFrame.Frame, 1.0)
    call BlzFrameSetEnable(MoveAbleFrame.Frame, true)
    call BlzFrameSetLevel(MoveAbleFrame.Frame, 6)
    set MoveAbleFrame.Frame = null
endfunction


globals
    real mouseX = 0.0
    real mouseY = 0.0
    real mouseX_prev = 0.0
    real mouseY_prev = 0.0
    real mouseX_interp = 0.0
    real mouseY_interp = 0.0
    real interpProgress = 1.0
    real interpSpeed  = 0
endglobals

function UpdateTargetXYInstant takes nothing returns nothing
    local real x = BlzGetTriggerPlayerMouseX()
    local real y = BlzGetTriggerPlayerMouseY()
    local real z

    call MoveLocation(moveLoc, x, y)
    set z = GetLocationZ(moveLoc)
    call World2Screen(x, y, z)

    // не трогаем prev/interp — сразу задаём target
    set targetX = ScreenX
    set targetY = ScreenY
endfunction
function UpdateMouseScreenCoords takes nothing returns nothing
    local real x = BlzGetTriggerPlayerMouseX()
    local real y = BlzGetTriggerPlayerMouseY()
    local real z

    call MoveLocation(moveLoc, x, y)
    set z = GetLocationZ(moveLoc)
    call World2Screen(x, y, z)

    set mouseX_prev = mouseX
    set mouseY_prev = mouseY
    set mouseX = ScreenX
    set mouseY = ScreenY
    set mouseX_interp = mouseX
    set mouseY_interp = mouseY
    set interpProgress = 1.0

    set targetX = mouseX
    set targetY = mouseY
endfunction

function PolledWait2 takes real time returns nothing
local timer t
local real R
    if time>0. then
        set t = CreateTimer()
        call TimerStart(t,time,false,null)
        loop
            set R=TimerGetRemaining(t)
            exitwhen R<=0.
                call TriggerSleepAction(R)
        endloop
        call DestroyTimer(t)
        set t=null
    endif
endfunction

function GetWorld2Screen takes nothing returns real
    local real x = BlzGetTriggerPlayerMouseX()
    local real y = BlzGetTriggerPlayerMouseY()
    local real z
   
    call MoveLocation(moveLoc, x, y)
    set z = GetLocationZ(moveLoc)
    call World2Screen(x, y, z)
    return SquareRoot((currentX - ScreenX)*(currentX - ScreenX) + (currentY - ScreenY)*(currentY - ScreenY))
endfunction


function update2 takes nothing returns nothing
    local real dx
    local real dy
    local real distMouse
    local real dist
    local real dynamicSpeed
    local real speed = moveSpeed
    set up = up + 1
    call BJDebugMsg("update2 = " + I2S(up))
    if MoveAbleFrame.Frame == null then
        return
    endif
    if flag or wasMoving then
        set dx = mouseX - mouseX_prev
        set dy = mouseY - mouseY_prev
        set distMouse = SquareRoot(dx * dx + dy * dy)

        set interpSpeed = 0.1 + distMouse * 0.6
        if interpSpeed > 0.3 then
            set interpSpeed = 0.3
        endif
        set interpProgress = interpProgress + interpSpeed
        if interpProgress > 1.0 then
            set interpProgress = 1.0
        endif

        set mouseX_interp = mouseX_prev + (mouseX - mouseX_prev) * interpProgress
        set mouseY_interp = mouseY_prev + (mouseY - mouseY_prev) * interpProgress

        set targetX = mouseX_interp
        set targetY = mouseY_interp

        set dist = SquareRoot((targetX - currentX)*(targetX - currentX) + (targetY - currentY)*(targetY - currentY))
        if dist < 0.05 then
            set speed = moveSpeed + (0.05 - dist) * 2.0
            if speed > 0.025 then
                set speed = 0.025
            endif
        endif

        set currentX = currentX * (1.0 - speed) + targetX * speed
        set currentY = currentY * (1.0 - speed) + targetY * speed

        call moveFrame(currentX, currentY)
       


        if not flag and dist < 0.001 then
            set wasMoving = false
            call BlzFrameSetScale(MoveAbleFrame.Frame, 1.0)
            call BlzFrameSetEnable(MoveAbleFrame.Frame, true)
            call BlzFrameSetLevel(MoveAbleFrame.Frame, 6)
        endif
    endif
endfunction

globals
    integer up = 0
endglobals

function MouseClick takes nothing returns nothing
    local player p = GetTriggerPlayer()
    local integer id = GetPlayerId(p) + 1
    local real dist
    local timer t
    local boolean b = GetLocalPlayer() == p
    if BlzGetTriggerPlayerMouseButton() == MOUSE_BUTTON_TYPE_RIGHT then
       
        if MoveAbleFrame.Enabled then
            if RIGHT_MOUSE_BUTTON_FRAME_DRAG_ENABLE then

                if MoveAbleFrame.PlayerHoveredFrame != null then
                    call TimerStart(PlayerTimer[id], 0.001, b, function update2)
                    set flag = true
                    call startMoving(MoveAbleFrame.PlayerHoveredFrame, FRAMEPOINT_CENTER)
                    call TempFrameSetting()
                    call UpdateMouseScreenCoords()
                    set currentX = targetX
                    set currentY = targetY
                    call moveFrame(currentX, currentY)
                else
                    set flag = false
                    set MoveAbleFrame.Frame = null
                    call PauseTimer(PlayerTimer[id])
                endif
            else

                if MoveAbleFrame.PlayerHoveredFrame != null then
                    call TimerStart(PlayerTimer[id], 0.001, b, function update2)
                    set flag = not flag
                    set PolledMoving = not PolledMoving
                    if flag then
                        call startMoving(MoveAbleFrame.PlayerHoveredFrame, FRAMEPOINT_CENTER)
                        call UpdateMouseScreenCoords()
                        set currentX = targetX
                        set currentY = targetY
                        call moveFrame(currentX, currentY)
                        call TempFrameSetting()
                    else

                        call NormalFrameSetting()
                        call PauseTimer(PlayerTimer[id])
                    endif
                else

                    if DRAG_TIMER_ENABLE then
                        if PolledMoving then
                            set PolledMoving = not PolledMoving
                            if GetWorld2Screen() > 0.0029 then
                                set t = CreateTimer()
                                call TimerStart(t, 0.15, false, null)
                                loop
                                    set dist = GetWorld2Screen()
                                    exitwhen dist < 0.002 or TimerGetRemaining(t) <= 0.
                                    call TriggerSleepAction(0.01)
                                endloop
                                call DestroyTimer(t)
                                set t = null
                            endif
                        endif
                    endif
                    call PauseTimer(PlayerTimer[id])
                    set b = false
                    set flag = false
                    call NormalFrameSetting()
                endif
            endif
        endif
    endif
endfunction


function MouseRelease takes nothing returns nothing
local player p = GetTriggerPlayer()
local integer id = GetPlayerId(p) + 1
local real dist

    if MoveAbleFrame.Frame == null then
        return
    endif
    if RIGHT_MOUSE_BUTTON_FRAME_DRAG_ENABLE then
        set dist = GetWorld2Screen()

        if dist < 0.003 then
            set flag = false
            set wasMoving = true
            set targetX = ScreenX
            set targetY = ScreenY
        else
            if DRAG_TIMER_ENABLE then
                call PolledWait2(0.15)
            endif
            set flag = false
            set wasMoving = true
            call UpdateTargetXYInstant()
        endif
        call PauseTimer(PlayerTimer[id])
        call NormalFrameSetting()
    endif
endfunction

function OnMouseMove takes nothing returns nothing
    local real newX = BlzGetTriggerPlayerMouseX()
    local real newY = BlzGetTriggerPlayerMouseY()
    //set GetFunctionName_DEBUG = "OnMouseMove"
    set mouseX_prev = mouseX
    set mouseY_prev = mouseY

    set mouseX = newX
    set mouseY = newY
    set interpProgress = 0.0
endfunction

function CoordsMouse takes nothing returns nothing
    local real x = BlzGetTriggerPlayerMouseX()
    local real y = BlzGetTriggerPlayerMouseY()
    local real z
    if flag == true then
        call MoveLocation(moveLoc, x, y)
        set z = GetLocationZ(moveLoc)
        call World2Screen(x,y,z)

        set mouseX_prev = mouseX
        set mouseY_prev = mouseY
        set mouseX = ScreenX
        set mouseY = ScreenY
        set interpProgress = 0.0
    endif
endfunction

function onTriggerInit takes nothing returns nothing
local integer playerIndex = 0
//local timer Timer = CreateTimer()
local integer i = 1
loop
    exitwhen i == 13
    set PlayerTimer[i] = CreateTimer()
    set i = i + 1
endloop
//local timer Timer2 = CreateTimer()
    call PolledWait2(0.01)
    set MoveAbleFrame = structMoveAbleFrame.create()
   
    set MoveAbleFrame.TriggerFrameEnter = CreateTrigger()
    set MoveAbleFrame.TriggerFrameEnterAction = TriggerAddAction(MoveAbleFrame.TriggerFrameEnter,function GetTriggerPlayerHoveredFrame)
   
    set MoveAbleFrame.TriggerFrameLeave = CreateTrigger()
    set MoveAbleFrame.TriggerFrameLeaveAction = TriggerAddAction(MoveAbleFrame.TriggerFrameLeave, function GetTriggerPlayerHoveredFrameNull)

    set MoveAbleFrame.MouseClickTrigger = CreateTrigger()
    set MoveAbleFrame.MouseClickTriggerAction = TriggerAddAction(MoveAbleFrame.MouseClickTrigger, function MouseClick)
   
    set MoveAbleFrame.MouseReleaseTrigger = CreateTrigger()
    set MoveAbleFrame.MouseReleaseTriggerAction = TriggerAddAction(MoveAbleFrame.MouseReleaseTrigger, function MouseRelease)
   
    set MoveAbleFrame.MOUSE_MOVE_Trigger = CreateTrigger()
    set MoveAbleFrame.MOUSE_MOVE_TriggerAction = TriggerAddAction(MoveAbleFrame.MOUSE_MOVE_Trigger, function CoordsMouse)

    loop
    exitwhen playerIndex == GetBJMaxPlayers()
        call TriggerRegisterPlayerEvent(MoveAbleFrame.MouseClickTrigger, Player(playerIndex), EVENT_PLAYER_MOUSE_DOWN)
        call TriggerRegisterPlayerEvent(MoveAbleFrame.MouseReleaseTrigger, Player(playerIndex), EVENT_PLAYER_MOUSE_UP)
        call TriggerRegisterPlayerEvent(MoveAbleFrame.MOUSE_MOVE_Trigger, Player(playerIndex), EVENT_PLAYER_MOUSE_MOVE)
        set playerIndex = playerIndex + 1
    endloop
call ExecuteFunc("Init")
//call TimerStart(Timer, 0.001, true, function update2)
endfunction

endlibrary

JASS:
library Testb requires MoveAbleFrame

private function click takes nothing returns nothing
    //call updateDebugFrame()
endfunction

private function enter takes nothing returns nothing
    //call updateDebugFrame()
endfunction

function onGameStart takes nothing returns nothing
    local framehandle gameUI = BlzGetFrameByName("ConsoleUIBackdrop", 0)
    local trigger t = CreateTrigger()
    local trigger t2 = CreateTrigger()
    local framehandle b
    local real size = 0.04
    call TriggerAddAction(t, function click)
   
    call TriggerAddAction(t2, function enter)
   
    set b =  BlzCreateFrameByType("GLUETEXTBUTTON", "b 1", gameUI, "ScriptDialogButton", 0)
    call BlzFrameSetAbsPoint(b, FRAMEPOINT_CENTER, 0.55, 0.4)
    call BlzFrameSetSize(b,size,size)
    call BlzFrameSetText(b, "b 1")

    call setup(b)

    call BlzTriggerRegisterFrameEvent(t, b, FRAMEEVENT_CONTROL_CLICK)
    call BlzTriggerRegisterFrameEvent(t2, b, FRAMEEVENT_MOUSE_ENTER)

    set b =  BlzCreateFrameByType("GLUETEXTBUTTON", "b 2", gameUI, "ScriptDialogButton", 0)
    call BlzFrameSetAbsPoint(b, FRAMEPOINT_CENTER, 0.55, 0.35)
    call BlzFrameSetSize(b,size,size)
    call BlzFrameSetText(b, "b 2")
    call setup(b)
    call BlzTriggerRegisterFrameEvent(t, b, FRAMEEVENT_CONTROL_CLICK)
    call BlzTriggerRegisterFrameEvent(t2, b, FRAMEEVENT_MOUSE_ENTER)
    set b =  BlzCreateFrameByType("GLUETEXTBUTTON", "b 3", gameUI, "ScriptDialogButton", 0)
    call BlzFrameSetAbsPoint(b, FRAMEPOINT_CENTER, 0.55, 0.45)
    call BlzFrameSetSize(b,size,size)
    call BlzFrameSetText(b, "b 3")
    call setup(b)
    call BlzTriggerRegisterFrameEvent(t, b, FRAMEEVENT_CONTROL_CLICK)
    call BlzTriggerRegisterFrameEvent(t2, b, FRAMEEVENT_MOUSE_ENTER)
    //call enable(GetLocalPlayer(),true)
    call BJDebugMsg(GetPlayerName(GetLocalPlayer()))
    call BJDebugMsg("onGameStart END")
endfunction

endlibrary
 

Attachments

I don't have much knowledge about frames, but in multiplayer, all players' states must be synced. I assume each player can move their frames around, is that it? Because if so, I would have expected to see checks for GetLocalPlayer before moving frames. Right now, any time one player's client moves a frame, this info is synced across all players. Granted, I do not know if frames will cause desync if their locations differ per client or not.

Another thing is your PolledWait2 - this is using a timer, but circumvents it and instead it uses TriggerSleepAction. TriggerSleepAction is not accurate, it is affected by network latency.

Finally, you use a 0.001 timeout for your PlayerTimers which are used to update frame locs. This seems excessive - most often a fluid movement can be seen with timeouts as high as 0.03 seconds. You could still lower yours down to 0.02, for example. That will significantly reduce the number of calls. Right now it looks like those timers just bombard host with excessive data to sync.

So basically to me it seems you are syncing data that does not need syncing; you sync it too often, which floods host with too many client state updates; and polled waits that are not accurate and usually longer than expected in multiplayer games.

Edit: perhaps you should also include an artificial delay when recalculating mouse coordinates. When you wrote that with higher FPS it looks worse, then the cause could also be in you running CoordsMouse function more often. It's just a wild guess, but maybe something to check out on your side.
 
Thanks to this code, I found out that the game is frame-sensitive. You can launch the map and set 300 FPS in the settings, then switch to 200 FPS, and you'll be surprised that at 200 FPS, there are no delays like at 300 FPS.

Even if boolean DRAG_TIMER_ENABLE = false (meaning PollewWait2 is not used), the same problem persists—and it's again solved by lowering the FPS (???).

Test it yourself and be surprised.
 
Thanks to this code, I found out that the game is frame-sensitive. You can launch the map and set 300 FPS in the settings, then switch to 200 FPS, and you'll be surprised that at 200 FPS, there are no delays like at 300 FPS.

Even if boolean DRAG_TIMER_ENABLE = false (meaning PollewWait2 is not used), the same problem persists—and it's again solved by lowering the FPS (???).

Test it yourself and be surprised.
Like Nichilus said, try a consistent timer running 50 times per second (0.02s) without using PolledWait()/TriggerSleepAction() anywhere. Then test 0.03s, 0.04s, etc.
 
Back
Top