0
Under review

Runtime instantiation issues

miklelottesen 4 months ago • updated 4 months ago 29

Hi


I'm having troubles with runtime instantiation. I'm building a game with a huge map (roughly 100x100 km), so there aren't any hand placed trees, they're all generated. The way it works is by generating and instantiating all trees within a given radius around the player when the game starts or the player fast travels (they're all instantiated during Start(), or during a single Update() frame). Then as the player moves and reaches a certain distance from the spawn point, a new radius is generated. All trees that doesn't already exist, within the new radius, are then generated and instantiated, while all trees outside the radius are disabled and pooled for future use.


I get that LushLOD recalculates parents whenever new ones are added/existing ones are removed, and I've taken care of that issue by returning from the RecalculateParents() function prematurely if there are still trees being generated.


My problem is that I haven't managed to successfully run my game with the LushLOD Manager added to the scene; it just hangs whenever I hit play, and eventually I have to Ctrl+Alt+Delete my way out of it. Without the manager I can run the game just fine, and instantiate my LushLOD prefab trees just fine (though of course it doesn't transition from billboard to mesh, or vice versa, as I move). I even tried disabling the RecalculateParents() function entirely, and it still hangs when I hit play, with the manager on scene, so the recalculations aren't even an issue. It's only a couple of thousand trees, so it should work right? What's going on?



Additional question: would it be possible to skip the manager entirely, and have each LushLOD tree handle itself independently without costing too much CPU power, sort of like how SpeedTrees work? To be completely honest, that's what I expected when I bought this asset, as I wanted a solution that wasn't subscription based (and this is no critique of you, I should've read more about the asset I was buying, but I still wanna get what I can out of this), so that would be really cool!

Under review

The manager is where the recalculateparents function is, but the manager also handles certain global shader variables such as the position of the sun (see the manager's update() function for some of that). Without those global shader variables, the trees won't handle lighting correctly. All of that is global in that it affects every tree, and so it made sense to me to handle it in a single location, which is why I made the manager. The manager also handles certain material property values that affect every tree in a global sort of way (most of that is done in the manager's ApplyAll function).


The first thing is to figure out why the manager is causing your scene to hang. If your scene runs and at least partially works without the manager, then I would add in the manager, and then I would add a return; command to each function in the manager, one by one, until it no longer hangs. Then you'll at least know which function in the manager was causing the hang, and we could go from there to figure out what exact line of code is causing the hang, or if it's hanging because the function is being called way too many times for some reason. I would start with the manager's ApplyAll() function, since that's the biggest one and it's probably called the most frequently.

Actually, some of the manager's functions probably have to return a value, so you can't just return; to exit the functions. Well in that case, just try returning out of the ApplyAll() and also out of the manager's Update() function, and see if it runs with those two functions broken. Also you could try running the game outside of the editor (building it as a standalone or something), to see if it is any of the code relating to the inspector, such as all those buttons that appear in the manager's inspector window, in case any of that editor-related code is possibly what is causing the crash. None of that code gets run if you build the game as a standalone, so it would be worth a try to build it as a standalone to see if the hang goes away.

Sorry for the delay, I work during weekdays so I only really have time for game development during weekends.


I took a different approach: I modified each method inside _LushLODTreesManager and _LushLODTree, so that when a method is executed, its name is written to an external log file. The method names in _LushLODTree are prepended by "-->", so they can be distinguished from the manager's methods. So I hit Play and let the game hang for a few minutes. This is what I found:


Start
SetupHalfSrqTransition
CalculateLODDistances
UsingUltraShader
UsingUltraShader
Update
--> Start
--> CreateTreeManager
--> LoadSaneStartingSettings
GetHighQualityShadowCastingMode
GetBillboardShadowCastingMode
ReCalculateParents
--> Reset
CalculateLODDistances
UsingUltraShader
--> SetupStaticMaterial
ApplyAll
FindTreeUsingFastShader
SetupHalfSrqTransition
CalculateLODDistances
UsingUltraShader
UsingUltraShader
--> Reset
CalculateLODDistances
UsingUltraShader
--> SetupStaticMaterial
CalculateLODDistances
UsingUltraShader
--> DoShaderUpdate
--> GetBillboardLeavesShader
--> GetBillboardLeavesFarShader
    >>> The following bit repeats forever: <<<
UsingUltraShader
--> GetLeavesShader
UsingUltraShader
--> GetLeavesShader
UsingUltraShader
--> GetBarkShader
UsingUltraShader
--> GetBarkShader
--> DoShaderUpdate
--> SetupStaticMaterial
--> GetBillboardLeavesShader
--> GetBillboardLeavesFarShader

The problems begin during the first Update frame, which makes sense as that's right after the trees are added (I'm guessing that the LushLOD scripts are being executed before my tree instantiator script).

Update: it goes through the repeating part for each tree, then a new frame, and it calls ApplyAll() and loops through each tree again. It takes an extremely long time; after several minutes it only gets through around 30 frames. And this is with a total of 256 trees in the scene.

This is really odd, I'm looking into it now... gimme a few minutes...

Had to reinstall Unity, about to start looking at it now... I'm going forward with the assumption that these functions only loop like this if the manager is in the scene.... so gimme a minute to look into this...

Okay, quick question...


--> LoadSaneStartingSettings
GetHighQualityShadowCastingMode
GetBillboardShadowCastingMode
ReCalculateParents
--> Reset

I notice it is doing a recalculate parents right after LoadSaneStartingSettings, but before it calls Reset(). It would only recalculate parents in this particular spot, if it detected that the parents have not been set up yet. 


So just to clarify, were you spawning the trees at runtime via a script in this test? Or were you working with 256 trees that had already been manually placed?

For now, I'll assume you were working with 256 dynamically spawned trees... if that's not correct let me know.... gonna continue to work my way through your debug text till I find the problem... gimme a minute...

All trees have been spawned dynamically during the Start() frame, there were no trees before that.


Just to clarify that I did it correctly: I added a LushLOD tree to the GameObject that holds all the spawned trees, then I created a manager via that tree and setup the manager with the parent GameObject as parent and deleted the one LushLOD tree I had inserted. Then I changed my tree spawner to spawn the same LushLOD tree prefab, instead of the old tree model.


Also, I completely forgot to test a build version so I did, and the same thing happens (it ate so much of my pc's memory that I had to restart manually, in fact). So I guess we can confirm that it's not caused by editor code.

Another significant update:


I just inadvertedly checked my log file again, and found that the endless loop looks different when running the build version:


CalculateLODDistances
UsingUltraShader
CalculateLODDistances
UsingUltraShader
CalculateLODDistances
UsingUltraShader
CalculateLODDistances
UsingUltraShader
CalculateLODDistances
UsingUltraShader
CalculateLODDistances
UsingUltraShader
CalculateLODDistances
UsingUltraShader
CalculateLODDistances
UsingUltraShader
CalculateLODDistances
UsingUltraShader
CalculateLODDistances
UsingUltraShader
CalculateLODDistances
UsingUltraShader
(.....)

It appears that it's not even doing anything in _LushLODTree, but the manager is quite busy calculating LOD distances.

I think I probably found the problem... if you run a text search in the manager's code, look for every occurance of this line of code: 


ShaderSettingsChanged += 1;


and replace it with this instead:


if (ByPassChangedChecks == false) ShaderSettingsChanged += 1;


And let me know if that improves anything.

I tried implementing the change. Unfortunately, it didn't solve the problem. It seems to be going through the following loop for each tree:


--> DoShaderUpdate
--> GetBillboardLeavesShader
--> GetBillboardLeavesFarShader
UsingUltraShader
--> GetLeavesShader
UsingUltraShader
--> GetLeavesShader
UsingUltraShader
--> GetBarkShader
UsingUltraShader
--> GetBarkShader

..then it proceeds to do this for just one of the trees:

--> Start
--> CreateTreeManager
--> LoadSaneStartingSettings
GetHighQualityShadowCastingMode
GetBillboardShadowCastingMode
ReCalculateParents
--> Reset
CalculateLODDistances
UsingUltraShader
--> SetupStaticMaterial
ApplyAll
FindTreeUsingFastShader
SetupHalfSrqTransition
CalculateLODDistances
UsingUltraShader
UsingUltraShader
--> Reset
CalculateLODDistances
UsingUltraShader
--> SetupStaticMaterial
CalculateLODDistances
UsingUltraShader

..and then it loops through all the trees again. It appears that all of this is taking place within the same Update frame.

Ok, earlier you pasted for me this code:


Start
SetupHalfSrqTransition
CalculateLODDistances
UsingUltraShader
UsingUltraShader
Update
--> Start
--> CreateTreeManager
--> LoadSaneStartingSettings
GetHighQualityShadowCastingMode
GetBillboardShadowCastingMode
ReCalculateParents
--> Reset
CalculateLODDistances
UsingUltraShader
--> SetupStaticMaterial
ApplyAll
FindTreeUsingFastShader
SetupHalfSrqTransition
CalculateLODDistances
UsingUltraShader
UsingUltraShader
--> Reset
CalculateLODDistances
UsingUltraShader
--> SetupStaticMaterial
CalculateLODDistances
UsingUltraShader
--> DoShaderUpdate
--> GetBillboardLeavesShader
--> GetBillboardLeavesFarShader
    >>> The following bit repeats forever: <<<
UsingUltraShader
--> GetLeavesShader
UsingUltraShader
--> GetLeavesShader
UsingUltraShader
--> GetBarkShader
UsingUltraShader
--> GetBarkShader
--> DoShaderUpdate
--> SetupStaticMaterial
--> GetBillboardLeavesShader
--> GetBillboardLeavesFarShader

Can you make me another one of these debug reports, just like this one, except this time with the code change that you just made to the manager (where you edited the ShaderSettingsChanged)? So that I can compare the two and see if there was any difference...

I'll be back in a few hours

Sure:

Start
SetupHalfSrqTransition
CalculateLODDistances
UsingUltraShader
UsingUltraShader
Update
--> Start
--> CreateTreeManager
--> LoadSaneStartingSettings
GetHighQualityShadowCastingMode
GetBillboardShadowCastingMode
ReCalculateParents
--> Reset
CalculateLODDistances
UsingUltraShader
--> SetupStaticMaterial
ApplyAll
FindTreeUsingFastShader
SetupHalfSrqTransition
CalculateLODDistances
UsingUltraShader
UsingUltraShader
--> Reset
CalculateLODDistances
UsingUltraShader
--> SetupStaticMaterial
CalculateLODDistances
UsingUltraShader
    >>> Repeating bit for each tree: <<<
--> DoShaderUpdate
--> GetBillboardLeavesShader
--> GetBillboardLeavesFarShader
UsingUltraShader
--> GetLeavesShader
UsingUltraShader
--> GetLeavesShader
UsingUltraShader
--> GetBarkShader
UsingUltraShader
--> GetBarkShader

Can you attach a copy of your manager.cs file and post it here for me. You're actually working with a somehow older version of LushLOD. The current version I'm working with is actually version 0.8, which hasn't been released yet. :)


Becuase I'm not able to see how this error is possible, at least with my version of the manager. It could be that I already fixed this error in version 0.8...

Actually, I don't know if this thing will let you attach files. If not, you might have to upload it to dive.google.com and then send me a link to download it.

I've uploaded my version here: https://drive.google.com/open?id=1VzCNGeoyDsiIyRn0imvcLQI8G4hM7t0c


I forgot to disable the writing of each method name to the log file, but you can just comment out the WriteToLog method, which is right below the properties.


Edit: sorry, it's called WriteToLogFile - it's on line 378

UGH your code matches mine, in the spot I was worried about. So the fact that you're on an older version isn't the issue.


The code that should NOT be getting run, is the code near the spot where you added the line:


WriteToLogFile("Trees to go through: "+allTrees.Length);


But it looks to me like it is running that code when it shouldn't. Meaning it is looping through each tree, and each tree is triggering a loop through all of the trees again, potentially an endless loop.


I'm going to copy / paste your exact debug code here, but I'm adding in some comments so I can explain what I think is happening.


Okay so basically, here's my thinking:


Start
SetupHalfSrqTransition
CalculateLODDistances
UsingUltraShader
UsingUltraShader
Update 
    --> Start //first tree's start() function
        --> CreateTreeManager
        --> LoadSaneStartingSettings
        GetHighQualityShadowCastingMode
        GetBillboardShadowCastingMode
        ReCalculateParents
            --> Reset //This should be the call to reset, in the tree's Start() function.
                CalculateLODDistances
                UsingUltraShader
                --> SetupStaticMaterial
        ApplyAll(this); //<-- THIS should be the call to ApplyAll(this),
                // AT THE BOTTOM of the tree's start() function,
                // WHICH PASSES (this), aka it passes a reference to "this"
                // tree that is calling ApplyAll.
                // So the following code is being run by ApplyAll(THIS).
                // At the start of ApplyAll, we check to see if "this" is
                // not null, and if a reference to a tree WAS passed, then we
                // set ByPassChangedChecks == true. 
                // (See near the top of ApplyAll
                // where it sets ByPassChangedChecks to true.)
                // Right here, in your debug text, I'm expecting ByPassChangedChecks
                // to be TRUE, because we called ApplyAll and passed (this), which
                // means that ByPassChangedChecks should now be TRUE.
                // So, continuing on, here's the debug for ApplyAll(this):
            FindTreeUsingFastShader
                //#### LOD ####
                SetupHalfSrqTransition
                CalculateLODDistances
                UsingUltraShader
                UsingUltraShader
                    --> Reset
                        CalculateLODDistances
                        UsingUltraShader
                        --> SetupStaticMaterial
                //#### BILLBOARD QUALITY ####
                CalculateLODDistances
                    UsingUltraShader
                //And next it runs DoShaderUpdate, which I assume is being run
                //by the line of code just below the spot where you added this line:
                // WriteToLogFile("Trees to go through: "+allTrees.Length);
                //And here's that code:
                --> DoShaderUpdate
                //THE ABOVE LINE OF CODE should not have been run, because it only
                //runs if the value of "ShaderSettingsChanged" was changed, but it can't
                //change unless ByPassChangedChecks == false. But
                //ByPassChangedChecks should be TRUE, because we called ApplyAll(this), 
                // which should have set ByPassChangedChecks to TRUE.
                // so essentially, if ByPassChangedChecks == true, then
                // ShaderSettingsChanged should not have changed. And if
                // ShaderSettingsChanged didn't change, then we should not enter the
                // block of code near the spot where you added the line:
                // WriteToLogFile("Trees to go through: "+allTrees.Length);
                // However, by the looks of it, that's where we are right here
                // in this debug text.
                --> GetBillboardLeavesShader
                --> GetBillboardLeavesFarShader
    >>> The following bit repeats forever: <<<
UsingUltraShader
--> GetLeavesShader
UsingUltraShader
--> GetLeavesShader
UsingUltraShader
--> GetBarkShader
UsingUltraShader
--> GetBarkShader
--> DoShaderUpdate
--> SetupStaticMaterial
--> GetBillboardLeavesShader
--> GetBillboardLeavesFarShader

What you could do if you have time, is add a debug line to that function, same place you added the line:


WriteToLogFile("Trees to go through: "+allTrees.Length);

And add these lines: 


WriteToLogFile("Value of ShaderSettingsChanged: " + ShaderSettingsChanged);
WriteToLogFile("Value of ShaderSettingsChanged_PREVIOUS: " + ShaderSettingsChanged_PREVIOUS);
WriteToLogFile("Was this a specifictree?: " + ReferenceEquals(SpecificTree, null) == false ? "true" : "false");
WriteToLogFile("Value of ByPassChangedChecks: " + ByPassChangedChecks == true ? "true" : "false");


Here's the dump. I added a frame counter to make sure that it's indeed all happening in the same frame:

Current frame: 1
Trees to go through: 256
Value of ShaderSettingsChanged: 1
Value of ShaderSettingsChanged_PREVIOUS: 1
Was this a specifictree?: true
Value of ByPassChangedChecks: true
>
Current frame: 1
Trees to go through: 256
Value of ShaderSettingsChanged: 2
Value of ShaderSettingsChanged_PREVIOUS: 2
Was this a specifictree?: true
Value of ByPassChangedChecks: true
>
Current frame: 1
Trees to go through: 256
Value of ShaderSettingsChanged: 3
Value of ShaderSettingsChanged_PREVIOUS: 3
Was this a specifictree?: true
Value of ByPassChangedChecks: true
>
Current frame: 1
Trees to go through: 256
Value of ShaderSettingsChanged: 4
Value of ShaderSettingsChanged_PREVIOUS: 4
Was this a specifictree?: true
Value of ByPassChangedChecks: true
>
(etc..)

I tried commenting out the if (ByPassChangedChecks == false) ShaderSettingsChanged += 1; line, so that both that and the original if ShaderSettingsChanged += 1; is commented out. Doesn't change the problem, so it seems like ShaderSettingsChanged gets incremented elsewhere.

I tried implementing the if(ByPassChangedChecks == false) to all the 3 places where ShaderSettingsChanged gets incremented. Now, after a while, the game runs! And the trees appears as they should!


However, it's extremely laggy, and I don't know why because according to the log, the only thing that happens for each frame is that the manager's update method is called, followed by the update method for each tree, but nothing besides that (well, the manager calls GetBillboardShadowCastingMode a few times during the frame, but that's it!)

Wait a second, perhaps I should disable the logging function and try again - obviously that would cause a huge lag!! :D

Indeed, it runs a lot better now!

However, it's still quite laggy, and the main lags seem to be a combination of the _LushLODTree instances and camera culling.



It's most prelavent when generating new trees. I already told the manager to return out of RecalculateParents, until my tree instantiation queue is 0. During runtime, I'm instantiating maybe 20 trees per update frame and although the RecalculateParents isn't executed until all trees have been placed, there's still a lot of _LushLODTree activity for each update frame.


Edit: Somehow I got better performance by using forward rendering instead of deffered. Also, I forgot to set the quality from ultra to low the last time I transfered the project from my powerful desktop to my not so powerful laptop (which I'm on now), so that helped as well.


Still, it's running unacceptably slow and the heavy load seems to originate from each LushLOD tree's update function, every single frame. There are around 3000-5000 trees at any given time.

Maybe you could just do a hack solution, and tell the manager to return out of ApplyAll() always, except for when you want applyall() to be run. Then you would call it yourself after you finish spawning a batch of trees. Something like this:


ApplyAll(null, true, true, false);

It does appear that applyall() is being called multiple times per tree, on the same frame. This hack solution would guarantee to stop that from happening, at least. If you do this,, then you should probably un-comment out those ShaderSettingsChanged increment lines, since they probably do something important. :-)


Your next issue was the lag on the Update() function on the trees:


By "low setting", do you mean billboards only? Because billboard-only mode shouldn't use the update functions on the trees. The other modes do use the update() functions to do the transitions, but only for trees that are somewhat close to the camera. Most of the very far away trees fully turn off their update() function calls.


I think the demo scene has about 4000 trees, which isn't much different than the number of trees in your custom scene. You might check to see if your laptop can run the demo scene. If it runs my demo scene just fine, but can't run your custom scene, then maybe something isn't set up or working correctly in your custom scene.

Just to clarify... ApplyAll() is only supposed to be run once when trees are spawned, to apply the settings found in the manager to the tree(s). And that's it... it isn't supposed to run ApplyAll() on every frame. Only reason you would call ApplyAll() again would be if you spawned more trees, or if you change the settings in the manager and you need to apply those new changes to the trees.

Just to clarify again.. it *can* run applyall() multiple times in a single frame, if it is passing the value of a single tree. This is supposed to allow you to drop a single tree into the game, and it would instantly apply the settings from the manager to that tree, without having to loop through all the trees in the whole scene. So if you were to drop 5 trees into the scene at once, then it *would* call applyall() 5 times, once for each of those trees. This causes the applyall() to apply the settings from the manager to just those trees only. But since you're spawning trees at runtime, and it seems to be messing up so much for you, the hack solution would probably be your easiest solution to fix the problem, just to take full control over when applyall() is run. So again, if you just run it only once, after you spawn a batch of trees, then you shouldn't let it run ever again, until you spawn more trees, or until you change some setting in the manager (if you do that, it's not common) during gameplay and you want to apply those new manager settings to the trees. But if you've already applied the settings from the manager to all your trees in your scene, and you can't think of any particular reason why the settings would need to be applied again, then probably odds are the applyall() function doesn't need to be run anymore and you should block it from being run in that case.

I've implemented the hack, and I have to say it all runs way smoother than I'd have ever hoped for!!! So thank you for making this awesome plugin :) And thanks for the amazing support!!!