Jump to content
  • Advertisement
Sign in to follow this  
coderWalker

Game Engine all wrong?

This topic is 2653 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I am making a Game from the ground up using C++ OpenGL SDL and Boost::threads.

Lets assume for the moment my game is a Minecraft clone (which it's not)

I decided to make this game with classes. I have now been working on this game nonstop
for about 2 months. I am at the point where I am adding threads. It is making me wonder did
I lay the functions, classes and code out correctly, or have I done it some way that is inefficient.

I have the main file which creates the player class and a map class (and hud class).
Within the player class are all the functions for movement, gravity, picking, etc.
The map class contains a 9x9x9 array of pointers to Chunk classes.
Within the Chunk class is an array 16x16x16 with the blocks, weather they exist, thier textures,and fluid amounts.
The chunk class also contains functions to remove, add, change, move blocks, draw the chunk, etc.
If a block was to fall in the same chunk it would just call it within the chunk.
The map class also has some of the same commands that basically "link" to the functions in the chunks.
So when the player deletes a block "world0->blockRemove(x,y,z)" gets called with figures out which
chunk that block is in (in case fluid moves outside chunk) the calls the
correct "Chunk[xArray][yArray][zArray]->blockremove(x,y,z)" which removes the block.

The thing that makes me wonder the most if I coded this inefficiently is threads.
Remember:
2 threads cannot access the same memory at once (Chunk->block[x][y][z])
which means when 1 thread is moving chunk pointers the other must pause and wait.

I am using threads like so, I have the main thread continuing as normal, running the drawing, movement, and
block actions. I have another thread which is loading chunks.

For example: When the player moves outside the current chunk (x+) all the chunks at Chunk[0][y][z] are deleted,
then the chunks to the right are shifted left 1 space, then new chunks are loaded from file or created all the way
to the right.

What messes up the threads is that, thread0 (main thread) is always using the chunks and thier blocks because
movement, picking, updating (moving liquids) and mainly drawing.
Thread0 is paused and thread1 (preloader thread) loads in the new chunks, moves the array and then pauses
and lets thread0 continue.
I could easily just pause thread0 when moving the chunks, however when a chunk is loaded it checks the 6 chunks
around it to see if blocks exist (when inside chunk it checks blocks at x=0 and if a block exists outside the chunk to
the left 1 block, it doesn't draw those faces) Meaning when a chunk is made it must also access the 6 chunks around
it since we are in 3d.

The threads are then running as follows:

thread0 thread1

---| ------------
--init ---------
---|------------
---| ------------
new ~ --------
---|------------|
-------- load chunks
--------------- |
----------------|
----------------|
---|------------
game -------
---|------------
---|------------
----------------|
-------- load chunks
----------------|
----------------|
----------------|
---| ------------
game --------
---| ------------
---| ------------
(yes this was hard to draw)

They can not run at the same time because the chunks access data outside them selves.
If thread0 is draw chunk[0][0][0] and thread1 creates chunk [1][0][0], when it checks the blocks
to the left of it it will crash. (Segmentation Fault). So they can not run concurrently. This means
that now that I have multithreading.... it's not any faster at all and still lags when changing chunks.

After all this information, I am curious about what people think I should do, I am basically
at the point where I don't have any idea what to do, I have hit a wall.

Is my engine layout correct?

what should I change?

How should I change the threads?

I know I have to have some way that the chunks will already be there to shift because if not then
the game would lag when trying to load new chunks.

I have spent all day today (last 6 hours) recoding all of the thread1's functions making it a seperate
file to try and prevent some segmentation fault errors and threads accessing the same data at the
same time. I made two arrays with the intent of letting the threads run seperate and thread1 load
chunks then copy it's new chunk array over when thread0 is not drawing so that there is only 1 pause
for a couple of milliseonds. I did all this to only realise that the chunk arrays are pointer and thier pointing
to the same location so when the loader thread deletes a chunk and starts to shift them it crashes
the main thread. So a whole days work was pointless.

If someone has any suggestions please let me know!

Since I started threads I have had nothing but problems, however I must have them since I am on a
AMD Phenom II x4 2.2Ghz and the game lag's a ton when trying to load a whole group of new chunks.

Not trying to dump on anyone just trying to get an evaluation of the whole engine and it's functions and
what I need to do to get it to work correctly. Like I said I have been doing nothing but working hard on this
for a little over 2 months.

I sure everyone gets to this point in large projects, where you just hit a roadblock.

I never knew I could type this much on a post.

Hands are tired,
Thanks in advance,
Coder Walker

Share this post


Link to post
Share on other sites
Advertisement

Since I started threads I have had nothing but problems, however I must have them since I am on a
AMD Phenom II x4 2.2Ghz and the game lag's a ton when trying to load a whole group of new chunks.

Drop your code in a profiler. Intel's VTune, AMD's CodeAnalyst, or the valgrind/callgrind suit. My money is on the result showing that your game hangs because HDDs are slow. You need to be using boost::threads / boost::asio to asynchronously load the data from the disk. Once you have the data in memory you can decide if it should be worked on in a single or multithreaded fashion.


I could easily just pause thread0 when moving the chunks, however when a chunk is loaded it checks the 6 chunks
around it to see if blocks exist (when inside chunk it checks blocks at x=0 and if a block exists outside the chunk to
the left 1 block, it doesn't draw those faces) Meaning when a chunk is made it must also access the 6 chunks around
it since we are in 3d.
[/quote]
Part of making a program easy to thread is breaking up the systems into parts the definitely have to do with one another and parts that don't. This sounds like something that isn't part of the "loading" process, but more part of the "append block to active world" process.

When the player moves outside the current chunk (x+) all the chunks at Chunk[0][y][z] are deleted,
then the chunks to the right are shifted left 1 space
[/quote]
Again, is thie part of the "loading" process? or the "move player around" process?


Broken down it sounds like you want to have
main thread:
1) move player
2) drop any blocks that are too far away, and shift the map over
2a) in one of your previous posts, you mention saving these dropped blocks? that would be queued for the loader thread then.
2b) request the loader thread to fill in the blanks
3) check if the loader thread gave us any data to put in the map.
3a) check if this chunk is needed. We may have dumped it too (we requested blocks for +x, but the player has moved +y as well now. So anything in the corner got dropped already. )
3b) do whatever pre-processing you want (you said you check other active blocks to determine faces that don't draw?)
3c) add the chunk to the map
4) goto 1

loader thread:
1) wait for requests from main #2b (and #2a)
2) load / save
3) enqueue any data for main #3
4) goto 1

In the grand scheme of things, your loader thread shouldn't know anything about the map. It should only know about some coordinate it needs to load from the disk for you.
The synchronization points would only revolve around 2 points.
Loader #1 is synchronized with main #2a and #2b
Loader #3 is synchronized with main #3
And, neither of those sync points require the two threads to say mutually exclusive for any length of time ( lock -> queue request -> unlock ). This is what I was trying to get at with my other posts, don't tie anything about the map representation to the loader thread. Let it only worry about loading some coordiate into some temp memory. The main thread can then sort our copying that temp memory back into the real map if the coordinate needs data.

Share this post


Link to post
Share on other sites
That's the way I was doing it, I guess I have reverted. :(

Even with the second method I implemented the Quene method...
The problem still persists that to initilize a Chunk it has to check the 6 surrounding chunks.
By initialize I mean construct the draw buffer. (list of verts that need rendering)
If I was to omit this it would add 16x16x6x4 = 6144 Extra unneeded verts per chunk!
(16x16 faces on each side of chunk * 6 faces * 4 verts per face)
(Loading from HDD does not take a long time, its initializing the chunks that does.

I am mostly worried about how it should work,
however if your curious here is all the code I wrote for the thread so far:

#include "Global.h"

thread1::thread1(signed int xOffset,signed int yOffset,signed int zOffset)
{
//Create the second thread
offset.x=xOffset;
offset.y=yOffset;
offset.z=zOffset;
running = true;
shift.x = 0;
shift.y = 0;
shift.z = 0;
init();
tMapLoading = boost::thread(boost::bind(&thread1::main, this));
}
void thread1::moveMap(signed int x, signed int y, signed int z)
{
boost::mutex::scoped_lock lock(mShift);
if (shift.x==0)
{
shift.x+=x;
}
if (shift.y==0)
{
shift.y+=y;
}
if (shift.z==0)
{
shift.z+=z;
}
}
void thread1::init()
{
//Create the initial Chunks
for (int x=0;x<CLIPDIST;x++)
{
for (int y=0;y<CLIPDIST;y++)
{
for (int z=0;z<CLIPDIST;z++)
{
signed int xCur=x+offset.x-HALFCLIPDIST;
signed int yCur=y+offset.y-HALFCLIPDIST;
signed int zCur=z+offset.z-HALFCLIPDIST;
section[x][y][z].chk = Writer->readChunk(xCur,yCur,zCur);
section[x][y][z].chk->bufferInit();
section[x][y][z].exists=true;
}
}
}
}

void thread1::main()
{
copy();
while (running)
{
bool changed = false;
//Check for needed shifting
{
boost::mutex::scoped_lock lock(mShift);
if (shift.x>0)
{
xOffsetAdd();
shift.x--;
changed = true;
}
else if (shift.x<0)
{
xOffsetSub();
shift.x++;
changed = true;
}
if (shift.y>0)
{
yOffsetAdd();
shift.y--;
changed = true;
}
else if (shift.y<0)
{
yOffsetSub();
shift.y++;
changed = true;
}
if (shift.z>0)
{
zOffsetAdd();
shift.z--;
changed = true;
}
else if (shift.z<0)
{
zOffsetSub();
shift.z++;
changed = true;
}
}
//Check for missing chunks
for (int x=0;x<CLIPDIST;x++)
{
for (int y=0;y<CLIPDIST;y++)
{
for (int z=0;z<CLIPDIST;z++)
{
if (section[x][y][z].exists == false)
{
signed int xCur=x+offset.x-HALFCLIPDIST;
signed int yCur=y+offset.y-HALFCLIPDIST;
signed int zCur=z+offset.z-HALFCLIPDIST;
section[x][y][z].chk = Writer->readChunk(xCur,yCur,zCur);
section[x][y][z].chk->bufferInit();
section[x][y][z].exists=true;
changed = true;
}
}
}
}
if (changed == true)
{
cout << "New Chunks loaded";
copy();
}
}
cout << "thread1 has died";
}
void thread1::copy()
{
boost::mutex::scoped_lock lock(mChunks);
for (int x=0;x<CLIPDIST;x++)
{
for (int y=0;y<CLIPDIST;y++)
{
for (int z=0;z<CLIPDIST;z++)
{
world0->Chunk[x][y][z]=section[x][y][z].chk;
world0->chunkExists[x][y][z]=section[x][y][z].exists;
}
}
}
world0->xOffset = offset.x;
world0->yOffset = offset.y;
world0->zOffset = offset.z;
}
void thread1::xOffsetAdd()
{
//boost::mutex::scoped_lock lock(mChunks);
printf("Map->xOffset+=1\n");
offset.x +=1;
player1->x-=16;
//Save old
int x = 0;
for (int y=0;y<CLIPDIST;y++)
{
for (int z=0;z<CLIPDIST;z++)
{
if (section[x][y][z].exists==true)
{
Writer->writeChunk(section[x][y][z].chk->location.x,section[x][y][z].chk->location.y,section[x][y][z].chk->location.z,x,y,z);
delete section[x][y][z].chk;
}
}
}
////Shift Map -x
for (int x=0;x<CLIPDIST-1;x++)
{
for (int y=0;y<CLIPDIST;y++)
{
for (int z=0;z<CLIPDIST;z++)
{
section[x][y][z]=section[x+1][y][z];
}
}
}
////Mark to be added
x = CLIPDIST-1;
for (int y=0;y<CLIPDIST;y++)
{
for (int z=0;z<CLIPDIST;z++)
{
section[x][y][z].exists=false;
}
}
}

void thread1::xOffsetSub()
{
//boost::mutex::scoped_lock lock(mChunks);
//boost::mutex::scoped_lock lock(mMain);
printf("Map->xOffset-=1\n");
offset.x-=1;
player1->x+=16;
////Save Shifted
int x = CLIPDIST-1;
for (int y=0;y<CLIPDIST;y++)
{
for (int z=0;z<CLIPDIST;z++)
{
if (section[x][y][z].exists==true)
{
Writer->writeChunk(section[x][y][z].chk->location.x,section[x][y][z].chk->location.y,section[x][y][z].chk->location.z,x,y,z);
delete section[x][y][z].chk;
}
}
}
////Shift Map -x
for (int x=CLIPDIST-1;x>=1;x--)
{
for (int y=0;y<CLIPDIST;y++)
{
for (int z=0;z<CLIPDIST;z++)
{
section[x][y][z]=section[x-1][y][z];
}
}
}
////Mark to be added
x = 0;
for (int y=0;y<CLIPDIST;y++)
{
for (int z=0;z<CLIPDIST;z++)
{
section[x][y][z].exists=false;
}
}
}

void thread1::yOffsetAdd()
{
//boost::mutex::scoped_lock lock(mChunks);
//boost::mutex::scoped_lock lock(mMain);
printf("Map->yOffset+=1\n");
offset.y+=1;
player1->y-=16;
////Save Shifted
int y = 0;
for (int x=0;x<CLIPDIST;x++)
{
for (int z=0;z<CLIPDIST;z++)
{
if (section[x][y][z].exists==true)
{
Writer->writeChunk(section[x][y][z].chk->location.x,section[x][y][z].chk->location.y,section[x][y][z].chk->location.z,x,y,z);
delete section[x][y][z].chk;
}
}
}
////Shift Map -y
for (int y=0;y<CLIPDIST-1;y++)
{
for (int x=0;x<CLIPDIST;x++)
{
for (int z=0;z<CLIPDIST;z++)
{
section[x][y][z]=section[x][y+1][z];
}
}
}
////Mark to be added
y = CLIPDIST-1;
for (int x=0;x<CLIPDIST;x++)
{
for (int z=0;z<CLIPDIST;z++)
{
section[x][y][z].exists=false;
}
}
}

void thread1::yOffsetSub()
{
//boost::mutex::scoped_lock lock(mChunks);
//boost::mutex::scoped_lock lock(mMain);
printf("Map->yOffset-=1\n");
offset.y-=1;
player1->y+=16;
////Delete Old
int y = CLIPDIST-1;
for (int x=0;x<CLIPDIST;x++)
{
for (int z=0;z<CLIPDIST;z++)
{
if (section[x][y][z].exists==true)
{
Writer->writeChunk(section[x][y][z].chk->location.x,section[x][y][z].chk->location.y,section[x][y][z].chk->location.z,x,y,z);
delete section[x][y][z].chk;
}
}
}

////Shift Map -x
for (int y=CLIPDIST-1;y>=1;y--)
{
for (int x=0;x<CLIPDIST;x++)
{
for (int z=0;z<CLIPDIST;z++)
{
section[x][y][z]=section[x][y-1][z];
}
}
}
//-Mark to be added
y = 0;
for (int x=0;x<CLIPDIST;x++)
{
for (int z=0;z<CLIPDIST;z++)
{
section[x][y][z].exists=false;
}
}
}

void thread1::zOffsetAdd()
{
//boost::mutex::scoped_lock lock(mChunks);
//boost::mutex::scoped_lock lock(mMain);
//vMapLoading.wait( mMapLoading );
//vMapLoading.w
printf("Map->zOffset+=1\n");
offset.z+=1;
player1->z-=16;
////Save Shifted
int z = 0;
for (int y=0;y<CLIPDIST;y++)
{
for (int x=0;x<CLIPDIST;x++)
{
if (section[x][y][z].exists==true)
{
Writer->writeChunk(section[x][y][z].chk->location.x,section[x][y][z].chk->location.y,section[x][y][z].chk->location.z,x,y,z);
delete section[x][y][z].chk;
}
}
}
////Shift Map -z
for (int z=0;z<CLIPDIST-1;z++)
{
for (int y=0;y<CLIPDIST;y++)
{
for (int x=0;x<CLIPDIST;x++)
{
section[x][y][z]=section[x][y][z+1];
}
}
}
//Mark to be added
z = CLIPDIST-1;
for (int y=0;y<CLIPDIST;y++)
{
for (int x=0;x<CLIPDIST;x++)
{
section[x][y][z].exists=false;
}
}
}

void thread1::zOffsetSub()
{
//boost::mutex::scoped_lock lock(mChunks);
//boost::mutex::scoped_lock lock(mMain);
printf("Map->zOffset-=1\n");
offset.z-=1;
player1->z+=16;
////Delete Old
int z = CLIPDIST-1;
for (int y=0;y<CLIPDIST;y++)
{
for (int x=0;x<CLIPDIST;x++)
{
if (section[x][y][z].exists==true)
{
Writer->writeChunk(section[x][y][z].chk->location.x,section[x][y][z].chk->location.y,section[x][y][z].chk->location.z,x,y,z);
delete section[x][y][z].chk;
}
}
}
////Shift Map -z
for (int z=CLIPDIST-1;z>=1;z--)
{
for (int y=0;y<CLIPDIST;y++)
{
for (int x=0;x<CLIPDIST;x++)
{
section[x][y][z]=section[x][y][z-1];
}
}
}
//Mark to be added
z = 0;
for (int y=0;y<CLIPDIST;y++)
{
for (int x=0;x<CLIPDIST;x++)
{
section[x][y][z].exists=false;
}
}
}

Share this post


Link to post
Share on other sites
I need your help, and it looks like you have done what I'm trying to implement. I will greatly appreciate your help for it. Can you please take a look at my problem here?

Share this post


Link to post
Share on other sites
I was hoping that I could have the second thread load them from the HDD and also initilize the chunk and check the surrounding block for possible faces to clip.
I guess that's not possible because both WILL have to share the same data. I will settle for it just loading and the other running, since I don't see any other way.

What other threads could I have?
(seems almost everything has to use the map's data)

What threads does Minecraft probably use?

Share this post


Link to post
Share on other sites

What other threads could I have?
(seems almost everything has to use the map's data)


To be honest, I'm not using threads like that at all. I use tbb pipelines to do load and process chunks, but so far not in parallel to rendering. So there is one thread loading the data, x threads parsing the data and filling data structures. A second pipeline does the processing (because you can NOT do visibility stuff unless all chunks are loaded) and again x threads process chunks while the main thread uploads finished vertex buffers to the card (yes, technically it could render a frame when there is no more data ready or a certain amount of time was spent with uploading).

Since the whole thing takes about .75ms per chunk (16*16*128) on my quadcore there's just a very short delay anyway.

Also, I might not be reading your code right, but I hope you don't copy around all the data everytime you scroll in one direction. Moving to the right means the new chunks overwrite the chunks that went off the map on the left, etc... basically all you need to do is map world position to your "chunk grid" with a simple modulo. If your visible part of the world is 32*32 chunks, chunk [192,84] is always at index [0,20] and so on. Load the new row/column, mark all new chunks and their neighbors as dirty to update visibility and that's it (if your chunks are always centered around your position, you might even be able to skip the neighbors, since the faces that might change their visibility are facing away from you anyway (but then you need to mark them so you won't forget updating them when the player crosses their row or column).

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

Participate in the game development conversation and more when you create an account on GameDev.net!

Sign me up!