Preface I realized that the root of my question is a little simpler than all of the details below make it out to be. All other things being equal, what is the most important system aspect to optimize for in order to prevent performance impacts from showing up later? Should I be reducing the main thread load, reducing the overall CPU load across all threads, or reducing the game's memory footprint? It seems like a simple question, but I haven't seen it addressed anywhere.
All the glorious specifics:
I am creating a 2D top-down game with custom mesh tilemaps. I have a fixed size map, but I'm chunking anyways. I have an asynchronous worker thread that will generate a mesh data object that will be imported into the chunk:
public class MeshData
{
public List<Vector3> verticies = new List<Vector3>();
public List<int> triangles = new List<int>();
public List<Vector2> uv = new List<Vector2>();
}
I can run my noise sampling and triangle creation in the worker thread and then import the mesh data via the main thread when the data is ready. This works for world generation, but I'm not sure what to do for on-the-fly updates.
Specifically, I need to change the Z coordinate of any mesh point at any time when the player raises/lowers the terrain. My first thought was to directly edit the MeshFilter
from the worker thread, but Unity doesn't allow secondary threads to modify gameobjects. So, that leaves me with a few other options:
- Save a copy of
MeshData
in each chunk and modify that in the worker thread. Re-import in the main thread when the work is done. (This consumes 208Mb overall if my map size doesn't change) - Re-generate the mesh triangles from the saved coordinate array. (burns unnecessary CPU cycles on secondary thread, but no additional memory usage).
- Edit the mesh directly from the game thread. (Puts all the work on the main thread, probably a bad idea)
- Implement coroutines (Still puts the work on the main thread, but more "gracefully" (source))
- I need to re-write my chunk generation entirely, I obviously failed to learn elementary idea "X".
Since I am an embedded developer by day, I often find myself worrying about clock cycles and memory allocations, perhaps more than I need to. To be clear, I don't have a lag problem yet, I'm just doing my best to avoid creating one. From what I can see, lag is a common problem around here. (ex. 1, ex. 2, ex. 3) I am capable of doing any of the above (or something else I missed), I just don't know which is the most viable solution. Any expert opinions would be gladly appreciated.
Excerpt from my chunk generator
public class ChunkGenerator : MonoBehaviour
{
private MeshFilter filter;
// Can be called in async thread.
public void GenerateHeightMap(Vector2Int chunkPos, Vector2Int chunkSize, Vector2Int worldSize, WorldPoint[,] grid)
{
// Set the real coordinates for this grid point and calculate the height
}
// Can be called in async thread.
public void CreateChunk(Vector2Int chunkPos, Vector2Int chunkSize, WorldPoint[,] grid)
{
MeshData mesh = new MeshData();
// Some for loops here to iterate over the cells.
MeshUtilities.CreateCell(mesh, cellPoints);
this.meshData = mesh;
}
// Called by main thread, interacts with a filter.
public void LoadMeshData()
{
filter = GetComponent<MeshFilter>();
Mesh mesh = new Mesh()
{
vertices = this.meshData.verticies.ToArray(),
uv = this.meshData.uv.ToArray(),
triangles = this.meshData.triangles.ToArray()
};
filter.mesh = mesh;
filter.mesh.RecalculateNormals(); // Enables shadows.
}
Excerpt from my world generator thread
private void ChunkManagementThread()
{
// While loop allows us to bail if the main thread dies (game exit).
while (chunkGenerationThreadRunning)
{
for (int x = 0; x < chunkArraySize; x++) for (int y = 0; y < chunkArraySize; y++)
{
ChunkGenerator chunk = this.chunks[x,y];
lock(chunk)
{
switch (chunk.state)
{
case ChunkState.GenerateHeightmap:
// Pass 1: Set the height for all points.
this.chunks[x, y].GenerateHeightMap(new Vector2Int(x,y),this.realChunkSize,this.realWorldSize,this.grid);
break;
case ChunkState.CreateChunk:
this.chunks[x, y].CreateChunk(new Vector2Int(x, y), this.realChunkSize, this.grid);
// Pass 3: Create triangles.
break;
case ChunkState.ActivateMesh:
// Finally, render the image. (Actually from main thread, not shown)
this.chunks[x, y].LoadMeshData(this.chunks[x, y].GetMesh());
break;
case ChunkState.UpdatesPending:
// Need to process updates here.
break;
// Etc...
(Note: I did cut my code down a lot since my problem isn't with syntax. It probably won't run as shown; I can post the full version if needed)