Android Question [Solved] simple Do Until loop producing long delay

HowardTheDuck

Member
Licensed User
Hi, I seem to be getting extremely long delays within the Sub GameLoop > Do Until loop. This loop contains only some basic math, if then statements, and two list assignments; I expected this to process relatively quickly, but it is sometimes taking over 11 seconds (not microseconds, but full seconds) just to process one iteration.

I think the only other processes which should be running simultaneously are Sub Sensor_SensorChanged and Timer1_Tick. Sub Timer1_Tick is a bit more complex as it contains the routines to draw the screen, but I see nothing which should cause such a long delay.

Does anyone have any suggestions to improve this?

A brief game explanation:
At start, it should generate a maze in a 10x10 grid which fills the screen horizontally. Tilting the device causes a ball (not yet programmed) which is displayed in the center of the screen, to roll around in the maze. Rather than having the ball move, it remains centered while the background moves about it.

To simulate the ball movement realistically but keep things simple, I am taking into account the force of gravity only, not inertia or friction. To calculate horizontal/vertical movement, I note the position of the ball, iterate over Sub GameLoop, note the time difference and the orientation angle. I base the distance calculation on how far the ball would have fallen "through the screen" and towards the earth in the given time, and then calculate the hypotenuse of the angle that forms relative to the screen to give me screen position, then move the center point of the view to that position.

There is more planned, but this is the basis of what is programmed currently. At present I might get a brief view of the maze for one frame, then it just goes black when it updates, having calculated the position incorrectly due to the delay.

B4X:
#Region Module Attributes
    #FullScreen: False
    #IncludeTitle: True
    #ApplicationLabel: aMazeBalls
    #VersionCode: 1
    #VersionName:
    #SupportedOrientations: portrait
    #CanInstallToExternalStorage: False
#End Region

'Activity module
Sub Process_Globals
    'This map maps between PhoneSensors and SensorData objects.
    Dim SensorsMap As Map
    Type SensorData (Name As String, ThreeValues As Boolean)
    Public level = 1 As Int
    Public MazeMap(10,10) As List' items in list indicate T/F for presence of wall on current cell 0=N, 1=E, 2=S, 3=W, 4=Visited(T/F)
    Public Timer1 As Timer
    Public FPS = 1000/60 As Long ' set denominator to desired FPS
    Public tiltY, tiltZ As Float
    Public Xdpi,Ydpi, Scale As Float
    Public TileWidth, ScreenHeight, ScreenWidth As Int
    Public LastX = 0, LastY = 0 As Double
    Public StartTile, CurrentTile, EndTile As List
End Sub

Sub Globals
    Private pnlScreen As Panel
    Private cvsScreen As Canvas
End Sub

Sub Activity_Create(FirstTime As Boolean)
    Activity.LoadLayout("Main")
    If FirstTime Then
        SensorsMap.Initialize
        Dim ps As PhoneSensors 'This object is only used to access the type constants.
        AddSensor(ps.TYPE_ORIENTATION, "ORIENTATION", True)
    End If
    cvsScreen.Initialize(pnlScreen)
    Timer1.Initialize("Timer1",FPS)
    Timer1.Enabled = True
    
    'determine the screen size so it can calculate velocity realistically
    Dim r As Reflector 'requires reflection library
    r.Target = r.GetContext
    r.Target = r.RunMethod("getResources")
    r.Target = r.RunMethod("getDisplayMetrics")
    Xdpi = r.GetField("xdpi")
    Ydpi = r.GetField("ydpi")
    TileWidth = Floor(GetDeviceLayoutValues.Width/10)
    ScreenHeight = GetDeviceLayoutValues.Height
    ScreenWidth = GetDeviceLayoutValues.Width
    Scale = GetDeviceLayoutValues.Scale
End Sub

Sub AddSensor(SensorType As Int, Name As String, ThreeValues As Boolean) As SensorData
    Dim sd As SensorData
    sd.Initialize
    sd.Name = Name
    sd.ThreeValues = ThreeValues
    Dim ps As PhoneSensors
    ps.Initialize(SensorType)
    SensorsMap.Put(ps, sd)'ps seems to be an object not a string, why does that work as a key?
End Sub

Sub Activity_Resume
    'Here we start listening for SensorChanged events.
    'By checking the return value we knoe if the sensor is supported.
    For i = 0 To SensorsMap.Size - 1
        Dim ps As PhoneSensors
        Dim sd As SensorData
        ps = SensorsMap.GetKeyAt(i)
        sd = SensorsMap.GetValueAt(i)
        If ps.StartListening("Sensor") = False Then
            Log(sd.Name & " is not supported.")
        End If
    Next
    GameLoop
End Sub

Sub Activity_Pause (UserClosed As Boolean)
    'Stop listening for events.
    For i = 0 To SensorsMap.Size - 1
        Dim ps As PhoneSensors
        ps = SensorsMap.GetKeyAt(i)
        ps.StopListening
    Next
End Sub

Sub Sensor_SensorChanged (Values() As Float)
    tiltY = Values(1)
    tiltZ = Values(2)
    'Z tilt left up to 90, right to -90
    'Y tilt toward me -90, away from me 90
End Sub

Sub GameLoop
    CreateMaze
    Dim velocX = 0, velocY = 0 As Double
    Dim LastUpdateTime = DateTime.Now As Long
    Dim LastTiltY = 0, LastTiltZ = 0 As Long
    StartTile.Initialize
    CurrentTile.Initialize
    EndTile.Initialize
    StartTile = DefineStartTile 'origin is midpoint of this tile, which at start is midpoint of screen
    'keep track of movement of ball relative to origin, then calculate what needs displayed based upon those coordinates
    'use precise numbers (not Integers) to track pixel location, then floor for display purposes
    EndTile = DefineEndTile(StartTile)
    CurrentTile.Add(StartTile.Get(0))
    CurrentTile.Add(StartTile.Get(1))
    'loop while current location <> EndPoint
    Do Until CurrentTile.Get(0) = EndTile.Get(0) And CurrentTile.Get(1) = EndTile.Get(1)
        Sleep(30)
        'there should be a pause button, touching the screen should start/stop the game. it should be paused at the start of a new level
        
        'if tilt Y or Z is not level, calculate where ball would have moved to since last update and place it there
        'for simplification, I am ignoring momentum and friction in calculating ball position
        If tiltY <> 0 Or tiltZ <> 0 Then
            Dim UpdateInterval, Adjacent=0, Hypotenuse=0, deltaX=0, deltaY=0 As Long
            Dim dirX=1, dirY=1 As Int
            UpdateInterval = DateTime.Now - LastUpdateTime
            LastUpdateTime = DateTime.Now
            Dim Ipss = 32.17405 * 12 As Long 'this is inches per second squared, the acceleration of a falling object
            If tiltY <> 0 Then
                'make sure velocY is still valid, i.e. if it has changed direction reset it to 0
                If LastTiltY < 0 And tiltY >= 0 Then velocY = 0
                If LastTiltY > 0 And tiltY <= 0 Then velocY = 0
                'determine direction of travel
                If tiltY >0 Then
                    dirY = 1
                Else
                    dirY = -1
                End If
                Adjacent = velocY*(UpdateInterval/1000) + 0.5*Ipss*Power((UpdateInterval/1000),2)'distance ball would have moved toward ground, not across screen, in inches
                velocY = velocY + Ipss*(UpdateInterval/1000)
                Hypotenuse = Adjacent/CosD(tiltY)'this should be the distance travelled in the vertical direction of the screen, in inches. convert to pixels
                deltaY = Hypotenuse * Ydpi / Scale'divide to convert from actual to displayed resolution? should be number of displayed pixels to move from last location
                LastTiltY = tiltY
            End If
            If tiltZ <> 0 Then
                'make sure velocX is still valid, i.e. if it has changed direction reset it to 0
                If LastTiltZ < 0 And tiltZ >= 0 Then velocX = 0
                If LastTiltZ > 0 And tiltZ <= 0 Then velocX = 0
                'determine direction of travel
                If tiltZ > 0 Then
                    dirX = -1
                Else
                    dirX = 1
                End If
                Adjacent = velocX*(UpdateInterval/1000) + 0.5*Ipss*Power((UpdateInterval/1000),2)'distance ball would have moved toward ground, not across screen, in inches
                velocX = velocX + Ipss*(UpdateInterval/1000)
                Hypotenuse = Adjacent/CosD(tiltZ)'this should be the distance travelled in the vertical direction of the screen, in inches. convert to pixels
                deltaX = Hypotenuse * Xdpi / Scale'divide to convert from actual to displayed resolution? should be number of displayed pixels to move from last location
                LastTiltZ = tiltZ
            End If
'            deltaX=0
'            deltaY=0
            'now update the current position of the ball and determine what tile it is in
            Log("deltaX:" & deltaX & " " & "deltaY:" & deltaY & " UpdateInterval:" & UpdateInterval)
            LastX = LastX + dirX*deltaX
            LastY = LastY + dirY*deltaY
            Dim TileShiftX = 0, TileShiftY = 0 As Int
            TileShiftX = Floor((LastX+(TileWidth/2))/TileWidth)
            TileShiftY = Floor((LastY+(TileWidth/2))/TileWidth)
            CurrentTile.Set(0,StartTile.Get(0)+TileShiftX)
            CurrentTile.Set(1,StartTile.Get(1)+TileShiftY)
        End If
    Loop
    Log("Level up")
    'if current location = EndPoint then increment level and restart GameLoop
    level = level + 1
    GameLoop
End Sub

Sub CreateMaze
    'define the number of rows and columns
    If level = 1 Then
        'start with a simple 10x10 grid
        Dim Height = 10 As Int
    Else If level = 2 Then
        'now generate a maze big enough to fill the screen vertically, requiring it to scroll horizontally
        Dim Height = 15 As Int
    Else
        'generate a grid of [level 2 height] + (level - 2), square
        Dim Height = 15 + level - 2 As Int
    End If
    Public MazeMap(Height,Height) As List' items in list indicate T/F for presence of wall on current cell 0=N, 1=E, 2=S, 3=W, 4=Visited(T/F)
    Dim visited = 1, currentX = 0, currentY = 0, nextX, nextY As Int
    Dim nextLoc As String
    Dim path As List
    path.Initialize
    path.Add("0,0")
    MazeMap(0,0).Initialize
    MazeMap(0,0).AddAll(Array As Boolean(True,True,True,True,True))
    Do Until visited = Height * Height
        'look up neighboring cells and see which are unvisited
        Dim unvisited As List
        unvisited.Initialize
        For i = -1 To 1
            For j = -1 To 1
                If i + currentX >= 0 And i + currentX < Height Then
                    If j + currentY >= 0 And j + currentY < Height Then
                        If MazeMap(i + currentX, j + currentY).IsInitialized Then
                            If MazeMap(i + currentX, j + currentY).Get(4) = False Then
                                unvisited.Add((i + currentX) & "," & (j + currentY))
                            End If
                        Else
                            unvisited.Add((i + currentX) & "," & (j + currentY))
                        End If
                    End If
                End If
            Next
        Next
        
        If unvisited.Size > 0 Then
            'choose one at random
            nextLoc = unvisited.Get(Rnd(0,unvisited.Size))
            
            'make sure there are no walls between current location and nextLoc
            Dim commaLoc As Int
            commaLoc = nextLoc.IndexOf(",")
            nextX = nextLoc.SubString2(0, commaLoc)
            nextY = nextLoc.SubString(commaLoc + 1)
        
            If MazeMap(nextX,nextY).IsInitialized = False Then
                MazeMap(nextX,nextY).Initialize
                MazeMap(nextX,nextY).AddAll(Array As Boolean(True,True,True,True,True))
            End If
            'presence of wall on current cell 0=N, 1=E, 2=S, 3=W, 4=Visited(T/F)
            If nextX = currentX Then
                'remaining in same row, is it to the left or right?
                If currentY < nextY Then
                    'remove the top wall of current and lower wall of next
                    MazeMap(currentX,currentY).Set(0,False)
                    MazeMap(nextX,nextY).Set(2,False)
                Else
                    'remove the lower wall of current and top wall of next
                    MazeMap(currentX,currentY).Set(2,False)
                    MazeMap(nextX,nextY).Set(0,False)
                End If
            Else
                'remaining in same column, is it up or down?
                If currentX > nextX Then
                    'remove the left wall of current and the right wall of next
                    MazeMap(currentX,currentY).Set(3,False)
                    MazeMap(nextX,nextY).Set(1,False)
                Else
                    'remove the right wall of current and the left wall of next
                    MazeMap(currentX,currentY).Set(1,False)
                    MazeMap(nextX,nextY).Set(3,False)
                End If
            End If
        
            'set as visited
            path.Add(nextX & "," & nextY)
            MazeMap(nextX,nextY).Set(4,True)
            visited = visited + 1   
        Else
            'There is no where new to go, back up to the previous location and try this again
            path.RemoveAt(path.Size - 1)
            nextLoc = path.Get(path.Size - 1)
            nextX = nextLoc.SubString2(0, commaLoc)
            nextY = nextLoc.SubString(commaLoc + 1)
            
        End If
        currentX = nextX
        currentY = nextY
        nextX = 0'error using Null here
        nextY = 0
    Loop
End Sub

Sub DefineStartTile As List
    'choose a random starting point
    Dim tempList As List
    tempList.Initialize
    tempList.Add(Rnd(0,MazeMap.Length))
    tempList.Add(Rnd(0,MazeMap.Length))
    Return tempList
End Sub

Sub DefineEndTile(x_startpos As List) As List
    'determine what quadrant starting point is in (if any) and place endpoint in a different quadrant
    Dim x1,x2,mid As Int 'this will define the range to select from
    Dim tempList As List
    tempList.Initialize
    mid = Floor(MazeMap.Length/2)
    If x_startpos.Get(0) < mid Then
        x1 = mid
        x2 = MazeMap.Length -1
    Else
        x1 = 0
        x2 = mid
    End If
    tempList.Add(Rnd(x1,x2))
    tempList.Add(Rnd(0, MazeMap.Length))
    Return tempList
End Sub

Sub Timer1_Tick
    'refresh the screen with the current data
    'LastX and LastY tells where the current view is centered relative to the origin
    Log("Timer1_Tick " & DateTime.Now)
    Private rect1 As Rect
    rect1.Initialize(0dip,0dip,100%x,100%y)
    cvsScreen.DrawRect(rect1,Colors.Black,True,1) 'draw a black background
    'iterate over entire maze cell by cell to determine what is visible and where it should be displayed
    'may wish to put something in here to reduce iteration scope for very large mazes
    Log("Timer1_Tick start of nested for loops")
    For i = 0 To MazeMap.Length - 1
        For j = 0 To MazeMap.Length - 1
            'determine where this tile is relative to starting tile and LastX,LastY
            Dim DistToStartX, DistToStartY As Int
            DistToStartX = (StartTile.Get(0) - i)*TileWidth
            DistToStartY = (StartTile.Get(1) - j)*TileWidth
            
            Dim ULTileX,ULTileY As Double 'x & y coordinates of upper left corner of current tile
            If CurrentTile.Get(0) <> StartTile.Get(0) Then
                ULTileX = 0 - (TileWidth/2) - DistToStartX - LastX'coordinates relative to center of screen
            Else
                ULTileX = 0 - DistToStartX - LastX
            End If
            If CurrentTile.Get(1) <> StartTile.Get(1) Then
                ULTileY = 0 + (TileWidth/2) + DistToStartY - LastY
            Else
                ULTileY = 0 + DistToStartY - LastY
            End If
            'the above may have produced fractional numbers, floor everything so we can use it for pixel display
            ULTileX = Floor(ULTileX)
            ULTileY = Floor(ULTileY)
            
            Log(ULTileX & " " & ULTileY & " " & LastX  & " " & LastY)
            'the upper left corner of this tile's coordinates are known; check whether any of this tile is visible
            If ULTileX >= (-1*(Floor(ScreenWidth/2))-TileWidth) And ULTileX < Floor(ScreenWidth/2) Then 'if any of the X coordinates are visible
                If ULTileY <= (Floor(ScreenHeight/2) + TileWidth) And ULTileY > -1*(Floor(ScreenHeight/2)) Then 'if any of the Y coordinates are visible
                    Log("Draw tile")
                    'draw this tile's features
                    'determine the coordinates of the 4 visible corners of this tile
                    Dim ULX, ULY, URX, URY, LLX, LLY, LRX, LRY As Int
                    If ULTileX + Floor(ScreenWidth/2) < 0 Then
                        ULX = 0'truncate, tile begins to the left of the screen
                    Else
                        ULX = ULTileX + Floor(ScreenWidth/2)
                    End If
                    
                    If ULTileY - Floor(ScreenHeight/2) > 0 Then
                        ULY = 0'truncate, tile begins above the screen
                    Else
                        ULY = Floor(ScreenHeight/2) - ULTileY
                    End If
                    
                    If ULTileX + TileWidth + Floor(ScreenWidth/2) > ScreenWidth Then
                        URX = ScreenWidth'truncate, tile extends past the right of the screen
                        Log("used ScreenWidth: " & ScreenWidth)
                    Else
                        URX = ULTileX + TileWidth + Floor(ScreenWidth/2)
                        Log("all visible: " & URX)
                    End If
                    
                    If ULTileY - Floor(ScreenHeight/2) > 0 Then
                        URY = 0'truncate, tile begins above the screen
                    Else   
                        URY = Floor(ScreenHeight/2) - ULTileY
                    End If
                    
                    If ULTileX + Floor(ScreenWidth/2) < 0 Then
                        LLX = 0 'truncate, tile begins to the left of the screen
                    Else
                        LLX = ULTileX + Floor(ScreenWidth/2)
                    End If
                    
                    If ULTileY - TileWidth < -Floor(ScreenHeight/2) Then
                        LLY = ScreenHeight'truncate, tile lies partially below screen
                    Else
                        LLY = Floor(ScreenHeight/2) - ULTileY + TileWidth
                    End If
                    
                    If ULTileX + TileWidth + Floor(ScreenWidth/2) > ScreenWidth Then
                        LRX = ScreenWidth'truncate, tile extends past the right of the screen
                    Else
                        LRX = ULTileX + TileWidth + Floor(ScreenWidth/2)
                    End If
                    
                    If ULTileY - TileWidth < -1*(Floor(ScreenHeight/2)) Then
                        LRY = ScreenHeight'truncate, tile lies partially below screen
                    Else
                        LRY = Floor(ScreenHeight/2) - ULTileY + TileWidth
                    End If
                    
                    Log(ULX & " " & ULY & " " & URX & " " & URY & " " & LLX & " " & LLY & " " & LRX & " " & LRY)
                    
                    'coordinates are defined above, draw the tile
                    Private Tile As Rect
                    Tile.Initialize(ULX,ULY,LRX,LRY)
                    cvsScreen.DrawRect(Tile,Colors.White,True,0)
                    
                    'now draw any required boundary lines on this tile
                    'need to test whether these boundaries are supposed to be visible
                    If MazeMap(i,j).Get(0) Then
                        'North wall is True
                        Private North As Rect
                        North.Initialize(ULX,ULY,URX,URY-1)
                        cvsScreen.DrawRect(North,Colors.Black,True,0)
                    End If
                    If MazeMap(i,j).Get(1) Then
                        'East wall is True
                        Private East As Rect
                        East.Initialize(URX-1,URY,LRX,LRY)
                        cvsScreen.DrawRect(East,Colors.Black,True,0)
                    End If
                    If MazeMap(i,j).Get(2) Then
                        'South wall is True
                        Private South As Rect
                        South.Initialize(LLX,LLY+1,LRX,LRY)
                        cvsScreen.DrawRect(South,Colors.Black,True,0)
                    End If
                    If MazeMap(i,j).Get(3) Then
                        'West wall is True
                        Private West As Rect
                        West.Initialize(ULX,ULY,ULX+1,LLY)
                        cvsScreen.DrawRect(West,Colors.Black,True,0)
                    End If
                End If
            End If
        Next
    Next
    pnlScreen.Invalidate
End Sub
 

OliverA

Expert
Licensed User
What i see: Gameloop is creating a never ending call chain. You call gameloop from gameloop before gameloop exits. So you always call gameloop, but gameloop never gets a chance to exit. Looks like a memory leak (ever growing heap space for each gameloop call).
 

HowardTheDuck

Member
Licensed User
hmm, well that is another problem (thank you), but probably not the problem causing the delay. It is never exiting the Sub GameLoop > Do Until loop because there is no way to satisfy the until statement at present. so, I think ignore that for now, it is not relevant. Just consider that the game remains on level 1 and never exits that loop.
 

Syd Wright

Well-Known Member
Licensed User
What i see: Gameloop is creating a never ending call chain. You call gameloop from gameloop before gameloop exits. So you always call gameloop, but gameloop never gets a chance to exit. Looks like a memory leak (ever growing heap space for each gameloop call).
OliverA is right. It is not "another problem", but one of the main causes of your problem.
You also may want to change Sleep(30) to Sleep(1).
 

HowardTheDuck

Member
Licensed User
Syd Wright, thanks, it was the Sleep(30), reducing that to 1 caused the delays to shrink down to ~20-30 milliseconds, which should be acceptable for this. Thanks again.

If anyone was wondering about that other loop:
If you remove the call to GameLoop on line 173, as I now have, you will find that it exhibits the exact same behavior. The only way that second GameLoop can be accessed is if you tilt the device in just the right way to move the center of the screen to the location of "EndTile" which is unlikely to happen by accident, and never once happened while I was keeping track of it in the logs. I've removed it, it was nothing more than a reminder to myself to build a proper loop to allow leveling up. I didn't expect anyone to focus on it, as I knew it to be irrelevant. My fault for leaving that red herring in there, sorry!

sorex, yes, it is drawing the maze out of sight, but that was due to the delay in GameLoop described in the first post. It reads that the user has been causing the background to scroll (by tilting the device) for too long a period of time, thus pushing the maze off-screen. once I build some collision detection into this, it should prevent that from happening.

thanks again for the input everyone!
 
Top