Last active
November 28, 2022 10:53
-
-
Save liamcary/628e5c4833ae0ba18c2c570992aeb993 to your computer and use it in GitHub Desktop.
A script to improve performance of RealtimeTransforms in Rigidbody mode. This is a prototype, I haven't tested it thoroughly for production.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Normal.Realtime; | |
using System; | |
using System.Collections; | |
using System.Reflection; | |
using UnityEngine; | |
namespace Normcore.Realtime | |
{ | |
/// <summary> | |
/// This script is intended to be attached to an object with a RealtimeTransform in Rigidbody mode. We have to use reflection | |
/// to override the behaviour because the relevant normcore methods/classes are private/internal/sealed/etc. | |
/// | |
/// RealtimeTransform's FixedUpdate method when using the Rigidbody strategy performs some operations multiple | |
/// times instead of caching and re-using the results. For one object its a small amount, but when you have dozens | |
/// of objects and multiple fixed updates running in one frame it can add up quickly. | |
/// | |
/// While profiling i saw that I had 83 realtime transforms with rigidbodies and on average 2 fixed updates per frame. | |
/// The default behaviour accesses RealtimeModel isOwnedLocallySelf and ownerIdSelf twice each, which is already 332 calls. | |
/// This was taking .76ms on my high-end development PC. I didn't test on quest, because its already too high. | |
/// | |
/// I added this script to some of the realtime transforms in the scene to roughly compare values in the profiler. | |
/// In a frame with three fixed updates, 30 instances using the original RealtimeTransform took 0.37ms to complete. | |
/// In that same frame, 54 instances with this script added took 0.21ms to complete. It's a small sample size and imprecise | |
/// metrics, but thats roughly 3 or more times faster if you scale to the same quantity. It would likely be faster again if | |
/// this was using normcore methods directly instead of using reflection. | |
/// </summary> | |
[DefaultExecutionOrder(-94)] // One higher than RealtimeTransform | |
public class RealtimeTransformOptimizer : MonoBehaviour | |
{ | |
[SerializeField] RealtimeTransform _realtimeTransform; | |
[SerializeField] RealtimeView _realtimeView; | |
[SerializeField] Rigidbody _rigidbody; | |
bool _isInitialized; | |
int _clientId = -1; | |
bool _isAlreadyOwned; | |
RealtimeTransformModel _realtimeTransformModel; | |
Action _incrementFixedRoomTime; | |
Action<RealtimeTransformModel> _remoteFixedUpdate; | |
static bool _isStaticInitialized; | |
static FieldInfo _strategyField; | |
static PropertyInfo _modelProperty; | |
static MethodInfo _incrementMethod; | |
static MethodInfo _remoteFixedUpdateMethod; | |
void Reset() | |
{ | |
_realtimeTransform = GetComponent<RealtimeTransform>(); | |
_realtimeView = GetComponent<RealtimeView>(); | |
_rigidbody = GetComponent<Rigidbody>(); | |
} | |
void OnEnable() | |
{ | |
if (_realtimeTransform == null) { | |
enabled = false; // Support stripping realtime components at runtime | |
} else { | |
_realtimeTransform.StopAllCoroutines(); | |
} | |
} | |
void Awake() | |
{ | |
_realtimeView.didReplaceAllComponentModels += HandleReplacedAllComponentModels; | |
} | |
void OnDestroy() | |
{ | |
if (_realtimeView != null) { | |
_realtimeView.didReplaceAllComponentModels -= HandleReplacedAllComponentModels; | |
} | |
if (_realtimeTransform != null && _realtimeTransform.realtime != null) { | |
_realtimeTransform.realtime.didConnectToRoom -= HandleConnected; | |
_realtimeTransform.realtime.didDisconnectFromRoom -= HandleDisconnected; | |
} | |
} | |
void FixedUpdate() | |
{ | |
if (!_isInitialized || _clientId == -1) { | |
return; | |
} | |
_incrementFixedRoomTime(); | |
if (_realtimeTransform.ownerIDSelf == _clientId) { | |
if (!_isAlreadyOwned) { | |
_rigidbody.WakeUp(); | |
_isAlreadyOwned = true; | |
} | |
} else { | |
_remoteFixedUpdate(_realtimeTransformModel); | |
_isAlreadyOwned = false; | |
} | |
} | |
void HandleConnected(Normal.Realtime.Realtime realtime) | |
{ | |
_clientId = realtime.clientID; | |
} | |
void HandleDisconnected(Normal.Realtime.Realtime realtime) | |
{ | |
_clientId = -1; | |
} | |
void HandleReplacedAllComponentModels(RealtimeView view) | |
{ | |
object strategyValue; | |
if (!_isStaticInitialized) { | |
_strategyField = _realtimeTransform.GetType().GetField("_strategy", BindingFlags.Instance | BindingFlags.NonPublic); | |
_modelProperty = _realtimeTransform.GetType().GetProperty("model", BindingFlags.Instance | BindingFlags.NonPublic); | |
strategyValue = _strategyField.GetValue(_realtimeTransform); | |
_incrementMethod = strategyValue.GetType().GetMethod("IncrementFixedRoomTime", BindingFlags.Instance | BindingFlags.NonPublic); | |
_remoteFixedUpdateMethod = strategyValue.GetType().GetMethod("RemoteFixedUpdate", BindingFlags.Instance | BindingFlags.NonPublic); | |
_isStaticInitialized = true; | |
} else { | |
strategyValue = _strategyField.GetValue(_realtimeTransform); | |
} | |
_realtimeTransformModel = (RealtimeTransformModel) _modelProperty.GetValue(_realtimeTransform); | |
_incrementFixedRoomTime = (Action) Delegate.CreateDelegate(typeof(Action), strategyValue, _incrementMethod, true); | |
_remoteFixedUpdate = (Action<RealtimeTransformModel>) Delegate.CreateDelegate(typeof(Action<RealtimeTransformModel>), strategyValue, _remoteFixedUpdateMethod, true); | |
_realtimeTransform.realtime.didConnectToRoom += HandleConnected; | |
_realtimeTransform.realtime.didDisconnectFromRoom += HandleDisconnected; | |
_clientId = _realtimeTransform.realtime.clientID; | |
if (_realtimeTransform.ownerIDSelf == _clientId) { | |
_isAlreadyOwned = true; | |
_rigidbody.WakeUp(); | |
} | |
_isInitialized = true; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Updated with a few improvements:
This has been reliable for Realtime prefab instances in my project, but I haven't tested SceneView RealtimeTransforms with Rigidbodies.