Howdy,
I’m just dipping my toes into the new DOTS workflow, and I’m playing with the job system. I’m exploring the concepts that make open world games possible, and I’m working on loading and unloading the world around the player using additively loaded scenes.
Each scene currently holds just a Terrain object, and has a unique name to indicate its location on the overall map. I use a prefab empty gameobject with the same name in the world to check the player’s location against, and then if the player is close enough to that gameobject it will load the scene associated with it. A normal ForEach loop, with enough scenes, will take a long time to check the distance to all the other scenes. A solution I wanted to explore to reduce that time was the Job system.
Unfortunately, it seems like I can’t use the SceneManager.LoadScene() function inside of a IJobParallelFor job because the NativeArray doesn’t accept strings. I might be able to work around this using the build index, but it brings up the question: Is there a way to get strings into a NativeArray if you needed to work with string objects specifically?
Here’s my attempt at creating a job struct:
public struct LoadTerrainJob : IJobParallelFor
{
public NativeArray<string> SceneNames;
public NativeArray<float3> playerLocs;
public NativeArray<float3> sceneLoc;
public NativeArray<float> rads;
public void Execute(int index)
{
}
}
The NativeArray throws the following error: The type ‘string’ must be a non-nullable value type in order to use it as parameter ‘T’ in the generic type or method ‘NativeArray’.
1 Like
Try the FixedString* types. They might work for you
Think about solutions, where you don’t need strings in jobs. Is just wasteful.
Use unique IDs if have to.
You can even use entities as your identifier.
Regarding calling scene, you can do in many ways. You can create an entity in job and in other system in main thread, use that entity with scene ID, to open scene you need.
But generally, I think you doing something wrong, if you need iterate through many scenes.
Consider using spatial mapping, to find nearest points (scenes / objects / etc) of interest.
Well, to update on my attempt to load scenes using the job system: It seems either that I’ve done something wrong, or that using the Scenemanager through the job system takes longer than using coroutines and async loading. I suspect it’s the former. I’ll post my code, but I don’t expect anyone to help me fix the job:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEditor.SceneManagement;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Collections;
public struct LoadTerrainJob : IJobParallelFor
{
public NativeArray<TerrainSceneLoader.SceneData> SceneDatas;
public void Execute(int index)
{
var data = SceneDatas[index];
data.Update();
SceneDatas[index] = data;
}
}
[ExecuteInEditMode]
public class TerrainSceneLoader : MonoBehaviour
{
public bool useJobs = false;
public Transform PlayerLocation;
public float LoadRadius = 500f;
public List<GameObject> CellPrefabs; //CellPrefabs have the scene name and location to spawn
public struct SceneData
{
public int SceneBuildIndex;
public float3 SceneLoc;
public float3 PlayerLoc;
public float Radius;
public void Update()
{
if (Application.isPlaying)
{
float dist = math.distance(PlayerLoc, SceneLoc);
if (dist < Radius && !SceneManager.GetSceneByBuildIndex(SceneBuildIndex).isLoaded)
{
SceneManager.LoadScene(SceneBuildIndex, LoadSceneMode.Additive);
}
else if (SceneManager.GetSceneByBuildIndex(SceneBuildIndex).isLoaded)
{
SceneManager.UnloadSceneAsync(SceneBuildIndex);
}
}
}
public SceneData(int SBI, GameObject CellPrefab, float3 PlayerLocation, float LoadRadius)
{
SceneBuildIndex = SBI;
SceneLoc = CellPrefab.transform.position;
PlayerLoc = PlayerLocation;
Radius = LoadRadius;
}
}
private void Start()
{
if(PlayerLocation == null)
{
PlayerLocation = GameObject.FindGameObjectsWithTag("Player")[0].transform;
}
}
private void Update()
{
float startTime = Time.realtimeSinceStartup;
if (useJobs)
{
var SceneDataArray = new NativeArray<TerrainSceneLoader.SceneData>(
CellPrefabs.Count, Allocator.TempJob);
for(var i = 0; i < CellPrefabs.Count; i++)
{
SceneDataArray[i] = new TerrainSceneLoader.SceneData(
SceneManager.GetSceneByName(CellPrefabs[i].name).buildIndex,
CellPrefabs[i], PlayerLocation.position, LoadRadius);
}
var job = new LoadTerrainJob
{
SceneDatas = SceneDataArray
};
var jobHandle = job.Schedule(CellPrefabs.Count, 1);
jobHandle.Complete();
SceneDataArray.Dispose();
}
else
{
Vector3 pLoc = new Vector3(
PlayerLocation.position.x,
0,
PlayerLocation.position.z);
foreach (GameObject cell in CellPrefabs)
{
if (Vector3.Distance(pLoc,
cell.transform.position) < LoadRadius)
{
StartCoroutine(LoadScene(cell.name));
}
else if (Vector3.Distance(pLoc,
cell.transform.position) > LoadRadius)
{
StartCoroutine(UnloadScene(cell.name));
}
}
}
Debug.Log(((Time.realtimeSinceStartup - startTime) * 1000f) + "ms");
}
IEnumerator LoadScene(string SceneName)
{
if(!SceneManager.GetSceneByName(SceneName).isLoaded && Application.isPlaying)
{
AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(SceneName, LoadSceneMode.Additive);
while (!asyncLoad.isDone)
{
yield return null;
}
}
yield return null;
}
IEnumerator UnloadScene(string SceneName)
{
if (SceneManager.GetSceneByName(SceneName).isLoaded && Application.isPlaying)
{
AsyncOperation asyncUnload = SceneManager.UnloadSceneAsync(SceneName);
while (!asyncUnload.isDone)
{
yield return null;
}
}
yield return null;
}
}
With the jobs, times range somewhere between 2 and 9 ms. With coroutines and async loading, times range somewhere between 0.1 and 0.3 ms.