separate main loop in FixedUpdate and RealUpdate for framerate independency

This commit is contained in:
OctoSpacc 2023-11-11 12:30:35 +01:00
parent 27c3bc089f
commit 3917df3b67
11 changed files with 258 additions and 77 deletions

2
.gitignore vendored
View File

@ -4,5 +4,7 @@
*.exe
*.run
*.tmp
stdout.txt
stderr.txt
.tmp/
Build/

View File

@ -10,6 +10,7 @@ before_script: |
curl -o ./mingw32.7z https://hlb0.octt.eu.org/Drive/Misc/mingw32-9.2.0.7z.sfx.exe
7z x ./mingw32.7z
cp ./mingw32/bin/*.dll ./mingw32/libexec/gcc/mingw32/9.2.0/
mkdir -p /opt/Sdk
mv ./mingw32 /opt/Sdk/mingw32
curl https://apt.devkitpro.org/install-devkitpro-pacman | bash
dkp-pacman -Sy nds-dev

View File

@ -95,7 +95,7 @@ __Normal__: $(BuildObjects)
__Web__:
mkdir -p ./Build/Web
emcc $(BuildSources) $(CFlags) $(Defines) $(LdFlags) --preload-file $(AppAssets) -o ./Build/Web/Emscripten.js
emcc $(BuildSources) $(CFlags) $(Defines) $(LdFlags) --preload-file $(AppAssets)@CHARS.png -o ./Build/Web/Emscripten.js
cp ../Emscripten.html ./Build/Web/$(AppName).html
# TODO: bundle JS, WASM, and assets package in HTML file

View File

@ -13,6 +13,9 @@ int accelY = 2;
int paddleSxY;
int paddleDxY;
char paddleSxMove = 0;
char paddleDxMove = 0;
MultiSpacc_SurfaceConfig windowConfig = {0};
MultiSpacc_Window *window;
MultiSpacc_Surface *screen;
@ -33,6 +36,9 @@ MultiSpacc_Surface *tilesImg;
#define PaddleSxX PaddleWidth
#define PaddleDxX windowConfig.width - 2*PaddleWidth
#define PaddleAccel 2
#define DeltaTime 1
/*{pal:"nes",layout:"nes"}*/
const char palette[32] = {
0x0F, // screen
@ -54,17 +60,104 @@ const unsigned char paddleMetaSprite[] = {
128
};
MultiSpacc_SpritesMap msdata;
bool FixedUpdate( void *args )
{
if(!paused)
{
ballX += accelX * DeltaTime;
ballY += accelY * DeltaTime;
if( ballX <= 0 || ballX >= (windowConfig.width - BallSize) )
{
accelX *= -1;
}
if( ballY <= 0 || ballY >= (windowConfig.height - BallSize) )
{
accelY *= -1;
}
#define TouchingPaddleSx ( ballX <= PaddleSxX+BallSize && ballY >= paddleSxY-BallSize && ballY <= (paddleSxY + 8*PaddleHeight) )
#define TouchingPaddleDx ( ballX >= PaddleDxX-BallSize && ballY >= paddleDxY-BallSize && ballY <= (paddleDxY + 8*PaddleHeight) )
if( TouchingPaddleSx || TouchingPaddleDx )
{
accelX *= -1;
}
if (paddleSxMove == 1)
{
paddleSxY -= PaddleAccel * DeltaTime;
paddleSxMove = 0;
}
else if (paddleSxMove == 2)
{
paddleSxY += PaddleAccel * DeltaTime;
paddleSxMove = 0;
}
}
return true;
}
bool RealUpdate( void *args, double deltaTime )
{
//MultiSpacc_WaitFrame(&nextTick);
if(!paused)
{
MultiSpacc_BlitLayer( background, screen );
//printf("%d %f %f %d\n", ballX, deltaTime, accelX*deltaTime, ballX+accelX*deltaTime);
MultiSpacc_SetSprite( BallSprite, ballX+accelX*deltaTime, ballY+accelY*deltaTime, BallTile, tilesImg, screen );
#define PaddleSxYDisplay (paddleSxMove == 0 ? paddleSxY : paddleSxY+PaddleAccel*deltaTime)
#define PaddleDxYDisplay (paddleDxMove == 0 ? paddleDxY : paddleDxY+PaddleAccel*deltaTime)
MultiSpacc_SetMetaSprite( PaddleSxSprite, PaddleSxX, paddleSxY, &msdata, PaddleHeight, tilesImg, screen );
MultiSpacc_SetMetaSprite( PaddleDxSprite, PaddleDxX, paddleDxY, &msdata, PaddleHeight, tilesImg, screen );
}
if( paddleSxY > 0 && MultiSpacc_CheckKey( MultiSpacc_Key_Up, 0 ) )
{
//paddleSxY -= PaddleAccel * DeltaTime;
paddleSxMove = 1;
}
else if( paddleSxY < (windowConfig.height - 8*PaddleHeight) && MultiSpacc_CheckKey( MultiSpacc_Key_Down, 0 ) )
{
//paddleSxY += PaddleAccel * DeltaTime;
paddleSxMove = 2;
}
// TODO: listen for OS terminate signal
// TODO: fix SDL not waiting for key release with inputs checked this way
if( MultiSpacc_CheckKey( MultiSpacc_Key_Pause, 0 ) )
{
if(!paused)
{
paused = true;
MultiSpacc_PrintText( "Pause", background, &windowConfig, 3, 3, tilesImg );
}
else
{
MultiSpacc_PrintText( "Exit", background, &windowConfig, 3, 3, tilesImg );
return false;
}
}
//if( !MultiSpacc_WaitUpdateDisplay( window, &nextTick ) )
if( !MultiSpacc_UpdateDisplay(window) )
{
MultiSpacc_PrintDebug("[E] Error Updating Screen.\n");
return false;
}
return true;
}
/*
bool MainLoop( void *args )
{
MultiSpacc_SpritesMap msdata;
int chr[] = { 129, 129, 129, 129 };
int x[] = { 0, 0, 0, 0 };
int y[] = { 0, 8, 16, 24 };
msdata.chr = chr;
msdata.x = x;
msdata.y = y;
MultiSpacc_WaitFrame(&nextTick);
if (!paused)
{
@ -75,21 +168,8 @@ bool MainLoop( void *args )
MultiSpacc_SetMetaSprite( PaddleSxSprite, PaddleSxX, paddleSxY, &msdata, PaddleHeight, tilesImg, screen );
MultiSpacc_SetMetaSprite( PaddleDxSprite, PaddleDxX, paddleDxY, &msdata, PaddleHeight, tilesImg, screen );
//oam_meta_spr( PaddleWidth, paddleSxY, 4, paddleMetaSprite );
//oam_meta_spr( windowConfig.width - 2*PaddleWidth, paddleDxY, 20, paddleMetaSprite );
// MultiSpacc_Sprite( PaddleSxSprite , PaddleWidth, paddleSxY , PaddleTile, tilesImg, screen );
// MultiSpacc_Sprite( PaddleSxSprite+1, PaddleWidth, paddleSxY + PaddleWidth, PaddleTile, tilesImg, screen );
// MultiSpacc_Sprite( PaddleSxSprite+2, PaddleWidth, paddleSxY + 2*PaddleWidth, PaddleTile, tilesImg, screen );
// MultiSpacc_Sprite( PaddleSxSprite+3, PaddleWidth, paddleSxY + 3*PaddleWidth, PaddleTile, tilesImg, screen );
// MultiSpacc_Sprite( PaddleDxSprite , windowConfig.width - 2*PaddleWidth, paddleDxY , PaddleTile, tilesImg, screen );
// MultiSpacc_Sprite( PaddleDxSprite+1, windowConfig.width - 2*PaddleWidth, paddleDxY + PaddleWidth, PaddleTile, tilesImg, screen );
// MultiSpacc_Sprite( PaddleDxSprite+2, windowConfig.width - 2*PaddleWidth, paddleDxY + 2*PaddleWidth, PaddleTile, tilesImg, screen );
// MultiSpacc_Sprite( PaddleDxSprite+3, windowConfig.width - 2*PaddleWidth, paddleDxY + 3*PaddleWidth, PaddleTile, tilesImg, screen );
ballX += accelX;
ballY += accelY;
ballX += accelX * DeltaTime;
ballY += accelY * DeltaTime;
if( ballX <= 0 || ballX >= (windowConfig.width - BallSize) )
{
@ -115,21 +195,22 @@ bool MainLoop( void *args )
if( paddleSxY > 0 && MultiSpacc_CheckKey( MultiSpacc_Key_Up, 0 ) )
{
--paddleSxY;
paddleSxY -= PaddleAccel * DeltaTime;
}
else if( paddleSxY < (windowConfig.height - 8*PaddleHeight) && MultiSpacc_CheckKey( MultiSpacc_Key_Down, 0 ) )
{
++paddleSxY;
paddleSxY += PaddleAccel * DeltaTime;
}
/* TODO: listen for OS terminate signal */
// TODO: listen for OS terminate signal
if( MultiSpacc_CheckKey( MultiSpacc_Key_Pause, 0 ) )
{
if (!paused) paused = true;
else return false;
}
if( !MultiSpacc_WaitUpdateDisplay( window, &nextTick ) )
//if( !MultiSpacc_WaitUpdateDisplay( window, &nextTick ) )
if( !MultiSpacc_UpdateDisplay(window) )
{
MultiSpacc_PrintDebug("[E] Error Updating Screen.\n");
return false;
@ -137,10 +218,18 @@ bool MainLoop( void *args )
return true;
}
*/
int main( int argc, char *argv[] )
{
windowConfig.width = 320;
int chr[] = { 129, 129, 129, 129 };
int x[] = { 0, 0, 0, 0 };
int y[] = { 0, 8, 16, 24 };
msdata.chr = chr;
msdata.x = x;
msdata.y = y;
windowConfig.width = 256;
windowConfig.height = 240;
windowConfig.bits = 16;
memcpy( windowConfig.palette, palette, 32 );
@ -170,5 +259,6 @@ int main( int argc, char *argv[] )
paddleSxY = windowConfig.height/2 - 24;
paddleDxY = windowConfig.height/2 - 24;
return MultiSpacc_SetMainLoop( MainLoop, NULL );
//return MultiSpacc_SetMainLoop( MainLoop, NULL );
return MultiSpacc_SetMainLoop( FixedUpdate, RealUpdate, &nextTick, NULL );
}

View File

@ -3,15 +3,23 @@
bool MultiSpacc_CheckKey( int key, char pad )
{
#if defined(MultiSpacc_Target_SDLCom)
SDL_Event event;
while( SDL_PollEvent(&event) ) // TODO: fix this, it eliminates all events beside the first
{
if ( event.type == SDL_KEYDOWN && event.key.keysym.sym == key )
{
return true;
}
}
return false;
#if defined(MultiSpacc_Target_SDL12)
Uint8 *keys = SDL_GetKeyState(NULL);
#elif defined(MultiSpacc_Target_SDL20)
const Uint8 *keys;
SDL_PumpEvents();
keys = SDL_GetKeyboardState(NULL);
#endif
return keys[key];
// SDL_Event event;
// while( SDL_PollEvent(&event) ) // TODO: fix this, it eliminates all events beside the first
// {
// if ( event.type == SDL_KEYDOWN && event.key.keysym.sym == key )
// {
// return true;
// }
// }
// return false;
#elif defined(MultiSpacc_Target_NES)
return ( pad_poll(pad) & key );
#endif

View File

@ -22,22 +22,28 @@
#endif
#if defined(MultiSpacc_Target_SDLCommon) && (defined(MultiSpacc_Target_PC) || defined(MultiSpacc_Target_Web))
#define MultiSpacc_Key_Pause SDLK_ESCAPE
// #define MultiSpacc_Key_Pause SDLK_ESCAPE
#define MultiSpacc_Key_Pause SDL_SCANCODE_ESCAPE
#elif defined(MultiSpacc_Target_NES)
#define MultiSpacc_Key_Pause PAD_START
#endif
#if defined(MultiSpacc_Target_SDLCommon) && (defined(MultiSpacc_Target_PC) || defined(MultiSpacc_Target_Web))
#define MultiSpacc_Key_Select SDLK_TAB
// #define MultiSpacc_Key_Select SDLK_TAB
#define MultiSpacc_Key_Select SDL_SCANCODE_TAB
#elif defined(MultiSpacc_Target_NES)
#define MultiSpacc_Key_Select PAD_SELECT
#endif
#if defined(MultiSpacc_Target_SDLCommon) && (defined(MultiSpacc_Target_PC) || defined(MultiSpacc_Target_Web))
#define MultiSpacc_Key_Up SDLK_UP
#define MultiSpacc_Key_Down SDLK_DOWN
#define MultiSpacc_Key_Left SDLK_LEFT
#define MultiSpacc_Key_Right SDLK_RIGHT
// #define MultiSpacc_Key_Up SDLK_UP
// #define MultiSpacc_Key_Down SDLK_DOWN
// #define MultiSpacc_Key_Left SDLK_LEFT
// #define MultiSpacc_Key_Right SDLK_RIGHT
#define MultiSpacc_Key_Up SDL_SCANCODE_UP
#define MultiSpacc_Key_Down SDL_SCANCODE_DOWN
#define MultiSpacc_Key_Left SDL_SCANCODE_LEFT
#define MultiSpacc_Key_Right SDL_SCANCODE_RIGHT
#elif defined(MultiSpacc_Target_NDS)
// ...
#elif defined(MultiSpacc_Target_NES)
@ -48,10 +54,10 @@
#endif
#if defined(MultiSpacc_Target_SDLCommon) && (defined(MultiSpacc_Target_PC) || defined(MultiSpacc_Target_Web))
#define MultiSpacc_Key_Action1 SPACE
#define MultiSpacc_Key_Action2 SHIFT
#define MultiSpacc_Key_Confirm ENTER
#define MultiSpacc_Key_Cancel ESC
// #define MultiSpacc_Key_Action1 SPACE
// #define MultiSpacc_Key_Action2 SHIFT
// #define MultiSpacc_Key_Confirm ENTER
// #define MultiSpacc_Key_Cancel ESC
#endif
bool MultiSpacc_CheckKey( int key, char pad );

View File

@ -111,9 +111,9 @@ void MultiSpacc_SetSprite( int id, int x, int y, int sprite, MultiSpacc_Surface
void MultiSpacc_SetMetaSprite( int id, int x, int y, MultiSpacc_SpritesMap *map, int mapSize, MultiSpacc_Surface *tiles, MultiSpacc_Surface *screen )
{
int i;
for(i=0; i<mapSize; i++)
{
//oam_spr( (x + map->x[i]), (y + map->y[i]), map->chr[i], 0, (id*4 + 4*i) );
MultiSpacc_SetSprite( (id + i), (x + map->x[i]), (y + map->y[i]), map->chr[i], tiles, screen );
}
}
@ -131,3 +131,44 @@ MultiSpacc_Surface *MultiSpacc_CreateSurface( MultiSpacc_SurfaceConfig *surfaceC
return SDL_CreateRGBSurface( 0, surfaceConfig->width, surfaceConfig->height, surfaceConfig->bits, 0, 0, 0, 0 );
#endif
}
// partially copied from Unity's implementation, see <https://docs.unity3d.com/Manual/TimeFrameManagement.html>
bool MultiSpacc_MainLoopHandler( MultiSpacc_MainLoopHandlerArgs *handlerArgs )
{
#if defined(MultiSpacc_Target_SDLStandard)
//if( !handlerArgs->functionFixedUpdate(handlerArgs->args) || !handlerArgs->functionUpdate( handlerArgs->args, *handlerArgs->nextTick ) ){
// return false;
//}
Uint32 ticksNow;
Uint32 deltaTime;
//int cyclesSinceLast;
//MultiSpacc_WaitFrame(handlerArgs->nextTick);
ticksNow = SDL_GetTicks();
deltaTime = (ticksNow - handlerArgs->elapsedRealTime);
handlerArgs->elapsedRealTime += deltaTime;
//cyclesSinceLast = deltaTime / (1000 / MultiSpacc_GameTick);
while ( handlerArgs->elapsedRealTime - handlerArgs->elapsedFixedTime > (1000 / MultiSpacc_GameTick) ) {
//for( int i=0; i<cyclesSinceLast; i++ ){
//if( handlerArgs->elapsedTime - handlerArgs->elapsedFixedTime > (1000 / MultiSpacc_GameTick) ){
if( !handlerArgs->functionFixedUpdate(handlerArgs->args) ){
return false;
}
//handlerArgs->ticksLast = SDL_GetTicks();
handlerArgs->elapsedFixedTime += (1000 / MultiSpacc_GameTick);
}
if( !handlerArgs->functionRealUpdate( handlerArgs->args, (double)(handlerArgs->elapsedRealTime - handlerArgs->elapsedFixedTime)/((double)1000 / MultiSpacc_GameTick) ) ){
return false;
}
#elif defined(MultiSpacc_Target_NES)
ppu_wait_frame();
if( !handlerArgs->functionFixedUpdate(handlerArgs->args) || !handlerArgs->functionRealUpdate( handlerArgs->args, 1 ) ){
}
#endif
return true;
}

View File

@ -1,6 +1,9 @@
#ifndef _MultiSpacc_MultiSpacc_h_
#define _MultiSpacc_MultiSpacc_h_
// Fixed amount of times per second to call the FixedUpdate function
#define MultiSpacc_GameTick 50
#include <stdarg.h>
#ifndef MultiSpacc_Target_NES
#include <stdbool.h>
@ -59,6 +62,8 @@
#ifdef MultiSpacc_Target_NES
#include <nes.h>
#include "neslib.h"
#define float int
#define double int
#define Uint32 int
#define MultiSpacc_Window char
#define MultiSpacc_Surface char
@ -99,13 +104,25 @@ typedef struct MultiSpacc_SpritesMap {
int *flags;
} MultiSpacc_SpritesMap;
typedef struct MultiSpacc_MainLoopHandlerArgs {
bool (*functionFixedUpdate)( void *args );
bool (*functionRealUpdate)( void *args, double deltaTime );
void *args;
//Uint32 *nextTick;
//Uint32 ticksLast;
Uint32 elapsedRealTime;
Uint32 elapsedFixedTime;
} MultiSpacc_MainLoopHandlerArgs;
bool MultiSpacc_MainLoopHandler( MultiSpacc_MainLoopHandlerArgs *handlerArgs );
MultiSpacc_Window *MultiSpacc_SetWindow( MultiSpacc_SurfaceConfig *windowConfig );
MultiSpacc_Surface *MultiSpacc_GetWindowSurface( MultiSpacc_Window *Window );
void MultiSpacc_SetAppTitle( MultiSpacc_Window *Window, const char Title[] );
void MultiSpacc_SetAppIcon( MultiSpacc_Window *Window, MultiSpacc_Surface *Icon );
bool MultiSpacc_SetMainLoop( bool function( void *args ), void *args );
bool MultiSpacc_SetMainLoop( bool functionFixedUpdate( void *args ), bool functionRealUpdate( void *args, double deltaTime ), Uint32 *nextTick, void *args );
MultiSpacc_Surface *MultiSpacc_LoadImage( char FilePath[], MultiSpacc_Surface *Screen, Uint32 *ColorKey );
int MultiSpacc_SetColorKey( MultiSpacc_Surface *Surface, bool Flag, Uint32 Key );

View File

@ -1,12 +1,26 @@
#include "./MultiSpacc.h"
bool MultiSpacc_SetMainLoop( bool function( void *args ), void *args )
bool MultiSpacc_SetMainLoop( bool functionFixedUpdate( void *args ), bool functionRealUpdate( void *args, double deltaTime ), Uint32 *nextTick, void *args )
{
MultiSpacc_MainLoopHandlerArgs handlerArgs;
handlerArgs.functionFixedUpdate = functionFixedUpdate;
handlerArgs.functionRealUpdate = functionRealUpdate;
handlerArgs.args = args;
//handlerArgs.nextTick = nextTick;
//handlerArgs.ticksLast = 0;
handlerArgs.elapsedRealTime = 0;
handlerArgs.elapsedFixedTime = 0;
#ifdef MultiSpacc_Target_Web
emscripten_set_main_loop_arg( (em_arg_callback_func)function, args, -1, true );
emscripten_set_main_loop_arg( (em_arg_callback_func)MultiSpacc_MainLoopHandler, &handlerArgs, -1, true );
#else
while(true){
if( !function(args) ){
//MultiSpacc_WaitFrame(nextTick);
//if( !functionUpdate(args) || !functionFixedUpdate(args) ){
// return false;
//}
if ( !MultiSpacc_MainLoopHandler(&handlerArgs) ){
return false;
}
}

View File

@ -1,5 +1,25 @@
#include "./MultiSpacc.h"
// todo: frame-indepentent movements with delta time
void MultiSpacc_WaitFrame( Uint32 *nextTick )
{
#if defined(MultiSpacc_Target_SDLStandard)
Uint32 now = SDL_GetTicks();
if ( *nextTick <= now ) {
MultiSpacc_Sleep(0);
} else {
MultiSpacc_Sleep( *nextTick - now );
}
//*nextTick += 1000/MultiSpacc_GameTick;
*nextTick = SDL_GetTicks() + 1000/MultiSpacc_GameTick;
#elif defined(MultiSpacc_Target_NDS)
swiWaitForVBlank();
#elif defined(MultiSpacc_Target_NES)
ppu_wait_frame();
//ppu_wait_nmi();
#endif
}
bool MultiSpacc_UpdateDisplay( MultiSpacc_Window *window )
{
#if defined(MultiSpacc_Target_SDL12)
@ -11,32 +31,12 @@ bool MultiSpacc_UpdateDisplay( MultiSpacc_Window *window )
#endif
}
bool MultiSpacc_WaitFrame( Uint32 *nextTick )
{
#if defined(MultiSpacc_Target_SDLStandard)
// TODO: check if this actually works with any framerate or it caps the game speed
Uint32 now;
now = SDL_GetTicks();
if ( *nextTick <= now ) {
MultiSpacc_Sleep(0);
} else {
MultiSpacc_Sleep( *nextTick - now );
}
*nextTick += 1000/60; // TODO: framerate specified from app and variable framerate
*nextTick = SDL_GetTicks() + 1000/60; // this last part should execute at the start of a loop maybe ???
#elif defined(MultiSpacc_Target_NDS)
swiWaitForVBlank();
#elif defined(MultiSpacc_Target_NES)
ppu_wait_frame();
#endif
return true;
}
bool MultiSpacc_WaitUpdateDisplay( MultiSpacc_Window *window, Uint32 *nextTick )
{
if( !MultiSpacc_UpdateDisplay(window) || !MultiSpacc_WaitFrame(nextTick) ){
if( !MultiSpacc_UpdateDisplay(window) ){
return false;
}
MultiSpacc_WaitFrame(nextTick);
return true;
}

View File

@ -1,6 +1,8 @@
#ifndef _MultiSpacc_VideoCycle_h_
#define _MultiSpacc_VideoCycle_h_
void MultiSpacc_WaitFrame( Uint32 *nextTick );
bool MultiSpacc_UpdateDisplay( MultiSpacc_Window *window );
bool MultiSpacc_WaitUpdateDisplay( MultiSpacc_Window *window, Uint32 *nextTick );
//#if defined(MultiSpacc_Target_NES)