What is good cross platform gameloop?
Re: What is good cross platform gameloop?
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.
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.
Re: What is good cross platform gameloop?
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.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 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
Last edited by coderJeff on Jun 15, 2018 20:07, edited 1 time in total.
Reason: Update GRAPH.Draw() routine
Reason: Update GRAPH.Draw() routine
Re: What is good cross platform gameloop?
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()
Re: What is good cross platform gameloop?
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.
Re: What is good cross platform gameloop?
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).
Without doing anything, runtime error after about 20 seconds.
Same behavior with FreeBASIC1.06.0 32-bit gas (Win 10).
Re: What is good cross platform gameloop?
(191) line -( i, 500 + tlist(curr,j) * 50 ), pen_color(j)
curr = 1000 inducing the runtime error.
Program works with this fix:
[/s]
[edit]
See the right fix below.
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.
Re: What is good cross platform gameloop?
Ahh, I see it now, I was too impatient. here's what it should be:
Previously, there was a "curr += 1" statement that could case the array access to go out of bounds. Thanks, fxm. Updated the original post.
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
Re: What is good cross platform gameloop?
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:
Cheers Mike
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
Re: What is good cross platform gameloop?
Here is the "Fix your timestep" game loop tutorial by Glemm Fiedler, converted to FreeBASIC.
Cheers, Mike
Part 1: Fixed delta time
Part 2: Variable delta time
Part 3: Semi-fixed time step
Part 4: Free the physics
Part 5: The final touch
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
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
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
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
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
Re: What is good cross platform gameloop?
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.
Re: What is good cross platform gameloop?
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.h4tt3n wrote:I've been tampering with a game loop that treats framerate a bit as a damped spring or a constraint.
Thanks for the conversions of the tutorial code samples. Short and easy to digest for comparison.
Re: What is good cross platform gameloop?
Since I've been working professionally with such systems, I think that, a back propagating AN,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.
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).
Re: What is good cross platform gameloop?
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:
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.
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()
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.
Re: What is good cross platform gameloop?
your coding skills rock! So accessible, relevant, production-ready, formal and informal at the same time, instructive, general ..
where did you come from?
where did you come from?
Re: What is good cross platform gameloop?
Well, um, thanks...dafhi wrote:your coding skills rock! So accessible, relevant, production-ready, formal and informal at the same time, instructive, general ..
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.
What do you mean? I'm from Argentina... =Ddafhi wrote:where did you come from?