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\$
  • \$\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 '19 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 '19 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 '19 at 2:53
  • \$\begingroup\$ Can you upload this repo to Github, so we could try running it? \$\endgroup\$ – nihohit Jun 5 '19 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\$ – Muhammad Faizan Khan Jun 5 '19 at 12:20
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\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.