Unity - запуск алгоритма поиска пути в отдельном потоке

Я реализовал алгоритм поиска пути A* в моей игре Unity 2D. Все работает, но это может привести к сбоям при поиске на широкой карте.

Проблема вызвана циклом while, выполняющимся в основном потоке. Я хочу, чтобы алгоритм мог запускаться в отдельном потоке, чтобы остановить запуск игры при запуске функции.

Мое понимание сопрограмм состоит в том, что их лучше использовать для последовательных функций, а не для сложных вычислений, подобных этому. Функция должна возвращать значение или использовать ссылки, чтобы прикрепить значение.

Как я могу реализовать этот тяжелый для CPU расчет, не блокируя основной поток? Т.е. многопоточность?

РЕДАКТИРОВАТЬ:

Текущая реализация сопрограмм, как указал Гейзенбаг.

Неполное извлечение из "тяжелой функции вычислений", которая должна распространяться в течение нескольких кадров даже на рабочие нагрузки.

//if the daemon is currently searching
public bool Searching;

//Create list for the algorithm
Pathfinding_Path Path = new Pathfinding_Path();
List<Pathfinding_Point> OpenList = new List<Pathfinding_Point>();
List<Pathfinding_Point> ClosedList = new List<Pathfinding_Point>();

//Agent is the object that shall pathfind, position is goal, callback
public IEnumerator Pathfind(GameObject Agent, Vector3 Position, Func<Pathfinding_Path,Vector3, bool,bool> Callback)
{
    //Abort if already searching
    if (Searching)
        yield break;

    Searching = true;

    //If the target position is not clear, abort
    if (!IsClear(Position))
    {
        Searching = false;
        yield break;
    }

    //Get the size of the agent
    Vector3 AgentSize = GetSize(Agent);

    //Start the algorithm
    Pathfinding_Point start = CreatePoint(AgentSize, Agent.transform.position, Position, 0);
    //Get possible steps from the first position
    CreateAdjacent(start, Position);
    //Add the node to the search tree
    OpenList.Add(start);

    //Keep track of how many iterations the function has ran (to not keep on going forever)
    int iterations = 0;

    //If there is an object to visit and the number of iterations is allowed
    while (OpenList.Count > 0 && iterations < 250)
    {
        iterations++;

        //Get the best node and visit it
        Pathfinding_Point point = GetBest(OpenList);
        OpenList.Remove(point);
        ClosedList.Add(point);    

        //Add all neighbors to the search tree
        foreach (Pathfinding_Point adjacent in point.Adjacent)
        {
            if (!ClosedList.Contains(adjacent))
            {
                if (!OpenList.Contains(adjacent))
                {
                    adjacent.Parent = point;


                    //The goal position is near, this is goal
                    if (Vector3.Distance(adjacent.Position, Position) <= AgentSize.sqrMagnitude * 0.5f)
                    {
                        //Add the final point to the path
                        Path.Add(adjacent);

                        //Get the last point
                        Pathfinding_Point step = Path.Points[0];
                        //Track backwards to find path
                        while(step.Parent != null){
                            Path.Add(step.Parent);
                            step = step.Parent;
                        }

                        Path.Finalize();

                        //Return the final path somehow (preferably using a callback method)
                        Callback(Path, Position, false);
                        Searching = false;
                        //Don't run the function no more
                        yield break;
                    } 
                    else if (IsClear(adjacent))
                    {
                        //Add to search tree
                        CreateAdjacent(adjacent, Position);
                        OpenList.Add(adjacent);
                    }
                }
                else
                {
                    //If the score is lower this way, re-calculate it
                    if (point.G + 1 < adjacent.G)
                    {
                        adjacent.G = point.G + 1;
                        adjacent.F = adjacent.G + adjacent.H;
                    }
                }
            }
        }
    }

    //If there are no more ways to go
    if(OpenList.Count == 0)
        yield break;

    //Here, the search has exceeded its limit on 250 iterations and shall continue after a small delay
    yield return new WaitForSeconds(0.005f);
    //The callback will run this function again, until the goal is reached or if there are no more nodes to visit
    Callback(Path, Position, true);
}

Обратный вызов, который должен обрабатывать различные случаи, которых может достичь функция поиска

//Path to use if it succeded, position that was the initial target, if the search is not yet finished and should be continued
bool GetPath(Pathfinding_Path Path, Vector3 pz, bool Continue)
{
    //Run the function again with the same parameters as the first time
    if (Continue)
    {
        StartCoroutine(Pathfinder.Pathfind(gameObject, pz, GetPath));
    }
    else if (Path.Points.Count > 0)
    {
        //A path has been found
        InvestigatePath = Path;
    }

    return true;
}

2 ответа

Решение

В конечном итоге вы можете использовать потоки как обычно в C#. Дело в том, что это не удобное решение, потому что вам нужно синхронизировать поток с циклом двигателя. Это не может быть тривиальным.

Мое понимание сопрограмм состоит в том, что их лучше использовать для последовательных функций, а не для сложных вычислений, подобных этому.

Это неправда. Одна из основных целей сопрограмм (они являются просто блоками итераторов) состоит в том, чтобы распределить вычисления по времени (несколько кадров), чтобы избежать икоты. Это форма совместной многозадачности, так что вы получите практически все преимущества многопоточности без усложнения синхронизации, потому что сопрограммы будут выполняться сразу после завершения основного цикла Обновление сценариев.

Используя сопрограммы, вы несете ответственность за то, сколько вычислений будет выполняться в каждом кадре, поэтому вы должны организовать свой код для поддержания стабильной частоты кадров. В случае поиска пути может быть что-то вроде этого:

IEnumerator PathFinding()
{
  while(!goalNodeReached)
  {
    VisitMaxNodes(maxNodesToVisit); // this function visit only a subset of the graph each frame
    yield return null;
  }
}

Вы должны иметь возможность создавать новый поток обычным способом и выполнять свои вычисления, пока новый поток не должен взаимодействовать с самим Unity, есть довольно много способов, которыми это может быть выполнено, но трудно сказать, какой из них использовать. В прошлый раз, когда я использовал его, Unity не поддерживал некоторые функции языка.NET 4.0, такие как Задачи, но это могло измениться.

Другие вопросы по тегам