C++ 2 independent while loops

Started by
9 comments, last by BennettSteele 12 years, 8 months ago
I see that games seem to have a least 3 independent loops:
1 for drawing,1 for game logic and 1 for loading and such
which all of these can be called more or less, depending on things. how is it possible to have 2 loops that do not depend on each other to finish or am i not understanding the logic?
Advertisement
You're not understanding the logic.
For starters, it is all usually done from one loop

while( running )
{
UpdateLogic();
UpdateNetwork();
UpdateAudio();
LoadResources();
Render();
}

Now, some of those steps require special care. Your network tick, audio tick, and loader tick all need to use some kind of asynchronous call so that the game doesn't hang when they have work to do. This means using functionality like non-blocking sockets + polling for network code. It could also mean running another thread for your loader code so that you can open files and then decompress them on another thread before passing the loaded data back to the main thread for use.

Bigger games do however do more loops in parallel. You'll often end up with a worker thread-pool that spreads out entity/particle/rendering updates across many cores.
[font=arial, verdana, tahoma, sans-serif][size=2]They are not necessarily independent. For instance, you can have a loop that iterates once per frame, but in the part where you do the physics or the game logic, you iterate a number of times that depends on the time elapsed, so that the timing of the frames doesn't change the simulation.[/font][font=arial, verdana, tahoma, sans-serif][size=2]
[/font][font=arial, verdana, tahoma, sans-serif][size=2]They could also be implemented in separate threads.[/font]
So what are threads exactly? A link or something would be nice...


[color=#1C2837][size=2] Your network tick, audio tick, and loader tick all need to use some kind of asynchronous call so that the game doesn't hang when they have work to do.[/quote]

So what you are saying is each call has a "tick" in which it uses to allow the main loop to continue on without it?
Threading can be a good thing if you really need it, be careful though as it is a daunting prospect to place into your game and many can run perfectly fine without the complexity needed. A lot of times, placing things like your draw function in its own thread *can* be dangerous if it is not timed properly, especially upon exit of your program, so be conscious of what is happening in your main thread versus your other threads at any given time.

This one is a good foundation to get started....
http://www.devarticl...threading-in-C/

... and since multi-threading, especially when dealing with manipulation of shared variables and the like require locking down, you will need to learn about mutexes as well.
http://www.paulbridger.com/mutexes/

Just a quick google search brought up both of these for me when I was looking, but I bookmarked them because they helped me.

Multi-threading requires a lot of planning and understanding of the data you are manipulating, less you come up with errors or worse, cause your program to deadlock.
Ok, i still dont get it... lets say i have this:


#ifndef MAP_H_INCLUDED
#define MAP_H_INCLUDED

class Map
{
public:
SDL_Surface *sky;
SDL_Surface *ground;
int Environment[SIZEOFMAP][SIZEOFMAP];

Map(std::string Dest);
void Box(int x,int y,int sizex,int sizey,int type);
void Point(int x,int y,int type);
void Save(std::string Dest);
void Load(std::string Dest);
};
void encrypt (char e[] )
{

}


Map::Map(std::string Dest)
{
std::string LoadSkyBMP="stuff/maps/"+Dest+"/sky.bmp";
std::string LoadGroundBMP="stuff/maps/"+Dest+"/ground.bmp";
std::string LoadMapTXT="stuff/maps/"+Dest+"/map.map";
std::ifstream NewMap;
NewMap.open(LoadMapTXT.c_str());
if(!std::ifstream(LoadMapTXT.c_str())){APP.Log.Write("NO MAP FOUND\n");exit(EXIT_FAILURE);}
std::stringstream Buf;
Buf<<NewMap.rdbuf();
std::string MapsHere(Buf.str());
//for( ; * MapsHere != '\0'; ==* MapsHere ) --(* MapsHere);
bool FoundSpawn=false;
int At=0;
for(int Y=0;Y<SIZEOFMAP;Y++)
{
for(int X=0;X<SIZEOFMAP;X++)
{
bool NotFoundX=true;
std::stringstream Buffer;
while(NotFoundX)
{
At++;
if(MapsHere[At]=='|')
{
int NewBlock=atoi((Buffer.str()).c_str());
Map::Environment[Y][X]=NewBlock;
if(NewBlock==-1 && FoundSpawn==false){Game.myX=X;Game.myY=Y;FoundSpawn=true;APP.WorldLog.Write("FOUND SPAWN\n");}
NotFoundX=false;
}
else{Buffer<<MapsHere[At];}
}
}
}
NewMap.close();
ApplySurface(0,0,SDL_LoadBMP(LoadSkyBMP.c_str()),Map::sky);
ApplySurface(0,0,SDL_LoadBMP(LoadGroundBMP.c_str()),Map::ground);
APP.MAPNAME=Dest;
APP.WorldLog.Write("MAP INITIALIZED AND LOADED\n");
}



void Map::Box(int x,int y,int sizex,int sizey,int type)
{
int Dirx,Diry;
if(sizex>0){Dirx=1;}else{Dirx=-1;}
if(sizey>0){Diry=1;}else{Diry=-1;}
sizex=abs(sizex);sizey=abs(sizey);
for (int mapy=0;mapy<sizey+1;mapy++){for(int mapx=0;mapx<sizex+1;mapx++)
{
Map::Environment[y+mapy*Diry][x+mapx*Dirx]=type;
}}
}


void Map::Point(int x,int y,int type)
{
Map::Environment[y][x]=type;
}

void Map::Save(std::string Dest)
{
std::string SaveMapTXT="stuff/maps/"+Dest+"/map.map";
File NewMap;
NewMap.Open(SaveMapTXT.c_str());
NewMap.Write("|");
for (int Mapy=0;Mapy<SIZEOFMAP;Mapy++){for(int Mapx=0;Mapx<SIZEOFMAP;Mapx++)
{
char Buf[3];
itoa(Map::Environment[Mapy][Mapx],Buf,10);
std::string What(Buf);
What+='|';
//for(int i=0; What != '\0'; ++i ){++What;}
NewMap.Write(What.c_str());
}
}
NewMap.Close();
std::string Report="MAP SAVED: "+Dest+"\n";
APP.WorldLog.Write(Report.c_str());
}

void Map::Load(std::string Dest)
{
std::string LoadSkyBMP="stuff/maps/"+Dest+"/sky.bmp";
std::string LoadGroundBMP="stuff/maps/"+Dest+"/ground.bmp";
std::string LoadMapTXT="stuff/maps/"+Dest+"/map.map";
std::ifstream NewMap;
NewMap.open(LoadMapTXT.c_str());
if(std::ifstream(LoadMapTXT.c_str()))
{
std::stringstream Buf;
Buf<<NewMap.rdbuf();
std::string MapsHere(Buf.str());
//for( ; * MapsHere != '\0'; ==* MapsHere ) --(* MapsHere);
bool FoundDoor=false;
int At=0;
for(int Y=0;Y<SIZEOFMAP;Y++)
{
for(int X=0;X<SIZEOFMAP;X++)
{
bool NotFoundX=true;
std::stringstream Buffer;
while(NotFoundX)
{
At++;
if(MapsHere[At]=='|')
{
int NewBlock=atoi((Buffer.str()).c_str());
Map::Environment[Y][X]=NewBlock;
if(NewBlock==Game.LastDoor && FoundDoor==false){Game.myX=X;Game.myY=Y;FoundDoor=true;APP.WorldLog.Write("FOUND PROPER DOOR\n");}
NotFoundX=false;
}
else{Buffer<<MapsHere[At];}
}
}
}
NewMap.close();
ApplySurface(0,0,SDL_LoadBMP(LoadSkyBMP.c_str()),Map::sky);
ApplySurface(0,0,SDL_LoadBMP(LoadGroundBMP.c_str()),Map::ground);
APP.MAPNAME=Dest;
APP.Log.Write("MAP LOADED\n");
}
else{APP.Log.Write("NO MAP FOUND\n");}
}
#endif // MAP_H_INCLUDED




and i want the Map::Load() function to run on its own thread just for that function, how would i do that?
Just as a bit of a warning: if you don't understand the basic form of a game loop, then trying to implement a multi-threaded one might break your brain. Multi-threaded programming can be tricky, and you should only try to do it if you are certain you really need it.

Are you entirely certain that you want your Map::Load to run concurrently with your game loop anyway? It is not designed properly to be a "streaming" type of load (where map data is loaded in the background) so it doesn't really make sense to have it concurrent. Load your map then, when it is loaded, spawn your player and execute the game loop.

Now, if you do want to implement background streaming, you need to completely rethink the problem and the way your map data is organized, so as to be able to stream it in small chunks and implement the streaming as a sequence of operations that can be run in small spurts each iteration, and be interrupted when it is time to move on to another part of the loop.. This, too, can be a complex task, that doesn't necessarily require multi-threading but however it is implemented it requires careful design.

This is pretty much what an earlier poster meant by "ticks". A tick in this context is just some task that is to be executed. The constraint upon a tick is that it not hog too much time, that it pass execution on within a reasonable timeframe so that the other tasks can be performed.

You iterate through the loop. First you update input. This tick must not block; ie, you do not use WaitEvent type functionality, but rather PollEvent. If there are events waiting, you iterate those events and hand them off to event handlers. Those event handlers must also be considerate of the fact that you are in a loop, and not block or take too much time. After events, you handle logic ticks. Just like everything else, this has to be considerate of timing. You can not block or wait, and you have to ensure that things are done quickly. If iterating every single object and updating logic is too slow, you can build a more complex system that updates subsets of objects each time. This might not be necessary for simpler games. Then, you can hand off execution to the streaming loader system. This system would have a queue of tasks to perform (loading level geometry, tiles, whatever) and would be structured to spend some specifiable amount of time completing tasks. When the allotted time has passed, it has to hand off execution back to the loop. AFter all that, you render.

In this typical game loop, concurrency is an illusion. All of these things are happening sequentially, rather than at the same time. However, the timing is such that if you are careful to not allow any one aspect to hog too much time, human perception won't be able to tell the difference.

It is only in the advanced cases, where the sheer amount of processing each turn is too much for a single thread, that multi-threading really shines. Being able to throw more cores or threads at a problem is good for certain classes of problems, but this category of problems is much narrower than one might think. Still, for a streaming world, having a producer thread can be handy. But before you tackle that complexity, profile the be-jesus out of your program and make sure that it is really necessary and that you can't reorganize, restructure, and find some simpler way of solving the problem.
So how would i be able to stream a 175kb file without causing the main loop to wait on it? Would i need to limit the read buffer when loading the file? or make several files for the different chunks? I think it would be to limit the reading buffer, then each frame use a smaller portion. when it is finished, it will change the map.
So i think i might have fixed it, dumb un-initialized variables! D:<

Here is the new code:

#ifndef MAP_H_INCLUDED
#define MAP_H_INCLUDED

class Map
{
public:
SDL_Surface *sky;
SDL_Surface *ground;
int Environment[SIZEOFMAP][SIZEOFMAP];

int LoadTick,x,y;
bool loading;
std::string Loadingfrom;
Map(std::string Dest);
void Box(int x,int y,int sizex,int sizey,int type);
void Point(int x,int y,int type);
void Save(std::string Dest);
void Load(std::string Dest);
};
void encrypt (char e[] )
{

}


Map::Map(std::string Dest)
{
Map::LoadTick=0;
Map::loading=false;
Map::x=0;
Map::y=0;
std::string LoadSkyBMP="stuff/maps/"+Dest+"/sky.bmp";
std::string LoadGroundBMP="stuff/maps/"+Dest+"/ground.bmp";
std::string LoadMapTXT="stuff/maps/"+Dest+"/map.map";
std::ifstream NewMap;
NewMap.open(LoadMapTXT.c_str());
if(!std::ifstream(LoadMapTXT.c_str())){APP.Log.Write("NO MAP FOUND\n");exit(EXIT_FAILURE);}
std::stringstream Buf;
Buf<<NewMap.rdbuf();
std::string MapsHere(Buf.str());
//for( ; * MapsHere != '\0'; ==* MapsHere ) --(* MapsHere);
bool FoundSpawn=false;
int At=0;
for(int Y=0;Y<SIZEOFMAP;Y++)
{
for(int X=0;X<SIZEOFMAP;X++)
{
bool NotFoundX=true;
std::stringstream Buffer;
while(NotFoundX)
{
At++;
if(MapsHere[At]=='|')
{
int NewBlock=atoi((Buffer.str()).c_str());
Map::Environment[Y][X]=NewBlock;
if(NewBlock==-1 && FoundSpawn==false){Game.myX=X;Game.myY=Y;FoundSpawn=true;APP.WorldLog.Write("FOUND SPAWN\n");}
NotFoundX=false;
}
else{Buffer<<MapsHere[At];}
}
}
}
NewMap.close();
ApplySurface(0,0,SDL_LoadBMP(LoadSkyBMP.c_str()),Map::sky);
ApplySurface(0,0,SDL_LoadBMP(LoadGroundBMP.c_str()),Map::ground);
APP.MAPNAME=Dest;
APP.WorldLog.Write("MAP INITIALIZED AND LOADED\n");
}



void Map::Box(int x,int y,int sizex,int sizey,int type)
{
int Dirx,Diry;
if(sizex>0){Dirx=1;}else{Dirx=-1;}
if(sizey>0){Diry=1;}else{Diry=-1;}
sizex=abs(sizex);sizey=abs(sizey);
for (int mapy=0;mapy<sizey+1;mapy++){for(int mapx=0;mapx<sizex+1;mapx++)
{
Map::Environment[y+mapy*Diry][x+mapx*Dirx]=type;
}}
}


void Map::Point(int x,int y,int type)
{
Map::Environment[y][x]=type;
}

void Map::Save(std::string Dest)
{
std::string SaveMapTXT="stuff/maps/"+Dest+"/map.map";
File NewMap;
NewMap.Open(SaveMapTXT.c_str());
NewMap.Write("|");
for (int Mapy=0;Mapy<SIZEOFMAP;Mapy++){for(int Mapx=0;Mapx<SIZEOFMAP;Mapx++)
{
char Buf[3];
itoa(Map::Environment[Mapy][Mapx],Buf,10);
std::string What(Buf);
What+='|';
//for(int i=0; What != '\0'; ++i ){++What;}
NewMap.Write(What.c_str());
}
}
NewMap.Close();
std::string Report="MAP SAVED: "+Dest+"\n";
APP.WorldLog.Write(Report.c_str());
}

void Map::Load(std::string Dest)
{
Map::Loadingfrom=Dest;
std::string LoadSkyBMP="stuff/maps/"+Dest+"/sky.bmp";
std::string LoadGroundBMP="stuff/maps/"+Dest+"/ground.bmp";
std::string LoadMapTXT="stuff/maps/"+Dest+"/map.map";
std::ifstream NewMap;
NewMap.open(LoadMapTXT.c_str());
if(std::ifstream(LoadMapTXT.c_str()))
{
char Buf[100];
NewMap.seekg(Map::LoadTick*100);
NewMap.read(Buf,100);//rdbuf();
std::string MapsHere(Buf);
//for( ; * MapsHere != '\0'; ==* MapsHere ) --(* MapsHere);
bool FoundSpawn=false;
int At=0,X=0,Y=0;
while(At<100)
{
bool NotFoundX=true;
std::stringstream Buffer;
while(NotFoundX)
{
At++;
if(MapsHere[At]=='|' && Map::y<300)
{
if(Map::x==299){Map::x=0;Map::y++;}
if(X==100){X=0;Y++;}
int NewBlock=atoi((Buffer.str()).c_str());
Map::Environment[Map::y][Map::x]=NewBlock;
if(NewBlock==-1 && FoundSpawn==false){Game.myX=X+Map::x;Game.myY=Y+Map::y;FoundSpawn=true;APP.WorldLog.Write("FOUND SPAWN\n");}
NotFoundX=false;
Map::x++;
X++;
}
else{Buffer<<MapsHere[At];}
}
}

NewMap.close();

char Loaded[3];
itoa(Map::LoadTick*11,Loaded,10);
std::string rep(Loaded);rep="MAP LOADED "+rep+"%\n";
APP.Log.Write(rep.c_str());
if(Map::LoadTick==9)
{
ApplySurface(0,0,SDL_LoadBMP(LoadSkyBMP.c_str()),Map::sky);
ApplySurface(0,0,SDL_LoadBMP(LoadGroundBMP.c_str()),Map::ground);
APP.MAPNAME=Dest;
Map::Loadingfrom="";
Map::loading=false;
Map::LoadTick=0;
Map::x=0;
Map::y=0;
APP.WorldLog.Write("MAP LOADED\n");
}
Map::LoadTick++;
}
else{APP.Log.Write("NO MAP FOUND\n");}
}
#endif // MAP_H_INCLUDED

If you want to avoid blocking the main loop, do the reading in a non-blocking fashion. For example, IOCP is a common solution.

It depends on how complex your game is. If your game is small I would just suck all the files into memory at startup into an associative container. All "loads" that occur use in-memory calls to create textures/scripts/whatever. Putting all your data into a zipped archive can speed this up, because generally reading and uncompressing data is faster than reading the data uncompressed. A library like PhysFS can help with this.

Don't over engineer. Get your game working first. Once you have a cool game, then it makes sense to start worrying about reducing load times or removing loading screens.

This topic is closed to new replies.

Advertisement