I am building large, procedurally generated levels at runtime. These may take 10~50 seconds to build on their own thread. I do not want to break it into fractions of a frame-sized bits. The player will be kept busy in VR doing other things between levels. So dropping the FPS is not acceptable. Everything I want to do can be done off the main thread, except for the graph updating. I thought maybe I could do it with AstarPath.active.Scan(), but Scan() makes many calls to methods that only run on the main thread. The parts that use pathfinding I have been able to separate off because pathfinding can be called and run off the main thread with a callback when it is done.
This is definitely something I’ve personally not touched but there’s ScanAsync() as well. It does explicitly say to not expect a higher framerate from this but I did want to bring it up. Have you had any success with this? I’m also wondering if using work items will be of any help for you, as described here.
Thanks, I saw the ScanAsync(), but as you say, it explicitly says not to expect a higher framerate. A previous question of mine was how to do this with WorkItems, but for that, you have to use an IEnumerator and break the code into sections that you believe will run in under one frame. You make the pieces too large and the FPS drops too small, and adds 30 or more to the processing time.
What I am looking at doing is writing a partial class AstarPath with a two pasrt scanInternal. The first part does all the locking to make Astar graph changing functions happy about being called. then calls my function, which has a callback to the main thread to run the second part of scanInternal that unlocks everything so that path finding will run again. The other option would be to do the same thing with WorkItem code. The ScanInteral code looks like it is all in one place, so it is easier for me to see all the locking and unlocking on one screen. Since I know I no other code will touch the data or objects I will be changing this should work.
Definitely keep us posted on how your plan works out ![]()
It looks like using an IEnumerator and breaking the code into chunks that will hopefully finish each frame without slowing the FPS on any target machine is what I will try.
I was unable to write a modified Scan that would let me run Apply() using
new Thread(()=> {
//code here to update graph
// code below is a callback from a Thread to the main thread.
UnityMainThreadDispatcher.Instance().Enqueue(FinishScan());
});
I tried but kept getting errors related to Astars locking functions.
I looked at ScanAsync() but it is the Prepare() method that runs over multiple frames and the docs for Prepare() say
No changes must be made to the graph in this method. Those should only be done in the Apply method.
So my current plan is to do all the work I can that does not need path-finding, and a method that will produce a list of node data holder structs, about 250K. Then, to write a function that will be called in an IEnumerator that calls AddWorkitem(), which does one chunk of work each frame. On my desktop, that is about 1K of node creation per ms. I don’t know what that will be on various low-end VR devices. I know the Meta Quest 2 has 8 cores, so using one thread full-time should not be a problem.
Sounds like a plan- speaking out of my ass here, but I feel like targeting Quest 2 is a good place to target. Before that is really just the OG Oculus Quest in the mainstream, and anything else is pretty much gonna be PCVR I think? So if it runs well on Quest 2 I’d imagine it would do well in the overwhelming majority of VR stuff. Maybe I’m just missing an entire market segment though ![]()
Not much I’m adding with this comment, I also just love VR! Let us know how your idea pans out!
Hi
Currently, as much as possible is done in separate threads, using the unity job system.
However, the code guarantees that from the main-thread’s perspective, the graphs are always in a consistent state. That’s why it does all the updates at the end in the Apply method.
It would be theoretically possible to modify the Apply method to run over multiple frames. But in that case, pathfinding and other things like GetNearest calls could receive invalid data for a few frames. (though pathfinding can, of course, be paused during this time).
The easiest way is to recalculate the graph in small chunks.
If you run AstarPath.active.UpdateGraph(bounds), and then wait until AstarPath.active.IsAnyGraphUpdateInProgress returns false before you schedule the next one, then this will run at a pretty good FPS, I think. You’d have to split the bounds of the graph up into smaller regions and call UpdateGraph with each one.
This would take much longer than Scan or ScanAsync, of course, since it’s significantly limited in the number of CPU power it has available.
Thanks.
I looked at using Scan/ScanAsync and doing most of the work on a separate thread, storing the planned changes in a temp graph, then just updating the graph in the final part of the Scan. It is a large graph, 250K nodes, so even that was slowing the FPS. I had used a PointGraph as my base since it is the simplest and very flexible. In GridGraph, there are methods for fast copies from a temp to the real graph, but IIRC, they are internal methods, so I could not use them on a PointGraph. I will look at it again in a couple of weeks. The deadline for the client was this week, so I just hacked some A* code from scratch and ran it on a separate thread. A* is not needed except for level generation in this project, so even though my code runs 10~20x slower, it was acceptable.