• (3 Pages)
  • +
  • 1
  • 2
  • 3

C++ Tile Engine from Scratch -- Part 4

#1 RevTorA  Icon User is online

  • D.I.C Head
  • member icon

Reputation: 76
  • View blog
  • Posts: 247
  • Joined: 22-April 11

Posted 17 May 2011 - 08:51 AM

C++ Tile Engine from Scratch -- Part 4
Welcome to the 4th tutorial in this series on creating a Tile Engine from scratch in C++. In the last tutorial we implemented a camera and a level class so we could display a lot of tiles on the screen at once. Currently our level is generated in the code, which is temporary and ugly. Also, we're loading each tile sprite from individual files, which isn't easy to maintain and edit.

So in this tutorial, we're going to load our levels from an editable file on the harddrive, and we're going to join our tiles together in what are called tilesets to be parsed and loaded as a whole.

File Format
In my experience, there have been two popular ways to store game related data on the hard drive. The first is to store everything in a binary format, by writing allocated structs straight to a file. The upside to this method is that it is fast (actually I can't think of a faster way to load info from a file into data structures in your program), and uses the least amount of space possible. However, the data written is mostly, or completely, unreadable by humans. It's in pure binary format, so trying to view the file in a text editor that tries to convert the written values into ASCII will result in complete garbage.

The other method is to create your own file structure, and manually read or write each required value. For instance, you might have map data stored as comma-delimited grid of numeric values each indicating a tile type, such as in this little example:
1,1,1,1,1,1,1,1,1,1
1,0,0,0,2,2,0,0,0,1
1,0,0,0,2,2,0,0,0,1
1,0,0,0,2,2,0,0,0,1
1,0,0,0,2,2,0,0,0,1
1,0,0,0,2,2,0,0,0,1
1,0,0,0,2,2,0,0,0,1
1,0,0,0,2,2,0,0,0,1
1,0,0,0,2,2,0,0,0,1
1,1,1,1,1,1,1,1,1,1


0 might be defined as a grass tile, 1 as a wall tile, 2 as a water tile, etc. Of course, if you want extra layers, you'd have to write out another grid for each layer, and if you wanted to store values for, say, walkability of the tile, you'd need another grid. Then there are other values needed such as the level name, dimensions, where any npcs are, etc. Then you need to write your own parser to read and write these files.

The upside to this method is that it is actually human readable. While there may be some uncertainty about what each number represents or the like, it's still readable with a text editor. That means that in the absence of an editor for these files, you could edit them manually with a text editor. The downside is that the parser for such a format would be much larger than the one for the binary format, and slower. The files are also going to be larger than the binary files.

However, I'd like to use another method for storing and reading these files. Rather than rely on our own convoluted parser, let's instead take advantage of a widely known and standardized file format; namely, XML. XML is well documented, is known by a wide range of web and software developers alike, and best of all is that there are libraries in many different programming languages designed specifically for parsing, reading, and writing XML files.

The upside to XML is its popularity and documentation. However we decide to implement our XML, it will be easily readable by anyone familiar with XML. The downside is that this method will easily take up much more harddrive space than the corresponding binary or new file format. Why use XML if it's going to take up so much space? Honestly, harddrive space and memory is so cheap nowadays that sacrificing file size for standardization and human readability is more than a fair tradeoff.

Alright so how are we going to read and write our XML files? Well, the library I'll be using for this project is RapidXML, which you can get here. It is a very small, very fast xml parsing library and it is very easy to use and to add to our project. Unlike SFML, which we needed to build and set up just right to get it to work, RapidXML comes as a few code files which we simply copy to our project's source directory, and include wherever we need to use it. You can't ask for a simpler way to do this!

Once you've downloaded RapidXML, go ahead and copy "rapidxml.hpp" to our source directory. This is the only file we need for this part of the project. Later on we'll want "rapidxml_print.hpp" so we can easily print our own XML directly to a filestream, which will prove very useful when we want to start saving game states and the like. But for now, we only need "rapidxml.hpp".

That's all there is to "installing" rapidxml for use with our project. Now it's time to decide how we're going to define our xml files.

Tileset File Format
Up till now, we've loaded our sprites manually as individual image files. This ends up becoming a management nightmare when you have hundreds of tiles (especially with any animation), so it's time to start combining our sprites into what are called tilesets. A tileset is simply a collection of sprites laid out in a grid pattern, where each grid cell contains a single sprite or animation frame.

Later on, when we start loading in character animations, objects, effects, and everything else, we'll be using grid cells that aren't necessarily square. However, for now our tiles will be square, making it easier to parse and load the tileset. Now that we know how we're storing our tiles, let's figure out how we're going to define them with xml.

I've found that the easiest way to determine how to define an xml format is to simply write out some xml. First of all, we don't need any xml headers since RapidXML doesn't do any DTD validation. Since we're defining a tileset, let's make that our first xml node:
<tileset>
</tileset>



Great! Now what would be the next node we need. How about a node for each tileset image file we're loading from. We don't need to limit the user to one image per tileset. And, we can use an attribute to define the path of the image file being used. Here's my example xml:
<tileset>
	<imagefile path="tileset.png">
	</imagefile>
</tileset>



Now there's only one more piece to define, and that is a node for each of the tiles in our tileset. The reason I want to define our tiles in an xml file is so we can easily add in animation later on. You'll see what I mean in the next tutorial. Also, this way we can give each and every tile being used by the tile engine a separate ID, rather than having to worry about which image file or tileset the tile is coming from.

So we need a tile node, and that node needs all the attributes required to parse the tile. That means we need the x and y coordinates of the tile, which will be in tiles from the upper left corner of the screen, and an id give the tile in our engine. Just for kicks, and to get you excited for the next tutorial, I'm going to add in a frames attribute to determine how many frames are in the tile's animation. That means we'll need to extract more than one tile's worth of images for this tile. Alright, let's finish our tileset xml. Here's my example tileset:
<tileset>
	<imagefile path="tileset.png">
		<tile x="0" y="0" frames="1" id="0" /> 
		<tile x="1" y="0" frames="1" id="1" /> 
		<tile x="2" y="0" frames="1" id="2" /> 
		<tile x="3" y="0" frames="1" id="3" /> 
	</imagefile>
 </tileset>
 


Alright, now let's write some code to parse this xml and use it to load a tileset into our engine. We'll start by adding a new method prototype in our ImageManager class called "LoadTileset". We also need a new std::map for linking the tile IDs to their respective index in the imageList. Here's the new ImageManager.h:
 #ifndef _IMAGEMANAGER_H
#define _IMAGEMANAGER_H

#include <vector>
#include <map>
#include <string>
#include <SFML\Graphics.hpp>

class ImageManager
{
private:
	std::vector<sf::Image> imageList;
	std::map<int, int> imageIDs;

	int tileSize;
public:
	ImageManager();
	~ImageManager();

	void setTileSize(int tileSize) { this->tileSize = tileSize; }

	void AddImage(sf::Image& image, int id);
	sf::Image& GetImage(int id);

	//Loads tileset from xml format
	void LoadTileset(std::string filename);
};

#endif



Actually parsing the xml file using rapidxml is very easy. The only hiccup I've run into is loading the xml file into a format that rapidxml appreciates. Here is the code we'll use for doing just that, which will go into the method definition of LoadTileset in "ImageManager.cpp":
//Load the file
std::ifstream inFile(filename);

if(!inFile)
	throw "Could not load tileset: " + filename;

//Dump contents of file into a string
std::string xmlContents;

//Blocked out of preference
{
	std::string line;
	while(std::getline(inFile, line))
		xmlContents += line;
}

//Convert string to rapidxml readable char*
std::vector<char> xmlData = std::vector<char>(xmlContents.begin(), xmlContents.end());
xmlData.push_back('\0');



There we go, now the xml file has been loaded into xmlData. We can get the rapidxml compatible char* with &xmlData[0]. We can now create an xml_document and parse the xml file we've loaded. After that we can use the xml file to load and parse all of our tilesets, adding them to imageList. Here's the code we'll be using:
//Convert string to rapidxml readable char*
std::vector<char> xmlData = std::vector<char>(xmlContents.begin(), xmlContents.end());
xmlData.push_back('\0');

//Create a parsed document with &xmlData[0] which is the char*
xml_document<> doc;
doc.parse<parse_no_data_nodes>(&xmlData[0]);

//Get the root node
xml_node<>* root = doc.first_node();

//Some variables used in the following code
std::string imagePath;
sf::Image tileset;

//Go through each imagefile
xml_node<>* imagefile = root->first_node("imagefile");
while(imagefile)
{
	//Get the image file we're parsing and load it
	imagePath = imagefile->first_attribute("path")->value();
	tileset.LoadFromFile(imagePath);
	
	//Go through each tile
	xml_node<>* tile = imagefile->first_node("tile");
	while(tile)
	{
		//Get all the attributes
		int x = atoi(tile->first_attribute("x")->value());
		int y = atoi(tile->first_attribute("y")->value());
		int frames = atoi(tile->first_attribute("frames")->value());
		int id = atoi(tile->first_attribute("id")->value());

		//Copy the right tile image from tileset
		sf::Image tileImage;
		tileImage.Create(tileSize, tileSize);
		tileImage.Copy(tileset, 0, 0, sf::IntRect(x * tileSize, y * tileSize, frames * tileSize, tileSize), true);

		//Add the image to our image list
		AddImage(tileImage, id);

		//Go to the next tile
		tile = tile->next_sibling();
	}

	//Go to the next imagefile
	imagefile = imagefile->next_sibling();
}



We parse the document and store the root node (<tileset>) in root. Then we load the first <imagefile> node into imagefile and, using a while loop, go through each imagefile in <tileset>. We use the path attribute of <imagepath> to load the tileset image into a new image called tileset.

Then, using the same method we used to loop through each imagefile, we loop through every tile in the <imagefile>. We store each of the four attributes we've defined in our xml into variables, using atoi to convert the string values to integers.

The next 3 lines are how we copy a piece of the tileset image into a new image using SFML. First we define a new Image, then use the Create method to get the image ready for the copy. Then we use the Copy method to copy a piece of the source image, which is our tileset, into our new image. The piece is defined with the IntRect, and is determined by the x, y, and frames values we've parsed from the xml file.

Once we have the new image, we add it to our imageList using AddImage, which we implemented last tutorial. Then we set tile to the next tile using tile->next_sibling();. After we've gone through all the tiles, we go to the next image file with imagefile->next_sibling();.

We need to modify AddImage slightly so that it adds the tile's id to imageIDs, and change GetImage to use imageIDs to return the right tile:
void ImageManager::AddImage(sf::Image& image, int id)
{
	imageList.push_back(image);

	//Map for pairing image ids and the image's index in imageList
	imageIDs[id] = imageList.size() - 1;
}

sf::Image& ImageManager::GetImage(int id)
{
	return imageList[imageIDs[id]];
}



Now we have a way to load a tileset into our engine with a tileset definition file written in xml! Piece of cake right? You can test this code out now by writing your own tileset xml file.

Level File Format
At the end of the last tutorial, we had set up a class for holding information about the level, mainly the 2D array of tiles that make up the map. Unfortunately, the only way we had of making a map was an ugly hard-coded map generator. Let's get rid of that, and instead use XML again to define levels.

Just like with our tileset xml definition, let's start by just writing some XML. We can skip the header once again, and start with just a level node:
<level width="20" height="20">
</level>



As I was writing the level node, I realized that we need to define the size of the level. Why not define it right here? So I've added a width and a height attribute. Now we need to think about what a level has. For instance, there are resources that we'll want to load for each level, such as any tilesets used by the level. How about a tileset node:
<level width="20" height="20">
	<tileset path="tileset.xml"/>
</level>



There we go, now our level has a tileset. At the moment we don't have any other resources to load, but we've allowed ourselves an easy way to define any resources we come up with later, such as scripts, npcs, etc. Now let's define a tile node, since our map is made up of a bunch of tiles. If we think about it, every tile on the map has an x and y coordinates as well as an id for which tile sprite to draw. In preparation for a feature we'll be adding in our next tutorial, I'm going to call that attribute "baseid". Don't worry, the reason for this will make sense after the next tutorial. Alright, let's define some tiles:
<level width="20" height="20">
  <tileset path="tileset.xml" /> 
  <tile x="0" y="0" baseid="0" walkable="true" /> 
  <tile x="1" y="0" baseid="0" walkable="true" /> 
  <tile x="2" y="0" baseid="1" walkable="false" /> 
  <tile x="3" y="0" baseid="1" walkable="false" /> 
  <tile x="4" y="0" baseid="0" walkable="true" /> 
</level>



I don't want to implement an entire map, since that would easily double the size of this tutorial. I have included an example map in the source code for this tutorial. I've also added a walkable attribute for a later tutorial. Since it doesn't matter if we actually parse an attribute or not, I figured it would be a good idea to add the walkable attribute now.

Alright, now that we have an idea how our level xml files are going to be laid out, let's write the code to load the data in to create a new level. You'll find that the code is actually very similar to the code for loading in a tileset. That's one of the nice effects of using the same file type for everything. Here's the code we'll use for our new Level::LoadLevel method:

void Level::LoadLevel(std::string filename, ImageManager& imageManager)
{
	//Loads a level from xml file
	//Load the file
	std::ifstream inFile(filename);

	if(!inFile)
		throw "Could not load tileset: " + filename;

	//Dump contents of file into a string
	std::string xmlContents;

	//Blocked out of preference
	{
		std::string line;
		while(std::getline(inFile, line))
			xmlContents += line;
	}

	//Convert string to rapidxml readable char*
	std::vector<char> xmlData = std::vector<char>(xmlContents.begin(), xmlContents.end());
    xmlData.push_back('\0');

	//Create a parsed document with &xmlData[0] which is the char*
	xml_document<> doc;
	doc.parse<parse_no_data_nodes>(&xmlData[0]);

	//Get the root node
	xml_node<>* root = doc.first_node();

	//Get level attributes
	int width = atoi(root->first_attribute("width")->value());
	int height = atoi(root->first_attribute("height")->value());

	//Resize level
	this->w = width;
	this->h = height;
	SetDimensions(width, height);

	//Load each necessary tileset
	xml_node<>* tileset = root->first_node("tileset");
	while(tileset)
	{
		std::string path = tileset->first_attribute("path")->value();
		//Load tileset
		imageManager.LoadTileset(path);
		//Go to next tileset
		tileset = tileset->next_sibling("tileset");
	}

	//Go through each tile
	xml_node<>* tile = root->first_node("tile");
	while(tile)
	{
		//Get all the attributes
		int x = atoi(tile->first_attribute("x")->value());
		int y = atoi(tile->first_attribute("y")->value());
		int baseid = atoi(tile->first_attribute("baseid")->value());

		std::string walkString = tile->first_attribute("walkable")->value();
		bool walkable = (walkString == "true")? true : false;

		//Create the tile and add it to the level.
		Tile* newTile = new Tile(imageManager.GetImage(baseid));
		AddTile(x, y, newTile);

		//Go to the next tile
		tile = tile->next_sibling("tile");
	}
}



This is very similar to our LoadTileset method. We load the xml file into a rapidxml compatible char*, and load the root <level> node into root. Before we begin running through all the resources and tiles, we grab the width and height and use them to set the map dimensions.

Then we loop through each required tileset in the same way we looped through imagefiles and tiles in the last part of this tutorial. After grabbing the path from the <tileset>, we use it to load the tileset with the supplied imageManager.

After we loop through the tilesets, we loop through each <tile> in the xml file. Just like loading tiles from the the tileset, we grab each of the attributes from each tile before creating a new Tile and adding it to the map with AddTile.

After editing Engine.cpp to use our new LoadTileset and LoadLevel methods, we're ready to run the code.

Conclusion
While there may not be any visible changes to our engine, implementing these two functions has greatly increased the versatility of our engine by allowing levels and tilesets to be modified without having to recompile the engine every time. In the end, this will greatly increase development speed and prevent a lot of design headaches. I hope you're not too disappointed by the lack of cool new visual additions, but don't worry because in the next tutorial we're going to start animating!

Also, unlike last tutorial, I actually have working source code for this tutorial, as well as an example tileset and level. So if you find yourself stuck or just want to be caught up for the next tutorial, go ahead and download it. See you soon!

Attached File(s)



Is This A Good Question/Topic? 1
  • +

Replies To: C++ Tile Engine from Scratch -- Part 4

#2 Adell  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 3
  • Joined: 24-May 11

Posted 29 May 2011 - 06:26 AM

Thanks a lot. At thirst it was a bit confusing, but after creating a second tileset, seeing how 2 tilesets work with each other made it clear. Looking forward to next part.
Was This Post Helpful? 0
  • +
  • -

#3 keelx  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 14
  • Joined: 23-May 11

Posted 29 May 2011 - 06:38 PM

Very nice tutorial! Although I did notice one problem- if you try and edit any of the tile's basid's to 1 in the level1.xml, it still displays the green grass (the 0 tile), instead of the dirt (the 1 tile). I'm not quite sure how to fix this myself, though.
Was This Post Helpful? 0
  • +
  • -

#4 Adell  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 3
  • Joined: 24-May 11

Posted 29 May 2011 - 10:04 PM

Maybe you made an error somewhere? Because for me everything works. Try comparing your code with attached one.
Was This Post Helpful? 0
  • +
  • -

#5 keelx  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 14
  • Joined: 23-May 11

Posted 29 May 2011 - 10:12 PM

View PostAdell, on 29 May 2011 - 10:04 PM, said:

Maybe you made an error somewhere? Because for me everything works. Try comparing your code with attached one.

I am using the attatched source... I had to make a few changes here and there to make it work on ubuntu. But nothing much. Is this just me, or is this a problem with anyone else?
Was This Post Helpful? 0
  • +
  • -

#6 keelx  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 14
  • Joined: 23-May 11

Posted 04 June 2011 - 11:43 PM

View Postkeelx, on 29 May 2011 - 10:12 PM, said:

View PostAdell, on 29 May 2011 - 10:04 PM, said:

Maybe you made an error somewhere? Because for me everything works. Try comparing your code with attached one.

I am using the attatched source... I had to make a few changes here and there to make it work on ubuntu. But nothing much. Is this just me, or is this a problem with anyone else?

found the problem. Apparently, in the old version of SFML, which the poster of the tutorial is using, an IntRect is defined by (x, y, w, h) while in the new SFML, IntRect is defined by (x1, y1, x2, y2). This messed up the image copying, and so I needed to change it to coordinates, rather than width and height.
Here is the code I am using, seems to work fine, however I'm not so sure on the animations, seeing as I'm not sure how he/she is going to implement them:
In ImageManager.cpp, in the function LoadTileSet:
tileImage.Copy(tileset, 0, 0, sf::IntRect(x * tileSize, y * tileSize, x * frames * tileSize + tileSize, y * tileSize + tileSize), true);


Was This Post Helpful? 0
  • +
  • -

#7 jaimcm  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 8
  • Joined: 17-October 10

Posted 06 June 2011 - 06:36 AM

Hey.

I'm still a newbie programmer and the following function has been bugging me:

void Level::AddTile(int x, int y, Tile* tile)
{
map[x][y] = tile;
}

Couldn't this cause a memory leak if not used very carefully?

Also, would you not have to use the Destructor of the Level class (or an Unload function) to free the allocated memory:

Level::~Level()
{
for every element in the 2D Tile* array
delete[x][y];
}

Thanks to anyone who can clear this up for me.
Was This Post Helpful? 0
  • +
  • -

#8 keelx  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 14
  • Joined: 23-May 11

Posted 06 June 2011 - 11:24 AM

View Postjaimcm, on 06 June 2011 - 06:36 AM, said:

Hey.

I'm still a newbie programmer and the following function has been bugging me:

void Level::AddTile(int x, int y, Tile* tile)
{
map[x][y] = tile;
}

Couldn't this cause a memory leak if not used very carefully?

Also, would you not have to use the Destructor of the Level class (or an Unload function) to free the allocated memory:

Level::~Level()
{
for every element in the 2D Tile* array
delete[x][y];
}

Thanks to anyone who can clear this up for me.

No, IIRC, local and member variables are deleted automagically at the end of their defining scope, be it that the containing class face deletion, or the scope simply ends somewhere in the program.

This post has been edited by keelx: 06 June 2011 - 11:29 AM

Was This Post Helpful? 0
  • +
  • -

#9 jaimcm  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 8
  • Joined: 17-October 10

Posted 06 June 2011 - 01:05 PM

View Postkeelx, on 06 June 2011 - 11:24 AM, said:

No, IIRC, local and member variables are deleted automagically at the end of their defining scope, be it that the containing class face deletion, or the scope simply ends somewhere in the program.


I'm not too sure about that, tbh.
Consider this rough and very incomplete attempt:
function{
Tile* tile1 = new Tile(img_mgr.GetImage(1));
Tile* tile2 = new Tile(img_mgr.GetImage(4));

level_mgr.AddTile(0,0,tile1);

//This, afaik, should create a memory leak as there will be no pointer to the instance of Tile that used to be pointed to by tile1 (which gets destroyed at end of this function?). So that instance just hangs there with no way of getting deleted?
level_mgr.AddTile(0,0,tile2); 
}



Also, when the Level instance goes out of scope the destructor will invoke the vector destructor which will in turn destruct each element and free the memory each element used. This will only remove the Tile pointers and not the dynamically allocated Tile objects that were pointed to by those pointers.

I seriously doubt that I'm correct and I hope someone can clarify this.
Striving to be the best I can be :)

Jai.
Was This Post Helpful? 0
  • +
  • -

#10 keelx  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 14
  • Joined: 23-May 11

Posted 06 June 2011 - 10:49 PM

View Postjaimcm, on 06 June 2011 - 01:05 PM, said:

View Postkeelx, on 06 June 2011 - 11:24 AM, said:

No, IIRC, local and member variables are deleted automagically at the end of their defining scope, be it that the containing class face deletion, or the scope simply ends somewhere in the program.


I'm not too sure about that, tbh.
Consider this rough and very incomplete attempt:
function{
Tile* tile1 = new Tile(img_mgr.GetImage(1));
Tile* tile2 = new Tile(img_mgr.GetImage(4));

level_mgr.AddTile(0,0,tile1);

//This, afaik, should create a memory leak as there will be no pointer to the instance of Tile that used to be pointed to by tile1 (which gets destroyed at end of this function?). So that instance just hangs there with no way of getting deleted?
level_mgr.AddTile(0,0,tile2); 
}



Also, when the Level instance goes out of scope the destructor will invoke the vector destructor which will in turn destruct each element and free the memory each element used. This will only remove the Tile pointers and not the dynamically allocated Tile objects that were pointed to by those pointers.

I seriously doubt that I'm correct and I hope someone can clarify this.
Striving to be the best I can be :)

Jai.


Well, you can't delete the actual objects, you can set them to NULL, but this is pretty much pointless, unless you need to check the existence of the object itself. I believe the deletion of the pointers would be sufficient for this purpose, which happens at the end of the scope.
Was This Post Helpful? 0
  • +
  • -

#11 jaimcm  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 8
  • Joined: 17-October 10

Posted 07 June 2011 - 02:06 AM

View Postkeelx, on 06 June 2011 - 10:49 PM, said:

Well, you can't delete the actual objects, you can set them to NULL, but this is pretty much pointless, unless you need to check the existence of the object itself. I believe the deletion of the pointers would be sufficient for this purpose, which happens at the end of the scope.


Hey Keelx

I still don't agree but not saying you're wrong. Could you maybe give me a code example of how the functions and use of syntax that I'm querying actually works successfully without creating a memory leak?

As far as I gathered, from what I've quoted above, you're saying that when the pointers go out of scope and they are deleted, the objects that they point to will also be deleted? I doubt that is what you mean though as that is not true.

Thanks.
Was This Post Helpful? 0
  • +
  • -

#12 keelx  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 14
  • Joined: 23-May 11

Posted 07 June 2011 - 12:12 PM

View Postjaimcm, on 07 June 2011 - 02:06 AM, said:

View Postkeelx, on 06 June 2011 - 10:49 PM, said:

Well, you can't delete the actual objects, you can set them to NULL, but this is pretty much pointless, unless you need to check the existence of the object itself. I believe the deletion of the pointers would be sufficient for this purpose, which happens at the end of the scope.


Hey Keelx

I still don't agree but not saying you're wrong. Could you maybe give me a code example of how the functions and use of syntax that I'm querying actually works successfully without creating a memory leak?

As far as I gathered, from what I've quoted above, you're saying that when the pointers go out of scope and they are deleted, the objects that they point to will also be deleted? I doubt that is what you mean though as that is not true.

Thanks.

Okay, I realize that in my previous post I was flat out wrong. Backwards. The delete operator does not remove a pointer from memory, as this is impossible. It is just a number, and you can't 'delete' a number. However, objects are different. The delete operator removes the chunk of memory (the object) pointed to by the pointer sent to the operator. However, the pointer is still pointing to that same space in memory, which by now would hold nothing. So what do you do? You reset the pointer to NULL. But I don't think this 'dangling pointer' will have much of an effect on the memory consumption...
Was This Post Helpful? 0
  • +
  • -

#13 keelx  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 14
  • Joined: 23-May 11

Posted 07 June 2011 - 12:25 PM

View Postjaimcm, on 07 June 2011 - 02:06 AM, said:

View Postkeelx, on 06 June 2011 - 10:49 PM, said:

Well, you can't delete the actual objects, you can set them to NULL, but this is pretty much pointless, unless you need to check the existence of the object itself. I believe the deletion of the pointers would be sufficient for this purpose, which happens at the end of the scope.


Hey Keelx

I still don't agree but not saying you're wrong. Could you maybe give me a code example of how the functions and use of syntax that I'm querying actually works successfully without creating a memory leak?

As far as I gathered, from what I've quoted above, you're saying that when the pointers go out of scope and they are deleted, the objects that they point to will also be deleted? I doubt that is what you mean though as that is not true.

Thanks.

Okay, I realize that in my previous post I was flat out wrong. Backwards. The delete operator does not remove a pointer from memory, as this is impossible. It is just a number, and you can't 'delete' a number. However, objects are different. The delete operator removes the chunk of memory (the object) pointed to by the pointer sent to the operator. However, the pointer is still pointing to that same space in memory, which by now would hold nothing. So what do you do? You reset the pointer to NULL. But I don't think this 'dangling pointer' will have much of an effect on the memory consumption...

for example, to remove these 'dangling pointers' I believe this would be sufficient:
function
{
Tile* tile = new Tile(/*arguments*/);
Level->addTile(x, y, tile);
/* when addTile is called, it fills the values of the tile chunk of memory to the predefined chunk of memory created in the Level object. */
//Then, to remove the dangling pointer safely,
tile = NULL;
} //Then the actual memory pointed to by tile is deallocated here.
//Everyone is happy. 
//The memory pointed to tile was deallocated, the pointer is no longer pointing to a nonexistant object, and the values for the tile object were essentially copied into the Level object's map[][] array.



The only thing I would change from his code would be to set the pointers to NULL.
Was This Post Helpful? 0
  • +
  • -

#14 jaimcm  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 8
  • Joined: 17-October 10

Posted 07 June 2011 - 12:38 PM

View Postkeelx, on 06 June 2011 - 10:49 PM, said:

Okay, I realize that in my previous post I was flat out wrong. Backwards. The delete operator does not remove a pointer from memory, as this is impossible. It is just a number, and you can't 'delete' a number. However, objects are different. The delete operator removes the chunk of memory (the object) pointed to by the pointer sent to the operator. However, the pointer is still pointing to that same space in memory, which by now would hold nothing. So what do you do? You reset the pointer to NULL. But I don't think this 'dangling pointer' will have much of an effect on the memory consumption...

for example, to remove these 'dangling pointers' I believe this would be sufficient:
function
{
Tile* tile = new Tile(/*arguments*/);
Level->addTile(x, y, tile);
/* when addTile is called, it fills the values of the tile chunk of memory to the predefined chunk of memory created in the Level object. */
//Then, to remove the dangling pointer safely,
tile = NULL;
} //Then the actual memory pointed to by tile is deallocated here.
//Everyone is happy. 
//The memory pointed to tile was deallocated, the pointer is no longer pointing to a nonexistant object, and the values for the tile object were essentially copied into the Level object's map[][] array.



The only thing I would change from his code would be to set the pointers to NULL.


Hey Keelx, thanks for your reply.

I think that above example is wrong (from what I've learned anyway, which isn't too much :P).
using 'new' to allocate memory for a Tile object is dynamically allocating memory on the Heap for the Tile to be stored and then new returns a pointer to that location on the heap.

Dynamically allocated memory (heap) does not get destructed/freed at the end of function scope (I believe :S).

calling 'Level->AddTile(x,y,tile)' is in actual fact passing a Tile pointer to the function and not a tile, therefore it's the value of the pointer (the Tiles address in heap memory) that is being copied and put into the 2D array of Tile pointers.

It is fine to not do 'tile=NULL' at the end of the function as it WILL be destructed/freed from memory (just the pointer, not the object it points to).

This leaves us with a 2D Array of Tile pointers all pointing to dynamically created Tile objects which MUST be dealt with when reassigning or destructing etc. They can't just be left there. There has to be a loop that goes through each Tile pointer in the levels 2D Array to 'delete' the Tile objects they point to.

Please tell me I'm not MAJORLY mistaken here lol.
Thanks.
Was This Post Helpful? 0
  • +
  • -

#15 keelx  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 14
  • Joined: 23-May 11

Posted 07 June 2011 - 02:50 PM

View Postjaimcm, on 07 June 2011 - 12:38 PM, said:

View Postkeelx, on 06 June 2011 - 10:49 PM, said:

Okay, I realize that in my previous post I was flat out wrong. Backwards. The delete operator does not remove a pointer from memory, as this is impossible. It is just a number, and you can't 'delete' a number. However, objects are different. The delete operator removes the chunk of memory (the object) pointed to by the pointer sent to the operator. However, the pointer is still pointing to that same space in memory, which by now would hold nothing. So what do you do? You reset the pointer to NULL. But I don't think this 'dangling pointer' will have much of an effect on the memory consumption...

for example, to remove these 'dangling pointers' I believe this would be sufficient:
function
{
Tile* tile = new Tile(/*arguments*/);
Level->addTile(x, y, tile);
/* when addTile is called, it fills the values of the tile chunk of memory to the predefined chunk of memory created in the Level object. */
//Then, to remove the dangling pointer safely,
tile = NULL;
} //Then the actual memory pointed to by tile is deallocated here.
//Everyone is happy. 
//The memory pointed to tile was deallocated, the pointer is no longer pointing to a nonexistant object, and the values for the tile object were essentially copied into the Level object's map[][] array.



The only thing I would change from his code would be to set the pointers to NULL.


Hey Keelx, thanks for your reply.

I think that above example is wrong (from what I've learned anyway, which isn't too much :P).
using 'new' to allocate memory for a Tile object is dynamically allocating memory on the Heap for the Tile to be stored and then new returns a pointer to that location on the heap.

Dynamically allocated memory (heap) does not get destructed/freed at the end of function scope (I believe :S).

calling 'Level->AddTile(x,y,tile)' is in actual fact passing a Tile pointer to the function and not a tile, therefore it's the value of the pointer (the Tiles address in heap memory) that is being copied and put into the 2D array of Tile pointers.

It is fine to not do 'tile=NULL' at the end of the function as it WILL be destructed/freed from memory (just the pointer, not the object it points to).

This leaves us with a 2D Array of Tile pointers all pointing to dynamically created Tile objects which MUST be dealt with when reassigning or destructing etc. They can't just be left there. There has to be a loop that goes through each Tile pointer in the levels 2D Array to 'delete' the Tile objects they point to.

Please tell me I'm not MAJORLY mistaken here lol.
Thanks.


Did some research, asked StackOverflow, now I feel like a n00b, lol. Apparently, and I believe you said this, but only the pointer is freed end-of-scope. I learned that for every
new
, there must be a
delete
. However, you can't delete the tile objects if they were added to the map, because the pointer was deleted end-of-scope. However, we can deallocate that memory from the Level, someone on StackOverflow suggested this:
void Level::deleteTile(int x, int y) 
{ 
    if (map[x][y] != NULL)
    {
        delete map[x][y];
        map[x][y] = NULL;
     }
}

//Change addTile to this:
void Level::addTile(int x, int y, Tile *tile)
{
    deleteTile(x, y);
    map[x][y] = tile;
}

//and the destructor:
void ~Level()
{
    for (int i; i<levelwidth; i++)
    {
        for (int j; j<levelheight; j++)
        {
            delete map[i][j];
        }
    }
}


I think that would work wonders...
Was This Post Helpful? 0
  • +
  • -

  • (3 Pages)
  • +
  • 1
  • 2
  • 3