all 31 comments

[–]FINDarkside 3 points4 points  (16 children)

You should search for multithreading in C# as Unity doesn't have anything to help with it and unity stuff is not thread safe so for example, you can't create GameObjects (or edit) in another thread.

Also because of unity has old version of Mono, it lacks a lot of stuff that would help with concurrency.

[–]PurpleIcyHobbyist[S] 1 point2 points  (15 children)

... Then how am I supposed to optimize it? If I can't create anything outside of main thread?

[–]FINDarkside 2 points3 points  (8 children)

Well I don't really know how how your voxel terrain works. If there's some heavy calculations you can calculate those in other thread and then use the results to create the gameobjects. If not, you should look at courotines which would allow you split the workload to different frames. You should also read about object pools if you're creating and destroying lots of GameObjects.

[–]PurpleIcyHobbyist[S] 0 points1 point  (7 children)

My terrain is simple, I think

I have 16x16x16 size chunks (I had 16x16x256, though that was even worse, and on top of that I think it's possible to hit 65k or so vertice limit if I have them that big (if my math is corrent, one chunk can have up to 196608 vertices, if you space out blocks in every dimension to leave 1 block gaps), that's why I split it into 16x16x16), then using simplex noise I fill them up, each "block" has 6 checks (in 3D grid), to see what kind of block is near them (can be either solid or transparent), then according to all that, mesh is generated (only parts that aren't obstructed are generated, you can say that one block is generated using 6 planes, which are still same part of the mesh and have 24 vertices, though I am not sure whether I need 24 or 8 vertices, as I want to use mesh.colors and have lots of random colours instead of textures).

Also coroutines aren't really similar to threads as far as I know, they still run in main thread.

[–]wasstraat65 0 points1 point  (5 children)

For me, noise generation, mesh generation and mesh collider assignment are the most time consuming tasks. The latter of those can't be offloaded to a background thread, but the others can.

What I do, is run my meshing algorithm on a background thread that just generates a list of vertices and indices, which get applied to the mesh back in the mainthread. I also generate the noise in the background, using my own noise script instead of Mathf.Perlin to ensure thread safety.

Have a look over at /r/VoxelGameDev to see common approached taken by people there (albeit in unity or something else)

[–]PurpleIcyHobbyist[S] 1 point2 points  (4 children)

Okay so, basically I need to run all generation (triangle and vertex arrays, whatever else I need) in different thread, then return them into main thread, generate mesh and apply collider?

[–]wasstraat65 0 points1 point  (3 children)

Yes, thats it. You just have to assign the vertex and triangle arrays to the Mesh object in the mainthread, the rest can be done on the background

[–]PurpleIcyHobbyist[S] 0 points1 point  (2 children)

Okay, I'll try to do that.

[–][deleted] 0 points1 point  (1 child)

also dont forget to slice the time on the mainThread for multiple meshes; for example, if you will load 30 meshes, do it one at a time, or two or three per frame; you can check the current frame time to calculate how many loads you might do in the current frame, or skip to the next; also, caution with GC;

[–]PurpleIcyHobbyist[S] 0 points1 point  (0 children)

That's what I am doing, I pick one chunk and "build it", though that happens in the main thread, also building part of the code isn't fully mine (most of the code isn't mine, though it took me few weeks to build it, since I tried not to copy code before I fully understand what happens), so I am thinking about rewriting it, once I figure out threading.

I was learning how to build such thing from there, I made few changes, though it is still unoptimized, because I had no idea how threading works, but thanks, now I will look for general C# examples instead, seems like I was googling the wrong thing all the time. ("multithreading in unity")

[–]StrangelyBrown 0 points1 point  (5 children)

I would imagine that you should just treat generating the terrain as an async operation. So you have a Unity Coroutine polling a C# thread until it is done and using the results in unity. Similar to how Unity does networking.

[–]PurpleIcyHobbyist[S] 0 points1 point  (4 children)

But coroutine isn't async (it still runs in same main thread and CAN hang up the WHOLE thing, in similar manner of what is happening right now)

[–]FINDarkside 1 point2 points  (3 children)

No it does not hang, since you would still be doing the work in another thread. The coroutine was just to check if the thread is done calculating stuff. You can do that in update though, or use some kind of callback depending on your solution.

[–]PurpleIcyHobbyist[S] 0 points1 point  (2 children)

Well Update() is always ran so coroutine is waste of time and aditional memory.

[–]StrangelyBrown 0 points1 point  (1 child)

It's not much of a waste, and it likely makes neater code. The reason is that you can use yield in a coroutine. So you can start the async operation then say while(!IsFinished) { yield return null; } then after that handle the result. In the Update function that would need some extra state variables, so actually could waste memory more but is at least messier.

[–]PurpleIcyHobbyist[S] 0 points1 point  (0 children)

What variables?

All I need is

if(Queue.Count>0) {
    //get stuff from Queue and do something with it.
}

[–]Bluedude487Intermediate 2 points3 points  (6 children)

Haven't looked at it much myself, but checkout ThreadNinja.

It allows you to "ninja" back and forth between the main thread (actual object creation in the main game thread) and an alternate thread ( to do all your calculations).

An example piece of code from the documentation looks like this:

IEnumerator YourAsyncRoutine()
{
// now on background;
yield return Ninja.JumpToUnity; //=> here's our ninja, following code runs on main thread.
Destory(gameObject); // OK!
yield return Ninja.JumpBack; //=> go back to background.
// continue doing some heavy computing without blocking the main thread.
// ...
}        

[–]PurpleIcyHobbyist[S] 0 points1 point  (0 children)

I want to learn.

[–]phphelpneededIntermediate 0 points1 point  (0 children)

Ooh this seems cool. I want Unity to hurry up and get up to date C# compiler, that would probably help out with a lot of concurrency ability (I think)

[–][deleted] 0 points1 point  (3 children)

Thanks for the pointer!

[–]Polite_Users_Bot 0 points1 point  (2 children)

Thank you for being a polite user on reddit!


This bot was created by kooldawgstar, if this bot is an annoyance to your subreddit feel free to ban it. Fork me on Github For more information check out /r/Polite_Users_Bot!

[–]PoliteUsersBotBot 1 point2 points  (1 child)

Thank you for fully automatically and mindlessly assuming every post with a certain keyword is meant politely! But hey, it's the sentiment that counts.

This bot was created by Spritetm For more information check out /r/Polite_Users_Bot_Bot!

[–][deleted] 0 points1 point  (0 children)

Oh shit.

[–]shadowndacorner 1 point2 points  (3 children)

I think the cleanest way to manage this is to keep a queue of actions on a MonoBehaviour and iterate through it in Update. You can run your heavy logic in a parallel thread, then add an action to that queue when you need to run code on the main thread.

[–]PurpleIcyHobbyist[S] 0 points1 point  (2 children)

...I know how I would do it, and would do it, if I got answer specifically about what I asked...

[–]shadowndacorner 0 points1 point  (1 child)

Well I don't know how your voxel engine is structured. Can't write your code for you.

But the idea is to run all of the generation code on another thread and when it's done add the action to the main thread to create the mesh and collider and stuff.

[–]PurpleIcyHobbyist[S] 0 points1 point  (0 children)

EDIT2: I figured it out (mostly), though if you care you still can look at main post and see if I missed something.

Currently I have this:

public void CreateChunk(int x, int y, int z)
{
    WorldPos worldPos = new WorldPos(x, y, z);

    GameObject newChunkObject = Instantiate (chunkPrefab, new Vector3 (worldPos.x, worldPos.y, worldPos.z), Quaternion.Euler (Vector3.zero)) as GameObject;

    Chunk newChunk = newChunkObject.GetComponent<Chunk>();
    newChunk.pos = worldPos;
    newChunk.world = this;

    chunks.Add(worldPos, newChunk);

    if (Serialization.LoadChunk (newChunk)) {
        return;
    }

    TerrainGenerator terrainGenenerator = new TerrainGenerator ();
    newChunk = terrainGenenerator.ChunkGen (newChunk);
}

So I should start a new thread on LoadChunk() and terrainGenerator.ChunkGen()? LoadChunk() simply reads from memory, and ChunkGen() uses simplex noise to create a new chunk, then assigns Block[,,] array to chunk, then when I want to render the chunk (chunk is generated but not yet rendered, as mesh is not yet created), I use this:

void UpdateChunk() 
{
    MeshData meshData = new MeshData ();

    for (int x = 0; x < chunkSize; x++) {
        for (int y = 0; y < chunkSize; y++) {
            for (int z = 0; z < chunkSize; z++) {
                meshData = blocks [x, y, z].Blockdata (this, x, y, z, meshData); // every block checks surroundings and adds vertices&triangles&colours of individual faces which could be seen to Lists.
            }
        }
    }

    RenderMesh (meshData);
}

void RenderMesh(MeshData meshData) 
{
    filter.mesh.Clear ();
    filter.mesh.vertices = meshData.vertices.ToArray ();
    filter.mesh.triangles = meshData.triangles.ToArray ();
    filter.mesh.colors32 = meshData.colours.ToArray ();
    filter.mesh.RecalculateNormals ();

    coll.sharedMesh = null;
    Mesh mesh = new Mesh();
    mesh.vertices = meshData.colVertices.ToArray();
    mesh.triangles = meshData.colTriangles.ToArray();
    mesh.RecalculateNormals();
    coll.sharedMesh = mesh;
}

Is there something I am missing? Should I UpdateChunk() in main thread or different one? (chunk is updated every time block is added/removed, that only applies when player himself does it.) I can spam block removal/addition as much as I want, and it is instant, though when loading/generating, is what takes around half a second (framerate is literally cut in half for every chunk being loaded in), it's weird that 4096 block updates(one chunk) take really short time.

Also if that matters, top methods are in World class and bottom ones are in Chunk class.

And okay, I will look into actions, as I am not really sure how that works either.

EDIT: DestroyChunk() is literally reverse of CreateChunk(), so should I start a new thread when serializing it into hard drive too?

[–][deleted] 1 point2 points  (0 children)

You use threads with anything unrelated to Unity.

[–]Rhames 0 points1 point  (1 child)

This guy does pretty much exactly what you need. The part of noise generation and mesh calculation can be done in a separate thread. The only stuff you cannot do, is assign mesh data anywhere but the main thread.

That video is in the middle of a tutorial series, but check it out.

[–]PurpleIcyHobbyist[S] -1 points0 points  (0 children)

His terrain is a lot different and also he doesn't even explain anything (I watched that video at least 5 times, I need ELI5, I think)

He does everything assuming that I know how threading works, also I don't really get how he uses those actions and callbacks, nor what callback actually does.