Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Immediate Ambient Light Updating To Prevent Flicker On Transition To … #2454

Open
wants to merge 15 commits into
base: master
Choose a base branch
from

Conversation

AverniteDF
Copy link
Contributor

…Exterior

The purpose of this PR is to eliminate the glitchy flicker you sometimes get when exiting a building.

What the world outside looks like for a few frames before lighting gets updated:
transition_to_exterior_first_few_frames

And after:
transition_to_exterior_after

Immediate calls to PlayerAmbientLight.UpdateAmbientLight solves this.

The proposed code calls that method twice: once in the current frame and again on the next. The combination of the two calls completely eliminates all flickering as far as I can tell (I disabled HUD fade in so I could see every frame).

…Exterior

The purpose of this PR is to eliminate the glitchy flicker you sometimes get when exiting a building.

Immediate calls to PlayerAmbientLight.UpdateAmbientLight solves this.
@@ -587,6 +587,8 @@ private IEnumerator RestoreCachedSceneNextFrame(string sceneName)
yield return new WaitForEndOfFrame();
// Restore the scene from cache
stateManager.RestoreCachedScene(sceneName);

PlayerAmbientLight.Instance.UpdateAmbientLight(true); // Immediately update ambient lighting
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think Event/Delegate would be better style here (less coupling).

@petchema
Copy link
Collaborator

petchema commented Nov 1, 2022

I did not notice the 3Hz polling here, that explains some delayed updates...
I wonder if it would be possible to use other mechanisms (events/degates?) to get rid of polling (and its delays vs performance tradeoffs) without introducing coupling, are there best practices in Unity/C# in this domain?

@@ -52,34 +56,49 @@ void Update()
StartCoroutine(ChangeAmbientLight());
}

bool mutex;
Copy link
Collaborator

@petchema petchema Nov 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To ensure that "mutex" changes are immediately seen by all threads, I think it should at very least be declared volatile.
Busy looping is also terrible(1) and doesn't work (it's immediately followed by a race). If you don't want to go the full Mutex class way, use at least a lock() statement

  1. If fact if mutex ever happens to be true at the moment while (mutex) ; is executed, the thread is likely to hang forever: https://learn.microsoft.com/en-us/archive/msdn-magazine/2012/december/csharp-the-csharp-memory-model-in-theory-and-practice "Broken Polling Loop"

@AverniteDF
Copy link
Contributor Author

AverniteDF commented Nov 1, 2022 via email

Got rid of silly mutex business.

Removed ManageAmbientLight coroutine and put calls for periodic ambient light updating in Update method.
@AverniteDF
Copy link
Contributor Author

I've put up a cleaner implementation (no mutex stuff and ManageAmbientLight coroutine has been removed). In current form it still requires the two calls to PlayerAmbientLight.UpdateAmbientLight from other classes, otherwise you get two frames of incorrect lighting after interior->exterior transition.

It is helpful to set FadeBehaviour.allowFade to false when testing this so you can more clearly see what's going on during transition.

@AverniteDF
Copy link
Contributor Author

I think it would be best to call UpdateAmbientLight() in the main thread, as late as possible and immediately before the drawing of each frame.

Are there any suggestions as to where that might be? I tried doing it in LateUpdate() but that didn't seem late enough.

The other option as I see it is to call UpdateAmbientLight() on demand as needed, in which case there'd be no need to call it every frame or periodically.

With the second option, UpdateAmbientLight() would be called after, and whenever, player location or game time is changed.

@petchema
Copy link
Collaborator

petchema commented Nov 2, 2022

The other option as I see it is to call UpdateAmbientLight() on demand as needed, in which case there'd be no need to call it every frame or periodically.

With the second option, UpdateAmbientLight() would be called after, and whenever, player location or game time is changed.

Yes, that's what events/delegates could be used for. It's a publication/subscription mechanism, so code notifying about changes (location, weather,...) would unaware of the PlayerAmbientLight object (and possibly other objects) listening for those notifications; This decreases coupling and is more dynamic than plain method calls.

SaveLoadManager no longer needs to be modified for this PR
@AverniteDF
Copy link
Contributor Author

AverniteDF commented Nov 2, 2022 via email

@AverniteDF
Copy link
Contributor Author

Ah, that does work better. The event handler got rid of the need to modify PlayerEnterExit and also the Instance member I added to PlayerAmbientLight.

@AverniteDF
Copy link
Contributor Author

There's a wrinkle inPlayerEnterExit.BuildingTransitionExteriorLogic()that I'd like to point out.

It has to do with the last two lines of code in that method:

// Update serializable state from scene cache for interior->exterior transition
SaveLoadManager.RestoreCachedScene(world.SceneName);

// Fire event
RaiseOnTransitionExteriorEvent();

The call toRaiseOnTransitionExteriorEvent()is not quite accurate here because the previous line,SaveLoadManager.RestoreCachedScene(), attempts to restore the cached scene on a separate thread.

So other classes are being notified that the transition has completed when in fact it is still pending.

I believe this is the reason why I kept getting a pesky ambient lighting flicker when exiting buildings, and I originally needed a second call toUpdateAmbientLighting()at the bottom ofRestoreCachedSceneNextFrame().

TheRaiseOnTransitionExteriorEvent()call could be passed toRestoreCachedScene()as a post-Action and this would fire the event upon actual transition completion. However, downstream code in the calling thread may be relying on the event being fired as currently implemented.

It would be nice to eliminate theRestoreCachedSceneNextFrame()method, if possible. Find out what needs to be registered and do the scene restoration immediately instead of on a separate thread.

This is required to prevent ambient light flicker on transition to exterior because SunlightManager.Update() is not called per tick while player is indoors.
I also removed the tavern ambient light modifier (it wasn't meant to be in the PR and was included by accident).
To allow PlayerAmbientLight access so it can force update SunightManager to ensure it's in a ready state before calculating outdoors ambient light
@AverniteDF
Copy link
Contributor Author

Okay I found what was causing the pesky flicker. It wasn't the delayed cached scene restoration. It was happening because SunlightManager wasn't in a ready state when CalcDaytimeAmbientLight() was being called.

It seems that SunlightManager.Upate() isn't called per tick when the player is indoors, or at least not reliably. I know that if you load a character who is inside a building the first time you exit the data members of SunlightManager aren't correct until its Update() method gets called.

It's working quite well now.

@Interkarma
Copy link
Owner

Thank you for the PR. I know the flicker you're looking at fixing, and appreciate the suggested fix. I also appreciate the refinement after @petchema's review. Cheers both. :)

@KABoissonneault
Copy link
Collaborator

Is this still relevant? If the original contributor is still around, I'd like for them to revert the changes PlayerEnterExit.cs and SaveLoadManager.cs, since it's just whitespace. Otherwise, I can give this a run and see if I notice a difference

@petchema
Copy link
Collaborator

The original issue should still be around, easiest way to spot it is probably to exit a tavern at night, and notice the exterior is bright for a split second before light abruptly adjusts to dark then ramps up to final correct exterior lighting. Effect varies a bit between tries, depending on when the 3hz light adjustment polling takes place vs. your building exit, but it's there.
With this patch when you exit that same tavern the light is immediately dark and ramps up to final correct exterior lighting, no initial flash.
(Whether it's the best way to solve that issue is another story, cleanly handling transient states is hard.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants