#include #include #include #include "basis.h" #include "printsdlerror.h" /* Copyright (c) 2013-2021 Devine Lu Linvega 2018 web port by rezmason 2021 C89 port by rezmason Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE. */ /* The game loop's methods are made accessible to the event watch through this type */ typedef struct { void (*resize)(int, int); void (*update)(double, double); void (*render)(SDL_Renderer *); } WatchContext; static SDL_Window *_window = NULL; static SDL_Renderer *_renderer = NULL; static unsigned long _now = 0; static unsigned long _start = 0; static unsigned long _last = 0; static double _totalTime = 0; static double _deltaTime = 0; static double _performanceFrequency = 1; static SDL_Event _event; static int _quitting = 0; static int _focused = 0; static int init(int chunkSize) { int sdlFlags = SDL_INIT_VIDEO | SDL_INIT_AUDIO; int imgFlags = IMG_INIT_PNG | IMG_INIT_JPG; int mixFlags = MIX_INIT_MP3; if(SDL_Init(sdlFlags) < 0) { return printSDLError("SDL could not initialize."); } SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1"); if(IMG_Init(imgFlags) != imgFlags) { return printSDLError("SDL_image could not initialize."); } if(Mix_Init(mixFlags) != mixFlags) { return printSDLError("SDL_mixer could not initialize."); } if(Mix_OpenAudio(MIX_DEFAULT_FREQUENCY /*44100*/, MIX_DEFAULT_FORMAT, 2, chunkSize) < 0) { return printSDLError("SDL_mixer could not initialize (Mix_OpenAudio)."); } return 1; } /* When the game first initializes, this method fits its window to the screen so that it is proportional and as wide as possible. */ static void fitWindowToScreen(int *width, int *height) { SDL_DisplayMode displayMode; if(SDL_GetCurrentDisplayMode(0, &displayMode) != 0) { printSDLError("SDL could not get current display mode."); } if(*width > displayMode.w || *height > displayMode.h) { float aspectRatio = (float)*width / (float)*height; float displayAspectRatio = (float)displayMode.w / (float)displayMode.h; if(displayAspectRatio < aspectRatio) { *width = displayMode.w / 128 * 128; *height = *width / aspectRatio; } else { *height = displayMode.h / 128 * 128; *width = *height * aspectRatio; } } } static int createWindow(char *name, int *windowW, int *windowH, int fitToScreen) { int flags = SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE; SDL_DestroyWindow(_window); _window = NULL; SDL_DestroyRenderer(_renderer); _renderer = NULL; if(fitToScreen) { fitWindowToScreen(windowW, windowH); } _window = SDL_CreateWindow(name, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, *windowW, *windowH, flags); if(_window == NULL) { return printSDLError("SDL could not create a window."); } _renderer = SDL_CreateRenderer( _window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); if(_renderer == NULL) { return printSDLError("SDL could not create a renderer."); } SDL_SetRenderDrawColor(_renderer, 0, 0, 0, 0xFF); return 1; } static void updateTime() { _last = _now; _now = SDL_GetPerformanceCounter(); _totalTime = (double)(_now - _start) / _performanceFrequency; _deltaTime = (double)(_now - _last) / _performanceFrequency; } /* SDL games usually freeze when you manually resize their window. This hack circumvents the game loop to allow us to at least re-render the contents of the window as the player adjusts its dimensions. */ static int eventWatch(void *data, SDL_Event *event) { if(event->type == SDL_WINDOWEVENT && event->window.event == SDL_WINDOWEVENT_SIZE_CHANGED) { WatchContext *watchContext = data; SDL_Delay(10); watchContext->resize(event->window.data1, event->window.data2); if(_focused) { #if 0 updateTime(); if(_deltaTime < 1) { watchContext->update(_totalTime, _deltaTime); } #endif } SDL_RenderClear(_renderer); watchContext->render(_renderer); SDL_RenderPresent(_renderer); } return 1; } int Basis_Init(char *name, int chunkSize, int *windowW, int *windowH, int fitToScreen) { int status = 1; status &= init(chunkSize); status &= createWindow(name, windowW, windowH, fitToScreen); return status; } void Basis_Halt() { _quitting = 1; } void Basis_Quit() { Basis_Halt(); SDL_DestroyRenderer(_renderer); _renderer = NULL; SDL_DestroyWindow(_window); _window = NULL; Mix_CloseAudio(); IMG_Quit(); Mix_Quit(); SDL_Quit(); } SDL_Renderer * Basis_GetRenderer() { return _renderer; } /* Basis_Run encapsulates a core game loop. It also supplies the values that are then passed on to its handlers, such as the game time, events, and the window renderer. Lastly, it registers the event watch for the resize hack above. */ void Basis_Run(void (*processEvent)(SDL_Event *), void (*resize)(int, int), void (*update)(double, double), void (*render)(SDL_Renderer *)) { int windowW, windowH; WatchContext watchContext; _performanceFrequency = (double)SDL_GetPerformanceFrequency(); _start = SDL_GetPerformanceCounter(); _quitting = 0; _focused = 0; watchContext.resize = resize; watchContext.update = update; watchContext.render = render; SDL_AddEventWatch(&eventWatch, &watchContext); SDL_GetWindowSize(_window, &windowW, &windowH); resize(windowW, windowH); while(!_quitting) { while(SDL_PollEvent(&_event) != 0) { switch(_event.type) { case SDL_QUIT: _quitting = 1; break; case SDL_WINDOWEVENT: switch(_event.window.event) { case SDL_WINDOWEVENT_SIZE_CHANGED: resize(_event.window.data1, _event.window.data2); break; case SDL_WINDOWEVENT_FOCUS_GAINED: _focused = 1; break; case SDL_WINDOWEVENT_FOCUS_LOST: _focused = 0; break; } break; case SDL_KEYDOWN: if(!_event.key.repeat) { processEvent(&_event); } break; case SDL_KEYUP: if(!_event.key.repeat) { processEvent(&_event); } break; case SDL_MOUSEMOTION: processEvent(&_event); break; case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEBUTTONUP: if(_event.button.button == SDL_BUTTON_LEFT) { processEvent(&_event); } break; } } if(_focused) { updateTime(); if(_deltaTime < 1) { update(_totalTime, _deltaTime); } } SDL_RenderClear(_renderer); render(_renderer); SDL_RenderPresent(_renderer); } SDL_DelEventWatch(&eventWatch, &watchContext); }