What is good cross platform gameloop?

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

Re: What is good cross platform gameloop?

Post by coderJeff »

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: 4313
Joined: Nov 04, 2005 14:23
Location: Ontario, Canada
Contact:

Re: What is good cross platform gameloop?

Post by coderJeff »

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
Moderator
Posts: 12081
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: What is good cross platform gameloop?

Post by fxm »

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: 4313
Joined: Nov 04, 2005 14:23
Location: Ontario, Canada
Contact:

Re: What is good cross platform gameloop?

Post by coderJeff »

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
Moderator
Posts: 12081
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: What is good cross platform gameloop?

Post by fxm »

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
Moderator
Posts: 12081
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: What is good cross platform gameloop?

Post by fxm »

(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
[/s]
[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: 4313
Joined: Nov 04, 2005 14:23
Location: Ontario, Canada
Contact:

Re: What is good cross platform gameloop?

Post by coderJeff »

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: 698
Joined: Oct 22, 2005 21:12
Location: Denmark

Re: What is good cross platform gameloop?

Post by h4tt3n »

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: 698
Joined: Oct 22, 2005 21:12
Location: Denmark

Re: What is good cross platform gameloop?

Post by h4tt3n »

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: 2586
Joined: May 24, 2007 22:10
Location: The Netherlands

Re: What is good cross platform gameloop?

Post by badidea »

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: 4313
Joined: Nov 04, 2005 14:23
Location: Ontario, Canada
Contact:

Re: What is good cross platform gameloop?

Post by coderJeff »

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: 3910
Joined: Jun 02, 2013 9:27
Location: Switzerland

Re: What is good cross platform gameloop?

Post by MrSwiss »

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
Moderator
Posts: 1730
Joined: Jul 25, 2017 17:22
Location: Argentina

Re: What is good cross platform gameloop?

Post by paul doe »

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: 1640
Joined: Jun 04, 2005 9:51

Re: What is good cross platform gameloop?

Post by dafhi »

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
Moderator
Posts: 1730
Joined: Jul 25, 2017 17:22
Location: Argentina

Re: What is good cross platform gameloop?

Post by paul doe »

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
Post Reply