Results 1 to 13 of 13

Thread: GameLoop theories!

  1. #1

    Thread Starter
    Super Moderator dday9's Avatar
    Join Date
    Mar 2011
    Location
    South Louisiana
    Posts
    11,715

    GameLoop theories!

    I know that there are a ton of ways to execute a game loop so I'm dedicating this thread to just that. I'd hope that this leads to improvement of all types of game loops.

    Here is my first example that I came up with about 3 months back:
    Code:
    Private quit As Boolean = False
    Private Sub GameLoop()
        'Update
        'Draw
        'Render
    
        'Loop
        If quit = False Then
            ExecuteAfterPause(CInt(16.6), New MethodInvoker(AddressOf GameLoop))
        End If
    End Sub
    This first one uses JMcIlhinney's pauser code.

    Here is my second example that I came up with literally about 3 seconds ago:
    Code:
        Private game_thread As Threading.Thread
    
        Private Sub Form1_FormClosing(sender As Object, e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
            quit = True
        End Sub
    
        Private Sub Form1_Load(sender As Object, e As System.EventArgs) Handles Me.Load
    
            game_thread = New Threading.Thread(AddressOf GameLoop)
            game_thread.Start()
    
        End Sub
    
        Private quit As Boolean = False
        Private Sub GameLoop()
    
            Dim s As New Stopwatch
            s.Start()
    
            'Update
            'Draw
            'Render
    
            'Loop
            While s.Elapsed <= TimeSpan.FromMilliseconds(16.6)
                Application.DoEvents()
            End While
    
            Console.WriteLine(s.ElapsedMilliseconds)
    
            s.Stop()
            If quit = False Then
                game_thread = New Threading.Thread(AddressOf GameLoop)
                game_thread.Start()
            End If
        End Sub
    For both of those I get 17 frames per milliseconds. The second one is a little bit more jittery and I think that has to do with the DoEvents there. There are even times where it spikes up to 30 frames per millisecond.
    Last edited by dday9; Dec 24th, 2013 at 11:38 AM. Reason: Took out the VB.Net prefix.
    "Code is like humor. When you have to explain it, it is bad." - Cory House
    VbLessons | Code Tags | Sword of Fury - Jameram

  2. #2
    College Grad!!! Jacob Roman's Avatar
    Join Date
    Aug 2004
    Location
    Miami Beach, FL
    Posts
    5,339

    Re: GameLoop theories!

    Ooooh but there are far better ways to do a game loop my friend. And I'm about to unleash my own personal secrets. Games have not just the game itself, but also an Intro, a Title Screen (with options), sometimes a demo, a loading screen, and even an ending (not to mention collision detection, ai, controls, physics, and a bunch of other things), all managed by one entity...the main game loop. And it must be locked at 60 frames per second. Unless you live in Europe where the standard is 50 frames per second. To lock the framerate at any given rate, you do this:

    vb.net Code:
    1. Option Explicit On
    2.     Option Strict On
    3.      
    4.     Public Class Form1
    5.      
    6.         Private Declare Function timeGetTime Lib "winmm.dll" () As Integer
    7.      
    8.         Private Milliseconds As Integer
    9.         Private Get_Frames_Per_Second As Integer
    10.         Private Frame_Count As Integer
    11.      
    12.         Private Running As Boolean
    13.      
    14.         Private Function Get_Elapsed_Time() As Single
    15.             Dim Current_Time As Integer
    16.      
    17.             Current_Time = timeGetTime
    18.             Return Convert.ToSingle(Current_Time) / 1000
    19.         End Function
    20.      
    21.         Private Sub Lock_Framerate(ByVal Target_FPS As Integer)
    22.             Static Last_Time As Integer
    23.             Dim Current_Time As Integer
    24.             Dim FPS As Single
    25.      
    26.             Do
    27.                 Current_Time = timeGetTime
    28.                 FPS = 1000 / Convert.ToSingle(Current_Time - Last_Time)
    29.             Loop While (FPS > Target_FPS)
    30.      
    31.             Last_Time = timeGetTime
    32.         End Sub
    33.      
    34.         Private Function Get_FPS() As String
    35.             Frame_Count = Frame_Count + 1
    36.      
    37.             If Get_Elapsed_Time() - Milliseconds >= 1 Then
    38.                 Get_Frames_Per_Second = Frame_Count
    39.                 Frame_Count = 0
    40.                 Milliseconds = Convert.ToInt32(Get_Elapsed_Time)
    41.             End If
    42.      
    43.             Return "Frames Per Second: " & Convert.ToString(Get_Frames_Per_Second)
    44.         End Function
    45.      
    46.         Private Sub Game_Loop()
    47.             Do While Running = True
    48.                 'Game Code Here
    49.                 Lock_Framerate(60)
    50.                 Me.Text = Get_FPS()
    51.                 Application.DoEvents()
    52.             Loop
    53.         End Sub
    54.      
    55.         Private Sub Main()
    56.             With Me
    57.                 .Show()
    58.                 .BackColor = Color.FromArgb(255, 0, 0, 0)
    59.                 .DoubleBuffered = True
    60.             End With
    61.      
    62.             Running = True
    63.             Game_Loop()
    64.         End Sub
    65.      
    66.         Private Sub Shutdown()
    67.             Running = False
    68.             Application.Exit()
    69.         End Sub
    70.      
    71.         Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
    72.             Main()
    73.         End Sub
    74.      
    75.         Private Sub Form1_FormClosing(ByVal sender As Object, ByVal e As System.ComponentModel.CancelEventArgs) Handles MyBase.FormClosing
    76.             Shutdown()
    77.         End Sub
    78.     End Class

    Unless you use DirectX, then it locks the framerate for you. Once your game gets more complex and you wanna add more things, you break it up a notch:

    vb.net Code:
    1. Private Enum GAME_STATUS
    2.         INTRO = 1
    3.         TITLE_SCREEN = 2
    4.         LOAD_SCREEN = 3
    5.         GAME_WORLD = 4
    6.         ENDING = 5
    7.     End Enum
    8.  
    9.     Private Status as GAME_STATUS
    10.  
    11.     ...
    12.  
    13.         Private Sub Game_Loop()
    14.             Do While Running = True
    15.                 Select Case Status
    16.                     Case INTRO
    17.                         Intro_Loop
    18.                     Case TITLE_SCREEN
    19.                         Title_Screen_Loop
    20.                     Case LOAD_SCREEN
    21.                         Load_Screen_Loop
    22.                     Case GAME_WORLD
    23.                         Game_World_Loop
    24.                     Case ENDING
    25.                         Ending_Loop
    26.                 End Select
    27.                 Lock_Framerate(60)
    28.                 Me.Text = Get_FPS()
    29.                 Application.DoEvents()
    30.             Loop
    31.         End Sub

    Where the Intro_Loop, Title_Screen_Loop, Game_World_Loop, etc., are their own sub routines that contain all the code done in the main game loop. But the subs DO NOT contain their own endless Do loops. That would defeat the purpose of what we are doing and the frame rate would not be locked because you are stuck in another endless loop. The subs only contain the code that is executed over and over related to the game. Doing this would help you build an entire game with these entities from start to finish. And you can lock the frame rate at any given rate desired, even for debugging purposes which helps a ton. I not only do this code for VB6 and VB.NET, but also C++ as well. It works. I hope this helps.

  3. #3

    Thread Starter
    Super Moderator dday9's Avatar
    Join Date
    Mar 2011
    Location
    South Louisiana
    Posts
    11,715

    Re: GameLoop theories!

    Holy crap I wish I had known about the timeGetTime function before! This is going to be fun :]

    Do you run the loop on the UI thread or do you run it on it's own separate thread?

    Have you ever tried running a game loop using parallelism?

    I haven't ever run anything that was very data intensive such as complex AI, so I really have nothing to compare to when it comes to FPS and multi/parallel threading.
    "Code is like humor. When you have to explain it, it is bad." - Cory House
    VbLessons | Code Tags | Sword of Fury - Jameram

  4. #4
    College Grad!!! Jacob Roman's Avatar
    Join Date
    Aug 2004
    Location
    Miami Beach, FL
    Posts
    5,339

    Re: GameLoop theories!

    To be honest I dont do multithreading. Its all under one thread and works like a charm, even in my massively sized games I wrote. I just dont see the need to put the loop on another thread.

  5. #5

    Thread Starter
    Super Moderator dday9's Avatar
    Join Date
    Mar 2011
    Location
    South Louisiana
    Posts
    11,715

    Re: GameLoop theories!

    Let me ask you this Jacob, I know why it's important to have a game locked in at a certain FPS, but why is the target at 60(or 50 in Europe)? I never quite got why it should be at 60 and not at say... 40 or hell even 120.
    "Code is like humor. When you have to explain it, it is bad." - Cory House
    VbLessons | Code Tags | Sword of Fury - Jameram

  6. #6
    PowerPoster dunfiddlin's Avatar
    Join Date
    Jun 2012
    Posts
    8,245

    Re: GameLoop theories!

    What's your screen refresh rate? 60Hz, right? (As indeed is your electricity supply.) There's no point having an FPS greater than that because it would simply fail to show some frames altogether. And if its lower than that it could go out of phase with the possibility of visual glitches. In Europe the power supply is 50Hz and the refresh rate has usually matched it. Having said that I'm now running a 60Hz refresh rate (my display settings won't allow me not to!) so I'm not sure whether it's still strictly necessary to change the target fps to suit us Right Pondians.
    As the 6-dimensional mathematics professor said to the brain surgeon, "It ain't Rocket Science!"

    Reviews: "dunfiddlin likes his DataTables" - jmcilhinney

    Please be aware that whilst I will read private messages (one day!) I am unlikely to reply to anything that does not contain offers of cash, fame or marriage!

  7. #7

    Thread Starter
    Super Moderator dday9's Avatar
    Join Date
    Mar 2011
    Location
    South Louisiana
    Posts
    11,715

    Re: GameLoop theories!

    Oh ok, that makes sense. Because I was looking at reviews on some different processors and I saw that they were saying: "Oh with this processor I had blah blah blah amount of FPS" and I'm thinking ok so it's better to have a higher FPS? But really it's all dependent on the screen's refresh rate then.
    "Code is like humor. When you have to explain it, it is bad." - Cory House
    VbLessons | Code Tags | Sword of Fury - Jameram

  8. #8
    College Grad!!! Jacob Roman's Avatar
    Join Date
    Aug 2004
    Location
    Miami Beach, FL
    Posts
    5,339

    Re: GameLoop theories!

    Quote Originally Posted by dday9 View Post
    Oh ok, that makes sense. Because I was looking at reviews on some different processors and I saw that they were saying: "Oh with this processor I had blah blah blah amount of FPS" and I'm thinking ok so it's better to have a higher FPS? But really it's all dependent on the screen's refresh rate then.
    Exactomundo. And if you work with DirectX, it does it for you. No locking framerates necessary. And whats good about my code on locking the framerate is that you can slow it down to lets say 10 FPS for debugging purposes.

  9. #9

    Thread Starter
    Super Moderator dday9's Avatar
    Join Date
    Mar 2011
    Location
    South Louisiana
    Posts
    11,715

    Re: GameLoop theories!

    See XNA has a managed game loop built in... if you use the GameStudios which is only available for C#. This forced me to think of ways for game loops. Now with XNA being no more... I'm trying to expand my options.
    "Code is like humor. When you have to explain it, it is bad." - Cory House
    VbLessons | Code Tags | Sword of Fury - Jameram

  10. #10
    Sinecure devotee
    Join Date
    Aug 2013
    Location
    Southern Tier NY
    Posts
    6,582

    Re: GameLoop theories!

    Thought you should know that TimeGetTime is not necessarily consistent across all hardware and versions of Windows, so may or may not give you 60hz using Jacob's code.
    I've gotten to use a lot of different computers, and Windows (or the hardware manufactures) lack of consistency when it comes to the clocks behind these various API calls has been a real thorn in the side.
    Just when you think you have a simple timing solution, you find a machine that doesn't work.
    In this case, I tried it on an HP EliteBook 8470p laptop (running Windows 7 Enterprise 64-bit), and I asked for 60hz, but was getting 32hz.
    I investigated and found that the time returned by the timeGetTime function incremented at 64hz, i.e. some example deltas
    35006709, 35006709, 35006709,..... doesn't matter how often you read it in the loop, it won't change until
    35006724, 35006724, 35006724,..... 15.625 milliseconds has passed....
    35006740, 35006740, 35006740,.....

    So, what happens is we try to wait for 16.666 milliseconds to pass, and the timeGetTime changes just short of that, so we continue to wait, and it doesn't change again until another 64hz period has passed, at which time we see we've exceeded the 16.666 milliseconds (by quite a bit) and return.
    The result is, we always wait two 64hz periods, so are running at 32 hz.

    One way to improve on that situation is to establish a base time, and add the "trigger" time to that.
    Instead of
    31. Last_Time = timeGetTime
    you have
    31. Last_Time += 1000/Target_FPS

    The result is you will average 60 hz over time. In this case, the code will run at 64 hz for around 11 passes (15 to 16ms), and then 32hz for 1 pass(31 ms)
    and repeat.
    You need a safety check to initialize the Last_Time, and in case of debugging, or some other reason your code gets delayed and you miss a number of frames, you reset the Last_Time to re-sync to the current time.
    Since we expect one time period out of 11 to be almost twice the target_FBS, we'll set the limit to be three times the target_FBS interval (i.e. if the update was less than 20hz, reset the Last_Time value)
    If ((Current_Time - Last_Time) > (3 * (1000 / Target_FPS))) Then Last_Time = Current_Time

    Bottom line, you can't always trust that timeGetTime will return 1 ms resolution, or even close to it.

  11. #11

    Thread Starter
    Super Moderator dday9's Avatar
    Join Date
    Mar 2011
    Location
    South Louisiana
    Posts
    11,715

    Re: GameLoop theories!

    What happens if you run my very first example?
    "Code is like humor. When you have to explain it, it is bad." - Cory House
    VbLessons | Code Tags | Sword of Fury - Jameram

  12. #12
    Sinecure devotee
    Join Date
    Aug 2013
    Location
    Southern Tier NY
    Posts
    6,582

    Re: GameLoop theories!

    Quote Originally Posted by dday9 View Post
    What happens if you run my very first example?
    I'm on a different machine now, but I did a quick test to see what interval I was getting on timeGetTime to see if I could use this laptop as a test platform, or wait until I was back on the machine that had the 64hz based interval.
    The interesting thing on this machine, is that it looks like it is mostly 100hz based, with the millisecond interval being 10ms for a good portion of a second.
    But then it will drop to 1ms resolution for a series, up to 22 times in a row, then return to 10ms. On this machine I never saw it slower than 10ms, but testing 400 loops (which would be about 4 seconds at 10ms), showed that it increased to 1ms resolution for several streaks, but never in a consistent pattern. it would also have short breaks (usually only 3 or 4 times through the loop) where it would be some other smaller number, in the range of 2 to 4ms.

    Anyway, since it wasn't a consistent 1ms resolution, being 10ms the majority of the time, it seemed an acceptable surrogate for the other computer.
    Using your first code, based on JMcIlhinney's pauser code, it behaved as I would expect, with erratic timing, the majority of the time being around 20 to 27ms (JMc's pause time (17) + the 10ms coarseness of the timer, dropping to 17ms or so when the timer dropped to 1ms resolution).
    Your second code in the first post was a very consistent 17ms, because the stopwatch object on most of the machines I've tried is very consistent, and when I tested the stopwatch earlier with my timing test (I only tested the millisecond accuracy), it was consistent at 1ms. It actually would probably be accurate to some sub-millisecond value, if I looked at the frequency of Tick change, but I didn't).
    I removed the Applications.DoEvents call from your second code, because that loop is on a background thread so what purpose would calling DoEvents have on a background thread (other than possibly, as you suspected, introduce some connection back to the GUI thread, making the timing inconsistent).
    The way the background thread is used is the main drawback of that code (in my opinion), since it is launching and exiting a background thread for every loop, which is a lot of resource thrashing.

    The simplest and best timing on this machine (and I will have to relook at the other machine to check its stopwatch accuracy), is to simply replace the timeGetTime call in Jacob's code with a stopwatch.ElapsedMilliseconds call to get a very well behaved locked frame rate, the vast majority of the time. It definitely would appear, given the half dozen or so machines that I've tried it on over the last two years, is that the stopwatch is more likely to give a reasonable result compared to timeGetTime. The stopwatch object uses the high resolution timer tick count, if it is supported, so is based on the same source that the unmanaged API calls QueryPerformanceFrequency and QueryPerformanceCounter are based.
    The only caveat (which also applies to QueryPerformanceCounter), is that on multi-processor machines, the counter value can be different depending on which core the thread is running on, so you would want to specify processor affinity for a thread, with the ProcessThread.ProcessorAffinity method, to have consistent timing at the possible cost of performance loss by the system not being able to switch your thread to a lighter loaded processor (but hopefully keeping the processors balanced by moving threads off "your" processor that aren't tied to a particular processor, so keeping the impact of your desire to stick to one processor at a minimum.
    Last edited by passel; Mar 20th, 2014 at 12:27 AM.

  13. #13
    College Grad!!! Jacob Roman's Avatar
    Join Date
    Aug 2004
    Location
    Miami Beach, FL
    Posts
    5,339

    Re: GameLoop theories!

    Guys guys lol. I posted the wrong code. Must have been drunk cause that was an old and obsolete program. I don't really use the timeGetTime for my game loops so don't sweat it. I was just rushing out the answer

    I always use the QueryPerformance APIs. 60 frames per second every time. Sorry for the mishap, I don't want the thread to stretch on over it :rollseyes:

    vb.net Code:
    1. Option Explicit On
    2. Option Strict On
    3.  
    4. Public Class Form1
    5.  
    6.     Private Declare Function QueryPerformanceCounter Lib "kernel32" (ByRef lpPerformanceCount As Long) As Integer
    7.     Private Declare Function QueryPerformanceFrequency Lib "kernel32" (ByRef lpFrequency As Long) As Integer
    8.  
    9.     Private Milliseconds As Single
    10.     Private Get_Frames_Per_Second As Integer
    11.     Private Frame_Count As Integer
    12.  
    13.     Private Running As Boolean
    14.  
    15.     Private Ticks_Per_Second As Long
    16.     Private Start_Time As Long
    17.  
    18.     Private Sub Hi_Res_Timer_Initialize()
    19.         QueryPerformanceFrequency(Ticks_Per_Second)
    20.     End Sub
    21.  
    22.     Private Function Get_Elapsed_Time() As Single
    23.         Dim Current_Time As Long
    24.  
    25.         QueryPerformanceCounter(Current_Time)
    26.         Return CSng(Current_Time / Ticks_Per_Second)
    27.     End Function
    28.  
    29.     Private Sub Lock_Framerate(ByVal Target_FPS As Long)
    30.  
    31.         Static Last_Time As Long
    32.         Dim Current_Time As Long
    33.         Dim FPS As Single
    34.  
    35.         Do
    36.             QueryPerformanceCounter(Current_Time)
    37.             FPS = CSng(Ticks_Per_Second / (Current_Time - Last_Time))
    38.         Loop While (FPS > Target_FPS)
    39.  
    40.         QueryPerformanceCounter(Last_Time)
    41.  
    42.     End Sub
    43.  
    44.     Private Function Get_FPS() As String
    45.         Frame_Count = Frame_Count + 1
    46.  
    47.         If Get_Elapsed_Time() - Milliseconds >= 1 Then
    48.             Get_Frames_Per_Second = Frame_Count
    49.             Frame_Count = 0
    50.             Milliseconds = Convert.ToInt32(Get_Elapsed_Time)
    51.         End If
    52.  
    53.         Return "Frames Per Second: " & Convert.ToString(Get_Frames_Per_Second)
    54.     End Function
    55.  
    56.     Private Sub Game_Loop()
    57.         Do While Running = True
    58.             'Game Code Here
    59.  
    60.             Lock_Framerate(60)
    61.             Me.Text = Get_FPS()
    62.             Application.DoEvents()
    63.         Loop
    64.     End Sub
    65.  
    66.     Private Sub Main()
    67.         Me.Show()
    68.         Hi_Res_Timer_Initialize()
    69.         Milliseconds = Get_Elapsed_Time()
    70.         Running = True
    71.         Game_Loop()
    72.     End Sub
    73.  
    74.     Private Sub Shutdown()
    75.         Running = False
    76.         Application.Exit()
    77.     End Sub
    78.  
    79.     Private Sub Form1_FormClosing(ByVal sender As Object, ByVal e As System.ComponentModel.CancelEventArgs) Handles MyBase.FormClosing
    80.         Shutdown()
    81.     End Sub
    82.  
    83.     Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
    84.         Main()
    85.     End Sub
    86. End Class

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width