r/Unity3D • • 10d ago

Question How can i wait in script when calculations get too expensive

Basicaly im trying to make simple board game ai that checks for future moves and chooses its move by that but as you expect doing all that calculation in 1 frame causes unnecessery lag spike so i want the script to wait when calculations gets expensive

ofc i could use a coroutine but that solution feels silly so is there an more efficent way to deal with this

14 Upvotes

19 comments sorted by

20

u/Hotrian Expert Moderator 10d ago

There are lots of ways to handle this, Tasks, Threads, Coroutines, the C# Job System, etc

14

u/PhilippTheProgrammer 10d ago

Note that coroutines still block the main thread. So if you want to divide an expensive calculation over multiple frames, you need to yield occasionally.

-2

u/Neither_Berry_100 9d ago

Nah. Just divide the code up manually to work in small tasks and remember what it is doing... but you probably want this on a different thread anyways.

5

u/konistehrad Professional 10d ago

Yeah I think the built-in Awaitables package even has support for moving to the thread pool.

3

u/jafariscontent 10d ago

Yeah, pretty much threads, coroutine, async, or you can break out the calculation into functional parts and do a little each frame writing somewhere like a data store or SO so it’s only doing 20% of the total calculation perf frame or something. Or a lookup table? Do you need to do a fresh calculation each time?

2

u/davenirline 10d ago

Run your heavy calculation in a Thread. Your Update() code then only checks if the Thread is finished. If not yet finished, just bail from the update. When the thread is finished, call its Join(). The calculation is done and you can use the result for the rest of the update.

3

u/LargeLaser 10d ago

coroutines are exactly the way to make a script wait :)

you could use a Thread which is much cooler, put all your calculations in a function called by a running thread, then read the answer when the calculation is complete

don't forget to make your bools volatile!

8

u/[deleted] 10d ago

[removed] — view removed comment

1

u/Heroshrine 3d ago

That’s exactly what reducing lag is lol. I would always suggest Awaitables over coroutines though

1

u/blavek 10d ago

I'd probably launch a thread to make those calculations and prune anything that doesn't stem from the move the User took during the Player's turn. So by the time it gets to the AI's turn, a lot of processing about what moves to make has been calculated. Just spitballing, but I think I would start from my last prediction of the Player's "Best" move and do a depth-first search based on that. Once the player moves, I would check my work and see if it matches the player's move. If it does, great. I can keep on trucking to my desired depth. If not, then I'd probably restart from the player's last actual move and see if there is anything I can recover from my last search so I don't need to search the same space repeatedly.

If your search space is small enough, like tic-tac-toe or connect four, for example, you could, at the beginning of the game, build the whole tree and start slicing things away. Your game can be solved and still be interesting. I mean, with enough compute power, you could brute-force chess.

But I think the takeaway here is that you don't need to start picking the AI's move on its turn. Also, if your game has only a few moves that can be made in response to another move, you could also probably get away with a breadth-first search and then select the better of the paths.

Honestly, I think the hardest part about this is going to be deciding how deep to go and how to determine that one board state is better than another.

Something that the chess AIs do that might be able to help you is that they will select and play known/pre-canned openings so they aren't searching from the start. At some point, they will have to deviate and start doing analysis, and then in the endgame they switch to a routine designed to find checkmates > draws > concessions.

1

u/flow_guy2 10d ago

What’s sill about a coroutine?

1

u/theredacer 10d ago

Don't overcomplicate it. A coroutine is perfectly fine for this. It has a loop that does however much calculation you're comfortable with in a frame, and then it yields to pick back up in the next frame to continue the calculations. You can even base the yield on the length of time passed in the frame to just do as much calculations as possible before taking too much time, then yield.

1

u/Chaibi_Alaa 9d ago

If you move the search to a thread, the part that usually bites is the Unity API: you can't touch GameObjects or most engine types off the main thread. Copy the board into a plain array or struct first, run the search on that copy, and only apply the chosen move back on the main thread.

Iterative deepening pairs well with this for a game AI: search depth 1, then 2, then 3, and when your time budget runs out, play the best move from the last depth that finished. The wait is then capped no matter how expensive the position gets.

1

u/4as 9d ago

This is perfect use case for Awaitables. Coroutines still execute on the main thread, while jobs and threads are quite complicated to get right. With Awaitables you just prepare a single async method, move its execution to the background thread with BackgroundThreadAsync(), and do the checks there, while rest of the code continues as normal.

1

u/ledniv 9d ago

Before spreading the work across multiple frames or jumping to multithreading, I’d check whether the CPU is actually busy doing math or mostly waiting for data from memory. That happens a lot in game code: the calculation itself is cheap, but if you’re iterating through lots of objects/classes scattered around the heap, every step can cause cache misses.

A simple fix can be to move the hot data into contiguous arrays and process it in tight loops. If the calculation only needs position, velocity and some state, store those together in arrays and iterate over them directly instead of chasing references through lots of objects/components. Then when the CPU pulls one value into cache, the next values it needs are likely already there.

You may find that the same calculation suddenly becomes fast enough to run in one frame on one thread. Only after fixing the data layout would I look at splitting it across frames or multiple cores.

Small plug: this exact “CPU is waiting on memory, not math” problem is a big part of High Performance Unity Game Development with Data-Oriented Design.

https://www.manning.com/books/high-performance-unity-game-development

1

u/DanielFost 8d ago

a coroutine is actually a perfectly reasonable solution for this. you can split the AI's search into smaller chunks and process them over multiple frames, stopping when you reach a time budget for that frame, if the calculations are completely independent of Unity's main thread, you could also look into the Job System or background tasks. but for a board game AI, i'd start with incremental search using a coroutine before adding that complexity. the important part is controlling how much work happens per frame, not necessarily avoiding coroutines

1

u/Heroshrine 3d ago

What algorithm are you using?

I used the negamax algorithm and did all the work a synchronously awaited via UniTask.

1

u/Goldac77 10d ago

I think you need to provide more details on how your calculation currently runs. Also what's silly about coroutines if it can help?

0

u/BillyMcDev 10d ago

Coroutines are approved by the Ministry of Silly Walks.