You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

13696 lines
474 KiB
C#

3 weeks ago
#if !FUSION_DEV
#region Assets/Photon/Fusion/Editor/AssemblyAttributes/FusionEditorAssemblyAttributes.Common.cs
// merged EditorAssemblyAttributes
#region RegisterEditorLoader.cs
// the default edit-mode loader
[assembly:Fusion.Editor.FusionGlobalScriptableObjectEditorAttribute(typeof(Fusion.FusionGlobalScriptableObject), AllowEditMode = true, Order = int.MaxValue)]
#endregion
#endregion
#region Assets/Photon/Fusion/Editor/AssetObjectEditor.cs
namespace Fusion.Editor {
using UnityEditor;
[CustomEditor(typeof(AssetObject), true)]
public class AssetObjectEditor : UnityEditor.Editor {
public override void OnInspectorGUI() {
base.OnInspectorGUI();
}
}
}
#endregion
#region Assets/Photon/Fusion/Editor/BehaviourEditor.cs
namespace Fusion.Editor {
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
[CustomEditor(typeof(Fusion.Behaviour), true)]
[CanEditMultipleObjects]
public partial class BehaviourEditor : FusionEditor {
}
}
#endregion
#region Assets/Photon/Fusion/Editor/ChildLookupEditor.cs
// removed July 12 2021
#endregion
#region Assets/Photon/Fusion/Editor/CustomTypes/FixedBufferPropertyAttributeDrawer.cs
namespace Fusion.Editor {
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Fusion.Internal;
using Unity.Collections.LowLevel.Unsafe;
using UnityEditor;
using UnityEditor.Compilation;
using UnityEngine;
[CustomPropertyDrawer(typeof(FixedBufferPropertyAttribute))]
unsafe class FixedBufferPropertyAttributeDrawer : PropertyDrawerWithErrorHandling {
public const string FixedBufferFieldName = "Data";
public const string WrapperSurrogateDataPath = "Surrogate.Data";
private const float SpacingSubLabel = 2;
private static readonly int _multiFieldPrefixId = "MultiFieldPrefixId".GetHashCode();
private static int[] _buffer = Array.Empty<int>();
private static SurrogatePool _pool = new SurrogatePool();
private static GUIContent[] _vectorProperties = new[] {
new GUIContent("X"),
new GUIContent("Y"),
new GUIContent("Z"),
new GUIContent("W"),
};
private Dictionary<string, bool> _needsSurrogateCache = new Dictionary<string, bool>();
private Dictionary<Type, UnitySurrogateBase> _optimisedReaderWriters = new Dictionary<Type, UnitySurrogateBase>();
private Type ActualFieldType => ((FixedBufferPropertyAttribute)attribute).Type;
private int Capacity => ((FixedBufferPropertyAttribute)attribute).Capacity;
private Type SurrogateType => ((FixedBufferPropertyAttribute)attribute).SurrogateType;
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
if (SurrogateType == null) {
return EditorGUIUtility.singleLineHeight;
}
if (NeedsSurrogate(property)) {
var fixedBufferProperty = GetFixedBufferProperty(property);
var firstElement = fixedBufferProperty.GetFixedBufferElementAtIndex(0);
if (!firstElement.IsArrayElement()) {
// it seems that with multiple seclection child elements are not accessible
Debug.Assert(property.serializedObject.targetObjects.Length > 1);
return EditorGUIUtility.singleLineHeight;
}
var wrapper = _pool.Acquire(fieldInfo, Capacity, property, SurrogateType);
try {
return EditorGUI.GetPropertyHeight(wrapper.Property);
} catch (Exception ex) {
FusionEditorLog.ErrorInspector($"Error in GetPropertyHeight for {property.propertyPath}: {ex}");
return EditorGUIUtility.singleLineHeight;
}
} else {
int count = 1;
if (!EditorGUIUtility.wideMode) {
count++;
}
return count * (EditorGUIUtility.singleLineHeight) + (count - 1) * EditorGUIUtility.standardVerticalSpacing;
}
}
protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) {
if (NeedsSurrogate(property)) {
if (SurrogateType == null) {
this.SetInfo($"[Networked] properties of type {ActualFieldType.FullName} in structs are not yet supported");
EditorGUI.LabelField(position, label, GUIContent.none);
} else {
int capacity = Capacity;
var fixedBufferProperty = GetFixedBufferProperty(property);
Array.Resize(ref _buffer, Math.Max(_buffer.Length, fixedBufferProperty.fixedBufferSize));
var firstElement = fixedBufferProperty.GetFixedBufferElementAtIndex(0);
if (!firstElement.IsArrayElement()) {
Debug.Assert(property.serializedObject.targetObjects.Length > 1);
SetInfo($"Type does not support multi-edit");
EditorGUI.LabelField(position, label);
} else {
var wrapper = _pool.Acquire(fieldInfo, Capacity, property, SurrogateType);
{
bool surrogateOutdated = false;
var targetObjects = property.serializedObject.targetObjects;
if (targetObjects.Length > 1) {
for (int i = 0; i < targetObjects.Length; ++i) {
using (var so = new SerializedObject(targetObjects[i])) {
using (var sp = so.FindPropertyOrThrow($"{property.propertyPath}.Data")) {
if (UpdateSurrogateFromFixedBuffer(sp, wrapper.Surrogates[i], false, _pool.Flush)) {
surrogateOutdated = true;
}
}
}
}
if (surrogateOutdated) {
// it seems that a mere Update won't do here
wrapper.Property = new SerializedObject(wrapper.Wrappers).FindPropertyOrThrow(WrapperSurrogateDataPath);
}
} else {
// an optimised path, no alloc needed
Debug.Assert(wrapper.Surrogates.Length == 1);
if (UpdateSurrogateFromFixedBuffer(fixedBufferProperty, wrapper.Surrogates[0], false, _pool.Flush)) {
wrapper.Property.serializedObject.Update();
}
}
}
// check if there has been any chagnes
EditorGUI.BeginChangeCheck();
EditorGUI.BeginProperty(position, label, property);
try {
EditorGUI.PropertyField(position, wrapper.Property, label, true);
} catch (Exception ex) {
FusionEditorLog.ErrorInspector($"Error in OnGUIInternal for {property.propertyPath}: {ex}");
}
EditorGUI.EndProperty();
if (EditorGUI.EndChangeCheck()) {
wrapper.Property.serializedObject.ApplyModifiedProperties();
// when not having multiple different values, just write the whole thing
if (UpdateSurrogateFromFixedBuffer(fixedBufferProperty, wrapper.Surrogates[0], true, !fixedBufferProperty.hasMultipleDifferentValues)) {
fixedBufferProperty.serializedObject.ApplyModifiedProperties();
// refresh?
wrapper.Property.serializedObject.Update();
}
}
}
}
} else {
if (!_optimisedReaderWriters.TryGetValue(SurrogateType, out var surrogate)) {
surrogate = (UnitySurrogateBase)Activator.CreateInstance(SurrogateType);
_optimisedReaderWriters.Add(SurrogateType, surrogate);
}
if (ActualFieldType == typeof(float)) {
DoFloatField(position, property, label, (IUnityValueSurrogate<float>)surrogate);
} else if (ActualFieldType == typeof(Vector2)) {
DoFloatVectorProperty(position, property, label, 2, (IUnityValueSurrogate<Vector2>)surrogate);
} else if (ActualFieldType == typeof(Vector3)) {
DoFloatVectorProperty(position, property, label, 3, (IUnityValueSurrogate<Vector3>)surrogate);
} else if (ActualFieldType == typeof(Vector4)) {
DoFloatVectorProperty(position, property, label, 4, (IUnityValueSurrogate<Vector4>)surrogate);
}
}
}
private void DoFloatField(Rect position, SerializedProperty property, GUIContent label, IUnityValueSurrogate<float> surrogate) {
var fixedBuffer = GetFixedBufferProperty(property);
Debug.Assert(1 == fixedBuffer.fixedBufferSize);
var valueProp = fixedBuffer.GetFixedBufferElementAtIndex(0);
int value = valueProp.intValue;
surrogate.Read(&value, 1);
EditorGUI.BeginProperty(position, label, property);
EditorGUI.BeginChangeCheck();
surrogate.DataProperty = EditorGUI.FloatField(position, label, surrogate.DataProperty);
if (EditorGUI.EndChangeCheck()) {
surrogate.Write(&value, 1);
valueProp.intValue = value;
property.serializedObject.ApplyModifiedProperties();
}
EditorGUI.EndProperty();
}
private unsafe void DoFloatVectorProperty<T>(Rect position, SerializedProperty property, GUIContent label, int count, IUnityValueSurrogate<T> readerWriter) where T : unmanaged {
EditorGUI.BeginProperty(position, label, property);
try {
var fixedBuffer = GetFixedBufferProperty(property);
Debug.Assert(count == fixedBuffer.fixedBufferSize);
int* raw = stackalloc int[count];
for (int i = 0; i < count; ++i) {
raw[i] = fixedBuffer.GetFixedBufferElementAtIndex(i).intValue;
}
readerWriter.Read(raw, 1);
int changed = 0;
var data = readerWriter.DataProperty;
float* pdata = (float*)&data;
int id = GUIUtility.GetControlID(_multiFieldPrefixId, FocusType.Keyboard, position);
position = UnityInternal.EditorGUI.MultiFieldPrefixLabel(position, id, label, count);
if (position.width > 1) {
using (new EditorGUI.IndentLevelScope(-EditorGUI.indentLevel)) {
float w = (position.width - (count - 1) * SpacingSubLabel) / count;
var nestedPosition = new Rect(position) { width = w };
for (int i = 0; i < count; ++i) {
var propLabel = _vectorProperties[i];
float prefixWidth = EditorStyles.label.CalcSize(propLabel).x;
using (new FusionEditorGUI.LabelWidthScope(prefixWidth)) {
EditorGUI.BeginChangeCheck();
var newValue = propLabel == null ? EditorGUI.FloatField(nestedPosition, pdata[i]) : EditorGUI.FloatField(nestedPosition, propLabel, pdata[i]);
if (EditorGUI.EndChangeCheck()) {
changed |= (1 << i);
pdata[i] = newValue;
}
}
nestedPosition.x += w + SpacingSubLabel;
}
}
}
if (changed != 0) {
readerWriter.DataProperty = data;
readerWriter.Write(raw, 1);
for (int i = 0; i < count; ++i) {
if ((changed & (1 << i)) != 0) {
fixedBuffer.GetFixedBufferElementAtIndex(i).intValue = raw[i];
}
}
property.serializedObject.ApplyModifiedProperties();
}
} finally {
EditorGUI.EndProperty();
}
}
private SerializedProperty GetFixedBufferProperty(SerializedProperty prop) {
var result = prop.FindPropertyRelativeOrThrow(FixedBufferFieldName);
Debug.Assert(result.isFixedBuffer);
return result;
}
private bool NeedsSurrogate(SerializedProperty property) {
if (_needsSurrogateCache.TryGetValue(property.propertyPath, out var result)) {
return result;
}
result = true;
if (ActualFieldType == typeof(float) || ActualFieldType == typeof(Vector2) || ActualFieldType == typeof(Vector3) || ActualFieldType == typeof(Vector4)) {
var attributes = UnityInternal.ScriptAttributeUtility.GetFieldAttributes(fieldInfo);
if (attributes == null || attributes.Count == 0) {
// fast drawers do not support any additional attributes
result = false;
}
}
_needsSurrogateCache.Add(property.propertyPath, result);
return result;
}
private bool UpdateSurrogateFromFixedBuffer(SerializedProperty sp, UnitySurrogateBase surrogate, bool write, bool force) {
int count = sp.fixedBufferSize;
Array.Resize(ref _buffer, Math.Max(_buffer.Length, count));
// need to get to the first property... `GetFixedBufferElementAtIndex` is slow and allocs
var element = sp.Copy();
element.Next(true); // .Array
element.Next(true); // .Array.size
element.Next(true); // .Array.data[0]
fixed (int* p = _buffer) {
UnsafeUtility.MemClear(p, count * sizeof(int));
try {
surrogate.Write(p, Capacity);
} catch (Exception ex) {
SetError($"Failed writing: {ex}");
}
int i = 0;
if (!force) {
// find first difference
for (; i < count; ++i, element.Next(true)) {
Debug.Assert(element.propertyType == SerializedPropertyType.Integer);
if (element.intValue != p[i]) {
break;
}
}
}
if (i < count) {
// update data
if (write) {
for (; i < count; ++i, element.Next(true)) {
element.intValue = p[i];
}
} else {
for (; i < count; ++i, element.Next(true)) {
p[i] = element.intValue;
}
}
// update surrogate
surrogate.Read(p, Capacity);
return true;
} else {
return false;
}
}
}
private class SurrogatePool {
private const int MaxTTL = 10;
private FieldInfo _surrogateField = typeof(FusionUnitySurrogateBaseWrapper).GetField(nameof(FusionUnitySurrogateBaseWrapper.Surrogate));
private Dictionary<(Type, string, int), PropertyEntry> _used = new Dictionary<(Type, string, int), PropertyEntry>();
private Dictionary<Type, Stack<FusionUnitySurrogateBaseWrapper>> _wrappersPool = new Dictionary<Type, Stack<FusionUnitySurrogateBaseWrapper>>();
public SurrogatePool() {
Undo.undoRedoPerformed += () => Flush = true;
EditorApplication.update += () => {
Flush = false;
if (!WasUsed) {
return;
}
WasUsed = false;
var keysToRemove = new List<(Type, string, int)>();
foreach (var kv in _used) {
var entry = kv.Value;
if (--entry.TTL < 0) {
// return to pool
keysToRemove.Add(kv.Key);
foreach (var wrapper in entry.Wrappers) {
_wrappersPool[wrapper.Surrogate.GetType()].Push(wrapper);
}
}
}
// make all the wrappers available again
foreach (var key in keysToRemove) {
FusionEditorLog.TraceInspector($"Cleaning up {key}");
_used.Remove(key);
}
};
CompilationPipeline.compilationFinished += obj => {
// destroy SO's, we don't want them to hold on to the surrogates
var wrappers = _wrappersPool.Values.SelectMany(x => x)
.Concat(_used.Values.SelectMany(x => x.Wrappers));
foreach (var wrapper in wrappers) {
UnityEngine.Object.DestroyImmediate(wrapper);
}
};
}
public bool Flush { get; private set; }
public bool WasUsed { get; private set; }
public PropertyEntry Acquire(FieldInfo field, int capacity, SerializedProperty property, Type type) {
WasUsed = true;
bool hadNulls = false;
var key = (type, property.propertyPath, property.serializedObject.targetObjects.Length);
if (_used.TryGetValue(key, out var entry)) {
var countValid = entry.Wrappers.Count(x => x);
if (countValid != entry.Wrappers.Length) {
// something destroyed wrappers
Debug.Assert(countValid == 0);
_used.Remove(key);
hadNulls = true;
} else {
entry.TTL = MaxTTL;
return entry;
}
}
// acquire new entry
var wrappers = new FusionUnitySurrogateBaseWrapper[key.Item3];
if (!_wrappersPool.TryGetValue(type, out var pool)) {
pool = new Stack<FusionUnitySurrogateBaseWrapper>();
_wrappersPool.Add(type, pool);
}
for (int i = 0; i < wrappers.Length; ++i) {
// pop destroyed ones
while (pool.Count > 0 && !pool.Peek()) {
pool.Pop();
hadNulls = true;
}
if (pool.Count > 0) {
wrappers[i] = pool.Pop();
} else {
FusionEditorLog.TraceInspector($"Allocating surrogate {type}");
wrappers[i] = ScriptableObject.CreateInstance<FusionUnitySurrogateBaseWrapper>();
}
if (wrappers[i].SurrogateType != type) {
FusionEditorLog.TraceInspector($"Replacing type {wrappers[i].Surrogate?.GetType()} with {type}");
wrappers[i].Surrogate = (UnitySurrogateBase)Activator.CreateInstance(type);
wrappers[i].Surrogate.Init(capacity);
wrappers[i].SurrogateType = type;
}
}
FusionEditorLog.TraceInspector($"Created entry for {property.propertyPath}");
entry = new PropertyEntry() {
Property = new SerializedObject(wrappers).FindPropertyOrThrow(WrapperSurrogateDataPath),
Surrogates = wrappers.Select(x => x.Surrogate).ToArray(),
TTL = MaxTTL,
Wrappers = wrappers
};
_used.Add(key, entry);
if (hadNulls) {
GUIUtility.ExitGUI();
}
return entry;
}
public class PropertyEntry {
public SerializedProperty Property;
public UnitySurrogateBase[] Surrogates;
public int TTL;
public FusionUnitySurrogateBaseWrapper[] Wrappers;
}
}
}
}
#endregion
#region Assets/Photon/Fusion/Editor/CustomTypes/INetworkPrefabSourceDrawer.cs
namespace Fusion.Editor {
using System;
using UnityEditor;
using UnityEngine;
[CustomPropertyDrawer(typeof(INetworkPrefabSource), true)]
class INetworkPrefabSourceDrawer : PropertyDrawerWithErrorHandling {
const int ThumbnailWidth = 20;
protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) {
using (new FusionEditorGUI.PropertyScopeWithPrefixLabel(position, label, property, out position)) {
EditorGUI.BeginChangeCheck();
var source = property.managedReferenceValue as INetworkPrefabSource;
position = DrawThumbnailPrefix(position, source);
source = DrawSourceObjectPicker(position, GUIContent.none, source);
if (EditorGUI.EndChangeCheck()) {
// see how it can be loaded
property.managedReferenceValue = source;
property.serializedObject.ApplyModifiedProperties();
}
}
}
public static Rect DrawThumbnailPrefix(Rect position, INetworkPrefabSource source) {
if (source == null) {
return position;
}
var pos = position;
pos.width = ThumbnailWidth;
FusionEditorGUI.DrawTypeThumbnail(pos, source.GetType(), "NetworkPrefabSource", source.Description);
position.xMin += ThumbnailWidth;
return position;
}
public static void DrawThumbnail(Rect position, INetworkPrefabSource source) {
if (source == null) {
return;
}
var pos = position;
pos.x += (pos.width - ThumbnailWidth) / 2;
pos.width = ThumbnailWidth;
FusionEditorGUI.DrawTypeThumbnail(pos, source.GetType(), "NetworkPrefabSource", source.Description);
}
public static INetworkPrefabSource DrawSourceObjectPicker(Rect position, GUIContent label, INetworkPrefabSource source) {
NetworkProjectConfigUtilities.TryGetPrefabEditorInstance(source?.AssetGuid ?? default, out var target);
EditorGUI.BeginChangeCheck();
target = NetworkPrefabRefDrawer.DrawNetworkPrefabPicker(position, label, target);
if (EditorGUI.EndChangeCheck()) {
if (target) {
var factory = new NetworkAssetSourceFactory();
return factory.TryCreatePrefabSource(new NetworkAssetSourceFactoryContext(target));
} else {
return null;
}
} else {
return source;
}
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
return EditorGUIUtility.singleLineHeight;
}
}
}
#endregion
#region Assets/Photon/Fusion/Editor/CustomTypes/NetworkBoolDrawer.cs
namespace Fusion.Editor {
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEditor;
using UnityEngine;
[CustomPropertyDrawer(typeof(NetworkBool))]
public class NetworkBoolDrawer : PropertyDrawer {
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
using (new FusionEditorGUI.PropertyScope(position, label, property)) {
var valueProperty = property.FindPropertyRelativeOrThrow("_value");
EditorGUI.BeginChangeCheck();
bool isChecked = EditorGUI.Toggle(position, label, valueProperty.intValue > 0);
if (EditorGUI.EndChangeCheck()) {
valueProperty.intValue = isChecked ? 1 : 0;
valueProperty.serializedObject.ApplyModifiedProperties();
}
}
}
}
}
#endregion
#region Assets/Photon/Fusion/Editor/CustomTypes/NetworkObjectGuidDrawer.cs
namespace Fusion.Editor {
using UnityEditor;
using UnityEngine;
[CustomPropertyDrawer(typeof(NetworkObjectGuid))]
[FusionPropertyDrawerMeta(HasFoldout = false)]
class NetworkObjectGuidDrawer : PropertyDrawerWithErrorHandling {
protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) {
var guid = GetValue(property);
using (new FusionEditorGUI.PropertyScopeWithPrefixLabel(position, label, property, out position)) {
if (!GUI.enabled) {
GUI.enabled = true;
EditorGUI.SelectableLabel(position, $"{(System.Guid)guid}");
GUI.enabled = false;
} else {
EditorGUI.BeginChangeCheck();
var text = EditorGUI.TextField(position, ((System.Guid)guid).ToString());
ClearErrorIfLostFocus();
if (EditorGUI.EndChangeCheck()) {
if (NetworkObjectGuid.TryParse(text, out guid)) {
SetValue(property, guid);
property.serializedObject.ApplyModifiedProperties();
} else {
SetError($"Unable to parse {text}");
}
}
}
}
}
public static unsafe NetworkObjectGuid GetValue(SerializedProperty property) {
var guid = new NetworkObjectGuid();
var prop = property.FindPropertyRelativeOrThrow(nameof(NetworkObjectGuid.RawGuidValue));
guid.RawGuidValue[0] = prop.GetFixedBufferElementAtIndex(0).longValue;
guid.RawGuidValue[1] = prop.GetFixedBufferElementAtIndex(1).longValue;
return guid;
}
public static unsafe void SetValue(SerializedProperty property, NetworkObjectGuid guid) {
var prop = property.FindPropertyRelativeOrThrow(nameof(NetworkObjectGuid.RawGuidValue));
prop.GetFixedBufferElementAtIndex(0).longValue = guid.RawGuidValue[0];
prop.GetFixedBufferElementAtIndex(1).longValue = guid.RawGuidValue[1];
}
}
}
#endregion
#region Assets/Photon/Fusion/Editor/CustomTypes/NetworkPrefabAttributeDrawer.cs
namespace Fusion.Editor {
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEditor;
using UnityEngine;
[CustomPropertyDrawer(typeof(NetworkPrefabAttribute))]
[FusionPropertyDrawerMeta(HasFoldout = false)]
class NetworkPrefabAttributeDrawer : PropertyDrawerWithErrorHandling {
protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) {
var leafType = fieldInfo.FieldType.GetUnityLeafType();
if (leafType != typeof(GameObject) && leafType != typeof(NetworkObject) && !leafType.IsSubclassOf(typeof(NetworkObject))) {
SetError($"{nameof(NetworkPrefabAttribute)} only works for {typeof(GameObject)} and {typeof(NetworkObject)} fields");
return;
}
using (new FusionEditorGUI.PropertyScopeWithPrefixLabel(position, label, property, out position)) {
GameObject prefab;
if (leafType == typeof(GameObject)) {
prefab = (GameObject)property.objectReferenceValue;
} else {
var component = (NetworkObject)property.objectReferenceValue;
prefab = component != null ? component.gameObject : null;
}
EditorGUI.BeginChangeCheck();
prefab = (GameObject)EditorGUI.ObjectField(position, prefab, typeof(GameObject), false);
// ensure the results are filtered
if (UnityInternal.ObjectSelector.isVisible) {
var selector = UnityInternal.ObjectSelector.get;
if (UnityInternal.EditorGUIUtility.LastControlID == selector.objectSelectorID) {
var filter = selector.searchFilter;
if (!filter.Contains(NetworkProjectConfigImporter.FusionPrefabTagSearchTerm)) {
if (string.IsNullOrEmpty(filter)) {
filter = NetworkProjectConfigImporter.FusionPrefabTagSearchTerm;
} else {
filter = NetworkProjectConfigImporter.FusionPrefabTagSearchTerm + " " + filter;
}
selector.searchFilter = filter;
}
}
}
if (EditorGUI.EndChangeCheck()) {
UnityEngine.Object result;
if (!prefab) {
result = null;
} else {
if (leafType == typeof(GameObject)) {
result = prefab;
} else {
result = prefab.GetComponent(leafType);
if (!result) {
SetError($"Prefab {prefab} does not have a {leafType} component");
return;
}
}
}
property.objectReferenceValue = prefab;
property.serializedObject.ApplyModifiedProperties();
}
if (prefab) {
var no = prefab.GetComponent<NetworkObject>();
if (!no) {
SetError($"Prefab {prefab} does not have a {nameof(NetworkObject)} component");
}
if (!AssetDatabaseUtils.HasLabel(prefab, NetworkProjectConfigImporter.FusionPrefabTag)) {
SetError($"Prefab {prefab} is not tagged as a Fusion prefab. Try reimporting.");
}
}
}
}
}
}
#endregion
#region Assets/Photon/Fusion/Editor/CustomTypes/NetworkPrefabRefDrawer.cs
namespace Fusion.Editor {
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEditor;
using UnityEngine;
[CustomPropertyDrawer(typeof(NetworkPrefabRef))]
[FusionPropertyDrawerMeta(HasFoldout = false)]
class NetworkPrefabRefDrawer : PropertyDrawerWithErrorHandling {
protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) {
var prefabRef = NetworkObjectGuidDrawer.GetValue(property);
using (new FusionEditorGUI.PropertyScopeWithPrefixLabel(position, label, property, out position)) {
NetworkObject prefab = null;
if (prefabRef.IsValid && !NetworkProjectConfigUtilities.TryGetPrefabEditorInstance(prefabRef, out prefab)) {
SetError($"Prefab with guid {prefabRef} not found.");
}
EditorGUI.BeginChangeCheck();
prefab = DrawNetworkPrefabPicker(position, GUIContent.none, prefab);
if (EditorGUI.EndChangeCheck()) {
if (prefab) {
prefabRef = NetworkObjectEditor.GetPrefabGuid(prefab);
} else {
prefabRef = default;
}
NetworkObjectGuidDrawer.SetValue(property, prefabRef);
property.serializedObject.ApplyModifiedProperties();
}
SetInfo($"{prefabRef}");
if (prefab) {
var expectedPrefabRef = NetworkObjectEditor.GetPrefabGuid(prefab);
if (!prefabRef.Equals(expectedPrefabRef)) {
SetError($"Resolved {prefab} has a different guid ({expectedPrefabRef}) than expected ({prefabRef}). " +
$"This can happen if prefabs are incorrectly resolved, e.g. when there are multiple resources of the same name.");
} else if (!expectedPrefabRef.IsValid) {
SetError($"Prefab {prefab} needs to be reimported.");
} else if (!AssetDatabaseUtils.HasLabel(prefab, NetworkProjectConfigImporter.FusionPrefabTag)) {
SetError($"Prefab {prefab} is not tagged as a Fusion prefab. Try reimporting.");
} else {
// ClearError();
}
}
}
}
public static NetworkObject DrawNetworkPrefabPicker(Rect position, GUIContent label, NetworkObject prefab) {
var prefabGo = (GameObject)EditorGUI.ObjectField(position, label, prefab ? prefab.gameObject : null, typeof(GameObject), false);
// ensure the results are filtered
if (UnityInternal.ObjectSelector.isVisible) {
var selector = UnityInternal.ObjectSelector.get;
if (UnityInternal.EditorGUIUtility.LastControlID == selector.objectSelectorID) {
var filter = selector.searchFilter;
if (!filter.Contains(NetworkProjectConfigImporter.FusionPrefabTagSearchTerm)) {
if (string.IsNullOrEmpty(filter)) {
filter = NetworkProjectConfigImporter.FusionPrefabTagSearchTerm;
} else {
filter = NetworkProjectConfigImporter.FusionPrefabTagSearchTerm + " " + filter;
}
selector.searchFilter = filter;
}
}
}
if (prefabGo) {
return prefabGo.GetComponent<NetworkObject>();
} else {
return null;
}
}
}
}
#endregion
#region Assets/Photon/Fusion/Editor/CustomTypes/NetworkStringDrawer.cs
namespace Fusion.Editor {
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using UnityEditor;
using UnityEngine;
[CustomPropertyDrawer(typeof(NetworkString<>))]
[FusionPropertyDrawerMeta(HasFoldout = false)]
class NetworkStringDrawer : PropertyDrawerWithErrorHandling {
private string _str = "";
private Action<int[], int> _write;
private Action<int[], int> _read;
private int _expectedLength;
public NetworkStringDrawer() {
_write = (buffer, count) => {
unsafe {
fixed (int* p = buffer) {
_str = new string((sbyte*)p, 0, Mathf.Clamp(_expectedLength, 0, count) * 4, Encoding.UTF32);
}
}
};
_read = (buffer, count) => {
unsafe {
fixed (int* p = buffer) {
var charCount = UTF32Tools.Convert(_str, (uint*)p, count).CharacterCount;
if (charCount < _str.Length) {
_str = _str.Substring(0, charCount);
}
}
}
};
}
protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) {
var length = property.FindPropertyRelativeOrThrow(nameof(NetworkString<_2>._length));
var data = property.FindPropertyRelativeOrThrow($"{nameof(NetworkString<_2>._data)}.Data");
_expectedLength = length.intValue;
data.UpdateFixedBuffer(_read, _write, false);
EditorGUI.BeginChangeCheck();
using (new FusionEditorGUI.ShowMixedValueScope(data.hasMultipleDifferentValues)) {
_str = EditorGUI.TextField(position, label, _str);
}
if (EditorGUI.EndChangeCheck()) {
_expectedLength = _str.Length;
if (data.UpdateFixedBuffer(_read, _write, true, data.hasMultipleDifferentValues)) {
length.intValue = Encoding.UTF32.GetByteCount(_str) / 4;
data.serializedObject.ApplyModifiedProperties();
}
}
}
}
}
#endregion
#region Assets/Photon/Fusion/Editor/CustomTypes/NormalizedRectAttributeDrawer.cs
namespace Fusion.Editor {
using System;
using UnityEditor;
using UnityEngine;
#if UNITY_EDITOR
[CustomPropertyDrawer(typeof(NormalizedRectAttribute))]
public class NormalizedRectAttributeDrawer : PropertyDrawer {
bool isDragNewRect;
bool isDragXMin, isDragXMax, isDragYMin, isDragYMax, isDragAll;
MouseCursor lockCursorStyle;
Vector2 mouseDownStart;
static GUIStyle _compactLabelStyle;
static GUIStyle _compactValueStyle;
const float EXPANDED_HEIGHT = 140;
const float COLLAPSE_HEIGHT = 48;
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
if (property.propertyType == SerializedPropertyType.Rect) {
return property.isExpanded ? EXPANDED_HEIGHT : COLLAPSE_HEIGHT;
} else {
return base.GetPropertyHeight(property, label);
}
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
EditorGUI.BeginProperty(position, label, property);
bool hasChanged = false;
EditorGUI.LabelField(new Rect(position) { height = 17 }, label);
var value = property.rectValue;
if (property.propertyType == SerializedPropertyType.Rect) {
var dragarea = new Rect(position) {
yMin = position.yMin + 16 + 3,
yMax = position.yMax - 2,
//xMin = position.xMin + 16,
//xMax = position.xMax - 4
};
// lower foldout box
GUI.Box(dragarea, GUIContent.none, EditorStyles.helpBox);
property.isExpanded = GUI.Toggle(new Rect(position) { xMin = dragarea.xMin + 2, yMin = dragarea.yMin + 2, width = 12, height = 16 }, property.isExpanded, GUIContent.none, EditorStyles.foldout);
bool isExpanded = property.isExpanded;
float border = isExpanded ? 4 : 2;
dragarea.xMin += 18;
dragarea.yMin += border;
dragarea.xMax -= border;
dragarea.yMax -= border;
// Reshape the inner box to the correct aspect ratio
if (isExpanded) {
var ratio = (attribute as NormalizedRectAttribute).AspectRatio;
if (ratio == 0) {
var currentRes = UnityEditor.Handles.GetMainGameViewSize();
ratio = currentRes.x / currentRes.y;
}
// Don't go any wider than the inspector box.
var width = (dragarea.height * ratio);
if (width < dragarea.width) {
var x = (dragarea.width - width) / 2;
dragarea.x = dragarea.xMin + (int)x;
dragarea.width = (int)(width);
}
}
// Simulated desktop rect
GUI.Box(dragarea, GUIContent.none, EditorStyles.helpBox);
var invertY = (attribute as NormalizedRectAttribute).InvertY;
Event e = Event.current;
const int HANDLE_SIZE = 8;
var normmin = new Vector2(value.xMin, invertY ? 1f - value.yMin : value.yMin);
var normmax = new Vector2(value.xMax, invertY ? 1f - value.yMax : value.yMax);
var minreal = Rect.NormalizedToPoint(dragarea, normmin);
var maxreal = Rect.NormalizedToPoint(dragarea, normmax);
var lowerleftrect = new Rect(minreal.x , minreal.y - (invertY ? HANDLE_SIZE : 0), HANDLE_SIZE, HANDLE_SIZE);
var upperrghtrect = new Rect(maxreal.x - HANDLE_SIZE, maxreal.y - (invertY ? 0 : HANDLE_SIZE), HANDLE_SIZE, HANDLE_SIZE);
var upperleftrect = new Rect(minreal.x , maxreal.y - (invertY ? 0 : HANDLE_SIZE), HANDLE_SIZE, HANDLE_SIZE);
var lowerrghtrect = new Rect(maxreal.x - HANDLE_SIZE, minreal.y - (invertY ? HANDLE_SIZE : 0), HANDLE_SIZE, HANDLE_SIZE);
var currentrect = Rect.MinMaxRect(minreal.x, invertY ? maxreal.y : minreal.y, maxreal.x, invertY ? minreal.y : maxreal.y);
if (lockCursorStyle == MouseCursor.Arrow) {
if (isExpanded) {
EditorGUIUtility.AddCursorRect(lowerleftrect, MouseCursor.Link);
EditorGUIUtility.AddCursorRect(upperrghtrect, MouseCursor.Link);
EditorGUIUtility.AddCursorRect(upperleftrect, MouseCursor.Link);
EditorGUIUtility.AddCursorRect(lowerrghtrect, MouseCursor.Link);
}
EditorGUIUtility.AddCursorRect(currentrect, MouseCursor.MoveArrow);
} else {
// Lock cursor to a style while dragging, otherwise the slow inspector update causes rapid mouse icon changes.
EditorGUIUtility.AddCursorRect(dragarea, lockCursorStyle);
}
EditorGUI.DrawRect(lowerleftrect, Color.yellow);
EditorGUI.DrawRect(upperrghtrect, Color.yellow);
EditorGUI.DrawRect(upperleftrect, Color.yellow);
EditorGUI.DrawRect(lowerrghtrect, Color.yellow);
var mousepos = e.mousePosition;
if (e.button == 0) {
if (e.type == EventType.MouseUp) {
isDragXMin = false;
isDragYMin = false;
isDragXMax = false;
isDragYMax = false;
isDragAll = false;
lockCursorStyle = MouseCursor.Arrow;
isDragNewRect = false;
hasChanged = true;
}
if (e.type == EventType.MouseDown ) {
if (isExpanded && lowerleftrect.Contains(mousepos)) {
isDragXMin = true;
isDragYMin = true;
lockCursorStyle = MouseCursor.Link;
} else if (isExpanded && upperrghtrect.Contains(mousepos)) {
isDragXMax = true;
isDragYMax = true;
lockCursorStyle = MouseCursor.Link;
} else if (isExpanded && upperleftrect.Contains(mousepos)) {
isDragXMin = true;
isDragYMax = true;
lockCursorStyle = MouseCursor.Link;
} else if (isExpanded && lowerrghtrect.Contains(mousepos)) {
isDragXMax = true;
isDragYMin = true;
lockCursorStyle = MouseCursor.Link;
} else if (currentrect.Contains(mousepos)) {
isDragAll = true;
// mouse start is stored as a normalized offset from the Min values.
mouseDownStart = Rect.PointToNormalized(dragarea, mousepos) - normmin;
lockCursorStyle = MouseCursor.MoveArrow;
} else if (isExpanded && dragarea.Contains(mousepos)) {
mouseDownStart = mousepos;
isDragNewRect = true;
}
}
}
if (e.type == EventType.MouseDrag) {
Rect rect;
if (isDragNewRect) {
var start = Rect.PointToNormalized(dragarea, mouseDownStart);
var end = Rect.PointToNormalized(dragarea, e.mousePosition);
if (invertY) {
rect = Rect.MinMaxRect(
Math.Max(0f, Math.Min(start.x, end.x)),
Math.Max(0f, 1f - Math.Max(start.y, end.y)),
Math.Min(1f, Math.Max(start.x, end.x)),
Math.Min(1f, 1f - Math.Min(start.y, end.y))
);
} else {
rect = Rect.MinMaxRect(
Math.Max(0f, Math.Min(start.x, end.x)),
Math.Max(0f, Math.Min(start.y, end.y)),
Math.Min(1f, Math.Max(start.x, end.x)),
Math.Min(1f, Math.Max(start.y, end.y))
);
}
property.rectValue = rect;
hasChanged = true;
} else if (isDragAll){
var normmouse = Rect.PointToNormalized(dragarea, e.mousePosition);
rect = new Rect(value) {
x = Math.Max(normmouse.x - mouseDownStart.x, 0),
y = Math.Max(invertY ? (1 - normmouse.y + mouseDownStart.y) : (normmouse.y - mouseDownStart.y), 0)
};
if (rect.xMax > 1) {
rect = new Rect(rect) { x = rect.x + (1f - rect.xMax)};
}
if (rect.yMax > 1) {
rect = new Rect(rect) { y = rect.y + (1f - rect.yMax) };
}
property.rectValue = rect;
hasChanged = true;
} else if (isDragXMin || isDragXMax || isDragYMin || isDragYMax) {
const float VERT_HANDLE_MIN_DIST = .2f;
const float HORZ_HANDLE_MIN_DIST = .05f;
var normmouse = Rect.PointToNormalized(dragarea, e.mousePosition);
if (invertY) {
rect = Rect.MinMaxRect(
isDragXMin ? Math.Min( normmouse.x, value.xMax - HORZ_HANDLE_MIN_DIST) : value.xMin,
isDragYMin ? Math.Min(1f - normmouse.y, value.yMax - VERT_HANDLE_MIN_DIST) : value.yMin,
isDragXMax ? Math.Max( normmouse.x, value.xMin + HORZ_HANDLE_MIN_DIST) : value.xMax,
isDragYMax ? Math.Max(1f - normmouse.y, value.yMin + VERT_HANDLE_MIN_DIST) : value.yMax
);
} else {
rect = Rect.MinMaxRect(
isDragXMin ? Math.Min(normmouse.x, value.xMax - HORZ_HANDLE_MIN_DIST) : value.xMin,
isDragYMin ? Math.Min(normmouse.y, value.yMax - VERT_HANDLE_MIN_DIST) : value.yMin,
isDragXMax ? Math.Max(normmouse.x, value.xMin + HORZ_HANDLE_MIN_DIST) : value.xMax,
isDragYMax ? Math.Max(normmouse.y, value.yMin + VERT_HANDLE_MIN_DIST) : value.yMax
);
}
property.rectValue = rect;
hasChanged = true;
}
}
const float SPACING = 4f;
const int LABELS_WIDTH = 16;
const float COMPACT_THRESHOLD = 340f;
bool useCompact = position.width < COMPACT_THRESHOLD;
var labelwidth = EditorGUIUtility.labelWidth;
var fieldwidth = (position.width - labelwidth- 3 * SPACING) * 0.25f ;
var fieldbase = new Rect(position) { xMin = position.xMin + labelwidth, height = 16, width = fieldwidth - (useCompact ? 0 : LABELS_WIDTH) };
if (_compactValueStyle == null) {
_compactLabelStyle = new GUIStyle(EditorStyles.miniLabel) { fontSize = 9, alignment = TextAnchor.MiddleLeft, padding = new RectOffset(2, 0, 1, 0) };
_compactValueStyle = new GUIStyle(EditorStyles.miniTextField) { fontSize = 9, alignment = TextAnchor.MiddleLeft, padding = new RectOffset(2, 0, 1, 0) };
}
GUIStyle valueStyle = _compactValueStyle;
//if (useCompact) {
// if (_compactStyle == null) {
// _compactStyle = new GUIStyle(EditorStyles.miniTextField) { fontSize = 9, alignment = TextAnchor.MiddleLeft, padding = new RectOffset(2, 0, 1, 0) };
// }
// valueStyle = _compactStyle;
//} else {
// valueStyle = EditorStyles.textField;
//}
// Only draw labels when not in compact
if (!useCompact) {
Rect l1 = new Rect(fieldbase) { x = fieldbase.xMin };
Rect l2 = new Rect(fieldbase) { x = fieldbase.xMin + 1 * (fieldwidth + SPACING) };
Rect l3 = new Rect(fieldbase) { x = fieldbase.xMin + 2 * (fieldwidth + SPACING) };
Rect l4 = new Rect(fieldbase) { x = fieldbase.xMin + 3 * (fieldwidth + SPACING) };
GUI.Label(l1, "L:", _compactLabelStyle);
GUI.Label(l2, "R:", _compactLabelStyle);
GUI.Label(l3, "T:", _compactLabelStyle);
GUI.Label(l4, "B:", _compactLabelStyle);
}
// Draw value fields
Rect f1 = new Rect(fieldbase) { x = fieldbase.xMin + 0 * fieldwidth + (useCompact ? 0 : LABELS_WIDTH) };
Rect f2 = new Rect(fieldbase) { x = fieldbase.xMin + 1 * fieldwidth + (useCompact ? 0 : LABELS_WIDTH) + 1 * SPACING };
Rect f3 = new Rect(fieldbase) { x = fieldbase.xMin + 2 * fieldwidth + (useCompact ? 0 : LABELS_WIDTH) + 2 * SPACING };
Rect f4 = new Rect(fieldbase) { x = fieldbase.xMin + 3 * fieldwidth + (useCompact ? 0 : LABELS_WIDTH) + 3 * SPACING };
using (var check = new EditorGUI.ChangeCheckScope()) {
float newxmin, newxmax, newymin, newymax;
if (invertY) {
newxmin = EditorGUI.DelayedFloatField(f1, (float)Math.Round(value.xMin, useCompact ? 2 : 3), valueStyle);
newxmax = EditorGUI.DelayedFloatField(f2, (float)Math.Round(value.xMax, useCompact ? 2 : 3), valueStyle);
newymax = EditorGUI.DelayedFloatField(f3, (float)Math.Round(value.yMax, useCompact ? 2 : 3), valueStyle);
newymin = EditorGUI.DelayedFloatField(f4, (float)Math.Round(value.yMin, useCompact ? 2 : 3), valueStyle);
} else {
newxmin = EditorGUI.DelayedFloatField(f1, (float)Math.Round(value.xMin, useCompact ? 2 : 3), valueStyle);
newxmax = EditorGUI.DelayedFloatField(f2, (float)Math.Round(value.xMax, useCompact ? 2 : 3), valueStyle);
newymin = EditorGUI.DelayedFloatField(f3, (float)Math.Round(value.yMin, useCompact ? 2 : 3), valueStyle);
newymax = EditorGUI.DelayedFloatField(f4, (float)Math.Round(value.yMax, useCompact ? 2 : 3), valueStyle);
}
if (check.changed) {
if (newxmin != value.xMin) value.xMin = Math.Min(newxmin, value.xMax - .05f);
if (newxmax != value.xMax) value.xMax = Math.Max(newxmax, value.xMin + .05f);
if (newymax != value.yMax) value.yMax = Math.Max(newymax, value.yMin + .05f);
if (newymin != value.yMin) value.yMin = Math.Min(newymin, value.yMax - .05f);
property.rectValue = value;
property.serializedObject.ApplyModifiedProperties();
}
}
var nmins = new Vector2(value.xMin, invertY ? 1f - value.yMin : value.yMin);
var nmaxs = new Vector2(value.xMax, invertY ? 1f - value.yMax : value.yMax);
var mins = Rect.NormalizedToPoint(dragarea, nmins);
var maxs = Rect.NormalizedToPoint(dragarea, nmaxs);
var area = Rect.MinMaxRect(minreal.x, invertY ? maxreal.y : minreal.y, maxreal.x, invertY ? minreal.y : maxreal.y);
EditorGUI.DrawRect(area, new Color(1f, 1f, 1f, .1f));
//GUI.DrawTexture(area, GUIContent.none, EditorStyles.helpBox);
//GUI.Box(area, GUIContent.none, EditorStyles.helpBox);
} else {
Debug.LogWarning($"{nameof(NormalizedRectAttribute)} only valid on UnityEngine.Rect fields. Will use default rendering for '{property.type} {property.name}' in class '{fieldInfo.DeclaringType}'.");
EditorGUI.PropertyField(position, property, label);
}
if (hasChanged) {
GUI.changed = true;
property.serializedObject.ApplyModifiedProperties();
}
EditorGUI.EndProperty();
}
}
#endif
}
#endregion
#region Assets/Photon/Fusion/Editor/CustomTypes/SceneRefDrawer.cs
namespace Fusion.Editor {
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEditor;
using UnityEngine;
[CustomPropertyDrawer(typeof(SceneRef))]
public class SceneRefDrawer : PropertyDrawer {
public const int CheckboxWidth = 16;
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
using (new FusionEditorGUI.PropertyScopeWithPrefixLabel(position, label, property, out position)) {
var valueProperty = property.FindPropertyRelativeOrThrow(nameof(SceneRef.RawValue));
long rawValue = valueProperty.longValue;
var togglePos = position;
togglePos.width = CheckboxWidth;
bool hasValue = rawValue > 0;
EditorGUI.BeginChangeCheck();
if (EditorGUI.Toggle(togglePos, hasValue) != hasValue) {
rawValue = valueProperty.longValue = hasValue ? 0 : 1;
valueProperty.serializedObject.ApplyModifiedProperties();
}
if (rawValue > 0) {
position.xMin += togglePos.width;
rawValue = EditorGUI.LongField(position, rawValue - 1);
rawValue = Math.Max(0, rawValue) + 1;
if (EditorGUI.EndChangeCheck()) {
valueProperty.longValue = rawValue;
valueProperty.serializedObject.ApplyModifiedProperties();
}
}
}
}
}
}
#endregion
#region Assets/Photon/Fusion/Editor/CustomTypes/SerializableDictionaryDrawer.cs
namespace Fusion.Editor {
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;
[CustomPropertyDrawer(typeof(SerializableDictionary), true)]
class SerializableDictionaryDrawer : PropertyDrawerWithErrorHandling {
const string ItemsPropertyPath = SerializableDictionary<int,int>.ItemsPropertyPath;
const string EntryKeyPropertyPath = SerializableDictionary<int, int>.EntryKeyPropertyPath;
protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) {
var entries = property.FindPropertyRelativeOrThrow(ItemsPropertyPath);
entries.isExpanded = property.isExpanded;
using (new FusionEditorGUI.PropertyScope(position, label, property)) {
EditorGUI.PropertyField(position, entries, label, true);
property.isExpanded = entries.isExpanded;
string error = VerifyDictionary(entries, EntryKeyPropertyPath);
if (error != null) {
SetError(error);
} else {
ClearError();
}
}
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
var entries = property.FindPropertyRelativeOrThrow(ItemsPropertyPath);
return EditorGUI.GetPropertyHeight(entries, label, true);
}
private static HashSet<SerializedProperty> _dictionaryKeyHash = new HashSet<SerializedProperty>(new SerializedPropertyUtilities.SerializedPropertyEqualityComparer());
private static string VerifyDictionary(SerializedProperty prop, string keyPropertyName) {
Debug.Assert(prop.isArray);
try {
for (int i = 0; i < prop.arraySize; ++i) {
var keyProperty = prop.GetArrayElementAtIndex(i).FindPropertyRelativeOrThrow(keyPropertyName);
if (!_dictionaryKeyHash.Add(keyProperty)) {
var groups = Enumerable.Range(0, prop.arraySize)
.GroupBy(x => prop.GetArrayElementAtIndex(x).FindPropertyRelative(keyPropertyName), x => x, _dictionaryKeyHash.Comparer)
.Where(x => x.Count() > 1)
.ToList();
// there are duplicates - take the slow and allocating path now
return string.Join("\n", groups.Select(x => $"Duplicate keys for elements: {string.Join(", ", x)}"));
}
}
return null;
} finally {
_dictionaryKeyHash.Clear();
}
}
}
}
#endregion
#region Assets/Photon/Fusion/Editor/CustomTypes/TickRateDrawer.cs
namespace Fusion.Editor {
using System;
using System.Reflection;
using UnityEditor;
using UnityEngine;
[CustomPropertyDrawer(typeof(TickRate.Selection))]
[FusionPropertyDrawerMeta(HasFoldout = false)]
public class TickRateDrawer : PropertyDrawer {
private const int PAD = 0;
// Cached pop items for client rate
private static GUIContent[] _clientRateOptions;
private static GUIContent[] ClientRateOptions {
get {
if (_clientRateOptions != null) {
return _clientRateOptions;
}
ExtractClientRates();
return _clientRateOptions;
}
}
// Cached pop items for client rate
private static int[] _clientRateValues;
private static int[] ClientRateValues {
get {
if (_clientRateValues != null) {
return _clientRateValues;
}
ExtractClientRates();
return _clientRateValues;
}
}
//
// private static GUIContent[] _ratioOptions = new GUIContent[4];
// private static int[] _ratioValues = new int[] { 0, 1, 2, 3 };
private static readonly GUIContent[][] _reusableRatioGUIArrays = new GUIContent[4][] { new GUIContent[1], new GUIContent[2], new GUIContent[3], new GUIContent[4] };
private static readonly int[][] _reusableRatioIntArrays = new int[4][] { new int[1], new int[2], new int[3], new int[4] };
private static readonly GUIContent[][] _reusableServerGUIArrays = new GUIContent[4][] { new GUIContent[1], new GUIContent[2], new GUIContent[3], new GUIContent[4] };
private static readonly int[][] _reusableServerIntArrays = new int[4][] { new int[1], new int[2], new int[3], new int[4] };
private static readonly LazyGUIStyle _buttonStyle = LazyGUIStyle.Create(_ => new GUIStyle(EditorStyles.miniButton) {
fontSize = 9,
alignment = TextAnchor.MiddleCenter
});
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
return (base.GetPropertyHeight(property, label) + EditorGUIUtility.standardVerticalSpacing) * 4 + PAD * 2;
}
private int DrawPopup(ref Rect labelRect, ref Rect fieldRect, float rowHeight, GUIContent guiContent, int[] sliderValues, int[] values, GUIContent[] options, int currentValue, int offset = 0) {
EditorGUI.LabelField(labelRect, guiContent);
int indentHold = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0;
int value;
Rect dropRect = fieldRect;
if (fieldRect.width > 120) {
var slideRect = new Rect(fieldRect) { xMax = fieldRect.xMax - 64 };
dropRect = new Rect(fieldRect) { xMin = fieldRect.xMax - 64 };
var sliderRange = Math.Max(3, sliderValues.Length - 1);
if (sliderRange == 3) {
var dividerRect = new Rect(slideRect);
// dividerRect.yMin += 2;
// dividerRect.yMax -= 2;
var quarter = slideRect.width * 1f /4;
using (new EditorGUI.DisabledScope(!(options.Length + offset >= 4))) {
if (GUI.Toggle(new Rect(dividerRect) { width = quarter }, currentValue == 3, new GUIContent("1/8"), _buttonStyle )) {
currentValue = 3;
}
}
using (new EditorGUI.DisabledScope(!(options.Length + offset >= 3 && offset <= 2))) {
if (GUI.Toggle(new Rect(dividerRect) { width = quarter, x = dividerRect.x + quarter }, currentValue == 2, new GUIContent("1/4"), _buttonStyle)) {
currentValue = 2;
}
}
using (new EditorGUI.DisabledScope(!(options.Length + offset >= 2 && offset <= 1))) {
if (GUI.Toggle(new Rect(dividerRect) { width = quarter, x = dividerRect.x + quarter * 2 }, currentValue == 1, new GUIContent("1/2"), _buttonStyle)) {
currentValue = 1;
}
}
using (new EditorGUI.DisabledScope(!(options.Length + offset >= 1 && offset == 0))) {
if (GUI.Toggle(new Rect(dividerRect) { width = quarter, x = dividerRect.x + quarter * 3 }, currentValue == 0, new GUIContent("1:1"), _buttonStyle)) {
currentValue = 0;
}
}
EditorGUI.LabelField(dropRect, options[currentValue - offset], new GUIStyle(EditorStyles.label){padding = new RectOffset(4, 0, 0, 0)});
value = values[currentValue - offset];
} else {
currentValue = (int)GUI.HorizontalSlider(slideRect, (float)currentValue, sliderRange, 0);
// Clamp slider ranges into valid enum ranges
if (currentValue - offset < 0) {
currentValue = offset;
}
else if (currentValue - offset >= values.Length) {
currentValue = values.Length - 1 + offset;
}
value = values[EditorGUI.Popup(dropRect, GUIContent.none, currentValue - offset, options)];
// value = values[EditorGUI.Popup(fieldRect, GUIContent.none, currentValue - offset, options)];
}
} else {
// Handling for very narrow window. Falls back to just a basic popup for each value.
dropRect = fieldRect;
value = values[EditorGUI.Popup(dropRect, GUIContent.none, currentValue - offset, options)];
}
EditorGUI.indentLevel = indentHold;
labelRect.y += rowHeight + EditorGUIUtility.standardVerticalSpacing;
fieldRect.y += rowHeight + EditorGUIUtility.standardVerticalSpacing;
return value;
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
using (new FusionEditorGUI.PropertyScope(position, label, property)) {
var rowHeight = base.GetPropertyHeight(property, label);
// using (new FusionEditorGUI.PropertyScopeWithPrefixLabel(position, label, property, out position)) {
// EditorGUI.LabelField(new Rect(position){ yMin = position.yMin + rowHeight}, GUIContent.none, FusionGUIStyles.GroupBoxType.Gray.GetStyle());
position = new Rect(position) {
xMin = position.xMin + PAD,
xMax = position.xMax - PAD,
yMin = position.yMin + PAD,
yMax = position.yMax - PAD
};
var clientRateProperty = property.FindPropertyRelativeOrThrow(nameof(TickRate.Selection.Client));
var serverRateProperty = property.FindPropertyRelativeOrThrow(nameof(TickRate.Selection.ServerIndex));
var clientSendProperty = property.FindPropertyRelativeOrThrow(nameof(TickRate.Selection.ClientSendIndex));
var serverSendProperty = property.FindPropertyRelativeOrThrow(nameof(TickRate.Selection.ServerSendIndex));
var selection = GetSelectionValue(property);
var hold = selection;
var tickRate = TickRate.Get(TickRate.IsValid(selection.Client) ? selection.Client : TickRate.Default.Client);
var clientRateIndex = GetIndexForClientRate(tickRate.Client);
var rect = new Rect(position) { height = base.GetPropertyHeight(property, label) };
//var fieldWidth = Math.Max(Math.Min(position.width * .33f, MAX_FIELD_WIDTH), MIN_FIELD_WIDTH);
var labelWidth = EditorGUIUtility.labelWidth;
var labelRect = new Rect(rect) { width = labelWidth}; // { xMax = rect.xMax - fieldWidth }};
//var fieldRect = new Rect(rect) { xMin = rect.xMax -fieldWidth };
var fieldRect = new Rect(rect) { xMin = rect.xMin + labelWidth};
// CLIENT SIM RATE
selection.Client = DrawPopup(ref labelRect, ref fieldRect, rowHeight, new GUIContent("Client Tick Rate"), _clientRateValues,_clientRateValues, ClientRateOptions, clientRateIndex);
// TODO: This validates every tick without checking for changes. May be good, may not.
selection = tickRate.ClampSelection(selection);
// CLIENT SEND RATE
var ratioOptions = _reusableRatioGUIArrays[tickRate.Count - 1]; // _ratioOptions;
var ratioValues = _reusableRatioIntArrays[tickRate.Count - 1]; //_ratioValues;
for (var i = 0; i < tickRate.Count; ++i) {
ratioOptions[i] = new GUIContent(tickRate.GetTickRate(i).ToString());
ratioValues[i] = i;
}
selection.ClientSendIndex = DrawPopup(ref labelRect, ref fieldRect, rowHeight, new GUIContent("Client Send Rate"), ratioValues, ratioValues, ratioOptions, selection.ClientSendIndex);
// SERVER SIM RATE
selection.ServerIndex = DrawPopup(ref labelRect, ref fieldRect, rowHeight, new GUIContent("Server Tick Rate"), ratioValues, ratioValues, ratioOptions, selection.ServerIndex);
selection = tickRate.ClampSelection(selection);
// SERVER SEND RATE - uses a subset of ratio - since it CANNOT be higher than Server Rate.
var sOffset = selection.ServerIndex;
var sLen = ratioOptions.Length - sOffset;
var sSendOptions = _reusableServerGUIArrays[sLen - 1]; // new GUIContent[sLen];
var sSendValues = _reusableServerIntArrays[sLen - 1]; // new int[sLen];
for (var i = 0; i < sLen; ++i) {
sSendOptions[i] = ratioOptions[i + sOffset];
sSendValues[i] = ratioValues[i + sOffset];
}
selection.ServerSendIndex = DrawPopup(ref labelRect, ref fieldRect, rowHeight, new GUIContent("Server Send Rate"), ratioValues, sSendValues, sSendOptions, selection.ServerSendIndex, sOffset);
if (hold.Equals(selection) == false) {
selection = tickRate.ClampSelection(selection);
// FIELD INFO SET VALUE ALTERNATIVE
// fieldInfo.SetValue(targetObject, selection);
clientRateProperty.intValue = selection.Client;
clientSendProperty.intValue = selection.ClientSendIndex;
serverRateProperty.intValue = selection.ServerIndex;
serverSendProperty.intValue = selection.ServerSendIndex;
property.serializedObject.ApplyModifiedProperties();
}
}
}
private int GetIndexForClientRate(int clientRate) {
for (var i = ClientRateValues.Length - 1; i >= 0; --i)
if (_clientRateValues[i] == clientRate) {
return i;
}
return -1;
}
// Extract in reverse order so all the popups are consistent.
private static void ExtractClientRates() {
int cnt = TickRate.Available.Count;
_clientRateOptions = new GUIContent[cnt];
_clientRateValues = new int[cnt];
for (int i = 0, reverse = cnt -1; i < cnt; ++i, --reverse) {
_clientRateOptions[i] = new GUIContent(TickRate.Available[reverse].Client.ToString());
_clientRateValues[i] = TickRate.Available[reverse].Client;
}
}
// Wacky reflection to locate the value
private static TickRate.Selection GetSelectionValue (SerializedProperty property) {
object obj = property.serializedObject.targetObject;
string path = property.propertyPath;
string[] parts = path.Split ('.');
foreach (var t in parts) {
obj = GetValueFromFieldName (t, obj);
}
return (TickRate.Selection)obj;
}
private static object GetValueFromFieldName(string name, object obj, BindingFlags bindings = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) {
FieldInfo field = obj.GetType().GetField(name, bindings);
if (field != null) {
return field.GetValue(obj);
}
return TickRate.Default;
}
}
}
#endregion
#region Assets/Photon/Fusion/Editor/DebugDllToggle.cs
namespace Fusion.Editor {
using System;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;
public static class DebugDllToggle {
const string FusionRuntimeDllGuid = "e725a070cec140c4caffb81624c8c787";
public static string[] FileList = new[] {
"Fusion.Common.dll",
"Fusion.Common.pdb",
"Fusion.Runtime.dll",
"Fusion.Runtime.pdb",
"Fusion.Realtime.dll",
"Fusion.Realtime.pdb",
"Fusion.Sockets.dll",
"Fusion.Sockets.pdb"};
[MenuItem("Tools/Fusion/Toggle Debug Dlls")]
public static void Toggle() {
// find the root
string dir;
{
var fusionRuntimeDllPath = AssetDatabase.GUIDToAssetPath(FusionRuntimeDllGuid);
if (string.IsNullOrEmpty(fusionRuntimeDllPath)) {
Debug.LogError($"Cannot locate assemblies directory");
return;
} else {
dir = PathUtils.Normalize(Path.GetDirectoryName(fusionRuntimeDllPath));
}
}
var dllsAvailable = FileList.All(f => File.Exists($"{dir}/{f}"));
var debugFilesAvailable = FileList.All(f => File.Exists($"{dir}/{f}.debug"));
if (dllsAvailable == false) {
Debug.LogError("Cannot find all fusion dlls");
return;
}
if (debugFilesAvailable == false) {
Debug.LogError("Cannot find all specially marked .debug dlls");
return;
}
if (FileList.Any(f => new FileInfo($"{dir}/{f}.debug").Length == 0)) {
Debug.LogError("Debug dlls are not valid");
return;
}
try {
foreach (var f in FileList) {
var tempFile = FileUtil.GetUniqueTempPathInProject();
FileUtil.MoveFileOrDirectory($"{dir}/{f}", tempFile);
FileUtil.MoveFileOrDirectory($"{dir}/{f}.debug", $"{dir}/{f}");
FileUtil.MoveFileOrDirectory(tempFile, $"{dir}/{f}.debug");
File.Delete(tempFile);
}
if (new FileInfo($"{dir}/{FileList[0]}").Length >
new FileInfo($"{dir}/{FileList[0]}.debug").Length) {
Debug.Log("Activated Fusion DEBUG dlls");
}
else {
Debug.Log("Activated Fusion RELEASE dlls");
}
} catch (Exception e) {
Debug.LogAssertion(e);
Debug.LogError($"Failed to rename files");
}
AssetDatabase.Refresh();
}
}
}
#endregion
#region Assets/Photon/Fusion/Editor/EditorRecompileHook.cs
namespace Fusion.Editor {
using System;
using System.IO;
using UnityEditor;
using UnityEditor.Compilation;
using UnityEngine;
[InitializeOnLoad]
public static class EditorRecompileHook {
static EditorRecompileHook() {
EditorApplication.update += delegate {
if (PlayerSettings.allowUnsafeCode == false) {
PlayerSettings.allowUnsafeCode = true;
// request re-compile
CompilationPipeline.RequestScriptCompilation(RequestScriptCompilationOptions.None);
}
};
AssemblyReloadEvents.beforeAssemblyReload += ShutdownRunners;
CompilationPipeline.compilationStarted += _ => ShutdownRunners();
CompilationPipeline.compilationStarted += _ => StoreConfigPath();
}
static void ShutdownRunners() {
var runners = NetworkRunner.GetInstancesEnumerator();
while (runners.MoveNext()) {
if (runners.Current) {
runners.Current.Shutdown();
}
}
}
static void StoreConfigPath() {
const string ConfigPathCachePath = "Temp/FusionILWeaverConfigPath.txt";
var configPath = NetworkProjectConfigUtilities.GetGlobalConfigPath();
if (string.IsNullOrEmpty(configPath)) {
// delete
try {
File.Delete(ConfigPathCachePath);
} catch (FileNotFoundException) {
// ok
} catch (Exception ex) {
FusionEditorLog.ErrorConfig($"Error when clearing the config path file for the Weaver. Weaving results may be invalid: {ex}");
}
} else {
try {
System.IO.File.WriteAllText(ConfigPathCachePath, configPath);
} catch (Exception ex) {
FusionEditorLog.ErrorConfig($"Error when writing the config path file for the Weaver. Weaving results may be invalid: {ex}");
}
}
}
}
}
#endregion
#region Assets/Photon/Fusion/Editor/FusionAssistants.cs
namespace Fusion.Editor {
using UnityEngine;
using System;
static class FusionAssistants {
public const int PRIORITY = 0;
public const int PRIORITY_LOW = 1000;
/// <summary>
/// Ensure GameObject has component T. Will create as needed and return the found/created component.
/// </summary>
public static T EnsureComponentExists<T>(this GameObject go) where T : Component {
if (go.TryGetComponent<T>(out var t))
return t;
else
return go.AddComponent<T>();
}
public static GameObject EnsureComponentsExistInScene(string preferredGameObjectName, params Type[] components) {
GameObject go = null;
foreach(var c in components) {
var found = UnityEngine.Object.FindFirstObjectByType(c);
if (found)
continue;
if (go == null)
go = new GameObject(preferredGameObjectName);
go.AddComponent(c);
}
return go;
}
public static T EnsureExistsInScene<T>(string preferredGameObjectName = null, GameObject onThisObject = null, params Type[] otherRequiredComponents) where T : Component {
if (preferredGameObjectName == null)
preferredGameObjectName = typeof(T).Name;
T comp;
comp = UnityEngine.Object.FindFirstObjectByType<T>();
if (comp == null) {
// T was not found in scene, create a new gameobject and add T, as well as other required components
if (onThisObject == null)
onThisObject = new GameObject(preferredGameObjectName);
comp = onThisObject.AddComponent<T>();
foreach (var add in otherRequiredComponents) {
onThisObject.AddComponent(add);
}
} else {
// Make sure existing found T has the indicated extra components as well.
foreach (var add in otherRequiredComponents) {
if (comp.GetComponent(add) == false)
comp.gameObject.AddComponent(add);
}
}
return comp;
}
/// <summary>
/// Create a scene object with all of the supplied arguments and parameters applied.
/// </summary>
public static GameObject CreatePrimitive(
PrimitiveType? primitive,
string name,
Vector3? position,
Quaternion? rotation,
Vector3? scale,
Transform parent,
Material material,
params Type[] addComponents) {
GameObject go;
if (primitive.HasValue) {
go = GameObject.CreatePrimitive(primitive.Value);
go.name = name;
if (material != null)
go.GetComponent<Renderer>().material = material;
foreach (var type in addComponents) {
go.AddComponent(type);
}
} else {
go = new GameObject(name, addComponents);
}
if (position.HasValue)
go.transform.position = position.Value;
if (rotation.HasValue)
go.transform.rotation = rotation.Value;
if (scale.HasValue)
go.transform.localScale = scale.Value;
if (parent)
go.transform.parent = parent;
return go;
}
internal static EnableOnSingleRunner EnsureComponentHasVisibilityNode(this Component component) {
var allExistingNodes = component.GetComponents<EnableOnSingleRunner>();
foreach (var existingNodes in allExistingNodes) {
foreach (var comp in existingNodes.Components) {
if (comp == component) {
return existingNodes;
}
}
}
// Component is not represented yet. If there is a VisNodes already, use it. Otherwise make one.
EnableOnSingleRunner targetNodes = component.GetComponent<EnableOnSingleRunner>();
if (targetNodes == null) {
targetNodes = component.gameObject.AddComponent<EnableOnSingleRunner>();
}
// Add this component to the collection.
int newArrayPos = targetNodes.Components.Length;
Array.Resize(ref targetNodes.Components, newArrayPos + 1);
targetNodes.Components[newArrayPos] = component;
return targetNodes;
}
}
}
#endregion
#region Assets/Photon/Fusion/Editor/FusionBootstrapEditor.cs
namespace Fusion.Editor {
using System.Linq;
using UnityEditor;
using UnityEngine;
using UnityEngine.SceneManagement;
[CustomEditor(typeof(FusionBootstrap))]
public class FusionBootstrapEditor : BehaviourEditor {
public override void OnInspectorGUI() {
base.OnInspectorGUI();
if (Application.isPlaying)
return;
var currentScene = SceneManager.GetActiveScene();
if (!currentScene.IsAddedToBuildSettings()) {
using (new FusionEditorGUI.WarningScope("Current scene is not added to Build Settings list.")) {
if (GUILayout.Button("Add Scene To Build Settings")) {
if (currentScene.name == "") {
UnityEditor.SceneManagement.EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo();
}
if (currentScene.name != "") {
EditorBuildSettings.scenes = EditorBuildSettings.scenes
.Concat(new[] { new EditorBuildSettingsScene(currentScene.path, true) })
.ToArray();
}
}
}
}
}
}
}
#endregion
#region Assets/Photon/Fusion/Editor/FusionBuildTriggers.cs
namespace Fusion.Editor {
using UnityEditor;
using UnityEditor.Build;
using UnityEditor.Build.Reporting;
public class FusionBuildTriggers : IPreprocessBuildWithReport {
public const int CallbackOrder = 1000;
public int callbackOrder => CallbackOrder;
public void OnPreprocessBuild(BuildReport report) {
if (report.summary.platformGroup != BuildTargetGroup.Standalone) {
return;
}
if (!PlayerSettings.runInBackground) {
FusionEditorLog.Warn($"Standalone builds should have {nameof(PlayerSettings)}.{nameof(PlayerSettings.runInBackground)} enabled. " +
$"Otherwise, loss of application focus may result in connection termination.");
}
}
}
}
#endregion
#region Assets/Photon/Fusion/Editor/FusionEditor.Common.cs
// merged Editor
#region AssetDatabaseUtils.Addressables.cs
#if (FUSION_ADDRESSABLES || FUSION_ENABLE_ADDRESSABLES) && !FUSION_DISABLE_ADDRESSABLES
namespace Fusion.Editor {
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEditor.AddressableAssets;
using UnityEditor.AddressableAssets.Settings;
using UnityEngine;
public partial class AssetDatabaseUtils {
public static void AddAddressableAssetsWithLabelMonitor(string label, Action<Hash128> handler) {
AddressableAssetSettings.OnModificationGlobal += (settings, modificationEvent, data) => {
switch (modificationEvent) {
case AddressableAssetSettings.ModificationEvent.EntryAdded:
case AddressableAssetSettings.ModificationEvent.EntryCreated:
case AddressableAssetSettings.ModificationEvent.EntryModified:
case AddressableAssetSettings.ModificationEvent.EntryMoved:
IEnumerable<AddressableAssetEntry> entries;
if (data is AddressableAssetEntry singleEntry) {
entries = Enumerable.Repeat(singleEntry, 1);
} else {
entries = (IEnumerable<AddressableAssetEntry>)data;
}
List<AddressableAssetEntry> allEntries = new List<AddressableAssetEntry>();
foreach (var entry in entries) {
entry.GatherAllAssets(allEntries, true, true, true);
if (allEntries.Any(x => HasLabel(x.AssetPath, label))) {
handler(settings.currentHash);
break;
}
allEntries.Clear();
}
break;
case AddressableAssetSettings.ModificationEvent.EntryRemoved:
// TODO: check what has been removed
handler(settings.currentHash);
break;
}
};
}
public static AddressableAssetEntry GetAddressableAssetEntry(UnityEngine.Object source) {
if (source == null || !AssetDatabase.Contains(source)) {
return null;
}
return GetAddressableAssetEntry(GetAssetGuidOrThrow(source));
}
public static AddressableAssetEntry GetAddressableAssetEntry(string guid) {
if (string.IsNullOrEmpty(guid)) {
return null;
}
var addressableSettings = AddressableAssetSettingsDefaultObject.Settings;
return addressableSettings.FindAssetEntry(guid);
}
public static AddressableAssetEntry CreateOrMoveAddressableAssetEntry(UnityEngine.Object source, string groupName = null) {
if (source == null || !AssetDatabase.Contains(source))
return null;
return CreateOrMoveAddressableAssetEntry(GetAssetGuidOrThrow(source), groupName);
}
public static AddressableAssetEntry CreateOrMoveAddressableAssetEntry(string guid, string groupName = null) {
if (string.IsNullOrEmpty(guid)) {
return null;
}
var addressableSettings = AddressableAssetSettingsDefaultObject.Settings;
AddressableAssetGroup group;
if (string.IsNullOrEmpty(groupName)) {
group = addressableSettings.DefaultGroup;
} else {
group = addressableSettings.FindGroup(groupName);
}
if (group == null) {
throw new ArgumentOutOfRangeException($"Group {groupName} not found");
}
var entry = addressableSettings.CreateOrMoveEntry(guid, group);
return entry;
}
public static bool RemoveMoveAddressableAssetEntry(UnityEngine.Object source) {
if (source == null || !AssetDatabase.Contains(source)) {
return false;
}
return RemoveMoveAddressableAssetEntry(GetAssetGuidOrThrow(source));
}
public static bool RemoveMoveAddressableAssetEntry(string guid) {
if (string.IsNullOrEmpty(guid)) {
return false;
}
var addressableSettings = AddressableAssetSettingsDefaultObject.Settings;
return addressableSettings.RemoveAssetEntry(guid);
}
}
}
#endif
#endregion
#region AssetDatabaseUtils.cs
namespace Fusion.Editor {
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEditor.Build;
using UnityEditor.PackageManager;
using UnityEngine;
using Object = UnityEngine.Object;
public static partial class AssetDatabaseUtils {
public static void SetAssetAndTheMainAssetDirty(UnityEngine.Object obj) {
EditorUtility.SetDirty(obj);
var assetPath = AssetDatabase.GetAssetPath(obj);
if (string.IsNullOrEmpty(assetPath)) {
return;
}
var mainAsset = AssetDatabase.LoadMainAssetAtPath(assetPath);
if (!mainAsset || mainAsset == obj) {
return;
}
EditorUtility.SetDirty(mainAsset);
}
public static string GetAssetPathOrThrow(int instanceID) {
var result = AssetDatabase.GetAssetPath(instanceID);
if (string.IsNullOrEmpty(result)) {
throw new ArgumentException($"Asset with InstanceID {instanceID} not found");
}
return result;
}
public static string GetAssetPathOrThrow(UnityEngine.Object obj) {
var result = AssetDatabase.GetAssetPath(obj);
if (string.IsNullOrEmpty(result)) {
throw new ArgumentException($"Asset {obj} not found");
}
return result;
}
public static string GetAssetPathOrThrow(string assetGuid) {
var result = AssetDatabase.GUIDToAssetPath(assetGuid);
if (string.IsNullOrEmpty(result)) {
throw new ArgumentException($"Asset with Guid {assetGuid} not found");
}
return result;
}
public static string GetAssetGuidOrThrow(string assetPath) {
var result = AssetDatabase.AssetPathToGUID(assetPath);
if (string.IsNullOrEmpty(result)) {
throw new ArgumentException($"Asset with path {assetPath} not found");
}
return result;
}
public static string GetAssetGuidOrThrow(int instanceId) {
var assetPath = GetAssetPathOrThrow(instanceId);
return GetAssetGuidOrThrow(assetPath);
}
public static string GetAssetGuidOrThrow(UnityEngine.Object obj) {
var assetPath = GetAssetPathOrThrow(obj);
return GetAssetGuidOrThrow(assetPath);
}
public static (string, long) GetGUIDAndLocalFileIdentifierOrThrow<T>(LazyLoadReference<T> reference) where T : UnityEngine.Object {
if (!AssetDatabase.TryGetGUIDAndLocalFileIdentifier(reference, out var guid, out long localId)) {
throw new ArgumentException($"Asset with instanceId {reference} not found");
}
return (guid, localId);
}
public static (string, long) GetGUIDAndLocalFileIdentifierOrThrow(UnityEngine.Object obj) {
if (!AssetDatabase.TryGetGUIDAndLocalFileIdentifier(obj, out var guid, out long localId)) {
throw new ArgumentException(nameof(obj));
}
return (guid, localId);
}
public static (string, long) GetGUIDAndLocalFileIdentifierOrThrow(int instanceId) {
if (!AssetDatabase.TryGetGUIDAndLocalFileIdentifier(instanceId, out var guid, out long localId)) {
throw new ArgumentException($"Asset with instanceId {instanceId} not found");
}
return (guid, localId);
}
public static void MoveAssetOrThrow(string source, string destination) {
var error = AssetDatabase.MoveAsset(source, destination);
if (!string.IsNullOrEmpty(error)) {
throw new ArgumentException($"Failed to move {source} to {destination}: {error}");
}
}
public static bool HasLabel(string assetPath, string label) {
var guidStr = AssetDatabase.AssetPathToGUID(assetPath);
if (!GUID.TryParse(guidStr, out var guid)) {
return false;
}
var labels = AssetDatabase.GetLabels(guid);
var index = Array.IndexOf(labels, label);
return index >= 0;
}
public static bool HasLabel(UnityEngine.Object obj, string label) {
var labels = AssetDatabase.GetLabels(obj);
var index = Array.IndexOf(labels, label);
return index >= 0;
}
public static bool HasLabel(GUID guid, string label) {
var labels = AssetDatabase.GetLabels(guid);
var index = Array.IndexOf(labels, label);
return index >= 0;
}
public static bool HasAnyLabel(string assetPath, params string[] labels) {
var guidStr = AssetDatabase.AssetPathToGUID(assetPath);
if (!GUID.TryParse(guidStr, out var guid)) {
return false;
}
var assetLabels = AssetDatabase.GetLabels(guid);
foreach (var label in labels) {
if (Array.IndexOf(assetLabels, label) >= 0) {
return true;
}
}
return false;
}
public static bool SetLabel(string assetPath, string label, bool present) {
var guid = AssetDatabase.GUIDFromAssetPath(assetPath);
if (guid.Empty()) {
return false;
}
var labels = AssetDatabase.GetLabels(guid);
var index = Array.IndexOf(labels, label);
if (present) {
if (index >= 0) {
return false;
}
ArrayUtility.Add(ref labels, label);
} else {
if (index < 0) {
return false;
}
ArrayUtility.RemoveAt(ref labels, index);
}
var obj = AssetDatabase.LoadMainAssetAtPath(assetPath);
if (obj == null) {
return false;
}
AssetDatabase.SetLabels(obj, labels);
return true;
}
public static bool SetLabel(UnityEngine.Object obj, string label, bool present) {
var labels = AssetDatabase.GetLabels(obj);
var index = Array.IndexOf(labels, label);
if (present) {
if (index >= 0) {
return false;
}
ArrayUtility.Add(ref labels, label);
} else {
if (index < 0) {
return false;
}
ArrayUtility.RemoveAt(ref labels, index);
}
AssetDatabase.SetLabels(obj, labels);
return true;
}
public static bool SetLabels(string assetPath, string[] labels) {
var obj = AssetDatabase.LoadMainAssetAtPath(assetPath);
if (obj == null) {
return false;
}
AssetDatabase.SetLabels(obj, labels);
return true;
}
public static bool HasScriptingDefineSymbol(BuildTargetGroup group, string value) {
var defines = PlayerSettings.GetScriptingDefineSymbols(NamedBuildTarget.FromBuildTargetGroup(group)).Split(';');
return System.Array.IndexOf(defines, value) >= 0;
}
public static T SetScriptableObjectType<T>(ScriptableObject obj) where T : ScriptableObject {
return (T)SetScriptableObjectType(obj, typeof(T));
}
public static ScriptableObject SetScriptableObjectType(ScriptableObject obj, Type type) {
const string ScriptPropertyName = "m_Script";
if (!obj) {
throw new ArgumentNullException(nameof(obj));
}
if (type == null) {
throw new ArgumentNullException(nameof(type));
}
if (!type.IsSubclassOf(typeof(ScriptableObject))) {
throw new ArgumentException($"Type {type} is not a subclass of {nameof(ScriptableObject)}");
}
if (obj.GetType() == type) {
return obj;
}
var tmp = ScriptableObject.CreateInstance(type);
try {
using (var dst = new SerializedObject(obj)) {
using (var src = new SerializedObject(tmp)) {
var scriptDst = dst.FindPropertyOrThrow(ScriptPropertyName);
var scriptSrc = src.FindPropertyOrThrow(ScriptPropertyName);
Debug.Assert(scriptDst.objectReferenceValue != scriptSrc.objectReferenceValue);
dst.CopyFromSerializedProperty(scriptSrc);
dst.ApplyModifiedPropertiesWithoutUndo();
return (ScriptableObject)dst.targetObject;
}
}
} finally {
UnityEngine.Object.DestroyImmediate(tmp);
}
}
private static bool IsEnumValueObsolete<T>(string valueName) where T : System.Enum {
var fi = typeof(T).GetField(valueName);
var attributes = fi.GetCustomAttributes(typeof(System.ObsoleteAttribute), false);
return attributes?.Length > 0;
}
private static IEnumerable<BuildTargetGroup> ValidBuildTargetGroups {
get {
foreach (var name in System.Enum.GetNames(typeof(BuildTargetGroup))) {
if (IsEnumValueObsolete<BuildTargetGroup>(name))
continue;
var group = (BuildTargetGroup)System.Enum.Parse(typeof(BuildTargetGroup), name);
if (group == BuildTargetGroup.Unknown)
continue;
yield return group;
}
}
}
public static bool? HasScriptingDefineSymbol(string value) {
bool anyDefined = false;
bool anyUndefined = false;
foreach (BuildTargetGroup group in ValidBuildTargetGroups) {
if (HasScriptingDefineSymbol(group, value)) {
anyDefined = true;
} else {
anyUndefined = true;
}
}
return (anyDefined && anyUndefined) ? (bool?)null : anyDefined;
}
public static void UpdateScriptingDefineSymbol(BuildTargetGroup group, string define, bool enable) {
UpdateScriptingDefineSymbolInternal(new[] { group },
enable ? new[] { define } : null,
enable ? null : new[] { define });
}
public static void UpdateScriptingDefineSymbol(string define, bool enable) {
UpdateScriptingDefineSymbolInternal(ValidBuildTargetGroups,
enable ? new[] { define } : null,
enable ? null : new[] { define });
}
public static void UpdateScriptingDefineSymbol(BuildTargetGroup group, IEnumerable<string> definesToAdd, IEnumerable<string> definesToRemove) {
UpdateScriptingDefineSymbolInternal(new[] { group },
definesToAdd,
definesToRemove);
}
public static void UpdateScriptingDefineSymbol(IEnumerable<string> definesToAdd, IEnumerable<string> definesToRemove) {
UpdateScriptingDefineSymbolInternal(ValidBuildTargetGroups,
definesToAdd,
definesToRemove);
}
private static void UpdateScriptingDefineSymbolInternal(IEnumerable<BuildTargetGroup> groups, IEnumerable<string> definesToAdd, IEnumerable<string> definesToRemove) {
EditorApplication.LockReloadAssemblies();
try {
foreach (var group in groups) {
var originalDefines = PlayerSettings.GetScriptingDefineSymbols(NamedBuildTarget.FromBuildTargetGroup(group));
var defines = originalDefines.Split(';').ToList();
if (definesToRemove != null) {
foreach (var d in definesToRemove) {
defines.Remove(d);
}
}
if (definesToAdd != null) {
foreach (var d in definesToAdd) {
defines.Remove(d);
defines.Add(d);
}
}
var newDefines = string.Join(";", defines);
PlayerSettings.SetScriptingDefineSymbols(NamedBuildTarget.FromBuildTargetGroup(group), newDefines);
}
} finally {
EditorApplication.UnlockReloadAssemblies();
}
}
public static AssetEnumerable IterateAssets<T>(string root = null, string label = null) where T : UnityEngine.Object {
return IterateAssets(root, label, typeof(T));
}
public static AssetEnumerable IterateAssets(string root = null, string label = null, Type type = null) {
return new AssetEnumerable(root, label, type);
}
public struct AssetEnumerator : IEnumerator<HierarchyProperty> {
private HierarchyProperty _hierarchyProperty;
private int _rootFolderIndex;
private readonly string[] _rootFolders;
private readonly string _searchFilter;
private static bool IsPackageHidden(UnityEditor.PackageManager.PackageInfo info) => info.type == "module" || info.type == "feature" && info.source != PackageSource.Embedded;
public AssetEnumerator(string root, string label, Type type) {
{
string searchFilter;
if (type == typeof(GameObject)) {
searchFilter = "t:prefab";
} else if (type != null) {
searchFilter = "t:" + type.FullName;
} else {
searchFilter = "";
}
if (!string.IsNullOrEmpty(label)) {
if (searchFilter.Length > 0) {
searchFilter += " ";
}
searchFilter += "l:" + label;
}
_searchFilter = searchFilter;
}
_rootFolderIndex = 0;
if (string.IsNullOrEmpty(root)) {
// search everywhere
_rootFolders = new[] { "Assets" }.Concat(UnityEditor.PackageManager.PackageInfo.GetAllRegisteredPackages()
.Where(x => !IsPackageHidden(x))
.Select(x => x.assetPath))
.ToArray();
_hierarchyProperty = new HierarchyProperty(_rootFolders[0]);
} else {
_rootFolders = null;
_hierarchyProperty = new HierarchyProperty(root);
}
_hierarchyProperty.SetSearchFilter(_searchFilter, (int)SearchableEditorWindow.SearchMode.All);
}
public bool MoveNext() {
if (_hierarchyProperty.Next(null)) {
return true;
}
if (_rootFolders == null || _rootFolderIndex + 1 >= _rootFolders.Length) {
return false;
}
var newHierarchyProperty = new HierarchyProperty(_rootFolders[++_rootFolderIndex]);
newHierarchyProperty.SetSearchFilter(_searchFilter, (int)SearchableEditorWindow.SearchMode.All);
_hierarchyProperty = newHierarchyProperty;
// try again
return MoveNext();
}
public void Reset() {
throw new System.NotImplementedException();
}
public HierarchyProperty Current => _hierarchyProperty;
object IEnumerator.Current => Current;
public void Dispose() {
}
}
public struct AssetEnumerable : IEnumerable<HierarchyProperty> {
private readonly string _root;
private readonly string _label;
private readonly Type _type;
public AssetEnumerable(string root, string label, Type type) {
_type = type;
_root = root;
_label = label;
}
public AssetEnumerator GetEnumerator() => new AssetEnumerator(_root, _label, _type);
IEnumerator<HierarchyProperty> IEnumerable<HierarchyProperty>.GetEnumerator() => GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
}
}
#endregion
#region EditorButtonDrawer.cs
namespace Fusion.Editor {
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;
public struct EditorButtonDrawer {
private struct ButtonEntry {
public MethodInfo Method;
public GUIContent Content;
public EditorButtonAttribute Attribute;
public (DoIfAttributeBase, Func<object, object>)[] DoIfs;
}
private Editor _lastEditor;
private List<ButtonEntry> _buttons;
public void Draw(Editor editor) {
var targets = editor.targets;
if (_lastEditor != editor) {
_lastEditor = editor;
Refresh(editor);
}
if (_buttons == null || targets == null || targets.Length == 0) {
return;
}
foreach (var entry in _buttons) {
if (entry.Attribute.Visibility == EditorButtonVisibility.PlayMode && !EditorApplication.isPlaying) {
continue;
}
if (entry.Attribute.Visibility == EditorButtonVisibility.EditMode && EditorApplication.isPlaying) {
continue;
}
if (!entry.Attribute.AllowMultipleTargets && editor.targets.Length > 1) {
continue;
}
bool readOnly = false;
bool hidden = false;
string warningMessage = null;
bool warningAsBox = false;
foreach (var (doIf, getter) in entry.DoIfs) {
bool checkResult;
if (getter == null) {
checkResult = DoIfAttributeDrawer.CheckDraw(doIf, editor.serializedObject);
} else {
var value = getter(targets[0]);
checkResult = DoIfAttributeDrawer.CheckCondition(doIf, value);
}
if (!checkResult) {
if (doIf is DrawIfAttribute drawIf) {
if (drawIf.Hide) {
hidden = true;
break;
} else {
readOnly = true;
}
} else if (doIf is WarnIfAttribute warnIf) {
warningMessage = warnIf.Message;
warningAsBox = warnIf.AsBox;
}
}
}
if (hidden) {
continue;
}
using (warningMessage == null ? null : (IDisposable)new FusionEditorGUI.WarningScope(warningMessage)) {
var rect = FusionEditorGUI.LayoutHelpPrefix(editor, entry.Method);
using (new EditorGUI.DisabledScope(readOnly)) {
if (GUI.Button(rect, entry.Content)) {
EditorGUI.BeginChangeCheck();
if (entry.Method.IsStatic) {
entry.Method.Invoke(null, null);
} else {
foreach (var target in targets) {
entry.Method.Invoke(target, null);
if (entry.Attribute.DirtyObject) {
EditorUtility.SetDirty(target);
}
}
}
if (EditorGUI.EndChangeCheck()) {
editor.serializedObject.Update();
}
}
}
}
}
}
private void Refresh(Editor editor) {
if (editor == null) {
throw new ArgumentNullException(nameof(editor));
}
var targetType = editor.target.GetType();
_buttons = targetType
.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.FlattenHierarchy)
.Where(x => x.GetParameters().Length == 0 && x.IsDefined(typeof(EditorButtonAttribute)))
.Select(method => {
var attribute = method.GetCustomAttribute<EditorButtonAttribute>();
var label = new GUIContent(attribute.Label ?? ObjectNames.NicifyVariableName(method.Name));
var drawIfs = method.GetCustomAttributes<DoIfAttributeBase>()
.Select(x => {
var prop = editor.serializedObject.FindProperty(x.ConditionMember);
return prop != null ? (x, null) : (x, targetType.CreateGetter(x.ConditionMember));
})
.ToArray();
return new ButtonEntry() {
Attribute = attribute,
Content = label,
Method = method,
DoIfs = drawIfs,
};
})
.OrderBy(x => x.Attribute.Priority)
.ToList();
}
}
}
#endregion
#region EnumDrawer.cs
namespace Fusion.Editor {
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;
public struct EnumDrawer {
private Mask256[] _values;
private string[] _names;
private bool _isFlags;
private Type _enumType;
private Mask256 _allBitMask;
private FieldInfo[] _fields;
[NonSerialized]
private List<int> _selectedIndices;
public Mask256[] Values => _values;
public string[] Names => _names;
public bool IsFlags => _isFlags;
public Type EnumType => _enumType;
public Mask256 BitMask => _allBitMask;
public FieldInfo[] Fields => _fields;
public bool EnsureInitialized(Type enumType, bool includeFields) {
if (enumType == null) {
throw new ArgumentNullException(nameof(enumType));
}
bool isEnum = enumType.IsEnum;
if (!isEnum && !typeof(FieldsMask).IsAssignableFrom(enumType)) {
throw new ArgumentException("Type must be an enum or FieldsMask", nameof(enumType));
}
// Already initialized
if (_enumType == enumType) {
return false;
}
if (isEnum) {
var enumUnderlyingType = Enum.GetUnderlyingType(enumType);
var rawValues = Enum.GetValues(enumType);
_fields = includeFields ? new FieldInfo[rawValues.Length] : null;
_names = Enum.GetNames(enumType);
_values = new Mask256[rawValues.Length];
_isFlags = enumType.GetCustomAttribute<FlagsAttribute>() != null;
_enumType = enumType;
for (int i = 0; i < rawValues.Length; ++i) {
if (enumUnderlyingType == typeof(int) ||
enumUnderlyingType == typeof(long) ||
enumUnderlyingType == typeof(short) ||
enumUnderlyingType == typeof(byte)) {
_values[i] = Convert.ToInt64(rawValues.GetValue(i));
} else {
_values[i] = unchecked((long)Convert.ToUInt64(rawValues.GetValue(i)));
}
_allBitMask[0] |= _values[i][0];
if (includeFields) {
_fields[i] = enumType.GetField(_names[i], BindingFlags.Static | BindingFlags.Public);
}
}
} else {
// Handling for FieldsMask
var tType = enumType.GenericTypeArguments[0];
_fields = tType.GetFields();
_names = new string[_fields.Length];
_values = new Mask256[_fields.Length];
_isFlags = true;
_enumType = enumType;
for (int i = 0; i < _values.Length; i++) {
long value = (long)1 << i;
_allBitMask.SetBit(i, true);;
_values[i].SetBit(i, true); // = (long)1 << i;
_names[i] = _fields[i].Name;
}
}
for (int i = 0; i < _names.Length; ++i) {
_names[i] = ObjectNames.NicifyVariableName(_names[i]);
}
return true;
}
public void Draw(Rect position, SerializedProperty property, Type enumType, bool isEnum) {
if (property == null) {
throw new ArgumentNullException(nameof(property));
}
EnsureInitialized(enumType, false);
Mask256 currentValue;
if (isEnum) {
currentValue = new Mask256(
property.longValue
);
} else {
currentValue = new Mask256(
property.GetFixedBufferElementAtIndex(0).longValue,
property.GetFixedBufferElementAtIndex(1).longValue,
property.GetFixedBufferElementAtIndex(2).longValue,
property.GetFixedBufferElementAtIndex(3).longValue
);
}
_selectedIndices ??= new List<int>();
_selectedIndices.Clear();
// find out what to show
for (int i = 0; i < _values.Length; ++i) {
var value = _values[i];
if (_isFlags == false) {
if (currentValue[0]== value[0]) {
_selectedIndices.Add(i);
break;
}
} else if (Equals(currentValue & value, value)) {
_selectedIndices.Add(i);
}
}
string labelValue;
if (_selectedIndices.Count == 0) {
if (_isFlags && currentValue.IsNothing()) {
labelValue = "Nothing";
} else {
labelValue = "";
}
} else if (_selectedIndices.Count == 1) {
labelValue = _names[_selectedIndices[0]];
} else {
Debug.Assert(_isFlags);
if (_selectedIndices.Count == _values.Length) {
labelValue = "Everything";
} else {
var names = _names;
labelValue = string.Join(", ", _selectedIndices.Select(x => names[x]));
}
}
if (EditorGUI.DropdownButton(position, new GUIContent(labelValue), FocusType.Keyboard)) {
var values = _values;
var indices = _selectedIndices;
if (_isFlags) {
var allOptions = new[] { "Nothing", "Everything" }.Concat(_names).ToArray();
List<int> allIndices = new List<int>();
if (_selectedIndices.Count == 0) {
allIndices.Add(0); // nothing
}
else if (_selectedIndices.Count == _values.Length) {
allIndices.Add(1); // everything
}
allIndices.AddRange(_selectedIndices.Select(x => x + 2));
UnityInternal.EditorUtility.DisplayCustomMenu(position, allOptions, allIndices.ToArray(), (userData, options, selected) => {
if (selected == 0) {
// Clicked None
if (isEnum) {
property.longValue = 0;
}
else {
property.GetFixedBufferElementAtIndex(0).longValue = 0;
property.GetFixedBufferElementAtIndex(1).longValue = 0;
property.GetFixedBufferElementAtIndex(2).longValue = 0;
property.GetFixedBufferElementAtIndex(3).longValue = 0;
}
} else if (selected == 1) {
// Selected Everything
if (isEnum) {
property.longValue = 0;
} else {
property.GetFixedBufferElementAtIndex(0).longValue = 0;
property.GetFixedBufferElementAtIndex(1).longValue = 0;
property.GetFixedBufferElementAtIndex(2).longValue = 0;
property.GetFixedBufferElementAtIndex(3).longValue = 0;
}
foreach (var value in values) {
if (isEnum) {
property.longValue |= value[0];
} else{
property.GetFixedBufferElementAtIndex(0).longValue |= value[0];
property.GetFixedBufferElementAtIndex(1).longValue |= value[1];
property.GetFixedBufferElementAtIndex(2).longValue |= value[2];
property.GetFixedBufferElementAtIndex(3).longValue |= value[3];
}
}
} else {
// Toggled a value
selected -= 2;
if (indices.Contains(selected)) {
if (isEnum) {
property.longValue &= ~values[selected][0];
} else {
property.GetFixedBufferElementAtIndex(0).longValue &= ~values[selected][0];
property.GetFixedBufferElementAtIndex(1).longValue &= ~values[selected][1];
property.GetFixedBufferElementAtIndex(2).longValue &= ~values[selected][2];
property.GetFixedBufferElementAtIndex(3).longValue &= ~values[selected][3];
}
} else {
if (isEnum) {
property.longValue |= (long)values[selected][0];
} else {
property.GetFixedBufferElementAtIndex(0).longValue |= (long)values[selected][0];
property.GetFixedBufferElementAtIndex(1).longValue |= (long)values[selected][1];
property.GetFixedBufferElementAtIndex(2).longValue |= (long)values[selected][2];
property.GetFixedBufferElementAtIndex(3).longValue |= (long)values[selected][3];
}
}
}
property.serializedObject.ApplyModifiedProperties();
}, null);
} else {
// non-flags enum
UnityInternal.EditorUtility.DisplayCustomMenu(position, _names, _selectedIndices.ToArray(), (userData, options, selected) => {
if (!indices.Contains(selected)) {
property.longValue = values[selected][0];
property.serializedObject.ApplyModifiedProperties();
}
}, null);
}
}
}
}
}
#endregion
#region HashCodeUtilities.cs
namespace Fusion.Editor {
internal static class HashCodeUtilities {
public const int InitialHash = (5381 << 16) + 5381;
/// <summary>
/// This may only be deterministic on 64 bit systems.
/// </summary>
/// <param name="str"></param>
/// <param name="initialHash"></param>
/// <returns></returns>
public static int GetHashDeterministic(this string str, int initialHash = InitialHash) {
unchecked {
var hash1 = initialHash;
var hash2 = initialHash;
for (var i = 0; i < str.Length; i += 2) {
hash1 = ((hash1 << 5) + hash1) ^ str[i];
if (i == str.Length - 1) {
break;
}
hash2 = ((hash2 << 5) + hash2) ^ str[i + 1];
}
return hash1 + hash2 * 1566083941;
}
}
public static int CombineHashCodes(int a, int b) {
return ((a << 5) + a) ^ b;
}
public static int CombineHashCodes(int a, int b, int c) {
var t = ((a << 5) + a) ^ b;
return ((t << 5) + t) ^ c;
}
public static unsafe int GetArrayHashCode<T>(T* ptr, int length, int initialHash = InitialHash) where T : unmanaged {
var hash = initialHash;
for (var i = 0; i < length; ++i) {
hash = hash * 31 + ptr[i].GetHashCode();
}
return hash;
}
public static int GetHashCodeDeterministic(byte[] data, int initialHash = 0) {
var hash = initialHash;
for (var i = 0; i < data.Length; ++i) {
hash = hash * 31 + data[i];
}
return hash;
}
public static int GetHashCodeDeterministic(string data, int initialHash = 0) {
var hash = initialHash;
for (var i = 0; i < data.Length; ++i) {
hash = hash * 31 + data[i];
}
return hash;
}
public static unsafe int GetHashCodeDeterministic<T>(T data, int initialHash = 0) where T : unmanaged {
return GetHashCodeDeterministic(&data, initialHash);
}
public static unsafe int GetHashCodeDeterministic<T>(T* data, int initialHash = 0) where T : unmanaged {
var hash = initialHash;
var ptr = (byte*)data;
for (var i = 0; i < sizeof(T); ++i) {
hash = hash * 31 + ptr[i];
}
return hash;
}
}
}
#endregion
#region LazyAsset.cs
namespace Fusion.Editor {
using System;
using System.Collections.Generic;
using UnityEngine;
using Object = UnityEngine.Object;
public class LazyAsset<T> {
private T _value;
private Func<T> _factory;
public LazyAsset(Func<T> factory) {
_factory = factory;
}
public T Value {
get {
if (NeedsUpdate) {
lock (_factory) {
if (NeedsUpdate) {
_value = _factory();
}
}
}
return _value;
}
}
public static implicit operator T(LazyAsset<T> lazyAsset) {
return lazyAsset.Value;
}
public bool NeedsUpdate {
get {
if (_value is UnityEngine.Object obj) {
return !obj;
} else {
return _value == null;
}
}
}
}
public class LazyGUIStyle {
private Func<List<Object>, GUIStyle> _factory;
private GUIStyle _value;
private List<Object> _dependencies = new List<Object>();
public LazyGUIStyle(Func<List<Object>, GUIStyle> factory) {
_factory = factory;
}
public static LazyGUIStyle Create(Func<List<Object>, GUIStyle> factory) {
return new LazyGUIStyle(factory);
}
public static implicit operator GUIStyle(LazyGUIStyle lazyAsset) {
return lazyAsset.Value;
}
public GUIStyle Value {
get {
if (NeedsUpdate) {
lock (_factory) {
if (NeedsUpdate) {
_dependencies.Clear();
_value = _factory(_dependencies);
}
}
}
return _value;
}
}
public bool NeedsUpdate {
get {
if (_value == null) {
return true;
}
foreach (var dependency in _dependencies) {
if (!dependency) {
return true;
}
}
return false;
}
}
public Vector2 CalcSize(GUIContent content) => Value.CalcSize(content);
public void Draw(Rect position, GUIContent content, bool isHover, bool isActive, bool on, bool hasKeyboardFocus) => Value.Draw(position, content, isHover, isActive, on, hasKeyboardFocus);
public void Draw(Rect position, bool isHover, bool isActive, bool on, bool hasKeyboardFocus) => Value.Draw(position, isHover, isActive, on, hasKeyboardFocus);
}
public class LazyGUIContent {
private Func<List<Object>, GUIContent> _factory;
private GUIContent _value;
private List<Object> _dependencies = new List<Object>();
public LazyGUIContent(Func<List<Object>, GUIContent> factory) {
_factory = factory;
}
public static LazyGUIContent Create(Func<List<Object>, GUIContent> factory) {
return new LazyGUIContent(factory);
}
public static implicit operator GUIContent(LazyGUIContent lazyAsset) {
return lazyAsset.Value;
}
public GUIContent Value {
get {
if (NeedsUpdate) {
lock (_factory) {
if (NeedsUpdate) {
_dependencies.Clear();
_value = _factory(_dependencies);
}
}
}
return _value;
}
}
public bool NeedsUpdate {
get {
if (_value == null) {
return true;
}
foreach (var dependency in _dependencies) {
if (!dependency) {
return true;
}
}
return false;
}
}
}
public static class LazyAsset {
public static LazyAsset<T> Create<T>(Func<T> factory) {
return new LazyAsset<T>(factory);
}
}
}
#endregion
#region PathUtils.cs
namespace Fusion.Editor {
using System;
// TODO: this should be moved to the runtime part
static partial class PathUtils {
public static bool TryMakeRelativeToFolder(string path, string folderWithSlashes, out string result) {
var index = path.IndexOf(folderWithSlashes, StringComparison.Ordinal);
if (index < 0) {
result = string.Empty;
return false;
}
if (folderWithSlashes[0] != '/' && index > 0) {
result = string.Empty;
return false;
}
result = path.Substring(index + folderWithSlashes.Length);
return true;
}
[Obsolete("Use " + nameof(TryMakeRelativeToFolder) + " instead")]
public static bool MakeRelativeToFolder(string path, string folder, out string result) {
result = string.Empty;
var formattedPath = Normalize(path);
if (formattedPath.Equals(folder, StringComparison.Ordinal) ||
formattedPath.EndsWith("/" + folder)) {
return true;
}
var index = formattedPath.IndexOf(folder + "/", StringComparison.Ordinal);
var size = folder.Length + 1;
if (index >= 0 && formattedPath.Length >= size) {
result = formattedPath.Substring(index + size, formattedPath.Length - index - size);
return true;
}
return false;
}
[Obsolete("Use Normalize instead")]
public static string MakeSane(string path) {
return Normalize(path);
}
public static string Normalize(string path) {
return path.Replace("\\", "/").Replace("//", "/").TrimEnd('\\', '/').TrimStart('\\', '/');
}
public static string GetPathWithoutExtension(string path) {
if (path == null)
return null;
int length;
if ((length = path.LastIndexOf('.')) == -1)
return path;
return path.Substring(0, length);
}
}
}
#endregion
#region FusionCodeDoc.cs
namespace Fusion.Editor {
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Xml;
using UnityEditor;
using UnityEngine;
public static class FusionCodeDoc {
public const string Label = "FusionCodeDoc";
public const string Extension = "xml";
public const string ExtensionWithDot = "." + Extension;
private static Dictionary<string, CodeDoc> _parsedCodeDocs = new();
private static Dictionary<(string assemblyName, string memberKey), (GUIContent withoutType, GUIContent withType)> _guiContentCache = new();
private static string CrefColor => EditorGUIUtility.isProSkin ? "#FFEECC" : "#664400";
public static GUIContent FindEntry(MemberInfo member, bool addTypeInfo = true) {
switch (member) {
case FieldInfo field:
return FindEntry(field, addTypeInfo);
case PropertyInfo property:
return FindEntry(property);
case MethodInfo method:
return FindEntry(method);
case Type type:
return FindEntry(type);
default:
throw new ArgumentOutOfRangeException(nameof(member));
}
}
public static GUIContent FindEntry(FieldInfo field, bool addTypeInfo = true) {
if (field == null) {
throw new ArgumentNullException(nameof(field));
}
return FindEntry(field, $"F:{SanitizeTypeName(field.DeclaringType)}.{field.Name}", addTypeInfo);
}
public static GUIContent FindEntry(PropertyInfo property) {
if (property == null) {
throw new ArgumentNullException(nameof(property));
}
return FindEntry(property, $"P:{SanitizeTypeName(property.DeclaringType)}.{property.Name}");
}
public static GUIContent FindEntry(MethodInfo method) {
if (method == null) {
throw new ArgumentNullException(nameof(method));
}
return FindEntry(method, $"M:{SanitizeTypeName(method.DeclaringType)}.{method.Name}");
}
public static GUIContent FindEntry(Type type) {
if (type == null) {
throw new ArgumentNullException(nameof(type));
}
return FindEntry(type, $"T:{SanitizeTypeName(type)}");
}
private static GUIContent FindEntry(MemberInfo member, string key, bool addTypeInfo = true) {
Assembly assembly;
if (member is Type type) {
assembly = type.Assembly;
} else {
assembly = member.DeclaringType.Assembly;
}
var assemblyName = assembly.GetName().Name;
if (_guiContentCache.TryGetValue((assemblyName, key), out var content)) {
return addTypeInfo ? content.withType : content.withoutType;
}
foreach (var path in AssetDatabase.FindAssets($"l:{Label} t:TextAsset")
.Select(x => AssetDatabase.GUIDToAssetPath(x))
.Where(x => Path.GetFileNameWithoutExtension(x).Contains(assemblyName, StringComparison.OrdinalIgnoreCase))) {
// has this path been parsed already?
if (!_parsedCodeDocs.TryGetValue(path, out var parsedCodeDoc)) {
_parsedCodeDocs.Add(path, null);
if (!string.IsNullOrEmpty(path)) {
FusionEditorLog.Trace($"Trying to parse {path} for {key}");
if (TryParseCodeDoc(path, out parsedCodeDoc)) {
_parsedCodeDocs[path] = parsedCodeDoc;
}
} else {
FusionEditorLog.Trace($"Code doc for {assemblyName} not found.");
}
}
if (parsedCodeDoc?.AssemblyName == assemblyName) {
// code doc entries are sorted, so binary search is possible
if (TryFindEntry(parsedCodeDoc, key, out var entry)) {
content.withoutType = new GUIContent(entry.Summary, entry.Tooltip);
content.withType = content.withoutType;
_guiContentCache.Add((assemblyName, key), content);
Type returnType = null;
if (member is FieldInfo field) {
returnType = field.FieldType;
} else if (member is PropertyInfo property) {
returnType = property.PropertyType;
}
if (returnType != null) {
var typeEntry = FindEntry(returnType);
string typeSummary = "";
if (typeEntry != null) {
typeSummary += $"\n\n<color={CrefColor}>[{returnType.Name}]</color> {typeEntry}";
}
if (returnType.IsEnum) {
// find all the enum values
foreach (var enumValue in returnType.GetFields(BindingFlags.Static | BindingFlags.Public)) {
var enumValueEntry = FindEntry(enumValue, addTypeInfo: false);
if (enumValueEntry != null) {
typeSummary += $"\n\n<color={CrefColor}>[{returnType.Name}.{enumValue.Name}]</color> {enumValueEntry}";
}
}
}
if (typeSummary.Length > 0) {
content.withType = new GUIContent((entry.Summary + typeSummary).Trim('\n'), entry.Tooltip);
_guiContentCache[(assemblyName, key)] = content;
}
}
return addTypeInfo ? content.withType : content.withoutType;;
}
}
}
_guiContentCache.Add((assemblyName, key), default);
return addTypeInfo ? content.withType : content.withoutType;
bool TryFindEntry(CodeDoc codeDoc, string key, out Entry entry) {
var searchEntry = EntryKeyComparer.DummyEntry;
searchEntry.Key = key;
var index = Array.BinarySearch(codeDoc.Entries, searchEntry, EntryKeyComparer.Instance);
if (index < 0) {
entry = null;
return false;
}
entry = codeDoc.Entries[index];
return true;
}
}
private static string SanitizeTypeName(Type type) {
return type.FullName.Replace('+', '.');
}
public static void InvalidateCache() {
_parsedCodeDocs.Clear();
_guiContentCache.Clear();
}
private static bool TryParseCodeDoc(string path, out CodeDoc result) {
var xmlDoc = new XmlDocument();
try {
xmlDoc.Load(path);
} catch (Exception e) {
FusionEditorLog.Error($"Failed to load {path}: {e}");
result = null;
return false;
}
var assemblyName = xmlDoc.DocumentElement.SelectSingleNode("assembly")
?.SelectSingleNode("name")
?.FirstChild
?.Value;
if (assemblyName == null) {
result = null;
return false;
}
var members = xmlDoc.DocumentElement.SelectSingleNode("members")
.SelectNodes("member");
var buffer = new List<Entry>();
foreach (XmlNode node in members) {
var key = node.Attributes["name"].Value;
var summary = node.SelectSingleNode("summary")?.InnerXml.Trim();
if (summary == null) {
continue;
}
// remove generic indicator
summary = summary.Replace("`1", "");
// fork tooltip and help summaries
var help = Reformat(summary, false);
var tooltip = Reformat(summary, true);
buffer.Add(new FusionCodeDoc.Entry() {
Key = key,
Summary = help,
Tooltip = tooltip
});
}
result = new CodeDoc() {
AssemblyName = assemblyName,
Entries = buffer.OrderBy(x => x.Key, StringComparer.OrdinalIgnoreCase).ToArray()
};
return true;
}
private static string Reformat(string summary, bool forTooltip) {
// Tooltips don't support formatting tags. Inline help does.
if (forTooltip) {
summary = Regexes.SeeWithCref.Replace(summary, "$1");
summary = Regexes.See.Replace(summary, "$1");
summary = Regexes.XmlEmphasizeBrackets.Replace(summary, "$1");
} else {
var colorstring = $"<color={CrefColor}>$1</color>";
summary = Regexes.SeeWithCref.Replace(summary, colorstring);
summary = Regexes.See.Replace(summary, colorstring);
}
summary = Regexes.XmlCodeBracket.Replace(summary, "$1");
// Reduce all sequential whitespace characters into a single space.
summary = Regexes.WhitespaceString.Replace(summary, " ");
// Turn <para> and <br> into line breaks
summary = Regex.Replace(summary, @"</para>\s?<para>", "\n\n"); // prevent back to back paras from producing 4 line returns.
summary = Regex.Replace(summary, @"</?para>\s?", "\n\n");
summary = Regex.Replace(summary, @"</?br\s?/?>\s?", "\n\n");
// unescape <>
summary = summary.Replace("&lt;", "<");
summary = summary.Replace("&gt;", ">");
summary = summary.Replace("&amp;", "&");
summary = summary.Trim();
return summary;
}
private class Entry {
public string Key;
public string Summary;
public string Tooltip;
}
private class CodeDoc {
public string AssemblyName;
public Entry[] Entries;
}
private class Postprocessor : AssetPostprocessor {
private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths) {
foreach (var path in importedAssets) {
if (!path.StartsWith("Assets/") || !path.EndsWith(ExtensionWithDot)) {
continue;
}
if (AssetDatabaseUtils.HasLabel(path, Label)) {
FusionEditorLog.Trace($"Code doc {path} was imported, refreshing");
InvalidateCache();
}
// is there a dll with the same name?
if (File.Exists(path.Substring(0, path.Length - ExtensionWithDot.Length) + ".dll")) {
FusionEditorLog.Trace($"Detected a dll next to {path}, applying label and refreshing.");
AssetDatabaseUtils.SetLabel(path, Label, true);
InvalidateCache();
}
}
}
}
private class EntryKeyComparer : IComparer<Entry> {
public static readonly EntryKeyComparer Instance = new EntryKeyComparer();
public static readonly Entry DummyEntry = new Entry();
public int Compare(Entry x, Entry y) {
return string.Compare(x.Key, y.Key, StringComparison.OrdinalIgnoreCase);
}
}
private static class Regexes {
public static readonly Regex SeeWithCref = new(@"<see\w* cref=""(?:\w: ?)?([\w\.\d]*)(?:\(.*\))?"" ?\/>", RegexOptions.None);
public static readonly Regex See = new(@"<see\w* .*>([\w\.\d]*)<\/see\w*>", RegexOptions.None);
public static readonly Regex WhitespaceString = new(@"\s+");
public static readonly Regex XmlCodeBracket = new(@"<code>([\s\S]*?)</code>");
public static readonly Regex XmlEmphasizeBrackets = new(@"<\w>([\s\S]*?)</\w>");
}
}
}
#endregion
#region FusionEditor.cs
namespace Fusion.Editor {
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;
public abstract class FusionEditor :
#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED
Sirenix.OdinInspector.Editor.OdinEditor
#else
UnityEditor.Editor
#endif
{
private EditorButtonDrawer _buttonDrawer;
protected void PrepareOnInspectorGUI() {
FusionEditorGUI.InjectScriptHeaderDrawer(this);
}
protected void DrawEditorButtons() {
_buttonDrawer.Draw(this);
}
public override void OnInspectorGUI() {
PrepareOnInspectorGUI();
base.OnInspectorGUI();
DrawEditorButtons();
}
protected void DrawScriptPropertyField() {
FusionEditorGUI.ScriptPropertyField(this);
}
#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED
public new bool DrawDefaultInspector() {
EditorGUI.BeginChangeCheck();
base.DrawDefaultInspector();
return EditorGUI.EndChangeCheck();
}
#else
protected virtual void OnEnable() {
}
protected virtual void OnDisable() {
}
#endif
}
}
#endregion
#region FusionEditorGUI.InlineHelp.cs
namespace Fusion.Editor {
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;
public static partial class FusionEditorGUI {
private const float SCROLL_WIDTH = 16f;
private const float LEFT_HELP_INDENT = 8f;
private static (object, string) s_expandedHelp;
internal static Rect GetInlineHelpButtonRect(Rect position, bool expectFoldout = true, bool forScriptHeader = false) {
var style = FusionEditorSkin.HelpButtonStyle;
float width = style.fixedWidth <= 0 ? 16.0f : style.fixedWidth;
float height = style.fixedHeight <= 0 ? 16.0f : style.fixedHeight;
// this 2 lower than line height, but makes it look better
const float FirstLineHeight = 16;
int offsetY = forScriptHeader ? -1 : 1;
var buttonRect = new Rect(position.x - width, position.y + (FirstLineHeight - height) / 2 + + offsetY, width, height);
using (new EditorGUI.IndentLevelScope(expectFoldout ? -1 : 0)) {
buttonRect.x = EditorGUI.IndentedRect(buttonRect).x;
// give indented items a little extra padding - no need for them to be so crammed
if (buttonRect.x > 8) {
buttonRect.x -= 2;
}
}
return buttonRect;
}
internal static bool DrawInlineHelpButton(Rect buttonRect, bool state, bool doButton = true, bool doIcon = true) {
var style = FusionEditorSkin.HelpButtonStyle;
var result = false;
if (doButton) {
EditorGUIUtility.AddCursorRect(buttonRect, MouseCursor.Link);
using (new EnabledScope(true)) {
result = GUI.Button(buttonRect, state ? InlineHelpStyle.HideInlineContent : InlineHelpStyle.ShowInlineContent, GUIStyle.none);
}
}
if (doIcon) {
// paint over what the inspector has drawn
if (Event.current.type == EventType.Repaint) {
style.Draw(buttonRect, false, false, state, false);
}
}
return result;
}
internal static Vector2 GetInlineBoxSize(GUIContent content) {
// const int InlineBoxExtraHeight = 4;
var outerStyle = FusionEditorSkin.InlineBoxFullWidthStyle;
var innerStyle = FusionEditorSkin.RichLabelStyle;
var outerMargin = outerStyle.margin;
var outerPadding = outerStyle.padding;
var width = UnityInternal.EditorGUIUtility.contextWidth - outerMargin.left - outerMargin.right;
// well... we do this, because there's no way of knowing the indent and scroll bar existence
// when property height is calculated
width -= 25.0f;
if (content == null || width <= 0) {
return default;
}
width -= outerPadding.left + outerPadding.right;
var height = innerStyle.CalcHeight(content, width);
// assume min height
height = Mathf.Max(height, EditorGUIUtility.singleLineHeight);
// add back all the padding
height += outerPadding.top + outerPadding.bottom;
height += outerMargin.top + outerMargin.bottom;
return new Vector2(width, Mathf.Max(0, height));
}
internal static Rect DrawInlineBoxUnderProperty(GUIContent content, Rect propertyRect, Color color, bool drawSelector = false, bool hasFoldout = false) {
using (new EnabledScope(true)) {
var boxSize = GetInlineBoxSize(content);
if (Event.current.type == EventType.Repaint && boxSize.y > 0) {
var boxMargin = FusionEditorSkin.InlineBoxFullWidthStyle.margin;
var boxRect = new Rect() {
x = boxMargin.left,
y = propertyRect.yMax - boxSize.y,
width = UnityInternal.EditorGUIUtility.contextWidth - boxMargin.horizontal,
height = boxSize.y,
};
using (new BackgroundColorScope(color)) {
FusionEditorSkin.InlineBoxFullWidthStyle.Draw(boxRect, false, false, false, false);
var labelRect = boxRect;
labelRect = FusionEditorSkin.InlineBoxFullWidthStyle.padding.Remove(labelRect);
FusionEditorSkin.RichLabelStyle.Draw(labelRect, content, false, false, false, false);
if (drawSelector) {
var selectorMargin = FusionEditorSkin.InlineSelectorStyle.margin;
var selectorRect = new Rect() {
x = selectorMargin.left,
y = propertyRect.y - selectorMargin.top,
width = propertyRect.x - selectorMargin.horizontal,
height = propertyRect.height - boxSize.y - selectorMargin.bottom,
};
if (hasFoldout) {
selectorRect.width -= 20.0f;
}
FusionEditorSkin.InlineSelectorStyle.Draw(selectorRect, false, false, false, false);
}
}
}
propertyRect.height -= boxSize.y;
return propertyRect;
}
}
internal static void DrawScriptHeaderBackground(Rect position, Color color) {
if (Event.current.type != EventType.Repaint) {
return;
}
var style = FusionEditorSkin.ScriptHeaderBackgroundStyle;
var boxMargin = style.margin;
var boxRect = new Rect() {
x = boxMargin.left,
y = position.y - boxMargin.top,
width = UnityInternal.EditorGUIUtility.contextWidth - boxMargin.horizontal,
height = position.height + boxMargin.bottom,
};
using (new BackgroundColorScope(color)) {
style.Draw(boxRect, false, false, false, false);
}
}
public static void DrawScriptHeaderIcon(Rect position) {
if (Event.current.type != EventType.Repaint) {
return;
}
var style = FusionEditorSkin.ScriptHeaderIconStyle;
var boxMargin = style.margin;
var boxRect = boxMargin.Remove(position);
style.Draw(boxRect, false, false, false, false);
}
public static bool InjectScriptHeaderDrawer(Editor editor) => InjectScriptHeaderDrawer(editor, out _);
internal static bool InjectScriptHeaderDrawer(Editor editor, out ScriptFieldDrawer drawer) => InjectScriptHeaderDrawer(editor.serializedObject, out drawer);
public static bool InjectScriptHeaderDrawer(SerializedObject serializedObject) => InjectScriptHeaderDrawer(serializedObject, out _);
internal static bool InjectScriptHeaderDrawer(SerializedObject serializedObject, out ScriptFieldDrawer drawer) {
var sp = serializedObject.FindPropertyOrThrow(ScriptPropertyName);
var rootType = serializedObject.targetObject.GetType();
var injected = TryInjectDrawer(sp, null, () => null, () => new ScriptFieldDrawer(), out drawer);
if (drawer.attribute == null) {
UnityInternal.PropertyDrawer.SetAttribute(drawer, rootType.GetCustomAttributes<ScriptHelpAttribute>(true).SingleOrDefault() ?? new ScriptHelpAttribute());
}
return injected;
}
public static void SetScriptFieldHidden(Editor editor, bool hidden) {
var sp = editor.serializedObject.FindPropertyOrThrow(ScriptPropertyName);
TryInjectDrawer(sp, null, () => null, () => new ScriptFieldDrawer(), out var drawer);
drawer.ForceHide = hidden;
}
public static Rect LayoutHelpPrefix(Editor editor, SerializedProperty property) {
var fieldInfo = UnityInternal.ScriptAttributeUtility.GetFieldInfoFromProperty(property, out _);
if (fieldInfo == null) {
return EditorGUILayout.GetControlRect(true);
}
var help = FusionCodeDoc.FindEntry(fieldInfo);
return LayoutHelpPrefix(editor, property.propertyPath, help);
}
public static Rect LayoutHelpPrefix(Editor editor, MemberInfo memberInfo) {
var help = FusionCodeDoc.FindEntry(memberInfo);
return LayoutHelpPrefix(editor, memberInfo.Name, help);
}
public static Rect LayoutHelpPrefix(Editor editor, string path, GUIContent help) {
var rect = EditorGUILayout.GetControlRect(true);
if (help == null) {
return rect;
}
var buttonRect = GetInlineHelpButtonRect(rect, false);
var wasExpanded = IsHelpExpanded(editor, path);
if (wasExpanded) {
var helpSize = GetInlineBoxSize(help);
var r = EditorGUILayout.GetControlRect(false, helpSize.y);
r.y = rect.y;
r.height += rect.height;
DrawInlineBoxUnderProperty(help, r, FusionEditorSkin.HelpInlineBoxColor, true);
}
if (DrawInlineHelpButton(buttonRect, wasExpanded, doButton: true, doIcon: true)) {
SetHelpExpanded(editor, path, !wasExpanded);
}
return rect;
}
private static void AddDrawer(SerializedProperty property, PropertyDrawer drawer) {
var handler = UnityInternal.ScriptAttributeUtility.GetHandler(property);
if (handler.m_PropertyDrawers == null) {
handler.m_PropertyDrawers = new List<PropertyDrawer>();
}
InsertPropertyDrawerByAttributeOrder(handler.m_PropertyDrawers, drawer);
}
private static bool TryInjectDrawer<DrawerType>(SerializedProperty property, FieldInfo field, Func<PropertyAttribute> attributeFactory, Func<DrawerType> drawerFactory, out DrawerType drawer)
where DrawerType : PropertyDrawer {
var handler = UnityInternal.ScriptAttributeUtility.GetHandler(property);
drawer = GetPropertyDrawer<DrawerType>(handler.m_PropertyDrawers);
if (drawer != null) {
return false;
}
if (handler.Equals(UnityInternal.ScriptAttributeUtility.sharedNullHandler)) {
// need to add one?
handler = UnityInternal.PropertyHandler.New();
UnityInternal.ScriptAttributeUtility.propertyHandlerCache.SetHandler(property, handler);
}
var attribute = attributeFactory();
drawer = drawerFactory();
FusionEditorLog.Assert(drawer != null, "drawer != null");
UnityInternal.PropertyDrawer.SetAttribute(drawer, attribute);
UnityInternal.PropertyDrawer.SetFieldInfo(drawer, field);
AddDrawer(property, drawer);
return true;
}
internal static bool IsHelpExpanded(object id, string path) {
return s_expandedHelp == (id, path);
}
internal static void SetHelpExpanded(object id, string path, bool value) {
if (value) {
s_expandedHelp = (id, path);
} else {
s_expandedHelp = default;
}
}
private static bool HasPropertyDrawer<T>(IEnumerable<PropertyDrawer> orderedDrawers) where T : PropertyDrawer {
return orderedDrawers?.Any(x => x is T) ?? false;
}
private static T GetPropertyDrawer<T>(IEnumerable<PropertyDrawer> orderedDrawers) where T : PropertyDrawer {
return orderedDrawers?.OfType<T>().FirstOrDefault();
}
internal static int InsertPropertyDrawerByAttributeOrder<T>(List<T> orderedDrawers, T drawer) where T : PropertyDrawer {
if (orderedDrawers == null) {
throw new ArgumentNullException(nameof(orderedDrawers));
}
if (drawer == null) {
throw new ArgumentNullException(nameof(drawer));
}
var index = orderedDrawers.BinarySearch(drawer, PropertyDrawerOrderComparer.Instance);
if (index < 0) {
index = ~index;
}
orderedDrawers.Insert(index, drawer);
return index;
}
internal static class InlineHelpStyle {
public const float MarginOuter = 16.0f;
public static GUIContent HideInlineContent = new("", "Hide");
public static GUIContent ShowInlineContent = new("", "");
}
internal static class LazyAuto {
public static LazyAuto<T> Create<T>(Func<T> valueFactory) {
return new LazyAuto<T>(valueFactory);
}
}
internal class LazyAuto<T> : Lazy<T> {
public LazyAuto(Func<T> valueFactory) : base(valueFactory) {
}
public static implicit operator T(LazyAuto<T> lazy) {
return lazy.Value;
}
}
private class PropertyDrawerOrderComparer : IComparer<PropertyDrawer> {
public static readonly PropertyDrawerOrderComparer Instance = new();
public int Compare(PropertyDrawer x, PropertyDrawer y) {
var ox = x.attribute?.order ?? int.MaxValue;
var oy = y.attribute?.order ?? int.MaxValue;
return ox - oy;
}
}
}
}
#endregion
#region FusionEditorGUI.Odin.cs
namespace Fusion.Editor {
using System;
using UnityEditor;
using UnityEngine;
#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED
using System.Collections.Generic;
using Sirenix.Utilities.Editor;
using Sirenix.OdinInspector.Editor;
using Sirenix.Utilities;
#endif
public static partial class FusionEditorGUI {
public static T IfOdin<T>(T ifOdin, T ifNotOdin) {
#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED
return ifOdin;
#else
return ifNotOdin;
#endif
}
public static UnityEngine.Object ForwardObjectField(Rect position, UnityEngine.Object value, Type objectType, bool allowSceneObjects) {
#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED
return SirenixEditorFields.UnityObjectField(position, value, objectType, allowSceneObjects);
#else
return EditorGUI.ObjectField(position, value, objectType, allowSceneObjects);
#endif
}
public static UnityEngine.Object ForwardObjectField(Rect position, GUIContent label, UnityEngine.Object value, Type objectType, bool allowSceneObjects) {
#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED
return SirenixEditorFields.UnityObjectField(position, label, value, objectType, allowSceneObjects);
#else
return EditorGUI.ObjectField(position, label, value, objectType, allowSceneObjects);
#endif
}
public static bool ForwardPropertyField(Rect position, SerializedProperty property, GUIContent label, bool includeChildren, bool lastDrawer = true) {
#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED
if (lastDrawer) {
switch (property.propertyType) {
case SerializedPropertyType.ObjectReference: {
EditorGUI.BeginChangeCheck();
UnityInternal.ScriptAttributeUtility.GetFieldInfoFromProperty(property, out var fieldType);
var value = SirenixEditorFields.UnityObjectField(position, label, property.objectReferenceValue, fieldType ?? typeof(UnityEngine.Object), true);
if (EditorGUI.EndChangeCheck()) {
property.objectReferenceValue = value;
}
return false;
}
case SerializedPropertyType.Integer: {
EditorGUI.BeginChangeCheck();
var value = SirenixEditorFields.IntField(position, label, property.intValue);
if (EditorGUI.EndChangeCheck()) {
property.intValue = value;
}
return false;
}
case SerializedPropertyType.Float: {
EditorGUI.BeginChangeCheck();
var value = SirenixEditorFields.FloatField(position, label, property.floatValue);
if (EditorGUI.EndChangeCheck()) {
property.floatValue = value;
}
return false;
}
case SerializedPropertyType.Color: {
EditorGUI.BeginChangeCheck();
var value = SirenixEditorFields.ColorField(position, label, property.colorValue);
if (EditorGUI.EndChangeCheck()) {
property.colorValue = value;
}
return false;
}
case SerializedPropertyType.Vector2: {
EditorGUI.BeginChangeCheck();
var value = SirenixEditorFields.Vector2Field(position, label, property.vector2Value);
if (EditorGUI.EndChangeCheck()) {
property.vector2Value = value;
}
return false;
}
case SerializedPropertyType.Vector3: {
EditorGUI.BeginChangeCheck();
var value = SirenixEditorFields.Vector3Field(position, label, property.vector3Value);
if (EditorGUI.EndChangeCheck()) {
property.vector3Value = value;
}
return false;
}
case SerializedPropertyType.Vector4: {
EditorGUI.BeginChangeCheck();
var value = SirenixEditorFields.Vector4Field(position, label, property.vector4Value);
if (EditorGUI.EndChangeCheck()) {
property.vector4Value = value;
}
return false;
}
case SerializedPropertyType.Quaternion: {
EditorGUI.BeginChangeCheck();
var value = SirenixEditorFields.RotationField(position, label, property.quaternionValue, GlobalConfig<GeneralDrawerConfig>.Instance.QuaternionDrawMode);
if (EditorGUI.EndChangeCheck()) {
property.quaternionValue = value;
}
return false;
}
case SerializedPropertyType.String: {
EditorGUI.BeginChangeCheck();
var value = SirenixEditorFields.TextField(position, label, property.stringValue);
if (EditorGUI.EndChangeCheck()) {
property.stringValue = value;
}
return false;
}
case SerializedPropertyType.Enum: {
UnityInternal.ScriptAttributeUtility.GetFieldInfoFromProperty(property, out var type);
if (type != null && type.IsEnum) {
EditorGUI.BeginChangeCheck();
bool flags = type.GetCustomAttributes(typeof(FlagsAttribute), false).Length > 0;
Enum value = SirenixEditorFields.EnumDropdown(position, label, (Enum)Enum.ToObject(type, property.intValue));
if (EditorGUI.EndChangeCheck()) {
property.intValue = Convert.ToInt32(Convert.ChangeType(value, Enum.GetUnderlyingType(type)));
}
return false;
}
break;
}
default:
break;
}
}
#endif
return EditorGUI.PropertyField(position, property, label, includeChildren);
}
}
}
#endregion
#region FusionEditorGUI.Scopes.cs
namespace Fusion.Editor {
using System;
using System.Runtime.CompilerServices;
using UnityEditor;
using UnityEngine;
public static partial class FusionEditorGUI {
public sealed class CustomEditorScope : IDisposable {
private SerializedObject serializedObject;
public bool HadChanges { get; private set; }
public CustomEditorScope(SerializedObject so) {
serializedObject = so;
EditorGUI.BeginChangeCheck();
so.UpdateIfRequiredOrScript();
ScriptPropertyField(so);
}
public void Dispose() {
HadChanges = EditorGUI.EndChangeCheck();
serializedObject.ApplyModifiedProperties();
}
}
public struct EnabledScope: IDisposable {
private readonly bool value;
public EnabledScope(bool enabled) {
value = GUI.enabled;
GUI.enabled = enabled;
}
public void Dispose() {
GUI.enabled = value;
}
}
public readonly struct BackgroundColorScope : IDisposable {
private readonly Color value;
public BackgroundColorScope(Color color) {
value = GUI.backgroundColor;
GUI.backgroundColor = color;
}
public void Dispose() {
GUI.backgroundColor = value;
}
}
public struct ColorScope: IDisposable {
private readonly Color value;
public ColorScope(Color color) {
value = GUI.color;
GUI.color = color;
}
public void Dispose() {
GUI.color = value;
}
}
public struct ContentColorScope: IDisposable {
private readonly Color value;
public ContentColorScope(Color color) {
value = GUI.contentColor;
GUI.contentColor = color;
}
public void Dispose() {
GUI.contentColor = value;
}
}
public struct FieldWidthScope: IDisposable {
private readonly float value;
public FieldWidthScope(float fieldWidth) {
value = EditorGUIUtility.fieldWidth;
EditorGUIUtility.fieldWidth = fieldWidth;
}
public void Dispose() {
EditorGUIUtility.fieldWidth = value;
}
}
public struct HierarchyModeScope: IDisposable {
private readonly bool value;
public HierarchyModeScope(bool value) {
this.value = EditorGUIUtility.hierarchyMode;
EditorGUIUtility.hierarchyMode = value;
}
public void Dispose() {
EditorGUIUtility.hierarchyMode = value;
}
}
public struct IndentLevelScope: IDisposable {
private readonly int value;
public IndentLevelScope(int indentLevel) {
value = EditorGUI.indentLevel;
EditorGUI.indentLevel = indentLevel;
}
public void Dispose() {
EditorGUI.indentLevel = value;
}
}
public struct LabelWidthScope: IDisposable {
private readonly float value;
public LabelWidthScope(float labelWidth) {
value = EditorGUIUtility.labelWidth;
EditorGUIUtility.labelWidth = labelWidth;
}
public void Dispose() {
EditorGUIUtility.labelWidth = value;
}
}
public struct ShowMixedValueScope: IDisposable {
private readonly bool value;
public ShowMixedValueScope(bool show) {
value = EditorGUI.showMixedValue;
EditorGUI.showMixedValue = show;
}
public void Dispose() {
EditorGUI.showMixedValue = value;
}
}
public struct PropertyScope : IDisposable {
public PropertyScope(Rect position, GUIContent label, SerializedProperty property) {
EditorGUI.BeginProperty(position, label, property);
}
public void Dispose() {
EditorGUI.EndProperty();
}
}
public readonly struct PropertyScopeWithPrefixLabel : IDisposable {
private readonly int indent;
public PropertyScopeWithPrefixLabel(Rect position, GUIContent label, SerializedProperty property, out Rect indentedPosition) {
EditorGUI.BeginProperty(position, label, property);
indentedPosition = EditorGUI.PrefixLabel(position, label);
indent = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0;
}
public void Dispose() {
EditorGUI.indentLevel = indent;
EditorGUI.EndProperty();
}
}
public readonly struct BoxScope: IDisposable {
private readonly int _indent;
/// <summary>
///if fields include inline help (?) buttons, use indent : 1
/// </summary>
public BoxScope(string message, int indent = 0, float space = 0.0f) {
EditorGUILayout.BeginVertical(FusionEditorSkin.OutlineBoxStyle);
if (!string.IsNullOrEmpty(message)) {
EditorGUILayout.LabelField(message, EditorStyles.boldLabel);
}
if (space > 0.0f) {
GUILayout.Space(space);
}
_indent = EditorGUI.indentLevel;
if (indent != 0) {
EditorGUI.indentLevel += indent;
}
}
public void Dispose() {
EditorGUI.indentLevel = _indent;
EditorGUILayout.EndVertical();
}
}
public struct WarningScope: IDisposable {
public WarningScope(string message, float space = 0.0f) {
var backgroundColor = GUI.backgroundColor;
GUI.backgroundColor = FusionEditorSkin.WarningInlineBoxColor;
EditorGUILayout.BeginVertical(FusionEditorSkin.InlineBoxFullWidthScopeStyle);
GUI.backgroundColor = backgroundColor;
EditorGUILayout.LabelField(new GUIContent(message, FusionEditorSkin.WarningIcon), FusionEditorSkin.RichLabelStyle);
if (space > 0.0f) {
GUILayout.Space(space);
}
}
public void Dispose() {
EditorGUILayout.EndVertical();
}
}
public struct ErrorScope : IDisposable {
public ErrorScope(string message, float space = 0.0f) {
var backgroundColor = GUI.backgroundColor;
GUI.backgroundColor = FusionEditorSkin.ErrorInlineBoxColor;
EditorGUILayout.BeginVertical(FusionEditorSkin.InlineBoxFullWidthScopeStyle);
GUI.backgroundColor = backgroundColor;
EditorGUILayout.LabelField(new GUIContent(message, FusionEditorSkin.ErrorIcon), FusionEditorSkin.RichLabelStyle);
if (space > 0.0f) {
GUILayout.Space(space);
}
}
public void Dispose() {
EditorGUILayout.EndVertical();
}
}
public readonly struct GUIContentScope : IDisposable {
private readonly string _text;
private readonly string _tooltip;
private readonly GUIContent _content;
public GUIContentScope(GUIContent content) {
_content = content;
_text = content?.text;
_tooltip = content?.tooltip;
}
public void Dispose() {
if (_content != null) {
_content.text = _text;
_content.tooltip = _tooltip;
}
}
}
}
}
#endregion
#region FusionEditorGUI.Utils.cs
namespace Fusion.Editor {
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;
public static partial class FusionEditorGUI {
public const string ScriptPropertyName = "m_Script";
private const int IconHeight = 14;
public static readonly GUIContent WhitespaceContent = new(" ");
public static Color PrefebOverridenColor => new(1f / 255f, 153f / 255f, 235f / 255f, 0.75f);
public static float FoldoutWidth => 16.0f;
public static Rect Decorate(Rect rect, string tooltip, MessageType messageType, bool hasLabel = false, bool drawBorder = true, bool drawButton = true) {
if (hasLabel) {
rect.xMin += EditorGUIUtility.labelWidth;
}
var content = EditorGUIUtility.TrTextContentWithIcon(string.Empty, tooltip, messageType);
var iconRect = rect;
iconRect.width = Mathf.Min(16, rect.width);
iconRect.xMin -= iconRect.width;
iconRect.y += (iconRect.height - IconHeight) / 2;
iconRect.height = IconHeight;
if (drawButton) {
using (new EnabledScope(true)) {
GUI.Label(iconRect, content, GUIStyle.none);
}
}
//GUI.Label(iconRect, content, new GUIStyle());
if (drawBorder) {
Color borderColor;
switch (messageType) {
case MessageType.Warning:
borderColor = new Color(1.0f, 0.5f, 0.0f);
break;
case MessageType.Error:
borderColor = new Color(1.0f, 0.0f, 0.0f);
break;
default:
borderColor = new Color(1f, 1f, 1f, .0f);
break;
}
GUI.DrawTexture(rect, Texture2D.whiteTexture, ScaleMode.StretchToFill, false, 0, borderColor, 1.0f, 1.0f);
}
return iconRect;
}
public static void AppendTooltip(string tooltip, ref GUIContent label) {
if (!string.IsNullOrEmpty(tooltip)) {
label = new GUIContent(label);
if (string.IsNullOrEmpty(label.tooltip)) {
label.tooltip = tooltip;
} else {
label.tooltip += "\n\n" + tooltip;
}
}
}
public static void ScriptPropertyField(Editor editor) {
ScriptPropertyField(editor.serializedObject);
}
public static void ScriptPropertyField(SerializedObject obj) {
var scriptProperty = obj.FindProperty(ScriptPropertyName);
if (scriptProperty != null) {
using (new EditorGUI.DisabledScope(true)) {
EditorGUILayout.PropertyField(scriptProperty);
}
}
}
public static void Overlay(Rect position, string label) {
GUI.Label(position, label, FusionEditorSkin.OverlayLabelStyle);
}
public static void Overlay(Rect position, GUIContent label) {
GUI.Label(position, label, FusionEditorSkin.OverlayLabelStyle);
}
public static float GetLinesHeight(int count) {
return count * (EditorGUIUtility.singleLineHeight) + (count - 1) * EditorGUIUtility.standardVerticalSpacing;
}
public static float GetLinesHeightWithNarrowModeSupport(int count) {
if (!EditorGUIUtility.wideMode) {
count++;
}
return count * (EditorGUIUtility.singleLineHeight) + (count - 1) * EditorGUIUtility.standardVerticalSpacing;
}
public static System.Type GetDrawerTypeIncludingWorkarounds(System.Attribute attribute) {
var drawerType = UnityInternal.ScriptAttributeUtility.GetDrawerTypeForType(attribute.GetType(), null, false);
if (drawerType == null) {
return null;
}
if (drawerType == typeof(PropertyDrawerForArrayWorkaround)) {
drawerType = PropertyDrawerForArrayWorkaround.GetDrawerType(attribute.GetType());
}
return drawerType;
}
public static void DisplayTypePickerMenu(Rect position, Type[] baseTypes, Action<Type> callback, Func<Type, bool> filter, string noneOptionLabel = "[None]", bool groupByNamespace = true, Type selectedType = null) {
var types = new List<Type>();
foreach (var baseType in baseTypes) {
types.AddRange(TypeCache.GetTypesDerivedFrom(baseType).Where(filter));
}
if (baseTypes.Length > 1) {
types = types.Distinct().ToList();
}
types.Sort((a, b) => string.CompareOrdinal(a.FullName, b.FullName));
List<GUIContent> menuOptions = new List<GUIContent>();
var actualTypes = new Dictionary<string, System.Type>();
menuOptions.Add(new GUIContent(noneOptionLabel));
actualTypes.Add(noneOptionLabel, null);
int selectedIndex = -1;
foreach (var ns in types.GroupBy(x => string.IsNullOrEmpty(x.Namespace) ? "[Global Namespace]" : x.Namespace)) {
foreach (var t in ns) {
var nameWithoutNamespace = t.FullName;
if (string.IsNullOrEmpty(nameWithoutNamespace)) {
continue;
}
if (t.Namespace != null && t.Namespace.Length > 0) {
nameWithoutNamespace = nameWithoutNamespace.Substring(t.Namespace.Length + 1);
}
string path;
if (groupByNamespace) {
path = ns.Key + "/" + nameWithoutNamespace;
} else {
path = t.FullName;
}
if (actualTypes.ContainsKey(path)) {
continue;
}
menuOptions.Add(new GUIContent(path));
actualTypes.Add(path, t);
if (selectedType == t) {
selectedIndex = menuOptions.Count - 1;
}
}
}
EditorUtility.DisplayCustomMenu(position, menuOptions.ToArray(), selectedIndex, (userData, options, selected) => {
var path = options[selected];
var newType = ((Dictionary<string, System.Type>)userData)[path];
callback(newType);
}, actualTypes);
}
public static void DisplayTypePickerMenu(Rect position, Type[] baseTypes, Action<Type> callback, string noneOptionLabel = "[None]", bool groupByNamespace = true, Type selectedType = null, bool enableAbstract = false, bool enableGenericTypeDefinitions = false) {
DisplayTypePickerMenu(position, baseTypes, callback, x =>
(enableAbstract || !x.IsAbstract) &&
(enableGenericTypeDefinitions || !x.IsGenericTypeDefinition));
}
public static void DisplayTypePickerMenu(Rect position, Type baseType, Action<Type> callback, string noneOptionLabel = "[None]", bool groupByNamespace = true, Type selectedType = null, bool enableAbstract = false, bool enableGenericTypeDefinitions = false) {
DisplayTypePickerMenu(position, new [] { baseType }, callback, x =>
(enableAbstract || !x.IsAbstract) &&
(enableGenericTypeDefinitions || !x.IsGenericTypeDefinition));
}
}
}
#endregion
#region FusionEditorUtility.cs
namespace Fusion.Editor {
using System;
using UnityEditor;
partial class FusionEditorUtility {
public static void DelayCall(EditorApplication.CallbackFunction callback) {
FusionEditorLog.Assert(callback.Target == null, "DelayCall callback needs to stateless");
EditorApplication.delayCall -= callback;
EditorApplication.delayCall += callback;
}
}
}
#endregion
#region FusionGlobalScriptableObjectEditorAttribute.cs
namespace Fusion.Editor {
using System;
using UnityEditor;
class FusionGlobalScriptableObjectEditorAttribute : FusionGlobalScriptableObjectSourceAttribute {
public FusionGlobalScriptableObjectEditorAttribute(Type objectType) : base(objectType) {
}
public override FusionGlobalScriptableObjectLoadResult Load(Type type) {
var defaultAssetPath = FusionGlobalScriptableObjectUtils.FindDefaultAssetPath(type, fallbackToSearchWithoutLabel: true);
if (string.IsNullOrEmpty(defaultAssetPath)) {
return default;
}
var result = (FusionGlobalScriptableObject)AssetDatabase.LoadAssetAtPath(defaultAssetPath, type);
FusionEditorLog.Assert(result);
return result;
}
}
}
#endregion
#region FusionGlobalScriptableObjectUtils.cs
namespace Fusion.Editor {
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;
public static class FusionGlobalScriptableObjectUtils {
public const string GlobalAssetLabel = "FusionDefaultGlobal";
public static void SetDirty(this FusionGlobalScriptableObject obj) {
EditorUtility.SetDirty(obj);
}
public static string GetGlobalAssetPath<T>() where T : FusionGlobalScriptableObject<T> {
return FindDefaultAssetPath(typeof(T), fallbackToSearchWithoutLabel: false);
}
public static bool TryGetGlobalAssetPath<T>(out string path) where T : FusionGlobalScriptableObject<T> {
path = FindDefaultAssetPath(typeof(T), fallbackToSearchWithoutLabel: false);
return !string.IsNullOrEmpty(path);
}
private static FusionGlobalScriptableObjectAttribute GetAttributeOrThrow(Type type) {
var attribute = type.GetCustomAttribute<FusionGlobalScriptableObjectAttribute>();
if (attribute == null) {
throw new InvalidOperationException($"Type {type.FullName} needs to be decorated with {nameof(FusionGlobalScriptableObjectAttribute)}");
}
return attribute;
}
public static bool EnsureAssetExists<T>() where T : FusionGlobalScriptableObject<T> {
var defaultAssetPath = FindDefaultAssetPath(typeof(T), fallbackToSearchWithoutLabel: true);
if (!string.IsNullOrEmpty(defaultAssetPath)) {
// already exists
return false;
}
// need to create a new asset
CreateDefaultAsset(typeof(T));
return true;
}
private static FusionGlobalScriptableObject CreateDefaultAsset(Type type) {
var attribute = GetAttributeOrThrow(type);
var directoryPath = Path.GetDirectoryName(attribute.DefaultPath);
if (!string.IsNullOrEmpty(directoryPath) && !Directory.Exists(directoryPath)) {
Directory.CreateDirectory(directoryPath);
AssetDatabase.Refresh();
}
if (File.Exists(attribute.DefaultPath)) {
throw new InvalidOperationException($"Asset file already exists at '{attribute.DefaultPath}'");
}
// is this a regular asset?
if (attribute.DefaultPath.EndsWith(".asset", StringComparison.OrdinalIgnoreCase)) {
var instance = (FusionGlobalScriptableObject)ScriptableObject.CreateInstance(type);
AssetDatabase.CreateAsset(instance, attribute.DefaultPath);
AssetDatabase.SaveAssets();
SetGlobal(instance);
EditorUtility.SetDirty(instance);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
FusionEditorLog.TraceImport($"Created new global {type.Name} instance at {attribute.DefaultPath}");
return instance;
} else {
string defaultContents = null;
if (!string.IsNullOrEmpty(attribute.DefaultContentsGeneratorMethod)) {
var method = type.GetMethod(attribute.DefaultContentsGeneratorMethod, BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
if (method == null) {
throw new InvalidOperationException($"Generator method '{attribute.DefaultContentsGeneratorMethod}' not found on type {type.FullName}");
}
defaultContents = (string)method.Invoke(null, null);
}
if (defaultContents == null) {
defaultContents = attribute.DefaultContents;
}
File.WriteAllText(attribute.DefaultPath, defaultContents ?? string.Empty);
AssetDatabase.ImportAsset(attribute.DefaultPath, ImportAssetOptions.ForceUpdate);
var instance = (FusionGlobalScriptableObject)AssetDatabase.LoadAssetAtPath(attribute.DefaultPath, type);
if (!instance) {
throw new InvalidOperationException($"Failed to load a newly created asset at '{attribute.DefaultPath}'");
}
SetGlobal(instance);
FusionEditorLog.TraceImport($"Created new global {type.Name} instance at {attribute.DefaultPath}");
return instance;
}
}
private static bool IsDefault(this FusionGlobalScriptableObject obj) {
return Array.IndexOf(AssetDatabase.GetLabels(obj), GlobalAssetLabel) >= 0;
}
private static bool SetGlobal(FusionGlobalScriptableObject obj) {
var labels = AssetDatabase.GetLabels(obj);
if (Array.IndexOf(labels, GlobalAssetLabel) >= 0) {
return false;
}
Array.Resize(ref labels, labels.Length + 1);
labels[^1] = GlobalAssetLabel;
AssetDatabase.SetLabels(obj, labels);
return true;
}
internal static string FindDefaultAssetPath(Type type, bool fallbackToSearchWithoutLabel = false) {
// first try to locate assets of the type, with the default label
var defaults = AssetDatabaseUtils.IterateAssets(type: type, label: GlobalAssetLabel)
.Select(x => AssetDatabase.GUIDToAssetPath(x.guid))
.ToList();
if (defaults.Count == 1) {
return defaults[0];
}
if (defaults.Count > 1) {
throw new InvalidOperationException($"There are multiple assets of type '{type.Name}' marked as default: '{string.Join("', '", defaults)}'. Remove all labels but one.");
}
if (fallbackToSearchWithoutLabel) {
// now as a fallback, locate all assets of the type and apply the label only if there is only one
var candidates = AssetDatabaseUtils.IterateAssets(type: type)
.Select(x => AssetDatabase.GUIDToAssetPath(x.guid))
.ToList();
if (candidates.Count == 1) {
// mark as default
AssetDatabaseUtils.SetLabel(candidates[0], GlobalAssetLabel, true);
EditorUtility.SetDirty(AssetDatabase.LoadMainAssetAtPath(candidates[0]));
FusionEditorLog.Log($"Set '{candidates[0]}' as the default asset for '{type.Name}'");
return candidates[0];
}
if (candidates.Count > 1) {
throw new InvalidOperationException($"There are no assets of type '{type.Name}' with {GlobalAssetLabel}, but there are multiple candidates: '{string.Join("', '", candidates)}'. Assign label manually or remove all but one.");
}
}
// no candidates
return string.Empty;
}
public static bool TryImportGlobal<T>() where T : FusionGlobalScriptableObject<T> {
var globalPath = GetGlobalAssetPath<T>();
if (string.IsNullOrEmpty(globalPath)) {
return false;
}
AssetDatabase.ImportAsset(globalPath);
return true;
}
}
}
#endregion
#region FusionGrid.cs
namespace Fusion.Editor {
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
using Object = UnityEngine.Object;
[Serializable]
public class FusionGridState : TreeViewState {
public MultiColumnHeaderState HeaderState;
public bool SyncSelection;
}
public class FusionGridItem : TreeViewItem {
public virtual Object TargetObject => null;
}
public abstract class FusionGrid<TItem> : FusionGrid<TItem, FusionGridState>
where TItem : FusionGridItem {
}
[Serializable]
public abstract class FusionGrid<TItem, TState>
where TState : FusionGridState, new()
where TItem : FusionGridItem
{
[SerializeField] public bool HasValidState;
[SerializeField] public TState State;
[SerializeField] public float UpdatePeriod = 1.0f;
class GUIState {
public InternalTreeView TreeView;
public MultiColumnHeader MultiColumnHeader;
public SearchField SearchField;
}
[NonSerialized] private Lazy<GUIState> _gui;
[NonSerialized] private Lazy<Column[]> _columns;
[NonSerialized] private float _nextUpdateTime;
[NonSerialized] private int _lastContentHash;
public virtual int GetContentHash() {
return 0;
}
public FusionGrid() {
ResetColumns();
ResetGUI();
}
void ResetColumns() {
_columns = new Lazy<Column[]>(() => {
var columns = CreateColumns().ToArray();
for (int i = 0; i < columns.Length; ++i) {
((MultiColumnHeaderState.Column)columns[i]).userData = i;
}
return columns;
});
}
void ResetGUI() {
_gui = new Lazy<GUIState>(() => {
var result = new GUIState();
result.MultiColumnHeader = new MultiColumnHeader(State.HeaderState);
result.MultiColumnHeader.sortingChanged += _ => result.TreeView.Reload();
result.MultiColumnHeader.ResizeToFit();
result.SearchField = new SearchField();
result.SearchField.downOrUpArrowKeyPressed += () => result.TreeView.SetFocusAndEnsureSelectedItem();
result.TreeView = new InternalTreeView(this, result.MultiColumnHeader);
return result;
});
}
public void OnInspectorUpdate() {
if (!HasValidState) {
return;
}
if (!_gui.IsValueCreated) {
return;
}
if (_nextUpdateTime > Time.realtimeSinceStartup) {
return;
}
_nextUpdateTime = Time.realtimeSinceStartup + UpdatePeriod;
var hash = GetContentHash();
if (_lastContentHash == hash) {
return;
}
_lastContentHash = hash;
_gui.Value.TreeView.Reload();
}
public void OnEnable() {
if (HasValidState) {
return;
}
var visibleColumns = new List<int>();
int sortingColumn = -1;
for (int i = 0; i < _columns.Value.Length; ++i) {
var column = _columns.Value[i];
if (!column.initiallyVisible) {
continue;
}
visibleColumns.Add(i);
if (sortingColumn < 0 && column.initiallySorted) {
sortingColumn = i;
column.sortedAscending = true;
}
}
var headerState = new MultiColumnHeaderState(_columns.Value.Cast<MultiColumnHeaderState.Column>().ToArray()) {
visibleColumns = visibleColumns.ToArray(),
sortedColumnIndex = sortingColumn,
};
State = new TState() { HeaderState = headerState };
HasValidState = true;
ResetGUI();
}
public void OnGUI(Rect rect) {
_gui.Value.TreeView.OnGUI(rect);
}
public void DrawToolbarReloadButton() {
if (GUILayout.Button(new GUIContent(FusionEditorSkin.RefreshIcon, "Refresh"), EditorStyles.toolbarButton, GUILayout.ExpandWidth(false))) {
_gui.Value.TreeView.Reload();
}
}
public void DrawToolbarSyncSelectionButton() {
EditorGUI.BeginChangeCheck();
State.SyncSelection = GUILayout.Toggle(State.SyncSelection, "Sync Selection", EditorStyles.toolbarButton);
if (EditorGUI.EndChangeCheck()) {
if (State.SyncSelection) {
_gui.Value.TreeView.SyncSelection();
}
}
}
public void DrawToolbarSearchField() {
_gui.Value.TreeView.searchString = _gui.Value.SearchField.OnToolbarGUI(_gui.Value.TreeView.searchString);
}
public void DrawToolbarResetView() {
if (GUILayout.Button("Reset View", EditorStyles.toolbarButton, GUILayout.ExpandWidth(false))) {
HasValidState = false;
ResetColumns();
}
}
public void ResetTree() {
ResetGUI();
}
protected abstract IEnumerable<Column> CreateColumns();
protected abstract IEnumerable<TItem> CreateRows();
protected virtual GenericMenu CreateContextMenu(TItem item, TreeView treeView) {
return null;
}
protected static Column MakeSimpleColumn<T>(Expression<Func<TItem, T>> propertyExpression, Column column) {
string propertyName;
if (propertyExpression.Body is MemberExpression memberExpression) {
propertyName = memberExpression.Member.Name;
} else {
throw new ArgumentException("Expression is not a member access expression.");
}
var accessor = propertyExpression.Compile();
Func<TItem, string> toString = item => $"{accessor(item)}";
column.getSearchText ??= toString;
column.getComparer ??= order => (a, b) => EditorUtility.NaturalCompare(toString(a), toString(b)) * order;
column.cellGUI ??= (item, rect, selected, focused) => TreeView.DefaultGUI.Label(rect, toString(item), selected, focused);
if (string.IsNullOrEmpty(column.headerContent.text) && string.IsNullOrEmpty(column.headerContent.tooltip)) {
column.headerContent = new GUIContent(propertyName);
}
return column;
}
public class Column : MultiColumnHeaderState.Column {
public Func<TItem, string> getSearchText;
public Func<int, Comparison<TItem>> getComparer;
public Action<TItem, Rect, bool, bool> cellGUI;
public bool initiallyVisible = true;
public bool initiallySorted;
//
// [Obsolete("Do not use", true)]
// public new int userData => throw new NotImplementedException();
}
class InternalTreeView : TreeView {
public InternalTreeView(FusionGrid<TItem, TState> grid, MultiColumnHeader header) : base(grid.State, header) {
Grid = grid;
showAlternatingRowBackgrounds = true;
this.Reload();
}
public new TState state => (TState)base.state;
public FusionGrid<TItem, TState> Grid { get; }
protected override void SelectionChanged(IList<int> selectedIds) {
base.SelectionChanged(selectedIds);
if (state.SyncSelection) {
SyncSelection();
}
}
protected override void SingleClickedItem(int id) {
var item = (TItem)FindItem(id, rootItem);
var obj = item.TargetObject;
if (obj) {
EditorGUIUtility.PingObject(obj);
}
base.SingleClickedItem(id);
}
public void SyncSelection() {
List<Object> selection = new List<Object>();
foreach (var id in this.state.selectedIDs) {
if (id == 0) {
continue;
}
var item = (TItem)FindItem(id, rootItem);
var obj = item.TargetObject;
if (obj) {
selection.Add(obj);
}
}
Selection.objects = selection.ToArray();
}
private Column GetColumnForIndex(int index) {
var column = multiColumnHeader.GetColumn(index);
var ud = column.userData;
return Grid._columns.Value[ud];
}
protected override TreeViewItem BuildRoot() {
var allItems = new List<TItem>();
var root = new TreeViewItem {
id = 0,
depth = -1,
displayName = "Root"
};
foreach (var row in Grid.CreateRows()) {
allItems.Add(row);
}
SetupParentsAndChildrenFromDepths(root, allItems.Cast<TreeViewItem>().ToList());
return root;
}
private class ComparisonComparer : IComparer<TItem> {
public Comparison<TItem> Comparison;
public int Compare(TItem x, TItem y) => Comparison(x, y);
}
private Comparison<TItem> GetComparision() {
if (multiColumnHeader.sortedColumnIndex < 0) {
return null;
}
var column = GetColumnForIndex(multiColumnHeader.sortedColumnIndex);
var isSortedAscending = multiColumnHeader.IsSortedAscending(multiColumnHeader.sortedColumnIndex);
return column.getComparer(isSortedAscending ? 1 : -1);
}
protected override IList<TreeViewItem> BuildRows(TreeViewItem root) {
var comparision = GetComparision();
if (comparision == null) {
return base.BuildRows(root);
}
// stable sort
return base.BuildRows(root).OrderBy(x => (TItem)x, new ComparisonComparer() { Comparison = comparision }).ToArray();
}
protected override void ContextClickedItem(int id) {
var item = (TItem)FindItem(id, rootItem);
if (item == null) {
return;
}
var menu = Grid.CreateContextMenu(item, this);
if (menu != null) {
menu.ShowAsContext();
}
}
protected override void RowGUI(RowGUIArgs args) {
for (var i = 0; i < args.GetNumVisibleColumns(); ++i) {
var cellRect = args.GetCellRect(i);
CenterRectUsingSingleLineHeight(ref cellRect);
var item = (TItem)args.item;
var column = GetColumnForIndex(args.GetColumn(i));
column.cellGUI?.Invoke(item, cellRect, args.selected, args.focused);
}
}
protected override bool DoesItemMatchSearch(TreeViewItem item_, string search) {
var item = item_ as TItem;
if (item == null) {
return base.DoesItemMatchSearch(item_, search);
}
var searchParts = (search ?? "").Split(' ', StringSplitOptions.RemoveEmptyEntries);
if (searchParts.Length == 0) {
return true;
}
var columns = multiColumnHeader.state.columns;
for (var i = 0; i < columns.Length; ++i) {
if (!multiColumnHeader.IsColumnVisible(i)) {
continue;
}
var column = GetColumnForIndex(i);
var text = column.getSearchText?.Invoke(item);
if (text == null) {
continue;
}
bool columnMatchesSearch = true;
foreach (var part in searchParts) {
if (!text.Contains(part, StringComparison.OrdinalIgnoreCase)) {
columnMatchesSearch = false;
break;
}
}
if (columnMatchesSearch) {
return true;
}
}
return false;
}
}
class InternalTreeViewItem : TreeViewItem {
}
}
}
#endregion
#region FusionMonoBehaviourDefaultEditor.cs
namespace Fusion.Editor {
using UnityEditor;
[CustomEditor(typeof(FusionMonoBehaviour), true)]
internal class FusionMonoBehaviourDefaultEditor : FusionEditor {
}
}
#endregion
#region FusionPropertyDrawerMetaAttribute.cs
namespace Fusion.Editor {
using System;
[AttributeUsage(AttributeTargets.Class)]
public class FusionPropertyDrawerMetaAttribute : Attribute {
public bool HasFoldout { get; set; }
public bool HandlesUnits { get; set; }
}
}
#endregion
#region FusionScriptableObjectDefaultEditor.cs
namespace Fusion.Editor {
using UnityEditor;
[CustomEditor(typeof(FusionScriptableObject), true)]
internal class FusionScriptableObjectDefaultEditor : FusionEditor {
}
}
#endregion
#region RawDataDrawer.cs
namespace Fusion.Editor {
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using UnityEditor;
using UnityEngine;
public struct RawDataDrawer {
private StringBuilder _builder;
private GUIContent _lastValue;
private int _lastHash;
public void Clear() {
_builder?.Clear();
_lastHash = 0;
_lastValue = GUIContent.none;
}
public bool HasContent => _lastValue != null && _lastValue.text.Length > 0;
public unsafe void Refresh<T>(Span<T> data, int maxLength = 2048, bool addSpaces = true) where T : unmanaged {
int charactersPerElement = 2 * sizeof(T);
int arrayHash = 0;
int effectiveArraySize;
{
int length = 0;
int i;
for (i = 0; i < data.Length && length < maxLength; ++i) {
arrayHash = arrayHash * 31 + data[i].GetHashCode();
length += charactersPerElement;
if (addSpaces) {
length += 1;
}
}
effectiveArraySize = i;
}
if (_builder == null || arrayHash != _lastHash) {
var format = "{0:x" + charactersPerElement + "}" + (addSpaces ? " " : "");
_builder ??= new StringBuilder();
_builder.Clear();
for (int i = 0; i < effectiveArraySize; ++i) {
_builder.AppendFormat(format, data[i]);
}
if (effectiveArraySize < data.Length) {
_builder.AppendLine("...");
}
_lastHash = arrayHash;
_lastValue = new GUIContent(_builder.ToString());
} else {
Debug.Assert(_lastValue != null);
}
}
public void Refresh(IList<byte> values, int maxLength = 2048) {
Assert.Check(values != null);
const int charactersPerElement = 2;
int arraySize = values.Count;
int arrayHash = 0;
int effectiveArraySize;
{
int length = 0;
int i;
for (i = 0; i < arraySize && length < maxLength; ++i) {
arrayHash = arrayHash * 31 + values[i];
length += charactersPerElement + 1;
}
effectiveArraySize = i;
}
if (_builder == null || arrayHash != _lastHash) {
var format = "{0:x" + charactersPerElement + "} ";
_builder ??= new StringBuilder();
_builder.Clear();
for (int i = 0; i < effectiveArraySize; ++i) {
_builder.AppendFormat(format, values[i]);
}
if (effectiveArraySize < arraySize) {
_builder.AppendLine("...");
}
_lastHash = arrayHash;
_lastValue = new GUIContent(_builder.ToString());
} else {
Debug.Assert(_lastValue != null);
}
}
public void Refresh(SerializedProperty property, int maxLength = 2048) {
Assert.Check(property != null);
Assert.Check(property.isArray);
int charactersPerElement;
switch (property.arrayElementType) {
case "long":
case "ulong":
charactersPerElement = 16;
break;
case "int":
case "uint":
charactersPerElement = 8;
break;
case "short":
case "ushort":
charactersPerElement = 4;
break;
case "sbyte":
case "byte":
charactersPerElement = 2;
break;
default:
throw new NotImplementedException(property.arrayElementType);
}
int arrayHash = 0;
int effectiveArraySize;
{
int length = 0;
int i;
for (i = 0; i < property.arraySize && length < maxLength; ++i) {
arrayHash = arrayHash * 31 + property.GetArrayElementAtIndex(i).longValue.GetHashCode();
length += charactersPerElement + 1;
}
effectiveArraySize = i;
}
if (_builder == null || arrayHash != _lastHash) {
var format = "{0:x" + charactersPerElement + "} ";
_builder ??= new StringBuilder();
_builder.Clear();
for (int i = 0; i < effectiveArraySize; ++i) {
_builder.AppendFormat(format, property.GetArrayElementAtIndex(i).longValue);
}
if (effectiveArraySize < property.arraySize) {
_builder.AppendLine("...");
}
_lastHash = arrayHash;
_lastValue = new GUIContent(_builder.ToString());
} else {
Debug.Assert(_lastValue != null);
}
}
public float GetHeight(float width) {
return FusionEditorSkin.RawDataStyle.Value.CalcHeight(_lastValue ?? GUIContent.none, width);
}
public string Draw(Rect position) => Draw(GUIContent.none, position);
public string Draw(GUIContent label, Rect position) {
var id = GUIUtility.GetControlID(UnityInternal.EditorGUI.DelayedTextFieldHash, FocusType.Keyboard, position);
return UnityInternal.EditorGUI.DelayedTextFieldInternal(position, id, label, _lastValue.text ?? string.Empty, "0123456789abcdefABCDEF ", FusionEditorSkin.RawDataStyle);
}
public string DrawLayout() {
var position = EditorGUILayout.GetControlRect(false, 18f, FusionEditorSkin.RawDataStyle);
return Draw(position);
}
}
}
#endregion
#region ReflectionUtils.cs
namespace Fusion.Editor {
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using UnityEditor;
static partial class ReflectionUtils {
public const BindingFlags DefaultBindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance;
public static Type GetUnityLeafType(this Type type) {
if (type.HasElementType) {
type = type.GetElementType();
} else if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>)) {
type = type.GetGenericArguments()[0];
}
return type;
}
public static T CreateMethodDelegate<T>(this Type type, string methodName, BindingFlags flags = DefaultBindingFlags) where T : Delegate {
try {
return CreateMethodDelegateInternal<T>(type, methodName, flags);
} catch (Exception ex) {
throw new InvalidOperationException(CreateMethodExceptionMessage<T>(type.Assembly, type.FullName, methodName, flags), ex);
}
}
public static Delegate CreateMethodDelegate(this Type type, string methodName, BindingFlags flags, Type delegateType) {
try {
return CreateMethodDelegateInternal(type, methodName, flags, delegateType);
} catch (Exception ex) {
throw new InvalidOperationException(CreateMethodExceptionMessage(type.Assembly, type.FullName, methodName, flags, delegateType), ex);
}
}
public static T CreateMethodDelegate<T>(Assembly assembly, string typeName, string methodName, BindingFlags flags = DefaultBindingFlags) where T : Delegate {
try {
var type = assembly.GetType(typeName, true);
return CreateMethodDelegateInternal<T>(type, methodName, flags);
} catch (Exception ex) {
throw new InvalidOperationException(CreateMethodExceptionMessage<T>(assembly, typeName, methodName, flags), ex);
}
}
public static Delegate CreateMethodDelegate(Assembly assembly, string typeName, string methodName, BindingFlags flags, Type delegateType) {
try {
var type = assembly.GetType(typeName, true);
return CreateMethodDelegateInternal(type, methodName, flags, delegateType);
} catch (Exception ex) {
throw new InvalidOperationException(CreateMethodExceptionMessage(assembly, typeName, methodName, flags, delegateType), ex);
}
}
public static T CreateMethodDelegate<T>(this Type type, string methodName, BindingFlags flags, Type delegateType, params DelegateSwizzle[] fallbackSwizzles) where T : Delegate {
try {
var method = GetMethodOrThrow(type, methodName, flags, delegateType, fallbackSwizzles, out var swizzle);
var delegateParameters = typeof(T).GetMethod("Invoke").GetParameters();
var parameters = new List<ParameterExpression>();
for (var i = 0; i < delegateParameters.Length; ++i) {
parameters.Add(Expression.Parameter(delegateParameters[i].ParameterType, $"param_{i}"));
}
var convertedParameters = new List<Expression>();
{
var methodParameters = method.GetParameters();
if (swizzle == null) {
for (int i = 0, j = method.IsStatic ? 0 : 1; i < methodParameters.Length; ++i, ++j) {
convertedParameters.Add(Expression.Convert(parameters[j], methodParameters[i].ParameterType));
}
} else {
var swizzledParameters = swizzle.Swizzle(parameters.ToArray());
for (int i = 0, j = method.IsStatic ? 0 : 1; i < methodParameters.Length; ++i, ++j) {
convertedParameters.Add(Expression.Convert(swizzledParameters[j], methodParameters[i].ParameterType));
}
}
}
MethodCallExpression callExpression;
if (method.IsStatic) {
callExpression = Expression.Call(method, convertedParameters);
} else {
var instance = Expression.Convert(parameters[0], method.DeclaringType);
callExpression = Expression.Call(instance, method, convertedParameters);
}
var l = Expression.Lambda(typeof(T), callExpression, parameters);
var del = l.Compile();
return (T)del;
} catch (Exception ex) {
throw new InvalidOperationException(CreateMethodExceptionMessage<T>(type.Assembly, type.FullName, methodName, flags), ex);
}
}
public static T CreateConstructorDelegate<T>(this Type type, BindingFlags flags, Type delegateType, params DelegateSwizzle[] fallbackSwizzles) where T : Delegate {
try {
var constructor = GetConstructorOrThrow(type, flags, delegateType, fallbackSwizzles, out var swizzle);
var delegateParameters = typeof(T).GetMethod("Invoke").GetParameters();
var parameters = new List<ParameterExpression>();
for (var i = 0; i < delegateParameters.Length; ++i) {
parameters.Add(Expression.Parameter(delegateParameters[i].ParameterType, $"param_{i}"));
}
var convertedParameters = new List<Expression>();
{
var constructorParameters = constructor.GetParameters();
if (swizzle == null) {
for (int i = 0, j = 0; i < constructorParameters.Length; ++i, ++j) {
convertedParameters.Add(Expression.Convert(parameters[j], constructorParameters[i].ParameterType));
}
} else {
var swizzledParameters = swizzle.Swizzle(parameters.ToArray());
for (int i = 0, j = 0; i < constructorParameters.Length; ++i, ++j) {
convertedParameters.Add(Expression.Convert(swizzledParameters[j], constructorParameters[i].ParameterType));
}
}
}
var newExpression = Expression.New(constructor, convertedParameters);
var l = Expression.Lambda(typeof(T), newExpression, parameters);
var del = l.Compile();
return (T)del;
} catch (Exception ex) {
throw new InvalidOperationException(CreateConstructorExceptionMessage(type.Assembly, type.FullName, flags), ex);
}
}
/// <summary>
/// Returns the first found member of the given name. Includes private members.
/// </summary>
public static MemberInfo GetMemberIncludingBaseTypes(this Type type, string memberName, BindingFlags flags = DefaultBindingFlags, Type stopAtType = null) {
var members = type.GetMember(memberName, flags);
if (members.Length > 0) {
return members[0];
}
type = type.BaseType;
// loop as long as we have a parent class to search.
while (type != null) {
// No point recursing into the abstracts.
if (type == stopAtType) {
break;
}
members = type.GetMember(memberName, flags);
if (members.Length > 0) {
return members[0];
}
type = type.BaseType;
}
return null;
}
/// <summary>
/// Normal reflection GetField() won't find private fields in parents (only will find protected). So this recurses the
/// hard to find privates.
/// This is needed since Unity serialization does find inherited privates.
/// </summary>
public static FieldInfo GetFieldIncludingBaseTypes(this Type type, string fieldName, BindingFlags flags = DefaultBindingFlags, Type stopAtType = null) {
var field = type.GetField(fieldName, flags);
if (field != null) {
return field;
}
type = type.BaseType;
// loop as long as we have a parent class to search.
while (type != null) {
// No point recursing into the abstracts.
if (type == stopAtType) {
break;
}
field = type.GetField(fieldName, flags);
if (field != null) {
return field;
}
type = type.BaseType;
}
return null;
}
public static FieldInfo GetFieldOrThrow(this Type type, string fieldName, BindingFlags flags = DefaultBindingFlags) {
var field = type.GetField(fieldName, flags);
if (field == null) {
throw new ArgumentOutOfRangeException(nameof(fieldName), CreateFieldExceptionMessage(type.Assembly, type.FullName, fieldName, flags));
}
return field;
}
public static FieldInfo GetFieldOrThrow<T>(this Type type, string fieldName, BindingFlags flags = DefaultBindingFlags) {
return GetFieldOrThrow(type, fieldName, typeof(T), flags);
}
public static FieldInfo GetFieldOrThrow(this Type type, string fieldName, Type fieldType, BindingFlags flags = DefaultBindingFlags) {
var field = type.GetField(fieldName, flags);
if (field == null) {
throw new ArgumentOutOfRangeException(nameof(fieldName), CreateFieldExceptionMessage(type.Assembly, type.FullName, fieldName, flags));
}
if (fieldType != null) {
if (field.FieldType != fieldType) {
throw new InvalidProgramException($"Field {type.FullName}.{fieldName} is of type {field.FieldType}, not expected {fieldType}");
}
}
return field;
}
public static PropertyInfo GetPropertyOrThrow<T>(this Type type, string propertyName, BindingFlags flags = DefaultBindingFlags) {
return GetPropertyOrThrow(type, propertyName, typeof(T), flags);
}
public static PropertyInfo GetPropertyOrThrow(this Type type, string propertyName, Type propertyType, BindingFlags flags = DefaultBindingFlags) {
var property = type.GetProperty(propertyName, flags);
if (property == null) {
throw new ArgumentOutOfRangeException(nameof(propertyName), CreateFieldExceptionMessage(type.Assembly, type.FullName, propertyName, flags));
}
if (property.PropertyType != propertyType) {
throw new InvalidProgramException($"Property {type.FullName}.{propertyName} is of type {property.PropertyType}, not expected {propertyType}");
}
return property;
}
public static PropertyInfo GetPropertyOrThrow(this Type type, string propertyName, BindingFlags flags = DefaultBindingFlags) {
var property = type.GetProperty(propertyName, flags);
if (property == null) {
throw new ArgumentOutOfRangeException(nameof(propertyName), CreateFieldExceptionMessage(type.Assembly, type.FullName, propertyName, flags));
}
return property;
}
public static ConstructorInfo GetConstructorInfoOrThrow(this Type type, Type[] types, BindingFlags flags = DefaultBindingFlags) {
var constructor = type.GetConstructor(flags, null, types, null);
if (constructor == null) {
throw new ArgumentOutOfRangeException(nameof(types), CreateConstructorExceptionMessage(type.Assembly, type.FullName, types, flags));
}
return constructor;
}
public static Type GetNestedTypeOrThrow(this Type type, string name, BindingFlags flags) {
var result = type.GetNestedType(name, flags);
if (result == null) {
throw new ArgumentOutOfRangeException(nameof(name), CreateFieldExceptionMessage(type.Assembly, type.FullName, name, flags));
}
return result;
}
public static Func<object, object> CreateGetter(this Type type, string memberName, BindingFlags flags = DefaultBindingFlags) {
return CreateGetter<object>(type, memberName, flags);
}
public static Func<object, T> CreateGetter<T>(this Type type, string memberName, BindingFlags flags = DefaultBindingFlags) {
var candidates = type.GetMembers(flags).Where(x => x.Name == memberName)
.ToList();
if (candidates.Count > 1) {
throw new InvalidOperationException($"Multiple members with name {memberName} found in type {type.FullName}");
}
if (candidates.Count == 0) {
throw new ArgumentOutOfRangeException(nameof(memberName),$"No members with name {memberName} found in type {type.FullName}");
}
var candidate = candidates[0];
bool isStatic = false;
switch (candidate) {
case FieldInfo field:
isStatic = field.IsStatic;
break;
case PropertyInfo property:
isStatic = property.GetMethod.IsStatic;
break;
case MethodInfo method:
isStatic = method.IsStatic;
break;
}
if (isStatic) {
var getter = CreateStaticAccessorInternal<T>(candidate).GetValue;
return _ => getter();
} else {
return CreateAccessorInternal<T>(candidate).GetValue;
}
}
public static InstanceAccessor<object> CreateFieldAccessor(this Type type, string fieldName, Type expectedFieldType = null, BindingFlags flags = DefaultBindingFlags) {
return CreateFieldAccessor<object>(type, fieldName, expectedFieldType);
}
public static InstanceAccessor<FieldType> CreateFieldAccessor<FieldType>(this Type type, string fieldName, Type expectedFieldType = null, BindingFlags flags = DefaultBindingFlags) {
var field = type.GetFieldOrThrow(fieldName, expectedFieldType ?? typeof(FieldType), BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
return CreateAccessorInternal<FieldType>(field);
}
public static StaticAccessor<object> CreateStaticFieldAccessor(this Type type, string fieldName, Type expectedFieldType = null) {
return CreateStaticFieldAccessor<object>(type, fieldName, expectedFieldType);
}
public static StaticAccessor<FieldType> CreateStaticFieldAccessor<FieldType>(this Type type, string fieldName, Type expectedFieldType = null) {
var field = type.GetFieldOrThrow(fieldName, expectedFieldType ?? typeof(FieldType), BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
return CreateStaticAccessorInternal<FieldType>(field);
}
public static InstanceAccessor<PropertyType> CreatePropertyAccessor<PropertyType>(this Type type, string fieldName, Type expectedPropertyType = null, BindingFlags flags = DefaultBindingFlags) {
var field = type.GetPropertyOrThrow(fieldName, expectedPropertyType ?? typeof(PropertyType), BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
return CreateAccessorInternal<PropertyType>(field);
}
public static StaticAccessor<object> CreateStaticPropertyAccessor(this Type type, string fieldName, Type expectedFieldType = null) {
return CreateStaticPropertyAccessor<object>(type, fieldName, expectedFieldType);
}
public static StaticAccessor<FieldType> CreateStaticPropertyAccessor<FieldType>(this Type type, string fieldName, Type expectedFieldType = null) {
var field = type.GetPropertyOrThrow(fieldName, expectedFieldType ?? typeof(FieldType), BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
return CreateStaticAccessorInternal<FieldType>(field);
}
private static string CreateMethodExceptionMessage<T>(Assembly assembly, string typeName, string methodName, BindingFlags flags) {
return CreateMethodExceptionMessage(assembly, typeName, methodName, flags, typeof(T));
}
private static string CreateMethodExceptionMessage(Assembly assembly, string typeName, string methodName, BindingFlags flags, Type delegateType) {
return $"{assembly.FullName}.{typeName}.{methodName} with flags: {flags} and type: {delegateType}";
}
private static string CreateFieldExceptionMessage(Assembly assembly, string typeName, string fieldName, BindingFlags flags) {
return $"{assembly.FullName}.{typeName}.{fieldName} with flags: {flags}";
}
private static string CreateConstructorExceptionMessage(Assembly assembly, string typeName, BindingFlags flags) {
return $"{assembly.FullName}.{typeName}() with flags: {flags}";
}
private static string CreateConstructorExceptionMessage(Assembly assembly, string typeName, Type[] types, BindingFlags flags) {
return $"{assembly.FullName}.{typeName}({string.Join(", ", types.Select(x => x.FullName))}) with flags: {flags}";
}
private static T CreateMethodDelegateInternal<T>(this Type type, string name, BindingFlags flags) where T : Delegate {
return (T)CreateMethodDelegateInternal(type, name, flags, typeof(T));
}
private static Delegate CreateMethodDelegateInternal(this Type type, string name, BindingFlags flags, Type delegateType) {
var method = GetMethodOrThrow(type, name, flags, delegateType);
return Delegate.CreateDelegate(delegateType, null, method);
}
private static MethodInfo GetMethodOrThrow(Type type, string name, BindingFlags flags, Type delegateType) {
return GetMethodOrThrow(type, name, flags, delegateType, Array.Empty<DelegateSwizzle>(), out _);
}
private static MethodInfo FindMethod(Type type, string name, BindingFlags flags, Type returnType, params Type[] parameters) {
var method = type.GetMethod(name, flags, null, parameters, null);
if (method == null) {
return null;
}
if (method.ReturnType != returnType) {
return null;
}
return method;
}
private static ConstructorInfo GetConstructorOrThrow(Type type, BindingFlags flags, Type delegateType, DelegateSwizzle[] swizzles, out DelegateSwizzle firstMatchingSwizzle) {
var delegateMethod = delegateType.GetMethod("Invoke");
var allDelegateParameters = delegateMethod.GetParameters().Select(x => x.ParameterType).ToArray();
var constructor = type.GetConstructor(flags, null, allDelegateParameters, null);
if (constructor != null) {
firstMatchingSwizzle = null;
return constructor;
}
if (swizzles != null) {
foreach (var swizzle in swizzles) {
var swizzled = swizzle.Swizzle(allDelegateParameters);
constructor = type.GetConstructor(flags, null, swizzled, null);
if (constructor != null) {
firstMatchingSwizzle = swizzle;
return constructor;
}
}
}
var constructors = type.GetConstructors(flags);
throw new ArgumentOutOfRangeException(nameof(delegateType), $"No matching constructor found for {type}, " +
$"signature \"{delegateType}\", " +
$"flags \"{flags}\" and " +
$"params: {string.Join(", ", allDelegateParameters.Select(x => x.FullName))}" +
$", candidates are\n: {string.Join("\n", constructors.Select(x => x.ToString()))}");
}
private static MethodInfo GetMethodOrThrow(Type type, string name, BindingFlags flags, Type delegateType, DelegateSwizzle[] swizzles, out DelegateSwizzle firstMatchingSwizzle) {
var delegateMethod = delegateType.GetMethod("Invoke");
var allDelegateParameters = delegateMethod.GetParameters().Select(x => x.ParameterType).ToArray();
var method = FindMethod(type, name, flags, delegateMethod.ReturnType, flags.HasFlag(BindingFlags.Static) ? allDelegateParameters : allDelegateParameters.Skip(1).ToArray());
if (method != null) {
firstMatchingSwizzle = null;
return method;
}
if (swizzles != null) {
foreach (var swizzle in swizzles) {
var swizzled = swizzle.Swizzle(allDelegateParameters);
if (!flags.HasFlag(BindingFlags.Static) && swizzled[0] != type) {
throw new InvalidOperationException();
}
method = FindMethod(type, name, flags, delegateMethod.ReturnType, flags.HasFlag(BindingFlags.Static) ? swizzled : swizzled.Skip(1).ToArray());
if (method != null) {
firstMatchingSwizzle = swizzle;
return method;
}
}
}
var methods = type.GetMethods(flags);
throw new ArgumentOutOfRangeException(nameof(name), $"No method found matching name \"{name}\", " +
$"signature \"{delegateType}\", " +
$"flags \"{flags}\" and " +
$"params: {string.Join(", ", allDelegateParameters.Select(x => x.FullName))}" +
$", candidates are\n: {string.Join("\n", methods.Select(x => x.ToString()))}");
}
public static bool IsArrayOrList(this Type listType) {
if (listType.IsArray) {
return true;
}
if (listType.IsGenericType && listType.GetGenericTypeDefinition() == typeof(List<>)) {
return true;
}
return false;
}
public static Type GetArrayOrListElementType(this Type listType) {
if (listType.IsArray) {
return listType.GetElementType();
}
if (listType.IsGenericType && listType.GetGenericTypeDefinition() == typeof(List<>)) {
return listType.GetGenericArguments()[0];
}
return null;
}
public static Type MakeFuncType(params Type[] types) {
return GetFuncType(types.Length).MakeGenericType(types);
}
private static Type GetFuncType(int argumentCount) {
switch (argumentCount) {
case 1: return typeof(Func<>);
case 2: return typeof(Func<,>);
case 3: return typeof(Func<,,>);
case 4: return typeof(Func<,,,>);
case 5: return typeof(Func<,,,,>);
case 6: return typeof(Func<,,,,,>);
default: throw new ArgumentOutOfRangeException(nameof(argumentCount));
}
}
public static Type MakeActionType(params Type[] types) {
if (types.Length == 0) {
return typeof(Action);
}
return GetActionType(types.Length).MakeGenericType(types);
}
private static Type GetActionType(int argumentCount) {
switch (argumentCount) {
case 1: return typeof(Action<>);
case 2: return typeof(Action<,>);
case 3: return typeof(Action<,,>);
case 4: return typeof(Action<,,,>);
case 5: return typeof(Action<,,,,>);
case 6: return typeof(Action<,,,,,>);
default: throw new ArgumentOutOfRangeException(nameof(argumentCount));
}
}
private static StaticAccessor<T> CreateStaticAccessorInternal<T>(MemberInfo member) {
try {
var valueParameter = Expression.Parameter(typeof(T), "value");
var canWrite = true;
UnaryExpression valueExpression;
Expression memberExpression;
switch (member) {
case PropertyInfo property:
valueExpression = Expression.Convert(valueParameter, property.PropertyType);
memberExpression = Expression.Property(null, property);
canWrite = property.CanWrite;
break;
case FieldInfo field:
valueExpression = Expression.Convert(valueParameter, field.FieldType);
memberExpression = Expression.Field(null, field);
canWrite = field.IsInitOnly == false;
break;
case MethodInfo method when method.GetParameters().Length == 0:
valueExpression = null;
memberExpression = Expression.Call(method);
canWrite = false;
break;
default:
throw new InvalidOperationException($"Unsupported member type {member.GetType().Name}");
}
Func<T> getter;
var getExpression = Expression.Convert(memberExpression, typeof(T));
var getLambda = Expression.Lambda<Func<T>>(getExpression);
getter = getLambda.Compile();
Action<T> setter = null;
if (canWrite) {
var setExpression = Expression.Assign(memberExpression, valueExpression);
var setLambda = Expression.Lambda<Action<T>>(setExpression, valueParameter);
setter = setLambda.Compile();
}
return new StaticAccessor<T> {
GetValue = getter,
SetValue = setter
};
} catch (Exception ex) {
throw new InvalidOperationException($"Failed to create accessor for {member.DeclaringType}.{member.Name}", ex);
}
}
private static InstanceAccessor<T> CreateAccessorInternal<T>(MemberInfo member) {
try {
var instanceParameter = Expression.Parameter(typeof(object), "instance");
var instanceExpression = Expression.Convert(instanceParameter, member.DeclaringType);
var valueParameter = Expression.Parameter(typeof(T), "value");
var canWrite = true;
UnaryExpression valueExpression;
Expression memberExpression;
switch (member) {
case PropertyInfo property:
valueExpression = Expression.Convert(valueParameter, property.PropertyType);
memberExpression = Expression.Property(instanceExpression, property);
canWrite = property.CanWrite;
break;
case FieldInfo field:
valueExpression = Expression.Convert(valueParameter, field.FieldType);
memberExpression = Expression.Field(instanceExpression, field);
canWrite = field.IsInitOnly == false;
break;
case MethodInfo method when method.GetParameters().Length == 0:
valueExpression = null;
memberExpression = Expression.Call(instanceExpression, method);
canWrite = false;
break;
default:
throw new InvalidOperationException($"Unsupported member type {member.GetType().Name}");
}
var getExpression = Expression.Convert(memberExpression, typeof(T));
var getLambda = Expression.Lambda<Func<object, T>>(getExpression, instanceParameter);
var getter = getLambda.Compile();
Action<object, T> setter = null;
if (canWrite) {
var setExpression = Expression.Assign(memberExpression, valueExpression);
var setLambda = Expression.Lambda<Action<object, T>>(setExpression, instanceParameter, valueParameter);
setter = setLambda.Compile();
}
return new InstanceAccessor<T> {
GetValue = getter,
SetValue = setter
};
} catch (Exception ex) {
throw new InvalidOperationException($"Failed to create accessor for {member.DeclaringType}.{member.Name}", ex);
}
}
public struct InstanceAccessor<TValue> {
public Func<object, TValue> GetValue;
public Action<object, TValue> SetValue;
}
public struct StaticAccessor<TValue> {
public Func<TValue> GetValue;
public Action<TValue> SetValue;
}
public class DelegateSwizzle {
private readonly int[] _args;
public DelegateSwizzle(params int[] args) {
_args = args;
}
public int Count => _args.Length;
public T[] Swizzle<T>(T[] inputTypes) {
var result = new T[_args.Length];
for (var i = 0; i < _args.Length; ++i) {
result[i] = inputTypes[_args[i]];
}
return result;
}
}
#if UNITY_EDITOR
public static T CreateEditorMethodDelegate<T>(string editorAssemblyTypeName, string methodName, BindingFlags flags = DefaultBindingFlags) where T : Delegate {
return CreateMethodDelegate<T>(typeof(Editor).Assembly, editorAssemblyTypeName, methodName, flags);
}
public static Delegate CreateEditorMethodDelegate(string editorAssemblyTypeName, string methodName, BindingFlags flags, Type delegateType) {
return CreateMethodDelegate(typeof(Editor).Assembly, editorAssemblyTypeName, methodName, flags, delegateType);
}
#endif
}
}
#endregion
#region SerializedPropertyUtilities.cs
namespace Fusion.Editor {
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using UnityEditor;
static partial class SerializedPropertyUtilities {
private static readonly Regex _arrayElementRegex = new(@"\.Array\.data\[\d+\]$", RegexOptions.Compiled);
public static SerializedProperty FindPropertyOrThrow(this SerializedObject so, string propertyPath) {
var result = so.FindProperty(propertyPath);
if (result == null) {
throw new ArgumentOutOfRangeException(nameof(propertyPath), $"Property not found: {propertyPath} on {so.targetObject}");
}
return result;
}
public static SerializedProperty FindPropertyRelativeOrThrow(this SerializedProperty sp, string relativePropertyPath) {
var result = sp.FindPropertyRelative(relativePropertyPath);
if (result == null) {
throw new ArgumentOutOfRangeException(nameof(relativePropertyPath), $"Property not found: {relativePropertyPath} (relative to \"{sp.propertyPath}\" of {sp.serializedObject.targetObject}");
}
return result;
}
public static SerializedProperty FindPropertyRelativeToParentOrThrow(this SerializedProperty property, string relativePath) {
var result = FindPropertyRelativeToParent(property, relativePath);
if (result == null) {
throw new ArgumentOutOfRangeException(nameof(relativePath), $"Property not found: {relativePath} (relative to the parent of \"{property.propertyPath}\" of {property.serializedObject.targetObject}");
}
return result;
}
public static SerializedProperty FindPropertyRelativeToParent(this SerializedProperty property, string relativePath) {
var parentPath = property.propertyPath;
int startIndex = 0;
do {
// array element?
if (parentPath.EndsWith("]")) {
var match = _arrayElementRegex.Match(parentPath);
if (match.Success) {
parentPath = parentPath.Substring(0, match.Index);
}
}
var lastDotIndex = parentPath.LastIndexOf('.');
if (lastDotIndex < 0) {
if (string.IsNullOrEmpty(parentPath)) {
return null;
}
parentPath = string.Empty;
} else {
parentPath = parentPath.Substring(0, lastDotIndex);
}
} while (relativePath[startIndex++] == '^');
if (startIndex > 1) {
relativePath = relativePath.Substring(startIndex - 1);
}
if (string.IsNullOrEmpty(parentPath)) {
return property.serializedObject.FindProperty(relativePath);
} else {
return property.serializedObject.FindProperty(parentPath + "." + relativePath);
}
}
public static bool IsArrayElement(this SerializedProperty sp) {
var propertyPath = sp.propertyPath;
if (!propertyPath.EndsWith("]", StringComparison.Ordinal)) {
return false;
}
return true;
}
public static bool IsArrayElement(this SerializedProperty sp, out int index) {
var propertyPath = sp.propertyPath;
if (!propertyPath.EndsWith("]", StringComparison.Ordinal)) {
index = -1;
return false;
}
var indexStart = propertyPath.LastIndexOf("[", StringComparison.Ordinal);
if (indexStart < 0) {
index = -1;
return false;
}
index = int.Parse(propertyPath.Substring(indexStart + 1, propertyPath.Length - indexStart - 2));
return true;
}
public static SerializedProperty GetArrayFromArrayElement(this SerializedProperty sp) {
var path = sp.propertyPath;
var match = _arrayElementRegex.Match(path);
if (!match.Success) {
throw new ArgumentException($"Property is not an array element: {path}");
}
var arrayPath = path.Substring(0, match.Index);
return sp.serializedObject.FindProperty(arrayPath);
}
public static bool IsArrayProperty(this SerializedProperty sp) {
return sp.isArray && sp.propertyType != SerializedPropertyType.String;
}
public static SerializedPropertyEnumerable GetChildren(this SerializedProperty property, bool visibleOnly = true) {
return new SerializedPropertyEnumerable(property, visibleOnly);
}
public class SerializedPropertyEqualityComparer : IEqualityComparer<SerializedProperty> {
public static SerializedPropertyEqualityComparer Instance = new();
public bool Equals(SerializedProperty x, SerializedProperty y) {
return SerializedProperty.DataEquals(x, y);
}
public int GetHashCode(SerializedProperty p) {
bool enterChildren;
var isFirst = true;
var hashCode = 0;
var minDepth = p.depth + 1;
do {
enterChildren = false;
switch (p.propertyType) {
case SerializedPropertyType.Integer:
hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.intValue);
break;
case SerializedPropertyType.Boolean:
hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.boolValue.GetHashCode());
break;
case SerializedPropertyType.Float:
hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.floatValue.GetHashCode());
break;
case SerializedPropertyType.String:
hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.stringValue.GetHashCode());
break;
case SerializedPropertyType.Color:
hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.colorValue.GetHashCode());
break;
case SerializedPropertyType.ObjectReference:
hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.objectReferenceInstanceIDValue);
break;
case SerializedPropertyType.LayerMask:
hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.intValue);
break;
case SerializedPropertyType.Enum:
hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.intValue);
break;
case SerializedPropertyType.Vector2:
hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.vector2Value.GetHashCode());
break;
case SerializedPropertyType.Vector3:
hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.vector3Value.GetHashCode());
break;
case SerializedPropertyType.Vector4:
hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.vector4Value.GetHashCode());
break;
case SerializedPropertyType.Vector2Int:
hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.vector2IntValue.GetHashCode());
break;
case SerializedPropertyType.Vector3Int:
hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.vector3IntValue.GetHashCode());
break;
case SerializedPropertyType.Rect:
hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.rectValue.GetHashCode());
break;
case SerializedPropertyType.RectInt:
hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.rectIntValue.GetHashCode());
break;
case SerializedPropertyType.ArraySize:
hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.intValue);
break;
case SerializedPropertyType.Character:
hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.intValue.GetHashCode());
break;
case SerializedPropertyType.AnimationCurve:
hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.animationCurveValue.GetHashCode());
break;
case SerializedPropertyType.Bounds:
hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.boundsValue.GetHashCode());
break;
case SerializedPropertyType.BoundsInt:
hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.boundsIntValue.GetHashCode());
break;
case SerializedPropertyType.ExposedReference:
hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.exposedReferenceValue.GetHashCode());
break;
default: {
enterChildren = true;
break;
}
}
if (isFirst) {
if (!enterChildren)
// no traverse needed
{
return hashCode;
}
// since property is going to be traversed, a copy needs to be made
p = p.Copy();
isFirst = false;
}
} while (p.Next(enterChildren) && p.depth >= minDepth);
return hashCode;
}
}
public struct SerializedPropertyEnumerable : IEnumerable<SerializedProperty> {
private SerializedProperty property;
private bool visible;
public SerializedPropertyEnumerable(SerializedProperty property, bool visible) {
this.property = property;
this.visible = visible;
}
public SerializedPropertyEnumerator GetEnumerator() {
return new SerializedPropertyEnumerator(property, visible);
}
IEnumerator<SerializedProperty> IEnumerable<SerializedProperty>.GetEnumerator() {
return GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator() {
return GetEnumerator();
}
}
public struct SerializedPropertyEnumerator : IEnumerator<SerializedProperty> {
private SerializedProperty current;
private bool enterChildren;
private bool visible;
private int parentDepth;
public SerializedPropertyEnumerator(SerializedProperty parent, bool visible) {
current = parent.Copy();
enterChildren = true;
parentDepth = parent.depth;
this.visible = visible;
}
public SerializedProperty Current => current;
SerializedProperty IEnumerator<SerializedProperty>.Current => current;
object IEnumerator.Current => current;
public void Dispose() {
current.Dispose();
}
public bool MoveNext() {
bool entered = visible ? current.NextVisible(enterChildren) : current.Next(enterChildren);
enterChildren = false;
if (!entered) {
return false;
}
if (current.depth <= parentDepth) {
return false;
}
return true;
}
public void Reset() {
throw new NotImplementedException();
}
}
private static int[] _updateFixedBufferTemp = Array.Empty<int>();
internal static bool UpdateFixedBuffer(this SerializedProperty sp, Action<int[], int> fill, Action<int[], int> update, bool write, bool force = false) {
int count = sp.fixedBufferSize;
Array.Resize(ref _updateFixedBufferTemp, Math.Max(_updateFixedBufferTemp.Length, count));
// need to get to the first property... `GetFixedBufferElementAtIndex` is slow and allocates
var element = sp.Copy();
element.Next(true); // .Array
element.Next(true); // .Array.size
element.Next(true); // .Array.data[0]
unsafe {
fixed (int* p = _updateFixedBufferTemp) {
Unity.Collections.LowLevel.Unsafe.UnsafeUtility.MemClear(p, count * sizeof(int));
}
fill(_updateFixedBufferTemp, count);
int i = 0;
if (!force) {
// find the first difference
for (; i < count; ++i, element.Next(true)) {
FusionEditorLog.Assert(element.propertyType == SerializedPropertyType.Integer, "Invalid property type, expected integer");
if (element.intValue != _updateFixedBufferTemp[i]) {
break;
}
}
}
if (i < count) {
// update data
if (write) {
for (; i < count; ++i, element.Next(true)) {
element.intValue = _updateFixedBufferTemp[i];
}
} else {
for (; i < count; ++i, element.Next(true)) {
_updateFixedBufferTemp[i] = element.intValue;
}
}
update(_updateFixedBufferTemp, count);
return true;
} else {
return false;
}
}
}
}
}
#endregion
#region UnityInternal.cs
namespace Fusion.Editor {
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;
using static ReflectionUtils;
static partial class UnityInternal {
static Assembly FindAssembly(string name) {
return AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.GetName().Name == name);
}
[UnityEditor.InitializeOnLoad]
public static class Event {
static StaticAccessor<UnityEngine.Event> s_Current_ = typeof(UnityEngine.Event).CreateStaticFieldAccessor<UnityEngine.Event>(nameof(s_Current));
public static UnityEngine.Event s_Current => s_Current_.GetValue();
}
[UnityEditor.InitializeOnLoad]
public static class Editor {
public delegate bool DoDrawDefaultInspectorDelegate(SerializedObject obj);
public delegate void BoolSetterDelegate(UnityEditor.Editor editor, bool value);
public static readonly DoDrawDefaultInspectorDelegate DoDrawDefaultInspector = typeof(UnityEditor.Editor).CreateMethodDelegate<DoDrawDefaultInspectorDelegate>(nameof(DoDrawDefaultInspector));
public static readonly BoolSetterDelegate InternalSetHidden = typeof(UnityEditor.Editor).CreateMethodDelegate<BoolSetterDelegate>(nameof(InternalSetHidden), BindingFlags.NonPublic | BindingFlags.Instance);
}
[UnityEditor.InitializeOnLoad]
public static class EditorGUI {
public delegate string DelayedTextFieldInternalDelegate(Rect position, int id, GUIContent label, string value, string allowedLetters, GUIStyle style);
public delegate Rect MultiFieldPrefixLabelDelegate(Rect totalPosition, int id, GUIContent label, int columns);
public delegate string TextFieldInternalDelegate(int id, Rect position, string text, GUIStyle style);
public delegate string ToolbarSearchFieldDelegate(int id, Rect position, string text, bool showWithPopupArrow);
public delegate bool DefaultPropertyFieldDelegate(Rect position, UnityEditor.SerializedProperty property, GUIContent label);
public static readonly MultiFieldPrefixLabelDelegate MultiFieldPrefixLabel = typeof(UnityEditor.EditorGUI).CreateMethodDelegate<MultiFieldPrefixLabelDelegate>(nameof(MultiFieldPrefixLabel));
public static readonly TextFieldInternalDelegate TextFieldInternal = typeof(UnityEditor.EditorGUI).CreateMethodDelegate<TextFieldInternalDelegate>(nameof(TextFieldInternal));
public static readonly ToolbarSearchFieldDelegate ToolbarSearchField = typeof(UnityEditor.EditorGUI).CreateMethodDelegate<ToolbarSearchFieldDelegate>(nameof(ToolbarSearchField));
public static readonly DelayedTextFieldInternalDelegate DelayedTextFieldInternal = typeof(UnityEditor.EditorGUI).CreateMethodDelegate<DelayedTextFieldInternalDelegate>(nameof(DelayedTextFieldInternal));
public static readonly DefaultPropertyFieldDelegate DefaultPropertyField = typeof(UnityEditor.EditorGUI).CreateMethodDelegate<DefaultPropertyFieldDelegate>(nameof(DefaultPropertyField));
private static readonly FieldInfo s_TextFieldHash = typeof(UnityEditor.EditorGUI).GetFieldOrThrow(nameof(s_TextFieldHash));
private static readonly FieldInfo s_DelayedTextFieldHash = typeof(UnityEditor.EditorGUI).GetFieldOrThrow(nameof(s_DelayedTextFieldHash));
private static readonly StaticAccessor<float> s_indent = typeof(UnityEditor.EditorGUI).CreateStaticPropertyAccessor<float>(nameof(indent));
public static readonly Action EndEditingActiveTextField = typeof(UnityEditor.EditorGUI).CreateMethodDelegate<Action>(nameof(EndEditingActiveTextField));
public static int TextFieldHash => (int)s_TextFieldHash.GetValue(null);
public static int DelayedTextFieldHash => (int)s_DelayedTextFieldHash.GetValue(null);
internal static float indent => s_indent.GetValue();
}
[UnityEditor.InitializeOnLoad]
public static class EditorUtility {
public delegate void DisplayCustomMenuDelegate(Rect position, string[] options, int[] selected, UnityEditor.EditorUtility.SelectMenuItemFunction callback, object userData);
public static DisplayCustomMenuDelegate DisplayCustomMenu = typeof(UnityEditor.EditorUtility).CreateMethodDelegate<DisplayCustomMenuDelegate>(nameof(DisplayCustomMenu), BindingFlags.NonPublic | BindingFlags.Static);
}
[UnityEditor.InitializeOnLoad]
public static class HandleUtility {
public static readonly Action ApplyWireMaterial = typeof(UnityEditor.HandleUtility).CreateMethodDelegate<Action>(nameof(ApplyWireMaterial));
}
[UnityEditor.InitializeOnLoad]
public static class LayerMatrixGUI {
private const string TypeName =
#if UNITY_2023_1_OR_NEWER
"UnityEditor.LayerCollisionMatrixGUI2D";
#else
"UnityEditor.LayerMatrixGUI";
#endif
private static readonly Type InternalType =
#if UNITY_2023_1_OR_NEWER
FindAssembly("UnityEditor.Physics2DModule")?.GetType(TypeName, true);
#else
typeof(UnityEditor.Editor).Assembly.GetType(TypeName, true);
#endif
private static readonly Type InternalGetValueFuncType = InternalType?.GetNestedTypeOrThrow(nameof(GetValueFunc), BindingFlags.Public);
private static readonly Type InternalSetValueFuncType = InternalType?.GetNestedTypeOrThrow(nameof(SetValueFunc), BindingFlags.Public);
#if UNITY_2023_1_OR_NEWER
private static readonly Delegate _Draw = InternalType?.CreateMethodDelegate(nameof(Draw), BindingFlags.Public | BindingFlags.Static,
typeof(Action<,,>).MakeGenericType(
typeof(GUIContent), InternalGetValueFuncType, InternalSetValueFuncType)
);
#else
private delegate void Ref2Action<T1, T2, T3, T4>(T1 t1, ref T2 t2, T3 t3, T4 t4);
private static readonly Delegate _DoGUI = InternalType?.CreateMethodDelegate("DoGUI", BindingFlags.Public | BindingFlags.Static,
typeof(Ref2Action<,,,>).MakeGenericType(
typeof(GUIContent), typeof(bool), InternalGetValueFuncType, InternalSetValueFuncType)
);
#endif
public delegate bool GetValueFunc(int layerA, int layerB);
public delegate void SetValueFunc(int layerA, int layerB, bool val);
public static void Draw(GUIContent label, GetValueFunc getValue, SetValueFunc setValue) {
if (InternalType == null) {
throw new InvalidOperationException($"{TypeName} not found");
}
var getter = Delegate.CreateDelegate(InternalGetValueFuncType, getValue.Target, getValue.Method);
var setter = Delegate.CreateDelegate(InternalSetValueFuncType, setValue.Target, setValue.Method);
#if UNITY_2023_1_OR_NEWER
_Draw.DynamicInvoke(label, getter, setter);
#else
bool show = true;
var args = new object[] { label, show, getter, setter };
_DoGUI.DynamicInvoke(args);
#endif
}
}
[UnityEditor.InitializeOnLoad]
public static class DecoratorDrawer {
private static InstanceAccessor<PropertyAttribute> m_Attribute = typeof(UnityEditor.DecoratorDrawer).CreateFieldAccessor<PropertyAttribute>(nameof(m_Attribute));
public static void SetAttribute(UnityEditor.DecoratorDrawer drawer, PropertyAttribute attribute) {
m_Attribute.SetValue(drawer, attribute);
}
}
[UnityEditor.InitializeOnLoad]
public static class PropertyDrawer {
private static InstanceAccessor<PropertyAttribute> m_Attribute = typeof(UnityEditor.PropertyDrawer).CreateFieldAccessor<PropertyAttribute>(nameof(m_Attribute));
private static InstanceAccessor<FieldInfo> m_FieldInfo = typeof(UnityEditor.PropertyDrawer).CreateFieldAccessor<FieldInfo>(nameof(m_FieldInfo));
public static void SetAttribute(UnityEditor.PropertyDrawer drawer, PropertyAttribute attribute) {
m_Attribute.SetValue(drawer, attribute);
}
public static void SetFieldInfo(UnityEditor.PropertyDrawer drawer, FieldInfo fieldInfo) {
m_FieldInfo.SetValue(drawer, fieldInfo);
}
}
[UnityEditor.InitializeOnLoad]
public static class EditorGUIUtility {
private static readonly StaticAccessor<int> s_LastControlID = typeof(UnityEditor.EditorGUIUtility).CreateStaticFieldAccessor<int>(nameof(s_LastControlID));
private static readonly StaticAccessor<float> _contentWidth = typeof(UnityEditor.EditorGUIUtility).CreateStaticPropertyAccessor<float>(nameof(contextWidth));
public static int LastControlID => s_LastControlID.GetValue();
public static float contextWidth => _contentWidth.GetValue();
public delegate UnityEngine.Object GetScriptDelegate(string scriptClass);
public delegate Texture2D GetIconForObjectDelegate(UnityEngine.Object obj);
public delegate GUIContent TempContentDelegate(string text);
public delegate Texture2D GetHelpIconDelegate(MessageType type);
public static readonly GetScriptDelegate GetScript = typeof(UnityEditor.EditorGUIUtility).CreateMethodDelegate<GetScriptDelegate>(nameof(GetScript));
public static readonly GetIconForObjectDelegate GetIconForObject = typeof(UnityEditor.EditorGUIUtility).CreateMethodDelegate<GetIconForObjectDelegate>(nameof(GetIconForObject));
public static readonly TempContentDelegate TempContent = typeof(UnityEditor.EditorGUIUtility).CreateMethodDelegate<TempContentDelegate>(nameof(TempContent));
public static readonly GetHelpIconDelegate GetHelpIcon = typeof(UnityEditor.EditorGUIUtility).CreateMethodDelegate<GetHelpIconDelegate>(nameof(GetHelpIcon));
}
[UnityEditor.InitializeOnLoad]
public static class ScriptAttributeUtility {
public delegate List<PropertyAttribute> GetFieldAttributesDelegate(FieldInfo field);
public delegate FieldInfo GetFieldInfoFromPropertyDelegate(SerializedProperty property, out Type type);
public static readonly Type InternalType = typeof(UnityEditor.Editor).Assembly.GetType("UnityEditor.ScriptAttributeUtility", true);
public static readonly GetFieldInfoFromPropertyDelegate GetFieldInfoFromProperty =
CreateEditorMethodDelegate<GetFieldInfoFromPropertyDelegate>(
"UnityEditor.ScriptAttributeUtility",
"GetFieldInfoFromProperty",
BindingFlags.Static | BindingFlags.NonPublic);
public delegate Type GetDrawerTypeForPropertyAndTypeDelegate(SerializedProperty property, Type type);
public delegate Type GetDrawerTypeForTypeDelegate(Type type, Type[] renderPipelineTypes, bool isManagedReference);
#if UNITY_2023_3_OR_NEWER
public static readonly GetDrawerTypeForPropertyAndTypeDelegate GetDrawerTypeForPropertyAndType =
CreateEditorMethodDelegate<GetDrawerTypeForPropertyAndTypeDelegate>(
"UnityEditor.ScriptAttributeUtility",
nameof(GetDrawerTypeForPropertyAndType),
BindingFlags.Static | BindingFlags.NonPublic);
public static readonly GetDrawerTypeForTypeDelegate GetDrawerTypeForType =
CreateEditorMethodDelegate<GetDrawerTypeForTypeDelegate>(
"UnityEditor.ScriptAttributeUtility",
nameof(GetDrawerTypeForType),
BindingFlags.Static | BindingFlags.NonPublic);
#else
private delegate Type LegacyGetDrawerTypeForTypeDelegate(Type type);
private static readonly LegacyGetDrawerTypeForTypeDelegate LegacyGetDrawerTypeForType =
CreateEditorMethodDelegate<LegacyGetDrawerTypeForTypeDelegate>(
"UnityEditor.ScriptAttributeUtility",
"GetDrawerTypeForType",
BindingFlags.Static | BindingFlags.NonPublic);
public static readonly GetDrawerTypeForPropertyAndTypeDelegate GetDrawerTypeForPropertyAndType = (_, type) => LegacyGetDrawerTypeForType(type);
public static readonly GetDrawerTypeForTypeDelegate GetDrawerTypeForType = (type, _, _) => LegacyGetDrawerTypeForType(type);
#endif
private static readonly GetHandlerDelegate _GetHandler = InternalType.CreateMethodDelegate<GetHandlerDelegate>("GetHandler", BindingFlags.NonPublic | BindingFlags.Static,
MakeFuncType(typeof(SerializedProperty), PropertyHandler.InternalType)
);
public static readonly GetFieldAttributesDelegate GetFieldAttributes = InternalType.CreateMethodDelegate<GetFieldAttributesDelegate>(nameof(GetFieldAttributes));
private static readonly StaticAccessor<object> _propertyHandlerCache = InternalType.CreateStaticPropertyAccessor(nameof(propertyHandlerCache), PropertyHandlerCache.InternalType);
private static readonly StaticAccessor<object> s_SharedNullHandler = InternalType.CreateStaticFieldAccessor("s_SharedNullHandler", PropertyHandler.InternalType);
private static readonly StaticAccessor<object> s_NextHandler = InternalType.CreateStaticFieldAccessor("s_NextHandler", PropertyHandler.InternalType);
public static PropertyHandlerCache propertyHandlerCache => new() {
_instance = _propertyHandlerCache.GetValue()
};
public static PropertyHandler sharedNullHandler => PropertyHandler.Wrap(s_SharedNullHandler.GetValue());
public static PropertyHandler nextHandler => PropertyHandler.Wrap(s_NextHandler.GetValue());
public static PropertyHandler GetHandler(SerializedProperty property) {
return PropertyHandler.Wrap(_GetHandler(property));
}
private delegate object GetHandlerDelegate(SerializedProperty property);
}
public struct PropertyHandlerCache {
[UnityEditor.InitializeOnLoad]
private static class Statics {
public static readonly Type InternalType = typeof(UnityEditor.Editor).Assembly.GetType("UnityEditor.PropertyHandlerCache", true);
public static readonly GetPropertyHashDelegate GetPropertyHash = InternalType.CreateMethodDelegate<GetPropertyHashDelegate>(nameof(GetPropertyHash));
public static readonly GetHandlerDelegate GetHandler = InternalType.CreateMethodDelegate<GetHandlerDelegate>(nameof(GetHandler), BindingFlags.NonPublic | BindingFlags.Instance,
MakeFuncType(InternalType, typeof(SerializedProperty), PropertyHandler.InternalType));
public static readonly SetHandlerDelegate SetHandler = InternalType.CreateMethodDelegate<SetHandlerDelegate>(nameof(SetHandler), BindingFlags.NonPublic | BindingFlags.Instance,
MakeActionType(InternalType, typeof(SerializedProperty), PropertyHandler.InternalType));
public static readonly FieldInfo m_PropertyHandlers = InternalType.GetFieldOrThrow(nameof(m_PropertyHandlers));
}
public static Type InternalType => Statics.InternalType;
public delegate int GetPropertyHashDelegate(SerializedProperty property);
public delegate object GetHandlerDelegate(object instance, SerializedProperty property);
public delegate void SetHandlerDelegate(object instance, SerializedProperty property, object handlerInstance);
public object _instance;
public PropertyHandler GetHandler(SerializedProperty property) {
return new PropertyHandler {
_instance = Statics.GetHandler(_instance, property)
};
}
public void SetHandler(SerializedProperty property, PropertyHandler newHandler) {
Statics.SetHandler(_instance, property, newHandler._instance);
}
public IEnumerable<(int, PropertyHandler)> PropertyHandlers {
get {
var dict = (IDictionary)Statics.m_PropertyHandlers.GetValue(_instance);
foreach (DictionaryEntry entry in dict) {
yield return ((int)entry.Key, PropertyHandler.Wrap(entry.Value));
}
}
}
}
public struct PropertyHandler : IEquatable<PropertyHandler> {
[UnityEditor.InitializeOnLoad]
private static class Statics {
public static readonly Type InternalType = typeof(UnityEditor.Editor).Assembly.GetType("UnityEditor.PropertyHandler", true);
public static readonly InstanceAccessor<List<UnityEditor.DecoratorDrawer>> m_DecoratorDrawers = InternalType.CreateFieldAccessor<List<UnityEditor.DecoratorDrawer>>(nameof(m_DecoratorDrawers));
public static readonly InstanceAccessor<List<UnityEditor.PropertyDrawer>> m_PropertyDrawers = InternalType.CreateFieldAccessor<List<UnityEditor.PropertyDrawer>>(nameof(m_PropertyDrawers));
}
public static Type InternalType => Statics.InternalType;
public object _instance;
internal static PropertyHandler Wrap(object instance) {
return new() {
_instance = instance
};
}
public static PropertyHandler New() {
return Wrap(Activator.CreateInstance(InternalType));
}
public List<UnityEditor.PropertyDrawer> m_PropertyDrawers {
get => Statics.m_PropertyDrawers.GetValue(_instance);
set => Statics.m_PropertyDrawers.SetValue(_instance, value);
}
public bool Equals(PropertyHandler other) {
return _instance == other._instance;
}
public override int GetHashCode() {
return _instance?.GetHashCode() ?? 0;
}
public override bool Equals(object obj) {
return obj is PropertyHandler h ? Equals(h) : false;
}
public List<UnityEditor.DecoratorDrawer> decoratorDrawers {
get => Statics.m_DecoratorDrawers.GetValue(_instance);
set => Statics.m_DecoratorDrawers.SetValue(_instance, value);
}
}
[UnityEditor.InitializeOnLoad]
public static class EditorApplication {
public static readonly Action Internal_CallAssetLabelsHaveChanged = typeof(UnityEditor.EditorApplication).CreateMethodDelegate<Action>(nameof(Internal_CallAssetLabelsHaveChanged));
}
public struct ObjectSelector {
[UnityEditor.InitializeOnLoad]
private static class Statics {
public static readonly Type InternalType = typeof(UnityEditor.Editor).Assembly.GetType("UnityEditor.ObjectSelector", true);
public static readonly StaticAccessor<bool> _tooltip = InternalType.CreateStaticPropertyAccessor<bool>(nameof(isVisible));
public static readonly StaticAccessor<EditorWindow> _get = InternalType.CreateStaticPropertyAccessor<EditorWindow>(nameof(get), InternalType);
public static readonly InstanceAccessor<string> _searchFilter = InternalType.CreatePropertyAccessor<string>(nameof(searchFilter));
}
private EditorWindow _instance;
public static bool isVisible => Statics._tooltip.GetValue();
public static ObjectSelector get => new() {
_instance = Statics._get.GetValue()
};
public string searchFilter {
get => Statics._searchFilter.GetValue(_instance);
set => Statics._searchFilter.SetValue(_instance, value);
}
private static readonly InstanceAccessor<int> _objectSelectorID = Statics.InternalType.CreateFieldAccessor<int>(nameof(objectSelectorID));
public int objectSelectorID => _objectSelectorID.GetValue(_instance);
}
[UnityEditor.InitializeOnLoad]
public class InspectorWindow {
public static readonly Type InternalType = typeof(UnityEditor.Editor).Assembly.GetType("UnityEditor.InspectorWindow", true);
public static readonly InstanceAccessor<bool> _isLockedAccessor = InternalType.CreatePropertyAccessor<bool>(nameof(isLocked));
private readonly EditorWindow _instance;
public InspectorWindow(EditorWindow instance) {
if (instance == null) {
throw new ArgumentNullException(nameof(instance));
}
_instance = instance;
}
public bool isLocked {
get => _isLockedAccessor.GetValue(_instance);
set => _isLockedAccessor.SetValue(_instance, value);
}
}
[UnityEditor.InitializeOnLoad]
public static class SplitterGUILayout {
public static readonly Action EndHorizontalSplit = CreateMethodDelegate<Action>(typeof(UnityEditor.Editor).Assembly,
"UnityEditor.SplitterGUILayout", "EndHorizontalSplit", BindingFlags.Public | BindingFlags.Static
);
public static readonly Action EndVerticalSplit = CreateMethodDelegate<Action>(typeof(UnityEditor.Editor).Assembly,
"UnityEditor.SplitterGUILayout", "EndVerticalSplit", BindingFlags.Public | BindingFlags.Static
);
public static void BeginHorizontalSplit(SplitterState splitterState, GUIStyle style, params GUILayoutOption[] options) {
_beginHorizontalSplit.DynamicInvoke(splitterState.InternalState, style, options);
}
public static void BeginVerticalSplit(SplitterState splitterState, GUIStyle style, params GUILayoutOption[] options) {
_beginVerticalSplit.DynamicInvoke(splitterState.InternalState, style, options);
}
private static readonly Delegate _beginHorizontalSplit = CreateMethodDelegate(typeof(UnityEditor.Editor).Assembly,
"UnityEditor.SplitterGUILayout", "BeginHorizontalSplit", BindingFlags.Public | BindingFlags.Static,
typeof(Action<,,>).MakeGenericType(SplitterState.InternalType, typeof(GUIStyle), typeof(GUILayoutOption[]))
);
private static readonly Delegate _beginVerticalSplit = CreateMethodDelegate(typeof(UnityEditor.Editor).Assembly,
"UnityEditor.SplitterGUILayout", "BeginVerticalSplit", BindingFlags.Public | BindingFlags.Static,
typeof(Action<,,>).MakeGenericType(SplitterState.InternalType, typeof(GUIStyle), typeof(GUILayoutOption[]))
);
}
[UnityEditor.InitializeOnLoad]
[Serializable]
public class SplitterState : ISerializationCallbackReceiver {
public static readonly Type InternalType = typeof(UnityEditor.Editor).Assembly.GetType("UnityEditor.SplitterState", true);
private static readonly FieldInfo _relativeSizes = InternalType.GetFieldOrThrow("relativeSizes");
private static readonly FieldInfo _realSizes = InternalType.GetFieldOrThrow("realSizes");
private static readonly FieldInfo _splitSize = InternalType.GetFieldOrThrow("splitSize");
public string Json = "{}";
[NonSerialized]
public object InternalState = FromRelativeInner(new[] { 1.0f });
void ISerializationCallbackReceiver.OnAfterDeserialize() {
InternalState = JsonUtility.FromJson(Json, InternalType);
}
void ISerializationCallbackReceiver.OnBeforeSerialize() {
Json = JsonUtility.ToJson(InternalState);
}
public static SplitterState FromRelative(float[] relativeSizes, int[] minSizes = null, int[] maxSizes = null, int splitSize = 0) {
var result = new SplitterState();
result.InternalState = FromRelativeInner(relativeSizes, minSizes, maxSizes, splitSize);
return result;
}
private static object FromRelativeInner(float[] relativeSizes, int[] minSizes = null, int[] maxSizes = null, int splitSize = 0) {
return Activator.CreateInstance(InternalType, BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.CreateInstance,
null,
new object[] { relativeSizes, minSizes, maxSizes, splitSize },
null, null);
}
public float[] realSizes => ConvertArray((Array)_realSizes.GetValue(InternalState));
public float[] relativeSizes => ConvertArray((Array)_relativeSizes.GetValue(InternalState));
public float splitSize => Convert.ToSingle(_splitSize.GetValue(InternalState));
private static float[] ConvertArray(Array value) {
float[] result = new float[value.Length];
for (int i = 0; i < value.Length; ++i) {
result[i] = Convert.ToSingle(value.GetValue(i));
}
return result;
}
}
public sealed class InternalStyles {
public static InternalStyles Instance = new InternalStyles();
public LazyGUIStyle InspectorTitlebar => LazyGUIStyle.Create(_ => GetStyle("IN Title"));
public LazyGUIStyle FoldoutTitlebar => LazyGUIStyle.Create(_ => GetStyle("Titlebar Foldout", "Foldout"));
public LazyGUIStyle BoxWithBorders => LazyGUIStyle.Create(_ => GetStyle("OL Box"));
public LazyGUIStyle HierarchyTreeViewLine => LazyGUIStyle.Create(_ => GetStyle("TV Line"));
public LazyGUIStyle HierarchyTreeViewSceneBackground => LazyGUIStyle.Create(_ => GetStyle("SceneTopBarBg", "ProjectBrowserTopBarBg"));
public LazyGUIStyle OptionsButtonStyle => LazyGUIStyle.Create(_ => GetStyle("PaneOptions"));
public LazyGUIStyle AddComponentButton => LazyGUIStyle.Create(_ => GetStyle("AC Button"));
public LazyGUIStyle AnimationEventTooltip => LazyGUIStyle.Create(_ => GetStyle("AnimationEventTooltip"));
public LazyGUIStyle AnimationEventTooltipArrow => LazyGUIStyle.Create(_ => GetStyle("AnimationEventTooltipArrow"));
private static GUIStyle GetStyle(params string[] names) {
var skin = GUI.skin;
foreach (var name in names) {
var result = skin.FindStyle(name);
if (result != null) {
return result;
}
}
throw new ArgumentOutOfRangeException($"Style not found: {string.Join(", ", names)}", nameof(names));
}
}
public static InternalStyles Styles => InternalStyles.Instance;
}
}
#endregion
#region ArrayLengthAttributeDrawer.cs
namespace Fusion.Editor {
using UnityEditor;
using UnityEngine;
internal partial class ArrayLengthAttributeDrawer : DecoratingPropertyAttributeDrawer, INonApplicableOnArrayElements {
private GUIStyle _style;
private GUIStyle GetStyle() {
if (_style == null) {
_style = new GUIStyle(EditorStyles.miniLabel);
_style.alignment = TextAnchor.MiddleRight;
_style.contentOffset = new Vector2(-2, 0);
_style.normal.textColor = EditorGUIUtility.isProSkin ? new Color(255f / 255f, 221 / 255f, 0 / 255f, 1f) : Color.blue;
}
return _style;
}
protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) {
base.OnGUIInternal(position, property, label);
if (!property.isArray) {
return;
}
var overlayRect = position;
overlayRect.height = EditorGUIUtility.singleLineHeight;
var attrib = (ArrayLengthAttribute)attribute;
// draw length overlay
GUI.Label(overlayRect, $"[{attrib.MaxLength}]", GetStyle());
if (property.arraySize > attrib.MaxLength) {
property.arraySize = attrib.MaxLength;
property.serializedObject.ApplyModifiedProperties();
} else if (property.arraySize < attrib.MinLength) {
property.arraySize = attrib.MinLength;
property.serializedObject.ApplyModifiedProperties();
}
}
}
[CustomPropertyDrawer(typeof(ArrayLengthAttribute))]
[RedirectCustomPropertyDrawer(typeof(ArrayLengthAttribute), typeof(ArrayLengthAttributeDrawer))]
partial class PropertyDrawerForArrayWorkaround {
}
}
#endregion
#region AssemblyNameAttributeDrawer.cs
namespace Fusion.Editor {
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;
[CustomPropertyDrawer(typeof(AssemblyNameAttribute))]
internal class AssemblyNameAttributeDrawer : PropertyDrawerWithErrorHandling {
const float DropdownWidth = 20.0f;
static GUIContent DropdownContent = new GUIContent("");
string _lastCheckedAssemblyName;
[Flags]
enum AsmDefType {
Predefined = 1 << 0,
InPackages = 1 << 1,
InAssets = 1 << 2,
Editor = 1 << 3,
Runtime = 1 << 4,
All = Predefined | InPackages | InAssets | Editor | Runtime,
}
HashSet<string> _allAssemblies;
protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) {
var assemblyName = property.stringValue;
bool notFound = false;
if (!string.IsNullOrEmpty(assemblyName)) {
if (_allAssemblies == null) {
_allAssemblies = new HashSet<string>(GetAssemblies(AsmDefType.All), StringComparer.OrdinalIgnoreCase);
}
if (!_allAssemblies.Contains(assemblyName, StringComparer.OrdinalIgnoreCase)) {
SetInfo($"Assembly not found: {assemblyName}");
notFound = true;
}
}
using (new FusionEditorGUI.PropertyScope(position, label, property)) {
EditorGUI.BeginChangeCheck();
assemblyName = EditorGUI.TextField(new Rect(position) { xMax = position.xMax - DropdownWidth },
label,
assemblyName,
notFound ?
new GUIStyle(EditorStyles.textField) {
fontStyle = FontStyle.Italic,
normal = new GUIStyleState() { textColor = Color.gray }
} : EditorStyles.textField
);
var dropdownRect = EditorGUI.IndentedRect(new Rect(position) {
xMin = position.xMax - DropdownWidth
});
if (EditorGUI.DropdownButton(dropdownRect, DropdownContent, FocusType.Passive)) {
GenericMenu.MenuFunction2 onClicked = (userData) => {
property.stringValue = (string)userData;
property.serializedObject.ApplyModifiedProperties();
UnityInternal.EditorGUI.EndEditingActiveTextField();
ClearError(property);
};
var menu = new GenericMenu();
foreach (var (flag, prefix) in new[] {
(AsmDefType.Editor, "Editor/"),
(AsmDefType.Runtime, "")
}) {
if (menu.GetItemCount() != 0) {
menu.AddSeparator(prefix);
}
foreach (var name in GetAssemblies(flag | AsmDefType.InPackages)) {
menu.AddItem(new GUIContent($"{prefix}Packages/{name}"), string.Equals(name, assemblyName, StringComparison.OrdinalIgnoreCase), onClicked, name);
}
menu.AddSeparator(prefix);
foreach (var name in GetAssemblies(flag | AsmDefType.InAssets | AsmDefType.Predefined)) {
menu.AddItem(new GUIContent($"{prefix}{name}"), string.Equals(name, assemblyName, StringComparison.OrdinalIgnoreCase), onClicked, name);
}
}
menu.DropDown(dropdownRect);
}
if (EditorGUI.EndChangeCheck()) {
property.stringValue = assemblyName;
property.serializedObject.ApplyModifiedProperties();
base.ClearError();
}
}
}
static IEnumerable<string> GetAssemblies(AsmDefType types) {
IEnumerable<string> query = Enumerable.Empty<string>();
if (types.HasFlag(AsmDefType.Predefined)) {
if (types.HasFlag(AsmDefType.Runtime)) {
query = query.Concat(new[] {
"Assembly-CSharp-firstpass",
"Assembly-CSharp"
});
}
if (types.HasFlag(AsmDefType.Editor)) {
query = query.Concat(new[] {
"Assembly-CSharp-Editor-firstpass",
"Assembly-CSharp-Editor"
});
}
}
if (types.HasFlag(AsmDefType.InAssets) || types.HasFlag(AsmDefType.InPackages)) {
query = query.Concat(
AssetDatabase.FindAssets("t:asmdef")
.Select(x => AssetDatabase.GUIDToAssetPath(x))
.Where(x => {
if (types.HasFlag(AsmDefType.InAssets) && x.StartsWith("Assets/")) {
return true;
} else if (types.HasFlag(AsmDefType.InPackages) && x.StartsWith("Packages/")) {
return true;
} else {
return false;
}
})
.Select(x => JsonUtility.FromJson<AsmDefData>(File.ReadAllText(x)))
.Where(x => {
bool editorOnly = x.includePlatforms.Length == 1 && x.includePlatforms[0] == "Editor";
if (types.HasFlag(AsmDefType.Runtime) && !editorOnly) {
return true;
} else if (types.HasFlag(AsmDefType.Editor) && editorOnly) {
return true;
} else {
return false;
}
})
.Select(x => x.name)
.Distinct()
);
}
return query;
}
[Serializable]
private class AsmDefData {
public string[] includePlatforms = Array.Empty<string>();
public string name = string.Empty;
}
}
}
#endregion
#region BinaryDataAttributeDrawer.cs
namespace Fusion.Editor {
using System;
using System.Text;
using UnityEngine;
using UnityEditor;
internal partial class BinaryDataAttributeDrawer : PropertyDrawerWithErrorHandling, INonApplicableOnArrayElements {
private int MaxLines = 16;
private RawDataDrawer _drawer = new RawDataDrawer();
protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) {
using (new FusionEditorGUI.PropertyScope(position, label, property)) {
bool wasExpanded = property.isExpanded;
var foldoutPosition = new Rect(position) { height = EditorGUIUtility.singleLineHeight };
property.isExpanded = EditorGUI.Foldout(foldoutPosition, property.isExpanded, label);
if (property.hasMultipleDifferentValues) {
FusionEditorGUI.Overlay(foldoutPosition, $"---");
} else {
FusionEditorGUI.Overlay(foldoutPosition, $"{property.arraySize}");
}
if (!wasExpanded) {
return;
}
position.yMin += foldoutPosition.height + EditorGUIUtility.standardVerticalSpacing;
using (new FusionEditorGUI.EnabledScope(true)) {
_drawer.Draw(GUIContent.none, position);
}
}
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
if (!property.isExpanded) {
return EditorGUIUtility.singleLineHeight;
}
_drawer.Refresh(property);
// space for scrollbar and indent
var width = UnityInternal.EditorGUIUtility.contextWidth - 32.0f;
var height = _drawer.GetHeight(width);
return EditorGUIUtility.singleLineHeight +
EditorGUIUtility.standardVerticalSpacing +
Mathf.Min(FusionEditorGUI.GetLinesHeight(MaxLines), height);
}
}
[CustomPropertyDrawer(typeof(BinaryDataAttribute))]
[RedirectCustomPropertyDrawer(typeof(BinaryDataAttribute), typeof(BinaryDataAttributeDrawer))]
partial class PropertyDrawerForArrayWorkaround {
}
}
#endregion
#region BitSetAttributeDrawer.cs
// namespace Fusion.Editor {
// using System;
// using UnityEditor;
// using UnityEngine;
//
// [CustomPropertyDrawer(typeof(BitSetAttribute))]
// public class BitSetAttributeDrawer : PropertyDrawer {
//
// public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
//
// if (property.IsArrayElement()) {
// throw new NotSupportedException();
// }
//
// var longValue = property.longValue;
//
// int bitStart = 0;
// int bitEnd = ((BitSetAttribute)attribute).BitCount;
//
// using (new FusionEditorGUI.PropertyScopeWithPrefixLabel(position, label, property, out var valueRect)) {
// var pos = valueRect;
//
// DrawAndMeasureLabel(valueRect, bitStart, FusionEditorSkin.instance.MiniLabelLowerRight);
// DrawAndMeasureLabel(valueRect, bitEnd, FusionEditorSkin.instance.MiniLabelLowerLeft);
//
// var tmpContent = new GUIContent();
// tmpContent.text = $"{bitStart}";
// var bitStartSize = EditorStyles.miniLabel.CalcSize(tmpContent);
//
//
// tmpContent.text = $"{bitEnd}";
// var bitEndSize = EditorStyles.miniLabel.CalcSize(tmpContent);
// valueRect.width = bitEndSize.x;
// GUI.Label(valueRect, tmpContent, EditorStyles.miniLabel);
// valueRect.x += bitEndSize.x;
// var availableWidth = valueRect.width - bitStartSize.x - bitEndSize.x;
//
//
// // how may per one line?
// const float ToggleWidth = 15.0f;
//
// valueRect.width = ToggleWidth;
// for (int i = 0; i < 16; ++i) {
// EditorGUI.Toggle(valueRect, false);
// valueRect.x += ToggleWidth;
// }
// }
//
// float DrawAndMeasureLabel(Rect position, int label, GUIStyle style) {
// var tmpContent = new GUIContent($"{bitEnd}");
// var contentSize = style.CalcSize(tmpContent);
// GUI.Label(position, tmpContent, style);
// return contentSize.x;
// }
//
// //base.OnGUI(position, property, label);
// }
// }
// }
#endregion
#region DecoratingPropertyAttributeDrawer.cs
//#define FUSION_EDITOR_TRACE
namespace Fusion.Editor {
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using UnityEditor;
using UnityEngine;
public abstract class DecoratingPropertyAttributeDrawer : PropertyDrawer {
private bool _isLastDrawer;
private int _nestingLevel;
/// <summary>
/// The drawer that's been chosen by Unity; its job is to
/// iterate all ForwardingPropertyDrawerBase drawers
/// that'd be created had Unity 2020.3 supported multiple
/// property drawers - including self.
/// </summary>
protected DecoratingPropertyAttributeDrawer MainDrawer { get; private set; }
public List<DecoratingPropertyAttributeDrawer> PropertyDrawers { get; private set; }
public PropertyDrawer NextDrawer { get; private set; }
public DecoratingPropertyAttributeDrawer() {
TraceField("constructor");
}
[Obsolete("Derived classes should override and call OnGUIInternal", true)]
#pragma warning disable CS0809 // Obsolete member overrides non-obsolete member
public sealed override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
#pragma warning restore CS0809 // Obsolete member overrides non-obsolete member
TraceField($"OnGUI({position}, {property.propertyPath}, {label})");
EnsureInitialized(property);
FusionEditorLog.Assert(MainDrawer == this);
FusionEditorLog.Assert(PropertyDrawers != null);
FusionEditorLog.Assert(PropertyDrawers.Count > 0);
PropertyDrawers[0].InvokeOnGUIInternal(position, property, label);
}
[Obsolete("Derived classes should override and call GetPropertyHeightInternal", true)]
#pragma warning disable CS0809 // Obsolete member overrides non-obsolete member
public sealed override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
#pragma warning restore CS0809 // Obsolete member overrides non-obsolete member
TraceField($"GetPropertyHeight({property.propertyPath}, {label})");
EnsureInitialized(property);
FusionEditorLog.Assert(MainDrawer == this);
FusionEditorLog.Assert(PropertyDrawers != null);
FusionEditorLog.Assert(PropertyDrawers.Count > 0);
return PropertyDrawers[0].InvokeGetPropertyHeightInternal(property, label);
}
protected virtual float GetPropertyHeightInternal(SerializedProperty property, GUIContent label) {
FusionEditorLog.Assert(MainDrawer != null);
return MainDrawer.InvokeGetPropertyHeightOnNextDrawer(this, property, label);
}
protected virtual void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) {
TraceField($"OnGUIInternal({position}, {property.propertyPath}, {label})");
FusionEditorLog.Assert(MainDrawer != null);
FusionEditorLog.Assert(_nestingLevel == 0, $"{property.propertyPath} {GetType().FullName}");
_nestingLevel++;
try {
MainDrawer.InvokeOnGUIOnNextDrawer(this, position, property, label);
} finally {
_nestingLevel--;
}
}
private void InvokeOnGUIOnNextDrawer(DecoratingPropertyAttributeDrawer current, Rect position, SerializedProperty prop, GUIContent label) {
FusionEditorLog.Assert(MainDrawer == this);
var index = PropertyDrawers.IndexOf(current);
if (index < PropertyDrawers.Count - 1) {
PropertyDrawers[index + 1].InvokeOnGUIInternal(position, prop, label);
} else {
if (NextDrawer != null) {
NextDrawer.OnGUI(position, prop, label);
} else {
FusionEditorGUI.ForwardPropertyField(position, prop, label, prop.IsArrayProperty() ? true : prop.isExpanded, _isLastDrawer);
}
}
}
private void InvokeOnGUIInternal(Rect position, SerializedProperty prop, GUIContent label) {
if (prop.IsArrayElement() && this is INonApplicableOnArrayElements) {
MainDrawer.InvokeOnGUIOnNextDrawer(this, position, prop, label);
} else {
OnGUIInternal(position, prop, label);
}
}
private float InvokeGetPropertyHeightOnNextDrawer(DecoratingPropertyAttributeDrawer current, SerializedProperty prop, GUIContent label) {
FusionEditorLog.Assert(MainDrawer == this);
var index = PropertyDrawers.IndexOf(current);
if (index < PropertyDrawers.Count - 1) {
return PropertyDrawers[index + 1].InvokeGetPropertyHeightInternal(prop, label);
}
return NextDrawer?.GetPropertyHeight(prop, label) ?? EditorGUI.GetPropertyHeight(prop, label);
}
private float InvokeGetPropertyHeightInternal(SerializedProperty prop, GUIContent label) {
if (prop.IsArrayElement() && this is INonApplicableOnArrayElements) {
return MainDrawer.InvokeGetPropertyHeightOnNextDrawer(this, prop, label);
} else {
return GetPropertyHeightInternal(prop, label);
}
}
protected virtual bool EnsureInitialized(SerializedProperty property) {
if (MainDrawer != null || PropertyDrawers != null) {
return false;
}
if (fieldInfo == null) {
// this might happen if this drawer is created dynamically
var field = UnityInternal.ScriptAttributeUtility.GetFieldInfoFromProperty(property, out _);
FusionEditorLog.Assert(field != null, $"Could not find field for property {property.propertyPath} of type {property.serializedObject.targetObject.GetType().FullName} (I'm {GetType().FullName} {GetHashCode()})");
UnityInternal.PropertyDrawer.SetFieldInfo(this, field);
}
FusionEditorLog.Assert(attribute != null);
FusionEditorLog.Assert(attribute is DecoratingPropertyAttribute, $"Expected attribute to be of type {nameof(DecoratingPropertyAttribute)} but it's {attribute.GetType().FullName}");
PropertyDrawers = new List<DecoratingPropertyAttributeDrawer>();
MainDrawer = this;
NextDrawer = null;
var isLastDrawer = false;
var foundSelf = false;
var fieldAttributes = fieldInfo != null ? UnityInternal.ScriptAttributeUtility.GetFieldAttributes(fieldInfo) : null;
if (fieldAttributes != null) {
FusionEditorLog.Assert(fieldAttributes.OrderBy(x => x.order).SequenceEqual(fieldAttributes), "Expected field attributes to be sorted");
FusionEditorLog.Assert(fieldAttributes.Count > 0);
for (var i = 0; i < fieldAttributes.Count; ++i) {
var fieldAttribute = fieldAttributes[i];
var attributeDrawerType = UnityInternal.ScriptAttributeUtility.GetDrawerTypeForPropertyAndType(property, fieldAttribute.GetType());
if (attributeDrawerType == null) {
TraceField($"No drawer for {attributeDrawerType}");
continue;
}
if (attributeDrawerType == typeof(PropertyDrawerForArrayWorkaround)) {
attributeDrawerType = PropertyDrawerForArrayWorkaround.GetDrawerType(fieldAttribute.GetType());
}
if (attributeDrawerType.IsSubclassOf(typeof(DecoratorDrawer))) {
// decorators are their own thing
continue;
}
if (property.IsArrayElement() && attributeDrawerType.GetInterface(typeof(INonApplicableOnArrayElements).FullName) != null) {
// skip drawers that are not meant to be used on array elements
continue;
}
FusionEditorLog.Assert(attributeDrawerType.IsSubclassOf(typeof(PropertyDrawer)));
if (fieldAttribute.Equals(attribute)) {
// self
PropertyDrawers.Add(this);
FusionEditorLog.Assert(foundSelf == false);
foundSelf = true;
isLastDrawer = true;
TraceField($"Found self at {i} ({this})");
continue;
}
isLastDrawer = false;
}
}
if (!foundSelf) {
TraceField("Force-adding self");
PropertyDrawers.Add(this);
}
if (NextDrawer == null && isLastDrawer && fieldInfo != null) {
// try creating type drawer instead
var fieldType = fieldInfo.FieldType;
if (property.IsArrayElement()) {
fieldType = fieldType.GetUnityLeafType();
}
var typeDrawerType = UnityInternal.ScriptAttributeUtility.GetDrawerTypeForPropertyAndType(property, fieldType);
if (typeDrawerType != null) {
var drawer = (PropertyDrawer)Activator.CreateInstance(typeDrawerType);
UnityInternal.PropertyDrawer.SetFieldInfo(drawer, fieldInfo);
TraceField($"Found final drawer is type drawer ({drawer})");
NextDrawer = drawer;
}
}
if (isLastDrawer) {
_isLastDrawer = true;
}
return true;
}
internal void InitInjected(PropertyDrawer next) {
MainDrawer = this;
PropertyDrawers = new List<DecoratingPropertyAttributeDrawer> {
this
};
NextDrawer = next;
}
public PropertyDrawer GetNextDrawer(SerializedProperty property) {
if (NextDrawer != null) {
return NextDrawer;
}
var handler = UnityInternal.ScriptAttributeUtility.propertyHandlerCache.GetHandler(property);
var drawers = handler.m_PropertyDrawers;
var index = drawers.IndexOf(this);
if (index >= 0 && index < drawers.Count - 1) {
return drawers[index + 1];
}
return null;
}
[Conditional("FUSION_EDITOR_TRACE")]
private void TraceField(string message) {
FusionEditorLog.TraceInspector($"[{GetType().FullName}] [{GetHashCode():X8}] [{fieldInfo?.DeclaringType.Name}.{fieldInfo?.Name}] {message}");
}
}
}
#endregion
#region DisplayAsEnumAttributeDrawer.cs
namespace Fusion.Editor {
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEditor;
using UnityEngine;
[CustomPropertyDrawer(typeof(DisplayAsEnumAttribute))]
internal class DisplayAsEnumAttributeDrawer : PropertyDrawerWithErrorHandling {
private EnumDrawer _enumDrawer;
private Dictionary<(Type, string), Func<object, Type>> _cachedGetters = new Dictionary<(Type, string), Func<object, Type>>();
protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) {
var attr = (DisplayAsEnumAttribute)attribute;
var enumType = attr.EnumType;
if (enumType == null && !string.IsNullOrEmpty(attr.EnumTypeMemberName)) {
var objType = property.serializedObject.targetObject.GetType();
if (!_cachedGetters.TryGetValue((objType, attr.EnumTypeMemberName), out var getter)) {
// maybe this is a top-level property then and we can use reflection?
if (property.depth != 0) {
FusionEditorLog.ErrorInspector($"Can't get enum type for {property.propertyPath}: non-SerializedProperty checks only work for top-level properties");
} else {
try {
getter = objType.CreateGetter<Type>(attr.EnumTypeMemberName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy);
} catch (Exception e) {
FusionEditorLog.ErrorInspector($"Can't get enum type for {property.propertyPath}: unable to create getter for {attr.EnumTypeMemberName} with exception {e}");
}
}
_cachedGetters.Add((objType, attr.EnumTypeMemberName), getter);
}
enumType = getter(property.serializedObject.targetObject);
}
using (new FusionEditorGUI.PropertyScopeWithPrefixLabel(position, label, property, out var valueRect)) {
if (enumType == null) {
SetError($"Unable to get enum type for {property.propertyPath}");
} else if (!enumType.IsEnum) {
SetError($"Type {enumType} is not an enum type");
} else {
ClearError();
_enumDrawer.Draw(valueRect, property, enumType, true);
}
}
}
}
}
#endregion
#region DisplayNameAttributeDrawer.cs
namespace Fusion.Editor {
using UnityEditor;
using UnityEngine;
//[CustomPropertyDrawer(typeof(DisplayNameAttribute))]
internal class DisplayNameAttributeDrawer : DecoratingPropertyAttributeDrawer, INonApplicableOnArrayElements {
private GUIContent _label = new GUIContent();
protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) {
if (((DisplayNameAttribute)attribute).Name == null) {
base.OnGUIInternal(position, property, label);
return;
}
if (label.text == string.Empty && label.image == null || property.IsArrayElement()) {
base.OnGUIInternal(position, property, label);
return;
}
_label.text = ((DisplayNameAttribute)attribute).Name;
_label.image = label.image;
_label.tooltip = label.tooltip;
base.OnGUIInternal(position, property, _label);
}
#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED
[FusionOdinAttributeConverter]
static System.Attribute[] ConvertToOdinAttributes(System.Reflection.MemberInfo memberInfo, DisplayNameAttribute attribute) {
return new[] { new Sirenix.OdinInspector.LabelTextAttribute(attribute.Name) };
}
#endif
}
[CustomPropertyDrawer(typeof(DisplayNameAttribute))]
[RedirectCustomPropertyDrawer(typeof(DisplayNameAttribute), typeof(DisplayNameAttributeDrawer))]
partial class PropertyDrawerForArrayWorkaround {
}
}
#endregion
#region DoIfAttributeDrawer.cs
namespace Fusion.Editor {
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEditor;
internal abstract partial class DoIfAttributeDrawer : DecoratingPropertyAttributeDrawer, INonApplicableOnArrayElements {
private static Dictionary<(Type, string), Func<object, object>> _cachedGetters = new Dictionary<(Type, string), Func<object, object>>();
internal static bool CheckDraw(DoIfAttributeBase doIf, SerializedObject serializedObject) {
var compareProperty = serializedObject.FindProperty(doIf.ConditionMember);
if (compareProperty != null) {
return CheckProperty(doIf, compareProperty);
}
return CheckGetter(doIf, serializedObject, 0, string.Empty) == true;
}
internal static bool CheckDraw(DoIfAttributeBase doIf, SerializedProperty property) {
var compareProperty = property.depth < 0 ? property.FindPropertyRelative(doIf.ConditionMember) : property.FindPropertyRelativeToParent(doIf.ConditionMember);
if (compareProperty != null) {
return CheckProperty(doIf, compareProperty);
}
return CheckGetter(doIf, property.serializedObject, property.depth, property.propertyPath) == true;
}
private static bool CheckProperty(DoIfAttributeBase doIf, SerializedProperty compareProperty) {
switch (compareProperty.propertyType) {
case SerializedPropertyType.Boolean:
case SerializedPropertyType.Integer:
case SerializedPropertyType.Enum:
case SerializedPropertyType.Character:
return CheckCondition(doIf, compareProperty.longValue);
case SerializedPropertyType.ObjectReference:
return CheckCondition(doIf, compareProperty.objectReferenceInstanceIDValue);
case SerializedPropertyType.Float:
return CheckCondition(doIf, compareProperty.doubleValue);
default:
FusionEditorLog.ErrorInspector($"Can't check condition for {compareProperty.propertyPath}: unsupported property type {compareProperty.propertyType}");
return true;
}
}
private static bool? CheckGetter(DoIfAttributeBase doIf, SerializedObject serializedObject, int depth, string referencePath) {
var objType = serializedObject.targetObject.GetType();
if (!_cachedGetters.TryGetValue((objType, doIf.ConditionMember), out var getter)) {
// maybe this is a top-level property then and we can use reflection?
if (depth != 0) {
if (doIf.ErrorOnConditionMemberNotFound) {
FusionEditorLog.ErrorInspector($"Can't check condition for {referencePath}: non-SerializedProperty checks only work for top-level properties (depth:{depth}, conditionMember:{doIf.ConditionMember})");
}
} else {
try {
getter = objType.CreateGetter(doIf.ConditionMember, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy);
} catch (Exception e) {
if (doIf.ErrorOnConditionMemberNotFound) {
FusionEditorLog.ErrorInspector($"Can't check condition for {referencePath}: unable to create getter for {doIf.ConditionMember} with exception {e}");
}
}
}
_cachedGetters.Add((objType, doIf.ConditionMember), getter);
}
if (getter != null) {
bool? result = null;
foreach (var target in serializedObject.targetObjects) {
bool targetResult = CheckCondition(doIf, getter(target));
if (result.HasValue && result.Value != targetResult) {
return null;
} else {
result = targetResult;
}
}
return result;
} else {
return true;
}
}
public static bool CheckCondition(DoIfAttributeBase attribute, double value) {
if (!attribute._isDouble) throw new InvalidOperationException();
var doubleValue = attribute._doubleValue;
switch (attribute.Compare) {
case CompareOperator.Equal: return value == doubleValue;
case CompareOperator.NotEqual: return value != doubleValue;
case CompareOperator.Less: return value < doubleValue;
case CompareOperator.LessOrEqual: return value <= doubleValue;
case CompareOperator.GreaterOrEqual: return value >= doubleValue;
case CompareOperator.Greater: return value > doubleValue;
case CompareOperator.NotZero: return value != 0;
case CompareOperator.IsZero: return value == 0;
case CompareOperator.BitwiseAndNotEqualZero: throw new NotSupportedException();
default: throw new ArgumentOutOfRangeException();
}
}
public static bool CheckCondition(DoIfAttributeBase attribute, long value) {
if (attribute._isDouble) throw new InvalidOperationException();
var _longValue = attribute._longValue;
switch (attribute.Compare) {
case CompareOperator.Equal: return value == _longValue;
case CompareOperator.NotEqual: return value != _longValue;
case CompareOperator.Less: return value < _longValue;
case CompareOperator.LessOrEqual: return value <= _longValue;
case CompareOperator.GreaterOrEqual: return value >= _longValue;
case CompareOperator.Greater: return value > _longValue;
case CompareOperator.NotZero: return value != 0;
case CompareOperator.IsZero: return value == 0;
case CompareOperator.BitwiseAndNotEqualZero: return (value & _longValue) != 0;
default: throw new ArgumentOutOfRangeException();
}
}
public static bool CheckCondition(DoIfAttributeBase attribute, object value) {
if (attribute._isDouble) {
double converted = 0.0;
if (value != null) {
if (value is UnityEngine.Object o && !o) {
// treat as 0
} else if (value.GetType().IsValueType) {
converted = Convert.ToDouble(value);
} else {
converted = 1.0;
}
}
return CheckCondition(attribute, converted);
} else {
long converted = 0;
if (value != null) {
if (value is UnityEngine.Object o && !o) {
// treat as 0
} else if (value.GetType().IsValueType) {
converted = Convert.ToInt64(value);
} else {
converted = 1;
}
}
return CheckCondition(attribute, converted);
}
}
}
}
#endregion
#region DrawIfAttributeDrawer.cs
namespace Fusion.Editor {
using UnityEditor;
using UnityEngine;
internal partial class DrawIfAttributeDrawer : DoIfAttributeDrawer {
public DrawIfAttribute Attribute => (DrawIfAttribute)attribute;
protected override float GetPropertyHeightInternal(SerializedProperty property, GUIContent label) {
if (Attribute.Mode == DrawIfMode.ReadOnly || CheckDraw(Attribute, property)) {
return base.GetPropertyHeightInternal(property, label);
}
return -EditorGUIUtility.standardVerticalSpacing;
}
protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) {
var readOnly = Attribute.Mode == DrawIfMode.ReadOnly;
var draw = CheckDraw(Attribute, property);
if (readOnly || draw) {
EditorGUI.BeginDisabledGroup(!draw);
base.OnGUIInternal(position, property, label);
EditorGUI.EndDisabledGroup();
}
}
}
[CustomPropertyDrawer(typeof(DrawIfAttribute))]
[RedirectCustomPropertyDrawer(typeof(DrawIfAttribute), typeof(DrawIfAttributeDrawer))]
partial class PropertyDrawerForArrayWorkaround {
}
}
#endregion
#region DrawInlineAttributeDrawer.cs
namespace Fusion.Editor {
using UnityEditor;
using UnityEngine;
[CustomPropertyDrawer(typeof(DrawInlineAttribute))]
[FusionPropertyDrawerMeta(HasFoldout = false)]
internal partial class DrawInlineAttributeDrawer : PropertyDrawer {
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
EditorGUI.BeginProperty(position, label, property);
foreach (var childProperty in property.GetChildren()) {
position.height = EditorGUI.GetPropertyHeight(childProperty, true);
EditorGUI.PropertyField(position, childProperty, true);
position.y += position.height + EditorGUIUtility.standardVerticalSpacing;
}
EditorGUI.EndProperty();
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
float height = 0f;
foreach (var childProperty in property.GetChildren()) {
height += EditorGUI.GetPropertyHeight(childProperty, true) + EditorGUIUtility.standardVerticalSpacing;
}
height -= EditorGUIUtility.standardVerticalSpacing;
return height;
}
}
}
#endregion
#region ErrorIfAttributeDrawer.cs
namespace Fusion.Editor {
using UnityEditor;
using UnityEngine;
internal partial class ErrorIfAttributeDrawer : MessageIfDrawerBase {
private new ErrorIfAttribute Attribute => (ErrorIfAttribute)attribute;
protected override bool IsBox => Attribute.AsBox;
protected override string Message => Attribute.Message;
protected override MessageType MessageType => MessageType.Error;
override protected Color InlineBoxColor => FusionEditorSkin.ErrorInlineBoxColor;
protected override Texture MessageIcon => FusionEditorSkin.ErrorIcon;
}
[CustomPropertyDrawer(typeof(ErrorIfAttribute))]
[RedirectCustomPropertyDrawer(typeof(ErrorIfAttribute), typeof(ErrorIfAttributeDrawer))]
partial class PropertyDrawerForArrayWorkaround {
}
}
#endregion
#region ExpandableEnumAttributeDrawer.cs
namespace Fusion.Editor {
using System;
using System.Reflection;
using UnityEditor;
using UnityEngine;
[CustomPropertyDrawer(typeof(ExpandableEnumAttribute))]
internal class ExpandableEnumAttributeDrawer : PropertyDrawerWithErrorHandling {
private const float ToggleIndent = 5;
private readonly GUIContent[] _gridOptions = new[] { new GUIContent("Nothing"), new GUIContent("Everything") };
private EnumDrawer _enumDrawer;
private readonly LazyGUIStyle _buttonStyle = LazyGUIStyle.Create(_ => new GUIStyle(EditorStyles.miniButton) { fontSize = EditorStyles.miniButton.fontSize - 1 });
private new ExpandableEnumAttribute attribute => (ExpandableEnumAttribute)base.attribute;
protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) {
bool wasExpanded = attribute.AlwaysExpanded || property.isExpanded;
var rowRect = new Rect(position) {
height = EditorGUIUtility.singleLineHeight,
};
using (new FusionEditorGUI.PropertyScope(position, label, property)) {
var valueRect = EditorGUI.PrefixLabel(rowRect, label);
bool isEnum = property.propertyType == SerializedPropertyType.Enum;
var maskProperty = isEnum ? property : property.FindPropertyRelative("Mask").FindPropertyRelative("values");
Mask256 rawValue;
if (isEnum) {
rawValue = new Mask256(maskProperty.longValue);
} else {
rawValue = new Mask256(
maskProperty.GetFixedBufferElementAtIndex(0).longValue,
maskProperty.GetFixedBufferElementAtIndex(1).longValue,
maskProperty.GetFixedBufferElementAtIndex(2).longValue,
maskProperty.GetFixedBufferElementAtIndex(3).longValue
);
}
var foldoutRect = new Rect(valueRect) { width = FusionEditorGUI.FoldoutWidth };
valueRect.xMin += foldoutRect.width;
EditorGUI.BeginChangeCheck();
if (wasExpanded) {
if (_enumDrawer.IsFlags && attribute.ShowFlagsButtons) {
int gridValue = -1;
if (rawValue.IsNothing()) {
// nothing
gridValue = 0;
} else if (Equals(_enumDrawer.BitMask & rawValue, _enumDrawer.BitMask)) {
var test = _enumDrawer.BitMask & rawValue;
if (Equals(test, _enumDrawer.BitMask))
// everything
gridValue = 1;
}
// traverse values in reverse; make sure the first alias is used in case there are multiple
if (isEnum) {
for (int i = _enumDrawer.Values.Length; i-- > 0;) {
if (_enumDrawer.Values[i] == 0) {
_gridOptions[0].text = _enumDrawer.Names[i];
} else if ( _enumDrawer.Values[i] == _enumDrawer.BitMask[0]) {
// Unity's drawer does not replace "Everything"
_gridOptions[1].text = _enumDrawer.Names[i];
}
}
}
var gridSelection = GUI.SelectionGrid(valueRect, gridValue, _gridOptions, _gridOptions.Length, _buttonStyle);
if (gridSelection != gridValue) {
if (gridSelection == 0) {
rawValue = default;
} else if (gridSelection == 1) {
rawValue = _enumDrawer.BitMask;
}
}
} else {
// draw a dummy field to consume the prefix
EditorGUI.LabelField(valueRect, GUIContent.none);
}
} else {
if (isEnum) {
var enumValue = (Enum)Enum.ToObject(_enumDrawer.EnumType, rawValue[0]);
if (_enumDrawer.IsFlags) {
enumValue = EditorGUI.EnumFlagsField(valueRect, enumValue);
} else {
enumValue = EditorGUI.EnumPopup(valueRect, enumValue);
}
rawValue[0] = Convert.ToInt64(enumValue);
} else {
// Droplist for FieldsMask<T>
_enumDrawer.Draw(valueRect, maskProperty, fieldInfo.FieldType, false);
}
}
if (EditorGUI.EndChangeCheck()) {
if (isEnum) {
maskProperty.longValue = rawValue[0];
} else {
maskProperty.GetFixedBufferElementAtIndex(0).longValue = rawValue[0];
maskProperty.GetFixedBufferElementAtIndex(1).longValue = rawValue[1];
maskProperty.GetFixedBufferElementAtIndex(2).longValue = rawValue[2];
maskProperty.GetFixedBufferElementAtIndex(3).longValue = rawValue[3];
}
property.serializedObject.ApplyModifiedProperties();
}
if (!attribute.AlwaysExpanded) {
using (new FusionEditorGUI.EnabledScope(true)) {
property.isExpanded = EditorGUI.Toggle(foldoutRect, wasExpanded, EditorStyles.foldout);
}
}
if (wasExpanded) {
if (Event.current.type == EventType.Repaint) {
EditorStyles.helpBox.Draw(new Rect(position) { yMin = rowRect.yMax }, GUIContent.none, false, false, false, false);
}
EditorGUI.BeginChangeCheck();
rowRect.xMin += ToggleIndent;
for (int i = 0; i < _enumDrawer.Values.Length; ++i) {
if (_enumDrawer.IsFlags && _enumDrawer.Values[i].IsNothing()) {
continue;
}
rowRect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
var toggleRect = rowRect;
var buttonRect = new Rect();
if (attribute.ShowInlineHelp) {
// move the button to keep it in the box
buttonRect = FusionEditorGUI.GetInlineHelpButtonRect(rowRect);
toggleRect.xMin += buttonRect.width + 0;
buttonRect.x += buttonRect.width - 3;
}
bool wasSelected = _enumDrawer.IsFlags
? Equals(rawValue & _enumDrawer.Values[i], _enumDrawer.Values[i])
: Equals(rawValue, _enumDrawer.Values[i]);
if (EditorGUI.ToggleLeft(toggleRect, _enumDrawer.Names[i], wasSelected) != wasSelected) {
if (_enumDrawer.IsFlags) {
if (wasSelected) {
rawValue &= ~_enumDrawer.Values[i];
} else {
rawValue |= _enumDrawer.Values[i];
}
} else if (!wasSelected) {
rawValue = _enumDrawer.Values[i];
}
}
if (attribute.ShowInlineHelp) {
var helpContent = FusionCodeDoc.FindEntry(_enumDrawer.Fields[i], false);
if (helpContent != null) {
var helpPath = GetHelpPath(property, _enumDrawer.Fields[i]);
var wasHelpExpanded = FusionEditorGUI.IsHelpExpanded(this, helpPath);
if (wasHelpExpanded) {
var helpSize = FusionEditorGUI.GetInlineBoxSize(helpContent);
var helpRect = rowRect;
helpRect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
helpRect.height = helpSize.y;
rowRect.y += helpSize.y;
FusionEditorGUI.DrawInlineBoxUnderProperty(helpContent, helpRect, FusionEditorSkin.HelpInlineBoxColor, true);
}
buttonRect.x += buttonRect.width;
if (FusionEditorGUI.DrawInlineHelpButton(buttonRect, wasHelpExpanded, doButton: true, doIcon: true)) {
FusionEditorGUI.SetHelpExpanded(this, helpPath, !wasHelpExpanded);
}
}
}
}
if (EditorGUI.EndChangeCheck()) {
if (isEnum) {
maskProperty.longValue = rawValue[0];
} else {
maskProperty.GetFixedBufferElementAtIndex(0).longValue = rawValue[0];
maskProperty.GetFixedBufferElementAtIndex(1).longValue = rawValue[1];
maskProperty.GetFixedBufferElementAtIndex(2).longValue = rawValue[2];
maskProperty.GetFixedBufferElementAtIndex(3).longValue = rawValue[3];
}
property.serializedObject.ApplyModifiedProperties();
}
}
}
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
var enumType = property.propertyType == SerializedPropertyType.Enum ? fieldInfo.FieldType.GetUnityLeafType() : fieldInfo.FieldType;
_enumDrawer.EnsureInitialized(enumType, attribute.ShowInlineHelp);
int rowCount = 0;
float height;
var forceExpand = attribute.AlwaysExpanded;
var showHelp = attribute.ShowInlineHelp;
if (forceExpand || property.isExpanded) {
if (_enumDrawer.IsFlags) {
foreach (var value in _enumDrawer.Values) {
if (value.IsNothing()) {
continue;
}
++rowCount;
}
} else {
rowCount = _enumDrawer.Values.Length;
}
height = (rowCount + 1) * (EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing);
if (showHelp) {
foreach (var field in _enumDrawer.Fields) {
if (FusionEditorGUI.IsHelpExpanded(this, GetHelpPath(property, field))) {
var helpContent = FusionCodeDoc.FindEntry(field, false);
if (helpContent != null) {
height += FusionEditorGUI.GetInlineBoxSize(helpContent).y;
}
}
}
}
} else {
height = EditorGUIUtility.singleLineHeight;
}
return height;
}
private static string GetHelpPath(SerializedProperty property, FieldInfo field) {
return property.propertyPath + "/" + field.Name;
}
}
}
#endregion
#region FieldEditorButtonAttributeDrawer.cs
namespace Fusion.Editor {
using System;
using System.Reflection;
using UnityEditor;
using UnityEngine;
using Object = UnityEngine.Object;
internal partial class FieldEditorButtonAttributeDrawer : DecoratingPropertyAttributeDrawer {
protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) {
var propertyPosition = position;
propertyPosition.height -= EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
base.OnGUIInternal(propertyPosition, property, label);
var buttonPosition = position;
buttonPosition.yMin = position.yMax - EditorGUIUtility.singleLineHeight;
var attribute = (FieldEditorButtonAttribute)this.attribute;
var targetObjects = property.serializedObject.targetObjects;
var targetObjectType = property.serializedObject.targetObject.GetType();
if (DrawButton(buttonPosition, attribute, targetObjectType, targetObjects)) {
property.serializedObject.Update();
property.serializedObject.ApplyModifiedProperties();
}
}
private static bool DrawButton(Rect buttonPosition, FieldEditorButtonAttribute attribute, Type targetObjectType, Object[] targetObjects) {
using (new EditorGUI.DisabledGroupScope(!attribute.AllowMultipleTargets && targetObjects.Length > 1)) {
if (GUI.Button(buttonPosition, attribute.Label, EditorStyles.miniButton)) {
var targetMethod = targetObjectType.GetMethod(attribute.TargetMethod, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
if (targetMethod == null) {
FusionEditorLog.ErrorInspector($"Unable to find method {attribute.TargetMethod} on type {targetObjectType}");
} else {
if (targetMethod.IsStatic) {
targetMethod.Invoke(null, null);
} else {
foreach (var targetObject in targetObjects) {
targetMethod.Invoke(targetObject, null);
}
}
return true;
}
}
return false;
}
}
protected override float GetPropertyHeightInternal(SerializedProperty property, GUIContent label) {
return base.GetPropertyHeightInternal(property, label) + EditorGUIUtility.standardVerticalSpacing + EditorGUIUtility.singleLineHeight;
}
}
[CustomPropertyDrawer(typeof(FieldEditorButtonAttribute))]
[RedirectCustomPropertyDrawer(typeof(FieldEditorButtonAttribute), typeof(FieldEditorButtonAttributeDrawer))]
partial class PropertyDrawerForArrayWorkaround {
}
}
#endregion
#region HideArrayElementLabelAttributeDrawer.cs
namespace Fusion.Editor {
using UnityEditor;
using UnityEngine;
[CustomPropertyDrawer(typeof(HideArrayElementLabelAttribute))]
partial class HideArrayElementLabelAttributeDrawer : DecoratingPropertyAttributeDrawer {
protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) {
if (property.IsArrayElement()) {
label = GUIContent.none;
}
base.OnGUIInternal(position, property, label);
}
}
}
#endregion
#region InlineHelpAttributeDrawer.cs
namespace Fusion.Editor {
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;
//[CustomPropertyDrawer(typeof(InlineHelpAttribute))]
internal partial class InlineHelpAttributeDrawer : DecoratingPropertyAttributeDrawer, INonApplicableOnArrayElements {
private bool _initialized;
private GUIContent _helpContent;
private GUIContent _labelContent;
protected new InlineHelpAttribute attribute => (InlineHelpAttribute)base.attribute;
protected override float GetPropertyHeightInternal(SerializedProperty property, GUIContent label) {
var height = base.GetPropertyHeightInternal(property, label);
if (height <= 0) {
return height;
}
if (FusionEditorGUI.IsHelpExpanded(this, property.propertyPath)) {
var helpContent = GetHelpContent(property);
if (helpContent != null) {
height += FusionEditorGUI.GetInlineBoxSize(helpContent).y;
}
}
return height;
}
protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) {
var helpContent = GetHelpContent(property);
if (position.height <= 0 || helpContent == null) {
// ignore
base.OnGUIInternal(position, property, label);
return;
}
var nextDrawer = GetNextDrawer(property);
var hasFoldout = HasFoldout(nextDrawer, property);
using (new FusionEditorGUI.GUIContentScope(label)) {
var (wasExpanded, buttonRect) = DrawInlineHelpBeforeProperty(label, helpContent, position, property.propertyPath, EditorGUI.indentLevel, hasFoldout, this);
var propertyRect = position;
if (wasExpanded) {
propertyRect.height -= FusionEditorGUI.GetInlineBoxSize(helpContent).y;
}
base.OnGUIInternal(propertyRect, property, label);
DrawInlineHelpAfterProperty(buttonRect, wasExpanded, helpContent, position);
}
}
private GUIContent GetHelpContent(SerializedProperty property) {
if (_initialized) {
return _helpContent;
}
_initialized = true;
if (property.IsArrayElement()) {
return null;
}
if (fieldInfo == null) {
return null;
}
_helpContent = FusionCodeDoc.FindEntry(fieldInfo, attribute.ShowTypeHelp);
return _helpContent;
}
private bool HasFoldout(PropertyDrawer nextDrawer, SerializedProperty property) {
var drawerMeta = nextDrawer?.GetType().GetCustomAttribute<FusionPropertyDrawerMetaAttribute>();
if (drawerMeta != null) {
return drawerMeta.HasFoldout;
}
if (property.IsArrayProperty()) {
return true;
}
if (property.propertyType == SerializedPropertyType.Generic) {
return true;
}
return false;
}
public static (bool expanded, Rect buttonRect) DrawInlineHelpBeforeProperty(GUIContent label, GUIContent helpContent, Rect propertyRect, string propertyPath, int depth, bool hasFoldout, object context, bool drawHelp = false) {
if (label != null) {
if (!string.IsNullOrEmpty(label.tooltip)) {
label.tooltip += "\n\n";
}
label.tooltip += helpContent.tooltip;
}
if (propertyRect.width > 1 && propertyRect.height > 1) {
var buttonRect = FusionEditorGUI.GetInlineHelpButtonRect(propertyRect, hasFoldout);
if (depth == 0 && hasFoldout) {
buttonRect.x = 16;
if (label != null) {
label.text = " " + label.text;
}
}
var wasExpanded = FusionEditorGUI.IsHelpExpanded(context, propertyPath);
if (FusionEditorGUI.DrawInlineHelpButton(buttonRect, wasExpanded, doButton: true, doIcon: false)) {
FusionEditorGUI.SetHelpExpanded(context, propertyPath, !wasExpanded);
}
return (wasExpanded, buttonRect);
}
return default;
}
public static void DrawInlineHelpAfterProperty(Rect buttonRect, bool wasExpanded, GUIContent helpContent, Rect propertyRect) {
if (buttonRect.width <= 0 && buttonRect.height <= 0) {
return;
}
using (new FusionEditorGUI.EnabledScope(true)) {
FusionEditorGUI.DrawInlineHelpButton(buttonRect, wasExpanded, doButton: false, doIcon: true);
}
if (!wasExpanded) {
return;
}
FusionEditorGUI.DrawInlineBoxUnderProperty(helpContent, propertyRect, FusionEditorSkin.HelpInlineBoxColor, true);
}
}
[CustomPropertyDrawer(typeof(InlineHelpAttribute))]
[RedirectCustomPropertyDrawer(typeof(InlineHelpAttribute), typeof(InlineHelpAttributeDrawer))]
partial class PropertyDrawerForArrayWorkaround {
}
}
#endregion
#region INonApplicableOnArrayElements.cs
namespace Fusion.Editor {
public interface INonApplicableOnArrayElements {
}
}
#endregion
#region LayerAttributeDrawer.cs
namespace Fusion.Editor {
using UnityEditor;
using UnityEngine;
[CustomPropertyDrawer(typeof(LayerAttribute))]
internal class LayerAttributeDrawer : PropertyDrawer {
public override void OnGUI(Rect p, SerializedProperty prop, GUIContent label) {
EditorGUI.BeginChangeCheck();
int value;
using (new FusionEditorGUI.PropertyScope(p, label, prop))
using (new FusionEditorGUI.ShowMixedValueScope(prop.hasMultipleDifferentValues)) {
value = EditorGUI.LayerField(p, label, prop.intValue);
}
if (EditorGUI.EndChangeCheck()) {
prop.intValue = value;
prop.serializedObject.ApplyModifiedProperties();
}
}
}
}
#endregion
#region LayerMatrixAttributeDrawer.cs
namespace Fusion.Editor {
using UnityEditor;
using UnityEngine;
internal partial class LayerMatrixAttributeDrawer : PropertyDrawerWithErrorHandling, INonApplicableOnArrayElements {
protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) {
using (new FusionEditorGUI.PropertyScopeWithPrefixLabel(position, label, property, out var valueRect)) {
if (GUI.Button(valueRect, "Edit", EditorStyles.miniButton)) {
PopupWindow.Show(valueRect, new LayerMatrixPopup(label?.text ?? property.displayName,
(layerA, layerB) => {
if (layerA >= property.arraySize) {
return false;
}
return (property.GetArrayElementAtIndex(layerA).intValue & (1 << layerB)) != 0;
},
(layerA, layerB, val) => {
if (Mathf.Max(layerA, layerB) >= property.arraySize) {
property.arraySize = Mathf.Max(layerA, layerB) + 1;
}
if (val) {
property.GetArrayElementAtIndex(layerA).intValue |= (1 << layerB);
property.GetArrayElementAtIndex(layerB).intValue |= (1 << layerA);
} else {
property.GetArrayElementAtIndex(layerA).intValue &= ~(1 << layerB);
property.GetArrayElementAtIndex(layerB).intValue &= ~(1 << layerA);
}
property.serializedObject.ApplyModifiedProperties();
}));
}
}
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
return EditorGUIUtility.singleLineHeight;
}
class LayerMatrixPopup : PopupWindowContent {
private const int checkboxSize = 16;
private const int margin = 30;
private const int MaxLayers = 32;
private readonly GUIContent _label;
private readonly int _numLayers;
private readonly float _labelWidth;
private readonly UnityInternal.LayerMatrixGUI.GetValueFunc _getter;
private readonly UnityInternal.LayerMatrixGUI.SetValueFunc _setter;
public LayerMatrixPopup(string label, UnityInternal.LayerMatrixGUI.GetValueFunc getter, UnityInternal.LayerMatrixGUI.SetValueFunc setter) {
_label = new GUIContent(label);
_getter = getter;
_setter = setter;
_labelWidth = 110;
_numLayers = 0;
for (int i = 0; i < MaxLayers; i++) {
string layerName = LayerMask.LayerToName(i);
if (string.IsNullOrEmpty(layerName)) {
continue;
}
_numLayers++;
_labelWidth = Mathf.Max(_labelWidth, GUI.skin.label.CalcSize(new GUIContent(layerName)).x);
}
}
public override void OnGUI(Rect rect) {
GUILayout.BeginArea(rect);
UnityInternal.LayerMatrixGUI.Draw(_label, _getter, _setter);
GUILayout.EndArea();
}
public override Vector2 GetWindowSize() {
int matrixWidth = checkboxSize * _numLayers;
float width = matrixWidth + _labelWidth + margin * 2;
float heigth = matrixWidth + _labelWidth + 15 + FusionEditorGUI.GetLinesHeight(3);
return new Vector2(Mathf.Max(width, 350), heigth);
}
}
}
[CustomPropertyDrawer(typeof(LayerMatrixAttribute))]
[RedirectCustomPropertyDrawer(typeof(LayerMatrixAttribute), typeof(LayerMatrixAttributeDrawer))]
partial class PropertyDrawerForArrayWorkaround {
}
}
#endregion
#region MaxStringByteCountAttributeDrawer.cs
namespace Fusion.Editor {
using UnityEditor;
using UnityEngine;
[CustomPropertyDrawer(typeof(MaxStringByteCountAttribute))]
internal class MaxStringByteCountAttributeDrawer : PropertyDrawerWithErrorHandling {
protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) {
var attribute = (MaxStringByteCountAttribute)this.attribute;
var encoding = System.Text.Encoding.GetEncoding(attribute.Encoding);
var byteCount = encoding.GetByteCount(property.stringValue);
using (new FusionEditorGUI.PropertyScope(position, label, property)) {
FusionEditorGUI.ForwardPropertyField(position, property, label, true);
}
FusionEditorGUI.Overlay(position, $"({byteCount} B)");
if (byteCount > attribute.ByteCount) {
FusionEditorGUI.Decorate(position, $"{attribute.Encoding} string max size ({attribute.ByteCount} B) exceeded: {byteCount} B", MessageType.Error, hasLabel: true);
}
}
}
}
#endregion
#region MessageIfDrawerBase.cs
namespace Fusion.Editor {
using UnityEditor;
using UnityEngine;
internal abstract class MessageIfDrawerBase : DoIfAttributeDrawer {
protected abstract bool IsBox { get; }
protected abstract string Message { get; }
protected abstract MessageType MessageType { get; }
protected abstract Color InlineBoxColor { get; }
protected abstract Texture MessageIcon { get; }
public DoIfAttributeBase Attribute => (DoIfAttributeBase)attribute;
private GUIContent _messageContent;
private GUIContent MessageContent {
get {
if (_messageContent == null) {
_messageContent = new GUIContent(Message, MessageIcon, Message);
}
return _messageContent;
}
}
protected override float GetPropertyHeightInternal(SerializedProperty property, GUIContent label) {
var height = base.GetPropertyHeightInternal(property, label);
if (IsBox) {
if (CheckDraw(Attribute, property)) {
float extra = CalcBoxHeight();
height += extra;
}
}
return height;
}
protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) {
if (!CheckDraw(Attribute, property)) {
base.OnGUIInternal(position, property, label);
} else {
if (!IsBox) {
var decorateRect = position;
decorateRect.height = EditorGUIUtility.singleLineHeight;
decorateRect.xMin += EditorGUIUtility.labelWidth;
// TODO: should the border be resized for arrays?
// if (property.IsArrayProperty()) {
// decorateRect.xMin = decorateRect.xMax - 48f;
// }
FusionEditorGUI.AppendTooltip(MessageContent.text, ref label);
base.OnGUIInternal(position, property, label);
FusionEditorGUI.Decorate(decorateRect, MessageContent.text, MessageType);
} else {
position = FusionEditorGUI.DrawInlineBoxUnderProperty(MessageContent, position, InlineBoxColor);
base.OnGUIInternal(position, property, label);
//position.y += position.height;
//position.height = extra;
//EditorGUI.HelpBox(position, MessageContent.text, MessageType);
}
}
}
private float CalcBoxHeight() {
// const float SCROLL_WIDTH = 16f;
// const float LEFT_HELP_INDENT = 8f;
//
// var width = UnityInternal.EditorGUIUtility.contextWidth - /*InlineHelpStyle.MarginOuter -*/ SCROLL_WIDTH - LEFT_HELP_INDENT;
// return EditorStyles.helpBox.CalcHeight(MessageContent, width);
return FusionEditorGUI.GetInlineBoxSize(MessageContent).y;
}
}
}
#endregion
#region PropertyDrawerForArrayWorkaround.cs
//#define FUSION_EDITOR_TRACE
namespace Fusion.Editor {
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
internal partial class PropertyDrawerForArrayWorkaround : DecoratorDrawer {
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
internal class RedirectCustomPropertyDrawerAttribute : Attribute {
public RedirectCustomPropertyDrawerAttribute(Type attributeType, Type drawerType) {
AttributeType = attributeType;
DrawerType = drawerType;
}
public Type AttributeType { get; }
public Type DrawerType { get; }
}
private static Dictionary<Type, Type> _attributeToDrawer = typeof(PropertyDrawerForArrayWorkaround)
.GetCustomAttributes<RedirectCustomPropertyDrawerAttribute>()
.ToDictionary(x => x.AttributeType, x => x.DrawerType);
private UnityInternal.PropertyHandler _handler;
private PropertyDrawer _drawer;
private bool _initialized;
public PropertyDrawerForArrayWorkaround() {
_handler = UnityInternal.ScriptAttributeUtility.nextHandler;
}
public override float GetHeight() {
if (!_initialized) {
_initialized = true;
if (!_attributeToDrawer.TryGetValue(attribute.GetType(), out var drawerType)) {
FusionEditorLog.ErrorInspector($"No drawer for {attribute.GetType()}");
} else if (_handler.decoratorDrawers?.Contains(this) != true) {
FusionEditorLog.Warn($"Unable to forward to {drawerType}.");
} else {
var drawer = (PropertyDrawer)Activator.CreateInstance(drawerType);
UnityInternal.PropertyDrawer.SetAttribute(drawer, attribute);
// if (_handler.decoratorDrawers.Contains(this)) {
// }
if (_handler.m_PropertyDrawers == null) {
_handler.m_PropertyDrawers = new List<PropertyDrawer>();
}
var insertPosition = _handler.m_PropertyDrawers.TakeWhile(x => x.attribute != null && x.attribute.order < attribute.order)
.Count();
FusionEditorLog.Trace($"Inserting {drawerType} at {insertPosition}");
_handler.m_PropertyDrawers.Insert(insertPosition, drawer);
}
}
return 0;
}
public static Type GetDrawerType(Type attributeDrawerType) {
return _attributeToDrawer[attributeDrawerType];
}
}
// [CustomPropertyDrawer(typeof(Attrib))]
// public class DummyDrawer : ForwardingPropertyDrawer {
// public class Attrib : PropertyAttribute {
// }
//
// public DummyDrawer() {
// //ReadOnlyAttribute
// }
//
// protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) {
// base.OnGUIInternal(position, property, label);
// }
//
// protected override float GetPropertyHeightInternal(SerializedProperty property, GUIContent label) {
// return base.GetPropertyHeightInternal(property, label);
// }
// }
//
// [CustomPropertyDrawer(typeof(Attrib))]
// public class FooPropertyDrawer : PropertyDrawer {
// public class Attrib : PropertyAttribute {
// public PropertyAttribute OtherAttribute;
// public Type OtherDrawerType;
// }
//
// private PropertyDrawer _otherDrawer;
//
// private void EnsureOtherDrawer(SerializedProperty property) {
// if (_otherDrawer == null) {
// var attrib = (Attrib)attribute;
// _otherDrawer = (PropertyDrawer)Activator.CreateInstance(attrib.OtherDrawerType);
// UnityInternal.PropertyDrawer.SetAttribute(_otherDrawer, attrib.OtherAttribute);
// UnityInternal.PropertyDrawer.SetFieldInfo(_otherDrawer, fieldInfo);
// }
// }
//
// public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
// EnsureOtherDrawer(property);
// return _otherDrawer.GetPropertyHeight(property, label);
// }
//
// public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
// EnsureOtherDrawer(property);
// _otherDrawer.OnGUI(position, property, label);
// }
// }
}
#endregion
#region PropertyDrawerWithErrorHandling.cs
namespace Fusion.Editor {
using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
internal abstract class PropertyDrawerWithErrorHandling : PropertyDrawer {
private SerializedProperty _currentProperty;
private readonly Dictionary<string, Entry> _errors = new();
private bool _hadError;
private string _info;
public float IconOffset;
public sealed override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
FusionEditorLog.Assert(_currentProperty == null);
var decoration = GetDecoration(property);
if (decoration != null) {
DrawDecoration(position, decoration.Value, label != GUIContent.none, true, false);
}
_currentProperty = property;
_hadError = false;
_info = null;
EditorGUI.BeginChangeCheck();
try {
OnGUIInternal(position, property, label);
} catch (ExitGUIException) {
// pass through
} catch (Exception ex) {
SetError(ex.ToString());
} finally {
// if there was a change but no error clear
if (EditorGUI.EndChangeCheck() && !_hadError) {
ClearError();
}
_currentProperty = null;
}
if (decoration != null) {
DrawDecoration(position, decoration.Value, label != GUIContent.none, false, true);
}
}
private void DrawDecoration(Rect position, (string, MessageType, bool) decoration, bool hasLabel, bool drawButton = true, bool drawIcon = true) {
var iconPosition = position;
iconPosition.height = EditorGUIUtility.singleLineHeight;
iconPosition.x -= IconOffset;
FusionEditorGUI.Decorate(iconPosition, decoration.Item1, decoration.Item2, hasLabel, drawButton: drawButton, drawBorder: decoration.Item3);
}
private (string, MessageType, bool)? GetDecoration(SerializedProperty property) {
if (_errors.TryGetValue(property.propertyPath, out var error)) {
return (error.message, error.type, true);
}
if (_info != null) {
return (_info, MessageType.Info, false);
}
return null;
}
protected abstract void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label);
protected void ClearError() {
ClearError(_currentProperty);
}
protected void ClearError(SerializedProperty property) {
_hadError = false;
_errors.Remove(property.propertyPath);
}
protected void ClearErrorIfLostFocus() {
if (GUIUtility.keyboardControl != UnityInternal.EditorGUIUtility.LastControlID) {
ClearError();
}
}
protected void SetError(string error) {
_hadError = true;
_errors[_currentProperty.propertyPath] = new Entry {
message = error,
type = MessageType.Error
};
}
protected void SetError(Exception error) {
SetError(error.ToString());
}
protected void SetWarning(string warning) {
if (_errors.TryGetValue(_currentProperty.propertyPath, out var entry) && entry.type == MessageType.Error) {
return;
}
_errors[_currentProperty.propertyPath] = new Entry {
message = warning,
type = MessageType.Warning
};
}
protected void SetInfo(string message) {
if (_errors.TryGetValue(_currentProperty.propertyPath, out var entry) && entry.type == MessageType.Error || entry.type == MessageType.Warning ) {
return;
}
_errors[_currentProperty.propertyPath] = new Entry {
message = message,
type = MessageType.Info
};
}
private struct Entry {
public string message;
public MessageType type;
}
}
}
#endregion
#region RangeAttributeDrawer.cs
namespace Fusion.Editor {
using UnityEditor;
using UnityEngine;
[CustomPropertyDrawer(typeof(RangeExAttribute))]
internal class RangeAttributeDrawer : PropertyDrawer {
const float FieldWidth = 130.0f;
const float Spacing = 5.0f;
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
var attrib = (RangeExAttribute)this.attribute;
var min = attrib.Min;
var max = attrib.Max;
int intValue = 0;
float floatValue = 0.0f;
if (property.propertyType == SerializedPropertyType.Float) {
floatValue = property.floatValue;
} else if (property.propertyType == SerializedPropertyType.Integer) {
intValue = property.intValue;
} else {
EditorGUI.LabelField(position, label.text, "Use RangeEx with float or int.");
return;
}
EditorGUI.BeginChangeCheck();
using (new FusionEditorGUI.PropertyScope(position, label, property)) {
if (attrib.UseSlider) {
if (!attrib.ClampMin || !attrib.ClampMax) {
var sliderRect = new Rect(position) {
xMin = position.xMin + EditorGUIUtility.labelWidth,
xMax = position.xMax - FieldWidth - Spacing
};
using (new FusionEditorGUI.LabelWidthScope(position.width - FieldWidth)) {
if (property.propertyType == SerializedPropertyType.Float) {
if (sliderRect.width > FieldWidth + Spacing) {
using (new EditorGUI.IndentLevelScope(-EditorGUI.indentLevel)) {
floatValue = GUI.HorizontalSlider(sliderRect, property.floatValue, (float)min, (float)max);
}
}
floatValue = EditorGUI.FloatField(position, label, floatValue);
} else {
if (sliderRect.width > FieldWidth) {
using (new EditorGUI.IndentLevelScope(-EditorGUI.indentLevel)) {
intValue = Mathf.RoundToInt(GUI.HorizontalSlider(sliderRect, property.intValue, (float)min, (float)max));
}
}
intValue = EditorGUI.IntField(position, label, intValue);
}
}
} else {
var fieldWidth = EditorGUIUtility.fieldWidth;
try {
EditorGUIUtility.fieldWidth = FieldWidth;
if (property.propertyType == SerializedPropertyType.Float) {
floatValue = EditorGUI.Slider(position, label, property.floatValue, (float)min, (float)max);
} else {
intValue = EditorGUI.IntSlider(position, label, property.intValue, (int)min, (int)max);
}
} finally {
EditorGUIUtility.fieldWidth = fieldWidth;
}
}
} else {
if (property.propertyType == SerializedPropertyType.Float) {
floatValue = EditorGUI.FloatField(position, label, property.floatValue);
} else {
intValue = EditorGUI.IntField(position, label, property.intValue);
}
}
}
if (EditorGUI.EndChangeCheck()) {
if (property.propertyType == SerializedPropertyType.Float) {
property.floatValue = Clamp(floatValue, attrib);
} else if (property.propertyType == SerializedPropertyType.Integer) {
property.intValue = Clamp(intValue, attrib);
}
property.serializedObject.ApplyModifiedProperties();
}
}
private float Clamp(float value, RangeExAttribute attrib) {
return Mathf.Clamp(value,
attrib.ClampMin ? (float)attrib.Min : float.MinValue,
attrib.ClampMax ? (float)attrib.Max : float.MaxValue);
}
private int Clamp(int value, RangeExAttribute attrib) {
return Mathf.Clamp(value,
attrib.ClampMin ? (int)attrib.Min : int.MinValue,
attrib.ClampMax ? (int)attrib.Max : int.MaxValue);
}
}
}
#endregion
#region ReadOnlyAttributeDrawer.cs
namespace Fusion.Editor {
using UnityEditor;
using UnityEngine;
internal partial class ReadOnlyAttributeDrawer : DecoratingPropertyAttributeDrawer, INonApplicableOnArrayElements {
protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) {
var attribute = (ReadOnlyAttribute)this.attribute;
bool isPlayMode = EditorApplication.isPlayingOrWillChangePlaymode;
using (new EditorGUI.DisabledGroupScope(isPlayMode ? attribute.InPlayMode : attribute.InEditMode)) {
base.OnGUIInternal(position, property, label);
}
}
}
[CustomPropertyDrawer(typeof(ReadOnlyAttribute))]
[RedirectCustomPropertyDrawer(typeof(ReadOnlyAttribute), typeof(ReadOnlyAttributeDrawer))]
partial class PropertyDrawerForArrayWorkaround {
}
}
#endregion
#region ScenePathAttributeDrawer.cs
namespace Fusion.Editor {
using System.Linq;
using UnityEditor;
using UnityEngine;
[CustomPropertyDrawer(typeof(ScenePathAttribute))]
internal class ScenePathAttributeDrawer : PropertyDrawerWithErrorHandling {
private SceneAsset[] _allScenes;
protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) {
var oldScene = AssetDatabase.LoadAssetAtPath<SceneAsset>(property.stringValue);
if (oldScene == null && !string.IsNullOrEmpty(property.stringValue)) {
// well, maybe by name then?
_allScenes = _allScenes ?? AssetDatabase.FindAssets("t:scene")
.Select(x => AssetDatabase.GUIDToAssetPath(x))
.Select(x => AssetDatabase.LoadAssetAtPath<SceneAsset>(x))
.ToArray();
var matchedByName = _allScenes.Where(x => x.name == property.stringValue).ToList();
;
if (matchedByName.Count == 0) {
SetError($"Scene not found: {property.stringValue}");
} else {
oldScene = matchedByName[0];
if (matchedByName.Count > 1) {
SetWarning("There are multiple scenes with this name");
}
}
}
using (new FusionEditorGUI.PropertyScope(position, label, property)) {
EditorGUI.BeginChangeCheck();
var newScene = EditorGUI.ObjectField(position, label, oldScene, typeof(SceneAsset), false) as SceneAsset;
if (EditorGUI.EndChangeCheck()) {
var assetPath = AssetDatabase.GetAssetPath(newScene);
property.stringValue = assetPath;
property.serializedObject.ApplyModifiedProperties();
ClearError();
}
}
}
}
}
#endregion
#region ScriptFieldDrawer.cs
namespace Fusion.Editor {
using System;
using UnityEditor;
using UnityEngine;
internal class ScriptFieldDrawer : PropertyDrawer {
private new ScriptHelpAttribute attribute => (ScriptHelpAttribute)base.attribute;
public bool ForceHide = false;
private bool _initialized;
private GUIContent _helpContent;
private GUIContent _headerContent;
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
if (ForceHide || attribute?.Hide == true) {
return;
}
if (attribute == null) {
EditorGUI.PropertyField(position, property, label);
return;
}
EnsureInitialized(property);
var helpButtonRect = FusionEditorGUI.GetInlineHelpButtonRect(position, false);
bool wasHelpExpanded = _helpContent != null && FusionEditorGUI.IsHelpExpanded(this, property.propertyPath);
if (wasHelpExpanded) {
position = FusionEditorGUI.DrawInlineBoxUnderProperty(_helpContent, position, FusionEditorSkin.HelpInlineBoxColor);
}
if (_helpContent != null) {
using (new FusionEditorGUI.EnabledScope(true)) {
if (FusionEditorGUI.DrawInlineHelpButton(helpButtonRect, wasHelpExpanded, true, false)) {
FusionEditorGUI.SetHelpExpanded(this, property.propertyPath, !wasHelpExpanded);
}
}
}
if (attribute.Style == ScriptHeaderStyle.Unity) {
EditorGUI.PropertyField(position, property, label);
} else {
using (new FusionEditorGUI.EnabledScope(true)) {
if (attribute.BackColor != ScriptHeaderBackColor.None) {
FusionEditorGUI.DrawScriptHeaderBackground(position, FusionEditorSkin.GetScriptHeaderColor(attribute.BackColor));
}
var labelPosition = FusionEditorSkin.ScriptHeaderLabelStyle.margin.Remove(position);
EditorGUIUtility.AddCursorRect(labelPosition, MouseCursor.Link);
EditorGUI.LabelField(labelPosition, _headerContent, FusionEditorSkin.ScriptHeaderLabelStyle);
var e = Event.current;
if (e.type == EventType.MouseDown && position.Contains(e.mousePosition)) {
if (e.clickCount == 1) {
if (!string.IsNullOrEmpty(attribute.Url)) {
Application.OpenURL(attribute.Url);
}
EditorGUIUtility.PingObject(property.objectReferenceValue);
} else {
AssetDatabase.OpenAsset(property.objectReferenceValue);
}
}
FusionEditorGUI.DrawScriptHeaderIcon(position);
}
}
if (_helpContent != null) {
using (new FusionEditorGUI.EnabledScope(true)) {
// paint over what the inspector has drawn
FusionEditorGUI.DrawInlineHelpButton(helpButtonRect, wasHelpExpanded, false, true);
}
}
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
if (ForceHide || attribute?.Hide == true) {
return -EditorGUIUtility.standardVerticalSpacing;
}
if (attribute == null) {
return EditorGUIUtility.singleLineHeight;
}
var height = EditorGUIUtility.singleLineHeight;
if (FusionEditorGUI.IsHelpExpanded(this, property.propertyPath) && _helpContent != null) {
height += FusionEditorGUI.GetInlineBoxSize(_helpContent).y;
}
return height;
}
private void EnsureInitialized(SerializedProperty property) {
if (_initialized) {
return;
}
_initialized = true;
var type = property.serializedObject.targetObject.GetType();
_headerContent = new GUIContent(ObjectNames.NicifyVariableName(type.Name).ToUpper());
_helpContent = FusionCodeDoc.FindEntry(type);
}
}
}
#endregion
#region SerializableTypeDrawer.cs
namespace Fusion.Editor {
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;
[CustomPropertyDrawer(typeof(SerializableType<>))]
[CustomPropertyDrawer(typeof(SerializableType))]
[CustomPropertyDrawer(typeof(SerializableTypeAttribute))]
internal class SerializableTypeDrawer : PropertyDrawerWithErrorHandling {
protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) {
var attr = (SerializableTypeAttribute)attribute;
SerializedProperty valueProperty;
if (property.propertyType == SerializedPropertyType.String) {
FusionEditorLog.Assert(attr != null);
valueProperty = property;
} else {
FusionEditorLog.Assert(property.propertyType == SerializedPropertyType.Generic);
valueProperty = property.FindPropertyRelativeOrThrow(nameof(SerializableType.AssemblyQualifiedName));
}
var assemblyQualifiedName = valueProperty.stringValue;
var baseType = typeof(object);
var leafType = fieldInfo.FieldType.GetUnityLeafType();
if (leafType.IsGenericType && leafType.GetGenericTypeDefinition() == typeof(SerializableType<>)) {
baseType = leafType.GetGenericArguments()[0];
}
if (attr?.BaseType != null) {
baseType = attr.BaseType;
}
position = EditorGUI.PrefixLabel(position, label);
string content = "[None]";
if (!string.IsNullOrEmpty(assemblyQualifiedName)) {
try {
var type = Type.GetType(assemblyQualifiedName, true);
content = type.FullName;
} catch (Exception e) {
SetError(e);
content = assemblyQualifiedName;
}
}
if (EditorGUI.DropdownButton(position, new GUIContent(content), FocusType.Keyboard)) {
ClearError();
FusionEditorGUI.DisplayTypePickerMenu(position, baseType, t => {
string typeName = string.Empty;
if (t != null) {
typeName = attr?.UseFullAssemblyQualifiedName == false ? SerializableType.GetShortAssemblyQualifiedName(t) : t.AssemblyQualifiedName;
}
valueProperty.stringValue = typeName;
valueProperty.serializedObject.ApplyModifiedProperties();
});
}
}
}
}
#endregion
#region SerializeReferenceTypePickerAttributeDrawer.cs
namespace Fusion.Editor {
using System.Linq;
using UnityEditor;
using UnityEngine;
[CustomPropertyDrawer(typeof(SerializeReferenceTypePickerAttribute))]
partial class SerializeReferenceTypePickerAttributeDrawer : DecoratingPropertyAttributeDrawer {
const string NullContent = "Null";
protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) {
var attribute = (SerializeReferenceTypePickerAttribute)this.attribute;
Rect pickerRect;
if (label == GUIContent.none) {
pickerRect = position;
pickerRect.height = EditorGUIUtility.singleLineHeight;
} else {
pickerRect = EditorGUI.PrefixLabel(new Rect(position) { height = EditorGUIUtility.singleLineHeight }, FusionEditorGUI.WhitespaceContent);
}
object instance = property.managedReferenceValue;
var instanceType = instance?.GetType();
if (EditorGUI.DropdownButton(pickerRect, new GUIContent(instanceType?.FullName ?? NullContent), FocusType.Keyboard)) {
var types = attribute.Types;
if (!types.Any()) {
types = new[] { fieldInfo.FieldType.GetUnityLeafType() };
}
FusionEditorGUI.DisplayTypePickerMenu(pickerRect, types, t => {
if (t == null) {
instance = null;
} else if (t.IsInstanceOfType(instance)) {
// do nothing
return;
} else {
instance = System.Activator.CreateInstance(t);
}
property.managedReferenceValue = instance;
property.serializedObject.ApplyModifiedProperties();
}, noneOptionLabel: NullContent, groupByNamespace: attribute.GroupTypesByNamespace, selectedType: instanceType);
}
base.OnGUIInternal(position, property, label);
}
}
}
#endregion
#region ToggleLeftAttributeDrawer.cs
namespace Fusion.Editor {
using UnityEditor;
using UnityEngine;
[CustomPropertyDrawer(typeof(ToggleLeftAttribute))]
internal class ToggleLeftAttributeDrawer : PropertyDrawer {
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
EditorGUI.BeginProperty(position, label, property);
EditorGUI.BeginChangeCheck();
var val = EditorGUI.ToggleLeft(position, label, property.boolValue);
if (EditorGUI.EndChangeCheck()) {
property.boolValue = val;
}
EditorGUI.EndProperty();
}
}
}
#endregion
#region UnitAttributeDrawer.cs
namespace Fusion.Editor {
using System;
using System.Reflection;
using UnityEditor;
using UnityEngine;
[CustomPropertyDrawer(typeof(UnitAttribute))]
[FusionPropertyDrawerMeta(HandlesUnits = true)]
internal partial class UnitAttributeDrawer : DecoratingPropertyAttributeDrawer {
private GUIContent _label;
private void EnsureInitialized() {
if (_label == null) {
_label = new GUIContent(UnitToLabel(((UnitAttribute)attribute).Unit));
}
}
protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) {
base.OnGUIInternal(position, property, label);
// check if any of the next drawers handles the unit
for (var nextDrawer = GetNextDrawer(property); nextDrawer != null; nextDrawer = (nextDrawer as DecoratingPropertyAttributeDrawer)?.GetNextDrawer(property)) {
var meta = nextDrawer.GetType().GetCustomAttribute<FusionPropertyDrawerMetaAttribute>();
if (meta?.HandlesUnits == true) {
return;
}
}
EnsureInitialized();
var propertyType = property.propertyType;
var isExpanded = property.isExpanded;
DrawUnitOverlay(position, _label, propertyType, isExpanded);
}
public static void DrawUnitOverlay(Rect position, GUIContent label, SerializedPropertyType propertyType, bool isExpanded, bool odinStyle = false) {
switch (propertyType) {
case SerializedPropertyType.Vector2 when odinStyle:
case SerializedPropertyType.Vector3 when odinStyle:
case SerializedPropertyType.Vector4 when odinStyle: {
var pos = position;
int memberCount = (propertyType == SerializedPropertyType.Vector2) ? 2 :
(propertyType == SerializedPropertyType.Vector3) ? 3 : 4;
pos.xMin += EditorGUIUtility.labelWidth;
pos.yMin = pos.yMax - EditorGUIUtility.singleLineHeight;
pos.width /= memberCount;
pos.height = EditorGUIUtility.singleLineHeight;
for (int i = 0; i < memberCount; ++i) {
FusionEditorGUI.Overlay(pos, label);
pos.x += pos.width;
}
break;
}
case SerializedPropertyType.Vector2:
case SerializedPropertyType.Vector3: {
Rect pos = position;
// vector properties get broken down into two lines when there's not enough space
if (EditorGUIUtility.wideMode) {
pos.xMin += EditorGUIUtility.labelWidth;
pos.width /= 3;
} else {
pos.xMin += 12;
pos.yMin = pos.yMax - EditorGUIUtility.singleLineHeight;
pos.width /= (propertyType == SerializedPropertyType.Vector2) ? 2 : 3;
}
pos.height = EditorGUIUtility.singleLineHeight;
FusionEditorGUI.Overlay(pos, label);
pos.x += pos.width;
FusionEditorGUI.Overlay(pos, label);
if (propertyType == SerializedPropertyType.Vector3) {
pos.x += pos.width;
FusionEditorGUI.Overlay(pos, label);
}
break;
}
case SerializedPropertyType.Vector4:
if (isExpanded) {
Rect pos = position;
pos.yMin = pos.yMax - 4 * EditorGUIUtility.singleLineHeight - 3 * EditorGUIUtility.standardVerticalSpacing;
pos.height = EditorGUIUtility.singleLineHeight;
for (int i = 0; i < 4; ++i) {
FusionEditorGUI.Overlay(pos, label);
pos.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
}
}
break;
default: {
var pos = position;
pos.height = EditorGUIUtility.singleLineHeight;
FusionEditorGUI.Overlay(pos, label);
}
break;
}
}
public static string UnitToLabel(Units units) {
switch (units) {
case Units.None: return string.Empty;
case Units.Ticks: return "ticks";
case Units.Seconds: return "s";
case Units.MilliSecs: return "ms";
case Units.Kilobytes: return "kB";
case Units.Megabytes: return "MB";
case Units.Normalized: return "normalized";
case Units.Multiplier: return "multiplier";
case Units.Percentage: return "%";
case Units.NormalizedPercentage: return "n%";
case Units.Degrees: return "\u00B0";
case Units.PerSecond: return "hz";
case Units.DegreesPerSecond: return "\u00B0/sec";
case Units.Radians: return "rad";
case Units.RadiansPerSecond: return "rad/s";
case Units.TicksPerSecond: return "ticks/s";
case Units.Units: return "units";
case Units.Bytes: return "B";
case Units.Count: return "count";
case Units.Packets: return "packets";
case Units.Frames: return "frames";
case Units.FramesPerSecond: return "fps";
case Units.SquareMagnitude: return "mag\u00B2";
default: throw new ArgumentOutOfRangeException(nameof(units), $"{units}");
}
}
}
}
#endregion
#region UnityAssetGuidAttributeDrawer.cs
namespace Fusion.Editor {
using System;
using UnityEditor;
using UnityEngine;
[CustomPropertyDrawer(typeof(UnityAssetGuidAttribute))]
[FusionPropertyDrawerMeta(HasFoldout = false)]
internal class UnityAssetGuidAttributeDrawer : PropertyDrawerWithErrorHandling {
protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) {
string guid;
position.width -= 40;
if (property.propertyType == SerializedPropertyType.Generic) {
guid = DrawMangledRawGuid(position, property, label);
} else if (property.propertyType == SerializedPropertyType.String) {
using (new FusionEditorGUI.PropertyScopeWithPrefixLabel(position, label, property, out position)) {
EditorGUI.PropertyField(position, property, GUIContent.none, false);
guid = property.stringValue;
}
} else {
throw new InvalidOperationException();
}
string assetPath = string.Empty;
bool parsable = GUID.TryParse(guid, out _);
if (parsable) {
ClearError();
assetPath = AssetDatabase.GUIDToAssetPath(guid);
}
using (new FusionEditorGUI.EnabledScope(!string.IsNullOrEmpty(assetPath))) {
position.x += position.width;
position.width = 40;
if (GUI.Button(position, "Ping")) {
EditorGUIUtility.PingObject(AssetDatabase.LoadMainAssetAtPath(assetPath));
}
}
if (string.IsNullOrEmpty(assetPath)) {
if (!parsable && !string.IsNullOrEmpty(guid)) {
SetError($"Invalid GUID: {guid}");
} else if (!string.IsNullOrEmpty(guid)) {
SetWarning($"GUID not found");
}
} else {
var asset = AssetDatabase.LoadMainAssetAtPath(assetPath);
if (asset == null) {
SetError($"Asset with this guid does not exist. Last path:\n{assetPath}");
} else {
SetInfo($"Asset path:\n{assetPath}");
}
}
}
private unsafe string DrawMangledRawGuid(Rect position, SerializedProperty property, GUIContent label) {
var inner = property.Copy();
inner.Next(true);
if (inner.depth != property.depth + 1 || !inner.isFixedBuffer || inner.fixedBufferSize != 2) {
throw new InvalidOperationException();
}
var prop0 = inner.GetFixedBufferElementAtIndex(0);
var prop1 = inner.GetFixedBufferElementAtIndex(1);
string guid;
unsafe {
var rawMangled = stackalloc long[2];
rawMangled[0] = prop0.longValue;
rawMangled[1] = prop1.longValue;
Guid guidStruct = default;
CopyAndMangleGuid((byte*)rawMangled, (byte*)&guidStruct);
using (new FusionEditorGUI.PropertyScope(position, label, property)) {
EditorGUI.BeginChangeCheck();
guid = EditorGUI.TextField(position, label, guidStruct.ToString("N"));
if (EditorGUI.EndChangeCheck()) {
if (Guid.TryParse(guid, out guidStruct)) {
CopyAndMangleGuid((byte*)&guidStruct, (byte*)rawMangled);
prop0.longValue = rawMangled[0];
prop1.longValue = rawMangled[1];
} else {
SetError($"Unable to parse {guid}");
}
}
}
}
return guid;
}
public static unsafe void CopyAndMangleGuid(byte* src, byte* dst) {
dst[0] = src[3];
dst[1] = src[2];
dst[2] = src[1];
dst[3] = src[0];
dst[4] = src[5];
dst[5] = src[4];
dst[6] = src[7];
dst[7] = src[6];
dst[8] = src[8];
dst[9] = src[9];
dst[10] = src[10];
dst[11] = src[11];
dst[12] = src[12];
dst[13] = src[13];
dst[14] = src[14];
dst[15] = src[15];
}
public bool HasFoldout(SerializedProperty property) {
return false;
}
}
}
#endregion
#region UnityResourcePathAttributeDrawer.cs
namespace Fusion.Editor {
using UnityEditor;
using UnityEngine;
[CustomPropertyDrawer(typeof(UnityResourcePathAttribute))]
internal class UnityResourcePathAttributeDrawer : PropertyDrawerWithErrorHandling {
protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) {
var attrib = (UnityResourcePathAttribute)attribute;
using (new FusionEditorGUI.PropertyScopeWithPrefixLabel(position, label, property, out position)) {
position.width -= 40;
EditorGUI.PropertyField(position, property, GUIContent.none, false);
Object asset = null;
var path = property.stringValue;
if (string.IsNullOrEmpty(path)) {
ClearError();
} else {
asset = Resources.Load(path, attrib.ResourceType);
if (asset == null) {
SetError($"Resource of type {attrib.ResourceType} not found at {path}");
} else {
SetInfo(AssetDatabase.GetAssetPath(asset));
}
}
using (new EditorGUI.DisabledScope(asset == null)) {
position.x += position.width;
position.width = 40;
if (GUI.Button(position, "Ping"))
// ping the main asset
{
EditorGUIUtility.PingObject(Resources.Load(path));
}
}
}
}
}
}
#endregion
#region WarnIfAttributeDrawer.cs
namespace Fusion.Editor {
using UnityEditor;
using UnityEngine;
partial class WarnIfAttributeDrawer : MessageIfDrawerBase {
private new WarnIfAttribute Attribute => (WarnIfAttribute)attribute;
protected override bool IsBox => Attribute.AsBox;
protected override string Message => Attribute.Message;
protected override MessageType MessageType => MessageType.Warning;
protected override Color InlineBoxColor => FusionEditorSkin.WarningInlineBoxColor;
protected override Texture MessageIcon => FusionEditorSkin.WarningIcon;
}
[CustomPropertyDrawer(typeof(WarnIfAttribute))]
[RedirectCustomPropertyDrawer(typeof(WarnIfAttribute), typeof(WarnIfAttributeDrawer))]
partial class PropertyDrawerForArrayWorkaround {
}
}
#endregion
#region ArrayLengthAttributeDrawer.Odin.cs
#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED
namespace Fusion.Editor {
using System;
using System.Collections;
using Sirenix.OdinInspector.Editor;
using UnityEditor;
using UnityEngine;
partial class ArrayLengthAttributeDrawer {
[FusionOdinAttributeConverter]
static System.Attribute[] ConvertToOdinAttributes(System.Reflection.MemberInfo memberInfo, ArrayLengthAttribute attribute) {
return new[] { new OdinAttributeProxy() { SourceAttribute = attribute } };
}
class OdinAttributeProxy : Attribute {
public ArrayLengthAttribute SourceAttribute;
}
class OdinDrawer : OdinAttributeDrawer<OdinAttributeProxy> {
protected override bool CanDrawAttributeProperty(InspectorProperty property) {
return property.GetUnityPropertyType() == SerializedPropertyType.ArraySize;
}
protected override void DrawPropertyLayout(GUIContent label) {
var valEntry = Property.ValueEntry;
var weakValues = valEntry.WeakValues;
for (int i = 0; i < weakValues.Count; ++i) {
var values = (IList)weakValues[i];
if (values == null) {
continue;
}
var arraySize = values.Count;
var attr = Attribute.SourceAttribute;
if (arraySize < attr.MinLength) {
arraySize = attr.MinLength;
} else if (arraySize > attr.MaxLength) {
arraySize = attr.MaxLength;
}
if (values.Count != arraySize) {
if (values is Array array) {
var newArr = Array.CreateInstance(array.GetType().GetElementType(), arraySize);
Array.Copy(array, newArr, Math.Min(array.Length, arraySize));
weakValues.ForceSetValue(i, newArr);
} else {
while (values.Count > arraySize) {
values.RemoveAt(values.Count - 1);
}
while (values.Count < arraySize) {
values.Add(null);
}
}
weakValues.ForceMarkDirty();
}
}
CallNextDrawer(label);
}
}
}
}
#endif
#endregion
#region BinaryDataAttributeDrawer.Odin.cs
#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED
namespace Fusion.Editor {
using Sirenix.OdinInspector;
partial class BinaryDataAttributeDrawer {
[FusionOdinAttributeConverter]
static System.Attribute[] ConvertToOdinAttributes(System.Reflection.MemberInfo memberInfo, BinaryDataAttribute attribute) {
return new[] { new DrawWithUnityAttribute() };
}
}
}
#endif
#endregion
#region DoIfAttributeDrawer.Odin.cs
#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED
namespace Fusion.Editor {
using System;
using System.Linq;
using System.Reflection;
using Sirenix.OdinInspector.Editor;
using UnityEngine;
partial class DoIfAttributeDrawer {
protected abstract class OdinProxyAttributeBase : Attribute {
public DoIfAttributeBase SourceAttribute;
}
protected abstract class OdinDrawerBase<T> : OdinAttributeDrawer<T> where T : OdinProxyAttributeBase {
protected override bool CanDrawAttributeProperty(InspectorProperty property) {
if (property.IsArrayElement(out _)) {
return false;
}
return true;
}
protected override void DrawPropertyLayout(GUIContent label) {
var doIf = this.Attribute.SourceAttribute;
bool allPassed = true;
bool anyPassed = false;
var targetProp = Property.FindPropertyRelativeToParent(doIf.ConditionMember);
if (targetProp == null) {
var objType = Property.ParentType;
if (!_cachedGetters.TryGetValue((objType, doIf.ConditionMember), out var getter)) {
// maybe this is a top-level property then and we can use reflection?
if (Property.GetValueDepth() != 0) {
if (doIf.ErrorOnConditionMemberNotFound) {
FusionEditorLog.ErrorInspector($"Can't check condition for {Property.Path}: non-SerializedProperty checks only work for top-level properties");
}
} else {
try {
_cachedGetters.Add((objType, doIf.ConditionMember), Property.ParentType.CreateGetter(doIf.ConditionMember, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy));
} catch (Exception e) {
if (doIf.ErrorOnConditionMemberNotFound) {
FusionEditorLog.ErrorInspector($"Can't check condition for {Property.Path}: unable to create getter for {doIf.ConditionMember} with exception {e}");
}
}
}
}
if (getter != null) {
foreach (var obj in Property.GetValueParent().ValueEntry.WeakValues) {
var value = getter(obj);
if (DoIfAttributeDrawer.CheckCondition(doIf, value)) {
anyPassed = true;
} else {
allPassed = false;
}
}
}
} else {
foreach (var value in targetProp.ValueEntry.WeakValues) {
if (DoIfAttributeDrawer.CheckCondition(doIf, value)) {
anyPassed = true;
} else {
allPassed = false;
}
}
}
DrawPropertyLayout(label, allPassed, anyPassed);
}
protected abstract void DrawPropertyLayout(GUIContent label, bool allPassed, bool anyPassed);
}
}
}
#endif
#endregion
#region DrawIfAttributeDrawer.Odin.cs
#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED
namespace Fusion.Editor {
using System;
using Sirenix.OdinInspector.Editor;
using UnityEditor;
using UnityEngine;
partial class DrawIfAttributeDrawer {
[FusionOdinAttributeConverter]
static System.Attribute[] ConvertToOdinAttributes(System.Reflection.MemberInfo memberInfo, DrawIfAttribute attribute) {
return new[] { new OdinAttributeProxy() { SourceAttribute = attribute } };
}
class OdinAttributeProxy : OdinProxyAttributeBase {
}
class OdinDrawer : OdinDrawerBase<OdinAttributeProxy> {
protected override void DrawPropertyLayout(GUIContent label, bool allPassed, bool anyPassed) {
var attribute = (DrawIfAttribute)Attribute.SourceAttribute;
if (!allPassed) {
if (attribute.Hide) {
return;
}
}
using (new EditorGUI.DisabledGroupScope(!allPassed)) {
base.CallNextDrawer(label);
}
}
}
}
}
#endif
#endregion
#region DrawInlineAttributeDrawer.Odin.cs
namespace Fusion.Editor {
partial class DrawInlineAttributeDrawer {
#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED
[FusionOdinAttributeConverter]
static System.Attribute[] ConvertToOdinAttributes(System.Reflection.MemberInfo memberInfo, DrawInlineAttribute attribute) {
return new System.Attribute[] { new Sirenix.OdinInspector.InlinePropertyAttribute(), new Sirenix.OdinInspector.HideLabelAttribute() };
}
#endif
}
}
#endregion
#region ErrorIfAttributeDrawer.Odin.cs
#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED
namespace Fusion.Editor {
using System;
using Sirenix.OdinInspector.Editor;
using UnityEditor;
using UnityEngine;
partial class ErrorIfAttributeDrawer {
[FusionOdinAttributeConverter]
static System.Attribute[] ConvertToOdinAttributes(System.Reflection.MemberInfo memberInfo, ErrorIfAttribute attribute) {
return new[] { new OdinAttributeProxy() { SourceAttribute = attribute } };
}
class OdinAttributeProxy : OdinProxyAttributeBase {
}
class OdinDrawer : OdinDrawerBase<OdinAttributeProxy> {
protected override void DrawPropertyLayout(GUIContent label, bool allPassed, bool anyPassed) {
var attribute = (ErrorIfAttribute)Attribute.SourceAttribute;
base.CallNextDrawer(label);
if (anyPassed) {
using (new FusionEditorGUI.ErrorScope(attribute.Message)) {
}
}
}
}
}
}
#endif
#endregion
#region FieldEditorButtonAttributeDrawer.Odin.cs
#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED
namespace Fusion.Editor {
using System;
using System.Linq;
using Sirenix.OdinInspector.Editor;
using UnityEditor;
using UnityEngine;
partial class FieldEditorButtonAttributeDrawer {
[FusionOdinAttributeConverter]
static System.Attribute[] ConvertToOdinAttributes(System.Reflection.MemberInfo memberInfo, FieldEditorButtonAttribute attribute) {
return new[] { new OdinAttributeProxy() { SourceAttribute = attribute } };
}
class OdinAttributeProxy : Attribute {
public FieldEditorButtonAttribute SourceAttribute;
}
class OdinDrawer : OdinAttributeDrawer<OdinAttributeProxy> {
protected override bool CanDrawAttributeProperty(InspectorProperty property) {
return !property.IsArrayElement(out _);
}
protected override void DrawPropertyLayout(GUIContent label) {
CallNextDrawer(label);
var buttonRect = EditorGUI.IndentedRect(EditorGUILayout.GetControlRect());
var attribute = Attribute.SourceAttribute;
var root = this.Property.SerializationRoot;
var targetType = root.ValueEntry.TypeOfValue;
var targetObjects = root.ValueEntry.WeakValues
.OfType<UnityEngine.Object>()
.ToArray();
if (DrawButton(buttonRect, attribute, targetType, targetObjects)) {
this.Property.MarkSerializationRootDirty();
}
}
}
}
}
#endif
#endregion
#region HideArrayElementLabelAttributeDrawer.Odin.cs
namespace Fusion.Editor {
partial class HideArrayElementLabelAttributeDrawer {
#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED
[FusionOdinAttributeConverter]
static System.Attribute[] ConvertToOdinAttributes(System.Reflection.MemberInfo memberInfo, HideArrayElementLabelAttribute attribute) {
// not yet supported
return System.Array.Empty<System.Attribute>();
}
#endif
}
}
#endregion
#region InlineHelpAttributeDrawer.Odin.cs
#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED
namespace Fusion.Editor {
using System;
using System.Linq;
using System.Reflection;
using Sirenix.OdinInspector.Editor;
using UnityEditor;
using UnityEngine;
partial class InlineHelpAttributeDrawer {
[FusionOdinAttributeConverter]
static System.Attribute[] ConvertToOdinAttributes(System.Reflection.MemberInfo memberInfo, InlineHelpAttribute attribute) {
return new[] { new OdinAttributeProxy() { SourceAttribute = attribute } };
}
class OdinAttributeProxy : Attribute {
public InlineHelpAttribute SourceAttribute;
}
class OdinDrawer : OdinAttributeDrawer<OdinAttributeProxy> {
protected override bool CanDrawAttributeProperty(InspectorProperty property) {
if (property.IsArrayElement(out _)) {
return false;
}
var helpContent = GetHelpContent(property, true);
if (helpContent == GUIContent.none) {
return false;
}
return true;
}
private Rect _lastRect;
private bool GetHasFoldout() {
var (meta, _) = Property.GetNextPropertyDrawerMetaAttribute(Attribute);
if (meta != null) {
return meta.HasFoldout;
}
return Property.GetUnityPropertyType() == SerializedPropertyType.Generic;
}
protected override void DrawPropertyLayout(GUIContent label) {
Rect buttonRect = default;
bool wasExpanded = false;
bool hasFoldout = GetHasFoldout();
Rect propertyRect = _lastRect;
var helpContent = GetHelpContent(Property, Attribute.SourceAttribute.ShowTypeHelp);
using (new FusionEditorGUI.GUIContentScope(label)) {
(wasExpanded, buttonRect) = InlineHelpAttributeDrawer.DrawInlineHelpBeforeProperty(label, helpContent, _lastRect, Property.Path, EditorGUI.indentLevel, hasFoldout, Property.SerializationRoot);
EditorGUILayout.BeginVertical();
this.CallNextDrawer(label);
EditorGUILayout.EndVertical();
}
if (Event.current.type == EventType.Repaint) {
_lastRect = GUILayoutUtility.GetLastRect();
}
if (propertyRect.width > 1 && propertyRect.height > 1) {
if (wasExpanded) {
var height = FusionEditorGUI.GetInlineBoxSize(helpContent).y;
EditorGUILayout.GetControlRect(false, height);
propertyRect.height += FusionEditorGUI.GetInlineBoxSize(helpContent).y;
}
DrawInlineHelpAfterProperty(buttonRect, wasExpanded, helpContent, propertyRect);
}
}
private GUIContent GetHelpContent(InspectorProperty property, bool includeTypeHelp) {
var parentType = property.ValueEntry.ParentType;
var memberInfo = parentType.GetField(property.Name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
return FusionCodeDoc.FindEntry(memberInfo, includeTypeHelp) ?? GUIContent.none;
}
}
}
}
#endif
#endregion
#region LayerMatrixAttributeDrawer.Odin.cs
#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED
namespace Fusion.Editor {
using System;
using System.Collections;
using System.Linq;
using Sirenix.OdinInspector.Editor;
using UnityEditor;
using UnityEditor.Animations;
using UnityEngine;
partial class LayerMatrixAttributeDrawer {
[FusionOdinAttributeConverter]
static System.Attribute[] ConvertToOdinAttributes(System.Reflection.MemberInfo memberInfo, LayerMatrixAttribute attribute) {
return new[] { new OdinAttributeProxy() { SourceAttribute = attribute } };
}
class OdinAttributeProxy : Attribute {
public LayerMatrixAttribute SourceAttribute;
}
class OdinDrawer : OdinAttributeDrawer<OdinAttributeProxy> {
protected override void DrawPropertyLayout(GUIContent label) {
var rect = EditorGUILayout.GetControlRect();
var valueRect = EditorGUI.PrefixLabel(rect, label);
if (GUI.Button(valueRect, "Edit")) {
int[] values = (int[])this.Property.ValueEntry.WeakValues[0];
PopupWindow.Show(valueRect, new LayerMatrixPopup(label.text, (layerA, layerB) => {
if (layerA >= values.Length) {
return false;
}
return (values[layerA] & (1 << layerB)) != 0;
}, (layerA, layerB, val) => {
if (Mathf.Max(layerA, layerB) >= values.Length) {
Array.Resize(ref values, Mathf.Max(layerA, layerB) + 1);
}
if (val) {
values[layerA] |= (1 << layerB);
values[layerB] |= (1 << layerA);
} else {
values[layerA] &= ~(1 << layerB);
values[layerB] &= ~(1 << layerA);
}
// sync other values
for (int i = 1; i < this.Property.ValueEntry.ValueCount; ++i) {
this.Property.ValueEntry.WeakValues.ForceSetValue(i, values.Clone());
}
Property.MarkSerializationRootDirty();
}));
}
}
}
}
}
#endif
#endregion
#region FusionOdinAttributeConverterAttribute.cs
namespace Fusion.Editor {
using System;
[AttributeUsage(AttributeTargets.Method)]
public class FusionOdinAttributeConverterAttribute : Attribute {
}
}
#endregion
#region FusionOdinAttributeProcessor.cs
#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED
namespace Fusion.Editor {
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Sirenix.Utilities;
using UnityEditor;
using UnityEngine;
public partial class FusionOdinAttributeProcessor : Sirenix.OdinInspector.Editor.OdinAttributeProcessor {
public override void ProcessChildMemberAttributes(Sirenix.OdinInspector.Editor.InspectorProperty parentProperty, MemberInfo member, List<Attribute> attributes) {
for (int i = 0; i < attributes.Count; ++i) {
var attribute = attributes[i];
if (attribute is PropertyAttribute) {
var drawerType = FusionEditorGUI.GetDrawerTypeIncludingWorkarounds(attribute);
if (drawerType != null) {
var method = drawerType.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
.FirstOrDefault(x => x.IsDefined(typeof(FusionOdinAttributeConverterAttribute)));
if (method != null) {
var replacementAttributes = (System.Attribute[])method.Invoke(null, new object[] { member, attribute }) ?? Array.Empty<Attribute>();
attributes.RemoveAt(i);
FusionEditorLog.TraceInspector($"Replacing attribute {attribute.GetType().FullName} of {member.ToString()} with {string.Join(", ", replacementAttributes.Select(x => x.GetType().FullName))}");
if (replacementAttributes.Length > 0) {
attributes.InsertRange(i, replacementAttributes);
}
i += replacementAttributes.Length - 1;
continue;
}
}
if (attribute is DecoratingPropertyAttribute) {
FusionEditorLog.Warn($"Unable to replace {nameof(DecoratingPropertyAttribute)}-derived attribute: {attribute.GetType().FullName}");
attributes.RemoveAt(i--);
}
}
}
}
}
}
#endif
#endregion
#region FusionOdinExtensions.cs
#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED
namespace Fusion.Editor {
using System;
using System.Reflection;
using Sirenix.OdinInspector.Editor;
using UnityEditor;
using UnityEngine;
public static class FusionOdinExtensions {
public static bool IsArrayElement(this InspectorProperty property, out int index) {
var propertyPath = property.UnityPropertyPath;
if (!propertyPath.EndsWith("]", StringComparison.Ordinal)) {
index = -1;
return false;
}
var indexStart = propertyPath.LastIndexOf("[", StringComparison.Ordinal);
if (indexStart < 0) {
index = -1;
return false;
}
index = int.Parse(propertyPath.Substring(indexStart + 1, propertyPath.Length - indexStart - 2));
return true;
}
public static bool IsArrayProperty(this InspectorProperty property) {
var memberType = property.Info.TypeOfValue;
if (!memberType.IsArrayOrList()) {
return false;
}
return true;
}
public static int GetValueDepth(this InspectorProperty property) {
int depth = 0;
var parent = property.GetValueParent();
while (parent?.IsTreeRoot == false) {
++depth;
parent = parent.GetValueParent();
}
return depth;
}
public static InspectorProperty GetValueParent(this InspectorProperty property) {
var parent = property.Parent;
while (parent?.Info.PropertyType == PropertyType.Group) {
parent = parent.Parent;
}
return parent;
}
public static SerializedPropertyType GetUnityPropertyType(this InspectorProperty inspectorProperty) {
if (inspectorProperty == null) {
throw new ArgumentNullException(nameof(inspectorProperty));
}
var valueType = inspectorProperty.ValueEntry.TypeOfValue;
if (valueType == typeof(bool)) {
return SerializedPropertyType.Boolean;
} else if (valueType == typeof(int) || valueType == typeof(long) || valueType == typeof(short) || valueType == typeof(byte) || valueType == typeof(uint) || valueType == typeof(ulong) || valueType == typeof(ushort) || valueType == typeof(sbyte)) {
return SerializedPropertyType.Integer;
} else if (valueType == typeof(float) || valueType == typeof(double)) {
return SerializedPropertyType.Float;
} else if (valueType == typeof(string)) {
return SerializedPropertyType.String;
} else if (valueType == typeof(Color)) {
return SerializedPropertyType.Color;
} else if (valueType == typeof(LayerMask)) {
return SerializedPropertyType.LayerMask;
} else if (valueType == typeof(Vector2)) {
return SerializedPropertyType.Vector2;
} else if (valueType == typeof(Vector3)) {
return SerializedPropertyType.Vector3;
} else if (valueType == typeof(Vector4)) {
return SerializedPropertyType.Vector4;
} else if (valueType == typeof(Vector2Int)) {
return SerializedPropertyType.Vector2Int;
} else if (valueType == typeof(Vector3Int)) {
return SerializedPropertyType.Vector3Int;
} else if (valueType == typeof(Rect)) {
return SerializedPropertyType.Rect;
} else if (valueType == typeof(RectInt)) {
return SerializedPropertyType.RectInt;
} else if (valueType == typeof(AnimationCurve)) {
return SerializedPropertyType.AnimationCurve;
} else if (valueType == typeof(Bounds)) {
return SerializedPropertyType.Bounds;
} else if (valueType == typeof(BoundsInt)) {
return SerializedPropertyType.BoundsInt;
} else if (valueType == typeof(Gradient)) {
return SerializedPropertyType.Gradient;
} else if (valueType == typeof(Quaternion)) {
return SerializedPropertyType.Quaternion;
} else if (valueType.IsEnum) {
return SerializedPropertyType.Enum;
} else if (typeof(UnityEngine.Object).IsAssignableFrom(valueType)) {
return SerializedPropertyType.ObjectReference;
} else if (valueType.IsArrayOrList()) {
return SerializedPropertyType.ArraySize;
}
return SerializedPropertyType.Generic;
}
public static InspectorProperty FindPropertyRelativeToParent(this InspectorProperty property, string path) {
InspectorProperty referenceProperty = property;
int parentIndex = 0;
do {
if (referenceProperty.GetValueParent() == null) {
return null;
}
referenceProperty = referenceProperty.GetValueParent();
} while (path[parentIndex++] == '^');
if (parentIndex > 1) {
path = path.Substring(parentIndex - 1);
}
var parts = path.Split('.');
if (parts.Length == 0) {
return null;
}
foreach (var part in parts) {
var child = referenceProperty.Children[part];
if (child != null) {
referenceProperty = child;
} else {
return null;
}
}
return referenceProperty;
}
public static (FusionPropertyDrawerMetaAttribute, Attribute) GetNextPropertyDrawerMetaAttribute(this InspectorProperty property, Attribute referenceAttribute) {
var attributeIndex = referenceAttribute == null ? -1 : property.Attributes.IndexOf(referenceAttribute);
for (int i = attributeIndex + 1; i < property.Attributes.Count; ++i) {
var otherAttribute = property.Attributes[i];
if (otherAttribute is DrawerPropertyAttribute == false) {
continue;
}
var attributeDrawerType = FusionEditorGUI.GetDrawerTypeIncludingWorkarounds(otherAttribute);
if (attributeDrawerType == null) {
continue;
}
var meta = attributeDrawerType.GetCustomAttribute<FusionPropertyDrawerMetaAttribute>();
if (meta != null) {
return (meta, otherAttribute);
}
}
var propertyDrawerType = UnityInternal.ScriptAttributeUtility.GetDrawerTypeForType(property.ValueEntry.TypeOfValue, null, false);
if (propertyDrawerType != null) {
var meta = propertyDrawerType.GetCustomAttribute<FusionPropertyDrawerMetaAttribute>();
if (meta != null) {
return (meta, null);
}
}
return (null, null);
}
}
}
#endif
#endregion
#region ReadOnlyAttributeDrawer.Odin.cs
namespace Fusion.Editor {
partial class ReadOnlyAttributeDrawer {
#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED
[FusionOdinAttributeConverter]
static System.Attribute[] ConvertToOdinAttributes(System.Reflection.MemberInfo memberInfo, ReadOnlyAttribute attribute) {
if (attribute.InEditMode && attribute.InPlayMode) {
return new[] { new Sirenix.OdinInspector.ReadOnlyAttribute() };
}
if (attribute.InEditMode) {
return new[] { new Sirenix.OdinInspector.DisableInEditorModeAttribute() };
}
if (attribute.InPlayMode) {
return new[] { new Sirenix.OdinInspector.DisableInPlayModeAttribute() };
}
return System.Array.Empty<System.Attribute>();
}
#endif
}
}
#endregion
#region SerializeReferenceTypePickerDrawer.Odin.cs
#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED
namespace Fusion.Editor {
using System;
partial class SerializeReferenceTypePickerAttributeDrawer {
[FusionOdinAttributeConverter]
static System.Attribute[] ConvertToOdinAttributes(System.Reflection.MemberInfo memberInfo, SerializeReferenceTypePickerAttribute attribute) {
return Array.Empty<System.Attribute>();
}
}
}
#endif
#endregion
#region UnitAttributeDrawer.Odin.cs
#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED
namespace Fusion.Editor {
using System;
using System.Reflection;
using Sirenix.OdinInspector.Editor;
using UnityEditor;
using UnityEngine;
partial class UnitAttributeDrawer {
[FusionOdinAttributeConverter]
static System.Attribute[] ConvertToOdinAttributes(System.Reflection.MemberInfo memberInfo, UnitAttribute attribute) {
return new[] { new OdinAttributeProxy() { SourceAttribute = attribute } };
}
class OdinAttributeProxy : Attribute {
public UnitAttribute SourceAttribute;
}
class OdinUnitAttributeDrawer : Sirenix.OdinInspector.Editor.OdinAttributeDrawer<OdinAttributeProxy> {
private GUIContent _label;
private Rect _lastRect;
protected override bool CanDrawAttributeProperty(Sirenix.OdinInspector.Editor.InspectorProperty property) {
for (Attribute attrib = null;;) {
var (meta, nextAttribute) = property.GetNextPropertyDrawerMetaAttribute(attrib);
attrib = nextAttribute;
if (meta?.HandlesUnits == true) {
if (attrib is OdinAttributeProxy == false) {
return false;
}
}
if (meta == null || attrib == null) {
break;
}
}
switch (property.GetUnityPropertyType()) {
case SerializedPropertyType.ArraySize:
return false;
default:
return true;
}
}
protected sealed override void DrawPropertyLayout(GUIContent label) {
using (new EditorGUILayout.VerticalScope()) {
this.CallNextDrawer(label);
}
if (Event.current.type == EventType.Repaint) {
_lastRect = GUILayoutUtility.GetLastRect();
}
if (_lastRect.width > 1 && _lastRect.height > 1) {
_label ??= new GUIContent();
_label.text = UnitToLabel(this.Attribute.SourceAttribute.Unit);
DrawUnitOverlay(_lastRect, _label, Property.GetUnityPropertyType(), false, odinStyle: true);
}
}
}
}
}
#endif
#endregion
#region WarnIfAttributeDrawer.Odin.cs
#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED
namespace Fusion.Editor {
using System;
using Sirenix.OdinInspector.Editor;
using UnityEditor;
using UnityEngine;
partial class WarnIfAttributeDrawer {
[FusionOdinAttributeConverter]
static System.Attribute[] ConvertToOdinAttributes(System.Reflection.MemberInfo memberInfo, WarnIfAttribute attribute) {
return new[] { new OdinAttributeProxy() { SourceAttribute = attribute } };
}
class OdinAttributeProxy : OdinProxyAttributeBase {
}
class OdinDrawer : OdinDrawerBase<OdinAttributeProxy> {
protected override void DrawPropertyLayout(GUIContent label, bool allPassed, bool anyPassed) {
var attribute = (WarnIfAttribute)Attribute.SourceAttribute;
base.CallNextDrawer(label);
if (anyPassed) {
using (new FusionEditorGUI.WarningScope(attribute.Message)) {
}
}
}
}
}
}
#endif
#endregion
#region INetworkAssetSourceFactory.cs
namespace Fusion.Editor {
using UnityEditor;
public partial interface INetworkAssetSourceFactory {
int Order { get; }
}
public readonly partial struct NetworkAssetSourceFactoryContext {
public readonly int InstanceID;
public readonly string AssetGuid;
public readonly string AssetName;
public readonly bool IsMainAsset;
public string AssetPath => AssetDatabaseUtils.GetAssetPathOrThrow(InstanceID);
public NetworkAssetSourceFactoryContext(string assetGuid, int instanceID, string assetName, bool isMainAsset) {
AssetGuid = assetGuid;
InstanceID = instanceID;
AssetName = assetName;
IsMainAsset = isMainAsset;
}
public NetworkAssetSourceFactoryContext(HierarchyProperty hierarchyProperty) {
AssetGuid = hierarchyProperty.guid;
InstanceID = hierarchyProperty.instanceID;
AssetName = hierarchyProperty.name;
IsMainAsset = hierarchyProperty.isMainRepresentation;
}
public NetworkAssetSourceFactoryContext(UnityEngine.Object obj) {
if (!obj) {
throw new System.ArgumentNullException(nameof(obj));
}
var instanceId = obj.GetInstanceID();
(AssetGuid, _) = AssetDatabaseUtils.GetGUIDAndLocalFileIdentifierOrThrow(instanceId);
InstanceID = instanceId;
AssetName = obj.name;
IsMainAsset = AssetDatabase.IsMainAsset(instanceId);
}
}
}
#endregion
#region NetworkAssetSourceFactoryAddressable.cs
#if (FUSION_ADDRESSABLES || FUSION_ENABLE_ADDRESSABLES) && !FUSION_DISABLE_ADDRESSABLES
namespace Fusion.Editor {
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor.AddressableAssets;
using UnityEditor.AddressableAssets.Settings;
public partial class NetworkAssetSourceFactoryAddressable : INetworkAssetSourceFactory {
public const int Order = 800;
int INetworkAssetSourceFactory.Order => Order;
protected bool TryCreateInternal<TSource, TAsset>(in NetworkAssetSourceFactoryContext context, out TSource result)
where TSource : NetworkAssetSourceAddressable<TAsset>, new()
where TAsset : UnityEngine.Object {
var addressableEntry = _guidToParentAddressable.Value[context.AssetGuid].SingleOrDefault();
if (addressableEntry == null) {
result = default;
return false;
}
result = new TSource() {
Address = new(addressableEntry.guid) {
SubObjectName = context.IsMainAsset ? string.Empty : context.AssetName,
},
};
return true;
}
readonly Lazy<ILookup<string, AddressableAssetEntry>> _guidToParentAddressable = new(() => CreateAddressablesLookup());
static ILookup<string, AddressableAssetEntry> CreateAddressablesLookup() {
var assetList = new List<AddressableAssetEntry>(128);
var assetsSettings = AddressableAssetSettingsDefaultObject.Settings;
if (assetsSettings == null) {
throw new System.InvalidOperationException("Unable to load Addressables settings. This may be due to an outdated Addressables version.");
}
Func<AddressableAssetGroup, bool> groupFilter = null;
CreateAddressablesGroupFilter(ref groupFilter);
Func<AddressableAssetEntry, bool> entryFilter = null;
CreateAddressablesEntryFilter(ref entryFilter);
assetsSettings.GetAllAssets(assetList, true, groupFilter: groupFilter, entryFilter: entryFilter);
return assetList.Where(x => !string.IsNullOrEmpty(x.guid)).ToLookup(x => x.guid);
}
static partial void CreateAddressablesEntryFilter(ref Func<AddressableAssetEntry, bool> filter);
static partial void CreateAddressablesGroupFilter(ref Func<AddressableAssetGroup, bool> filter);
}
}
#endif
#endregion
#region NetworkAssetSourceFactoryResource.cs
namespace Fusion.Editor {
public partial class NetworkAssetSourceFactoryResource : INetworkAssetSourceFactory {
public const int Order = 1000;
int INetworkAssetSourceFactory.Order => Order;
protected bool TryCreateInternal<TSource, TAsset>(in NetworkAssetSourceFactoryContext context, out TSource result)
where TSource : NetworkAssetSourceResource<TAsset>, new()
where TAsset : UnityEngine.Object {
if (!PathUtils.TryMakeRelativeToFolder(context.AssetPath, "/Resources/", out var resourcePath)) {
result = default;
return false;
}
var withoutExtension = PathUtils.GetPathWithoutExtension(resourcePath);
result = new TSource() {
ResourcePath = withoutExtension,
SubObjectName = context.IsMainAsset ? string.Empty : context.AssetName,
};
return true;
}
}
}
#endregion
#region NetworkAssetSourceFactoryStatic.cs
namespace Fusion.Editor {
using UnityEditor;
using UnityEngine;
public partial class NetworkAssetSourceFactoryStatic : INetworkAssetSourceFactory {
public const int Order = int.MaxValue;
int INetworkAssetSourceFactory.Order => Order;
protected bool TryCreateInternal<TSource, TAsset>(in NetworkAssetSourceFactoryContext context, out TSource result)
where TSource : NetworkAssetSourceStaticLazy<TAsset>, new()
where TAsset : UnityEngine.Object {
if (typeof(TAsset).IsSubclassOf(typeof(Component))) {
var prefab = (GameObject)EditorUtility.InstanceIDToObject(context.InstanceID);
result = new TSource() {
Object = prefab.GetComponent<TAsset>()
};
} else {
result = new TSource() {
Object = new(context.InstanceID)
};
}
return true;
}
}
}
#endregion
#endregion
#region Assets/Photon/Fusion/Editor/FusionEditorConfigImporter.cs
namespace Fusion.Editor {
using System.IO;
using UnityEditor.AssetImporters;
using UnityEngine;
[ScriptedImporter(0, "editorconfig")]
public class FusionEditorConfigImporter : ScriptedImporter {
public override void OnImportAsset(AssetImportContext ctx) {
var path = ctx.assetPath;
var contents = File.ReadAllText(path);
// create internal text asset for convenience
var mainAsset = new TextAsset(contents);
ctx.AddObjectToAsset("main", mainAsset);
ctx.SetMainObject(mainAsset);
// write the actual editorconfig for editors to consume
var editorConfigPath = Path.Combine(Path.GetDirectoryName(path), ".editorconfig");
File.WriteAllText(editorConfigPath, contents);
}
}
}
#endregion
#region Assets/Photon/Fusion/Editor/FusionHierarchyWindowOverlay.cs
namespace Fusion.Editor {
using System;
using Fusion.Analyzer;
using UnityEditor;
using UnityEngine;
using UnityEngine.SceneManagement;
internal class FusionHierarchyWindowOverlay {
[RuntimeInitializeOnLoadMethod]
public static void Initialize() {
UnityEditor.EditorApplication.hierarchyWindowItemOnGUI -= HierarchyWindowOverlay;
UnityEditor.EditorApplication.hierarchyWindowItemOnGUI += HierarchyWindowOverlay;
}
[StaticField(StaticFieldResetMode.None)]
private static Lazy<GUIStyle> s_hierarchyOverlayLabelStyle = new Lazy<GUIStyle>(() => {
var result = new GUIStyle(UnityEditor.EditorStyles.miniButton);
result.alignment = TextAnchor.MiddleCenter;
result.fontSize = 9;
result.padding = new RectOffset(4, 4, 0, 0);
result.fixedHeight = 13f;
return result;
});
[StaticField(StaticFieldResetMode.None)]
private static GUIContent s_multipleInstancesContent = EditorGUIUtility.IconContent("Warning", "multiple");
private static void HierarchyWindowOverlay(int instanceId, Rect position) {
var obj = UnityEditor.EditorUtility.InstanceIDToObject(instanceId);
if (obj != null) {
return;
}
// find a scene for this id
Scene scene = default;
for (int i = 0; i < SceneManager.sceneCount; ++i) {
var s = SceneManager.GetSceneAt(i);
if (s.handle == instanceId) {
scene = s;
break;
}
}
if (!scene.IsValid()) {
return;
}
var instances = NetworkRunner.Instances;
NetworkRunner matchingRunner = null;
bool multipleRunners = false;
for (int i = 0; i < instances.Count; ++i) {
var runner = instances[i];
if (runner.SimulationUnityScene == scene) {
if (matchingRunner == null) {
matchingRunner = runner;
} else {
multipleRunners = true;
break;
}
}
}
if (!matchingRunner) {
return;
}
var rect = new Rect(position) {
xMin = position.xMax - 56,
xMax = position.xMax - 2,
yMin = position.yMin + 1,
};
{
if (multipleRunners) {
if (EditorGUI.DropdownButton(rect, s_multipleInstancesContent, FocusType.Passive, s_hierarchyOverlayLabelStyle.Value)) {
var menu = new GenericMenu();
for (int i = 0; i < instances.Count; ++i) {
var runner = instances[i];
var otherScene = runner.SimulationUnityScene;
if (!otherScene.IsValid()) {
continue;
}
if (otherScene.handle == instanceId) {
menu.AddItem(MakeRunnerContent(runner), false, () => {
EditorGUIUtility.PingObject(runner);
Selection.activeObject = runner;
});
}
}
menu.ShowAsContext();
}
} else {
var runner = matchingRunner;
if (GUI.Button(rect, MakeRunnerContent(runner), s_hierarchyOverlayLabelStyle.Value)) {
EditorGUIUtility.PingObject(runner);
Selection.activeGameObject = runner.gameObject;
}
}
}
GUIContent MakeRunnerContent(NetworkRunner runner) {
return new GUIContent($"{runner.Mode} {(runner.LocalPlayer.IsRealPlayer ? "P" + runner.LocalPlayer.PlayerId.ToString() : "")}");
}
}
}
}
#endregion
#region Assets/Photon/Fusion/Editor/FusionHubWindowUtils.cs
// ----------------------------------------------------------------------------
// <copyright file="WizardWindowUtils.cs" company="Exit Games GmbH">
// PhotonNetwork Framework for Unity - Copyright (C) 2021 Exit Games GmbH
// </copyright>
// <summary>
// MenuItems and in-Editor scripts for PhotonNetwork.
// </summary>
// <author>developer@exitgames.com</author>
// ----------------------------------------------------------------------------
namespace Fusion.Editor {
#if FUSION_WEAVER && UNITY_EDITOR
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Text.RegularExpressions;
using Photon.Realtime;
using UnityEditor;
using UnityEngine;
using UnityEngine.UI;
public partial class FusionHubWindow {
/// <summary>
/// Section Definition.
/// </summary>
internal class Section {
public string Title;
public string Description;
public Action DrawMethod;
public Icon Icon;
public Section(string title, string description, Action drawMethod, Icon icon) {
Title = title;
Description = description;
DrawMethod = drawMethod;
Icon = icon;
}
}
public enum Icon {
Setup,
Documentation,
Samples,
Community,
ProductLogo,
PhotonCloud,
FusionIcon,
}
private static class Constants {
public const string UrlFusionDocsOnline = "https://doc.photonengine.com/fusion/v2/";
public const string UrlFusionIntro = "https://doc.photonengine.com/fusion/v2/getting-started/fusion-introduction";
public const string UrlFusionSDK = "https://doc.photonengine.com/fusion/v2/getting-started/sdk-download";
public const string UrlCloudDashboard = "https://id.photonengine.com/account/signin?email=";
public const string UrlDashboardProfile = "https://dashboard.photonengine.com/Account/Profile";
public const string UrlDashboard = "https://dashboard.photonengine.com/";
public const string UrlSampleSection = "https://doc.photonengine.com/fusion/v2/current/samples/overview";
public const string UrlFusion100 = "https://doc.photonengine.com/fusion/v2/tutorials/shared-mode-basics/overview";
public const string UrlFusionLoop = "https://doc.photonengine.com/fusion/v2/current/samples/fusion-application-loop";
public const string UrlHelloFusion = "https://doc.photonengine.com/fusion/v2/current/hello-fusion/hello-fusion";
public const string UrlHelloFusionVr = "https://doc.photonengine.com/fusion/v2/current/hello-fusion/hello-fusion-vr";
public const string UrlTanks = "https://doc.photonengine.com/fusion/v2/current/samples/fusion-tanknarok";
public const string UrlKarts = "https://doc.photonengine.com/fusion/v2/current/samples/fusion-karts";
public const string UrlDragonHuntersVR = "https://doc.photonengine.com/fusion/v2/current/samples/fusion-dragonhunters-vr";
public const string UrlFusionDocApi = "https://doc-api.photonengine.com/en/fusion/v2/index.html";
public const string WindowTitle = "Photon Fusion 2 Hub";
public const string Support = "You can contact the Photon Team using one of the following links. You can also go to Photon Documentation in order to get started.";
public const string DiscordText = "Create a Photon account and join the Discord.";
public const string DiscordHeader = "Community";
public const string DocumentationText = "Open the documentation.";
public const string DocumentationHeader = "Documentation";
public const string WelcomeText = "Thank you for installing Photon Fusion 2.\n\n" +
"Once you have set up your Fusion 2 App Id, explore the sections on the left to get started. " +
"More samples, tutorials, and documentation are being added regularly - so check back often.";
public const string RealtimeAppidSetupInstructions =
@"<b>An Fusion App Id Version 2 is required for networking.</b>
To acquire an Fusion App Id:
- Open the Photon Dashboard (Log-in as required).
- Select an existing Fusion 2 App Id, or;
- Create a new one (make sure to select SDK Version 2).
- Copy the App Id and paste into the field below (or into the PhotonAppSettings.asset).
";
public const string GettingStartedInstructions =
@"Links to demos, tutorials, API references and other information can be found on the PhotonEngine.com website.";
}
public Texture2D SetupIcon;
public Texture2D DocumentationIcon;
public Texture2D SamplesIcon;
public Texture2D CommunityIcon;
public Texture2D ProductLogo;
public Texture2D PhotonCloudIcon;
public Texture2D FusionIcon;
public Texture2D CorrectIcon;
private Texture2D GetIcon(Icon icon) {
switch (icon) {
case Icon.Setup: return SetupIcon;
case Icon.Documentation: return DocumentationIcon;
case Icon.Samples: return SamplesIcon;
case Icon.Community: return CommunityIcon;
case Icon.ProductLogo: return ProductLogo;
case Icon.PhotonCloud: return PhotonCloudIcon;
case Icon.FusionIcon: return FusionIcon;
default: return null;
}
}
[NonSerialized] private Section[] _sections;
private static string releaseHistoryHeader;
private static List<string> releaseHistoryTextAdded;
private static List<string> releaseHistoryTextChanged;
private static List<string> releaseHistoryTextFixed;
private static List<string> releaseHistoryTextRemoved;
private static List<string> releaseHistoryTextInternal;
private static string fusionReleaseHistory;
public GUISkin FusionHubSkin;
private static GUIStyle _navbarHeaderGraphicStyle;
private static GUIStyle textLabelStyle;
private static GUIStyle headerLabelStyle;
private static GUIStyle releaseNotesStyle;
private static GUIStyle headerTextStyle;
private static GUIStyle buttonActiveStyle;
private bool InitContent() {
if (_ready.HasValue && _ready.Value) {
return _ready.Value;
}
// skip while being loaded
if (FusionHubSkin == null) { return false; }
_sections = new[] {
new Section("Welcome", "Welcome to Photon Fusion 2", DrawWelcomeSection, Icon.Setup), new Section("Fusion 2 Setup", "Setup Photon Fusion 2", DrawSetupSection, Icon.PhotonCloud),
new Section("Tutorials & Samples", "Fusion Tutorials and Samples", DrawSamplesSection, Icon.Samples),
new Section("Documentation", "Photon Fusion Documentation", DrawDocumentationSection, Icon.Documentation),
new Section("Fusion Release Notes", "Fusion Release Notes", DrawFusionReleaseSection, Icon.Documentation),
new Section("Support", "Support and Community Links", DrawSupportSection, Icon.Community),
};
Color commonTextColor = Color.white;
var _guiSkin = FusionHubSkin;
_navbarHeaderGraphicStyle = new GUIStyle(_guiSkin.button) { alignment = TextAnchor.MiddleCenter };
headerTextStyle = new GUIStyle(_guiSkin.label) { fontSize = 18, padding = new RectOffset(12, 8, 8, 8), fontStyle = FontStyle.Bold, normal = { textColor = commonTextColor } };
buttonActiveStyle = new GUIStyle(_guiSkin.button) { fontStyle = FontStyle.Bold, normal = { background = _guiSkin.button.active.background, textColor = Color.white } };
textLabelStyle = new GUIStyle(_guiSkin.label) { wordWrap = true, normal = { textColor = commonTextColor }, richText = true, };
headerLabelStyle = new GUIStyle(textLabelStyle) { fontSize = 15, };
releaseNotesStyle = new GUIStyle(textLabelStyle) { richText = true, };
return (_ready = true).Value;
}
private static Action OpenURL(string url, params object[] args) {
return () => {
if (args.Length > 0) {
url = string.Format(url, args);
}
Application.OpenURL(url);
};
}
protected static bool IsAppIdValid() {
if (PhotonAppSettings.TryGetGlobal(out var global) && Guid.TryParse(global.AppSettings.AppIdFusion, out var guid)) {
return true;
}
return false;
}
static string titleVersionReformat, sectionReformat, header1Reformat, header2Reformat, header3Reformat, classReformat;
void InitializeFormatters() {
titleVersionReformat = "<size=22><color=white>$1</color></size>";
sectionReformat = "<i><color=lightblue>$1</color></i>";
header1Reformat = "<size=22><color=white>$1</color></size>";
header2Reformat = "<size=18><color=white>$1</color></size>";
header3Reformat = "<b><color=#ffffaaff>$1</color></b>";
classReformat = "<color=#FFDDBB>$1</color>";
}
/// <summary>
/// Converts readme files into Unity RichText.
/// </summary>
private void PrepareReleaseHistoryText() {
if (sectionReformat == null || sectionReformat == "") {
InitializeFormatters();
}
// Fusion
{
try {
var filePath = BuildPath(Application.dataPath, "Photon", "Fusion", "release_history.txt");
var text = (TextAsset)AssetDatabase.LoadAssetAtPath(filePath, typeof(TextAsset));
var baseText = text.text;
// #
baseText = Regex.Replace(baseText, @"^# (.*)", titleVersionReformat);
baseText = Regex.Replace(baseText, @"(?<=\n)# (.*)", header1Reformat);
// ##
baseText = Regex.Replace(baseText, @"(?<=\n)## (.*)", header2Reformat);
// ###
baseText = Regex.Replace(baseText, @"(?<=\n)### (.*)", header3Reformat);
// **Changes**
baseText = Regex.Replace(baseText, @"(?<=\n)\*\*(.*)\*\*", sectionReformat);
// `Class`
baseText = Regex.Replace(baseText, @"\`([^\`]*)\`", classReformat);
fusionReleaseHistory = baseText;
} catch {
fusionReleaseHistory = "Unable to load Release History.";
}
}
// Realtime
{
try {
var filePath = BuildPath(Application.dataPath, "Photon", "PhotonRealtime", "Code", "changes-realtime.txt");
var text = (TextAsset)AssetDatabase.LoadAssetAtPath(filePath, typeof(TextAsset));
var baseText = text.text;
var regexVersion = new Regex(@"Version (\d+\.?)*", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Multiline);
var regexAdded = new Regex(@"\b(Added:)(.*)\b", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Multiline);
var regexChanged = new Regex(@"\b(Changed:)(.*)\b", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Multiline);
var regexUpdated = new Regex(@"\b(Updated:)(.*)\b", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Multiline);
var regexFixed = new Regex(@"\b(Fixed:)(.*)\b", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Multiline);
var regexRemoved = new Regex(@"\b(Removed:)(.*)\b", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Multiline);
var regexInternal = new Regex(@"\b(Internal:)(.*)\b", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Multiline);
var matches = regexVersion.Matches(baseText);
if (matches.Count > 0) {
var currentVersionMatch = matches[0];
var lastVersionMatch = currentVersionMatch.NextMatch();
if (currentVersionMatch.Success && lastVersionMatch.Success) {
Func<MatchCollection, List<string>> itemProcessor = (match) => {
List<string> resultList = new List<string>();
for (int index = 0; index < match.Count; index++) {
resultList.Add(match[index].Groups[2].Value.Trim());
}
return resultList;
};
string mainText = baseText.Substring(currentVersionMatch.Index + currentVersionMatch.Length,
lastVersionMatch.Index - lastVersionMatch.Length - 1).Trim();
releaseHistoryHeader = currentVersionMatch.Value.Trim();
releaseHistoryTextAdded = itemProcessor(regexAdded.Matches(mainText));
releaseHistoryTextChanged = itemProcessor(regexChanged.Matches(mainText));
releaseHistoryTextChanged.AddRange(itemProcessor(regexUpdated.Matches(mainText)));
releaseHistoryTextFixed = itemProcessor(regexFixed.Matches(mainText));
releaseHistoryTextRemoved = itemProcessor(regexRemoved.Matches(mainText));
releaseHistoryTextInternal = itemProcessor(regexInternal.Matches(mainText));
}
}
} catch {
releaseHistoryHeader = "\nPlease look the file changes-realtime.txt";
releaseHistoryTextAdded = new List<string>();
releaseHistoryTextChanged = new List<string>();
releaseHistoryTextFixed = new List<string>();
releaseHistoryTextRemoved = new List<string>();
releaseHistoryTextInternal = new List<string>();
}
}
}
public static bool Toggle(bool value) {
var toggle = new GUIStyle("Toggle") { margin = new RectOffset(), padding = new RectOffset() };
return EditorGUILayout.Toggle(value, toggle, GUILayout.Width(15));
}
private static string BuildPath(params string[] parts) {
var basePath = "";
foreach (var path in parts) {
basePath = Path.Combine(basePath, path);
}
return basePath.Replace(Application.dataPath, Path.GetFileName(Application.dataPath));
}
}
#endif
}
#endregion
#region Assets/Photon/Fusion/Editor/FusionInstaller.cs
namespace Fusion.Editor {
#if !FUSION_DEV
using System;
using System.IO;
using UnityEditor;
using UnityEditor.Build;
using UnityEditor.PackageManager;
using UnityEngine;
[InitializeOnLoad]
class FusionInstaller {
const string DEFINE_VERSION = "FUSION2";
const string DEFINE = "FUSION_WEAVER";
const string PACKAGE_TO_SEARCH = "nuget.mono-cecil";
const string PACKAGE_TO_INSTALL = "com.unity.nuget.mono-cecil@1.10.2";
const string PACKAGES_DIR = "Packages";
const string MANIFEST_FILE = "manifest.json";
static FusionInstaller() {
#if UNITY_SERVER
var defines = PlayerSettings.GetScriptingDefineSymbols(UnityEditor.Build.NamedBuildTarget.Server);
#else
var group = BuildPipeline.GetBuildTargetGroup(EditorUserBuildSettings.activeBuildTarget);
var defines = PlayerSettings.GetScriptingDefineSymbols(NamedBuildTarget.FromBuildTargetGroup(group));
#endif
// Check for Defines
// change based on https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2249
if (defines.Contains(DEFINE) && defines.Contains(DEFINE_VERSION)) {
return;
}
if (!PlayerSettings.runInBackground) {
FusionEditorLog.LogInstaller($"Setting {nameof(PlayerSettings)}.{nameof(PlayerSettings.runInBackground)} to true");
PlayerSettings.runInBackground = true;
}
var manifest = Path.Combine(Path.GetDirectoryName(Application.dataPath) ?? string.Empty, PACKAGES_DIR, MANIFEST_FILE);
if (File.ReadAllText(manifest).Contains(PACKAGE_TO_SEARCH)) {
FusionEditorLog.LogInstaller($"Setting '{DEFINE}' & '{DEFINE_VERSION}' Define");
// append defines
if (defines.Contains(DEFINE) == false) { defines = $"{defines};{DEFINE}"; }
if (defines.Contains(DEFINE_VERSION) == false) { defines = $"{defines};{DEFINE_VERSION}"; }
#if UNITY_SERVER
PlayerSettings.SetScriptingDefineSymbols(UnityEditor.Build.NamedBuildTarget.Server, defines);
#else
PlayerSettings.SetScriptingDefineSymbols(NamedBuildTarget.FromBuildTargetGroup(group), defines);
#endif
} else {
FusionEditorLog.LogInstaller($"Installing '{PACKAGE_TO_INSTALL}' package");
Client.Add(PACKAGE_TO_INSTALL);
}
}
}
#endif
}
#endregion
#region Assets/Photon/Fusion/Editor/FusionSceneSetupAssistants.cs
namespace Fusion.Editor {
using UnityEditor;
using UnityEngine;
using UnityEngine.SceneManagement;
using System.Collections.Generic;
public static class FusionSceneSetupAssistants {
[MenuItem("Tools/Fusion/Scene/Setup Networking in the Scene", false, FusionAssistants.PRIORITY_LOW + 1)]
[MenuItem("GameObject/Fusion/Scene/Setup Networking in the Scene", false, FusionAssistants.PRIORITY + 1)]
public static void AddNetworkingToScene() {
(FusionBootstrap nds, NetworkRunner nr) n = AddNetworkStartup();
n.nr.gameObject.EnsureComponentExists<NetworkEvents>();
// Get scene and mark scene as dirty.
DirtyAndSaveScene(n.nds.gameObject.scene);
}
public static (FusionBootstrap, NetworkRunner) AddNetworkStartup() {
// Restrict to single AudioListener to disallow multiple active in shared instance mode (preventing log spam)
HandleAudioListeners();
// Restrict lights to single active instances node to Lights
HandleLights();
// Add NetworkDebugRunner if missing
var nds = FusionAssistants.EnsureExistsInScene<FusionBootstrap>("Prototype Network Start");
NetworkRunner nr = nds.RunnerPrefab == null ? null : nds.RunnerPrefab.TryGetComponent<NetworkRunner>(out var found) ? found : null;
// Add NetworkRunner to scene if the DebugStart doesn't have one as a prefab set already.
if (nr == null) {
// Add NetworkRunner to scene if NetworkDebugStart doesn't have one set as a prefab already.
nr = FusionAssistants.EnsureExistsInScene<NetworkRunner>("Prototype Runner");
nds.RunnerPrefab = nr;
// The runner go is also our fallback spawn point... so raise it into the air a bit
nr.transform.position = new Vector3(0, 3, 0);
}
return (nds, nr);
}
[MenuItem("Tools/Fusion/Scene/Add Current Scene To Build Settings", false, FusionAssistants.PRIORITY_LOW)]
[MenuItem("GameObject/Fusion/Scene/Add Current Scene To Build Settings", false, FusionAssistants.PRIORITY)]
public static void AddCurrentSceneToSettings() { DirtyAndSaveScene(SceneManager.GetActiveScene()); }
public static void DirtyAndSaveScene(Scene scene) {
UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(scene);
var scenename = scene.path;
// Give chance to save - required in order to build out. If users cancel will only be able to run in the editor.
if (scenename == "") {
UnityEditor.SceneManagement.EditorSceneManager.SaveModifiedScenesIfUserWantsTo(new Scene[] { scene });
scenename = scene.path;
}
// Add scene to Build and Fusion settings
if (scenename != "") {
scene.AddSceneToBuildSettings();
}
}
[MenuItem("Tools/Fusion/Scene/Setup Multi-Peer AudioListener Handling", false, FusionAssistants.PRIORITY_LOW + 1)]
[MenuItem("GameObject/Fusion/Scene/Setup Multi-Peer AudioListener Handling", false, FusionAssistants.PRIORITY + 1)]
public static void HandleAudioListeners() {
int count = 0;
foreach (var listener in Object.FindObjectsByType<AudioListener>(FindObjectsInactive.Exclude, FindObjectsSortMode.None)) {
count++;
listener.EnsureComponentHasVisibilityNode();
}
Debug.Log($"{count} {nameof(AudioListener)}(s) found and given a {nameof(RunnerVisibilityLink)} component.");
}
[MenuItem("Tools/Fusion/Scene/Setup Multi-Peer Lights Handling", false, FusionAssistants.PRIORITY_LOW + 1)]
[MenuItem("GameObject/Fusion/Scene/Setup Multi-Peer Lights Handling", false, FusionAssistants.PRIORITY + 1)]
public static void HandleLights() {
int count = 0;
foreach (var listener in Object.FindObjectsByType<Light>(FindObjectsInactive.Exclude, FindObjectsSortMode.None)) {
count++;
listener.EnsureComponentHasVisibilityNode();
}
Debug.Log($"{count} {nameof(Light)}(s) found and given a {nameof(RunnerVisibilityLink)} component.");
}
public static void AddSceneToBuildSettings(this Scene scene) {
var buildScenes = EditorBuildSettings.scenes;
bool isInBuildScenes = false;
foreach (var bs in buildScenes) {
if (bs.path == scene.path) {
isInBuildScenes = true;
break;
}
}
if (isInBuildScenes == false) {
var buildList = new List<EditorBuildSettingsScene>();
buildList.Add(new EditorBuildSettingsScene(scene.path, true));
buildList.AddRange(buildScenes);
Debug.Log($"Added '{scene.path}' as first entry in Build Settings.");
EditorBuildSettings.scenes = buildList.ToArray();
}
}
}
}
#endregion
#region Assets/Photon/Fusion/Editor/FusionUnitySurrogateBaseWrapper.cs
namespace Fusion.Editor {
using System;
using Internal;
using UnityEditor;
using UnityEngine;
internal class FusionUnitySurrogateBaseWrapper : ScriptableObject {
[SerializeReference]
public UnitySurrogateBase Surrogate;
[NonSerialized]
public SerializedProperty SurrogateProperty;
[NonSerialized]
public Type SurrogateType;
}
}
#endregion
#region Assets/Photon/Fusion/Editor/FusionWeaverTriggerImporter.cs
namespace Fusion.Editor {
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEditor.AssetImporters;
using UnityEngine;
[ScriptedImporter(1, ExtensionWithoutDot, NetworkProjectConfigImporter.ImportQueueOffset + 1)]
public class FusionWeaverTriggerImporter : ScriptedImporter {
public const string DependencyName = "FusionILWeaverTriggerImporter/ConfigHash";
public const string Extension = "." + ExtensionWithoutDot;
public const string ExtensionWithoutDot = "fusionweavertrigger";
public override void OnImportAsset(AssetImportContext ctx) {
ctx.DependsOnCustomDependency(DependencyName);
ILWeaverUtils.RunWeaver();
}
private static void RefreshDependencyHash() {
if (EditorApplication.isCompiling || EditorApplication.isUpdating) {
return;
}
var configPath = NetworkProjectConfigUtilities.GetGlobalConfigPath();
if (string.IsNullOrEmpty(configPath)) {
return;
}
try {
var cfg = NetworkProjectConfigImporter.LoadConfigFromFile(configPath);
var hash = new Hash128();
foreach (var path in cfg.AssembliesToWeave) {
hash.Append(path);
}
hash.Append(cfg.UseSerializableDictionary ? 1 : 0);
hash.Append(cfg.NullChecksForNetworkedProperties ? 1 : 0);
hash.Append(cfg.CheckRpcAttributeUsage ? 1 : 0);
hash.Append(cfg.CheckNetworkedPropertiesBeingEmpty ? 1 : 0);
AssetDatabase.RegisterCustomDependency(DependencyName, hash);
AssetDatabase.Refresh();
} catch {
// ignore the error
}
}
private class Postprocessor : AssetPostprocessor {
private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths) {
foreach (var path in importedAssets) {
if (path.EndsWith(NetworkProjectConfigImporter.Extension)) {
EditorApplication.delayCall -= RefreshDependencyHash;
EditorApplication.delayCall += RefreshDependencyHash;
}
}
}
}
}
}
#endregion
#region Assets/Photon/Fusion/Editor/ILWeaverUtils.cs
namespace Fusion.Editor {
using UnityEditor;
using UnityEditor.Compilation;
[InitializeOnLoad]
public static class ILWeaverUtils {
[MenuItem("Tools/Fusion/Run Weaver")]
public static void RunWeaver() {
CompilationPipeline.RequestScriptCompilation(
#if UNITY_2021_1_OR_NEWER
RequestScriptCompilationOptions.CleanBuildCache
#endif
);
}
}
}
#endregion
#region Assets/Photon/Fusion/Editor/NetworkBehaviourEditor.cs
namespace Fusion.Editor {
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;
[CustomEditor(typeof(NetworkBehaviour), true)]
[CanEditMultipleObjects]
public class NetworkBehaviourEditor : BehaviourEditor {
internal const string NETOBJ_REQUIRED_WARN_TEXT = "This <b>" + nameof(NetworkBehaviour) + "</b> requires a <b>" + nameof(NetworkObject) + "</b> component to function.";
IEnumerable<NetworkBehaviour> ValidTargets => targets
.Cast<NetworkBehaviour>()
.Where(x => x.Object && x.Object.IsValid && x.Object.gameObject.activeInHierarchy);
[NonSerialized]
int[] _buffer = Array.Empty<int>();
public override void OnInspectorGUI() {
base.PrepareOnInspectorGUI();
bool hasBeenApplied = false;
#if !FUSION_DISABLE_NBEDITOR_PRESERVE_BACKING_FIELDS
// serialize unchanged serialized state into zero-initialized memory;
// this makes sure defaults are preserved
TransferBackingFields(backingFieldsToState: true);
#endif
try {
// after the original values have been saved, they can be overwritten with
// whatever is in the state
foreach (var target in ValidTargets) {
target.CopyStateToBackingFields();
}
// move C# fields to SerializedObject
serializedObject.UpdateIfRequiredOrScript();
EditorGUI.BeginChangeCheck();
base.DrawDefaultInspector();
if (EditorGUI.EndChangeCheck()) {
// serialized properties -> C# fields
serializedObject.ApplyModifiedProperties();
hasBeenApplied = true;
// C# fields -> state
foreach (var target in ValidTargets) {
if (target.Object.HasStateAuthority) {
target.CopyBackingFieldsToState(false);
}
}
}
} finally {
#if !FUSION_DISABLE_NBEDITOR_PRESERVE_BACKING_FIELDS
// now restore the default values
TransferBackingFields(backingFieldsToState: false);
serializedObject.Update();
if (hasBeenApplied) {
serializedObject.ApplyModifiedProperties();
}
}
#endif
DrawNetworkObjectCheck();
DrawEditorButtons();
}
unsafe bool TransferBackingFields(bool backingFieldsToState) {
if (Allocator.REPLICATE_WORD_SIZE == sizeof(int)) {
int offset = 0;
bool hadChanges = false;
int requiredSize = ValidTargets.Sum(x => x.WordCount);
if (backingFieldsToState) {
if (_buffer.Length >= requiredSize) {
Array.Clear(_buffer, 0, _buffer.Length);
} else {
_buffer = new int[requiredSize];
}
} else {
if (_buffer.Length < requiredSize) {
throw new InvalidOperationException("Buffer is too small");
}
}
fixed (int* p = _buffer) {
foreach (var target in ValidTargets) {
var ptr = target.Ptr;
try {
target.Ptr = p + offset;
if (backingFieldsToState) {
target.CopyBackingFieldsToState(false);
} else {
target.CopyStateToBackingFields();
}
if (!hadChanges) {
if (Native.MemCmp(target.Ptr, ptr, target.WordCount * Allocator.REPLICATE_WORD_SIZE) != 0) {
hadChanges = true;
}
}
} finally {
target.Ptr = ptr;
}
offset += target.WordCount;
}
}
return hadChanges;
}
}
/// <summary>
/// Checks if GameObject or parent GameObject has a NetworkObject, and draws a warning and buttons for adding one if not.
/// </summary>
/// <param name="nb"></param>
void DrawNetworkObjectCheck() {
var targetsWithoutNetworkObjects = targets.Cast<NetworkBehaviour>().Where(x => x.transform.GetParentComponent<NetworkObject>() == false).ToList();
if (targetsWithoutNetworkObjects.Any()) {
using (new FusionEditorGUI.WarningScope(NETOBJ_REQUIRED_WARN_TEXT, 6f)) {
IEnumerable<GameObject> gameObjects = null;
if (GUI.Button(EditorGUILayout.GetControlRect(false, 22), "Add Network Object")) {
gameObjects = targetsWithoutNetworkObjects.Select(x => x.gameObject).Distinct();
}
if (GUI.Button(EditorGUILayout.GetControlRect(false, 22), "Add Network Object to Root")) {
gameObjects = targetsWithoutNetworkObjects.Select(x => x.transform.root.gameObject).Distinct();
}
if (gameObjects != null) {
foreach (var go in gameObjects) {
Undo.AddComponent<NetworkObject>(go);
}
}
}
}
}
}
}
#endregion
#region Assets/Photon/Fusion/Editor/NetworkMecanimAnimatorEditor.cs
namespace Fusion.Editor {
using UnityEditor;
[CustomEditor(typeof(NetworkMecanimAnimator))]
public class NetworkMecanimAnimatorEditor : BehaviourEditor {
public override void OnInspectorGUI() {
var na = target as NetworkMecanimAnimator;
if (na != null) {
AnimatorControllerTools.GetHashesAndNames(na, null, null, ref na.TriggerHashes, ref na.StateHashes);
na.TotalWords = na.GetWordCount().words;
}
base.OnInspectorGUI();
}
}
}
#endregion
#region Assets/Photon/Fusion/Editor/NetworkObjectBakerEditTime.cs
namespace Fusion.Editor {
using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
public class NetworkObjectBakerEditTime : NetworkObjectBaker {
private Dictionary<Type, int?> _executionOrderCache = new Dictionary<Type, int?>();
protected override bool TryGetExecutionOrder(MonoBehaviour obj, out int order) {
// is there a cached value?
if (_executionOrderCache.TryGetValue(obj.GetType(), out var orderNullable)) {
order = orderNullable ?? default;
return orderNullable != null;
}
var monoScript = UnityEditor.MonoScript.FromMonoBehaviour(obj);
if (monoScript) {
orderNullable = UnityEditor.MonoImporter.GetExecutionOrder(monoScript);
} else {
orderNullable = null;
}
_executionOrderCache.Add(obj.GetType(), orderNullable);
order = orderNullable ?? default;
return orderNullable != null;
}
protected override void SetDirty(MonoBehaviour obj) {
EditorUtility.SetDirty(obj);
}
protected override uint GetSortKey(NetworkObject obj) {
var globalId = GlobalObjectId.GetGlobalObjectIdSlow(obj);
int hash = 0;
hash = HashCodeUtilities.GetHashCodeDeterministic(globalId.identifierType, hash);
hash = HashCodeUtilities.GetHashCodeDeterministic(globalId.assetGUID, hash);
hash = HashCodeUtilities.GetHashCodeDeterministic(globalId.targetObjectId, hash);
hash = HashCodeUtilities.GetHashCodeDeterministic(globalId.targetPrefabId, hash);
return (uint)hash;
}
}
}
#endregion
#region Assets/Photon/Fusion/Editor/NetworkObjectEditor.cs
namespace Fusion.Editor {
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;
#if UNITY_2021_2_OR_NEWER
using UnityEditor.SceneManagement;
#else
using UnityEditor.Experimental.SceneManagement;
#endif
[CustomEditor(typeof(NetworkObject), true)]
[InitializeOnLoad]
[CanEditMultipleObjects]
public unsafe class NetworkObjectEditor : BehaviourEditor {
private bool _runtimeInfoFoldout;
private static PropertyInfo _isSpawnable = typeof(NetworkObject).GetPropertyOrThrow(nameof(NetworkObject.IsSpawnable));
private static FieldInfo _networkTypeId = typeof(NetworkObject).GetFieldOrThrow(nameof(NetworkObject.NetworkTypeId));
private static PropertyInfo _networkId = typeof(NetworkObject).GetPropertyOrThrow<NetworkId>(nameof(NetworkObject.Id));
private static FieldInfo _nestingRoot = typeof(NetworkObjectHeader).GetFieldOrThrow(nameof(NetworkObjectHeader.NestingRoot));
private static FieldInfo _nestingKey = typeof(NetworkObjectHeader).GetFieldOrThrow(nameof(NetworkObjectHeader.NestingKey));
private static PropertyInfo _InputAuthority = typeof(NetworkObject).GetPropertyOrThrow(nameof(NetworkObject.InputAuthority));
private static PropertyInfo _StateAuthority = typeof(NetworkObject).GetPropertyOrThrow(nameof(NetworkObject.StateAuthority));
private static PropertyInfo _HasInputAuthority = typeof(NetworkObject).GetPropertyOrThrow(nameof(NetworkObject.HasInputAuthority));
private static PropertyInfo _HasStateAuthority = typeof(NetworkObject).GetPropertyOrThrow(nameof(NetworkObject.HasStateAuthority));
static string GetLoadInfoString(NetworkObjectGuid guid) {
if (NetworkProjectConfigUtilities.TryGetGlobalPrefabSource(guid, out INetworkPrefabSource prefabSource)) {
return prefabSource.Description;
}
return "Null";
}
public override void OnInspectorGUI() {
FusionEditorGUI.InjectScriptHeaderDrawer(serializedObject);
FusionEditorGUI.ScriptPropertyField(serializedObject);
// these properties' isExpanded are going to be used for foldouts; that's the easiest
// way to get quasi-persistent foldouts
var flagsProperty = serializedObject.FindPropertyOrThrow(nameof(NetworkObject.Flags));
var obj = (NetworkObject)base.target;
var netObjType = typeof(NetworkObject);
if (targets.Length == 1) {
if (AssetDatabase.IsMainAsset(obj.gameObject) || PrefabStageUtility.GetPrefabStage(obj.gameObject)?.prefabContentsRoot == obj.gameObject) {
Debug.Assert(!AssetDatabaseUtils.IsSceneObject(obj.gameObject));
if (!obj.Flags.IsVersionCurrent()) {
using (new FusionEditorGUI.WarningScope("Prefab needs to be re-imported.")) {
if (GUILayout.Button("Reimport")) {
string assetPath = PrefabStageUtility.GetPrefabStage(obj.gameObject)?.assetPath ?? AssetDatabase.GetAssetPath(obj.gameObject);
Debug.Assert(!string.IsNullOrEmpty(assetPath));
AssetDatabase.ImportAsset(assetPath);
}
}
} else {
EditorGUILayout.Space();
EditorGUILayout.LabelField("Prefab Settings", EditorStyles.boldLabel);
// Is Spawnable
{
EditorGUI.BeginChangeCheck();
bool spawnable = EditorGUI.Toggle(FusionEditorGUI.LayoutHelpPrefix(this, _isSpawnable), _isSpawnable.Name, !obj.Flags.IsIgnored());
if (EditorGUI.EndChangeCheck()) {
var value = obj.Flags.SetIgnored(!spawnable);
serializedObject.FindProperty(nameof(NetworkObject.Flags)).intValue = (int)value;
serializedObject.ApplyModifiedProperties();
}
string loadInfo = "---";
if (spawnable) {
string assetPath = PrefabStageUtility.GetPrefabStage(obj.gameObject)?.assetPath ?? AssetDatabase.GetAssetPath(obj.gameObject);
if (!string.IsNullOrEmpty(assetPath)) {
var guid = AssetDatabase.AssetPathToGUID(assetPath);
loadInfo = GetLoadInfoString(NetworkObjectGuid.Parse(guid));
}
}
EditorGUILayout.LabelField("Prefab Source", loadInfo);
}
}
} else if (AssetDatabaseUtils.IsSceneObject(obj.gameObject)) {
if (!obj.Flags.IsVersionCurrent()) {
if (!EditorApplication.isPlaying) {
using (new FusionEditorGUI.WarningScope("This object hasn't been baked yet. Save the scene or enter playmode.")) {
}
}
}
}
}
if (EditorApplication.isPlaying && targets.Length == 1) {
EditorGUILayout.Space();
flagsProperty.isExpanded = EditorGUILayout.Foldout(flagsProperty.isExpanded, "Runtime Info");
if (flagsProperty.isExpanded) {
using (new FusionEditorGUI.BoxScope(null, 1)) {
EditorGUI.LabelField(FusionEditorGUI.LayoutHelpPrefix(this, _networkTypeId), _networkTypeId.Name, obj.NetworkTypeId.ToString());
EditorGUILayout.Toggle("Is Valid", obj.IsValid);
if (obj.IsValid) {
EditorGUI.LabelField(FusionEditorGUI.LayoutHelpPrefix(this, _networkId), _networkId.Name, obj.Id.ToString());
EditorGUILayout.IntField("Word Count", NetworkObject.GetWordCount(obj));
bool headerIsNull = obj.Header == null;
EditorGUI.LabelField(FusionEditorGUI.LayoutHelpPrefix(this, _nestingRoot), _nestingRoot.Name, headerIsNull ? "---" : obj.Header->NestingRoot.ToString());
EditorGUI.LabelField(FusionEditorGUI.LayoutHelpPrefix(this, _nestingKey), _nestingKey.Name, headerIsNull ? "---" : obj.Header->NestingKey.ToString());
EditorGUI.LabelField(FusionEditorGUI.LayoutHelpPrefix(this, _InputAuthority), _InputAuthority.Name, obj.InputAuthority.ToString());
EditorGUI.LabelField(FusionEditorGUI.LayoutHelpPrefix(this, _StateAuthority), _StateAuthority.Name, obj.StateAuthority.ToString());
EditorGUI.Toggle(FusionEditorGUI.LayoutHelpPrefix(this, _HasInputAuthority), _InputAuthority.Name, obj.HasInputAuthority);
EditorGUI.Toggle(FusionEditorGUI.LayoutHelpPrefix(this, _HasStateAuthority), _StateAuthority.Name, obj.HasStateAuthority);
EditorGUILayout.Toggle("Is Simulated", obj.Runner.Simulation.IsSimulated(obj));
EditorGUILayout.Toggle("Is Local PlayerObject", ReferenceEquals(obj.Runner.GetPlayerObject(obj.Runner.LocalPlayer), obj));
EditorGUILayout.Toggle("Has Main TRSP", obj.Meta.HasMainTRSP);
EditorGUILayout.LabelField("Runtime Flags", obj.RuntimeFlags.ToString());
EditorGUILayout.LabelField("Header Flags", obj.Header->Flags.ToString());
if (obj.Runner.IsClient) {
EditorGUILayout.IntField("Last Received Tick", obj.LastReceiveTick);
}
}
}
}
}
EditorGUI.BeginChangeCheck();
var config = NetworkProjectConfig.Global;
var isPlaying = EditorApplication.isPlaying;
void DrawToggleFlag(NetworkObjectFlags flag, string name, bool? force = null) {
var x = (obj.Flags & flag) == flag;
var r = EditorGUILayout.Toggle(name, x);
if (r != x || (force.HasValue && r != force.Value)) {
if (force.HasValue) {
r = force.Value;
}
if (r) {
obj.Flags |= flag;
} else {
obj.Flags &= ~flag;
}
EditorUtility.SetDirty(obj);
}
}
using (new EditorGUI.DisabledScope(isPlaying)) {
EditorGUILayout.Space();
EditorGUILayout.LabelField("Shared Mode Settings", EditorStyles.boldLabel);
DrawToggleFlag(NetworkObjectFlags.MasterClientObject, "Is Master Client Object");
EditorGUI.BeginDisabledGroup((obj.Flags & NetworkObjectFlags.MasterClientObject) == NetworkObjectFlags.MasterClientObject);
if ((obj.Flags & NetworkObjectFlags.MasterClientObject) == NetworkObjectFlags.MasterClientObject) {
DrawToggleFlag(NetworkObjectFlags.AllowStateAuthorityOverride, "Allow State Authority Override", false);
} else {
DrawToggleFlag(NetworkObjectFlags.AllowStateAuthorityOverride, "Allow State Authority Override");
}
EditorGUI.EndDisabledGroup();
EditorGUI.BeginDisabledGroup((obj.Flags & NetworkObjectFlags.AllowStateAuthorityOverride) == default);
if ((obj.Flags & NetworkObjectFlags.MasterClientObject) == NetworkObjectFlags.MasterClientObject) {
DrawToggleFlag(NetworkObjectFlags.DestroyWhenStateAuthorityLeaves, "Destroy When State Authority Leaves", false);
} else {
if ((obj.Flags & NetworkObjectFlags.AllowStateAuthorityOverride) == NetworkObjectFlags.AllowStateAuthorityOverride) {
DrawToggleFlag(NetworkObjectFlags.DestroyWhenStateAuthorityLeaves, "Destroy When State Authority Leaves");
} else {
DrawToggleFlag(NetworkObjectFlags.DestroyWhenStateAuthorityLeaves, "Destroy When State Authority Leaves", true);
}
}
EditorGUI.EndDisabledGroup();
//var destroyWhenStateAuthLeaves = serializedObject.FindProperty(nameof(NetworkObject.DestroyWhenStateAuthorityLeaves));
//EditorGUILayout.PropertyField(destroyWhenStateAuthLeaves);
//
//var allowStateAuthorityOverride = serializedObject.FindProperty(nameof(NetworkObject.AllowStateAuthorityOverride));
//EditorGUILayout.PropertyField(allowStateAuthorityOverride);
EditorGUILayout.Space();
EditorGUILayout.LabelField("Interest Management Settings", EditorStyles.boldLabel);
var objectInterest = serializedObject.FindProperty(nameof(NetworkObject.ObjectInterest));
EditorGUILayout.PropertyField(objectInterest);
if (objectInterest.intValue == (int)NetworkObject.ObjectInterestModes.AreaOfInterest) {
//EditorGUILayout.PropertyField(serializedObject.FindProperty(nameof(NetworkObject.AreaOfInterestTransform)));
}
//using (new EditorGUI.IndentLevelScope()) {
// EditorGUILayout.PropertyField(serializedObject.FindPropertyOrThrow(nameof(NetworkObject.DefaultInterestGroups)));
//}
}
if (EditorGUI.EndChangeCheck()) {
serializedObject.ApplyModifiedProperties();
}
EditorGUILayout.Space();
EditorGUI.DrawRect(EditorGUILayout.GetControlRect(false, 1), Color.gray);
EditorGUILayout.Space();
EditorGUILayout.LabelField("Baked Data", EditorStyles.boldLabel);
using (new FusionEditorGUI.BoxScope(null, 1)) {
using (new EditorGUI.DisabledScope(true)) {
using (new FusionEditorGUI.ShowMixedValueScope(flagsProperty.hasMultipleDifferentValues)) {
FusionEditorGUI.LayoutSelectableLabel(EditorGUIUtility.TrTextContent(nameof(obj.Flags)), obj.Flags.ToString());
FusionEditorGUI.LayoutSelectableLabel(EditorGUIUtility.TrTextContent(nameof(obj.SortKey)), obj.SortKey.ToString("X8"));
}
using (new EditorGUI.IndentLevelScope()) {
EditorGUILayout.PropertyField(serializedObject.FindPropertyOrThrow(nameof(NetworkObject.NestedObjects)));
EditorGUILayout.PropertyField(serializedObject.FindPropertyOrThrow(nameof(NetworkObject.NetworkedBehaviours)));
}
}
}
// Runtime buttons
if (obj.Runner && obj.Runner.IsRunning) {
EditorGUILayout.Space();
EditorGUI.DrawRect(EditorGUILayout.GetControlRect(false, 1), Color.gray);
EditorGUILayout.Space();
// Input Authority Popup
using (new EditorGUI.DisabledScope(obj.HasStateAuthority == false)) {
var elements = GetInputAuthorityPopupContent(obj);
var index = EditorGUILayout.Popup(_guiContentInputAuthority, elements.currentIndex, elements.content);
if (index != elements.currentIndex) {
obj.AssignInputAuthority(PlayerRef.FromIndex(elements.ids[index]));
}
}
if (obj.Runner.GameMode == GameMode.Shared) {
if (GUILayout.Button("Request State Authority")) {
obj.RequestStateAuthority();
}
}
if (GUILayout.Button("Despawn")) {
obj.Runner.Despawn(obj);
}
}
}
private static bool Set<T>(UnityEngine.Object host, ref T field, T value, Action<object> setDirty) {
if (!EqualityComparer<T>.Default.Equals(field, value)) {
Trace($"Object dirty: {host} ({field} vs {value})");
setDirty?.Invoke(host);
field = value;
return true;
} else {
return false;
}
}
private static bool Set<T>(UnityEngine.Object host, ref T[] field, List<T> value, Action<object> setDirty) {
var comparer = EqualityComparer<T>.Default;
if (field == null || field.Length != value.Count || !field.SequenceEqual(value, comparer)) {
Trace($"Object dirty: {host} ({field} vs {value})");
setDirty?.Invoke(host);
field = value.ToArray();
return true;
} else {
return false;
}
}
[System.Diagnostics.Conditional("FUSION_EDITOR_TRACE")]
private static void Trace(string msg) {
Debug.Log($"[Fusion/NetworkObjectEditor] {msg}");
}
public static NetworkObjectGuid GetPrefabGuid(NetworkObject prefab) {
if (prefab == null) {
throw new ArgumentNullException(nameof(prefab));
}
if (!AssetDatabase.TryGetGUIDAndLocalFileIdentifier(prefab, out var guidStr, out long _)) {
throw new ArgumentException($"No guid for {prefab}", nameof(prefab));
}
return NetworkObjectGuid.Parse(guidStr);
}
private static GUIContent[] _reusableContent;
private static int[] _reusablePlayerIds;
private static readonly GUIContent _guiContentEmpty = new GUIContent("");
private static readonly GUIContent _guiContentNone = new GUIContent("None");
private static readonly GUIContent _guiContentInputAuthority = new GUIContent("Input Authority");
private static (int[] ids, GUIContent[] content, int currentIndex) GetInputAuthorityPopupContent(NetworkObject obj) {
int requiredLength = obj.Runner.ActivePlayers.Count() + 2;
if (_reusableContent == null || requiredLength > _reusableContent.Length) {
_reusablePlayerIds = new int[requiredLength];
_reusablePlayerIds[0] = -1;
_reusablePlayerIds[1] = 0;
_reusableContent = new GUIContent[requiredLength];
_reusableContent[0] = _guiContentNone;
_reusableContent[1] = _guiContentEmpty;
}
int indexOfCurrentPlayer = 0;
// clear
for (int i = 2; i < _reusableContent.Length; i++) {
_reusableContent[i] = _guiContentEmpty;
}
int index = 2;
foreach (var player in obj.Runner.ActivePlayers) {
_reusablePlayerIds[index] = player.PlayerId;
_reusableContent[index] = new GUIContent($"Player {player.PlayerId}");
if (player.PlayerId == obj.InputAuthority.PlayerId) {
indexOfCurrentPlayer = index;
}
index++;
}
return (_reusablePlayerIds, _reusableContent, indexOfCurrentPlayer);
}
}
}
#endregion
#region Assets/Photon/Fusion/Editor/NetworkObjectPostprocessor.cs
namespace Fusion.Editor {
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEditor.Build;
using UnityEditor.Build.Reporting;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;
public class NetworkObjectPostprocessor : AssetPostprocessor {
public static event Action<NetworkObjectBakePrefabArgs> OnBakePrefab;
public static event Action<NetworkObjectBakeSceneArgs> OnBakeScene;
static NetworkObjectPostprocessor() {
EditorSceneManager.sceneSaving += OnSceneSaving;
EditorApplication.playModeStateChanged += OnPlaymodeChange;
}
static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths) {
FusionEditorLog.TraceImport($"Postprocessing imported assets [{importedAssets.Length}]:\n{string.Join("\n", importedAssets)}");
bool rebuildPrefabHash = false;
foreach (var path in importedAssets) {
if (!IsPrefabPath(path)) {
continue;
}
var go = AssetDatabase.LoadAssetAtPath<GameObject>(path);
if (!go) {
continue;
}
var isSpawnable = false;
var needsBaking = false;
var no = go.GetComponent<NetworkObject>();
if (no) {
// NO prefab, needs labels adjusted and hash needs to be rebuilt
rebuildPrefabHash = true;
needsBaking = true;
isSpawnable = !no.Flags.IsIgnored();
}
if (AssetDatabaseUtils.SetLabel(go, NetworkProjectConfigImporter.FusionPrefabTag, isSpawnable)) {
rebuildPrefabHash = true;
AssetDatabase.ImportAsset(path);
FusionEditorLog.TraceImport(path, "Labels were dirty");
} else if (no) {
FusionEditorLog.TraceImport(path, "Labels up to date");
}
if (needsBaking) {
#if UNITY_2023_1_OR_NEWER || UNITY_2022_3_OR_NEWER
if (Array.IndexOf(movedAssets, path) >= 0) {
// attempting to bake a prefab that has been moved would hang the editor
// https://issuetracker.unity3d.com/issues/editor-freezes-when-prefabutility-dot-loadprefabcontents-is-called-in-assetpostprocessor-dot-onpostprocessallassets-for-a-moved-prefab
continue;
}
#endif
FusionEditorLog.TraceImport(path, "Baking");
BakePrefab(path, out _);
}
}
foreach (var path in movedAssets) {
if (!IsPrefabPath(path)) {
continue;
}
if (!AssetDatabaseUtils.HasLabel(path, NetworkProjectConfigImporter.FusionPrefabTag)) {
continue;
}
rebuildPrefabHash = true;
break;
}
foreach (var path in deletedAssets) {
if (!IsPrefabPath(path)) {
continue;
}
rebuildPrefabHash = true;
break;
}
if (rebuildPrefabHash) {
EditorApplication.delayCall -= NetworkProjectConfigImporter.RefreshNetworkObjectPrefabHash;
EditorApplication.delayCall += NetworkProjectConfigImporter.RefreshNetworkObjectPrefabHash;
}
}
static bool IsPrefabPath(string path) {
return path.EndsWith(".prefab");
}
static bool IsNetworkObjectPrefab(string path, out NetworkObject no) {
if (!path.EndsWith(".prefab")) {
// not a prefab
no = null;
return false;
}
var go = AssetDatabase.LoadAssetAtPath<GameObject>(path);
if (!go) {
no = null;
return false;
}
no = go.GetComponent<NetworkObject>();
return no;
}
void OnPostprocessPrefab(GameObject prefab) {
var no = prefab.GetComponent<NetworkObject>();
if (no && no.IsSpawnable) {
var existing = prefab.GetComponent<NetworkObjectPrefabData>();
if (existing != null) {
// this is likely a variant prefab, can't add the next one
// also, component loses hide flags at this point, so they need to be restored
// weirdly, this is the only case where altering a component in OnPostprocessPrefab works
// without causing an import warning
existing.Guid = NetworkObjectGuid.Parse(AssetDatabase.AssetPathToGUID(context.assetPath));
existing.hideFlags = HideFlags.DontSaveInEditor | HideFlags.HideInInspector | HideFlags.NotEditable;
} else {
var indirect = prefab.AddComponent<NetworkObjectPrefabData>();
indirect.Guid = NetworkObjectGuid.Parse(AssetDatabase.AssetPathToGUID(context.assetPath));
indirect.hideFlags |= HideFlags.HideInInspector | HideFlags.NotEditable;
}
}
}
static bool BakePrefab(string prefabPath, out GameObject root) {
root = null;
var assetGuid = AssetDatabase.AssetPathToGUID(prefabPath);
if (!NetworkObjectGuid.TryParse(assetGuid, out var guid)) {
FusionEditorLog.ErrorImport(prefabPath, $"Unable to parse guid: \"{assetGuid}\", not going to bake");
return false;
}
var stageGo = PrefabUtility.LoadPrefabContents(prefabPath);
if (!stageGo) {
FusionEditorLog.ErrorImport(prefabPath, $"Unable to load prefab contents");
return false;
}
var sw = System.Diagnostics.Stopwatch.StartNew();
try {
bool dirty = false;
bool baked = false;
if (OnBakePrefab != null) {
var args = new NetworkObjectBakePrefabArgs(_baker, stageGo, prefabPath);
OnBakePrefab(args);
if (args.Handled) {
baked = true;
dirty = args.IsPrefabDirty;
}
}
if (!baked) {
dirty = _baker.Bake(stageGo).HadChanges;
}
FusionEditorLog.TraceImport(prefabPath, $"Baking took {sw.Elapsed}, changed: {dirty}");
if (dirty) {
root = PrefabUtility.SaveAsPrefabAsset(stageGo, prefabPath);
}
return root;
} finally {
PrefabUtility.UnloadPrefabContents(stageGo);
}
}
private static NetworkObjectBaker _baker = new NetworkObjectBakerEditTime();
private static void OnPlaymodeChange(PlayModeStateChange change) {
if (change != PlayModeStateChange.ExitingEditMode) {
return;
}
for (int i = 0; i < EditorSceneManager.sceneCount; ++i) {
BakeScene(EditorSceneManager.GetSceneAt(i));
}
}
private static void OnSceneSaving(Scene scene, string path) {
BakeScene(scene);
}
[MenuItem("Tools/Fusion/Scene/Bake Scene Objects", false, FusionAssistants.PRIORITY_LOW - 1)]
[MenuItem("GameObject/Fusion/Scene/Bake Scene Objects", false, FusionAssistants.PRIORITY - 1)]
public static void BakeAllOpenScenes() {
for (int i = 0; i < SceneManager.sceneCount; ++i) {
var scene = SceneManager.GetSceneAt(i);
try {
BakeScene(scene);
} catch (Exception ex) {
Debug.LogError($"Failed to bake scene {scene}: {ex}");
}
}
}
public static void BakeScene(Scene scene) {
var sw = System.Diagnostics.Stopwatch.StartNew();
try {
if (OnBakeScene != null) {
var args = new NetworkObjectBakeSceneArgs(_baker, scene);
OnBakeScene(args);
if (args.Handled) {
return;
}
}
foreach (var root in scene.GetRootGameObjects()) {
_baker.Bake(root);
}
} finally {
FusionEditorLog.TraceImport(scene.path, $"Baking {scene} took: {sw.Elapsed}");
}
}
}
public class NetworkObjectBakePrefabArgs {
public bool IsPrefabDirty { get; set; }
public bool Handled { get; set; }
public GameObject LoadedPrefabRoot { get; }
public string Path { get; }
public NetworkObjectBaker Baker { get; }
public NetworkObjectBakePrefabArgs(NetworkObjectBaker baker, GameObject loadedPrefabRoot, string path) {
LoadedPrefabRoot = loadedPrefabRoot;
Path = path;
Baker = baker;
}
}
public class NetworkObjectBakeSceneArgs {
public bool Handled { get; set; }
public Scene Scene { get; }
public NetworkObjectBaker Baker { get; }
public NetworkObjectBakeSceneArgs(NetworkObjectBaker baker, Scene scene) {
Scene = scene;
Baker = baker;
}
}
}
#endregion
#region Assets/Photon/Fusion/Editor/NetworkPrefabsInspector.cs
namespace Fusion.Editor {
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
using Object = UnityEngine.Object;
public class NetworkPrefabsInspector : EditorWindow {
private Grid _grid = new Grid();
[MenuItem("Tools/Fusion/Windows/Network Prefabs Inspector")]
[MenuItem("Window/Fusion/Network Prefabs Inspector")]
public static void ShowWindow() {
var window = GetWindow<NetworkPrefabsInspector>(false, "Network Prefabs Inspector");
window.Show();
}
private void OnEnable() {
_grid.PrefabTable = NetworkProjectConfig.Global.PrefabTable;
_grid.OnEnable();
}
private void OnInspectorUpdate() {
_grid.OnInspectorUpdate();
}
private void OnGUI() {
using (new EditorGUILayout.HorizontalScope(EditorStyles.toolbar)) {
_grid.DrawToolbarReloadButton();
_grid.DrawToolbarSyncSelectionButton();
GUILayout.FlexibleSpace();
EditorGUI.BeginChangeCheck();
_grid.OnlyLoaded = GUILayout.Toggle(_grid.OnlyLoaded, "Loaded Only", EditorStyles.toolbarButton);
if (EditorGUI.EndChangeCheck()) {
_grid.ResetTree();
}
_grid.DrawToolbarSearchField();
}
var rect = GUILayoutUtility.GetRect(GUIContent.none, GUIStyle.none, GUILayout.ExpandHeight(true), GUILayout.ExpandWidth(true));
_grid.OnGUI(rect);
}
private enum LoadState {
NotLoaded,
Loading,
LoadedNoInstances,
Loaded
}
[Serializable]
private class InspectorTreeViewState : TreeViewState {
public MultiColumnHeaderState HeaderState;
public bool SyncSelection;
}
private class GridItem : FusionGridItem {
private readonly NetworkPrefabId _prefabId;
private readonly NetworkPrefabTable _prefabTable;
public GridItem(NetworkPrefabTable prefabTable, NetworkPrefabId prefabId) {
_prefabId = prefabId;
_prefabTable = prefabTable;
}
public int InstanceCount => _prefabTable.GetInstancesCount(_prefabId);
public string Path => AssetDatabase.GUIDToAssetPath(Guid);
public string Guid => Source?.AssetGuid.ToUnityGuidString() ?? "Null";
public override Object TargetObject {
get {
if (Source?.AssetGuid.IsValid == true) {
if (NetworkProjectConfigUtilities.TryGetPrefabEditorInstance(Source.AssetGuid, out var result)) {
return result.gameObject;
}
}
return null;
}
}
public INetworkPrefabSource Source => _prefabTable.GetSource(_prefabId);
public string Description {
get => Source?.Description ?? "Null";
}
public LoadState LoadState {
get {
if (!_prefabTable.IsAcquired(_prefabId)) {
return LoadState.NotLoaded;
}
if (!_prefabTable.GetSource(_prefabId).IsCompleted) {
return LoadState.Loading;
}
if (_prefabTable.GetInstancesCount(_prefabId) == 0) {
return LoadState.LoadedNoInstances;
}
return LoadState.Loaded;
}
}
public NetworkPrefabId PrefabId => _prefabId;
}
[Serializable]
class Grid : FusionGrid<GridItem> {
[SerializeField]
public NetworkPrefabTable PrefabTable;
[SerializeField]
public bool OnlyLoaded;
public override int GetContentHash() {
return PrefabTable?.Version ?? 0;
}
protected override IEnumerable<Column> CreateColumns() {
yield return new() {
headerContent = new GUIContent("State"),
width = 40,
autoResize = false,
cellGUI = (item, rect, _, _) => {
var icon = FusionEditorSkin.LoadStateIcon;
string label = "";
Color color;
switch (item.LoadState) {
case LoadState.Loaded:
color = Color.green;
label = item.InstanceCount.ToString();
break;
case LoadState.LoadedNoInstances:
color = Color.yellow;
label = "0";
break;
case LoadState.Loading:
color = Color.yellow;
color.a = 0.5f;
label = "0";
break;
default:
color = Color.gray;
break;
}
using (new FusionEditorGUI.ContentColorScope(color)) {
EditorGUI.LabelField(rect, new GUIContent(label, icon, item.LoadState.ToString()));
}
},
getComparer = order => (a, b) => {
var result = a.LoadState.CompareTo(b.LoadState) * order;
if (result != 0) {
return result;
}
return a.InstanceCount.CompareTo(b.InstanceCount) * order;
},
};
yield return new() {
headerContent = new GUIContent("Type"),
width = 40,
maxWidth = 40,
minWidth = 40,
cellGUI = (item, rect, _, _) => INetworkPrefabSourceDrawer.DrawThumbnail(rect, item.Source),
getComparer = order => (a, b) => EditorUtility.NaturalCompare(a.Source?.GetType().Name ?? "", b.Source?.GetType().Name ?? "") * order,
};
yield return MakeSimpleColumn(x => x.PrefabId, new() {
cellGUI = (item, rect, selected, focused) => TreeView.DefaultGUI.Label(rect, item.PrefabId.ToString(false, false), selected , focused),
width = 50,
autoResize = false
});
yield return MakeSimpleColumn(x => x.Path, new() {
initiallySorted = true,
});
yield return MakeSimpleColumn(x => x.Guid, new() {
initiallyVisible = false
});
yield return MakeSimpleColumn(x => x.Description, new() {
initiallyVisible = false
});
}
protected override IEnumerable<GridItem> CreateRows() {
if (PrefabTable == null) {
yield break;
}
for (int i = 0; i < PrefabTable.Prefabs.Count; ++i) {
var prefabId = NetworkPrefabId.FromIndex(i);
if (OnlyLoaded && !PrefabTable.IsAcquired(prefabId)) {
continue;
}
yield return new GridItem(PrefabTable, NetworkPrefabId.FromIndex(i)) { id = (int)(i + 1) };
}
}
protected override GenericMenu CreateContextMenu(GridItem item, TreeView treeView) {
var menu = new GenericMenu();
var selection = treeView.GetSelection()
.Select(x => NetworkPrefabId.FromIndex(x-1))
.ToList();
var anyLoaded = selection.Any(x => PrefabTable.IsAcquired(x));
var anyNotLoaded = selection.Any(x => !PrefabTable.IsAcquired(x));
var anyInstances = selection.Any(x => PrefabTable.GetInstancesCount(x) > 0);
var spawnerRunners = NetworkRunner.Instances.Where(x => x && x.IsRunning && x.CanSpawn).ToArray();
var loadContent = new GUIContent("Load");
var loadAsyncContent = new GUIContent("Load (async)");
var unloadContent = new GUIContent("Unload");
var selectInstancesContent = new GUIContent("Select Instances");
var spawnContent = new GUIContent("Spawn");
var spawnAsyncContent = new GUIContent("Spawn (async)");
if (anyNotLoaded) {
menu.AddItem(loadContent, false, () => {
foreach (var id in selection) {
PrefabTable.Load(id, isSynchronous: true);
}
});
menu.AddItem(loadAsyncContent, false, () => {
foreach (var id in selection) {
PrefabTable.Load(id, isSynchronous: false);
}
});
} else {
menu.AddDisabledItem(loadContent);
menu.AddDisabledItem(loadAsyncContent);
}
if (anyLoaded) {
menu.AddItem(unloadContent, false, () => {
foreach (var id in selection) {
PrefabTable.Unload(id);
}
});
} else {
menu.AddDisabledItem(unloadContent);
}
if (anyInstances) {
menu.AddItem(selectInstancesContent, false, () => {
var lookup = new HashSet<NetworkObjectTypeId>(selection.Select(x => NetworkObjectTypeId.FromPrefabId(x)));
Selection.objects = FindObjectsByType<NetworkObject>(FindObjectsInactive.Include, FindObjectsSortMode.None)
.Where(x => x.NetworkTypeId.IsValid && lookup.Contains(x.NetworkTypeId))
.Select(x => x.gameObject)
.ToArray();
});
} else {
menu.AddDisabledItem(selectInstancesContent);
}
menu.AddSeparator("");
if (spawnerRunners.Any()) {
if (spawnerRunners.Length > 1) {
foreach (var runner in spawnerRunners.Where(x => x.CanSpawn)) {
AddSpawnItems($"/{runner.name}", runner);
}
} else {
AddSpawnItems($"", spawnerRunners[0]);
}
} else {
menu.AddDisabledItem(spawnContent);
menu.AddDisabledItem(spawnAsyncContent);
}
void AddSpawnItems(string s, NetworkRunner networkRunner) {
menu.AddItem(new GUIContent($"{spawnContent.text}{s}"), false, () => {
foreach (var id in selection) {
networkRunner.TrySpawn(id, out _);
}
});
menu.AddItem(new GUIContent($"{spawnAsyncContent.text}{s}"), false, () => {
foreach (var id in selection) {
networkRunner.SpawnAsync(id);
}
});
}
return menu;
}
}
}
}
#endregion
#region Assets/Photon/Fusion/Editor/NetworkPrefabSourceFactories.cs
namespace Fusion.Editor {
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;
partial interface INetworkAssetSourceFactory {
INetworkPrefabSource TryCreatePrefabSource(in NetworkAssetSourceFactoryContext context);
}
public class NetworkAssetSourceFactory {
private readonly List<INetworkAssetSourceFactory> _factories = TypeCache.GetTypesDerivedFrom<INetworkAssetSourceFactory>()
.Select(x => (INetworkAssetSourceFactory)Activator.CreateInstance(x))
.OrderBy(x => x.Order)
.ToList();
public INetworkPrefabSource TryCreatePrefabSource(in NetworkAssetSourceFactoryContext context, bool removeFaultedFactories = true) {
for (int i = 0; i < _factories.Count; ++i) {
var factory = _factories[i];
try {
var source = factory.TryCreatePrefabSource(in context);
if (source != null) {
return source;
}
} catch (Exception ex) when(removeFaultedFactories) {
FusionEditorLog.Error($"Prefab source factory {factory.GetType().Name} failed for {context.AssetPath}. " +
$"This factory will be removed from the list of available factories during this import." +
$"Reimport of fix the underlying issue: {ex}");
}
}
return null;
}
}
partial class NetworkAssetSourceFactoryStatic {
public INetworkPrefabSource TryCreatePrefabSource(in NetworkAssetSourceFactoryContext context) {
if (TryCreateInternal<NetworkPrefabSourceStaticLazy, NetworkObject>(context, out var result)) {
result.AssetGuid = NetworkObjectGuid.Parse(context.AssetGuid);
};
return result;
}
}
partial class NetworkAssetSourceFactoryResource {
public INetworkPrefabSource TryCreatePrefabSource(in NetworkAssetSourceFactoryContext context) {
if (TryCreateInternal<NetworkPrefabSourceResource, NetworkObject>(context, out var result)) {
result.AssetGuid = NetworkObjectGuid.Parse(context.AssetGuid);
};
return result;
}
}
#if FUSION_ENABLE_ADDRESSABLES && !FUSION_DISABLE_ADDRESSABLES
partial class NetworkAssetSourceFactoryAddressable {
public INetworkPrefabSource TryCreatePrefabSource(in NetworkAssetSourceFactoryContext context) {
if (TryCreateInternal<NetworkPrefabSourceAddressable, NetworkObject>(context, out var result)) {
result.AssetGuid = NetworkObjectGuid.Parse(context.AssetGuid);
};
return result;
}
}
#endif
}
#endregion
#region Assets/Photon/Fusion/Editor/NetworkRunnerEditor.cs
namespace Fusion.Editor {
using System;
using System.Linq;
using System.Runtime.InteropServices;
using UnityEditor;
using UnityEngine;
[CustomEditor(typeof(NetworkRunner))]
public class NetworkRunnerEditor : BehaviourEditor {
void Label<T>(string label, T value) {
EditorGUILayout.LabelField(label, (value != null ? value.ToString() : "null"));
}
public override void OnInspectorGUI() {
base.OnInspectorGUI();
var runner = target as NetworkRunner;
if (runner && EditorApplication.isPlaying) {
Label("State", runner.IsRunning ? "Running" : (runner.IsShutdown ? "Shutdown" : "None"));
if (runner.IsRunning) {
Label("Game Mode", runner.GameMode);
Label("Simulation Mode", runner.Mode);
Label("Is Player", runner.IsPlayer);
Label("Local Player", runner.LocalPlayer);
Label("Has Connection Token?", runner.GetPlayerConnectionToken() != null);
var localplayerobj = runner.LocalPlayer.IsRealPlayer ? runner.GetPlayerObject(runner.LocalPlayer) : null;
EditorGUILayout.ObjectField("Local PlayerObject", localplayerobj, typeof(NetworkObject), true);
Label("Is SinglePlayer", runner.IsSinglePlayer);
if (runner.TryGetSceneInfo(out var sceneInfo)) {
Label("Scene Info", sceneInfo);
} else {
Label("Scene Info", $"Invalid");
}
var playerCount = runner.ActivePlayers.Count();
Label("Active Players", playerCount);
if (runner.IsServer && playerCount > 0) {
foreach (var item in runner.ActivePlayers) {
// skip local player
if (runner.LocalPlayer == item) { continue; }
Label("Player:PlayerId", item.PlayerId);
Label("Player:ConnectionType", runner.GetPlayerConnectionType(item));
Label("Player:UserId", runner.GetPlayerUserId(item));
Label("Player:RTT", runner.GetPlayerRtt(item));
}
}
if (runner.IsClient) {
Label("Is Connected To Server", runner.IsConnectedToServer);
Label("Current Connection Type", runner.CurrentConnectionType);
}
}
Label("Is Cloud Ready", runner.IsCloudReady);
if (runner.IsCloudReady) {
Label("Is Shared Mode Master Client", runner.IsSharedModeMasterClient);
Label("UserId", runner.UserId);
Label("AuthenticationValues", runner.AuthenticationValues);
Label("SessionInfo:IsValid", runner.SessionInfo.IsValid);
if (runner.SessionInfo.IsValid) {
Label("SessionInfo:Name", runner.SessionInfo.Name);
Label("SessionInfo:IsVisible", runner.SessionInfo.IsVisible);
Label("SessionInfo:IsOpen", runner.SessionInfo.IsOpen);
Label("SessionInfo:Region", runner.SessionInfo.Region);
}
Label("LobbyInfo:IsValid", runner.LobbyInfo.IsValid);
if (runner.LobbyInfo.IsValid) {
Label("LobbyInfo:Name", runner.LobbyInfo.Name);
Label("LobbyInfo:Region", runner.LobbyInfo.Region);
}
}
} else {
if (runner.TryGetComponent<RunnerEnableVisibility>(out var _) == false) {
EditorGUILayout.Space(2);
if (GUI.Button(EditorGUILayout.GetControlRect(), $"Add {nameof(RunnerEnableVisibility)}")) {
runner.gameObject.AddComponent<RunnerEnableVisibility>();
}
}
}
}
}
}
#endregion
#region Assets/Photon/Fusion/Editor/NetworkSceneDebugStartEditor.cs
// file deleted
#endregion
#region Assets/Photon/Fusion/Editor/NetworkTRSPEditor.cs
namespace Fusion.Editor {
using UnityEditor;
[CustomEditor(typeof(NetworkTRSP), true)]
public unsafe class NetworkTRSPEditor : NetworkBehaviourEditor {
public override void OnInspectorGUI() {
base.OnInspectorGUI();
var t = (NetworkTRSP)target;
using (new EditorGUI.DisabledScope(true)) {
if (t && t.StateBufferIsValid && t.CanReceiveRenderCallback) {
var found = t.Runner.TryFindObject(t.Data.Parent.Object, out var parent);
EditorGUILayout.LabelField("Parent", $"'{(found ? parent.name : "Not Available")}' : {t.Data.Parent.Object.ToString()}");
}
}
}
}
}
#endregion
#region Assets/Photon/Fusion/Editor/PhotonAppSettingsEditor.cs
namespace Fusion.Editor {
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using Photon.Realtime;
[CustomEditor(typeof(PhotonAppSettings))]
public class PhotonAppSettingsEditor : Editor {
public override void OnInspectorGUI() {
FusionEditorGUI.InjectScriptHeaderDrawer(serializedObject);
base.DrawDefaultInspector();
}
[MenuItem("Tools/Fusion/Realtime Settings", priority = 200)]
public static void PingNetworkProjectConfigAsset() {
EditorGUIUtility.PingObject(PhotonAppSettings.Global);
Selection.activeObject = PhotonAppSettings.Global;
}
}
}
#endregion
#region Assets/Photon/Fusion/Editor/Utilities/AnimatorControllerTools.cs
// ---------------------------------------------------------------------------------------------
// <copyright>PhotonNetwork Framework for Unity - Copyright (C) 2020 Exit Games GmbH</copyright>
// <author>developer@exitgames.com</author>
// ---------------------------------------------------------------------------------------------
namespace Fusion.Editor {
using System.Collections.Generic;
using UnityEngine;
using UnityEditor.Animations;
using UnityEditor;
/// <summary>
/// Storage type for AnimatorController cached transition data, which is a bit different than basic state hashes
/// </summary>
[System.Serializable]
public class TransitionInfo {
public int index;
public int hash;
public int state;
public int destination;
public float duration;
public float offset;
public bool durationIsFixed;
public TransitionInfo(int index, int hash, int state, int destination, float duration, float offset, bool durationIsFixed) {
this.index = index;
this.hash = hash;
this.state = state;
this.destination = destination;
this.duration = duration;
this.offset = offset;
this.durationIsFixed = durationIsFixed;
}
}
public static class AnimatorControllerTools {
//// Attach methods to Fusion.Runtime NetworkedAnimator
//[InitializeOnLoadMethod]
//public static void RegisterFusionDelegates() {
// NetworkedAnimator.GetWordCountDelegate = GetWordCount;
//}
private static AnimatorController GetController(this Animator a) {
RuntimeAnimatorController rac = a.runtimeAnimatorController;
AnimatorOverrideController overrideController = rac as AnimatorOverrideController;
/// recurse until no override controller is found
while (overrideController != null) {
rac = overrideController.runtimeAnimatorController;
overrideController = rac as AnimatorOverrideController;
}
return rac as AnimatorController;
}
private static void GetTriggerNames(this AnimatorController ctr, List<string> namelist) {
namelist.Clear();
foreach (var p in ctr.parameters)
if (p.type == AnimatorControllerParameterType.Trigger) {
if (namelist.Contains(p.name)) {
Debug.LogWarning("Identical Trigger Name Found. Check animator on '" + ctr.name + "' for repeated trigger names.");
} else
namelist.Add(p.name);
}
}
private static void GetTriggerNames(this AnimatorController ctr, List<int> hashlist) {
hashlist.Clear();
foreach (var p in ctr.parameters)
if (p.type == AnimatorControllerParameterType.Trigger) {
hashlist.Add(Animator.StringToHash(p.name));
}
}
/// ------------------------------ STATES --------------------------------------
private static void GetStatesNames(this AnimatorController ctr, List<string> namelist) {
namelist.Clear();
foreach (var l in ctr.layers) {
var states = l.stateMachine.states;
ExtractNames(ctr, l.name, states, namelist);
var substates = l.stateMachine.stateMachines;
ExtractSubNames(ctr, l.name, substates, namelist);
}
}
private static void ExtractSubNames(AnimatorController ctr, string path, ChildAnimatorStateMachine[] substates, List<string> namelist) {
foreach (var s in substates) {
var sm = s.stateMachine;
var subpath = path + "." + sm.name;
ExtractNames(ctr, subpath, s.stateMachine.states, namelist);
ExtractSubNames(ctr, subpath, s.stateMachine.stateMachines, namelist);
}
}
private static void ExtractNames(AnimatorController ctr, string path, ChildAnimatorState[] states, List<string> namelist) {
foreach (var st in states) {
string name = st.state.name;
string layerName = path + "." + st.state.name;
if (!namelist.Contains(name)) {
namelist.Add(name);
}
if (namelist.Contains(layerName)) {
Debug.LogWarning("Identical State Name <i>'" + st.state.name + "'</i> Found. Check animator on '" + ctr.name + "' for repeated State names as they cannot be used nor networked.");
} else
namelist.Add(layerName);
}
}
private static void GetStatesNames(this AnimatorController ctr, List<int> hashlist) {
hashlist.Clear();
foreach (var l in ctr.layers) {
var states = l.stateMachine.states;
ExtractHashes(ctr, l.name, states, hashlist);
var substates = l.stateMachine.stateMachines;
ExtractSubtHashes(ctr, l.name, substates, hashlist);
}
}
private static void ExtractSubtHashes(AnimatorController ctr, string path, ChildAnimatorStateMachine[] substates, List<int> hashlist) {
foreach (var s in substates) {
var sm = s.stateMachine;
var subpath = path + "." + sm.name;
ExtractHashes(ctr, subpath, sm.states, hashlist);
ExtractSubtHashes(ctr, subpath, sm.stateMachines, hashlist);
}
}
private static void ExtractHashes(AnimatorController ctr, string path, ChildAnimatorState[] states, List<int> hashlist) {
foreach (var st in states) {
int hash = Animator.StringToHash(st.state.name);
string fullname = path + "." + st.state.name;
int layrhash = Animator.StringToHash(fullname);
if (!hashlist.Contains(hash)) {
hashlist.Add(hash);
}
if (hashlist.Contains(layrhash)) {
Debug.LogWarning("Identical State Name <i>'" + st.state.name + "'</i> Found. Check animator on '" + ctr.name + "' for repeated State names as they cannot be used nor networked.");
} else
hashlist.Add(layrhash);
}
}
//public static void GetTransitionNames(this AnimatorController ctr, List<string> transInfo)
//{
// transInfo.Clear();
// transInfo.Add("0");
// foreach (var l in ctr.layers)
// {
// foreach (var st in l.stateMachine.states)
// {
// string sname = l.name + "." + st.state.name;
// foreach (var t in st.state.transitions)
// {
// string dname = l.name + "." + t.destinationState.name;
// string name = (sname + " -> " + dname);
// transInfo.Add(name);
// //Debug.Log(sname + " -> " + dname + " " + Animator.StringToHash(sname + " -> " + dname));
// }
// }
// }
//}
//public static void GetTransitions(this AnimatorController ctr, List<TransitionInfo> transInfo)
//{
// transInfo.Clear();
// transInfo.Add(new TransitionInfo(0, 0, 0, 0, 0, 0, false));
// int index = 1;
// foreach (var l in ctr.layers)
// {
// foreach (var st in l.stateMachine.states)
// {
// string sname = l.name + "." + st.state.name;
// int shash = Animator.StringToHash(sname);
// foreach (var t in st.state.transitions)
// {
// string dname = l.name + "." + t.destinationState.name;
// int dhash = Animator.StringToHash(dname);
// int hash = Animator.StringToHash(sname + " -> " + dname);
// TransitionInfo ti = new TransitionInfo(index, hash, shash, dhash, t.duration, t.offset, t.hasFixedDuration);
// transInfo.Add(ti);
// //Debug.Log(index + " " + sname + " -> " + dname + " " + Animator.StringToHash(sname + " -> " + dname));
// index++;
// }
// }
// }
//}
const double AUTO_REBUILD_RATE = 10f;
private static List<string> tempNamesList = new List<string>();
private static List<int> tempHashList = new List<int>();
// This method is a near copy of the code used in NMA for determining WordCount, but uses GetController instead.
internal static (int paramCount, int boolCount, int layerCount, int words) GetWordCount(this NetworkMecanimAnimator netAnim) {
// always get new Animator in case it has changed.
Animator animator = netAnim.Animator;
if (animator == null) {
animator = netAnim.GetComponent<Animator>();
if (animator == null) {
return default;
}
// Add the animator we found
netAnim.Animator = animator;
}
AnimatorController ac = animator.GetController();
if (ac == null) {
return default;
}
var settings = netAnim.SyncSettings;
int param32Count = 0;
int paramBoolCount = 0;
bool includeI = (settings & AnimatorSyncSettings.ParameterInts) == AnimatorSyncSettings.ParameterInts;
bool includeF = (settings & AnimatorSyncSettings.ParameterFloats) == AnimatorSyncSettings.ParameterFloats;
bool includeB = (settings & AnimatorSyncSettings.ParameterBools) == AnimatorSyncSettings.ParameterBools;
bool includeT = (settings & AnimatorSyncSettings.ParameterTriggers) == AnimatorSyncSettings.ParameterTriggers;
var includeStat = (settings & AnimatorSyncSettings.StateRoot) == AnimatorSyncSettings.StateRoot;
var includeWght = (settings & AnimatorSyncSettings.LayerWeights) == AnimatorSyncSettings.LayerWeights;
var includeLyrs = (settings & AnimatorSyncSettings.StateLayers) == AnimatorSyncSettings.StateLayers;
var parameters = ac.parameters;
for (int i = 0; i < parameters.Length; ++i) {
var param = parameters[i];
switch (param.type) {
case AnimatorControllerParameterType.Int:
if (includeI)
param32Count++;
break;
case AnimatorControllerParameterType.Float:
if (includeF)
param32Count++;
break;
case AnimatorControllerParameterType.Bool:
if (includeB)
paramBoolCount++;
break;
case AnimatorControllerParameterType.Trigger:
if (includeT)
paramBoolCount++;
break;
}
}
int layerCount = ac.layers.Length;
int syncedLayerCount = includeLyrs ? layerCount : 1;
int stateWordCount = includeStat ? 2 * syncedLayerCount : 0;
int weightWordCount = (includeWght && layerCount > 0) ? (layerCount - 1) : 0;
int paramBoolsWordCount = (paramBoolCount + 31) >> 5;
int words = param32Count + paramBoolsWordCount + stateWordCount + weightWordCount;
return (param32Count, paramBoolCount, layerCount, words);
}
/// <summary>
/// Re-index all of the State and Trigger names in the current AnimatorController. Never hurts to run this (other than hanging the editor for a split second).
/// </summary>
internal static void GetHashesAndNames(this NetworkMecanimAnimator netAnim,
List<string> sharedTriggNames,
List<string> sharedStateNames,
ref int[] sharedTriggIndexes,
ref int[] sharedStateIndexes
//ref double lastRebuildTime
) {
// always get new Animator in case it has changed.
Animator animator = netAnim.Animator;
if (animator == null)
animator = netAnim.GetComponent<Animator>();
if (animator == null) {
return;
}
//if (animator && EditorApplication.timeSinceStartup - lastRebuildTime > AUTO_REBUILD_RATE) {
// lastRebuildTime = EditorApplication.timeSinceStartup;
AnimatorController ac = animator.GetController();
if (ac != null) {
if (ac.animationClips == null || ac.animationClips.Length == 0)
Debug.LogWarning("'" + animator.name + "' has an Animator with no animation clips. Some Animator Controllers require a restart of Unity, or for a Build to be made in order to initialize correctly.");
bool haschanged = false;
ac.GetTriggerNames(tempHashList);
tempHashList.Insert(0, 0);
if (!CompareIntArray(sharedTriggIndexes, tempHashList)) {
sharedTriggIndexes = tempHashList.ToArray();
haschanged = true;
}
ac.GetStatesNames(tempHashList);
tempHashList.Insert(0, 0);
if (!CompareIntArray(sharedStateIndexes, tempHashList)) {
sharedStateIndexes = tempHashList.ToArray();
haschanged = true;
}
if (sharedTriggNames != null) {
ac.GetTriggerNames(tempNamesList);
tempNamesList.Insert(0, null);
if (!CompareNameLists(tempNamesList, sharedTriggNames)) {
CopyNameList(tempNamesList, sharedTriggNames);
haschanged = true;
}
}
if (sharedStateNames != null) {
ac.GetStatesNames(tempNamesList);
tempNamesList.Insert(0, null);
if (!CompareNameLists(tempNamesList, sharedStateNames)) {
CopyNameList(tempNamesList, sharedStateNames);
haschanged = true;
}
}
if (haschanged) {
Debug.Log(animator.name + " has changed. SyncAnimator indexes updated.");
EditorUtility.SetDirty(netAnim);
}
}
//}
}
private static bool CompareNameLists(List<string> one, List<string> two) {
if (one.Count != two.Count)
return false;
for (int i = 0; i < one.Count; i++)
if (one[i] != two[i])
return false;
return true;
}
private static bool CompareIntArray(int[] old, List<int> temp) {
if (ReferenceEquals(old, null))
return false;
if (old.Length != temp.Count)
return false;
for (int i = 0; i < old.Length; i++)
if (old[i] != temp[i])
return false;
return true;
}
private static void CopyNameList(List<string> src, List<string> trg) {
trg.Clear();
for (int i = 0; i < src.Count; i++)
trg.Add(src[i]);
}
}
}
#endregion
#region Assets/Photon/Fusion/Editor/Utilities/AssetDatabaseUtils.cs
namespace Fusion.Editor {
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEditor;
#if UNITY_2021_2_OR_NEWER
using UnityEditor.SceneManagement;
#else
using UnityEditor.Experimental.SceneManagement;
#endif
using UnityEngine;
public static partial class AssetDatabaseUtils {
public static T GetSubAsset<T>(GameObject prefab) where T : ScriptableObject {
if (!AssetDatabase.IsMainAsset(prefab)) {
throw new InvalidOperationException($"Not a main asset: {prefab}");
}
string path = AssetDatabase.GetAssetPath(prefab);
if (string.IsNullOrEmpty(path)) {
throw new InvalidOperationException($"Empty path for prefab: {prefab}");
}
var subAssets = AssetDatabase.LoadAllAssetsAtPath(path).OfType<T>().ToList();
if (subAssets.Count > 1) {
Debug.LogError($"More than 1 asset of type {typeof(T)} on {path}, clean it up manually");
}
return subAssets.Count == 0 ? null : subAssets[0];
}
public static bool IsSceneObject(GameObject go) {
return ReferenceEquals(PrefabStageUtility.GetPrefabStage(go), null) && (PrefabUtility.IsPartOfPrefabAsset(go) == false || PrefabUtility.GetPrefabAssetType(go) == PrefabAssetType.NotAPrefab);
}
}
}
#endregion
#region Assets/Photon/Fusion/Editor/Utilities/FusionEditorGUI.cs
namespace Fusion.Editor {
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEditor;
using UnityEngine;
public static partial class FusionEditorGUI {
public static void LayoutSelectableLabel(GUIContent label, string contents) {
var rect = EditorGUILayout.GetControlRect();
rect = EditorGUI.PrefixLabel(rect, label);
using (new EditorGUI.IndentLevelScope(-EditorGUI.indentLevel)) {
EditorGUI.SelectableLabel(rect, contents);
}
}
public static bool DrawDefaultInspector(SerializedObject obj, bool drawScript = true) {
EditorGUI.BeginChangeCheck();
obj.UpdateIfRequiredOrScript();
// Loop through properties and create one field (including children) for each top level property.
SerializedProperty property = obj.GetIterator();
bool expanded = true;
while (property.NextVisible(expanded)) {
if ( ScriptPropertyName == property.propertyPath ) {
if (drawScript) {
using (new EditorGUI.DisabledScope("m_Script" == property.propertyPath)) {
EditorGUILayout.PropertyField(property, true);
}
}
} else {
EditorGUILayout.PropertyField(property, true);
}
expanded = false;
}
obj.ApplyModifiedProperties();
return EditorGUI.EndChangeCheck();
}
}
}
#endregion
#region Assets/Photon/Fusion/Editor/Utilities/FusionEditorGUI.Thumbnail.cs
namespace Fusion.Editor {
using System;
using System.Text;
using UnityEngine;
public static partial class FusionEditorGUI {
static readonly int _thumbnailFieldHash = "Thumbnail".GetHashCode();
static Texture2D _thumbnailBackground;
static GUIStyle _thumbnailStyle;
public static void DrawTypeThumbnail(Rect position, Type type, string prefixToSkip, string tooltip = null) {
EnsureThumbnailStyles();
var acronym = GenerateAcronym(type, prefixToSkip);
var content = new GUIContent(acronym, tooltip ?? type.FullName);
int controlID = GUIUtility.GetControlID(_thumbnailFieldHash, FocusType.Passive, position);
if (Event.current.type == EventType.Repaint) {
var originalColor = GUI.backgroundColor;
try {
GUI.backgroundColor = GetPersistentColor(type.FullName);
_thumbnailStyle.fixedWidth = position.width;
_thumbnailStyle.Draw(position, content, controlID);
} finally {
GUI.backgroundColor = originalColor;
}
}
}
static Color GetPersistentColor(string str) {
return GeneratePastelColor(HashCodeUtilities.GetHashDeterministic(str));
}
static Color GeneratePastelColor(int seed) {
var rng = new System.Random(seed);
int r = rng.Next(256) + 128;
int g = rng.Next(256) + 128;
int b = rng.Next(256) + 128;
r = Mathf.Min(r / 2, 255);
g = Mathf.Min(g / 2, 255);
b = Mathf.Min(b / 2, 255);
var result = new Color32((byte)r, (byte)g, (byte)b, 255);
return result;
}
static string GenerateAcronym(Type type, string prefixToStrip) {
StringBuilder acronymBuilder = new StringBuilder();
var str = type.Name;
if (!string.IsNullOrEmpty(prefixToStrip)) {
if (str.StartsWith(prefixToStrip)) {
str = str.Substring(prefixToStrip.Length);
}
}
for (int i = 0; i < str.Length; ++i) {
var c = str[i];
if (i != 0 && char.IsLower(c)) {
continue;
}
acronymBuilder.Append(c);
}
return acronymBuilder.ToString();
}
static void EnsureThumbnailStyles() {
if (_thumbnailBackground != null) {
return;
}
byte[] data = {
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x14,
0x08, 0x06, 0x00, 0x00, 0x00, 0x8d, 0x89, 0x1d, 0x0d, 0x00, 0x00, 0x00,
0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xae, 0xce, 0x1c, 0xe9, 0x00, 0x00,
0x00, 0x04, 0x67, 0x41, 0x4d, 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc,
0x61, 0x05, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00,
0x0e, 0xc3, 0x00, 0x00, 0x0e, 0xc3, 0x01, 0xc7, 0x6f, 0xa8, 0x64, 0x00,
0x00, 0x00, 0xf2, 0x49, 0x44, 0x41, 0x54, 0x38, 0x4f, 0xed, 0x95, 0x31,
0x0a, 0x83, 0x30, 0x14, 0x86, 0x63, 0x11, 0x74, 0x50, 0x74, 0x71, 0xf1,
0x34, 0x01, 0x57, 0x6f, 0xe8, 0xe0, 0xd0, 0xa5, 0x07, 0x10, 0x0a, 0xbd,
0x40, 0x0f, 0xe2, 0xa8, 0x9b, 0xee, 0xf6, 0x7d, 0x69, 0x4a, 0xa5, 0xd2,
0x2a, 0xa6, 0x4b, 0xa1, 0x1f, 0x04, 0x5e, 0xc2, 0xff, 0xbe, 0x68, 0x90,
0xa8, 0x5e, 0xd0, 0x69, 0x9a, 0x9e, 0xc3, 0x30, 0x1c, 0xa4, 0x9e, 0x3e,
0x0d, 0x32, 0x64, 0xa5, 0xd6, 0x32, 0x16, 0xf8, 0x51, 0x14, 0x1d, 0xb3,
0x2c, 0x1b, 0xab, 0xaa, 0x9a, 0xda, 0xb6, 0x9d, 0xd6, 0x20, 0x43, 0x96,
0x1e, 0x7a, 0x71, 0xdc, 0x55, 0x02, 0x0b, 0x45, 0x51, 0x0c, 0x82, 0x8d,
0x6f, 0x87, 0x1e, 0x7a, 0xad, 0xd4, 0xa0, 0xd9, 0x65, 0x8f, 0xec, 0x01,
0xbd, 0x38, 0x70, 0x29, 0xce, 0x81, 0x47, 0x77, 0x05, 0x87, 0x39, 0x53,
0x0e, 0x77, 0xcb, 0x99, 0xad, 0x81, 0x03, 0x97, 0x27, 0x8f, 0xc9, 0xdc,
0xbc, 0xbb, 0x2b, 0x9e, 0xe7, 0xa9, 0x83, 0xad, 0xbf, 0xc6, 0x5f, 0xe8,
0xce, 0x0f, 0x08, 0xe5, 0x63, 0x1c, 0xfb, 0xbe, 0xb7, 0xd3, 0xfd, 0xe0,
0xc0, 0x75, 0x08, 0x82, 0xe0, 0xda, 0x34, 0x8d, 0x5d, 0xde, 0x0f, 0x0e,
0x5c, 0xd4, 0x3a, 0xcf, 0x73, 0xe7, 0xcb, 0x01, 0x07, 0x2e, 0x84, 0x2a,
0x8e, 0xe3, 0x53, 0x59, 0x96, 0xbb, 0xa4, 0xf4, 0xd0, 0x8b, 0xc3, 0xc8,
0x2c, 0x3e, 0x0b, 0xec, 0x52, 0xd7, 0xf5, 0xd4, 0x75, 0x9d, 0x8d, 0xbf,
0x87, 0x0c, 0x59, 0x7a, 0xac, 0xec, 0x79, 0xc1, 0xce, 0xd0, 0x49, 0x92,
0x5c, 0xb8, 0x35, 0xa4, 0x5e, 0x5c, 0xfb, 0xf3, 0x41, 0x86, 0xac, 0xd4,
0xb3, 0x5f, 0x80, 0x52, 0x37, 0xfd, 0x56, 0x1b, 0x09, 0x40, 0x56, 0xe4,
0x85, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60,
0x82
};
var texture = new Texture2D(2, 2, TextureFormat.ARGB32, false);
if (!texture.LoadImage(data)) {
throw new InvalidOperationException();
}
_thumbnailBackground = texture;
_thumbnailStyle = new GUIStyle() {
normal = new GUIStyleState { background = _thumbnailBackground, textColor = Color.white },
border = new RectOffset(6, 6, 6, 6),
padding = new RectOffset(2, 1, 1, 1),
imagePosition = ImagePosition.TextOnly,
alignment = TextAnchor.MiddleCenter,
clipping = TextClipping.Clip,
wordWrap = true,
stretchWidth = false,
fontSize = 8,
fontStyle = FontStyle.Bold,
fixedWidth = texture.width,
};
}
}
}
#endregion
#region Assets/Photon/Fusion/Editor/Utilities/NetworkProjectConfigUtilities.cs
namespace Fusion.Editor {
using UnityEditor;
using UnityEngine;
using UnityEngine.SceneManagement;
using System.Collections.Generic;
using Fusion.Photon.Realtime;
using System.Linq;
using System.IO;
using System;
/// <summary>
/// Editor utilities for creating and managing the <see cref="NetworkProjectConfigAsset"/> singleton.
/// </summary>
[InitializeOnLoad]
public static class NetworkProjectConfigUtilities {
// Constructor runs on project load, allows for startup check for existence of NPC asset.
static NetworkProjectConfigUtilities() {
EditorApplication.playModeStateChanged += (change) => {
if (change == PlayModeStateChange.EnteredEditMode) {
NetworkProjectConfig.UnloadGlobal();
}
};
}
[MenuItem("Tools/Fusion/Network Project Config", priority = 200)]
[MenuItem("Assets/Create/Fusion/Network Project Config", priority = 0)]
static void PingNetworkProjectConfigAsset() {
FusionGlobalScriptableObjectUtils.EnsureAssetExists<NetworkProjectConfigAsset>();
NetworkProjectConfigUtilities.PingGlobalConfigAsset(true);
}
[MenuItem("Tools/Fusion/Rebuild Prefab Table", priority = 100)]
public static void RebuildPrefabTable() {
foreach (var prefab in AssetDatabase.FindAssets($"t:prefab")
.Select(AssetDatabase.GUIDToAssetPath)
.Select(x => (GameObject)AssetDatabase.LoadMainAssetAtPath(x))) {
if (prefab.TryGetComponent<NetworkObject>(out var networkObject) && !networkObject.Flags.IsIgnored()) {
AssetDatabaseUtils.SetLabel(prefab, NetworkProjectConfigImporter.FusionPrefabTag, true);
} else {
AssetDatabaseUtils.SetLabel(prefab, NetworkProjectConfigImporter.FusionPrefabTag, false);
}
}
AssetDatabase.Refresh();
ImportGlobalConfig();
Debug.Log("Rebuild Prefab Table done.");
}
public static void PingGlobalConfigAsset(bool select = false) {
if (NetworkProjectConfigAsset.TryGetGlobal(out var config)) {
EditorGUIUtility.PingObject(config);
if (select) {
Selection.activeObject = config;
}
}
}
public static bool TryGetGlobalPrefabSource<T>(NetworkObjectGuid guid, out T source) where T : class, INetworkPrefabSource {
if (NetworkProjectConfigAsset.TryGetGlobal(out var global)) {
if (global.Config.PrefabTable.GetSource(guid) is T sourceT) {
source = sourceT;
return true;
}
}
source = null;
return false;
}
public static bool TryGetPrefabId(NetworkObjectGuid guid, out NetworkPrefabId id) {
id = NetworkProjectConfig.Global.PrefabTable.GetId(guid);
return id.IsValid;
}
public static bool TryGetPrefabId(string prefabPath, out NetworkPrefabId id) {
var guidStr = AssetDatabase.AssetPathToGUID(prefabPath);
if (NetworkObjectGuid.TryParse(guidStr, out var guid)) {
return TryGetPrefabId(guid, out id);
}
id = default;
return false;
}
// public static bool TryResolvePrefab(NetworkObjectGuid guid, out NetworkObject prefab) {
// if (TryGetPrefabSource(guid, out NetworkPrefabSourceBase source)) {
// try {
// prefab = NetworkPrefabSourceFactory.ResolveOrThrow(source);
// return true;
// } catch (Exception ex) {
// FusionEditorLog.Trace(ex.ToString());
// }
// }
//
// prefab = null;
// return false;
// }
internal static bool TryGetPrefabEditorInstance(NetworkObjectGuid guid, out NetworkObject prefab) {
if (!guid.IsValid) {
prefab = null;
return false;
}
var path = AssetDatabase.GUIDToAssetPath(guid.ToUnityGuidString());
if (string.IsNullOrEmpty(path)) {
prefab = null;
return false;
}
var gameObject = AssetDatabase.LoadAssetAtPath<GameObject>(path);
if (!gameObject) {
prefab = null;
return false;
}
prefab = gameObject.GetComponent<NetworkObject>();
return prefab;
}
internal static string GetGlobalConfigPath() {
return FusionGlobalScriptableObjectUtils.GetGlobalAssetPath<NetworkProjectConfigAsset>();
}
public static bool ImportGlobalConfig() {
return FusionGlobalScriptableObjectUtils.TryImportGlobal<NetworkProjectConfigAsset>();
}
public static string SaveGlobalConfig() {
if (NetworkProjectConfigAsset.TryGetGlobal(out var global)) {
return SaveGlobalConfig(global.Config);
} else {
return SaveGlobalConfig(new NetworkProjectConfig());
}
}
public static string SaveGlobalConfig(NetworkProjectConfig config) {
FusionGlobalScriptableObjectUtils.EnsureAssetExists<NetworkProjectConfigAsset>();
string path = GetGlobalConfigPath();
var json = EditorJsonUtility.ToJson(config, true);
string existingJson = File.ReadAllText(path);
if (!string.Equals(json, existingJson)) {
AssetDatabase.MakeEditable(path);
File.WriteAllText(path, json);
}
AssetDatabase.ImportAsset(path);
return PathUtils.Normalize(path);
}
private static string[] GetEnabledBuildScenes() {
var scenes = new List<string>();
for (int i = 0; i < EditorBuildSettings.scenes.Length; ++i) {
var scene = EditorBuildSettings.scenes[i];
if (scene.enabled && string.IsNullOrEmpty(scene.path) == false) {
scenes.Add(scene.path);
}
}
return scenes.ToArray();
}
}
}
#endregion
#region Assets/Photon/Fusion/Editor/Utilities/NetworkRunnerUtilities.cs
namespace Fusion.Editor {
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
public static class NetworkRunnerUtilities {
static List<NetworkRunner> reusableRunnerList = new List<NetworkRunner>();
public static NetworkRunner[] FindActiveRunners() {
var runners = Object.FindObjectsByType<NetworkRunner>(FindObjectsInactive.Exclude, FindObjectsSortMode.InstanceID);
reusableRunnerList.Clear();
for (int i = 0; i < runners.Length; ++i) {
if (runners[i].IsRunning)
reusableRunnerList.Add(runners[i]);
}
if (reusableRunnerList.Count == runners.Length)
return runners;
return reusableRunnerList.ToArray();
}
public static void FindActiveRunners(List<NetworkRunner> nonalloc) {
var runners = Object.FindObjectsByType<NetworkRunner>(FindObjectsInactive.Exclude, FindObjectsSortMode.InstanceID);
nonalloc.Clear();
for (int i = 0; i < runners.Length; ++i) {
if (runners[i].IsRunning)
nonalloc.Add(runners[i]);
}
}
}
}
#endregion
#endif