I'm not entirely sure this question belongs on gamedev.stackexchange, but I'm technically working on a game and working with SDL, so it might not be entirely offtopic.
I've written a class called DebugText.
The point of the class is to have a nice way of printing values of variables to the game screen.
The idea is to call SetDebugText() with the variables in question every time they change or, as is currently the case, every time the game's Update() is called.
The issue is that when iterating over the map that contains my variables and their latest updated values, I get segfaults.
See the comments in DrawDebugText() below, it specifies where the error happens.
I've tried splitting the calls to it-first and it-second into separate lines and found that the problem doesn't always happen when calling it-first. It alters between it-first and it-second.
I can't find a pattern.
It doesn't fail on every call to DrawDebugText() either.
It might fail on the third time DrawDebugText() is called, or it might fail on the fourth.
Class header:
#ifndef CLIENT_DEBUGTEXT_H
#define CLIENT_DEBUGTEXT_H
#include <Map>
#include <Math.h>
#include <sstream>
#include <SDL.h>
#include <SDL_ttf.h>
#include "vector2.h"
using std::string;
using std::stringstream;
using std::map;
using std::pair;
using game::Vector2;
namespace game {
class DebugText {
private:
TTF_Font* debug_text_font;
map<string, string>* debug_text_list;
public:
void SetDebugText(string var, bool value);
void SetDebugText(string var, float value);
void SetDebugText(string var, int value);
void SetDebugText(string var, Vector2 value);
void SetDebugText(string var, string value);
int DrawDebugText(SDL_Surface*, SDL_Rect*);
void InitDebugText();
void Clear();
};
}
#endif
Class source file:
#include "debugtext.h"
namespace game {
// Copypasta function for handling the toString conversion
template <class T>
inline string to_string (const T& t)
{
stringstream ss (stringstream::in | stringstream::out);
ss << t;
return ss.str();
}
// Initializes SDL_TTF and sets its font
void DebugText::InitDebugText() {
if(TTF_WasInit())
TTF_Quit();
TTF_Init();
debug_text_font = TTF_OpenFont("LiberationSans-Regular.ttf", 16);
TTF_SetFontStyle(debug_text_font, TTF_STYLE_NORMAL);
}
// Iterates over the current debug_text_list and draws every element on the screen.
// After drawing with SDL you need to get a rect specifying the area on the screen that was changed and tell SDL that this part of the screen needs to be updated. this is done in the game's Draw() function
// This function sets rects_to_update to the new list of rects provided by all of the surfaces and returns the number of rects in the list. These two parameters are used in Draw() when calling on SDL_UpdateRects(), which takes an SDL_Rect* and a list length
int DebugText::DrawDebugText(SDL_Surface* screen, SDL_Rect* rects_to_update) {
if(debug_text_list == NULL)
return 0;
if(!TTF_WasInit())
InitDebugText();
rects_to_update = NULL;
// Specifying the font color
SDL_Color font_color = {0xff, 0x00, 0x00, 0x00}; // r, g, b, unused
int row_count = 0;
string line;
// The iterator variable
map<string, string>::iterator it;
// Gets the iterator and iterates over it
for(it = debug_text_list->begin(); it != debug_text_list->end(); it++) {
// Takes the first value (the name of the variable) and the second value (the value of the parameter in string form)
//---------THIS LINE GIVES ME SEGFAULTS-----
line = it->first + ": " + it->second;
//------------------------------------------
// Creates a surface with the text on it that in turn can be rendered to the screen itself later
SDL_Surface* debug_surface = TTF_RenderText_Solid(debug_text_font, line.c_str(), font_color);
if(debug_surface == NULL) { // A standard check for errors
fprintf(stderr, "Error: %s", TTF_GetError());
return NULL;
} else { // If SDL_TTF did its job right, then we now set a destination rect
row_count++;
SDL_Rect dstrect = {5, 5, 0, 0}; // x, y, w, h
dstrect.x = 20;
dstrect.y = 20*row_count;
// Draws the surface with the text on it to the screen
int res = SDL_BlitSurface(debug_surface,NULL,screen,&dstrect);
if(res != 0) { //Just an error check
fprintf(stderr, "Error: %s", SDL_GetError());
return NULL;
}
// Creates a new rect to specify the area that needs to be updated with
SDL_Rect* new_rect_to_update = (SDL_Rect*) malloc(sizeof(SDL_Rect));
new_rect_to_update->h = debug_surface->h;
new_rect_to_update->w = debug_surface->w;
new_rect_to_update->x = dstrect.x;
new_rect_to_update->y = dstrect.y;
// Just freeing the surface since it isn't necessary anymore
SDL_FreeSurface(debug_surface);
// Creates a new list of rects with room for the new rect
SDL_Rect* newtemp = (SDL_Rect*) malloc(row_count*sizeof(SDL_Rect));
// Copies the data from the old list of rects to the new one
memcpy(newtemp, rects_to_update, (row_count-1)*sizeof(SDL_Rect));
// Adds the new rect to the new list
newtemp[row_count-1] = *new_rect_to_update;
// Frees the memory used by the old list
free(rects_to_update);
// And finally redirects the pointer to the old list to the new list
rects_to_update = newtemp;
newtemp = NULL;
}
}
// When the entire map has been iterated over, return the number of lines that were drawn, ie. the number of rects in the returned rect list
return row_count;
}
// The SetDebugText used by all the SetDebugText overloads
// Takes two strings, inserts them into the map as a pair
void DebugText::SetDebugText(string var, string value) {
if (debug_text_list == NULL) {
debug_text_list = new map<string, string>();
}
debug_text_list->erase(var);
debug_text_list->insert(pair<string, string>(var, value));
}
// Writes the bool to a string and calls SetDebugText(string, string)
void DebugText::SetDebugText(string var, bool value) {
string result;
if (value)
result = "True";
else
result = "False";
SetDebugText(var, result);
}
// Does the same thing, but uses to_string() to convert the float
void DebugText::SetDebugText(string var, float value) {
SetDebugText(var, to_string(value));
}
// Same as above, but int
void DebugText::SetDebugText(string var, int value) {
SetDebugText(var, to_string(value));
}
// Vector2 is a struct of my own making. It contains the two float vars x and y
void DebugText::SetDebugText(string var, Vector2 value) {
SetDebugText(var + ".x", to_string(value.x));
SetDebugText(var + ".y", to_string(value.y));
}
// Empties the list. I don't actually use this in my code. Shame on me for writing something I don't use.
void DebugText::Clear() {
if(debug_text_list != NULL)
debug_text_list->clear();
}
}