0
\$\begingroup\$

I have to Load/Unload models from a specific URL based on player position. For this reason I am checking player position from model and then load/unload related piece of model in a Update event which runs on every frame.

Here is the update, that validating some checks before loading/unloading. I added this check for optimization purposes as the main loading/unloading loop is heavy:

 private void Update()
 {
     //If this feature is disable don't load/unload
     if (enableThisFeature == false) return;

     //if player is moving, dont load//unload
     if (Input.GetAxis("Horizontal") != 0 || Input.GetAxis("Vertical") != 0)
     {
         //do nothing when player is moving
         return;
     }
        
     DeactivateDistantTiles();
 }

After this I am checking player position and calling model load/unload:

private void DeactivateDistantTiles()
{
    playerPosition = transform.position;
    playerPosition = cameraController.currentActiveCam.transform.position; //transform.position;

    checkPlayerPositionChangeing = playerPosition.z != playerLastPos.z || playerPosition.x != playerLastPos.x;

    if (checkPlayerPositionChangeing)
    {
        ABLoadUnloadLoopCall();
    }

    playerLastPos = cameraController.currentActiveCam.transform.position;

}

Vector3 tilePosition;
float xDistance;
float zDistance;

public void ABLoadUnloadLoopCall()
{
        
    //old
    //foreach (SingleABLoader tile in tiles)
    //{
    //    Debug.Log("ABLoadUnloadLoopCall 123");
    //    Vector3 tilePosition = tile.gameObject.transform.position + (tileSize / 2f);

    //    float xDistance = Mathf.Abs(tilePosition.x - playerPosition.x);
    //    float zDistance = Mathf.Abs(tilePosition.z - playerPosition.z);

    //    if (xDistance + zDistance > maxDistance)
    //    {
    //        /*If you don't want to destroy the object on unload then use below line otherwise use DestroyBundleObject with true pararmeter */
    //        //tile.DestroyBundleObject();
    //        tile.DestroyBundleObject(true);
    //    }
    //    else
    //    {
    //        tile.StartDownloadingAB();
    //    }    
    //}

    //new
    for(int i = 0; i < tiles.Length; i++)
    {      
        tilePosition = tiles[i].gameObject.transform.position + (tileSize / 2f);

        xDistance = Mathf.Abs(tilePosition.x - playerPosition.x);
        zDistance = Mathf.Abs(tilePosition.z - playerPosition.z);

        if (xDistance + zDistance > maxDistance)
        {    
           /*If you don't want to destroy the object on unload then use below line otherwise use DestroyBundleObject with true pararmeter */
           //tiles[i].DestroyBundleObject();
           tiles[i].DestroyBundleObject(true);
        }
        else
        {
            tiles[i].StartDownloadingAB();
        }
    }
}

I found that ABLoadUnloadLoopCall making GC allocation in KBs in every frame which is very high (i think this is a problem, maybe i am wrong). So is there any way available that my above code optimize and make less allocation. My initial research suggest to use For loop instead foreach therefore in ABLoadUnloadLoopCall I am using for loop instead foreach but still my game lag/freeze for some minutes after loading the model/asset bundle. The lag/jerky/freeze usually disappear after some minutes/seconds of asset bundle loading.

Edit: Some More Code Snippet Added. Here is the Bundle/Model loading Code. Whenever a asset bundle download is require it add the download in a queue which later process one by one.

public void StartDownloadingAB()
{
    if (BundleLoadStatus == BundleLoadStatusEnum.bundleNotLoadedYet)
    {
        BundleLoadStatus = BundleLoadStatusEnum.bundlesLoading;
        enqueTheABDownloading.EnqueABDownloading(this);
    }

}

//actual code the download the bundle.
public IEnumerator DownloadAB()
{
     
    if (isBundleLoading == true)
        yield return false;
     
    BundleLoadStatus = BundleLoadStatusEnum.bundlesLoading;
    isBundleLoading = true;
     
    www = UnityWebRequestAssetBundle.GetAssetBundle(finalABLoaderURL);
    yield return www.SendWebRequest();
     
    if (www.error != null)
    {
        Debug.LogError("assetBundleURL : " + finalABLoaderURL);
        Debug.LogError("www error : " + www.error);
        www.Dispose();
        www = null;
        yield break;
    }
     
    bundle = ((DownloadHandlerAssetBundle)www.downloadHandler).assetBundle;
           
    AssetBundleRequest bundlePrefabAsync = bundle.LoadAssetAsync(bundle.name, typeof(GameObject));
    yield return bundlePrefabAsync;
     
    // if we got something out
    if (bundlePrefabAsync != null)
    {
        //First Off the Origin Shifting
        environmentOriginSetter.EnvironmentOriginSetterFeatureActive(false);//TODO
        //assetBundleToLoadObj = (GameObject)Instantiate(bundlePrefab);
             
        //Then Instantiate the Bundel Object and make it child to environment parent object.
        assetBundleToLoadObj = Instantiate(bundlePrefabAsync.asset as GameObject);
        assetBundleToLoadObj.transform.parent = envParent.transform;
        //assetBundleToLoadObj.transform.parent.transform.position = this.transform.localPosition;//new
     
        //Then Enable the Origin Setter feature again
        environmentOriginSetter.EnvironmentOriginSetterFeatureActive(true);
    }
     
    www.Dispose();
    www = null;
     
    // try to cleanup memory
    //Resources.UnloadUnusedAssets();//TODO open if memory problem occur
    bundle.Unload(false);//TODO open if memory problem occur
    bundle = null;
     
    isBundleLoading = false;
    BundleLoadStatus = BundleLoadStatusEnum.bundlesHasLoaded;
}

For Unloading, I am destroying the object

public void DestroyBundleObject(bool isDestroy)
{
    //bundle was loaded completely, write a bool, if it is true then loaded completely
    //bundle is loading, isBundleLoading
    //bundle is not load yet, 

    if (bundleObjectsDeleted == false && BundleLoadStatus == BundleLoadStatusEnum.bundlesHasLoaded)
    {
        bundleObjectsDeleted = true;
        if (assetBundleToLoadObj)
        {
            Destroy(assetBundleToLoadObj);
        }               
        BundleLoadStatus = BundleLoadStatusEnum.bundleNotLoadedYet;
    }
}

Before loading the bundle/Model, I Enque the desired bundle in a list and download it one by one.

public void EnqueABDownloading(SingleABLoader abToDownload)
{
    if (!singleAbLoader.Contains(abToDownload))
    {
        Debug.Log("Enque " + abToDownload.gameObject.name);
        singleAbLoader.Enqueue(abToDownload);

        if (isDownloadStarted == false)
        {
            StartCoroutine(StartDownloading());
        }
    }   
}

public IEnumerator StartDownloading()
{

    isDownloadStarted = true;
    Application.runInBackground = true;//enforce background loading.
    imgBlockClicks.SetActive(true);
    textLoading.SetActive(true);
    loadingSlider.gameObject.SetActive(true);

    while (singleAbLoader.Count > 0)
    {
        float sliderIncrementValue = 1f / singleAbLoader.Count;
        
        SingleABLoader singleAbLoaderCurrent = singleAbLoader.Dequeue();
        Debug.Log("Starting to call " + singleAbLoaderCurrent.gameObject.name);
        yield return singleAbLoaderCurrent.DownloadAB();
        Debug.Log("Finsihed to call " + singleAbLoaderCurrent.gameObject.name);
        //Debug.Log("Finished next loop");
        //singleAbLoaderCurrent.CallDownloadAB();
        //Debug.Log("download call for "+ singleAbLoaderCurrent.name);
        loadingSlider.value = sliderIncrementValue;
    }

    isDownloadStarted = false;

    textLoading.SetActive(false);
    imgBlockClicks.SetActive(false);

    //Application.runInBackground = false;
    loadingSlider.gameObject.SetActive(false);
    loadingSlider.value = 0;
}

I tried to profile on editor and found something like this: enter image description here

My recent test suggest that the error is occuring in FireFox and chrome while Edge is running fine.

\$\endgroup\$
13
  • \$\begingroup\$ This code looks extremely inefficient. I would recommend a complete refactor to 1. Eliminate separate asset bundles/loads for each tile (consider associating a bundle with a block of tiles or a tileset). 2. Initiate a load only when the player crosses into a new row/column, and only for the tiles that have newly entered the loaded area - you don't need to iterate over your whole tile list for this, you can find their coordinates with a little grid math. 3. Don't eagerly unload tiles that have just left the field - keep a cache to reduce redundant loading for back-tracking. \$\endgroup\$
    – DMGregory
    Jun 3, 2019 at 16:54
  • \$\begingroup\$ Lag disappearing after a couple of minutes might be a sign that the CPU finally caught up with you loading a lot of stuff again and again and ppaced the most relevant stuff in the cache. \$\endgroup\$
    – Bálint
    Jun 4, 2019 at 2:38
  • \$\begingroup\$ Is there any particular reason to delete everything as soon as it leaves the screen? Do you load an asset bundle everytime an object reenters the screen? \$\endgroup\$
    – Bálint
    Jun 4, 2019 at 2:53
  • \$\begingroup\$ Can you upload this repo to Github, so we could try running it? \$\endgroup\$
    – nihohit
    Jun 5, 2019 at 11:47
  • \$\begingroup\$ @dmgregory can u write the answer with some basic code or algorithm so I can try it? Like a little grid math is cumbersome task for me. \$\endgroup\$ Jun 5, 2019 at 12:20

1 Answer 1

0
\$\begingroup\$

The error was critical, no one able to answer this despite setting the bounty. Anyway the problem is resolve thanks to unity forum, thanks to JJ Johan . The Problem was shader compilation.

If I used GPU instancing it would cause a very long freeze the first time a mesh was rendered with that shader, only in the web build (and only WebGL 2 since it isn't supported in WebGL 1)

Note: The issue was not encounter in MS Edge browser even with the gpu instance checked property in shader.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .