Simple game loop using SDL
Clash Royale CLAN TAG#URR8PPP
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;
up vote
7
down vote
favorite
This is a basic game loop written in C++ using SDL. All feedback is appreciated especially feedback related to design and extensibility.
Main.cpp:
#include "Game.h"
int main(int argc, char* argv)
Game game;
game.Start();
return 0;
Game.h:
#pragma once
#include <SDL.h>
class Game
private:
SDL_Window* m_Window;
SDL_Renderer* m_Renderer;
bool m_Running;
float m_DeltaTime;
public:
void Start();
void Stop();
private:
void GameLoop();
void HandleEvents();
void Update(float deltaTime);
void Render();
;
Timer.h:
#pragma once
class Timer
private:
int m_StartTime;
int m_PausedTime;
bool m_Started;
bool m_Paused;
public:
Timer()
: m_StartTime(0), m_PausedTime(0), m_Started(false), m_Paused(false)
void Start();
void Stop();
void Pause();
void Unpause();
int GetTicks();
inline bool IsStarted() const return m_Started;
inline bool IsPaused() const return m_Paused;
;
Game.cpp:
#include "Game.h"
#include <iostream>
#include <SDL_image.h>
#include <SDL_ttf.h>
#include "Timer.h"
void Game::Start()
m_Running = true;
if (SDL_Init(SDL_INIT_EVERYTHING) != 0)
std::cout << "ERROR: Failed to initialise SDL!" << std::endl;
Stop();
if (IMG_Init(IMG_INIT_PNG) < 0)
std::cout << "ERROR: Failed to initialise SDL_image!" << std::endl;
Stop();
if (TTF_Init() == -1)
std::cout << "ERROR: Failed to initialise SDL_ttf!" << std::endl;
Stop();
m_Window = SDL_CreateWindow("RPG", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 800, 600, 0);
if (!m_Window)
std::cout << "ERROR: Failed to create SDL_Window!" << std::endl;
Stop();
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
m_Renderer = SDL_CreateRenderer(m_Window, -1, SDL_RENDERER_PRESENTVSYNC);
if (!m_Renderer)
std::cout << "ERROR: Failed to create SDL_Renderer!" << std::endl;
Stop();
SDL_SetRenderDrawColor(m_Renderer, 0, 0, 0, 255);
GameLoop();
void Game::Stop()
SDL_DestroyWindow(m_Window);
SDL_DestroyRenderer(m_Renderer);
m_Window = nullptr;
m_Renderer = nullptr;
IMG_Quit();
SDL_Quit();
m_Running = false;
void Game::GameLoop()
Timer deltaTimer;
deltaTimer.Start();
while (m_Running)
HandleEvents();
Update(m_DeltaTime);
m_DeltaTime = static_cast<float>(deltaTimer.GetTicks()) / 1000.0f;
deltaTimer.Start();
Render();
Stop();
void Game::HandleEvents()
SDL_Event event;
SDL_PollEvent(&event);
switch (event.type)
case SDL_QUIT:
m_Running = false;
break;
void Game::Update(float deltaTime)
void Game::Render()
SDL_RenderClear(m_Renderer);
static TTF_Font* font = TTF_OpenFont("assets/fonts/caliban.ttf", 12);
static SDL_Color colour = 255, 255, 255, 255 ;
SDL_Surface* textSurface = TTF_RenderText_Blended(font, std::string("FPS: " + std::to_string(1.0f / m_DeltaTime)).c_str(), colour);
SDL_Texture* textTexture = SDL_CreateTextureFromSurface(m_Renderer, textSurface);
SDL_Rect textBounds = 5, 5, textSurface->w, textSurface->h ;
SDL_RenderCopy(m_Renderer, textTexture, nullptr, &textBounds);
SDL_FreeSurface(textSurface);
SDL_DestroyTexture(textTexture);
SDL_RenderPresent(m_Renderer);
Timer.cpp:
#include "Timer.h"
#include <SDL.h>
void Timer::Start()
m_Started = true;
m_Paused = false;
m_StartTime = SDL_GetTicks();
m_PausedTime = 0;
void Timer::Stop()
m_Started = false;
m_Paused = false;
m_StartTime = 0;
m_PausedTime = 0;
void Timer::Pause()
if (m_Started && !m_Paused)
m_Paused = true;
m_PausedTime = SDL_GetTicks() - m_StartTime;
m_StartTime = 0;
void Timer::Unpause()
if (m_Started && m_Paused)
m_Paused = false;
m_StartTime = SDL_GetTicks() - m_PausedTime;
m_PausedTime = 0;
int Timer::GetTicks()
if (m_Started)
return m_Paused ? m_PausedTime : SDL_GetTicks() - m_StartTime;
return 0;
c++ game sdl
add a comment |Â
up vote
7
down vote
favorite
This is a basic game loop written in C++ using SDL. All feedback is appreciated especially feedback related to design and extensibility.
Main.cpp:
#include "Game.h"
int main(int argc, char* argv)
Game game;
game.Start();
return 0;
Game.h:
#pragma once
#include <SDL.h>
class Game
private:
SDL_Window* m_Window;
SDL_Renderer* m_Renderer;
bool m_Running;
float m_DeltaTime;
public:
void Start();
void Stop();
private:
void GameLoop();
void HandleEvents();
void Update(float deltaTime);
void Render();
;
Timer.h:
#pragma once
class Timer
private:
int m_StartTime;
int m_PausedTime;
bool m_Started;
bool m_Paused;
public:
Timer()
: m_StartTime(0), m_PausedTime(0), m_Started(false), m_Paused(false)
void Start();
void Stop();
void Pause();
void Unpause();
int GetTicks();
inline bool IsStarted() const return m_Started;
inline bool IsPaused() const return m_Paused;
;
Game.cpp:
#include "Game.h"
#include <iostream>
#include <SDL_image.h>
#include <SDL_ttf.h>
#include "Timer.h"
void Game::Start()
m_Running = true;
if (SDL_Init(SDL_INIT_EVERYTHING) != 0)
std::cout << "ERROR: Failed to initialise SDL!" << std::endl;
Stop();
if (IMG_Init(IMG_INIT_PNG) < 0)
std::cout << "ERROR: Failed to initialise SDL_image!" << std::endl;
Stop();
if (TTF_Init() == -1)
std::cout << "ERROR: Failed to initialise SDL_ttf!" << std::endl;
Stop();
m_Window = SDL_CreateWindow("RPG", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 800, 600, 0);
if (!m_Window)
std::cout << "ERROR: Failed to create SDL_Window!" << std::endl;
Stop();
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
m_Renderer = SDL_CreateRenderer(m_Window, -1, SDL_RENDERER_PRESENTVSYNC);
if (!m_Renderer)
std::cout << "ERROR: Failed to create SDL_Renderer!" << std::endl;
Stop();
SDL_SetRenderDrawColor(m_Renderer, 0, 0, 0, 255);
GameLoop();
void Game::Stop()
SDL_DestroyWindow(m_Window);
SDL_DestroyRenderer(m_Renderer);
m_Window = nullptr;
m_Renderer = nullptr;
IMG_Quit();
SDL_Quit();
m_Running = false;
void Game::GameLoop()
Timer deltaTimer;
deltaTimer.Start();
while (m_Running)
HandleEvents();
Update(m_DeltaTime);
m_DeltaTime = static_cast<float>(deltaTimer.GetTicks()) / 1000.0f;
deltaTimer.Start();
Render();
Stop();
void Game::HandleEvents()
SDL_Event event;
SDL_PollEvent(&event);
switch (event.type)
case SDL_QUIT:
m_Running = false;
break;
void Game::Update(float deltaTime)
void Game::Render()
SDL_RenderClear(m_Renderer);
static TTF_Font* font = TTF_OpenFont("assets/fonts/caliban.ttf", 12);
static SDL_Color colour = 255, 255, 255, 255 ;
SDL_Surface* textSurface = TTF_RenderText_Blended(font, std::string("FPS: " + std::to_string(1.0f / m_DeltaTime)).c_str(), colour);
SDL_Texture* textTexture = SDL_CreateTextureFromSurface(m_Renderer, textSurface);
SDL_Rect textBounds = 5, 5, textSurface->w, textSurface->h ;
SDL_RenderCopy(m_Renderer, textTexture, nullptr, &textBounds);
SDL_FreeSurface(textSurface);
SDL_DestroyTexture(textTexture);
SDL_RenderPresent(m_Renderer);
Timer.cpp:
#include "Timer.h"
#include <SDL.h>
void Timer::Start()
m_Started = true;
m_Paused = false;
m_StartTime = SDL_GetTicks();
m_PausedTime = 0;
void Timer::Stop()
m_Started = false;
m_Paused = false;
m_StartTime = 0;
m_PausedTime = 0;
void Timer::Pause()
if (m_Started && !m_Paused)
m_Paused = true;
m_PausedTime = SDL_GetTicks() - m_StartTime;
m_StartTime = 0;
void Timer::Unpause()
if (m_Started && m_Paused)
m_Paused = false;
m_StartTime = SDL_GetTicks() - m_PausedTime;
m_PausedTime = 0;
int Timer::GetTicks()
if (m_Started)
return m_Paused ? m_PausedTime : SDL_GetTicks() - m_StartTime;
return 0;
c++ game sdl
1
Are you using any specific C++ version? I would give some different advice for C++98 than for C++11 (or newer)...
â hoffmale
Jun 8 at 23:14
I am using Visual Studio so this could be any C++ version however I plan to use C++11/14 in the future.
â Ioan Thomas
Jun 8 at 23:16
add a comment |Â
up vote
7
down vote
favorite
up vote
7
down vote
favorite
This is a basic game loop written in C++ using SDL. All feedback is appreciated especially feedback related to design and extensibility.
Main.cpp:
#include "Game.h"
int main(int argc, char* argv)
Game game;
game.Start();
return 0;
Game.h:
#pragma once
#include <SDL.h>
class Game
private:
SDL_Window* m_Window;
SDL_Renderer* m_Renderer;
bool m_Running;
float m_DeltaTime;
public:
void Start();
void Stop();
private:
void GameLoop();
void HandleEvents();
void Update(float deltaTime);
void Render();
;
Timer.h:
#pragma once
class Timer
private:
int m_StartTime;
int m_PausedTime;
bool m_Started;
bool m_Paused;
public:
Timer()
: m_StartTime(0), m_PausedTime(0), m_Started(false), m_Paused(false)
void Start();
void Stop();
void Pause();
void Unpause();
int GetTicks();
inline bool IsStarted() const return m_Started;
inline bool IsPaused() const return m_Paused;
;
Game.cpp:
#include "Game.h"
#include <iostream>
#include <SDL_image.h>
#include <SDL_ttf.h>
#include "Timer.h"
void Game::Start()
m_Running = true;
if (SDL_Init(SDL_INIT_EVERYTHING) != 0)
std::cout << "ERROR: Failed to initialise SDL!" << std::endl;
Stop();
if (IMG_Init(IMG_INIT_PNG) < 0)
std::cout << "ERROR: Failed to initialise SDL_image!" << std::endl;
Stop();
if (TTF_Init() == -1)
std::cout << "ERROR: Failed to initialise SDL_ttf!" << std::endl;
Stop();
m_Window = SDL_CreateWindow("RPG", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 800, 600, 0);
if (!m_Window)
std::cout << "ERROR: Failed to create SDL_Window!" << std::endl;
Stop();
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
m_Renderer = SDL_CreateRenderer(m_Window, -1, SDL_RENDERER_PRESENTVSYNC);
if (!m_Renderer)
std::cout << "ERROR: Failed to create SDL_Renderer!" << std::endl;
Stop();
SDL_SetRenderDrawColor(m_Renderer, 0, 0, 0, 255);
GameLoop();
void Game::Stop()
SDL_DestroyWindow(m_Window);
SDL_DestroyRenderer(m_Renderer);
m_Window = nullptr;
m_Renderer = nullptr;
IMG_Quit();
SDL_Quit();
m_Running = false;
void Game::GameLoop()
Timer deltaTimer;
deltaTimer.Start();
while (m_Running)
HandleEvents();
Update(m_DeltaTime);
m_DeltaTime = static_cast<float>(deltaTimer.GetTicks()) / 1000.0f;
deltaTimer.Start();
Render();
Stop();
void Game::HandleEvents()
SDL_Event event;
SDL_PollEvent(&event);
switch (event.type)
case SDL_QUIT:
m_Running = false;
break;
void Game::Update(float deltaTime)
void Game::Render()
SDL_RenderClear(m_Renderer);
static TTF_Font* font = TTF_OpenFont("assets/fonts/caliban.ttf", 12);
static SDL_Color colour = 255, 255, 255, 255 ;
SDL_Surface* textSurface = TTF_RenderText_Blended(font, std::string("FPS: " + std::to_string(1.0f / m_DeltaTime)).c_str(), colour);
SDL_Texture* textTexture = SDL_CreateTextureFromSurface(m_Renderer, textSurface);
SDL_Rect textBounds = 5, 5, textSurface->w, textSurface->h ;
SDL_RenderCopy(m_Renderer, textTexture, nullptr, &textBounds);
SDL_FreeSurface(textSurface);
SDL_DestroyTexture(textTexture);
SDL_RenderPresent(m_Renderer);
Timer.cpp:
#include "Timer.h"
#include <SDL.h>
void Timer::Start()
m_Started = true;
m_Paused = false;
m_StartTime = SDL_GetTicks();
m_PausedTime = 0;
void Timer::Stop()
m_Started = false;
m_Paused = false;
m_StartTime = 0;
m_PausedTime = 0;
void Timer::Pause()
if (m_Started && !m_Paused)
m_Paused = true;
m_PausedTime = SDL_GetTicks() - m_StartTime;
m_StartTime = 0;
void Timer::Unpause()
if (m_Started && m_Paused)
m_Paused = false;
m_StartTime = SDL_GetTicks() - m_PausedTime;
m_PausedTime = 0;
int Timer::GetTicks()
if (m_Started)
return m_Paused ? m_PausedTime : SDL_GetTicks() - m_StartTime;
return 0;
c++ game sdl
This is a basic game loop written in C++ using SDL. All feedback is appreciated especially feedback related to design and extensibility.
Main.cpp:
#include "Game.h"
int main(int argc, char* argv)
Game game;
game.Start();
return 0;
Game.h:
#pragma once
#include <SDL.h>
class Game
private:
SDL_Window* m_Window;
SDL_Renderer* m_Renderer;
bool m_Running;
float m_DeltaTime;
public:
void Start();
void Stop();
private:
void GameLoop();
void HandleEvents();
void Update(float deltaTime);
void Render();
;
Timer.h:
#pragma once
class Timer
private:
int m_StartTime;
int m_PausedTime;
bool m_Started;
bool m_Paused;
public:
Timer()
: m_StartTime(0), m_PausedTime(0), m_Started(false), m_Paused(false)
void Start();
void Stop();
void Pause();
void Unpause();
int GetTicks();
inline bool IsStarted() const return m_Started;
inline bool IsPaused() const return m_Paused;
;
Game.cpp:
#include "Game.h"
#include <iostream>
#include <SDL_image.h>
#include <SDL_ttf.h>
#include "Timer.h"
void Game::Start()
m_Running = true;
if (SDL_Init(SDL_INIT_EVERYTHING) != 0)
std::cout << "ERROR: Failed to initialise SDL!" << std::endl;
Stop();
if (IMG_Init(IMG_INIT_PNG) < 0)
std::cout << "ERROR: Failed to initialise SDL_image!" << std::endl;
Stop();
if (TTF_Init() == -1)
std::cout << "ERROR: Failed to initialise SDL_ttf!" << std::endl;
Stop();
m_Window = SDL_CreateWindow("RPG", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 800, 600, 0);
if (!m_Window)
std::cout << "ERROR: Failed to create SDL_Window!" << std::endl;
Stop();
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
m_Renderer = SDL_CreateRenderer(m_Window, -1, SDL_RENDERER_PRESENTVSYNC);
if (!m_Renderer)
std::cout << "ERROR: Failed to create SDL_Renderer!" << std::endl;
Stop();
SDL_SetRenderDrawColor(m_Renderer, 0, 0, 0, 255);
GameLoop();
void Game::Stop()
SDL_DestroyWindow(m_Window);
SDL_DestroyRenderer(m_Renderer);
m_Window = nullptr;
m_Renderer = nullptr;
IMG_Quit();
SDL_Quit();
m_Running = false;
void Game::GameLoop()
Timer deltaTimer;
deltaTimer.Start();
while (m_Running)
HandleEvents();
Update(m_DeltaTime);
m_DeltaTime = static_cast<float>(deltaTimer.GetTicks()) / 1000.0f;
deltaTimer.Start();
Render();
Stop();
void Game::HandleEvents()
SDL_Event event;
SDL_PollEvent(&event);
switch (event.type)
case SDL_QUIT:
m_Running = false;
break;
void Game::Update(float deltaTime)
void Game::Render()
SDL_RenderClear(m_Renderer);
static TTF_Font* font = TTF_OpenFont("assets/fonts/caliban.ttf", 12);
static SDL_Color colour = 255, 255, 255, 255 ;
SDL_Surface* textSurface = TTF_RenderText_Blended(font, std::string("FPS: " + std::to_string(1.0f / m_DeltaTime)).c_str(), colour);
SDL_Texture* textTexture = SDL_CreateTextureFromSurface(m_Renderer, textSurface);
SDL_Rect textBounds = 5, 5, textSurface->w, textSurface->h ;
SDL_RenderCopy(m_Renderer, textTexture, nullptr, &textBounds);
SDL_FreeSurface(textSurface);
SDL_DestroyTexture(textTexture);
SDL_RenderPresent(m_Renderer);
Timer.cpp:
#include "Timer.h"
#include <SDL.h>
void Timer::Start()
m_Started = true;
m_Paused = false;
m_StartTime = SDL_GetTicks();
m_PausedTime = 0;
void Timer::Stop()
m_Started = false;
m_Paused = false;
m_StartTime = 0;
m_PausedTime = 0;
void Timer::Pause()
if (m_Started && !m_Paused)
m_Paused = true;
m_PausedTime = SDL_GetTicks() - m_StartTime;
m_StartTime = 0;
void Timer::Unpause()
if (m_Started && m_Paused)
m_Paused = false;
m_StartTime = SDL_GetTicks() - m_PausedTime;
m_PausedTime = 0;
int Timer::GetTicks()
if (m_Started)
return m_Paused ? m_PausedTime : SDL_GetTicks() - m_StartTime;
return 0;
c++ game sdl
asked Jun 8 at 22:58
Ioan Thomas
1546
1546
1
Are you using any specific C++ version? I would give some different advice for C++98 than for C++11 (or newer)...
â hoffmale
Jun 8 at 23:14
I am using Visual Studio so this could be any C++ version however I plan to use C++11/14 in the future.
â Ioan Thomas
Jun 8 at 23:16
add a comment |Â
1
Are you using any specific C++ version? I would give some different advice for C++98 than for C++11 (or newer)...
â hoffmale
Jun 8 at 23:14
I am using Visual Studio so this could be any C++ version however I plan to use C++11/14 in the future.
â Ioan Thomas
Jun 8 at 23:16
1
1
Are you using any specific C++ version? I would give some different advice for C++98 than for C++11 (or newer)...
â hoffmale
Jun 8 at 23:14
Are you using any specific C++ version? I would give some different advice for C++98 than for C++11 (or newer)...
â hoffmale
Jun 8 at 23:14
I am using Visual Studio so this could be any C++ version however I plan to use C++11/14 in the future.
â Ioan Thomas
Jun 8 at 23:16
I am using Visual Studio so this could be any C++ version however I plan to use C++11/14 in the future.
â Ioan Thomas
Jun 8 at 23:16
add a comment |Â
1 Answer
1
active
oldest
votes
up vote
7
down vote
accepted
Design
Single Responsibility Principle (SRP)
Your current design, while serving its purpose, is rather restricted regarding future development.
Game
is trying to do too much. It currently is responsible for:
- handling user input
- rendering
- possibly advancing the underlying simulation (
Game::Update
is currently empty)
Instead, each of those concerns should be separated, leaving Game
to manage the interactions between them. This would also allow for much tighter control over different systems (e.g. you might want to render as fast as possible, but only advance the underlying simulation every 0.05s). This would also allow for easier reuse in different contexts (e.g. developing an editor to create game levels by only using the renderer, without simulation updates).
Phrased differently, Game
s only purpose should be running the game loop, i.e. delegating the actual logic to other components and calling them in order.
Example class layouts:
class Renderer
SDLWindow sdl_window; // RAII wrapper for SDL_Window*
SDLRenderer sdl_renderer; // RAII wrapper for SDL_Renderer*
public:
void Render(const Simulation& current_state);
;
class Simulation
// TODO: insert game state
public:
void Update();
;
class Controls
public:
bool HandleInput(Renderer& renderer, Simulation& simulation);
;
class Game
Simulation sim;
Renderer renderer;
Controls controls;
public:
inline void Run()
while(controls.HandleInputs(renderer, sim))
sim.Update();
renderer.Render();
;
While that might seem a bit overkill for a (very) simple example, it will help in the future!
Note: This is just a basic outline. I would probably make use of the state pattern in Controls
and Simulation
to easily enable different modes (e.g. main menu vs. ingame). In some cases, it might make sense to integrate input handling into the Simulation
itself (e.g. region based control scheme change in a platformer). It all depends on your game!
Correctness
Resource handling
Any exception thrown during Game::Start
will leak resources. While these resources are usually reclaimed by the operating system, you shouldn't rely on this. (Did you ever have trouble with a file being locked after a program crashed? Something like this might happen here, too!)
A better approach would wrap resources like SDL_Window
or SDL_renderer
in a RAII class, so that they get correctly released, even in case of an exception.
Uninitialized value
The value of Game::m_deltaTime
during the first call to Game::Update
in Game::GameLoop
is uninitialized. This is very likely not intended.
Implementation
For Timer
:
- You might want to take a look at the
<chrono>
standard library header, especiallystd::chrono::high_resolution_clock
. This should provide a higher accuracy thanSDL_GetTicks
. Especially in the current state, where nothing happens each frame, a simple thing like calculating FPS might cause a division by zero because the timer resolution is too low.
For Game
(disregarding above design changes):
- All members are uninitialized upon construction.
Game::Stop
doesn't check if the member variables/SDL systems are actually initialized or ifm_Running
is true. This might cause lots of issues, especially when the member variables are still in an uninitialized state!- The instructions in
Game::Start
might be more appropriate inGame
s constructor (as they initialize/acquire resources). Similarly, most instructions inGame::Stop
would be more appropriate in the destructor. - I'm personally not a fan of the tail calls in
Game::Start
andGame::GameLoop
. I'd rather extract the sequence of calls toGame::Start
,Game::GameLoop
andGame::Stop
into aGame::Run
member function. Game::Stop
should not bepublic
in its current state. While the game is running, the original caller ofGame::Start
is still executing that call and unable to callGame::Stop
so only internal calls toGame::Stop
could be made anyways. The only other possible caller ofGame::Stop
would be another thread - butGame
, especiallyGame::Stop
, is not thread safe and might release resources still used by another thread!- I'm not a fan of the
Timer
usage inGame::GameLoop
. Not only is it impossible to use the timer pause/unpause functionality this way (as likely intended to pause the game), different systems very likely require different pause states (e.g. the GUI should still respond, even if the game is paused). Also, the repeated calls toTimer::Start
seem like a hacky workaround that is throwing away useful information; it would be just as easy to cache the result of the last call toTimer::GetTicks
in a local variable. - Variable naming: Most variables are more or less given descriptive names. But then, in
Game::Render
, all local variables have very general names.textSurface
doesn't contain any generic text, it contains the FPS counter (and similar fortextTexture
andtextBounds
).colour
might be better described by what it represents (white
) or, even better, what it's intended usage is:text_color
(system_text_color
?) or maybeforeground_color
.font
might be borderline acceptable, but even there a more descriptive name likesystem_font
or similar would help to better communicate its intended usage. - If you have to use console output for error reporting, prefer
std::cerr
overstd::cout
. Even though they might look the same for a human observer on the console window, they can easily be distiguished from normal console output by other programs. - There are a lot of "magic numbers" (constants without obvious meaning). While I can rather confidently estimate that
"assets/fonts/caliban.ttf"
is the path to a font file, I just have to guess what the default background color is: "fully opaque black" (RGBA)? "bright transparent blue" (ARGB)? Or maybe something completely different?
If interested in modern C++ facilities that help with correctness and memory management, this CppCon talk by Herb Sutter might be interesting.
â hoffmale
Jun 9 at 0:56
Thanks for the answer. I didn't know that even a simple setup like this already has quite tight coupling. I will definitely try to put separate logic into different systems and I will have a look at the talk you suggested.
â Ioan Thomas
Jun 9 at 12:26
@IoanThomas: Well, if this would be the final product, the coupling could be considered ok. However, since this is just the base for future progress, if not adressed now it will soon be too deeply rooted to easily refactor out.
â hoffmale
Jun 9 at 12:39
add a comment |Â
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
7
down vote
accepted
Design
Single Responsibility Principle (SRP)
Your current design, while serving its purpose, is rather restricted regarding future development.
Game
is trying to do too much. It currently is responsible for:
- handling user input
- rendering
- possibly advancing the underlying simulation (
Game::Update
is currently empty)
Instead, each of those concerns should be separated, leaving Game
to manage the interactions between them. This would also allow for much tighter control over different systems (e.g. you might want to render as fast as possible, but only advance the underlying simulation every 0.05s). This would also allow for easier reuse in different contexts (e.g. developing an editor to create game levels by only using the renderer, without simulation updates).
Phrased differently, Game
s only purpose should be running the game loop, i.e. delegating the actual logic to other components and calling them in order.
Example class layouts:
class Renderer
SDLWindow sdl_window; // RAII wrapper for SDL_Window*
SDLRenderer sdl_renderer; // RAII wrapper for SDL_Renderer*
public:
void Render(const Simulation& current_state);
;
class Simulation
// TODO: insert game state
public:
void Update();
;
class Controls
public:
bool HandleInput(Renderer& renderer, Simulation& simulation);
;
class Game
Simulation sim;
Renderer renderer;
Controls controls;
public:
inline void Run()
while(controls.HandleInputs(renderer, sim))
sim.Update();
renderer.Render();
;
While that might seem a bit overkill for a (very) simple example, it will help in the future!
Note: This is just a basic outline. I would probably make use of the state pattern in Controls
and Simulation
to easily enable different modes (e.g. main menu vs. ingame). In some cases, it might make sense to integrate input handling into the Simulation
itself (e.g. region based control scheme change in a platformer). It all depends on your game!
Correctness
Resource handling
Any exception thrown during Game::Start
will leak resources. While these resources are usually reclaimed by the operating system, you shouldn't rely on this. (Did you ever have trouble with a file being locked after a program crashed? Something like this might happen here, too!)
A better approach would wrap resources like SDL_Window
or SDL_renderer
in a RAII class, so that they get correctly released, even in case of an exception.
Uninitialized value
The value of Game::m_deltaTime
during the first call to Game::Update
in Game::GameLoop
is uninitialized. This is very likely not intended.
Implementation
For Timer
:
- You might want to take a look at the
<chrono>
standard library header, especiallystd::chrono::high_resolution_clock
. This should provide a higher accuracy thanSDL_GetTicks
. Especially in the current state, where nothing happens each frame, a simple thing like calculating FPS might cause a division by zero because the timer resolution is too low.
For Game
(disregarding above design changes):
- All members are uninitialized upon construction.
Game::Stop
doesn't check if the member variables/SDL systems are actually initialized or ifm_Running
is true. This might cause lots of issues, especially when the member variables are still in an uninitialized state!- The instructions in
Game::Start
might be more appropriate inGame
s constructor (as they initialize/acquire resources). Similarly, most instructions inGame::Stop
would be more appropriate in the destructor. - I'm personally not a fan of the tail calls in
Game::Start
andGame::GameLoop
. I'd rather extract the sequence of calls toGame::Start
,Game::GameLoop
andGame::Stop
into aGame::Run
member function. Game::Stop
should not bepublic
in its current state. While the game is running, the original caller ofGame::Start
is still executing that call and unable to callGame::Stop
so only internal calls toGame::Stop
could be made anyways. The only other possible caller ofGame::Stop
would be another thread - butGame
, especiallyGame::Stop
, is not thread safe and might release resources still used by another thread!- I'm not a fan of the
Timer
usage inGame::GameLoop
. Not only is it impossible to use the timer pause/unpause functionality this way (as likely intended to pause the game), different systems very likely require different pause states (e.g. the GUI should still respond, even if the game is paused). Also, the repeated calls toTimer::Start
seem like a hacky workaround that is throwing away useful information; it would be just as easy to cache the result of the last call toTimer::GetTicks
in a local variable. - Variable naming: Most variables are more or less given descriptive names. But then, in
Game::Render
, all local variables have very general names.textSurface
doesn't contain any generic text, it contains the FPS counter (and similar fortextTexture
andtextBounds
).colour
might be better described by what it represents (white
) or, even better, what it's intended usage is:text_color
(system_text_color
?) or maybeforeground_color
.font
might be borderline acceptable, but even there a more descriptive name likesystem_font
or similar would help to better communicate its intended usage. - If you have to use console output for error reporting, prefer
std::cerr
overstd::cout
. Even though they might look the same for a human observer on the console window, they can easily be distiguished from normal console output by other programs. - There are a lot of "magic numbers" (constants without obvious meaning). While I can rather confidently estimate that
"assets/fonts/caliban.ttf"
is the path to a font file, I just have to guess what the default background color is: "fully opaque black" (RGBA)? "bright transparent blue" (ARGB)? Or maybe something completely different?
If interested in modern C++ facilities that help with correctness and memory management, this CppCon talk by Herb Sutter might be interesting.
â hoffmale
Jun 9 at 0:56
Thanks for the answer. I didn't know that even a simple setup like this already has quite tight coupling. I will definitely try to put separate logic into different systems and I will have a look at the talk you suggested.
â Ioan Thomas
Jun 9 at 12:26
@IoanThomas: Well, if this would be the final product, the coupling could be considered ok. However, since this is just the base for future progress, if not adressed now it will soon be too deeply rooted to easily refactor out.
â hoffmale
Jun 9 at 12:39
add a comment |Â
up vote
7
down vote
accepted
Design
Single Responsibility Principle (SRP)
Your current design, while serving its purpose, is rather restricted regarding future development.
Game
is trying to do too much. It currently is responsible for:
- handling user input
- rendering
- possibly advancing the underlying simulation (
Game::Update
is currently empty)
Instead, each of those concerns should be separated, leaving Game
to manage the interactions between them. This would also allow for much tighter control over different systems (e.g. you might want to render as fast as possible, but only advance the underlying simulation every 0.05s). This would also allow for easier reuse in different contexts (e.g. developing an editor to create game levels by only using the renderer, without simulation updates).
Phrased differently, Game
s only purpose should be running the game loop, i.e. delegating the actual logic to other components and calling them in order.
Example class layouts:
class Renderer
SDLWindow sdl_window; // RAII wrapper for SDL_Window*
SDLRenderer sdl_renderer; // RAII wrapper for SDL_Renderer*
public:
void Render(const Simulation& current_state);
;
class Simulation
// TODO: insert game state
public:
void Update();
;
class Controls
public:
bool HandleInput(Renderer& renderer, Simulation& simulation);
;
class Game
Simulation sim;
Renderer renderer;
Controls controls;
public:
inline void Run()
while(controls.HandleInputs(renderer, sim))
sim.Update();
renderer.Render();
;
While that might seem a bit overkill for a (very) simple example, it will help in the future!
Note: This is just a basic outline. I would probably make use of the state pattern in Controls
and Simulation
to easily enable different modes (e.g. main menu vs. ingame). In some cases, it might make sense to integrate input handling into the Simulation
itself (e.g. region based control scheme change in a platformer). It all depends on your game!
Correctness
Resource handling
Any exception thrown during Game::Start
will leak resources. While these resources are usually reclaimed by the operating system, you shouldn't rely on this. (Did you ever have trouble with a file being locked after a program crashed? Something like this might happen here, too!)
A better approach would wrap resources like SDL_Window
or SDL_renderer
in a RAII class, so that they get correctly released, even in case of an exception.
Uninitialized value
The value of Game::m_deltaTime
during the first call to Game::Update
in Game::GameLoop
is uninitialized. This is very likely not intended.
Implementation
For Timer
:
- You might want to take a look at the
<chrono>
standard library header, especiallystd::chrono::high_resolution_clock
. This should provide a higher accuracy thanSDL_GetTicks
. Especially in the current state, where nothing happens each frame, a simple thing like calculating FPS might cause a division by zero because the timer resolution is too low.
For Game
(disregarding above design changes):
- All members are uninitialized upon construction.
Game::Stop
doesn't check if the member variables/SDL systems are actually initialized or ifm_Running
is true. This might cause lots of issues, especially when the member variables are still in an uninitialized state!- The instructions in
Game::Start
might be more appropriate inGame
s constructor (as they initialize/acquire resources). Similarly, most instructions inGame::Stop
would be more appropriate in the destructor. - I'm personally not a fan of the tail calls in
Game::Start
andGame::GameLoop
. I'd rather extract the sequence of calls toGame::Start
,Game::GameLoop
andGame::Stop
into aGame::Run
member function. Game::Stop
should not bepublic
in its current state. While the game is running, the original caller ofGame::Start
is still executing that call and unable to callGame::Stop
so only internal calls toGame::Stop
could be made anyways. The only other possible caller ofGame::Stop
would be another thread - butGame
, especiallyGame::Stop
, is not thread safe and might release resources still used by another thread!- I'm not a fan of the
Timer
usage inGame::GameLoop
. Not only is it impossible to use the timer pause/unpause functionality this way (as likely intended to pause the game), different systems very likely require different pause states (e.g. the GUI should still respond, even if the game is paused). Also, the repeated calls toTimer::Start
seem like a hacky workaround that is throwing away useful information; it would be just as easy to cache the result of the last call toTimer::GetTicks
in a local variable. - Variable naming: Most variables are more or less given descriptive names. But then, in
Game::Render
, all local variables have very general names.textSurface
doesn't contain any generic text, it contains the FPS counter (and similar fortextTexture
andtextBounds
).colour
might be better described by what it represents (white
) or, even better, what it's intended usage is:text_color
(system_text_color
?) or maybeforeground_color
.font
might be borderline acceptable, but even there a more descriptive name likesystem_font
or similar would help to better communicate its intended usage. - If you have to use console output for error reporting, prefer
std::cerr
overstd::cout
. Even though they might look the same for a human observer on the console window, they can easily be distiguished from normal console output by other programs. - There are a lot of "magic numbers" (constants without obvious meaning). While I can rather confidently estimate that
"assets/fonts/caliban.ttf"
is the path to a font file, I just have to guess what the default background color is: "fully opaque black" (RGBA)? "bright transparent blue" (ARGB)? Or maybe something completely different?
If interested in modern C++ facilities that help with correctness and memory management, this CppCon talk by Herb Sutter might be interesting.
â hoffmale
Jun 9 at 0:56
Thanks for the answer. I didn't know that even a simple setup like this already has quite tight coupling. I will definitely try to put separate logic into different systems and I will have a look at the talk you suggested.
â Ioan Thomas
Jun 9 at 12:26
@IoanThomas: Well, if this would be the final product, the coupling could be considered ok. However, since this is just the base for future progress, if not adressed now it will soon be too deeply rooted to easily refactor out.
â hoffmale
Jun 9 at 12:39
add a comment |Â
up vote
7
down vote
accepted
up vote
7
down vote
accepted
Design
Single Responsibility Principle (SRP)
Your current design, while serving its purpose, is rather restricted regarding future development.
Game
is trying to do too much. It currently is responsible for:
- handling user input
- rendering
- possibly advancing the underlying simulation (
Game::Update
is currently empty)
Instead, each of those concerns should be separated, leaving Game
to manage the interactions between them. This would also allow for much tighter control over different systems (e.g. you might want to render as fast as possible, but only advance the underlying simulation every 0.05s). This would also allow for easier reuse in different contexts (e.g. developing an editor to create game levels by only using the renderer, without simulation updates).
Phrased differently, Game
s only purpose should be running the game loop, i.e. delegating the actual logic to other components and calling them in order.
Example class layouts:
class Renderer
SDLWindow sdl_window; // RAII wrapper for SDL_Window*
SDLRenderer sdl_renderer; // RAII wrapper for SDL_Renderer*
public:
void Render(const Simulation& current_state);
;
class Simulation
// TODO: insert game state
public:
void Update();
;
class Controls
public:
bool HandleInput(Renderer& renderer, Simulation& simulation);
;
class Game
Simulation sim;
Renderer renderer;
Controls controls;
public:
inline void Run()
while(controls.HandleInputs(renderer, sim))
sim.Update();
renderer.Render();
;
While that might seem a bit overkill for a (very) simple example, it will help in the future!
Note: This is just a basic outline. I would probably make use of the state pattern in Controls
and Simulation
to easily enable different modes (e.g. main menu vs. ingame). In some cases, it might make sense to integrate input handling into the Simulation
itself (e.g. region based control scheme change in a platformer). It all depends on your game!
Correctness
Resource handling
Any exception thrown during Game::Start
will leak resources. While these resources are usually reclaimed by the operating system, you shouldn't rely on this. (Did you ever have trouble with a file being locked after a program crashed? Something like this might happen here, too!)
A better approach would wrap resources like SDL_Window
or SDL_renderer
in a RAII class, so that they get correctly released, even in case of an exception.
Uninitialized value
The value of Game::m_deltaTime
during the first call to Game::Update
in Game::GameLoop
is uninitialized. This is very likely not intended.
Implementation
For Timer
:
- You might want to take a look at the
<chrono>
standard library header, especiallystd::chrono::high_resolution_clock
. This should provide a higher accuracy thanSDL_GetTicks
. Especially in the current state, where nothing happens each frame, a simple thing like calculating FPS might cause a division by zero because the timer resolution is too low.
For Game
(disregarding above design changes):
- All members are uninitialized upon construction.
Game::Stop
doesn't check if the member variables/SDL systems are actually initialized or ifm_Running
is true. This might cause lots of issues, especially when the member variables are still in an uninitialized state!- The instructions in
Game::Start
might be more appropriate inGame
s constructor (as they initialize/acquire resources). Similarly, most instructions inGame::Stop
would be more appropriate in the destructor. - I'm personally not a fan of the tail calls in
Game::Start
andGame::GameLoop
. I'd rather extract the sequence of calls toGame::Start
,Game::GameLoop
andGame::Stop
into aGame::Run
member function. Game::Stop
should not bepublic
in its current state. While the game is running, the original caller ofGame::Start
is still executing that call and unable to callGame::Stop
so only internal calls toGame::Stop
could be made anyways. The only other possible caller ofGame::Stop
would be another thread - butGame
, especiallyGame::Stop
, is not thread safe and might release resources still used by another thread!- I'm not a fan of the
Timer
usage inGame::GameLoop
. Not only is it impossible to use the timer pause/unpause functionality this way (as likely intended to pause the game), different systems very likely require different pause states (e.g. the GUI should still respond, even if the game is paused). Also, the repeated calls toTimer::Start
seem like a hacky workaround that is throwing away useful information; it would be just as easy to cache the result of the last call toTimer::GetTicks
in a local variable. - Variable naming: Most variables are more or less given descriptive names. But then, in
Game::Render
, all local variables have very general names.textSurface
doesn't contain any generic text, it contains the FPS counter (and similar fortextTexture
andtextBounds
).colour
might be better described by what it represents (white
) or, even better, what it's intended usage is:text_color
(system_text_color
?) or maybeforeground_color
.font
might be borderline acceptable, but even there a more descriptive name likesystem_font
or similar would help to better communicate its intended usage. - If you have to use console output for error reporting, prefer
std::cerr
overstd::cout
. Even though they might look the same for a human observer on the console window, they can easily be distiguished from normal console output by other programs. - There are a lot of "magic numbers" (constants without obvious meaning). While I can rather confidently estimate that
"assets/fonts/caliban.ttf"
is the path to a font file, I just have to guess what the default background color is: "fully opaque black" (RGBA)? "bright transparent blue" (ARGB)? Or maybe something completely different?
Design
Single Responsibility Principle (SRP)
Your current design, while serving its purpose, is rather restricted regarding future development.
Game
is trying to do too much. It currently is responsible for:
- handling user input
- rendering
- possibly advancing the underlying simulation (
Game::Update
is currently empty)
Instead, each of those concerns should be separated, leaving Game
to manage the interactions between them. This would also allow for much tighter control over different systems (e.g. you might want to render as fast as possible, but only advance the underlying simulation every 0.05s). This would also allow for easier reuse in different contexts (e.g. developing an editor to create game levels by only using the renderer, without simulation updates).
Phrased differently, Game
s only purpose should be running the game loop, i.e. delegating the actual logic to other components and calling them in order.
Example class layouts:
class Renderer
SDLWindow sdl_window; // RAII wrapper for SDL_Window*
SDLRenderer sdl_renderer; // RAII wrapper for SDL_Renderer*
public:
void Render(const Simulation& current_state);
;
class Simulation
// TODO: insert game state
public:
void Update();
;
class Controls
public:
bool HandleInput(Renderer& renderer, Simulation& simulation);
;
class Game
Simulation sim;
Renderer renderer;
Controls controls;
public:
inline void Run()
while(controls.HandleInputs(renderer, sim))
sim.Update();
renderer.Render();
;
While that might seem a bit overkill for a (very) simple example, it will help in the future!
Note: This is just a basic outline. I would probably make use of the state pattern in Controls
and Simulation
to easily enable different modes (e.g. main menu vs. ingame). In some cases, it might make sense to integrate input handling into the Simulation
itself (e.g. region based control scheme change in a platformer). It all depends on your game!
Correctness
Resource handling
Any exception thrown during Game::Start
will leak resources. While these resources are usually reclaimed by the operating system, you shouldn't rely on this. (Did you ever have trouble with a file being locked after a program crashed? Something like this might happen here, too!)
A better approach would wrap resources like SDL_Window
or SDL_renderer
in a RAII class, so that they get correctly released, even in case of an exception.
Uninitialized value
The value of Game::m_deltaTime
during the first call to Game::Update
in Game::GameLoop
is uninitialized. This is very likely not intended.
Implementation
For Timer
:
- You might want to take a look at the
<chrono>
standard library header, especiallystd::chrono::high_resolution_clock
. This should provide a higher accuracy thanSDL_GetTicks
. Especially in the current state, where nothing happens each frame, a simple thing like calculating FPS might cause a division by zero because the timer resolution is too low.
For Game
(disregarding above design changes):
- All members are uninitialized upon construction.
Game::Stop
doesn't check if the member variables/SDL systems are actually initialized or ifm_Running
is true. This might cause lots of issues, especially when the member variables are still in an uninitialized state!- The instructions in
Game::Start
might be more appropriate inGame
s constructor (as they initialize/acquire resources). Similarly, most instructions inGame::Stop
would be more appropriate in the destructor. - I'm personally not a fan of the tail calls in
Game::Start
andGame::GameLoop
. I'd rather extract the sequence of calls toGame::Start
,Game::GameLoop
andGame::Stop
into aGame::Run
member function. Game::Stop
should not bepublic
in its current state. While the game is running, the original caller ofGame::Start
is still executing that call and unable to callGame::Stop
so only internal calls toGame::Stop
could be made anyways. The only other possible caller ofGame::Stop
would be another thread - butGame
, especiallyGame::Stop
, is not thread safe and might release resources still used by another thread!- I'm not a fan of the
Timer
usage inGame::GameLoop
. Not only is it impossible to use the timer pause/unpause functionality this way (as likely intended to pause the game), different systems very likely require different pause states (e.g. the GUI should still respond, even if the game is paused). Also, the repeated calls toTimer::Start
seem like a hacky workaround that is throwing away useful information; it would be just as easy to cache the result of the last call toTimer::GetTicks
in a local variable. - Variable naming: Most variables are more or less given descriptive names. But then, in
Game::Render
, all local variables have very general names.textSurface
doesn't contain any generic text, it contains the FPS counter (and similar fortextTexture
andtextBounds
).colour
might be better described by what it represents (white
) or, even better, what it's intended usage is:text_color
(system_text_color
?) or maybeforeground_color
.font
might be borderline acceptable, but even there a more descriptive name likesystem_font
or similar would help to better communicate its intended usage. - If you have to use console output for error reporting, prefer
std::cerr
overstd::cout
. Even though they might look the same for a human observer on the console window, they can easily be distiguished from normal console output by other programs. - There are a lot of "magic numbers" (constants without obvious meaning). While I can rather confidently estimate that
"assets/fonts/caliban.ttf"
is the path to a font file, I just have to guess what the default background color is: "fully opaque black" (RGBA)? "bright transparent blue" (ARGB)? Or maybe something completely different?
edited Jun 9 at 12:07
answered Jun 9 at 0:52
hoffmale
4,235630
4,235630
If interested in modern C++ facilities that help with correctness and memory management, this CppCon talk by Herb Sutter might be interesting.
â hoffmale
Jun 9 at 0:56
Thanks for the answer. I didn't know that even a simple setup like this already has quite tight coupling. I will definitely try to put separate logic into different systems and I will have a look at the talk you suggested.
â Ioan Thomas
Jun 9 at 12:26
@IoanThomas: Well, if this would be the final product, the coupling could be considered ok. However, since this is just the base for future progress, if not adressed now it will soon be too deeply rooted to easily refactor out.
â hoffmale
Jun 9 at 12:39
add a comment |Â
If interested in modern C++ facilities that help with correctness and memory management, this CppCon talk by Herb Sutter might be interesting.
â hoffmale
Jun 9 at 0:56
Thanks for the answer. I didn't know that even a simple setup like this already has quite tight coupling. I will definitely try to put separate logic into different systems and I will have a look at the talk you suggested.
â Ioan Thomas
Jun 9 at 12:26
@IoanThomas: Well, if this would be the final product, the coupling could be considered ok. However, since this is just the base for future progress, if not adressed now it will soon be too deeply rooted to easily refactor out.
â hoffmale
Jun 9 at 12:39
If interested in modern C++ facilities that help with correctness and memory management, this CppCon talk by Herb Sutter might be interesting.
â hoffmale
Jun 9 at 0:56
If interested in modern C++ facilities that help with correctness and memory management, this CppCon talk by Herb Sutter might be interesting.
â hoffmale
Jun 9 at 0:56
Thanks for the answer. I didn't know that even a simple setup like this already has quite tight coupling. I will definitely try to put separate logic into different systems and I will have a look at the talk you suggested.
â Ioan Thomas
Jun 9 at 12:26
Thanks for the answer. I didn't know that even a simple setup like this already has quite tight coupling. I will definitely try to put separate logic into different systems and I will have a look at the talk you suggested.
â Ioan Thomas
Jun 9 at 12:26
@IoanThomas: Well, if this would be the final product, the coupling could be considered ok. However, since this is just the base for future progress, if not adressed now it will soon be too deeply rooted to easily refactor out.
â hoffmale
Jun 9 at 12:39
@IoanThomas: Well, if this would be the final product, the coupling could be considered ok. However, since this is just the base for future progress, if not adressed now it will soon be too deeply rooted to easily refactor out.
â hoffmale
Jun 9 at 12:39
add a comment |Â
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f196143%2fsimple-game-loop-using-sdl%23new-answer', 'question_page');
);
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
1
Are you using any specific C++ version? I would give some different advice for C++98 than for C++11 (or newer)...
â hoffmale
Jun 8 at 23:14
I am using Visual Studio so this could be any C++ version however I plan to use C++11/14 in the future.
â Ioan Thomas
Jun 8 at 23:16