unity3d c# Unity:尝试使生物群系生成算法可多线程化

3df52oht  于 2023-01-09  发布在  C#
关注(0)|答案(1)|浏览(181)

我目前正在学习如何编写游戏代码,并设计了一个生物生成算法。
只要我在syncron下面运行该算法,它每次都会生成相同的输出,并且工作得非常好。
现在我尝试加快速度并使其多线程化,但是每次调用该方法时,结果都不一样。
据我所知,我使用线程保存集合,只要有必要,但它仍然不工作。
此外,我试图锁定集合,但这也不起作用。
所以我完全不知道为什么这不起作用。
如果你看到任何我可以做得更好的东西或者我如何可以解决这个问题,请让我知道。
此代码有效:

private Biome[,] Generate(string worldSeed, Vector2Int targetChunk, List<(Biome, float)> multiplier, float centroidsPerChunk)
{
    //Calculate the NeighboursToGenerate depeding on the cendroids per Chunk value
    int chunkNeighboursToGenerate = (int)Math.Ceiling(Math.Sqrt(1f / centroidsPerChunk * 12.5f));
    int chunkSize = 8;

    //Create List that contains all centroids of the chunk
    List<(Vector2Int, Biome)> centroids = new();

    //Create Centdroids for every chunk of the generated region around the targetchunk
    for (int chunkX = targetChunk.x - chunkNeighboursToGenerate; chunkX < targetChunk.x + chunkNeighboursToGenerate + 1; chunkX++)
    {
        for (int chunkZ = targetChunk.y - chunkNeighboursToGenerate; chunkZ < targetChunk.y + chunkNeighboursToGenerate + 1; chunkZ++)
        {
            List<(Vector2Int, Biome)> generatedCentdroids = GetCentdroidsByChunk(worldSeed, new(chunkX, chunkZ), centroidsPerChunk, chunkSize, multiplier, targetChunk, chunkNeighboursToGenerate);
            foreach ((Vector2Int, Biome) generatedCentdroid in generatedCentdroids)
            {
                centroids.Add(generatedCentdroid);
            }
        }
    }
    Biome[,] biomeMap = new Biome[chunkSize, chunkSize];

    //---Generate biomeMap of the target Chunk---
    for (int tx = 0; tx < chunkSize; tx++)
    {
        for (int tz = 0; tz < chunkSize; tz++)
        {
            int x = chunkSize * chunkNeighboursToGenerate + tx;
            int z = chunkSize * chunkNeighboursToGenerate + tz;
            biomeMap[tz, tx] = GetClosestCentroidBiome(new(x, z), centroids.ToArray());
        };
    };

    //Return the biome map of the target chunk
    return biomeMap;
}
private static List<(Vector2Int, Biome)> GetCentdroidsByChunk(string worldSeed, Vector2Int chunkToGenerate, float centroidsPerChunk, int chunkSize, List<(Biome, float)> multiplier, Vector2Int targetChunk, int chunkNeighboursToGenerate)
{
    List<(Vector2Int, Biome)> centroids = new();

    //---Generate Cendroids of a single chunk---
    float centroidsInThisChunk = centroidsPerChunk;

    //Init randomizer
    System.Random randomInstance = new(Randomizer.GetSeed(worldSeed, chunkToGenerate.x, chunkToGenerate.y));

    while (centroidsInThisChunk > 0.0f)
    {
        //if at least one more centroid is to generate do it
        //if not randomize by the given probability if another one should be generated
        if (centroidsInThisChunk >= 1 || (float)randomInstance.NextDouble() * (1 - 0) + 0 <= centroidsInThisChunk)
        {
            //Generate random point for a new centroid
            Vector2Int pos = new(randomInstance.Next(0, chunkSize + 1), randomInstance.Next(0, chunkSize + 1));

            //map the point to a zerobased coordinatesystem
            int mappedX = (((chunkToGenerate.x - targetChunk.x) + chunkNeighboursToGenerate) * chunkSize) + pos.x;
            int mappedZ = (((chunkToGenerate.y - targetChunk.y) + chunkNeighboursToGenerate) * chunkSize) + pos.y;
            Vector2Int mappedPos = new Vector2Int(mappedX, mappedZ);

            //Select the biom randomized
            Biome biome = Randomizer.GetRandomBiom(randomInstance, multiplier);
            centroids.Add(new(mappedPos, biome));
            centroidsInThisChunk -= 1.0f;
        }
        //if no centroid is left to generate, end the loop
        else
        {
            break;
        }
    }
    return centroids;
}

//Calculates the closest Centroid to the given possition
Biome GetClosestCentroidBiome(Vector2Int pixelPos, IEnumerable<(Vector2Int, Biome)> centroids)
{
    //Warp the possition so the biom borders won't be straight
    //Vector2 warpedPos = pixelPos + Get2DTurbulence(pixelPos);
    Vector2 warpedPos = pixelPos;

    float smallestDst = float.MaxValue;
    Biome closestBiome = Biome.Empty;
    foreach ((Vector2Int, Biome) centroid in centroids)
    {
        float distance = Vector2.Distance(warpedPos, centroid.Item1);
        if (distance < smallestDst)
        {
            smallestDst = distance;
            closestBiome = centroid.Item2;
        }
    }
    return closestBiome;
}
public static class Randomizer
{
    //Generates a random integerseed by combining an hashing the inputvalues
    public static int GetSeed(string worldSeed, int chunkx, int chunkz)
    {
        var stringSeed = worldSeed + ":" + chunkx + ";" + chunkz;
        MD5 md5Hasher = MD5.Create();
        byte[] hashed = md5Hasher.ComputeHash(Encoding.UTF8.GetBytes(stringSeed));
        return BitConverter.ToInt32(hashed, 0);
    }

    //Returns a random biome based on the given properbilities/multiplier
    //multiplier = 2 for example means the biom is generated twice as often as usually
    public static Biome GetRandomBiom(System.Random rndm, List<(Biome, float)> multiplier)
    {
        float multmax = 0.0f;
        multiplier.ForEach(x => multmax += x.Item2);
        //Generate a random value that is in the range of all multiplieres added
        float biome = (float)rndm.NextDouble() * (multmax + 0.01f);

        //Map the biome to the multipliers and return the biome
        float multcalc = 0.0f;
        for (int r = 0; r < multiplier.Count; r++)
        {
            multcalc += multiplier[r].Item2;
            if (multcalc >= biome)
            {
                return multiplier[r].Item1;
            }
        }
        //Return Biome.Empty if something did't worked correct
        return Biome.Empty;
    }
}

这是行不通的:

private Biome[,] Generate(string worldSeed, Vector2Int targetChunk, List<(Biome, float)> multiplier, float centroidsPerChunk)
{
    //Calculate the NeighboursToGenerate depeding on the cendroids per Chunk value
    int chunkNeighboursToGenerate = (int)Math.Ceiling(Math.Sqrt(1f / centroidsPerChunk * 12.5f));
    int chunkSize = 8;

    //Create List that contains all centroids of the chunk
    ConcurrentBag<(Vector2Int, Biome)> centroids = new();

    ConcurrentQueue<Task> tasks = new();
    //Create Centdroids for every chunk of the generated region around the targetchunk
    for (int chunkX = targetChunk.x - chunkNeighboursToGenerate; chunkX < targetChunk.x + chunkNeighboursToGenerate + 1; chunkX++)
    {
        for (int chunkZ = targetChunk.y - chunkNeighboursToGenerate; chunkZ < targetChunk.y + chunkNeighboursToGenerate + 1; chunkZ++)
        {
            tasks.Enqueue(Task.Run(() =>
            {
                List<(Vector2Int, Biome)> generatedCentdroids = GetCentdroidsByChunk(worldSeed, new(chunkX, chunkZ), centroidsPerChunk, chunkSize, multiplier, targetChunk, chunkNeighboursToGenerate);
                foreach ((Vector2Int, Biome) generatedCentdroid in generatedCentdroids)
                {
                    centroids.Add(generatedCentdroid);
                }
            }));
        }
    }
    Biome[,] biomeMap = new Biome[chunkSize, chunkSize];

    Task.WaitAll(tasks.ToArray());

    //---Generate biomeMap of the target Chunk---
    for (int tx = 0; tx < chunkSize; tx++)
    {
        for (int tz = 0; tz < chunkSize; tz++)
        {
            int x = chunkSize * chunkNeighboursToGenerate + tx;
            int z = chunkSize * chunkNeighboursToGenerate + tz;
            biomeMap[tz, tx] = GetClosestCentroidBiome(new(x, z), centroids.ToArray());
        };
    };

    //Return the biome map of the target chunk
    return biomeMap;
}
omqzjyyz

omqzjyyz1#

如果你刚开始编程,并且你想学习多线程,那么像这样转换一大段复杂的代码并不是你想要开始的地方。我强烈建议你在开始这样的事情之前,先阅读一本关于C#/.NET中线程/异步的书籍或教程。Unity也有自己的多线程库及其Job System,它是为Unity工作流构建的:https://docs.unity3d.com/Manual/JobSystemMultithreading.html
我不认为大多数人能仅仅从这两段代码中找到问题的根源。

  • 将tasks集合更改为List<T>tasks仅在一个线程上访问,因此无需使用ConcurrentQueue<T>
  • Biome是一个类吗?因为如果是的话,从技术上讲,它是很好的,但是从多个线程修改数据结构会变得非常快。虽然我看不出你在修改这些代码片段中的数据,但是没有完整的代码,我不能肯定。为了线程化的目的,把Biome变成一个struct或者做一个struct等价物。
  • 另外,要避免在循环中调用centroids.ToArray(),因为这样做实际上会一遍又一遍地复制原始数组。在循环外调用一次,这本身就应该是一个相当大的性能提升。
  • 只要找到一个完整的threading/async/Unity的Job系统教程(取决于你想为你的用例学习哪一个)并从那里开始,我可以从你在任务中使用并发库和List<T>来判断你是线程新手。了解什么代码在另一个线程上运行以及由此产生的影响(竞态条件等)是巨大的。

相关问题