#if !FUSION_DEV
#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/ChangeDllManager.cs
namespace Fusion.Editor {
using System;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;
///
/// Provides methods to toggle between different DLL modes for the Fusion framework.
///
public static class ChangeDllManager {
private const string FusionRuntimeDllGuid = "e725a070cec140c4caffb81624c8c787";
private static readonly string[] FileList = { "Fusion.Common.dll", "Fusion.Runtime.dll", "Fusion.Realtime.dll", "Fusion.Sockets.dll", "Fusion.Log.dll" };
///
/// Changes the DLL mode to Debug.
///
[MenuItem("Tools/Fusion/Change Dll Mode/Debug", false, 500)]
public static void ChangeDllModeToSharedDebug() {
ChangeDllMode(NetworkRunner.BuildTypes.Debug);
}
///
/// Changes the DLL mode to Release.
///
[MenuItem("Tools/Fusion/Change Dll Mode/Release", false, 501)]
public static void ChangeDllModeToSharedRelease() {
ChangeDllMode(NetworkRunner.BuildTypes.Release);
}
///
/// Changes the DLL mode based on the specified build type and build mode.
///
/// The build type ().
private static void ChangeDllMode(NetworkRunner.BuildTypes buildType) {
if (NetworkRunner.BuildType == buildType) {
Debug.Log($"Fusion Dll Mode is already {buildType}");
return;
}
Debug.Log($"Changing Fusion Dll Mode from {NetworkRunner.BuildType} to {buildType}");
var targetExtension = $"{GetBuildTypeExtension(buildType)}";
var targetSubFolder = GetBuildTypeSubFolder(buildType);
// find the root
var fusionRuntimeDllPath = AssetDatabase.GUIDToAssetPath(FusionRuntimeDllGuid);
if (string.IsNullOrEmpty(fusionRuntimeDllPath)) {
Debug.LogError($"Cannot locate Fusion assemblies directory");
return;
}
// Check if all dlls are present
var assembliesDir = PathUtils.Normalize(Path.GetDirectoryName(fusionRuntimeDllPath));
var originalFileTemplate = $"{assembliesDir}/{{0}}";
var targetFileTemplate = $"{assembliesDir}/{targetSubFolder}/{{0}}{targetExtension}";
var currentDlls = FileList.All(f => File.Exists(string.Format(originalFileTemplate, f)));
var targetDlls = FileList.All(f => File.Exists(string.Format(targetFileTemplate, f)));
if (currentDlls == false) {
Debug.LogError("Cannot find all Fusion dlls");
return;
}
if (targetDlls == false) {
Debug.LogError($"Cannot find all Fusion dlls marked with {targetExtension}");
return;
}
if (FileList.Any(f => new FileInfo(string.Format(targetFileTemplate, f)).Length == 0)) {
Debug.LogError("Targets dlls are not valid");
return;
}
// Move the files
try {
foreach (var f in FileList) {
var source = string.Format(targetFileTemplate, f);
var dest = string.Format(originalFileTemplate, f);
Debug.Log($"Moving {source} to {dest}");
FileUtil.ReplaceFile(source, dest);
}
Debug.Log($"Activated Fusion {buildType} dlls");
} catch (Exception e) {
Debug.LogAssertion(e);
Debug.LogError($"Failed to Change Fusion Dll Mode");
}
AssetDatabase.Refresh();
return;
// Gets the file extension for the specified build type.
string GetBuildTypeExtension(NetworkRunner.BuildTypes referenceBuildType) =>
referenceBuildType switch {
NetworkRunner.BuildTypes.Debug => ".debug",
NetworkRunner.BuildTypes.Release => ".release",
_ => throw new ArgumentOutOfRangeException()
};
// Gets the subfolder name for the specified build type.
string GetBuildTypeSubFolder(NetworkRunner.BuildTypes referenceBuildModes) =>
referenceBuildModes switch {
NetworkRunner.BuildTypes.Debug => "Debug",
NetworkRunner.BuildTypes.Release => "Release",
_ => throw new ArgumentOutOfRangeException()
};
}
}
}
#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();
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 _needsSurrogateCache = new Dictionary();
private Dictionary _optimisedReaderWriters = new Dictionary();
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)surrogate);
} else if (ActualFieldType == typeof(Vector2)) {
DoFloatVectorProperty(position, property, label, 2, (IUnityValueSurrogate)surrogate);
} else if (ActualFieldType == typeof(Vector3)) {
DoFloatVectorProperty(position, property, label, 3, (IUnityValueSurrogate)surrogate);
} else if (ActualFieldType == typeof(Vector4)) {
DoFloatVectorProperty(position, property, label, 4, (IUnityValueSurrogate)surrogate);
}
}
}
private void DoFloatField(Rect position, SerializedProperty property, GUIContent label, IUnityValueSurrogate 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(Rect position, SerializedProperty property, GUIContent label, int count, IUnityValueSurrogate 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> _wrappersPool = new Dictionary>();
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();
_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();
}
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();
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();
} 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 _write;
private Action _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.ItemsPropertyPath;
const string EntryKeyPropertyPath = SerializableDictionary.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 _dictionaryKeyHash = new HashSet(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;
var index = EditorGUI.Popup(dropRect, GUIContent.none, currentValue - offset, options);
index = Math.Clamp(index, 0, values.Length-1);
value = values[index];
}
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 - Force it to be 1:1 with the client tick rate - since different tick rates are not supported.
var srOptions = _reusableServerGUIArrays[0];
var srValues = _reusableServerIntArrays[0];
srOptions[0] = ratioOptions[0];
srValues[0] = 0;
selection.ServerIndex = DrawPopup(ref labelRect, ref fieldRect, rowHeight, new GUIContent("Server Tick Rate"), ratioValues, srValues, srOptions, 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/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;
///
/// Ensure GameObject has component T. Will create as needed and return the found/created component.
///
public static T EnsureComponentExists(this GameObject go) where T : Component {
if (go.TryGetComponent(out var t))
return t;
else
return go.AddComponent();
}
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(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();
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();
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;
}
///
/// Create a scene object with all of the supplied arguments and parameters applied.
///
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().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();
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();
if (targetNodes == null) {
targetNodes = component.gameObject.AddComponent();
}
// 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 INetworkAssetSourceFactory.cs
namespace Fusion.Editor {
using UnityEditor;
///
/// A factory that creates instances for a given asset.
///
public partial interface INetworkAssetSourceFactory {
///
/// The order in which this factory is executed. The lower the number, the earlier it is executed.
///
int Order { get; }
}
///
/// A context object that is passed to instances to create an instance.
///
public readonly partial struct NetworkAssetSourceFactoryContext {
///
/// Asset instance ID.
///
public readonly int InstanceID;
///
/// Asset Unity GUID;
///
public readonly string AssetGuid;
///
/// Asset name;
///
public readonly string AssetName;
///
/// Is this the main asset.
///
public readonly bool IsMainAsset;
///
/// Asset Unity path.
///
public string AssetPath => AssetDatabaseUtils.GetAssetPathOrThrow(InstanceID);
///
/// Create a new instance of .
///
public NetworkAssetSourceFactoryContext(string assetGuid, int instanceID, string assetName, bool isMainAsset) {
AssetGuid = assetGuid;
InstanceID = instanceID;
AssetName = assetName;
IsMainAsset = isMainAsset;
}
///
/// Create a new instance of .
///
public NetworkAssetSourceFactoryContext(HierarchyProperty hierarchyProperty) {
AssetGuid = hierarchyProperty.guid;
InstanceID = hierarchyProperty.instanceID;
AssetName = hierarchyProperty.name;
IsMainAsset = hierarchyProperty.isMainRepresentation;
}
///
/// Create a new instance of .
///
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 UnityEditor.AddressableAssets;
///
/// A implementation that creates
/// if the asset is an Addressable.
///
public partial class NetworkAssetSourceFactoryAddressable : INetworkAssetSourceFactory {
///
public const int Order = 800;
int INetworkAssetSourceFactory.Order => Order;
///
/// Creates a new instance. Checks if AddressableAssetSettings exists and logs a warning if it does not.
///
public NetworkAssetSourceFactoryAddressable() {
if (!AddressableAssetSettingsDefaultObject.SettingsExists) {
FusionEditorLog.WarnImport($"AddressableAssetSettings does not exist, Fusion will not be able to use Addressables for asset sources.");
}
}
///
/// Creates if the asset is an Addressable.
///
protected bool TryCreateInternal(in NetworkAssetSourceFactoryContext context, out TSource result)
where TSource : NetworkAssetSourceAddressable, new()
where TAsset : UnityEngine.Object {
if (!AddressableAssetSettingsDefaultObject.SettingsExists) {
result = default;
return false;
}
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.");
}
var addressableEntry = assetsSettings.FindAssetEntry(context.AssetGuid, true);
if (addressableEntry == null) {
result = default;
return false;
}
result = new TSource() {
RuntimeKey = $"{addressableEntry.guid}{(context.IsMainAsset ? string.Empty : $"[{context.AssetName}]")}",
};
return true;
}
}
}
#endif
#endregion
#region NetworkAssetSourceFactoryResource.cs
namespace Fusion.Editor {
///
/// A implementation that creates
/// instances for assets in the Resources folder.
///
public partial class NetworkAssetSourceFactoryResource : INetworkAssetSourceFactory {
///
public const int Order = 1000;
int INetworkAssetSourceFactory.Order => Order;
///
/// Creates if the asset is in the Resources folder.
///
protected bool TryCreateInternal(in NetworkAssetSourceFactoryContext context, out TSource result)
where TSource : NetworkAssetSourceResource, 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;
///
/// A implementation that creates .
///
public partial class NetworkAssetSourceFactoryStatic : INetworkAssetSourceFactory {
///
public const int Order = int.MaxValue;
int INetworkAssetSourceFactory.Order => Order;
///
/// Creates .
///
protected bool TryCreateInternal(in NetworkAssetSourceFactoryContext context, out TSource result)
where TSource : NetworkAssetSourceStaticLazy, new()
where TAsset : UnityEngine.Object {
if (typeof(TAsset).IsSubclassOf(typeof(Component))) {
var prefab = (GameObject)EditorUtility.InstanceIDToObject(context.InstanceID);
result = new TSource() {
Object = prefab.GetComponent()
};
} else {
result = new TSource() {
Object = new(context.InstanceID)
};
}
return true;
}
}
}
#endregion
#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;
partial class AssetDatabaseUtils {
///
/// Register a handler that will be called when an addressable asset with a specific label is added or removed.
///
public static void AddAddressableAssetsWithLabelMonitor(string label, Action 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 entries;
if (data is AddressableAssetEntry singleEntry) {
entries = Enumerable.Repeat(singleEntry, 1);
} else {
entries = (IEnumerable)data;
}
List allEntries = new List();
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;
}
};
}
internal static AddressableAssetEntry GetAddressableAssetEntry(UnityEngine.Object source) {
if (source == null || !AssetDatabase.Contains(source)) {
return null;
}
return GetAddressableAssetEntry(GetAssetGuidOrThrow(source));
}
internal static AddressableAssetEntry GetAddressableAssetEntry(string guid) {
if (string.IsNullOrEmpty(guid)) {
return null;
}
var addressableSettings = AddressableAssetSettingsDefaultObject.Settings;
return addressableSettings.FindAssetEntry(guid);
}
internal static AddressableAssetEntry CreateOrMoveAddressableAssetEntry(UnityEngine.Object source, string groupName = null) {
if (source == null || !AssetDatabase.Contains(source))
return null;
return CreateOrMoveAddressableAssetEntry(GetAssetGuidOrThrow(source), groupName);
}
internal 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;
}
internal static bool RemoveMoveAddressableAssetEntry(UnityEngine.Object source) {
if (source == null || !AssetDatabase.Contains(source)) {
return false;
}
return RemoveMoveAddressableAssetEntry(GetAssetGuidOrThrow(source));
}
internal static bool RemoveMoveAddressableAssetEntry(string guid) {
if (string.IsNullOrEmpty(guid)) {
return false;
}
var addressableSettings = AddressableAssetSettingsDefaultObject.Settings;
return addressableSettings.RemoveAssetEntry(guid);
}
[InitializeOnLoadMethod]
static void InitializeRuntimeCallbacks() {
FusionAddressablesUtils.SetLoadEditorInstanceHandler(LoadEditorInstance);
}
private static UnityEngine.Object LoadEditorInstance(string runtimeKey) {
if (string.IsNullOrEmpty(runtimeKey)) {
return default;
}
if (!FusionAddressablesUtils.TryParseAddress(runtimeKey, out var mainKey, out var subKey)) {
throw new ArgumentException($"Invalid address: {runtimeKey}", nameof(runtimeKey));
}
if (GUID.TryParse(mainKey, out _)) {
// a guid one, we can load it
if (string.IsNullOrEmpty(subKey)) {
var asset = AssetDatabase.LoadMainAssetAtPath(AssetDatabase.GUIDToAssetPath(mainKey));
if (asset != null) {
return asset;
}
} else {
foreach (var subAsset in AssetDatabase.LoadAllAssetRepresentationsAtPath(AssetDatabase.GUIDToAssetPath(mainKey))) {
if (subAsset.name == subKey) {
return subAsset;
}
}
// not returning null here, as there might be a chance for a guid-like address
}
}
// need to resort to addressable asset settings
// path... this sucks
if (!AddressableAssetSettingsDefaultObject.SettingsExists) {
FusionEditorLog.Error($"Unable to load asset: {runtimeKey}; AddressableAssetSettings does not exist");
return default;
}
var settings = AddressableAssetSettingsDefaultObject.Settings;
Assert.Check(settings != null);
var list = new List();
settings.GetAllAssets(list, true, entryFilter: x => {
if (x.IsFolder) {
return mainKey.StartsWith(x.address, StringComparison.OrdinalIgnoreCase);
} else {
return mainKey.Equals(x.address, StringComparison.OrdinalIgnoreCase);
}
});
// given the filtering above, the list will contain more than one if we
// check for a root asset that has nested assets
foreach (var entry in list) {
if (runtimeKey.Equals(entry.address, StringComparison.OrdinalIgnoreCase)) {
return entry.TargetAsset;
}
}
return default;
}
}
}
#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;
///
/// Utility methods for working with Unity's
///
public static partial class AssetDatabaseUtils {
///
/// Sets the asset dirty and, if is a sub-asset, also sets the main asset dirty.
///
///
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);
}
///
/// Returns the asset path for the given instance ID or throws an exception if the asset is not found.
///
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;
}
///
/// Returns the asset path for the given object or throws an exception if is
/// not an asset.
///
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;
}
///
/// Returns the asset path for the given asset GUID or throws an exception if the asset is not found.
///
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;
}
///
/// Returns the asset GUID for the given asset path or throws an exception if the asset is not found.
///
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;
}
///
/// Returns the asset GUID for the given instance ID or throws an exception if the asset is not found.
///
public static string GetAssetGuidOrThrow(int instanceId) {
var assetPath = GetAssetPathOrThrow(instanceId);
return GetAssetGuidOrThrow(assetPath);
}
///
/// Returns the asset GUID for the given object reference or throws an exception if the asset is not found.
///
public static string GetAssetGuidOrThrow(UnityEngine.Object obj) {
var assetPath = GetAssetPathOrThrow(obj);
return GetAssetGuidOrThrow(assetPath);
}
///
/// Gets the GUID and local file identifier for the given object reference or throws an exception if the asset is not found.
///
public static (string, long) GetGUIDAndLocalFileIdentifierOrThrow(LazyLoadReference 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);
}
///
/// Gets the GUID and local file identifier for the given object reference or throws an exception if the asset is not found.
///
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);
}
///
/// Gets the GUID and local file identifier for the instance ID or throws an exception if the asset is not found.
///
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);
}
///
/// Moves the asset at to or throws an exception if the move fails.
///
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}");
}
}
///
/// Returns if the asset at has the given .
///
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;
}
///
/// Returns if the asset has the given .
///
public static bool HasLabel(UnityEngine.Object obj, string label) {
var labels = AssetDatabase.GetLabels(obj);
var index = Array.IndexOf(labels, label);
return index >= 0;
}
///
/// Returns if the asset has the given .
///
public static bool HasLabel(GUID guid, string label) {
var labels = AssetDatabase.GetLabels(guid);
var index = Array.IndexOf(labels, label);
return index >= 0;
}
///
/// Returns if the asset at has any of the given .
///
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;
}
///
/// Sets or unsets label for the asset at , depending
/// on the value of .
///
/// if there was a change to the labels.
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;
}
///
/// Sets or unsets the label for the asset , depending
/// on the value of .
///
/// if there was a change to the labels.
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;
}
///
/// Sets all the labels for the asset at .
///
/// if the asset was found
public static bool SetLabels(string assetPath, string[] labels) {
var obj = AssetDatabase.LoadMainAssetAtPath(assetPath);
if (obj == null) {
return false;
}
AssetDatabase.SetLabels(obj, labels);
return true;
}
///
/// Checks if a scripting define is defined for .
///
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(ScriptableObject obj) where T : ScriptableObject {
return (T)SetScriptableObjectType(obj, typeof(T));
}
///
/// Changes the type of scriptable object.
///
/// The new instance with requested type
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(string valueName) where T : System.Enum {
var fi = typeof(T).GetField(valueName);
var attributes = fi.GetCustomAttributes(typeof(System.ObsoleteAttribute), false);
return attributes?.Length > 0;
}
internal static IEnumerable ValidBuildTargetGroups {
get {
foreach (var name in System.Enum.GetNames(typeof(BuildTargetGroup))) {
if (IsEnumValueObsolete(name))
continue;
var group = (BuildTargetGroup)System.Enum.Parse(typeof(BuildTargetGroup), name);
if (group == BuildTargetGroup.Unknown)
continue;
yield return group;
}
}
}
///
/// Checks if any and all have the given scripting define symbol.
///
/// if all groups have the symbol, if none have it, if some have it and some don't
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;
}
///
/// Adds or removes scripting define symbol from , depending
/// on the value of
///
public static void UpdateScriptingDefineSymbol(BuildTargetGroup group, string define, bool enable) {
UpdateScriptingDefineSymbolInternal(new[] { group },
enable ? new[] { define } : null,
enable ? null : new[] { define });
}
///
/// Adds or removes from all s, depending on the value of
///
public static void UpdateScriptingDefineSymbol(string define, bool enable) {
UpdateScriptingDefineSymbolInternal(ValidBuildTargetGroups,
enable ? new[] { define } : null,
enable ? null : new[] { define });
}
internal static void UpdateScriptingDefineSymbol(BuildTargetGroup group, IEnumerable definesToAdd, IEnumerable definesToRemove) {
UpdateScriptingDefineSymbolInternal(new[] { group },
definesToAdd,
definesToRemove);
}
internal static void UpdateScriptingDefineSymbol(IEnumerable definesToAdd, IEnumerable definesToRemove) {
UpdateScriptingDefineSymbolInternal(ValidBuildTargetGroups,
definesToAdd,
definesToRemove);
}
private static void UpdateScriptingDefineSymbolInternal(IEnumerable groups, IEnumerable definesToAdd, IEnumerable 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();
}
}
///
/// Iterates over all assets in the project that match the given search criteria, without
/// actually loading them.
///
/// The optional root folder
/// The optional label
public static AssetEnumerable IterateAssets(string root = null, string label = null) where T : UnityEngine.Object {
return IterateAssets(root, label, typeof(T));
}
///
/// Iterates over all assets in the project that match the given search criteria, without
/// actually loading them.
///
/// The optional root folder
/// The optional label
/// The optional type
public static AssetEnumerable IterateAssets(string root = null, string label = null, Type type = null) {
return new AssetEnumerable(root, label, type);
}
static Lazy s_rootFolders = new Lazy(() => new[] { "Assets" }.Concat(UnityEditor.PackageManager.PackageInfo.GetAllRegisteredPackages()
.Where(x => !IsPackageHidden(x))
.Select(x => x.assetPath))
.ToArray());
private static bool IsPackageHidden(UnityEditor.PackageManager.PackageInfo info) => info.type == "module" || info.type == "feature" && info.source != PackageSource.Embedded;
///
/// Enumerates assets in the project that match the given search criteria using API.
/// Obtained with .
///
public struct AssetEnumerator : IEnumerator {
private HierarchyProperty _hierarchyProperty;
private int _rootFolderIndex;
private readonly string[] _rootFolders;
///
/// Creates a new instance.
///
public AssetEnumerator(string root, string label, Type type) {
var searchFilter = MakeSearchFilter(label, type);
_rootFolderIndex = 0;
if (string.IsNullOrEmpty(root)) {
// search everywhere
_rootFolders = s_rootFolders.Value;
_hierarchyProperty = new HierarchyProperty(_rootFolders[0]);
} else {
_rootFolders = null;
_hierarchyProperty = new HierarchyProperty(root);
}
_hierarchyProperty.SetSearchFilter(searchFilter, (int)SearchableEditorWindow.SearchMode.All);
}
///
/// Updates internal .
///
///
public bool MoveNext() {
if (_hierarchyProperty.Next(null)) {
return true;
}
if (_rootFolders == null || _rootFolderIndex + 1 >= _rootFolders.Length) {
return false;
}
var newHierarchyProperty = new HierarchyProperty(_rootFolders[++_rootFolderIndex]);
UnityInternal.HierarchyProperty.CopySearchFilterFrom(newHierarchyProperty, _hierarchyProperty);
_hierarchyProperty = newHierarchyProperty;
// try again
return MoveNext();
}
///
/// Throws .
///
///
public void Reset() {
throw new System.NotImplementedException();
}
///
/// Returns the internernal . Most of the time
/// this will be the same instance as returned the last time, so do not cache
/// the result - check its properties intestead.
///
public HierarchyProperty Current => _hierarchyProperty;
object IEnumerator.Current => Current;
///
public void Dispose() {
}
private static string MakeSearchFilter(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;
}
return searchFilter;
}
}
///
/// Enumerable of assets in the project that match the given search criteria.
///
///
public struct AssetEnumerable : IEnumerable {
private readonly string _root;
private readonly string _label;
private readonly Type _type;
///
/// Not intended to be called directly. Use instead.
///
public AssetEnumerable(string root, string label, Type type) {
_type = type;
_root = root;
_label = label;
}
///
/// Not intended to be called directly. Use instead.
///
public AssetEnumerator GetEnumerator() => new AssetEnumerator(_root, _label, _type);
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
///
/// Sends out command to virtual peers
/// before calling .
///
public static void RegisterCustomDependencyWithMppmWorkaround(string customDependency, Hash128 hash) {
FusionMppm.MainEditor?.Send(new FusionMppmRegisterCustomDependencyCommand() {
DependencyName = customDependency,
Hash = hash.ToString(),
});
AssetDatabase.RegisterCustomDependency(customDependency, hash);
}
}
}
#endregion
#region EditorButtonDrawer.cs
namespace Fusion.Editor {
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;
struct EditorButtonDrawer {
private struct ButtonEntry {
public MethodInfo Method;
public GUIContent Content;
public EditorButtonAttribute Attribute;
public (DoIfAttributeBase, Func