What is good cross platform gameloop?

Game development specific discussions.
coderJeff
Site Admin
Posts: 2693
Joined: Nov 04, 2005 14:23
Location: Ontario, Canada
Contact:

Re: What is good cross platform gameloop?

Postby coderJeff » Jun 15, 2018 17:18

About tearing when writing to the display. We have looked at several methods for updating the display. locking, page flipping, etc, There are also several graphics drivers, different on each platform DOS/Win/Lin, etc.

You can see what driver is in use using SCREENCONTROL(fb.GET_DRIVER_NAME,s). If you want to try and force a driver, set the environment variable FBGFX=driver, where driver is DirectX or GDI, for example.

To eliminate tearing, we want to synchronize writing to the display memory, so we won't see top part a frame and the bottom part of the last. Typically some kind of wait for vertical blanking interrupt. SCREENSYNC is supposed to provide

DOS: polls hardware Port &h3DA
Linux: polls ioctl for the device vertical blank interrupt
Windows - DirectX: IDirectDraw2_WaitForVerticalBlank
Windows - GDI: sleep( 1000/refresh_rate) - it's fake.

On Win7 64bit, fbc 64-bit, DirectX (DirectDraw) driver always fails, so always GDI driver is used. Which means, always always going to see tearing in windows even in fullscreen.
On Win7 64bit, with fbc 32-bit, windows provides enough emulation that DirectX succeeds, IDirectDraw2_WaitForVerticalBlank works correctly in fullscreen mode (but not in windowed mode).

It's possible that there is a bug in initializing DirectX on fbc-64bit, but with the DirectDraw interface deprecated by Microsoft, I kind of doubt it.
coderJeff
Site Admin
Posts: 2693
Joined: Nov 04, 2005 14:23
Location: Ontario, Canada
Contact:

Re: What is good cross platform gameloop?

Postby coderJeff » Jun 15, 2018 18:22

dodicat wrote:Here is my simple idea.
1) fix a frame rate via a regulator, and keep it at that value throughout.
or
2) Enable various frame rates, but keep the screen motion constant.

I think a fixed frame rate gives more predictable results. In this next demo, I try to expand on your idea to show what can happen. The results are intended to be a bit exaggerated so the differences show up easily.

I had a closer look at your pendulum example and see you are using approximation formula for the pendulum position (angle). i.e. position(t) = formula(t), Also, visually, very noticeable that there are two pendulums with same length, and different periods.

I wrote a version of the pendulum code that:
1) uses units: meters, seconds, radians
2) shows 6 pendulums
3) the top 3 using the approximation formula, at x1, x2, x4 physics frame rates.
4) the bottom 3 using newton laws, numerical integration, at x1, x2, x4 physics frame rates.
5) Can toggle between fixed and variable frame rate.
6) press SPACE to add 0.5 second delay and see how methods respond.
7) +/- to change starting position of the pendulums
8) [ ] to change the pendulum length

Code: Select all

#include once "fbgfx.bi"
#include once "vbcompat.bi"

const pi = 3.14159265
const g = -9.81

dim shared pen_color( 1 to 6 ) as ulong = _
   { _
      rgb(127, 255, 255), rgb(255, 127, 255), rgb(255, 255, 127), _
      rgb(255, 63, 63), rgb(63, 255, 63), rgb(63, 63, 255) _
   }

type PENDULUM
   px as single          '' [m] pivot point x
   py as single          '' [m] pivot point y
   length as single      '' [m]
   radius as single      '' [m]
   amplitude as single   '' [radians]
   col as ulong          '' colour

   '' newton
   last_w as single      '' last angular velocity
   w as single           '' angular velocity

   '' calculated
   h_angle as single     '' [radians] harmonic (approximation)
   theta as single       '' [radians] calculate position angle
   bx as single          '' [m] center of mass x
   by as single          '' [m] center of mass y

   last_dtheta as single '' [radians] last change in theta
   tstart as double      '' [s] start of period
   last_T as double      '' [s] last measured period
   isstart as boolean    '' true = at start of period

   declare sub SetPosition( x as single, y as single, l as single, r as single, a_amp as single, c as ulong )
   declare sub physApprox( dt as const double )
   declare sub physNewton( dt as const double )
   declare sub draw()
end type

sub PENDULUM.SetPosition( x as single, y as single, l as single, r as single, a_amp as single, c as ulong )
   px = x
   py = y
   length = l
   radius = r
   amplitude = a_amp
   col = c
   w = 0
   last_w = 0
   h_angle = 0
   theta = a_amp
   bx = px + sin( theta ) * length
   by = py + cos( theta ) * length
   last_dtheta = 0
   tstart = timer
   last_T = 0
   isstart = true
end sub

sub PENDULUM.draw()
   
   color rgb(255,255,255)
   ..draw string (px, py - pmap(20, 2)), "T=" & format(last_T, "0.00" ) & " sec"

   line ( px, py ) - ( bx, by ), col
   circle ( bx, by ), radius, col,,,,f
end sub

sub PENDULUM.physApprox( dt as const double )
   
   dim theta0 as single = theta
   
   '' approximate angle, T = 2pi * sqr( L/-g )
   '' where:
   ''   T = period [seconds]
   ''   L = length [m]
   
   h_angle += dt / sqr( length / -g )

   '' theta = approximate position angle
   theta = cos( h_angle ) * amplitude
   bx = px + sin( theta ) * length
   by = py + cos( theta ) * length

   '' calculate change in theta
   dim as single dtheta = theta - theta0

   '' start of period?
   isstart = false
   if ( sgn(last_dtheta) <= 0 ) and ( sgn(dtheta) > 0 ) then
      dim t as double = timer
      last_T = t - tstart
      tstart = t
      isstart = true
   end if
   last_dtheta = dtheta

end sub

sub PENDULUM.physNewton( dt as const double )
   
   dim theta0 as single = theta

   '' compute the change in angular velocity
   '' and angular position

   '' project acceleration due to gravity
   '' on to direction of movement vector

   dim at as single = g * sin( theta )
   
   '' angular velocity
   last_w = w
   w += at * dt / length

   '' change in position
   theta += w * dt

   '' normalize angle
   if( theta > pi ) then theta -= 2 * pi
   if( theta < -pi ) then theta += 2 * pi

   '' calculate position
   bx = px + sin( theta ) * length
   by = py + cos( theta ) * length

   '' calculate change in theta
   dim as single dtheta = theta - theta0

   '' start of period?
   isstart = false
   if ( sgn(last_w) <= 0 ) and ( sgn(w) > 0 ) then
      dim t as double = timer
      last_T = t - tstart
      tstart = t
      isstart = true
   end if
   last_dtheta = dtheta

end sub

type FRAMETIMELIST
   const MAXTIMES = 60
   tlist(0 to MAXTIMES-1) as double
   thead as integer
   declare sub add( dt as double )
   declare sub draw()
end type

sub FRAMETIMELIST.add( dt as double )
   '' circular buffer to store times
   tlist( thead ) = dt
   thead = (thead + 1) mod MAXTIMES
end sub

sub FRAMETIMELIST.draw()
   dim curr as integer = thead, i as integer
   do
      line( 0, i * 6 + 1 ) - ( tlist(curr) * 1000, i * 6 + 4 ), rgb( 192, 127, 127 ), bf
      curr = (curr + 1) mod MAXTIMES
      i += 1
   loop while curr <> thead
end sub

type GRAPH
   const MAXPOINTS = 1000
   tlist(0 to MAXPOINTS-1, 1 to 6) as double
   thead as integer
   declare sub add( a1 as double, a2 as double, a3 as double, a4 as double, a5 as double , a6 as double)
   declare sub draw()
end type

sub GRAPH.add( a1 as double, a2 as double, a3 as double, a4 as double, a5 as double , a6 as double)
   '' circular buffer to store times
   tlist( thead, 1 ) = a1
   tlist( thead, 2 ) = a2
   tlist( thead, 3 ) = a3
   tlist( thead, 4 ) = a4
   tlist( thead, 5 ) = a5
   tlist( thead, 6 ) = a6
   thead = (thead + 1) mod MAXPOINTS
end sub

sub GRAPH.draw()
   for j as integer = 1 to 6
      dim curr as integer = thead, i as integer
      pset( i, 500 + tlist(curr,j) * 50 ), pen_color(j)
      curr = (curr + 1) mod MAXPOINTS
      do
         line -( i, 500 + tlist(curr,j) * 50 ), pen_color(j)
         curr = (curr + 1) mod MAXPOINTS
         i += 1
      loop while curr <> thead
   next
end sub

sub ResetAllPendula( p() as PENDULUM, start_length as single, start_angle as single )
   p(1).SetPosition( 2, 1, start_length, 0.25, start_angle, pen_color(1) )
   p(2).SetPosition( 4, 1, start_length, 0.25, start_angle, pen_color(2) )
   p(3).SetPosition( 6, 1, start_length, 0.25, start_angle, pen_color(3) )
   p(4).SetPosition( 2, 3, start_length, 0.25, start_angle, pen_color(4) )
   p(5).SetPosition( 4, 3, start_length, 0.25, start_angle, pen_color(5) )
   p(6).SetPosition( 6, 3, start_length, 0.25, start_angle, pen_color(6) )
end sub

'' main
dim ft as FRAMETIMELIST
dim gt as GRAPH

dim p( 1 to 6 ) as PENDULUM
dim start_angle as single = (pi/8)
dim start_length as single = 1
ResetAllPendula( p(), start_length, start_angle )

screenres 1024, 768, 32

dim k as string
dim as double t1 = timer, t2, dt

dim refresh_rate as integer
ScreenControl( fb.GET_SCREEN_REFRESH, refresh_rate )
dim fixed_time as double = 1/cdbl(refresh_rate)
dim use_fixed_rate as boolean = false

do
   t2 = timer
   dt = t2- t1
   t1 = t2

   ft.add dt

   if( use_fixed_rate ) then
      if( dt > fixed_time ) then
         dt = fixed_time
      end if
   end if
   
   '' input

   k = inkey
   select case k
   case chr(255) & chr(&h6b)
      exit do
   case chr(27)
      sleep
      exit do
   case "f", "F"
      use_fixed_rate = true
   case "v", "V"
      use_fixed_rate = false
   case "+"
      start_angle += (pi/16)
      ResetAllPendula( p(), start_length, start_angle )
   case "-"
      start_angle -= (pi/16)
      ResetAllPendula( p(), start_length, start_angle )
   case "["
      if start_length > 0.3 then
         start_length -= 0.1
         for i as integer = 1 to 6
            p(i).length = start_length
         next
      end if
   case "]"
      start_length += 0.1
      for i as integer = 1 to 6
         p(i).length = start_length
      next
   case " "
      sleep 500,1
   end select

   '' physics
   p(1).physApprox( dt )
   p(4).physNewton( dt )

   '' double physics
   for i as integer = 0 to 1
      p(2).physApprox( dt/2 )
      p(5).physNewton( dt/2 )
   next i

   '' quadruple physics
   for i as integer = 0 to 3
      p(3).physApprox( dt/4 )
      p(6).physNewton( dt/4 )
   next

   gt.add p(1).theta, p(2).theta, p(3).theta, p(4).theta, p(5).theta, p(6).theta

   '' draw everything
   window screen (0, 0)-(10, 8)
   screensync
   cls
   for i as integer = 1 to 6
      p(i).draw
   next

   window screen (0, 0)-(1000,750)
   ft.draw
   gt.draw

   window screen
   color rgb( 127, 127, 127 )
   locate 2, 10: print "Keys: V=Variable, F=Fixed, SPACE=0.5 sec DELAY, ESC=Exit"
   locate 3, 10: print "      +- Inc Dec Start Angle     [] Inc Dec bar length"
   print
   locate 5, 10
   print "Current Mode: ";
   color rgb( 255, 255, 255 )
   if( use_fixed_rate ) then
      print "FIXED"
   else
      print "VARIABLE"
   end if

loop


Image
Last edited by coderJeff on Jun 15, 2018 20:07, edited 1 time in total.
Reason: Update GRAPH.Draw() routine
fxm
Posts: 8351
Joined: Apr 22, 2009 12:46
Location: Paris (suburb), FRANCE

Re: What is good cross platform gameloop?

Postby fxm » Jun 15, 2018 18:35

Aborting due to runtime error 6 (out of bounds array access) at line 191 of C:\Users\fxmam\Documents\Mes Outils Personnels\FBIde0.4.6r4-FreeBASIC1.06.0.win64\FBIDETEMP.bas::DRAW()
coderJeff
Site Admin
Posts: 2693
Joined: Nov 04, 2005 14:23
Location: Ontario, Canada
Contact:

Re: What is good cross platform gameloop?

Postby coderJeff » Jun 15, 2018 19:32

fxm, I copied source from forum and tried it, assuming -exx option. fb 1.04 32-bit gas, 1.05 32-bit gas, current git fbc 1.06 32 bit gas & 64 bit gcc, I cannot make this error happen. What is your fbc command line options? I notice that I get gcc compile warnings fbc -gen gcc - O 1 and higher -O.
fxm
Posts: 8351
Joined: Apr 22, 2009 12:46
Location: Paris (suburb), FRANCE

Re: What is good cross platform gameloop?

Postby fxm » Jun 15, 2018 19:42

FreeBASIC1.06.0 64-bit (Win 10) with compile option: -exx -w pedantic
Without doing anything, runtime error after about 20 seconds.

Same behavior with FreeBASIC1.06.0 32-bit gas (Win 10).
fxm
Posts: 8351
Joined: Apr 22, 2009 12:46
Location: Paris (suburb), FRANCE

Re: What is good cross platform gameloop?

Postby fxm » Jun 15, 2018 19:46

(191) line -( i, 500 + tlist(curr,j) * 50 ), pen_color(j)
curr = 1000 inducing the runtime error.

Program works with this fix:

Code: Select all

sub GRAPH.draw()
   for j as integer = 1 to 6
      dim curr as integer = thead, i as integer
      pset( i, 500 + tlist(curr,j) * 50 ), pen_color(j)
      curr += 1
      do
         curr = (curr + 1) mod MAXPOINTS
         line -( i, 500 + tlist(curr,j) * 50 ), pen_color(j)
'         curr = (curr + 1) mod MAXPOINTS
         i += 1
      loop while curr <> thead
   next

end sub

[edit]
See the right fix below.
Last edited by fxm on Jun 15, 2018 20:12, edited 1 time in total.
coderJeff
Site Admin
Posts: 2693
Joined: Nov 04, 2005 14:23
Location: Ontario, Canada
Contact:

Re: What is good cross platform gameloop?

Postby coderJeff » Jun 15, 2018 20:06

Ahh, I see it now, I was too impatient. here's what it should be:

Code: Select all

sub GRAPH.draw()
   for j as integer = 1 to 6
      dim curr as integer = thead, i as integer
      pset( i, 500 + tlist(curr,j) * 50 ), pen_color(j)
      curr = (curr + 1) mod MAXPOINTS
      do
         line -( i, 500 + tlist(curr,j) * 50 ), pen_color(j)
         curr = (curr + 1) mod MAXPOINTS
         i += 1
      loop while curr <> thead
   next
end sub

Previously, there was a "curr += 1" statement that could case the array access to go out of bounds. Thanks, fxm. Updated the original post.
h4tt3n
Posts: 670
Joined: Oct 22, 2005 21:12
Location: Denmark

Re: What is good cross platform gameloop?

Postby h4tt3n » Jun 23, 2018 13:21

Hello folks,

First post in a looong time. I've been tampering with a game loop that treats framerate a bit as a damped spring or a constraint. As much cpu time as possible is freed with sleep. By setting the "stiffness" c_sleep to a near 1.0 value, it reaches an equilibrium faster, but you risk large fluctuations. By setting c_sleep to a near 0.0 value, the equation converges slower but is more stable. Press space to give the cpu a lot of heavy work, and notice how it does not affect frame rate:

Code: Select all

''
''
''
#Include "fbgfx.bi"

Using fb

''
Const As Double c_sleep         = 0.5                   '' sleep correction coefficient [0.0 - 1.0]
Const As Double max_sleep_s     = 1.0                   '' max. sleep time ( seconds )
Const As Double min_sleep_s     = 0.001                 '' min. sleep time ( seconds )
Const As Double rest_velocity   = 60.0                  '' desired frame rate ( loops / second )
Const As Double rest_delta_time     = 1.0 / rest_velocity '' desired loop time ( seconds / loop )

Dim As Double acceleration  = 0.0 '' delta frame rate ( loops / second^2 )
Dim As Double velocity      = 0.0 '' actual frame rate ( loops / second )
Dim As Double last_velocity = 0.0 '' actual frame rate ( loops / second )
Dim As Double delta_time_error  = 0.0 '' loop error time ( seconds )
Dim As Double last_loop_s   = 0.0 '' last loop time ( seconds )
Dim As Double this_loop_s   = 0.0 '' current time ( seconds )
Dim As Double delta_time    = 0.0 '' time it takes to do one loop ( seconds )
Dim As Double sleep_s       = 0.0 '' sleep time ( seconds )

Dim As Integer sleep_ms = 0 '' sleep time ( milliseconds )

Dim As Boolean game_is_running = TRUE  ''
Dim As Boolean game_is_working = FALSE ''

ScreenRes( 800, 600, 32 )

While( game_is_running )
   
   '' input
   game_is_running = IIf( MultiKey( fb.SC_ESCAPE ), FALSE, TRUE  )
   game_is_working = IIf( MultiKey( fb.SC_SPACE ) , TRUE , FALSE )
   
   '' loop time ( seconds )
   last_loop_s = this_loop_s
   this_loop_s = Timer()
   delta_time = this_loop_s - last_loop_s
   
   '' loops per second ( framerate )
   last_velocity = velocity
   velocity = 1.0 / delta_time
   
   acceleration = ( velocity - last_velocity ) / delta_time
   
   '' loop time error ( seconds )
   delta_time_error = delta_time - rest_delta_time
   
   '' adjust sleep time ( seconds )
   sleep_s -= delta_time_error * c_sleep
   
   '' clamp sleep time ( seconds )
   If( sleep_s > max_sleep_s ) Then sleep_s = max_sleep_s
   If( sleep_s < min_sleep_s ) Then sleep_s = min_sleep_s
   
   '' sleep time ( integer milliseconds )
   sleep_ms = Cast( Integer, sleep_s * 1000 )
   
   '' sleep
   Sleep( sleep_ms, 1 )
   
   '' write data to screen
   ScreenLock
      
      Cls
      
      Locate  2, 2: Print " delta_time      " & delta_time
      Locate  4, 2: Print " rest_delta_time     " & rest_delta_time
      Locate  6, 2: Print " delta_time_error    " & delta_time_error
      Locate  8, 2: Print " sleep_s         " & sleep_s
      Locate 10, 2: Print " sleep_ms        " & sleep_ms
      Locate 12, 2: Print " velocity        " & velocity
      Locate 14, 2: Print " acceleration    " & acceleration
      Locate 16, 2: Print " rest_velocity   " & rest_velocity
      Locate 18, 2: Print " game_is_working " & game_is_working
      
   ScreenUnLock
   
   '' do some cpu-heavy work to test sleep time correction
   If( game_is_working ) Then
      
      For i As Integer = 1 To 50000
         
         Dim As Single temp = Log( Sqr(i) )
         
      Next
      
   EndIf
   
Wend


Cheers Mike
h4tt3n
Posts: 670
Joined: Oct 22, 2005 21:12
Location: Denmark

Re: What is good cross platform gameloop?

Postby h4tt3n » Jun 23, 2018 13:32

Here is the "Fix your timestep" game loop tutorial by Glemm Fiedler, converted to FreeBASIC.

Cheers, Mike

Part 1: Fixed delta time

Code: Select all

''
'' Glenn Fiedler game loop tutorial
'' Code sample # 1
'' http://gafferongames.com/game-physics/fix-your-timestep/
'' Fixed delta time

'double t = 0.0;
'double dt = 1.0 / 60.0;
'
'while ( !quit )
'{
'    integrate( state, t, dt );
'    render( state );
'    t += dt;
'}

Dim As Double t = 0.0
Dim As Double dt = 1.0 / 60.0

Dim As Boolean quit = FALSE

While( Not quit )
   
   ''integrate( state, t, dt )
   ''render( state )
   Print "t:" & t
   
   t += dt
   
   If MultiKey(1) Then quit = TRUE
      
Wend


Part 2: Variable delta time

Code: Select all

''
'' Glenn Fiedler game loop tutorial
'' Code sample # 2
'' http://gafferongames.com/game-physics/fix-your-timestep/
'' Variable delta time

'double t = 0.0;
'
'double currentTime = hires_time_in_seconds();
'
'while ( !quit )
'{
'    double newTime = hires_time_in_seconds();
'    double frameTime = newTime - currentTime;
'    currentTime = newTime;
'
'    integrate( state, t, frameTime );
'    t += frameTime;
'
'    render( state );
'}

Dim As Double t = 0.0

Dim As Double currentTime = Timer()

Dim As Boolean quit = FALSE

While( Not quit )
   
   Dim As Double newTime = Timer()
   Dim As Double frameTime = newTime - currentTime
   
   currentTime = newTime
   
   ''integrate( state, t, frameTime )
   
   t += frameTime
   
   ''render( state )
   
   Locate 2, 2: Print" currentTime: " & currentTime
   Locate 4, 2: Print" newTime: " & newTime
   Locate 6, 2: Print" frameTime: " & frameTime
   
   If MultiKey(1) Then quit = TRUE
      
Wend


Part 3: Semi-fixed time step

Code: Select all

''
'' Glenn Fiedler game loop tutorial
'' Code sample # 3
'' http://gafferongames.com/game-physics/fix-your-timestep/
'' Semi-fixed time step

'double t = 0.0;
'double dt = 1 / 60.0;
'
'double currentTime = hires_time_in_seconds();
'
'while ( !quit )
'{
'    double newTime = hires_time_in_seconds();
'    double frameTime = newTime - currentTime;
'    currentTime = newTime;
'         
'    while ( frameTime > 0.0 )
'    {
'        float deltaTime = min( frameTime, dt );
'        integrate( state, t, deltaTime );
'        frameTime -= deltaTime;
'        t += deltaTime;
'    }
'
'    render( state );
'}

Dim As Double t = 0.0
Dim As Double dt = 1.0 / 60.0
Dim As Double deltaTime = 0.0

Dim As Double currentTime = Timer()

Dim As Boolean quit = FALSE

While( Not quit )
   
   Dim As Double newTime = Timer()
   Dim As Double frameTime = newTime - currentTime
   
   currentTime = newTime
   
   While( frameTime > 0.0 )
      
      deltaTime = IIf( frameTime < dt , frameTime, dt )
      ''integrate( state, t, deltaTime )
      frameTime -= deltaTime
      t += deltaTime
      
   Wend
   
   ''render( state )
   
   Locate 2, 2: Print" currentTime: " & currentTime
   Locate 4, 2: Print" newTime: " & newTime
   Locate 6, 2: Print" frameTime: " & frameTime
   Locate 8, 2: Print" deltaTime: " & deltaTime
   
   If MultiKey(1) Then quit = TRUE
      
Wend


Part 4: Free the physics

Code: Select all

''
'' Glenn Fiedler game loop tutorial
'' Code sample # 1
'' http://gafferongames.com/game-physics/fix-your-timestep/
'' Free the physics

'double t = 0.0;
'const double dt = 0.01;
'
'double currentTime = hires_time_in_seconds();
'double accumulator = 0.0;
'
'while ( !quit )
'{
'    double newTime = hires_time_in_seconds();
'    double frameTime = newTime - currentTime;
'    currentTime = newTime;
'
'    accumulator += frameTime;
'
'    while ( accumulator >= dt )
'    {
'        integrate( state, t, dt );
'        accumulator -= dt;
'        t += dt;
'    }
'
'    render( state );
'}


Dim As Double t = 0.0
Dim As Double dt = 0.01

Dim As Double currentTime = Timer()
Dim As Double accumulator = 0.0

Dim As Boolean quit = FALSE

While( Not quit )
   
   Dim As Double newTime = Timer()
   Dim As Double frameTime = newTime - currentTime
   
   currentTime = newTime
   
   accumulator += frameTime
   
   While( accumulator >= dt )
      
      ''integrate( state, t, dt )
      accumulator -= dt
      t += dt
      
   Wend
   
   ''render( state )
   
   Locate 2, 2: Print" currentTime: " & currentTime
   Locate 4, 2: Print" newTime: " & newTime
   Locate 6, 2: Print" frameTime: " & frameTime
   Locate 8, 2: Print" accumulator: " & accumulator
   
   If MultiKey(1) Then quit = TRUE
      
Wend


Part 5: The final touch

Code: Select all

''
'' Glenn Fiedler game loop tutorial
'' Code sample # 1
'' http://gafferongames.com/game-physics/fix-your-timestep/
'' The final touch

'double t = 0.0;
'double dt = 0.01;
'
'double currentTime = hires_time_in_seconds();
'double accumulator = 0.0;
'
'State previous;
'State current;
'
'while ( !quit )
'{
'    double newTime = time();
'    double frameTime = newTime - currentTime;
'    if ( frameTime > 0.25 )
'        frameTime = 0.25;
'    currentTime = newTime;
'
'    accumulator += frameTime;
'
'    while ( accumulator >= dt )
'    {
'        previousState = currentState;
'        integrate( currentState, t, dt );
'        t += dt;
'        accumulator -= dt;
'    }
'
'    const double alpha = accumulator / dt;
'
'    State state = currentState * alpha +
'        previousState * ( 1.0 - alpha );
'
'    render( state );
'}

Dim As Double t = 0.0
Dim As Double dt = 1.0 / 60.0'0.01

Dim As Double currentTime = Timer()
Dim As Double accumulator = 0.0

Dim As Boolean quit = FALSE

While( Not quit )
   
   Dim As Double newTime = Timer()
   Dim As Double frameTime = newTime - currentTime
   
   If ( frameTime > 0.25 ) Then frameTime = 0.25
   
   currentTime = newTime
   
   accumulator += frameTime
   
   While( accumulator >= dt )
      
      ''previousState = currentState
      ''integrate( currentState, t, dt )
      t += dt
      accumulator -= dt
      
   Wend
   
   Dim As Double alpha_ = accumulator / dt
   
'   State state = currentState * alpha +
'        previousState * ( 1.0 - alpha )
   
   ''render( state )
   
   Locate 2, 2: Print" currentTime: " & currentTime
   Locate 4, 2: Print" newTime: " & newTime
   Locate 6, 2: Print" frameTime: " & frameTime
   Locate 8, 2: Print" accumulator: " & accumulator
   
   If MultiKey(1) Then quit = TRUE
      
Wend
badidea
Posts: 975
Joined: May 24, 2007 22:10
Location: The Netherlands

Re: What is good cross platform gameloop?

Postby badidea » Jun 23, 2018 13:54

In case someone wonders who Glemm Fiedler is, see: https://gafferongames.com/ (multiple game related tutorials).
Last edited by badidea on Jun 24, 2018 8:37, edited 1 time in total.
coderJeff
Site Admin
Posts: 2693
Joined: Nov 04, 2005 14:23
Location: Ontario, Canada
Contact:

Re: What is good cross platform gameloop?

Postby coderJeff » Jun 24, 2018 2:23

h4tt3n wrote:I've been tampering with a game loop that treats framerate a bit as a damped spring or a constraint.

Neat! Thank you for sharing. This idea, plus dodicat's idea of regulator, sparked an idea for me. In process automation, to control a continuous process, a feedback loop is used with a PID controller. In this case for example: measure an output variable from the process, like frame rate, or frame time, use it in a proportional/integral/derivative function, to set a input variable to the process, like, sleep time, or simulation time. Too complicated? Probably. I think it's interesting though.

Thanks for the conversions of the tutorial code samples. Short and easy to digest for comparison.
MrSwiss
Posts: 2764
Joined: Jun 02, 2013 9:27
Location: Switzerland

Re: What is good cross platform gameloop?

Postby MrSwiss » Jun 24, 2018 12:06

coderJeff wrote:In process automation, to control a continuous process, a feedback loop is used with a PID controller.
Too complicated? Probably. I think it's interesting though.
Since I've been working professionally with such systems, I think that, a back propagating AN,
could be the anwswer, to the "too complicated" question. Because currently, the engineer
entering/adjusting the PID's parameters, has to play the part (of the AN).
This is the reason, that such adjustments (based on experience alone) do take quite some
time, until the optimal settings have been achieved (since, outside conditions may vary).
paul doe
Posts: 778
Joined: Jul 25, 2017 17:22
Location: Argentina

Re: What is good cross platform gameloop?

Postby paul doe » Aug 18, 2018 1:35

Here's the implementation plucked from the metaballs demo, but implemented using a template design, instead of a strategy design, to be a little easier to follow and compare:

Code: Select all

/'
  The base class for the game loop.
 
  It's implemented as seen here:
    https://www.koonsolo.com/news/dewitters-gameloop/
 
  Which is an implementation that I found clearer than that of
  Glenn Fiedler, seen in:
    https://gafferongames.com/post/fix_your_timestep/
 
  The class is implemented using the Template Design Pattern:
    https://www.tutorialspoint.com/design_pattern/template_pattern.htm
 
  It's not fully abstract, since the implementation requires a little
  housekeeping.
 
  The general idea is to decouple the rendering from the updating in the
  loop, running them at different framerates. In the class, the
  ticksPerSecond parameter of the constructor determines the framerate
  of the _update_ loop; that is, the game will run at that framerate,
  provided it can actually cope with it. If not, the game will simply
  run very choppily.
 
  The rendering, however, will be performed as fast as it can, and the
  excess time between updates (if there are any) will be used to
  interpolate frames for smoother movement. This is especially useful if
  you're using an integration method like Verlet, since it explicitly
  uses the previous position to derive velocities. Thus, you can directly
  use them for the interpolation bounds.
 
  The update() method accepts a deltaTime parameter, which is the one that
  will be used by the physics simulation. See the articles above for an
  in-depth explanation of why this value needs to be fixed, and the
  consecuences of passing a variable time slice to the integrator.
 
  By default, the update() and render() methods are set to do nothing, so
  to use the game loop, you need to override them in the subclasses with
  your custom behavior.
'/
type GameLoop extends Object
  public:
    declare virtual destructor()
   
    declare virtual sub update( byval as double )
    declare virtual sub render( byval as double = 0.0 )
   
    declare sub start()
    declare sub run()
 
  protected: 
    declare constructor()
    declare constructor( byval as integer )
    declare constructor( byval as integer, byval as double )
    declare function getTickCount() as double
 
  private:
    declare sub initialize()
   
    m_currentTick as double
    m_nextTick as double
    m_ticksPerSecond as integer
    m_skipTicks as integer
    m_deltaTime as double
end type

constructor GameLoop()
  m_ticksPerSecond = 60
  m_deltaTime = 0.01
end constructor

constructor GameLoop( _
  byval nTicksPerSecond as integer )
 
  m_ticksPerSecond = iif( nTicksPerSecond < 1, 1, nTicksPerSecond )
  m_deltaTime = 0.01
 
  initialize()
end constructor

constructor GameLoop( _
  byval nTicksPerSecond as integer, byval nDeltaTime as double )
 
  m_ticksPerSecond = iif( nTicksPerSecond < 1, 1, nTicksPerSecond )
  m_deltaTime = nDeltaTime
 
  initialize()
end constructor

destructor GameLoop()
end destructor

sub GameLoop.initialize()
  m_skipTicks = 1000 / m_ticksPerSecond
end sub

function GameLoop.getTickCount() as double
  /'
    This is used as the timer of the loop. It is simple and
    crappy, but for most purposes it suffices. It is scaled
    to milliseconds.
   
    For more accurate timing, something other function can be
    used. See for example:
      http://blog.nuclex-games.com/2012/04/perfectly-accurate-game-timing/
  '/
  return( timer() * 1000.0 )
end function

sub GameLoop.start()
  '' Initialize the game loop
  m_nextTick = getTickCount()
end sub

sub GameLoop.run()
  '' Runs a single iteration of the loop
  m_currentTick = getTickCount()
 
  do while( m_currentTick > m_nextTick )
    update( m_deltaTime )
   
    m_nextTick += m_skipTicks
  loop
 
  render( ( m_currentTick + m_skipTicks - m_nextTick ) / m_skipTicks )
 
  sleep( 1, 1 ) 
end sub

/'
  And these two are the only methods you need to define in the derived
  classes, to actually add any functionality to the loop. Normally, you
  don't need to expose them.
'/
sub GameLoop.update( byval deltaTime as double )
end sub

sub GameLoop.render( byval lerp as double = 0.0 )
end sub

/'
  These two interfaces will define the game and render states that
  the game loop will use to perform its magic. Here, they are just
  stubs.
 
  You can design them in two ways, depending on what your application
  needs: you can make the game loop class be responsible for them
  (passing the game loop new instances of these) and deleting them when
  the loop class is destroyed, or you can keep them fully decoupled of
  the game loop and dispose of them elsewhere. The latter is the method
  used here.
'/
type IRenderState extends Object
  declare constructor()
  declare virtual destructor()
end type

constructor IRenderState()
end constructor

destructor IRenderState()
end destructor

type IGameState extends Object
  declare constructor()
  declare virtual destructor()
end type

constructor IGameState()
end constructor

destructor IGameState()
end destructor

/'
  This is the custom implementation of the game loop. The class
  stores two weak pointers to the game and render states, which
  are injected through the constructor. You can also inject them
  through mutators (getters/setters), if you prefer.
'/
type MyGameLoop extends GameLoop
  public:
    declare constructor( _
      byval as IGameState ptr, _
      byval as IRenderState ptr, _
      byval as integer )
   
    declare constructor( _
      byval as IGameState ptr, _
      byval as IRenderState ptr, _
      byval as integer, _
      byval as double )
   
    declare destructor() override
   
  protected:
    /'
      Both methods are declared protected, because there's no
      need to expose them, since you're expected to call the
      run() method to perform all the needed logic.
    '/
    declare sub update( byval as double ) override
    declare sub render( byval as double = 0.0 ) override
 
  private:
    declare constructor()
   
    m_gameState as IGameState ptr
    m_renderState as IRenderState ptr
end type

constructor MyGameLoop()
end constructor

constructor MyGameLoop( _
  byval nGameState as IGameState ptr, _
  byval nRenderState as IRenderState ptr, _
  byval nTicksPerSecond as integer )
  /'
    Don't forget that you need to call the constructor of the
    base class to allow it to initialize it's internal state.
  '/
  base( nTicksPerSecond )
 
  m_gameState = nGameState
  m_renderState = nRenderState
end constructor

constructor MyGameLoop( _
  byval nGameState as IGameState ptr, _
  byval nRenderState as IRenderState ptr, _
  byval nTicksPerSecond as integer, _
  byval nDeltaTime as double )
  /'
    Don't forget that you need to call the constructor of the
    base class to allow it to initialize it's internal state.
    This constructor also allows you to specify the time slice
    you want for the update() method.
  '/ 
  base( nTicksPerSecond, nDeltaTime )
 
  m_gameState = nGameState
  m_renderState = nRenderState
end constructor

destructor MyGameLoop()
end destructor

sub MyGameLoop.update( byval deltaT as double )
  ? "Updating game. Time step: "; deltaT 
end sub

sub MyGameLoop.render( byval lerp as double = 0.0 )
   ? "Displaying game at :"; lerp
end sub

/'
  Main Code
'/
screenRes( 800, 600, 32 )

/'
  Here we instantiate the game and render states. Since they are
  stubs actually, I just instantiate the base classes, but in
  real code, you'll instantiate your own custom implementations
  of them (most likely through an Abstract Factory or similar).
'/
var gameState = new IGameState()
var renderState = new IRenderState()

'' Instantiate the game loop to update at 60 ticks per second.
var mainLoop = new MyGameLoop( _
  gameState, renderState, 60 )

'' Initialize the game loop and start it
mainLoop->start()

do
  '' Process the main loop
  mainLoop->run()
loop until( inkey() <> "" )

delete( mainLoop )
delete( renderState )
delete( gameState )

sleep()

I find this implementation a little clearer, but that's just my preference. Both should give the same results.

EDIT: added a stub for the default constructor in MyGameLoop class, since its absence can cause some problems depending on the compiler switches you use.
Last edited by paul doe on Aug 18, 2018 10:45, edited 5 times in total.
dafhi
Posts: 1197
Joined: Jun 04, 2005 9:51

Re: What is good cross platform gameloop?

Postby dafhi » Aug 18, 2018 3:21

your coding skills rock! So accessible, relevant, production-ready, formal and informal at the same time, instructive, general ..

where did you come from?
paul doe
Posts: 778
Joined: Jul 25, 2017 17:22
Location: Argentina

Re: What is good cross platform gameloop?

Postby paul doe » Aug 18, 2018 3:58

dafhi wrote:your coding skills rock! So accessible, relevant, production-ready, formal and informal at the same time, instructive, general ..

Well, um, thanks...

This way of coding is called programming by difference, and it allows you to lay out a framework to do all the boilerplate, and then only implement the things that change from one application to another. Games in particular are especially suitable for this kind of pattern, as they have a very homogeneous structure that you can exploit.
dafhi wrote:where did you come from?
What do you mean? I'm from Argentina... =D

Return to “Game Dev”

Who is online

Users browsing this forum: No registered users and 2 guests