- Home /
I'm having Unity go through a large number of iterations using for loops. How can I do this without Unity "freezing up" while the for loop is running?
I'm running slot math simulations in Unity using for loops which iterate through a large number of random screens. I typically run 10 to 100 million simulations in a single run which, depending on the game rules, can take from 1 minute to 30 minutes or more. As I'm waiting for the sims to finish, the Unity project freezes up and becomes unusable, i.e. everything becomes unclickable (scenes, game tab, inspector, etc). How can I make Unity run those sims in the background while still being able to interact with other things within the project? The code running the sims lives in the Update method in a Monobehavior class, and start once a button is clicked. Maybe it can be done differently?
EDIT: The simulations inside the for loop only include mathematical calculations. After reading the example here, I tried the following using Burst:
protected void Simulate(int _runs)
{
for (int run = 0; run < _runs; run++)
{
InitializeMachine();
float time1 = Time.realtimeSinceStartup;
stats = new StatsHandler(trials, this);
SimJob sim = new SimJob { _game = this, _trials = trials, };
sim.Execute();
float time2 = Time.realtimeSinceStartup;
float dt = time2 - time1;
elapsedTime = string.Format("{0:00}h:{1:00}m:{2:00.000}s", (int)dt / 3600, (int)dt / 60, dt % 60);
stats.ComputeRTP();
Debug.Log("game rtp: " + stats.GameRTP);
Debug.Log(trials + " trials completed in " + elapsedTime);
SetCustomStats();
stats.GenerateAndPrintStats(elapsedTime);
}
}
And this is how I defined my job struct:
[BurstCompile]
public struct SimJob : IJob
{
public SlotMachine _game;
public int _trials;
public void Execute()
{
for (int i = 0; i < _trials; i++)
{
int win = 0;
win = _game.Spin();
if (win > 0) _game.Stats.HitRateCount++;
if (win > _game.Bet) _game.Stats.WinRateCount++;
if (win >= _game.Bet * 10) _game.Stats.GoodWinRateCount++;
_game.Stats.AddToTotalWin(win);
}
}
}
... but the computation time is exactly the same as without using Burst...
EDIT: So, before I decide to infuse burst into my ocean of code, I want to make sure I understand how to implement it (and see the value of compiling with burst myself). To do this, I created a class that simulates the probability of getting one side of a fair coin after flipping it with and without using the burst compiler.
using UnityEngine;
using Unity.Jobs;
using Unity.Burst;
using Unity.Collections;
public class Sim : MonoBehaviour
{
[SerializeField] private int trials = 50000000;
[SerializeField] private bool usingBurst = false;
void Start()
{
}
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
float time1 = Time.realtimeSinceStartup;
if (usingBurst)
{
var trialsArray = new NativeArray<int>(1, Allocator.Persistent);
trialsArray[0] = trials;
var successesArray = new NativeArray<int>(1, Allocator.Persistent);
var rng = new NativeArray<Unity.Mathematics.Random>(1, Allocator.Persistent);
rng[0] = new Unity.Mathematics.Random((uint)Random.Range(1, 1000000));
SimJob simJob = new SimJob { _trials = trialsArray, _successes = successesArray, _rng = rng };
simJob.Run();
Debug.Log((double)successesArray[0] / trials);
trialsArray.Dispose();
successesArray.Dispose();
rng.Dispose();
}
else
{
int successes = 0;
for (int i = 0; i < trials; i++)
{
if (Random.Range(0, 2) == 0)
{
successes++;
}
}
Debug.Log((double)successes / trials);
}
float time2 = Time.realtimeSinceStartup;
Debug.Log("execution time in seconds "+ (usingBurst ? "with " : "without ") + "burst: " + (time2 - time1));
}
}
}
[BurstCompile]
public struct SimJob : IJob
{
public NativeArray<int> _trials;
public NativeArray<int> _successes;
public NativeArray<Unity.Mathematics.Random> _rng;
public void Execute()
{
int t = _trials[0];
int s = 0;
for (int i = 0; i < t; i++)
{
if (_rng[0].NextInt(0,2) == 0)
{
s++;
}
}
_successes[0] = s;
}
}
Since I don't want to use the Random class in my struct, I decided to go with using Mathematics.Random, but it looks like I need to seed it before each coin flip? The code above gives the same value each random trial when executed with burst, is there a way to only have to seed rng[0] once per simulation?
depends. What exactly do you do in the job? If it's just mathematical calculations you can use the job system and the burst compiler to move this to another thread at 10x speed. With this you would not have any impact on the "playability" of your game. Can you share some insight on your actual task?
@Captain_Pineapple Thank you for your response. Wow! I didn't know I could decrease my computation time by 10x. I have just become more interested in optimizing computation time than running "background" sims hahah! Please read my update above. I'd be happy to provide more insight if necessary. All the methods used for simulations only perform mathematical computations.
Hey, well for your current code that is no wonder. If you really want to use burst you have to take care of a lot of things. $$anonymous$$ost importantly for this is your data setup. Since your data has to be thread safe you have to prepare it accordingly. The User guide your referenced does contain all information on allowed data types. For your case that means: Everything that is done in Slot$$anonymous$$achine
has to be moved to the job or a struct since you can not have a class in a job. (that is because a class is a so called "reference type")
So what would be the best for you?
As i can not tell that from the code you posted you have to make a call here: are "runs" in the for loop dependend or independend of each other? so does the second run use data from the first run?
also what is the set of data needed to start the job? what is the set of data that comes out of it? can you list this up?
Well that basically looks like the thing you want to do. About the random generator i sadly cannot help you.
would you $$anonymous$$d sharing the results of this regarding the needed time? Be carefull though that the needed Time can be heavily influenced by running this in the editor. A built version will result in more accurate times.
This job should now also be able to run in parallel in other threads if you change the used Job interface. This will then as i wrote you in my last comment enable you to do the same action mutliple times in parallel with $$anonymous$$imal additional overhead.
you can change the Allocator used for the Native Arrays to TempJob. This allocator will provide more speed.
Also since the the random generator of Unity.$$anonymous$$athematics is a struct you do not need a native array here as long as you just have one job. I am not sure here if the same generator can be used accross multiple parallel jobs or if you'd have to create a generator for each job. You'll have to test that for youself.
other then that looks good, let us know how you progress.
did you manage to solve this?
I was able to run coin flip sims both ways. The sims run 25x faster with burst which is pretty insane. Though, I realized it would take a lot of time and patience to redesign the current code in my original project, so I won't be implementing burst anytime soon. Using coroutines partially solves my original question because it slows down my sims a lot. Regarding the question I posted, my inquiry turned in a different direction, and you've provided me satisfactory answers, so I wish I could accept one of your comments as the answer and close this question hahah.
Answer by RLord321 · Jun 02, 2020 at 02:11 AM
Lookup StartCoroutine. If you make a function return type IEnumerator and use yield return null, it will return control back to Unity so it won't freeze:
private bool completedTask;
void Start()
{
StartCoroutine(LongLoopFunction);
}
void Update()
{
if( completedTask)
{
Debug.Log("All Done");
}
}
private IEnumerator LongLoopFunction()
{
for(int i = 0; i < 100000; i++)
{
expensiveStatement;
yield return null;
}
completedTask = true;
}
Answer by shaderLab2 · Jun 09, 2020 at 03:44 AM
The nvoke() method is a delegation mechanism of Unity3D
void Start()
{
InvokeRepeating("Iktest", 0, 2);
}
void Update()
{
if (Input.GetKeyDown(KeyCode.I))
{
if (IsInvoking("Iktest"))
{
CancelInvoke("Iktest");
Debug.Log("cancel");
}
}
}
void Iktest()
{
Debug.Log("At work");
}