Unity crashes sporadically when threading

Hi! I’ve been working on a procedural planet generator.

Needless to say, it requires threading to run smoothly. It works well for about 5 minutes, but after a while Unity eats up about 7 GB of RAM and crashes. When built and run, it crashes with error unity virtualalloc remapping failed. I know that the Unity API isn’t threadsafe, but my program only uses Vectors for threading, which should be fine?

Here are the threaded parts of the code :

ConcurrentQueue<TerrainThreadInfo<TerrainData>> terrainThreadInfoQueue = new ConcurrentQueue<TerrainThreadInfo<TerrainData>>();
ConcurrentDictionary<string, MeshData> storedMeshData = new ConcurrentDictionary<string, MeshData>();

public void RequestTerrainData(Action<TerrainData> callback)
{
    ThreadStart threadStart = delegate { TerrainDataThread(callback); };
    Thread thread = new Thread(threadStart);
    thread.IsBackground = true;
    thread.Start()
}

public void TerrainDataThread(Action<TerrainData> callback)
{
    int size = root.GetSize(root);
    Vector3[] verts_holder = new Vector3[(QuadBuilder.res + 1) * (QuadBuilder.res + 1) * size];
    Vector3[] normals_holder = new Vector3[(QuadBuilder.res + 1) * (QuadBuilder.res + 1) * size];
    Vector2[] uvs_holder = new Vector2[(QuadBuilder.res + 1) * (QuadBuilder.res + 1) * size];
    Color[] colors_holder = new Color[(QuadBuilder.res + 1) * (QuadBuilder.res + 1) * size];
    int[] tris_holder = new int[(QuadBuilder.res) * (QuadBuilder.res) * 6 * size];
    TerrainData result = new TerrainData(verts_holder, normals_holder, uvs_holder, tris_holder, colors_holder, storedMeshData);
    result = GenerateMeshData(root.GetLeafNodes(root), result);
    lock(terrainThreadInfoQueue)
    {
        terrainThreadInfoQueue.Enqueue(new TerrainThreadInfo<TerrainData>(callback, result));
    }
}

void OnTerrainDataRecieved(TerrainData data)
{
    mesh = new Mesh();
    mesh.SetVertices(data.GetVertices());
    mesh.SetTriangles(data.GetTriangles(), 0);
    mesh.SetNormals(data.GetNormals());
    mesh.SetUVs(0, data.GetUVs());
    mesh.SetColors(data.GetColors());
    storedMeshData = new ConcurrentDictionary<string, MeshData>(data.GetCache());
    meshFilter.sharedMesh = mesh;
    readMeshData = true;
}

TerrainData GenerateMeshData(QuadTreeNode[] Leafnodes, TerrainData data)
{
    int[] t_tris = QuadBuilder.quadTemplateTriangles[15];
    for (int quad = 0; quad < Leafnodes.Length; quad++)
    {
        QuadTreeNode node = Leafnodes[quad];
        if (data.GetCache().ContainsKey(node.hashvalue))
        {
            MeshData cached;
            if (data.GetCache().TryGetValue(node.hashvalue, out cached))
            for (int i = 0; i < (QuadBuilder.res + 1) * (QuadBuilder.res + 1); i++)
            {
                data.GetVertices()[i + quad * (QuadBuilder.res + 1) * (QuadBuilder.res + 1)] = cached.GetVerts()
                data.GetNormals()[i + quad * (QuadBuilder.res + 1) * (QuadBuilder.res + 1)] = cached.GetNormals();
                data.GetColors()[i + quad * (QuadBuilder.res + 1) * (QuadBuilder.res + 1)] = cached.GetColors();
                data.GetUVs()[i + quad * (QuadBuilder.res + 1) * (QuadBuilder.res + 1)] = cached.GetUVs();
            }
        }
        else
        {
            // Values to save to cache after mesh is generated. This improves performance the next time we generate
            // data on this node
            Vector3[] c_verts = new Vector3[(QuadBuilder.res + 1) * (QuadBuilder.res + 1)];
            Vector3[] c_norms = new Vector3[(QuadBuilder.res + 1) * (QuadBuilder.res + 1)];
            Vector2[] c_uvs = new Vector2[(QuadBuilder.res + 1) * (QuadBuilder.res + 1)];
            Color[] c_colors = new Color[(QuadBuilder.res + 1) * (QuadBuilder.res + 1)];

            for (int x = 0, i = 0; x < (QuadBuilder.res + 1); x++)
            for (int z = 0; z < (QuadBuilder.res + 1); z++, i++)
            {
                // Seams are solved by a skirt on every tile. Ugly, but works
                Vector3 cornerTL = node.cornerTL;
                Vector3 cornerTR = node.cornerTR;
                Vector3 cornerBL = node.cornerBL;
                Vector3 cornerBR = node.cornerBR;
                Vector3 interpolated = Vector3.Lerp(Vector3.Lerp(cornerTL, cornerTR, (float)(x - 1) / (float)(QuadBuilder.res - 2)), Vector3.Lerp(cornerBL, cornerBR, (float)(x - 1) / (float)(QuadBuilder.res - 2)), (float)(z - 1) / (float)(QuadBuilder.res - 2));
                interpolated = interpolated.normalized * radius;
                Vector4 heightNormalData = GetHeightNormal(interpolated);
                Vector3 normal = GetNormal(heightNormalData, interpolated);
                Vector3 position = GetDisp(heightNormalData, interpolated);
                Vector2 uv = new Vector2(z / (float)QuadBuilder.res, x / (float)QuadBuilder.res);
                Color color = GetVertexColor(position);

                if (z == 0 || z == QuadBuilder.res || x == 0 || x == QuadBuilder.res)
                {
                    position = position.normalized * radius * 0.95f;
                }

                data.GetVertices()[i + quad * (QuadBuilder.res + 1) * (QuadBuilder.res + 1)] = position;
                data.GetNormals()[i + quad * (QuadBuilder.res + 1) * (QuadBuilder.res + 1)] = normal;
                data.GetUVs()[i + quad * (QuadBuilder.res + 1) * (QuadBuilder.res + 1)] = uv;
                data.GetColors()[i + quad * (QuadBuilder.res + 1) * (QuadBuilder.res + 1)] = color;
                c_verts *= position;
                c_norms *= normal;
                c_uvs *= uv;
                c_colors *= color;
            }

            // Save newly generated MeshData to cache
            if (data.GetCache().Count > 5000)
                data.GetCache().Clear();
            data.GetCache().Add(node.hashvalue, new MeshData(c_verts, c_norms, c_uvs, c_colors));
        }

        // Triangulation
        for (int i = 0; i < QuadBuilder.res * QuadBuilder.res * 6; i++)
        {
            data.GetTriangles()[i + quad * QuadBuilder.res * QuadBuilder.res * 6] =
                    !flipNormals
                ?	t_tris[t_tris.Length - 1 - i] + quad * (QuadBuilder.res + 1) * (QuadBuilder.res + 1)
                :	t_tris _+ quad * (QuadBuilder.res + 1) * (QuadBuilder.res + 1);
        }
    }
    return data;
}

storedMeshData contains a dictionary of previously generated chunks, so that not all chunks have to be rebuilt if not necessary. TerrainThreadInfoQueue is simply a queue of finished TerrainData that is sent to the MeshFilter for displaying. I would guess that the issue lies within GenerateMeshData, since I’ve seen similar implementations of threading that don’t seem to have this issue.

Looks like a memory leak or adjacent issue. Also, your code is written in a unnecessarily wasteful manner which might have obfuscated a bug somewhere here.

No idea why but (QuadBuilder.res + 1) * (QuadBuilder.res + 1) repeats 19 times where 3 is enough.

Some programming advice by example:

// this is wasteful:
for (int i = 0; i < (QuadBuilder.res + 1) * (QuadBuilder.res + 1); i++)
{
    data.GetVertices()[i + quad * (QuadBuilder.res + 1) * (QuadBuilder.res + 1)] = cached.GetVerts()
    data.GetNormals()[i + quad * (QuadBuilder.res + 1) * (QuadBuilder.res + 1)] = cached.GetNormals()
    data.GetColors()[i + quad * (QuadBuilder.res + 1) * (QuadBuilder.res + 1)] = cached.GetColors()
    data.GetUVs()[i + quad * (QuadBuilder.res + 1) * (QuadBuilder.res + 1)] = cached.GetUVs()
}
// the same results, but more frugal (less work for a cpu):
var dv = data.GetVertices();
var dn = data.GetNormals();
var dc = data.GetColors();
var du = data.GetUVs();

var cv = cached.GetVerts();
var cn = cached.GetNormals();
var cc = cached.GetColors();
var cu = cached.GetUVs();

int numIndices = (QuadBuilder.res + 1) * (QuadBuilder.res + 1);
for( int ic=0 ; ic<numIndices ; ic++ )
{
    int id = ic + quad * numIndices;
    dv[id] = cv[ic];
    dn[id] = cn[ic];
    dc[id] = cc[ic];
    du[id] = cu[ic];
}

>> As it happens, a frugal code (cpu-wise) is also easier to understand for humans.
I suggest you rewrite that GenerateMeshData method with this is mind.