First set of active profile change fixes#8050
Conversation
|
/azp run |
|
Azure Pipelines successfully started running 2 pipeline(s). |
|
/azp run |
|
Azure Pipelines successfully started running 2 pipeline(s). |
| using UnityEngine; | ||
| using UnityEngine.TestTools; | ||
|
|
||
| namespace Microsoft.MixedReality.Toolkit.Tests |
There was a problem hiding this comment.
A test that I would like to see, going forward:
- set the hololens 2 profile
- Press a button, make sure it presses via poke
- Set profile to be a hololens 1 profile
- verify that poking does not press a button
- Use head + airtap to press a button, verify that works.
There was a problem hiding this comment.
Definitely! We should have extensive tests for ensuring this works once all of the issues have been uncovered.
There was a problem hiding this comment.
Just to check @julenka you mean a test within this PR right?
There was a problem hiding this comment.
I will see if that scenario can work with just the changes in this PR. It may require additional changes to fully support the requested test.
There was a problem hiding this comment.
Looking at the profiles, there does not appear to be anything in the Default HoloLens1 profile that expressly disables near interaction.
I will update the profile as part of getting the above described test implemented.
|
/azp run |
|
Azure Pipelines successfully started running 2 pipeline(s). |
| } | ||
| catch(InvalidOperationException) | ||
| { | ||
| Debug.Log("Data provider collection was changed during Update."); |
There was a problem hiding this comment.
It feels like try/catching exceptions due to the collection changing instead of more completely managing the lifecycle could lead to unexpected behavior.
For example (but please correct me if I'm wrong here!):
- In update, the profile changes
- This exception is thrown, and (as far as I can tell), the new providers don't get
Updatecalled on them and instead this method bails LateUpdateis called on all (now, the new) data providers
If a data provider does something in LateUpdate to follow up on something that's expected to be done in Update, and I think there's a fairly strong promise that system lifecycle happens in a specific order, the provider could get into a weird state.
There was a problem hiding this comment.
Also it looks like only Update and LateUpdate are wrapped. What if I try to change the profile when a system is enabled / disabled / reset (maybe not a common scenario, but I'm not sure)?
There was a problem hiding this comment.
To follow up on this, I remember we had a conversation a while back about more explicitly handling where / when the profiles are torn down and started up. Something like:
- New profile is set
- Old services are torn down
- All remaining service lifecycle code is cancelled for this frame
- Next frame, new services are spun up
- Normal service lifecycle resumes
There was a problem hiding this comment.
All this to say I really think handling the lifecycle of all this more explicitly and completely might lead to not having to spot-fix specific issues one at a time (for example, if we wait until the next frame to try to recreate the UIRaycastCamera, would we have to make the DestroyImmediate race condition change? or does the frame boundary complete the clean-up and leave the scene fresh for the new services?).
There was a problem hiding this comment.
if we wait until the next frame to try to recreate the UIRaycastCamera, would we have to make the DestroyImmediate race condition change?
that appears to be an issue based on Unity indicating the object exists after Destroy and before cleanup. MRTK is checking the next frame and, seeing that the object exisst, does not recreate it.
There was a problem hiding this comment.
Super appreciate the feedback here!
There was a problem hiding this comment.
All remaining service lifecycle code is cancelled for this frame
I'll see what i can do to ensure complete update/cleanup between changes. It may introduce an extra frame, or so, between shutdown and restart.
There was a problem hiding this comment.
What if I try to change the profile when a system is enabled / disabled / reset.
The [Late]Update try/catch is only for data providers. Services are covered by the handling in ExecuteOnAllServices* methods, which are called in most (very few exceptions) situations.
An earlier incarnation of this change had exception handing for all data provider methods (enable, disable, etc) and that was originally deemed unneccessary.
Happy to revisit!
|
/azp run |
|
Azure Pipelines successfully started running 2 pipeline(s). |
|
Azure Pipelines successfully started running 2 pipeline(s). |
|
/azp run |
|
Azure Pipelines successfully started running 2 pipeline(s). |
|
/azp run |
|
Azure Pipelines successfully started running 2 pipeline(s). |
| /// <inheritdoc/> | ||
| public override void Initialize() | ||
| { | ||
| ReadProfile(); |
There was a problem hiding this comment.
It feels like this order is a bit off - ReadProfile needs BoundaryProfile (i.e. if it's null, it will crash right?)
Or is the check of BoundaryProfile == null on line 59 not needed?
There was a problem hiding this comment.
Let me double check. I believe all of the systems that have this method may behave the same.
| } | ||
| else | ||
| { | ||
| ResetConfiguration(value); |
There was a problem hiding this comment.
If I happened to pass the same profile in that matches the current one, we'd still want to reset in that case right?
Co-authored-by: Will <wiwei@microsoft.com>
| } | ||
| else | ||
| { | ||
| ResetConfiguration(value); |
There was a problem hiding this comment.
Do you know why in the else case were and synchronously resetting things within this call, but for the new thing we do that async on the next update?
It does feel a bit weird that we have a divergence of behavior between these two (it feels like we should be consistent with when we chose to tear things down and restart things).
There was a problem hiding this comment.
Suggest adding detailed comment describing the scenarios.
| if (IsActiveInstance) | ||
| { | ||
| LateUpdateAllServices(); | ||
| if (newProfile != null) |
There was a problem hiding this comment.
Can you add some comments for why we're doing RemoveCurrentProfile here and then InitializeNewProfile in Update?
It's obvious to folks working on this right now but the order actually is worth documenting.
| /// <remarks> | ||
| /// When setting the ActiveProfile, all currently running services will be destroyed and those specified in | ||
| /// the new profile will be instantiated and initialized. A noticable application hesitation may occur | ||
| /// during this process. |
There was a problem hiding this comment.
There's going to be time between LateUpdate and Update where MRTK is destroyed across calls to update the ActiveProfile - it woudl be good to capture that in the remarks.
Also would be good to mention that any scripts that have higher priority than the MRTK object will run before this has a chance to set things, so to be careful with accessing MRTK state if you happen to have such a thing.
|
|
||
| private void Update() | ||
| { | ||
| if ((CoreServices.InputSystem == null) || |
There was a problem hiding this comment.
Can you add a comment on this (i.e. designed to avoid running in cases where the MRTK instance is reinitializing)
|
|
||
| base.Initialize(); | ||
|
|
||
| if (!Application.isPlaying || !XRDevice.isPresent) { return; } |
There was a problem hiding this comment.
Can you explain a bit why we swapped the order here?
There was a problem hiding this comment.
I will address in a separate pr
There was a problem hiding this comment.
initialization needs to happen right away, otherwise profiles do not get read correctly.
There was a problem hiding this comment.
going to open a separate PR for the boundary system issues
| /// <inheritdoc /> | ||
| private void OnDestroy() | ||
| { | ||
| if (GazeCursor != null && !GazeCursor.Equals(null)) |
There was a problem hiding this comment.
Can you leave a comment about why we both check against null and also against null?
| execute(services[i]); | ||
| } | ||
|
|
||
| } | ||
| else | ||
| { | ||
| ResetConfiguration(value); |
There was a problem hiding this comment.
Suggest adding detailed comment describing the scenarios.
Split boundary system fixes from #8050
This change is the first in a series of changes that will addresses the issues encountered when changing the active mixed reality toolkit configuration profile while the application is running.
The actual number of future PRs has not yet been determined. Test collateral and documentation will be included in the series as fixes enable them.
Change from Destroy to DestroyImmediate as the service locator expects the UIRaycastCamera to me immediately removed in order to correctly re-create it.This change handles the InvalidOperationException that is thrown when a collection is changed during enumeration, This prevents the code from incorrectly aborting.This change replaces for with foreach and handles the InvalidOperationException that is thrown the services collection is changed during enumeration. This prevents the code from incorrectly aborting.In this PR, the behavior of runtime profile change is modified to first take the request, then destroy current profile services after all LateUpdate()s of current profile services have been called, and finally initialize the new services associated with the new profile in the next frame before any Update() for a service is called. This solves the problem of potentially performing the profile change in the middle of an Update or LateUpdate of a service which causes a variety of problems. Delaying the initialization of new services to the next frame also helps to resolve the problems related to Destroy().
Testing