From d6c3c91dc1d3ac5566398a11e996fc7e43510a2b Mon Sep 17 00:00:00 2001 From: Unity Technologies Date: Mon, 4 Jul 2022 16:35:28 +0000 Subject: [PATCH 01/94] Unity 2022.2.0a18 C# reference source code --- .../EditorUserBuildSettings.deprecated.cs | 18 +- .../Mono/Inspector/QualitySettingsEditor.cs | 101 ++ .../PerformanceTools/FrameDebuggerData.cs | 4 +- .../FrameDebuggerEventDetailsView.cs | 15 + .../PerformanceTools/FrameDebuggerStyles.cs | 3 +- Editor/Mono/PlayerSettings.deprecated.cs | 3 + Editor/Mono/PlayerSettingsAndroid.bindings.cs | 3 - Editor/Mono/Prefabs/PrefabUtility.cs | 271 +++++- .../BeeDriver/UnityBeeDriver.cs | 9 +- Editor/Mono/ShaderUtil.bindings.cs | 2 + .../DeviceInfo/DeviceInfo.cs | 1 + .../DeviceInfo/DeviceInfoImporter.cs | 1 + .../Shims/SystemInfoSimulation.cs | 1 + .../Algorithms/PropertyContainer+Accept.cs | 282 ++++++ .../PropertyContainer+GetProperty.cs | 145 +++ .../Algorithms/PropertyContainer+GetValue.cs | 215 ++++ .../Algorithms/PropertyContainer+Path.cs | 111 +++ .../Algorithms/PropertyContainer+SetValue.cs | 253 +++++ .../Runtime/Algorithms/PropertyContainer.cs | 13 + .../Runtime/Algorithms/VisitReturnCode.cs | 47 + Modules/Properties/Runtime/AssemblyInfo.cs | 9 + Modules/Properties/Runtime/Attributes.cs | 146 +++ Modules/Properties/Runtime/Exceptions.cs | 104 ++ .../Runtime/Properties/AttributesScope.cs | 54 + .../Runtime/Properties/DelegateProperty.cs | 79 ++ .../Properties/ICollectionElementProperty.cs | 70 ++ .../Properties/Internal/IAttributes.cs | 38 + .../Properties/Runtime/Properties/Property.cs | 271 ++++++ .../Runtime/Properties/PropertyPath.cs | 919 ++++++++++++++++++ .../Properties/ReflectedMemberProperty.cs | 302 ++++++ .../Runtime/PropertyBags/ArrayPropertyBag.cs | 24 + .../PropertyBags/ContainerPropertyBag.cs | 63 ++ .../PropertyBags/DictionaryPropertyBag.cs | 22 + .../PropertyBags/HashSetPropertyBag.cs | 21 + .../Runtime/PropertyBags/IPropertyBag.cs | 154 +++ .../IndexedCollectionPropertyBag.cs | 214 ++++ .../Internal/DefaultPropertyBags.cs | 280 ++++++ .../PropertyBags/Internal/PropertyBagStore.cs | 243 +++++ .../KeyValueCollectionPropertyBag.cs | 163 ++++ .../PropertyBags/KeyValuePairPropertyBag.cs | 71 ++ .../Runtime/PropertyBags/ListPropertyBag.cs | 24 + .../PropertyBags/PropertyBag+Accept.cs | 44 + .../PropertyBags/PropertyBag+Registration.cs | 235 +++++ .../PropertyBag+TypeConstruction.cs | 26 + .../Runtime/PropertyBags/PropertyBag.cs | 220 +++++ .../PropertyBags/PropertyCollection.cs | 229 +++++ .../Runtime/PropertyBags/SetPropertyBag.cs | 93 ++ .../Adapters/IExcludePropertyAdapter.cs | 91 ++ .../IVisitPrimitivesPropertyAdapter.cs | 25 + .../Adapters/IVisitPropertyAdapter.cs | 106 ++ .../PropertyVisitors/ConcreteTypeVisitor.cs | 25 + .../PropertyVisitors/ExcludeContext.cs | 61 ++ .../Runtime/PropertyVisitors/IAccept.cs | 147 +++ .../Runtime/PropertyVisitors/IVisitor.cs | 220 +++++ .../Internal/ReadOnlyAdapterCollection.cs | 56 ++ .../Runtime/PropertyVisitors/PathVisitor.cs | 164 ++++ .../PropertyVisitors/PropertyVisitor.cs | 309 ++++++ .../Runtime/PropertyVisitors/VisitContext.cs | 132 +++ .../Internal/ReflectedPropertyBag.cs | 33 + .../Internal/ReflectedPropertyBagProvider.cs | 243 +++++ .../Runtime/Utility/TypeConversion.cs | 680 +++++++++++++ .../Properties/Runtime/Utility/TypeTraits.cs | 148 +++ .../Properties/Runtime/Utility/TypeUtility.cs | 736 ++++++++++++++ .../Module/PropertiesEditorModule.cs | 21 + Modules/Terrain/Public/Terrain.bindings.cs | 2 + Modules/TerrainEditor/TerrainInspector.cs | 110 ++- .../Display/XRDisplaySubsystem.bindings.cs | 13 + Projects/CSharp/UnityEditor.csproj | 3 + Projects/CSharp/UnityEngine.csproj | 150 +++ README.md | 2 +- Runtime/Export/Device/SystemInfo.cs | 2 + Runtime/Export/Graphics/GraphicsEnums.cs | 16 + .../Graphics/GraphicsManagers.bindings.cs | 24 + .../RenderingCommandBuffer.bindings.cs | 12 + .../Export/Graphics/RenderingCommandBuffer.cs | 15 + Runtime/Export/Shaders/Material.bindings.cs | 7 +- Runtime/Export/Shaders/Material.cs | 17 + .../Export/StaticShim/SystemInfoShimBase.cs | 2 + .../Export/SystemInfo/SystemInfo.bindings.cs | 8 + 79 files changed, 9118 insertions(+), 78 deletions(-) create mode 100644 Modules/Properties/Runtime/Algorithms/PropertyContainer+Accept.cs create mode 100644 Modules/Properties/Runtime/Algorithms/PropertyContainer+GetProperty.cs create mode 100644 Modules/Properties/Runtime/Algorithms/PropertyContainer+GetValue.cs create mode 100644 Modules/Properties/Runtime/Algorithms/PropertyContainer+Path.cs create mode 100644 Modules/Properties/Runtime/Algorithms/PropertyContainer+SetValue.cs create mode 100644 Modules/Properties/Runtime/Algorithms/PropertyContainer.cs create mode 100644 Modules/Properties/Runtime/Algorithms/VisitReturnCode.cs create mode 100644 Modules/Properties/Runtime/AssemblyInfo.cs create mode 100644 Modules/Properties/Runtime/Attributes.cs create mode 100644 Modules/Properties/Runtime/Exceptions.cs create mode 100644 Modules/Properties/Runtime/Properties/AttributesScope.cs create mode 100644 Modules/Properties/Runtime/Properties/DelegateProperty.cs create mode 100644 Modules/Properties/Runtime/Properties/ICollectionElementProperty.cs create mode 100644 Modules/Properties/Runtime/Properties/Internal/IAttributes.cs create mode 100644 Modules/Properties/Runtime/Properties/Property.cs create mode 100644 Modules/Properties/Runtime/Properties/PropertyPath.cs create mode 100644 Modules/Properties/Runtime/Properties/ReflectedMemberProperty.cs create mode 100644 Modules/Properties/Runtime/PropertyBags/ArrayPropertyBag.cs create mode 100644 Modules/Properties/Runtime/PropertyBags/ContainerPropertyBag.cs create mode 100644 Modules/Properties/Runtime/PropertyBags/DictionaryPropertyBag.cs create mode 100644 Modules/Properties/Runtime/PropertyBags/HashSetPropertyBag.cs create mode 100644 Modules/Properties/Runtime/PropertyBags/IPropertyBag.cs create mode 100644 Modules/Properties/Runtime/PropertyBags/IndexedCollectionPropertyBag.cs create mode 100644 Modules/Properties/Runtime/PropertyBags/Internal/DefaultPropertyBags.cs create mode 100644 Modules/Properties/Runtime/PropertyBags/Internal/PropertyBagStore.cs create mode 100644 Modules/Properties/Runtime/PropertyBags/KeyValueCollectionPropertyBag.cs create mode 100644 Modules/Properties/Runtime/PropertyBags/KeyValuePairPropertyBag.cs create mode 100644 Modules/Properties/Runtime/PropertyBags/ListPropertyBag.cs create mode 100644 Modules/Properties/Runtime/PropertyBags/PropertyBag+Accept.cs create mode 100644 Modules/Properties/Runtime/PropertyBags/PropertyBag+Registration.cs create mode 100644 Modules/Properties/Runtime/PropertyBags/PropertyBag+TypeConstruction.cs create mode 100644 Modules/Properties/Runtime/PropertyBags/PropertyBag.cs create mode 100644 Modules/Properties/Runtime/PropertyBags/PropertyCollection.cs create mode 100644 Modules/Properties/Runtime/PropertyBags/SetPropertyBag.cs create mode 100644 Modules/Properties/Runtime/PropertyVisitors/Adapters/IExcludePropertyAdapter.cs create mode 100644 Modules/Properties/Runtime/PropertyVisitors/Adapters/IVisitPrimitivesPropertyAdapter.cs create mode 100644 Modules/Properties/Runtime/PropertyVisitors/Adapters/IVisitPropertyAdapter.cs create mode 100644 Modules/Properties/Runtime/PropertyVisitors/ConcreteTypeVisitor.cs create mode 100644 Modules/Properties/Runtime/PropertyVisitors/ExcludeContext.cs create mode 100644 Modules/Properties/Runtime/PropertyVisitors/IAccept.cs create mode 100644 Modules/Properties/Runtime/PropertyVisitors/IVisitor.cs create mode 100644 Modules/Properties/Runtime/PropertyVisitors/Internal/ReadOnlyAdapterCollection.cs create mode 100644 Modules/Properties/Runtime/PropertyVisitors/PathVisitor.cs create mode 100644 Modules/Properties/Runtime/PropertyVisitors/PropertyVisitor.cs create mode 100644 Modules/Properties/Runtime/PropertyVisitors/VisitContext.cs create mode 100644 Modules/Properties/Runtime/Reflection/Internal/ReflectedPropertyBag.cs create mode 100644 Modules/Properties/Runtime/Reflection/Internal/ReflectedPropertyBagProvider.cs create mode 100644 Modules/Properties/Runtime/Utility/TypeConversion.cs create mode 100644 Modules/Properties/Runtime/Utility/TypeTraits.cs create mode 100644 Modules/Properties/Runtime/Utility/TypeUtility.cs create mode 100644 Modules/PropertiesEditor/Module/PropertiesEditorModule.cs diff --git a/Editor/Mono/EditorUserBuildSettings.deprecated.cs b/Editor/Mono/EditorUserBuildSettings.deprecated.cs index 7a39ebd89f..ccb773f466 100644 --- a/Editor/Mono/EditorUserBuildSettings.deprecated.cs +++ b/Editor/Mono/EditorUserBuildSettings.deprecated.cs @@ -32,39 +32,29 @@ internal static void Internal_ActiveBuildTargetChanged() [Obsolete("forceOptimizeScriptCompilation is obsolete - will always return false. Control script optimization using the 'IL2CPP optimization level' configuration in Player Settings / Other.")] public static bool forceOptimizeScriptCompilation { get { return false; } } - [Obsolete(@"androidDebugMinification is obsolete. Use PlayerSettings.Android.minifyDebug and PlayerSettings.Android.minifyWithR8.")] + [Obsolete(@"androidDebugMinification is obsolete. Use PlayerSettings.Android.minifyDebug")] public static AndroidMinification androidDebugMinification { get { - if (PlayerSettings.Android.minifyDebug) - { - return PlayerSettings.Android.minifyWithR8 ? AndroidMinification.Gradle : AndroidMinification.Proguard; - } - return AndroidMinification.None; + return PlayerSettings.Android.minifyDebug ? AndroidMinification.Gradle : AndroidMinification.None; } set { PlayerSettings.Android.minifyDebug = value != AndroidMinification.None; - PlayerSettings.Android.minifyWithR8 = value == AndroidMinification.Gradle; } } - [Obsolete(@"androidReleaseMinification is obsolete. Use PlayerSettings.Android.minifyRelease and PlayerSettings.Android.minifyWithR8.")] + [Obsolete(@"androidReleaseMinification is obsolete. Use PlayerSettings.Android.minifyRelease")] public static AndroidMinification androidReleaseMinification { get { - if (PlayerSettings.Android.minifyRelease) - { - return PlayerSettings.Android.minifyWithR8 ? AndroidMinification.Gradle : AndroidMinification.Proguard; - } - return AndroidMinification.None; + return PlayerSettings.Android.minifyRelease ? AndroidMinification.Gradle : AndroidMinification.None; } set { PlayerSettings.Android.minifyRelease = value != AndroidMinification.None; - PlayerSettings.Android.minifyWithR8 = value == AndroidMinification.Gradle; } } } diff --git a/Editor/Mono/Inspector/QualitySettingsEditor.cs b/Editor/Mono/Inspector/QualitySettingsEditor.cs index 9442c7ee90..4a9830edb9 100644 --- a/Editor/Mono/Inspector/QualitySettingsEditor.cs +++ b/Editor/Mono/Inspector/QualitySettingsEditor.cs @@ -39,6 +39,23 @@ private class Content public static readonly GUIContent kAsyncUploadBufferSize = EditorGUIUtility.TrTextContent("Buffer Size", "The size (in megabytes) of the upload buffer Unity uses to stream Texture and Mesh data to GPU."); public static readonly GUIContent kAsyncUploadPersistentBuffer = EditorGUIUtility.TrTextContent("Persistent Buffer", "When enabled, the upload buffer persists even when there is nothing left to upload."); + public static readonly GUIContent kOverrideTerrainPixelError = EditorGUIUtility.TrTextContent("", "Whether to override pixel error in active Terrains."); + public static readonly GUIContent kOverrideTerrainBasemapDist = EditorGUIUtility.TrTextContent("", "Whether to override base map distance in active Terrains."); + public static readonly GUIContent kOverrideTerrainDensityScale = EditorGUIUtility.TrTextContent("", "Whether to override detail density scale in active Terrains."); + public static readonly GUIContent kOverrideTerrainDetailDistance = EditorGUIUtility.TrTextContent("", "Whether to override detail distance in active Terrains."); + public static readonly GUIContent kOverrideTerrainTreeDistance = EditorGUIUtility.TrTextContent("", "Whether to override tree distance in active Terrains."); + public static readonly GUIContent kOverrideTerrainBillboardStart = EditorGUIUtility.TrTextContent("", "Whether to override billboard start distance in active Terrains."); + public static readonly GUIContent kOverrideTerrainFadeLength = EditorGUIUtility.TrTextContent("", "Whether to override billboard fade length in active Terrains."); + public static readonly GUIContent kOverrideTerrainMaxTrees = EditorGUIUtility.TrTextContent("", "Whether to override max mesh trees in active Terrains."); + public static readonly GUIContent kTerrainPixelError = EditorGUIUtility.TrTextContent("Pixel Error", "Value set to Terrain pixel error (See Terrain settings)"); + public static readonly GUIContent kTerrainBasemapDistance = EditorGUIUtility.TrTextContent("Base Map Dist.", "Value set to Terrain base map distance (See Terrain settings)"); + public static readonly GUIContent kTerrainDetailDensityScale = EditorGUIUtility.TrTextContent("Detail Density Scale", "Value set to Terrain detail density scale (See Terrain settings)"); + public static readonly GUIContent kTerrainDetailDistance = EditorGUIUtility.TrTextContent("Detail Distance", "Value set to Terrain detail object distance (See Terrain settings)"); + public static readonly GUIContent kTerrainTreeDistance = EditorGUIUtility.TrTextContent("Tree Distance", "Value set to Terrain tree distance (See Terrain settings)"); + public static readonly GUIContent kTerrainBillboardStart = EditorGUIUtility.TrTextContent("Billboard Start", "Value set to Terrain billboard start distance (See Terrain settings)"); + public static readonly GUIContent kTerrainFadeLength = EditorGUIUtility.TrTextContent("Fade Length", "Value set to Terrain billboard fade length (See Terrain settings)"); + public static readonly GUIContent kTerrainMaxTrees = EditorGUIUtility.TrTextContent("Max Mesh Trees", "Value set to Terrain max mesh trees (See Terrain settings)"); + public static readonly GUIContent kRenderPipelineObject = EditorGUIUtility.TrTextContent("Render Pipeline Asset", "Specifies the Render Pipeline Asset to use for this quality level. It overrides the value set in the Graphics Settings Window."); } @@ -473,6 +490,20 @@ private void DrawCascadeSplitGUI(ref SerializedProperty shadowCascadeSplit) } } + private bool DrawOverrideToggle(ref SerializedProperty overrideProperty, TerrainQualityOverrides overrideFlag, GUIContent overrideStyle) + { + var overrideFlagsPropertyValue = (TerrainQualityOverrides)overrideProperty.enumValueFlag; + bool overrideActive = overrideFlagsPropertyValue.HasFlag(overrideFlag); + + var overrideRect = GUILayoutUtility.GetRect(GUIContent.none, EditorStyles.toggle, GUILayout.Height(EditorGUIUtility.singleLineHeight), GUILayout.ExpandWidth(false)); + overrideActive = GUI.Toggle(overrideRect, overrideActive, overrideStyle); + overrideProperty.enumValueFlag = overrideActive + ? (int)(overrideFlagsPropertyValue | overrideFlag) + : (int)(overrideFlagsPropertyValue & ~overrideFlag); + + return overrideActive; + } + static List s_PlatformsWithDifferentRPAssets = new(); void CheckSameRenderPipelineAssetForOverridenQualityLevels() @@ -543,6 +574,15 @@ public override void OnInspectorGUI() var realtimeReflectionProbes = currentSettings.FindPropertyRelative("realtimeReflectionProbes"); var billboardsFaceCameraPosition = currentSettings.FindPropertyRelative("billboardsFaceCameraPosition"); var useLegacyDetailsDistribution = currentSettings.FindPropertyRelative("useLegacyDetailDistribution"); + var terrainQualityOverridesProperty = currentSettings.FindPropertyRelative("terrainQualityOverrides"); + var terrainPixelErrorProperty = currentSettings.FindPropertyRelative("terrainPixelError"); + var terrainDetailDensityScaleProperty = currentSettings.FindPropertyRelative("terrainDetailDensityScale"); + var terrainBasemapDistanceProperty = currentSettings.FindPropertyRelative("terrainBasemapDistance"); + var terrainDetailDistanceProperty = currentSettings.FindPropertyRelative("terrainDetailDistance"); + var terrainTreeDistanceProperty = currentSettings.FindPropertyRelative("terrainTreeDistance"); + var terrainBillboardStartProperty = currentSettings.FindPropertyRelative("terrainBillboardStart"); + var terrainFadeLengthProperty = currentSettings.FindPropertyRelative("terrainFadeLength"); + var terrainMaxTreesProperty = currentSettings.FindPropertyRelative("terrainMaxTrees"); var vSyncCountProperty = currentSettings.FindPropertyRelative("vSyncCount"); var lodBiasProperty = currentSettings.FindPropertyRelative("lodBias"); var maximumLODLevelProperty = currentSettings.FindPropertyRelative("maximumLODLevel"); @@ -650,6 +690,67 @@ public override void OnInspectorGUI() EditorGUILayout.PropertyField(billboardsFaceCameraPosition, Content.kBillboardsFaceCameraPos); EditorGUILayout.PropertyField(useLegacyDetailsDistribution, Content.kUseLegacyDistribution); + GUILayout.Space(5); + GUILayout.Label(EditorGUIUtility.TempContent("Terrain Setting Overrides"), EditorStyles.boldLabel); + + var originalLabelWidth = EditorGUIUtility.labelWidth; + EditorGUIUtility.labelWidth -= EditorStyles.toggle.CalcSize(GUIContent.none).x + EditorStyles.toggle.margin.left; + + EditorGUILayout.BeginHorizontal(); + bool pixelErrorActive = DrawOverrideToggle(ref terrainQualityOverridesProperty, TerrainQualityOverrides.PixelError, Content.kOverrideTerrainPixelError); + using (new EditorGUI.DisabledScope(!pixelErrorActive)) + EditorGUILayout.Slider(terrainPixelErrorProperty, 1.0f, 200.0f, Content.kTerrainPixelError); + EditorGUILayout.EndHorizontal(); + + EditorGUILayout.BeginHorizontal(); + bool basemapActive = DrawOverrideToggle(ref terrainQualityOverridesProperty, TerrainQualityOverrides.BasemapDistance, Content.kOverrideTerrainBasemapDist); + using (new EditorGUI.DisabledScope(!basemapActive)) + { + EditorGUI.BeginChangeCheck(); + var newValue = EditorGUILayout.PowerSlider(Content.kTerrainBasemapDistance, Mathf.Clamp(terrainBasemapDistanceProperty.floatValue, 0.0f, 20000.0f), 0.0f, 20000.0f, 2); + if (EditorGUI.EndChangeCheck()) + terrainBasemapDistanceProperty.floatValue = newValue; + } + EditorGUILayout.EndHorizontal(); + + EditorGUILayout.BeginHorizontal(); + bool detailDensityActive = DrawOverrideToggle(ref terrainQualityOverridesProperty, TerrainQualityOverrides.DetailDensity, Content.kOverrideTerrainDensityScale); + using (new EditorGUI.DisabledScope(!detailDensityActive)) + EditorGUILayout.Slider(terrainDetailDensityScaleProperty, 0.0f, 1.0f, Content.kTerrainDetailDensityScale); + EditorGUILayout.EndHorizontal(); + + EditorGUILayout.BeginHorizontal(); + bool detailDistanceActive = DrawOverrideToggle(ref terrainQualityOverridesProperty, TerrainQualityOverrides.DetailDistance, Content.kOverrideTerrainDetailDistance); + using (new EditorGUI.DisabledScope(!detailDistanceActive)) + EditorGUILayout.Slider(terrainDetailDistanceProperty, 0, 1000, Content.kTerrainDetailDistance); + EditorGUILayout.EndHorizontal(); + + EditorGUILayout.BeginHorizontal(); + bool treeDistanceActive = DrawOverrideToggle(ref terrainQualityOverridesProperty, TerrainQualityOverrides.TreeDistance, Content.kOverrideTerrainTreeDistance); + using (new EditorGUI.DisabledScope(!treeDistanceActive)) + EditorGUILayout.Slider(terrainTreeDistanceProperty, 0.0f, 5000.0f, Content.kTerrainTreeDistance); + EditorGUILayout.EndHorizontal(); + + EditorGUILayout.BeginHorizontal(); + bool billboardStartActive = DrawOverrideToggle(ref terrainQualityOverridesProperty, TerrainQualityOverrides.BillboardStart, Content.kOverrideTerrainBillboardStart); + using (new EditorGUI.DisabledScope(!billboardStartActive)) + EditorGUILayout.Slider(terrainBillboardStartProperty, 5, 2000, Content.kTerrainBillboardStart); + EditorGUILayout.EndHorizontal(); + + EditorGUILayout.BeginHorizontal(); + bool fadeLengthActive = DrawOverrideToggle(ref terrainQualityOverridesProperty, TerrainQualityOverrides.FadeLength, Content.kOverrideTerrainFadeLength); + using (new EditorGUI.DisabledScope(!fadeLengthActive)) + EditorGUILayout.Slider(terrainFadeLengthProperty, 0, 200, Content.kTerrainFadeLength); + EditorGUILayout.EndHorizontal(); + + EditorGUILayout.BeginHorizontal(); + bool maxTreesActive = DrawOverrideToggle(ref terrainQualityOverridesProperty, TerrainQualityOverrides.MaxTrees, Content.kOverrideTerrainMaxTrees); + using (new EditorGUI.DisabledScope(!maxTreesActive)) + EditorGUILayout.IntSlider(terrainMaxTreesProperty, 0, 10000, Content.kTerrainMaxTrees); + EditorGUILayout.EndHorizontal(); + + EditorGUIUtility.labelWidth = originalLabelWidth; + if (!usingSRP || showShadowMaskUsage) { GUILayout.Space(10); diff --git a/Editor/Mono/PerformanceTools/FrameDebuggerData.cs b/Editor/Mono/PerformanceTools/FrameDebuggerData.cs index 553614df59..f74b9ddc01 100644 --- a/Editor/Mono/PerformanceTools/FrameDebuggerData.cs +++ b/Editor/Mono/PerformanceTools/FrameDebuggerData.cs @@ -43,7 +43,8 @@ internal enum FrameEventType BeginSubpass, SRPBatch, HierarchyLevelBreak, - HybridBatch + HybridBatch, + ConfigureFoveatedRendering, // ReSharper restore InconsistentNaming } @@ -216,6 +217,7 @@ internal class FrameDebuggerEventData public int rtFormat; public int rtDim; public int rtFace; + public int rtFoveatedRenderingMode; public int rtLoadAction; public int rtStoreAction; public int rtDepthLoadAction; diff --git a/Editor/Mono/PerformanceTools/FrameDebuggerEventDetailsView.cs b/Editor/Mono/PerformanceTools/FrameDebuggerEventDetailsView.cs index 09de61f797..1e6ddbf4f1 100644 --- a/Editor/Mono/PerformanceTools/FrameDebuggerEventDetailsView.cs +++ b/Editor/Mono/PerformanceTools/FrameDebuggerEventDetailsView.cs @@ -1284,11 +1284,26 @@ private void BuildCurEventDataStrings(FrameDebuggerEvent curEvent, FrameDebugger else m_TempSB2.AppendLine(FrameDebuggerStyles.EventDetails.k_NotAvailable); } + else if (m_LastEventData.type == FrameEventType.ConfigureFoveatedRendering) + { + // No extra data for this event at the moment + } else { m_TempSB1.AppendLine("Memoryless"); m_TempSB2.AppendLine((curEventData.rtMemoryless != 0) ? "Yes" : "No"); + m_TempSB1.AppendLine("Foveated Rendering Mode"); + switch ((FoveatedRenderingMode)curEventData.rtFoveatedRenderingMode) + { + case FoveatedRenderingMode.Disabled: + m_TempSB2.AppendLine("Disabled"); + break; + case FoveatedRenderingMode.Enabled: + m_TempSB2.AppendLine("Enabled"); + break; + } + m_TempSB1.AppendLine(); m_TempSB2.AppendLine(); diff --git a/Editor/Mono/PerformanceTools/FrameDebuggerStyles.cs b/Editor/Mono/PerformanceTools/FrameDebuggerStyles.cs index 736daf0757..b25bc5a01d 100644 --- a/Editor/Mono/PerformanceTools/FrameDebuggerStyles.cs +++ b/Editor/Mono/PerformanceTools/FrameDebuggerStyles.cs @@ -42,7 +42,8 @@ internal static class FrameDebuggerStyles "Begin Subpass", "SRP Batch", "", // on purpose empty string for kFrameEventHierarchyLevelBreak - "Hybrid Batch Group" + "Hybrid Batch Group", + "Configure Foveated Rendering", }; // General settings for the Frame Debugger Window and layout diff --git a/Editor/Mono/PlayerSettings.deprecated.cs b/Editor/Mono/PlayerSettings.deprecated.cs index 3d8e4f326e..3c7deb6721 100644 --- a/Editor/Mono/PlayerSettings.deprecated.cs +++ b/Editor/Mono/PlayerSettings.deprecated.cs @@ -164,6 +164,9 @@ public static AndroidTargetDevice targetDevice targetArchitectures = AndroidArchitecture.ARMv7; } } + + [Obsolete("minifyWithR8 is obsolete and has no effect anymore, since Android Gradle Plugin 7.0 always uses R8", false)] + public static bool minifyWithR8 { get { return true; } set {} } } partial class iOS diff --git a/Editor/Mono/PlayerSettingsAndroid.bindings.cs b/Editor/Mono/PlayerSettingsAndroid.bindings.cs index 3dae16de80..cb490d6e79 100644 --- a/Editor/Mono/PlayerSettingsAndroid.bindings.cs +++ b/Editor/Mono/PlayerSettingsAndroid.bindings.cs @@ -528,9 +528,6 @@ public static extern bool renderOutsideSafeArea set; } - // Minify java code using R8 if set to true, otherwise use proguard for minification - [NativeProperty("AndroidMinifyWithR8", TargetType.Function)] - public static extern bool minifyWithR8 { get; set; } // Minify java code in release build [NativeProperty("AndroidMinifyRelease", TargetType.Function)] public static extern bool minifyRelease { get; set; } diff --git a/Editor/Mono/Prefabs/PrefabUtility.cs b/Editor/Mono/Prefabs/PrefabUtility.cs index b533636363..4a6573bba4 100644 --- a/Editor/Mono/Prefabs/PrefabUtility.cs +++ b/Editor/Mono/Prefabs/PrefabUtility.cs @@ -406,6 +406,60 @@ static void ApplyPropertyOverrides(Object prefabInstanceObject, SerializedProper var serializedObjects = new List(); var changedObjects = new HashSet(); + HandleApplySingleProperties(prefabInstanceObject, optionalSingleInstanceProperty, assetPath, prefabSourceObject, prefabSourceSerializedObject, serializedObjects, changedObjects, allowApplyDefaultOverride, action); + + // Ensure importing of saved Prefab Assets only kicks in after all Prefab Asset have been saved + AssetDatabase.StartAssetEditing(); + + try + { + Action saveIfChanged = (SerializedObject serializedObject) => + { + if (changedObjects.Contains(serializedObject)) + { + bool applySuccess = action == InteractionMode.UserAction ? serializedObject.ApplyModifiedProperties() : serializedObject.ApplyModifiedPropertiesWithoutUndo(); + if (applySuccess) + { + SaveChangesToPrefabFileIfPersistent(serializedObject); + + if (action == InteractionMode.UserAction) + { + Undo.FlushUndoRecordObjects(); // flush'es ensure that SavePrefab() on undo/redo on the source happens in the right order + } + } + } + }; + + // Case 1292519 + // The Prefab to which the changes are applied is added first in serializedObjects by ApplySingleProperty - it must be saved first + // The rest of the objects are collected in reverse dependency order in ApplySingleProperty - starting from the instance where the changes are made down to the original Prefab + // Apply the changes and save them in dependency order (reversed array order) to make sure dependent values are saved after the values they depend upon + if (serializedObjects.Count > 0) + { + saveIfChanged(serializedObjects[0]); + for (int i = serializedObjects.Count - 1; i > 0; i--) + { + saveIfChanged(serializedObjects[i]); + } + } + } + finally + { + AssetDatabase.StopAssetEditing(); + } + } + + static void HandleApplySingleProperties( + Object prefabInstanceObject, + SerializedProperty optionalSingleInstanceProperty, + string assetPath, Object prefabSourceObject, + SerializedObject prefabSourceSerializedObject, + List serializedObjects, + HashSet changedObjects, + bool allowApplyDefaultOverride, + InteractionMode action + ) + { bool isObjectOnRootInAsset = IsObjectOnRootInAsset(prefabInstanceObject, assetPath); SerializedProperty property = null; @@ -437,14 +491,25 @@ static void ApplyPropertyOverrides(Object prefabInstanceObject, SerializedProper property = so.GetIterator(); } + bool allowWarnAboutApplyingPartsOfManagedReferences = property.isReferencingAManagedReferenceField; + if (!property.hasVisibleChildren) { if (property.prefabOverride) - ApplySingleProperty(property, prefabSourceSerializedObject, prefabSourceObject, isObjectOnRootInAsset, true, allowApplyDefaultOverride, serializedObjects, changedObjects, action); + ApplySinglePropertyAndRemoveOverride(property, prefabSourceSerializedObject, prefabSourceObject, isObjectOnRootInAsset, true, allowWarnAboutApplyingPartsOfManagedReferences, allowApplyDefaultOverride, serializedObjects, changedObjects, action, out _); } else { - while (property.Next(property.hasVisibleChildren) && (endProperty == null || !SerializedProperty.EqualContents(property, endProperty))) + if (property.prefabOverride && property.propertyType == SerializedPropertyType.ManagedReference) + { + allowWarnAboutApplyingPartsOfManagedReferences = false; // we should always be allowed to apply a managed reference root + ApplySinglePropertyAndRemoveOverride(property, prefabSourceSerializedObject, prefabSourceObject, isObjectOnRootInAsset, false, allowWarnAboutApplyingPartsOfManagedReferences, allowApplyDefaultOverride, serializedObjects, changedObjects, action, out _); + } + + var visitedManagedReferenceProperties = new HashSet(); + bool visitChildren = property.hasVisibleChildren; + + while (property.Next(visitChildren) && (endProperty == null || !SerializedProperty.EqualContents(property, endProperty))) { // If we apply a property that has child properties that are object references, and if they // reference non-asset objects, those references will get lost, since ApplySingleProperty @@ -465,50 +530,28 @@ static void ApplyPropertyOverrides(Object prefabInstanceObject, SerializedProper // NOTE: all property modifications are leafs except in the context of managed references. // Managed references can be overriden (and have visible children). - if (property.prefabOverride && (property.propertyType == SerializedPropertyType.ManagedReference || !property.hasVisibleChildren)) - ApplySingleProperty(property, prefabSourceSerializedObject, prefabSourceObject, isObjectOnRootInAsset, false, allowApplyDefaultOverride, serializedObjects, changedObjects, action); - } - } + bool isManagedReferenceRoot = property.propertyType == SerializedPropertyType.ManagedReference; + bool skipRestOfProperties = false; + if (property.prefabOverride && (isManagedReferenceRoot || !property.hasVisibleChildren)) + ApplySinglePropertyAndRemoveOverride(property, prefabSourceSerializedObject, prefabSourceObject, isObjectOnRootInAsset, false, allowWarnAboutApplyingPartsOfManagedReferences, allowApplyDefaultOverride, serializedObjects, changedObjects, action, out skipRestOfProperties); - // Ensure importing of saved Prefab Assets only kicks in after all Prefab Asset have been saved - AssetDatabase.StartAssetEditing(); + if (skipRestOfProperties) + break; - try - { - Action saveIfChanged = (SerializedObject serializedObject) => - { - if (changedObjects.Contains(serializedObject)) + // Avoid cyclic mangaged references + if (isManagedReferenceRoot) { - bool applySuccess = action == InteractionMode.UserAction ? serializedObject.ApplyModifiedProperties() : serializedObject.ApplyModifiedPropertiesWithoutUndo(); - if (applySuccess) - { - SaveChangesToPrefabFileIfPersistent(serializedObject); - - if (action == InteractionMode.UserAction) - { - Undo.FlushUndoRecordObjects(); // flush'es ensure that SavePrefab() on undo/redo on the source happens in the right order - } - } + if (visitedManagedReferenceProperties.Add(property.managedReferenceId)) + visitChildren = property.hasVisibleChildren; // First time seeing managed reference, so allow entering children if needed + else + visitChildren = false; } - }; - - // Case 1292519 - // The Prefab to which the changes are applied is added first in serializedObjects by ApplySingleProperty - it must be saved first - // The rest of the objects are collected in reverse dependency order in ApplySingleProperty - starting from the instance where the changes are made down to the original Prefab - // Apply the changes and save them in dependency order (reversed array order) to make sure dependent values are saved after the values they depend upon - if (serializedObjects.Count > 0) - { - saveIfChanged(serializedObjects[0]); - for (int i = serializedObjects.Count - 1; i > 0; i--) + else { - saveIfChanged(serializedObjects[i]); + visitChildren = property.hasVisibleChildren; } } } - finally - { - AssetDatabase.StopAssetEditing(); - } } static void SaveChangesToPrefabFileIfPersistent(SerializedObject serializedObject) @@ -564,6 +607,16 @@ static SerializedProperty GetArrayPropertyIfGivenPropertyIsPartOfArrayElementInI // Check if found array has different length on instance than on Asset. SerializedProperty arrayPropertyOnInstance = optionalSingleInstanceProperty.serializedObject.FindProperty(arrayPropertyPath); SerializedProperty arrayPropertyOnAsset = prefabSourceSerializedObject.FindProperty(arrayPropertyPath); + + // With the ManagedReferences feature we no longer guarantee that the Prefab Asset will have the same property so we need to check + // if it exists (could be a base class without any properties) + if (arrayPropertyOnAsset == null) + { + WarnIfApplyingManagedReferenceFieldIsNotPossible(optionalSingleInstanceProperty, null, action); + cancel = true; + return null; + } + if (arrayPropertyOnInstance.arraySize > arrayPropertyOnAsset.arraySize) { // Array size mismatches. Check if the property the user is attempting to apply @@ -603,17 +656,21 @@ static SerializedProperty GetArrayPropertyIfGivenPropertyIsPartOfArrayElementInI // That may be thousands of times if a component has lots of array data. // We provide as much information as possible to the method as parameters // so we don't have to recalculate it for each call. - static void ApplySingleProperty( + static void ApplySinglePropertyAndRemoveOverride( SerializedProperty instanceProperty, SerializedObject prefabSourceSerializedObject, Object applyTarget, bool isObjectOnRootInAsset, bool singlePropertyOnly, + bool allowWarnAboutApplyingPartsOfManagedReferences, bool allowApplyDefaultOverride, List serializedObjects, HashSet changedObjects, - InteractionMode action) + InteractionMode action, + out bool skipRestOfProperties) { + skipRestOfProperties = false; + if (!allowApplyDefaultOverride && isObjectOnRootInAsset && IsPropertyOverrideDefaultOverrideComparedToAnySource(instanceProperty)) { if (singlePropertyOnly) @@ -634,6 +691,7 @@ static void ApplySingleProperty( SerializedProperty sourceProperty = prefabSourceSerializedObject.FindProperty(instanceProperty.propertyPath); if (sourceProperty == null) { + // Special handling for arrays bool cancel; var instanceArrayProperty = GetArrayPropertyIfGivenPropertyIsPartOfArrayElementInInstanceWhichDoesNotExistInAsset(instanceProperty, prefabSourceSerializedObject, InteractionMode.AutomatedAction, out cancel); if (instanceArrayProperty != null) @@ -648,15 +706,34 @@ static void ApplySingleProperty( return; } } - else - { - Debug.LogError($"ApplySingleProperty copy state error: SerializedProperty could not be found for {instanceProperty.propertyPath}. Please report a bug."); - return; - } + } + + if (allowWarnAboutApplyingPartsOfManagedReferences && WarnIfApplyingManagedReferenceFieldIsNotPossible(instanceProperty, sourceProperty, action)) + { + skipRestOfProperties = true; + return; + } + + if (sourceProperty == null) + { + // If we reach here we need to investigate the situation in which it happens and fix it + Debug.LogError($"ApplySinglePropertyAndRemoveOverride error: Unhandled situation for {instanceProperty.propertyPath}. Please report a bug."); + skipRestOfProperties = true; + return; } // Copy overridden property value to asset - prefabSourceSerializedObject.CopyFromSerializedProperty(instanceProperty); + if (instanceProperty.propertyType == SerializedPropertyType.ManagedReference) + { + // For a managed reference root property we assign the entire object reference value so we can handle if the Asset value is null from start, since in + // this case we cannot use CopyFromSerializedProperty as we do for normal properties. + sourceProperty.managedReferenceValue = instanceProperty.managedReferenceValue; + } + else + { + prefabSourceSerializedObject.CopyFromSerializedProperty(instanceProperty); + } + changedObjects.Add(prefabSourceSerializedObject); // Abort if property has reference to object in scene. @@ -739,6 +816,10 @@ internal static void RevertPropertyOverrides(SerializedProperty[] instanceProper if (WarnIfInAnimationMode(OverrideOperation.Revert, action)) return; + foreach (var property in instanceProperties) + if (WarnIfRevertingManagedReferenceIsNotPossible(property, action)) + return; + foreach (var property in instanceProperties) RevertPropertyOverride(property, action); } @@ -750,7 +831,11 @@ public static void RevertPropertyOverride(SerializedProperty instanceProperty, I ThrowExceptionIfAllPrefabInstanceObjectsAreInvalid(instanceProperty.serializedObject.targetObjects, false); + if (WarnIfRevertingManagedReferenceIsNotPossible(instanceProperty, action)) + return; + instanceProperty.prefabOverride = false; + // Because prefabOverride changed ApplyModifiedProperties will do a prefab merge causing the revert. if (action == InteractionMode.UserAction) instanceProperty.serializedObject.ApplyModifiedProperties(); @@ -758,6 +843,100 @@ public static void RevertPropertyOverride(SerializedProperty instanceProperty, I instanceProperty.serializedObject.ApplyModifiedPropertiesWithoutUndo(); } + static bool WarnIfApplyingManagedReferenceFieldIsNotPossible(SerializedProperty instanceProperty, SerializedProperty sourceProperty, InteractionMode action) + { + bool isReferencingAManagedReferenceField = instanceProperty.isReferencingAManagedReferenceField; + if (!isReferencingAManagedReferenceField) + return false; + + if (sourceProperty != null) + { + // Even if we find the propertyPath on the source object we also need to validate if this is a managed reference to the correct source object, except when + // applying an entire object because then entire state is transfered to the asset and can merged back to the instance so here we don't show the dialog. + bool applyingNotPossible = instanceProperty.managedReferencePropertyPath != sourceProperty.managedReferencePropertyPath; + if (applyingNotPossible) + { + string title = L10n.Tr("Mismatching Objects"); + string errorMsg = L10n.Tr("Cannot apply SerializeReference field since the Prefab instance is referencing a new object compared to the Prefab Asset.\n\nThis means that the changes from the Prefab Asset cannot be merged back to the Prefab instance.\n\nYou can apply the root of the field or the entire component"); + if (action == InteractionMode.AutomatedAction) + throw new InvalidOperationException(title + ": " + errorMsg + $"(instance property { instanceProperty.managedReferencePropertyPath}, asset property { sourceProperty.managedReferencePropertyPath})"); + else + EditorUtility.DisplayDialog( + title, + errorMsg, + L10n.Tr("OK")); + return true; + } + } + else + { + // Special handling for managed references. + // If we have a valid managedReferencePropertyPath then the types must be different since we could not find the + // propertyPath on the Prefab Asset, tell the user that this is not supported. + if (!string.IsNullOrEmpty(instanceProperty.managedReferencePropertyPath)) + { + string title = L10n.Tr("Mismatching Types"); + string errorMsg = L10n.Tr("Cannot apply a SerializeReference sub field when the type from the Prefab instance is different from the Prefab Asset.\n\nYou can apply the root of the field or the entire component"); + if (action == InteractionMode.AutomatedAction) + throw new InvalidOperationException(title + ": " + errorMsg); + else + EditorUtility.DisplayDialog( + title, + errorMsg, + L10n.Tr("OK")); + + return true; + } + } + + return false; + } + + static bool WarnIfRevertingManagedReferenceIsNotPossible(SerializedProperty instanceProperty, InteractionMode action) + { + if (!instanceProperty.isReferencingAManagedReferenceField) + return false; + + var instanceObject = instanceProperty.serializedObject.targetObject; + var prefabSourceObject = GetCorrespondingObjectFromSource(instanceObject); + SerializedObject prefabSourceSerializedObject = new SerializedObject(prefabSourceObject); + SerializedProperty sourceProperty = prefabSourceSerializedObject.FindProperty(instanceProperty.propertyPath); + + string errorMsg = ""; + string title = ""; + if (sourceProperty != null) + { + // Object mismatch: + bool revertingNotPossible = instanceProperty.managedReferencePropertyPath != sourceProperty.managedReferencePropertyPath; + if (revertingNotPossible) + { + title = L10n.Tr("Mismatching Objects"); + errorMsg = L10n.Tr("Cannot revert a single SerializeReference field since the Prefab instance is referencing a new object compared to the Prefab Asset.\n\nThis means that the entire object is considered an override, cannot revert parts of it.\n\nYou can revert the root of the serialized reference or the entire component."); + } + } + else + { + // Type mismatch: + title = L10n.Tr("Mismatching Types"); + errorMsg = L10n.Tr("Cannot revert a parts of a SerializeReference property since the Prefab instance is referencing a different type compared to the Prefab Asset.\n\nYou can revert the root of the serialized reference or the entire component."); + } + + bool warnUser = !string.IsNullOrEmpty(errorMsg); + if (warnUser) + { + if (action == InteractionMode.AutomatedAction) + { + throw new InvalidOperationException(title + ": " + errorMsg); + } + else if (action == InteractionMode.UserAction) + { + EditorUtility.DisplayDialog(title, errorMsg, L10n.Tr("OK")); + } + } + + return warnUser; + } + public static void ApplyObjectOverride(Object instanceComponentOrGameObject, string assetPath, InteractionMode action) { DateTime startTime = DateTime.UtcNow; diff --git a/Editor/Mono/Scripting/ScriptCompilation/BeeDriver/UnityBeeDriver.cs b/Editor/Mono/Scripting/ScriptCompilation/BeeDriver/UnityBeeDriver.cs index 30cbac69b0..a2ab4826d1 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/BeeDriver/UnityBeeDriver.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/BeeDriver/UnityBeeDriver.cs @@ -88,6 +88,11 @@ private static void RecreateDagDirectoryIfNeeded(NPath dagDirectory) // Clear dag directory if it was produced with a different bee_backend, to avoid problem where bee_backend sometimes looses track of files. if (dagDirectory.Exists()) { + // When used DeleteMode.Normal, it sometimes was causing an error on Windows: + // Win32Exception: The directory is not empty. + // at NiceIO.NPath + WindowsFileSystem.Directory_Delete(NiceIO.NPath path, System.Boolean recursive)[0x000f4] in C:\buildslave\unity\build\External\NiceIO\NiceIO.cs:1792 + // Since we're recreating a directory anyways, using DeleteMode.Soft should be fine. + var deleteMode = DeleteMode.Soft; if (beeBackendInfoPath.Exists()) { var contents = beeBackendInfoPath.ReadAllText(); @@ -99,13 +104,13 @@ private static void RecreateDagDirectoryIfNeeded(NPath dagDirectory) !diskInfo.BeeBackendHash.Equals(currentInfo.BeeBackendHash)) { Console.WriteLine($"Clearing Bee directory '{dagDirectory}', since bee backend hash ('{beeBackendInfoPath}') is different, previous hash was {diskInfo.BeeBackendHash} (Unity version: {diskInfo.UnityVersion}), current hash is {currentInfo.BeeBackendHash} (Unity version: {currentInfo.UnityVersion})."); - dagDirectory.Delete(); + dagDirectory.Delete(deleteMode); } } else { Console.WriteLine($"Clearing Bee directory '{dagDirectory}', since bee backend information ('{beeBackendInfoPath}') is missing."); - dagDirectory.Delete(); + dagDirectory.Delete(deleteMode); } } diff --git a/Editor/Mono/ShaderUtil.bindings.cs b/Editor/Mono/ShaderUtil.bindings.cs index 68b1d27174..35efe9af55 100644 --- a/Editor/Mono/ShaderUtil.bindings.cs +++ b/Editor/Mono/ShaderUtil.bindings.cs @@ -203,6 +203,8 @@ public static void UpdateShaderAsset(Shader shader, string source, bool compileI UpdateShaderAsset(null, shader, source, compileInitialShaderVariants); } + extern public static ComputeShader CreateComputeShaderAsset(AssetImportContext context, string source); + [FreeFunction("GetShaderNameRegistry().AddShader")] extern public static void RegisterShader([NotNull("NullExceptionObject")] Shader shader); diff --git a/Modules/DeviceSimulatorEditor/DeviceInfo/DeviceInfo.cs b/Modules/DeviceSimulatorEditor/DeviceInfo/DeviceInfo.cs index de514a0d9c..cb3c7f0fd2 100644 --- a/Modules/DeviceSimulatorEditor/DeviceInfo/DeviceInfo.cs +++ b/Modules/DeviceSimulatorEditor/DeviceInfo/DeviceInfo.cs @@ -110,6 +110,7 @@ internal class GraphicsSystemInfoData public int graphicsShaderLevel; public bool graphicsMultiThreaded; public RenderingThreadingMode renderingThreadingMode; + public FoveatedRenderingCaps foveatedRenderingCaps; public bool hasHiddenSurfaceRemovalOnGPU; public bool hasDynamicUniformArrayIndexingInFragmentShaders; public bool supportsShadows; diff --git a/Modules/DeviceSimulatorEditor/DeviceInfo/DeviceInfoImporter.cs b/Modules/DeviceSimulatorEditor/DeviceInfo/DeviceInfoImporter.cs index 1364c95e95..c90f4de7d3 100644 --- a/Modules/DeviceSimulatorEditor/DeviceInfo/DeviceInfoImporter.cs +++ b/Modules/DeviceSimulatorEditor/DeviceInfo/DeviceInfoImporter.cs @@ -207,6 +207,7 @@ internal static void FindOptionalFieldAvailability(DeviceInfoAsset asset, XEleme "graphicsShaderLevel", "graphicsMultiThreaded", "renderingThreadingMode", + "foveatedRenderingCaps", "hasHiddenSurfaceRemovalOnGPU", "hasDynamicUniformArrayIndexingInFragmentShaders", "supportsShadows", diff --git a/Modules/DeviceSimulatorEditor/Shims/SystemInfoSimulation.cs b/Modules/DeviceSimulatorEditor/Shims/SystemInfoSimulation.cs index 6598b29290..6a6acd19f4 100644 --- a/Modules/DeviceSimulatorEditor/Shims/SystemInfoSimulation.cs +++ b/Modules/DeviceSimulatorEditor/Shims/SystemInfoSimulation.cs @@ -87,6 +87,7 @@ public void Dispose() public override int graphicsShaderLevel => m_GraphicsSystemInfoFields.Contains("graphicsShaderLevel") ? m_GraphicsSystemInfo.graphicsShaderLevel : base.graphicsShaderLevel; public override bool graphicsMultiThreaded => m_GraphicsSystemInfoFields.Contains("graphicsMultiThreaded") ? m_GraphicsSystemInfo.graphicsMultiThreaded : base.graphicsMultiThreaded; public override RenderingThreadingMode renderingThreadingMode => m_GraphicsSystemInfoFields.Contains("renderingThreadingMode") ? m_GraphicsSystemInfo.renderingThreadingMode : base.renderingThreadingMode; + public override FoveatedRenderingCaps foveatedRenderingCaps => m_GraphicsSystemInfoFields.Contains("foveatedRenderingCaps") ? m_GraphicsSystemInfo.foveatedRenderingCaps : base.foveatedRenderingCaps; public override bool hasHiddenSurfaceRemovalOnGPU => m_GraphicsSystemInfoFields.Contains("hasHiddenSurfaceRemovalOnGPU") ? m_GraphicsSystemInfo.hasHiddenSurfaceRemovalOnGPU : base.hasHiddenSurfaceRemovalOnGPU; public override bool hasDynamicUniformArrayIndexingInFragmentShaders => m_GraphicsSystemInfoFields.Contains("hasDynamicUniformArrayIndexingInFragmentShaders") ? m_GraphicsSystemInfo.hasDynamicUniformArrayIndexingInFragmentShaders : base.hasDynamicUniformArrayIndexingInFragmentShaders; public override bool supportsShadows => m_GraphicsSystemInfoFields.Contains("supportsShadows") ? m_GraphicsSystemInfo.supportsShadows : base.supportsShadows; diff --git a/Modules/Properties/Runtime/Algorithms/PropertyContainer+Accept.cs b/Modules/Properties/Runtime/Algorithms/PropertyContainer+Accept.cs new file mode 100644 index 0000000000..2eb31eda7c --- /dev/null +++ b/Modules/Properties/Runtime/Algorithms/PropertyContainer+Accept.cs @@ -0,0 +1,282 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using System.Collections.Generic; +using Unity.Properties.Internal; + +namespace Unity.Properties +{ + /// + /// Flags used to specify a set of exceptions. + /// + [Flags] + public enum VisitExceptionKind + { + /// + /// Flag used to specify no exception types. + /// + None = 0, + + /// + /// Flag used to specify internal exceptions thrown by the core visitation. + /// + Internal = 1 << 0, + + /// + /// Use this flag to specify exceptions thrown from the visitor code itself. + /// + Visitor = 1 << 1, + + /// + /// Use this flag to specify all exceptions. + /// + All = Internal | Visitor + } + + /// + /// Custom parameters to use for visitation. + /// + public struct VisitParameters + { + /// + /// Use this options to ignore specific exceptions during visitation. + /// + public VisitExceptionKind IgnoreExceptions { get; set; } + } + + public static partial class PropertyContainer + { + /// + /// Visit the specified using the specified . + /// + /// The visitor. + /// The container to visit. + /// The visit parameters to use. + /// The container is null. + /// The given container type is not valid for visitation. + /// No property bag was found for the given container. + public static void Accept(IPropertyBagVisitor visitor, TContainer container, VisitParameters parameters = default) + { + var returnCode = VisitReturnCode.Ok; + + try + { + if (TryAccept(visitor, ref container, out returnCode, parameters)) + return; + } + catch (Exception) + { + if ((parameters.IgnoreExceptions & VisitExceptionKind.Visitor) == 0) + throw; + } + + if ((parameters.IgnoreExceptions & VisitExceptionKind.Internal) != 0) + return; + + switch (returnCode) + { + case VisitReturnCode.Ok: + case VisitReturnCode.InvalidContainerType: + break; + case VisitReturnCode.NullContainer: + throw new ArgumentException("The given container was null. Visitation only works for valid non-null containers."); + case VisitReturnCode.MissingPropertyBag: + throw new MissingPropertyBagException(container.GetType()); + default: + throw new Exception($"Unexpected {nameof(VisitReturnCode)}=[{returnCode}]"); + } + } + + /// + /// Visit the specified using the specified . + /// + /// The visitor. + /// The container to visit. + /// The visit parameters to use. + /// The declared container type. + /// The container is null. + /// The container is null. + /// No property bag was found for the given container. + public static void Accept(IPropertyBagVisitor visitor, ref TContainer container, VisitParameters parameters = default) + { + var returnCode = VisitReturnCode.Ok; + + try + { + if (TryAccept(visitor, ref container, out returnCode, parameters)) + return; + } + catch (Exception) + { + if ((parameters.IgnoreExceptions & VisitExceptionKind.Visitor) == 0) + throw; + } + + if ((parameters.IgnoreExceptions & VisitExceptionKind.Internal) != 0) + return; + + switch (returnCode) + { + case VisitReturnCode.Ok: + case VisitReturnCode.InvalidContainerType: + break; + case VisitReturnCode.NullContainer: + throw new ArgumentException("The given container was null. Visitation only works for valid non-null containers."); + case VisitReturnCode.MissingPropertyBag: + throw new MissingPropertyBagException(container.GetType()); + default: + throw new Exception($"Unexpected {nameof(VisitReturnCode)}=[{returnCode}]"); + } + } + + /// + /// Tries to visit the specified by ref using the specified . + /// + /// The visitor. + /// The container to visit. + /// The visit parameters to use. + /// The declared container type. + /// if the visitation succeeded; otherwise. + public static bool TryAccept(IPropertyBagVisitor visitor, ref TContainer container, VisitParameters parameters = default) + { + return TryAccept(visitor, ref container, out _, parameters); + } + + /// + /// Tries to visit the specified by ref using the specified . + /// + /// The visitor. + /// The container to visit. + /// The visit parameters to use. + /// When this method returns, contains the return code. + /// The declared container type. + /// if the visitation succeeded; otherwise. + public static bool TryAccept(IPropertyBagVisitor visitor, ref TContainer container, out VisitReturnCode returnCode, VisitParameters parameters = default) + { + if (!TypeTraits.IsContainer) + { + returnCode = VisitReturnCode.InvalidContainerType; + return false; + } + + // Can not visit a null container. + if (TypeTraits.CanBeNull) + { + if (EqualityComparer.Default.Equals(container, default)) + { + returnCode = VisitReturnCode.NullContainer; + return false; + } + } + + if (!TypeTraits.IsValueType && typeof(TContainer) != container.GetType()) + { + if (!TypeTraits.IsContainer(container.GetType())) + { + returnCode = VisitReturnCode.InvalidContainerType; + return false; + } + + var properties = PropertyBagStore.GetPropertyBag(container.GetType()); + + if (null == properties) + { + returnCode = VisitReturnCode.MissingPropertyBag; + return false; + } + + // At this point the generic parameter is useless to us since it's not the correct type. + // Instead we need to retrieve the untyped property bag and accept on that. Since we don't know the type + // We need to box the container and let the property bag cast it internally. + var boxed = (object) container; + properties.Accept(visitor, ref boxed); + container = (TContainer) boxed; + } + else + { + var properties = PropertyBagStore.GetPropertyBag(); + + if (null == properties) + { + returnCode = VisitReturnCode.MissingPropertyBag; + return false; + } + + PropertyBag.AcceptWithSpecializedVisitor(properties, visitor, ref container); + } + + returnCode = VisitReturnCode.Ok; + return true; + } + + /// + /// Visit the specified using the specified at the given . + /// + /// The visitor. + /// The container to visit. + /// The property path to visit. + /// The visit parameters to use. + /// The container type. + /// The container is null. + /// The given container type is not valid for visitation. + /// No property bag was found for the given container. + public static void Accept(IPropertyVisitor visitor, ref TContainer container, in PropertyPath path, VisitParameters parameters = default) + { + var visitAtPath = ValueAtPathVisitor.Pool.Get(); + try + { + visitAtPath.Path = path; + visitAtPath.Visitor = visitor; + + Accept(visitAtPath, ref container, parameters); + + if ((parameters.IgnoreExceptions & VisitExceptionKind.Internal) == 0) + { + switch (visitAtPath.ReturnCode) + { + case VisitReturnCode.Ok: + break; + case VisitReturnCode.InvalidPath: + throw new InvalidPathException($"Failed to Visit at Path=[{path}]"); + default: + throw new Exception($"Unexpected {nameof(VisitReturnCode)}=[{visitAtPath.ReturnCode}]"); + } + } + } + finally + { + ValueAtPathVisitor.Pool.Release(visitAtPath); + } + } + + /// + /// Visit the specified using the specified at the given . + /// + /// The visitor. + /// The container to visit. + /// The property path to visit. + /// When this method returns, contains the return code. + /// The visit parameters to use. + /// The container type. + /// The container is null. + /// The given container type is not valid for visitation. + /// No property bag was found for the given container. + public static bool TryAccept(IPropertyVisitor visitor, ref TContainer container, in PropertyPath path, out VisitReturnCode returnCode, VisitParameters parameters = default) + { + var visitAtPath = ValueAtPathVisitor.Pool.Get(); + try + { + visitAtPath.Path = path; + visitAtPath.Visitor = visitor; + + return TryAccept(visitAtPath, ref container, out returnCode, parameters); + } + finally + { + ValueAtPathVisitor.Pool.Release(visitAtPath); + } + } + } +} diff --git a/Modules/Properties/Runtime/Algorithms/PropertyContainer+GetProperty.cs b/Modules/Properties/Runtime/Algorithms/PropertyContainer+GetProperty.cs new file mode 100644 index 0000000000..c0ce8560f1 --- /dev/null +++ b/Modules/Properties/Runtime/Algorithms/PropertyContainer+GetProperty.cs @@ -0,0 +1,145 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; + +namespace Unity.Properties +{ + public static partial class PropertyContainer + { + class GetPropertyVisitor : PathVisitor + { + public static readonly UnityEngine.Pool.ObjectPool Pool = new UnityEngine.Pool.ObjectPool(() => new GetPropertyVisitor(), null, v => v.Reset()); + + public IProperty Property; + + public override void Reset() + { + base.Reset(); + Property = default; + ReadonlyVisit = true; + } + + protected override void VisitPath(Property property, + ref TContainer container, ref TValue value) + { + Property = property; + } + } + + /// + /// Gets an on the specified container for the given . + /// + /// + /// While the container data is not actually read from or written to. The container itself is needed to resolve polymorphic fields and list elements. + /// + /// The container tree to search. + /// The property path to resolve. + /// The strongly typed container. + /// The for the given path. + /// The specified container or path is null. + /// The specified container type is not valid for visitation. + /// The specified container type has no property bag associated with it. + /// The specified was not found or could not be resolved. + public static IProperty GetProperty(TContainer container, in PropertyPath path) + => GetProperty(ref container, path); + + /// + /// Gets an on the specified container for the given . + /// + /// + /// While the container data is not actually read from or written to. The container itself is needed to resolve polymorphic fields and list elements. + /// + /// The container whose property will be returned. + /// The property path to resolve. + /// The strongly typed container. + /// The for the given path. + /// The specified container or path is null. + /// The specified container type is not valid for visitation. + /// The specified container type has no property bag associated with it. + /// The specified was not found or could not be resolved. + public static IProperty GetProperty(ref TContainer container, in PropertyPath path) + { + if (TryGetProperty(ref container, path, out var property, out var returnCode)) + { + return property; + } + + switch (returnCode) + { + case VisitReturnCode.NullContainer: + throw new ArgumentNullException(nameof(container)); + case VisitReturnCode.InvalidContainerType: + throw new InvalidContainerTypeException(container.GetType()); + case VisitReturnCode.MissingPropertyBag: + throw new MissingPropertyBagException(container.GetType()); + case VisitReturnCode.InvalidPath: + throw new ArgumentException($"Failed to get property for path=[{path}]"); + default: + throw new Exception($"Unexpected {nameof(VisitReturnCode)}=[{returnCode}]"); + } + } + + /// + /// Gets an on the specified container for the given . + /// + /// + /// While the container data is not actually read from or written to. The container itself is needed to resolve polymorphic fields and list elements. + /// + /// The container tree to search. + /// The property path to resolve. + /// When this method returns, contains the property associated with the specified path, if the property is found; otherwise, null. + /// The strongly typed container. + /// if the property was found at the specified path; otherwise, . + public static bool TryGetProperty(TContainer container, in PropertyPath path, out IProperty property) + => TryGetProperty(ref container, path, out property, out _); + + /// + /// Gets an on the specified container for the given . + /// + /// + /// While the container data is not actually read from or written to. The container itself is needed to resolve polymorphic fields and list elements. + /// + /// The container tree to search. + /// The property path to resolve. + /// When this method returns, contains the property associated with the specified path, if the property is found; otherwise, null. + /// The strongly typed container. + /// if the property was found at the specified path; otherwise, . + public static bool TryGetProperty(ref TContainer container, in PropertyPath path, out IProperty property) + => TryGetProperty(ref container, path, out property, out _); + + /// + /// Gets an on the specified container for the given . + /// + /// + /// While the container data is not actually read from or written to. The container itself is needed to resolve polymorphic fields and list elements. + /// + /// The container tree to search. + /// The property path to resolve. + /// When this method returns, contains the property associated with the specified path, if the property is found; otherwise, null. + /// When this method returns, contains the return code. + /// The strongly typed container. + /// if the property was found at the specified path; otherwise, . + public static bool TryGetProperty(ref TContainer container, in PropertyPath path, out IProperty property, out VisitReturnCode returnCode) + { + var getPropertyVisitor = GetPropertyVisitor.Pool.Get(); + try + { + getPropertyVisitor.Path = path; + if (!TryAccept(getPropertyVisitor, ref container, out returnCode)) + { + property = default; + return false; + } + returnCode = getPropertyVisitor.ReturnCode; + property = getPropertyVisitor.Property; + return returnCode == VisitReturnCode.Ok; + } + finally + { + GetPropertyVisitor.Pool.Release(getPropertyVisitor); + } + } + } +} diff --git a/Modules/Properties/Runtime/Algorithms/PropertyContainer+GetValue.cs b/Modules/Properties/Runtime/Algorithms/PropertyContainer+GetValue.cs new file mode 100644 index 0000000000..0ddb7ee23d --- /dev/null +++ b/Modules/Properties/Runtime/Algorithms/PropertyContainer+GetValue.cs @@ -0,0 +1,215 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; + +namespace Unity.Properties +{ + public static partial class PropertyContainer + { + class GetValueVisitor : PathVisitor + { + public static readonly UnityEngine.Pool.ObjectPool> Pool = new UnityEngine.Pool.ObjectPool>(() => new GetValueVisitor(), null, v => v.Reset()); + public TSrcValue Value; + + public override void Reset() + { + base.Reset(); + Value = default; + ReadonlyVisit = true; + } + + protected override void VisitPath(Property property, ref TContainer container, ref TValue value) + { + if (!TypeConversion.TryConvert(ref value, out Value)) + { + ReturnCode = VisitReturnCode.InvalidCast; + } + } + } + + /// + /// Gets the value of a property by name. + /// + /// The container whose property value will be returned. + /// The name of the property to get. + /// The value type. + /// The container type. + /// The value for the specified name. + /// The specified container or path is null. + /// The specified container type is not valid for visitation. + /// The specified container type has no property bag associated with it. + /// The specified could not be assigned to the property. + /// The specified was not found or could not be resolved. + public static TValue GetValue(TContainer container, string name) + => GetValue(ref container, name); + + /// + /// Gets the value of a property by name. + /// + /// The container whose property value will be returned. + /// The name of the property to get. + /// The container type. + /// The value type. + /// The value for the specified name. + /// The specified container or path is null. + /// The specified container type is not valid for visitation. + /// The specified container type has no property bag associated with it. + /// The specified could not be assigned to the property. + /// The specified was not found or could not be resolved. + public static TValue GetValue(ref TContainer container, string name) + { + var path = new PropertyPath(name); + return GetValue(ref container, path); + } + + /// + /// Gets the value of a property by path. + /// + /// The container whose property value will be returned. + /// The path of the property to get. + /// The container type. + /// The value type. + /// The value at the specified path. + /// The specified container or path is null. + /// The specified container type is not valid for visitation. + /// The specified container type has no property bag associated with it. + /// The specified could not be assigned to the property. + /// The specified was not found or could not be resolved. + public static TValue GetValue(TContainer container, in PropertyPath path) + => GetValue(ref container, path); + + /// + /// Gets the value of a property by path. + /// + /// The container whose property value will be returned. + /// The path of the property to get. + /// The container type. + /// The value type. + /// The value at the specified path. + /// The specified container or path is null. + /// The specified container type is not valid for visitation. + /// The specified container type has no property bag associated with it. + /// The specified could not be assigned to the property. + /// The specified was not found or could not be resolved. + public static TValue GetValue(ref TContainer container, in PropertyPath path) + { + if (path.IsEmpty) + throw new InvalidPathException("The specified PropertyPath is empty."); + + if (TryGetValue(ref container, path, out TValue value, out var returnCode)) + return value; + + switch (returnCode) + { + case VisitReturnCode.NullContainer: + throw new ArgumentNullException(nameof(container)); + case VisitReturnCode.InvalidContainerType: + throw new InvalidContainerTypeException(container.GetType()); + case VisitReturnCode.MissingPropertyBag: + throw new MissingPropertyBagException(container.GetType()); + case VisitReturnCode.InvalidCast: + throw new InvalidCastException($"Failed to GetValue of Type=[{typeof(TValue).Name}] for property with path=[{path}]"); + case VisitReturnCode.InvalidPath: + throw new InvalidPathException($"Failed to GetValue for property with Path=[{path}]"); + default: + throw new Exception($"Unexpected {nameof(VisitReturnCode)}=[{returnCode}]"); + } + } + + /// + /// Gets the value of a property by name. + /// + /// The container whose property value will be returned. + /// The name of the property to get. + /// When this method returns, contains the value associated with the specified name, if the property is found. otherwise the default value for the . + /// The container type. + /// The value type. + /// if the value exists for the specified name; otherwise, . + public static bool TryGetValue(TContainer container, string name, out TValue value) + => TryGetValue(ref container, name, out value); + + /// + /// Gets the value of a property by name. + /// + /// The container whose property value will be returned. + /// The name of the property to get. + /// When this method returns, contains the value associated with the specified name, if the property is found. otherwise the default value for the . + /// The container type. + /// The value type. + /// if the value exists for the specified name; otherwise, . + public static bool TryGetValue(ref TContainer container, string name, out TValue value) + { + var path = new PropertyPath(name); + return TryGetValue(ref container, path, out value, out _); + } + + /// + /// Gets the value of a property by path. + /// + /// The container whose property value will be returned. + /// The path of the property to get. + /// When this method returns, contains the value associated with the specified path, if the property is found. otherwise the default value for the . + /// The property value of the given container. + /// The container type. + /// The value type to set. + /// if the value exists at the specified path; otherwise, . + public static bool TryGetValue(TContainer container, in PropertyPath path, out TValue value) + => TryGetValue(ref container, path, out value, out _); + + /// + /// Gets the value of a property by path. + /// + /// The container whose property value will be returned. + /// The path of the property to get. + /// When this method returns, contains the value associated with the specified path, if the property is found. otherwise the default value for the . + /// The container type. + /// The value type. + /// if the value exists at the specified path; otherwise, . + public static bool TryGetValue(ref TContainer container, in PropertyPath path, out TValue value) + => TryGetValue(ref container, path, out value, out _); + + /// + /// Gets the value of a property by path. + /// + /// The container whose property value will be returned. + /// The path of the property to get. + /// When this method returns, contains the value associated with the specified path, if the property is found. otherwise the default value for the . + /// When this method returns, contains the return code. + /// The container type. + /// The value type. + /// if the value exists at the specified path; otherwise, . + public static bool TryGetValue(ref TContainer container, in PropertyPath path, out TValue value, out VisitReturnCode returnCode) + { + if (path.IsEmpty) + { + returnCode = VisitReturnCode.InvalidPath; + value = default; + return false; + } + + var visitor = GetValueVisitor.Pool.Get(); + visitor.Path = path; + visitor.ReadonlyVisit = true; + + try + { + if (!TryAccept(visitor, ref container, out returnCode)) + { + value = default; + return false; + } + + value = visitor.Value; + returnCode = visitor.ReturnCode; + } + finally + { + GetValueVisitor.Pool.Release(visitor); + } + + return returnCode == VisitReturnCode.Ok; + } + } +} diff --git a/Modules/Properties/Runtime/Algorithms/PropertyContainer+Path.cs b/Modules/Properties/Runtime/Algorithms/PropertyContainer+Path.cs new file mode 100644 index 0000000000..df435fc203 --- /dev/null +++ b/Modules/Properties/Runtime/Algorithms/PropertyContainer+Path.cs @@ -0,0 +1,111 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +namespace Unity.Properties +{ + public static partial class PropertyContainer + { + class ValueAtPathVisitor : PathVisitor + { + public static readonly UnityEngine.Pool.ObjectPool Pool = new UnityEngine.Pool.ObjectPool(() => new ValueAtPathVisitor(), null, v => v.Reset()); + public IPropertyVisitor Visitor; + + public override void Reset() + { + base.Reset(); + Visitor = default; + ReadonlyVisit = true; + } + + protected override void VisitPath(Property property, + ref TContainer container, ref TValue value) + { + ((IPropertyAccept) property).Accept(Visitor, ref container); + } + } + + class ExistsAtPathVisitor : PathVisitor + { + public static readonly UnityEngine.Pool.ObjectPool Pool = new UnityEngine.Pool.ObjectPool(() => new ExistsAtPathVisitor(), null, v => v.Reset()); + public bool Exists; + + public override void Reset() + { + base.Reset(); + Exists = default; + ReadonlyVisit = true; + } + + protected override void VisitPath(Property property, ref TContainer container, ref TValue value) + { + Exists = true; + } + } + + + /// + /// Returns if a property exists at the specified . + /// + /// The container tree to search. + /// The property path to resolve. + /// The container type. + /// if a property can be found at path. + public static bool IsPathValid(TContainer container, string path) + => IsPathValid(ref container, new PropertyPath(path)); + + /// + /// Returns if a property exists at the specified . + /// + /// The container tree to search. + /// The property path to resolve. + /// The container type. + /// if a property can be found at path. + public static bool IsPathValid(TContainer container, in PropertyPath path) + => IsPathValid(ref container, path); + + /// + /// Returns if a property exists at the specified . + /// + /// The container tree to search. + /// The property path to resolve. + /// The container type. + /// if a property can be found at path. + public static bool IsPathValid(ref TContainer container, string path) + { + var visitor = ExistsAtPathVisitor.Pool.Get(); + try + { + visitor.Path = new PropertyPath(path); + TryAccept(visitor, ref container); + return visitor.Exists; + } + finally + { + ExistsAtPathVisitor.Pool.Release(visitor); + } + } + + /// + /// Returns if a property exists at the specified . + /// + /// The container tree to search. + /// The property path to resolve. + /// The container type. + /// if a property can be found at path. + public static bool IsPathValid(ref TContainer container, in PropertyPath path) + { + var visitor = ExistsAtPathVisitor.Pool.Get(); + try + { + visitor.Path = path; + TryAccept(visitor, ref container); + return visitor.Exists; + } + finally + { + ExistsAtPathVisitor.Pool.Release(visitor); + } + } + } +} diff --git a/Modules/Properties/Runtime/Algorithms/PropertyContainer+SetValue.cs b/Modules/Properties/Runtime/Algorithms/PropertyContainer+SetValue.cs new file mode 100644 index 0000000000..df6c372d27 --- /dev/null +++ b/Modules/Properties/Runtime/Algorithms/PropertyContainer+SetValue.cs @@ -0,0 +1,253 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; + +namespace Unity.Properties +{ + public static partial class PropertyContainer + { + internal class SetValueVisitor : PathVisitor + { + public static readonly UnityEngine.Pool.ObjectPool> Pool = new UnityEngine.Pool.ObjectPool>(() => new SetValueVisitor(), null, v => v.Reset()); + public TSrcValue Value; + + public override void Reset() + { + base.Reset(); + Value = default; + } + + protected override void VisitPath(Property property, + ref TContainer container, ref TValue value) + { + if (property.IsReadOnly) + { + ReturnCode = VisitReturnCode.AccessViolation; + return; + } + + if (TypeConversion.TryConvert(ref Value, out TValue v)) + { + property.SetValue(ref container, v); + } + else + { + ReturnCode = VisitReturnCode.InvalidCast; + } + } + } + + /// + /// Sets the value of a property by name to the given value. + /// + /// + /// This method is NOT thread safe. + /// + /// The container whose property will be set. + /// The name of the property to set. + /// The container type to set the value on. + /// The value to assign to the property. + /// The value type to set. + /// The specified container or path is null. + /// The specified container type is not valid for visitation. + /// The specified container type has no property bag associated with it. + /// The specified could not be assigned to the property. + /// The specified was not found or could not be resolved. + public static void SetValue(TContainer container, string name, TValue value) + => SetValue(ref container, name, value); + + /// + /// Sets the value of a property by name to the given value. + /// + /// + /// This method is NOT thread safe. + /// + /// The container whose property will be set. + /// The name of the property to set. + /// The value to assign to the property. + /// The container type to set the value on. + /// The value type to set. + /// The specified container or path is null. + /// The specified container type is not valid for visitation. + /// The specified container type has no property bag associated with it. + /// The specified could not be assigned to the property. + /// The specified was not found or could not be resolved. + public static void SetValue(ref TContainer container, string name, TValue value) + { + var path = new PropertyPath(name); + SetValue(ref container, path, value); + } + + /// + /// Sets the value of a property at the given path to the given value. + /// + /// + /// This method is NOT thread safe. + /// + /// The container whose property will be set. + /// The path of the property to set. + /// The value to assign to the property. + /// The container type to set the value on. + /// The value type to set. + /// The specified container or path is null. + /// The specified container type is not valid for visitation. + /// The specified container type has no property bag associated with it. + /// The specified could not be assigned to the property. + /// The specified was not found or could not be resolved. + public static void SetValue(TContainer container, in PropertyPath path, TValue value) + => SetValue(ref container, path, value); + + /// + /// Sets the value of a property at the given path to the given value. + /// + /// + /// This method is NOT thread safe. + /// + /// The container whose property will be set. + /// The path of the property to set. + /// The value to assign to the property. + /// The container type to set the value on. + /// The value type to set. + /// The specified container or path is null. + /// The specified container type is not valid for visitation. + /// The specified container type has no property bag associated with it. + /// The specified could not be assigned to the property. + /// The specified was not found or could not be resolved. + /// The specified is read-only. + public static void SetValue(ref TContainer container, in PropertyPath path, TValue value) + { + if (path.Length == 0) + throw new ArgumentNullException(nameof(path)); + + if (path.Length <= 0) + throw new InvalidPathException("The specified PropertyPath is empty."); + + if (TrySetValue(ref container, path, value, out var returnCode)) + return; + + switch (returnCode) + { + case VisitReturnCode.NullContainer: + throw new ArgumentNullException(nameof(container)); + case VisitReturnCode.InvalidContainerType: + throw new InvalidContainerTypeException(container.GetType()); + case VisitReturnCode.MissingPropertyBag: + throw new MissingPropertyBagException(container.GetType()); + case VisitReturnCode.InvalidCast: + throw new InvalidCastException($"Failed to SetValue of Type=[{typeof(TValue).Name}] for property with path=[{path}]"); + case VisitReturnCode.InvalidPath: + throw new InvalidPathException($"Failed to SetValue for property with Path=[{path}]"); + case VisitReturnCode.AccessViolation: + throw new AccessViolationException($"Failed to SetValue for read-only property with Path=[{path}]"); + default: + throw new Exception($"Unexpected {nameof(VisitReturnCode)}=[{returnCode}]"); + } + } + + /// + /// Tries to set the value of a property at the given path to the given value. + /// + /// + /// This method is NOT thread safe. + /// + /// The container whose property will be set. + /// The name of the property to set. + /// The value to assign to the property. + /// The container type to set the value on. + /// The value type to set. + /// if the value was set correctly; otherwise. + public static bool TrySetValue(TContainer container, string name, TValue value) + => TrySetValue(ref container, name, value); + + /// + /// Tries to set the value of a property at the given path to the given value. + /// + /// + /// This method is NOT thread safe. + /// + /// The container whose property will be set. + /// The name of the property to set. + /// The value to assign to the property. + /// The container type to set the value on. + /// The value type to set. + /// if the value was set correctly; otherwise. + public static bool TrySetValue(ref TContainer container, string name, TValue value) + { + var path = new PropertyPath(name); + return TrySetValue(ref container, path, value); + } + + /// + /// Tries to set the value of a property at the given path to the given value. + /// + /// + /// This method is NOT thread safe. + /// + /// The container whose property will be set. + /// The path of the property to set. + /// The value to assign to the property. + /// The container type to set the value on. + /// The value type to set. + /// if the value was set correctly; otherwise. + public static bool TrySetValue(TContainer container, in PropertyPath path, TValue value) + => TrySetValue(ref container, path, value); + + /// + /// Tries to set the value of a property at the given path to the given value. + /// + /// + /// This method is NOT thread safe. + /// + /// The container whose property will be set. + /// The path of the property to set. + /// The value to assign to the property. + /// The container type to set the value on. + /// The value type to set. + /// if the value was set correctly; otherwise. + public static bool TrySetValue(ref TContainer container, in PropertyPath path, TValue value) + => TrySetValue(ref container, path, value, out _); + + /// + /// Tries to set the value of a property at the given path to the given value. + /// + /// + /// This method is NOT thread safe. + /// + /// The container whose property will be set. + /// The path of the property to set. + /// The value to assign to the property. + /// When this method returns, contains the return code. + /// The container type to set the value on. + /// The value type to set. + /// if the value was set correctly; otherwise. + public static bool TrySetValue(ref TContainer container, in PropertyPath path, TValue value, out VisitReturnCode returnCode) + { + if (path.IsEmpty) + { + returnCode = VisitReturnCode.InvalidPath; + return false; + } + + var visitor = SetValueVisitor.Pool.Get(); + visitor.Path = path; + visitor.Value = value; + try + { + if (!TryAccept(visitor, ref container, out returnCode)) + { + return false; + } + + returnCode = visitor.ReturnCode; + } + finally + { + SetValueVisitor.Pool.Release(visitor); + } + + return returnCode == VisitReturnCode.Ok; + } + } +} diff --git a/Modules/Properties/Runtime/Algorithms/PropertyContainer.cs b/Modules/Properties/Runtime/Algorithms/PropertyContainer.cs new file mode 100644 index 0000000000..26ff92aecc --- /dev/null +++ b/Modules/Properties/Runtime/Algorithms/PropertyContainer.cs @@ -0,0 +1,13 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +namespace Unity.Properties +{ + /// + /// The class is used as the entry point to operate on data containers using properties. + /// + public static partial class PropertyContainer + { + } +} diff --git a/Modules/Properties/Runtime/Algorithms/VisitReturnCode.cs b/Modules/Properties/Runtime/Algorithms/VisitReturnCode.cs new file mode 100644 index 0000000000..ab46bf0b4b --- /dev/null +++ b/Modules/Properties/Runtime/Algorithms/VisitReturnCode.cs @@ -0,0 +1,47 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +namespace Unity.Properties +{ + /// + /// Internal return code used during path visitation. + /// + public enum VisitReturnCode + { + /// + /// The visitation resolved successfully. + /// + Ok, + + /// + /// The container being visited was null. + /// + NullContainer, + + /// + /// The given container type is not valid for visitation. + /// + InvalidContainerType, + + /// + /// No property bag was found for the given container type. + /// + MissingPropertyBag, + + /// + /// Failed to resolve some part of the path (e.g. Name, Index or Key). + /// + InvalidPath, + + /// + /// Failed to reinterpret the target value as the requested type. + /// + InvalidCast, + + /// + /// Failed to set value at path because it is read-only. + /// + AccessViolation + } +} diff --git a/Modules/Properties/Runtime/AssemblyInfo.cs b/Modules/Properties/Runtime/AssemblyInfo.cs new file mode 100644 index 0000000000..08bc1d3185 --- /dev/null +++ b/Modules/Properties/Runtime/AssemblyInfo.cs @@ -0,0 +1,9 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Unity.Properties.CodeGen.IntegrationTests")] +[assembly: InternalsVisibleTo("Unity.Properties.Reflection.Tests")] +[assembly: InternalsVisibleTo("Unity.Properties.Tests")] diff --git a/Modules/Properties/Runtime/Attributes.cs b/Modules/Properties/Runtime/Attributes.cs new file mode 100644 index 0000000000..80f0f5d51d --- /dev/null +++ b/Modules/Properties/Runtime/Attributes.cs @@ -0,0 +1,146 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; + +namespace Unity.Properties +{ + /// + /// Use this attribute to enable the source generator to run on this assembly. + /// + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false)] + public class GeneratePropertyBagsForAssemblyAttribute : Attribute + { + + } + + /// + /// Use this attribute to have a property generated for the member. + /// + /// + /// By default public fields will have properties generated. + /// + /// + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] + public class CreatePropertyAttribute : Attribute + { + + } + + /// + /// Use this attribute to prevent have a property from being automatically generated on a public field. + /// + /// + [AttributeUsage(AttributeTargets.Field)] + public class DontCreatePropertyAttribute : Attribute + { + + } + + /// + /// A set of options to customize the behaviour of the code generator. + /// + [Flags] + public enum TypeGenerationOptions + { + /// + /// If this option is selected, no property bags will be generated. + /// + None = 0, + + /// + /// If this option is selected, any inherited value types will have property bags generated. + /// + ValueType = 1 << 1, + + /// + /// If this option is selected, any inherited reference types will have property bags generated. + /// + ReferenceType = 1 << 2, + + /// + /// The default set of type options. This includes both and . + /// + Default = ValueType | ReferenceType + } + + /// + /// Use this attribute to have the properties source generator generate property bags for types implementing the specified interface. + /// + /// + /// If you need to generate a property bag for a specific type use . + /// + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public class GeneratePropertyBagsForTypesQualifiedWithAttribute : Attribute + { + /// + /// The interface type to generate property bags for. + /// + public Type Type { get; } + + /// + /// Options used for additional filtering. + /// + public TypeGenerationOptions Options { get; } + + /// + /// Initializes a new instance of the attribute. + /// + /// The interface type to generate property bags for. + /// Additional type filtering options. + /// The type is null or the given type is not an interface. + public GeneratePropertyBagsForTypesQualifiedWithAttribute(Type type, TypeGenerationOptions options = TypeGenerationOptions.Default) + { + if (type == null) + { + throw new ArgumentException($"{nameof(type)} is null."); + } + + if (!type.IsInterface) + { + throw new ArgumentException($"{nameof(GeneratePropertyBagsForTypesQualifiedWithAttribute)} Type must be an interface type."); + } + + Type = type; + Options = options; + } + } + + /// + /// Use this attribute to have the source generator generate a property bag for a given type. + /// This attribute works for the specified type ONLY, it does NOT include derived types. + /// + /// + /// If you need to generate property bags for types implementing a specific interface use . + /// + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public class GeneratePropertyBagsForTypeAttribute : Attribute + { + /// + /// The type to generate a property bag for. + /// + public Type Type { get; } + + /// + /// Initializes a new instance of the attribute. + /// + /// The type to generate a property bag for. + /// The specified type is not a valid container type. + public GeneratePropertyBagsForTypeAttribute(Type type) + { + if (!TypeTraits.IsContainer(type)) + throw new ArgumentException($"{type.Name} is not a valid container type."); + + Type = type; + } + } + + /// + /// Use this attribute to have the source generator generate property bags for a given type. + /// + [AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class, Inherited = false)] + public class GeneratePropertyBagAttribute : Attribute + { + } +} diff --git a/Modules/Properties/Runtime/Exceptions.cs b/Modules/Properties/Runtime/Exceptions.cs new file mode 100644 index 0000000000..92b6ea4a87 --- /dev/null +++ b/Modules/Properties/Runtime/Exceptions.cs @@ -0,0 +1,104 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; + +namespace Unity.Properties +{ + /// + /// The exception that is thrown when trying to visit a container with no property bag. + /// + [Serializable] + public class MissingPropertyBagException : Exception + { + /// + /// The type which triggered the exception. + /// + public Type Type { get; } + + /// + /// Initializes a new instance of the class with a specified type. + /// + /// The type for which no property bag was found. + public MissingPropertyBagException(Type type) : base(GetMessageForType(type)) + { + Type = type; + } + + /// + /// Initializes a new instance of the class with a specified type and a reference to the inner exception that is the cause of this exception. + /// + /// The type for which no property bag was found. + /// The inner exception reference. + public MissingPropertyBagException(Type type, Exception inner) : base(GetMessageForType(type), inner) + { + Type = type; + } + + static string GetMessageForType(Type type) + { + return $"No PropertyBag was found for Type=[{type.FullName}]. Please make sure all types are declared ahead of time using [{nameof(GeneratePropertyBagAttribute)}], [{nameof(GeneratePropertyBagsForTypeAttribute)}] or [{nameof(GeneratePropertyBagsForTypesQualifiedWithAttribute)}]"; + } + } + + /// + /// The exception that is thrown when trying to visit an invalid container type. + /// + [Serializable] + public class InvalidContainerTypeException : Exception + { + /// + /// The type which triggered the exception. + /// + public Type Type { get; } + + /// + /// Initializes a new instance of the class with a specified type. + /// + /// The invalid container type. + public InvalidContainerTypeException(Type type) : base(GetMessageForType(type)) + { + Type = type; + } + + /// + /// Initializes a new instance of the class with a specified type and a reference to the inner exception that is the cause of this exception. + /// + /// The invalid container type. + /// The inner exception reference. + public InvalidContainerTypeException(Type type, Exception inner) : base(GetMessageForType(type), inner) + { + Type = type; + } + + static string GetMessageForType(Type type) + { + return $"Invalid container Type=[{type.Name}.{type.Name}]"; + } + } + + /// + /// The exception that is thrown when trying to resolve an invalid path. + /// + [Serializable] + public class InvalidPathException : Exception + { + /// + /// Initializes a new instance of the class with a specified path. + /// + /// The exception message. + public InvalidPathException(string message) : base(message) + { + } + + /// + /// Initializes a new instance of the class with a specified type and a reference to the inner exception that is the cause of this exception. + /// + /// The exception message. + /// The inner exception reference. + public InvalidPathException(string message, Exception inner) : base(message, inner) + { + } + } +} diff --git a/Modules/Properties/Runtime/Properties/AttributesScope.cs b/Modules/Properties/Runtime/Properties/AttributesScope.cs new file mode 100644 index 0000000000..22b3754be6 --- /dev/null +++ b/Modules/Properties/Runtime/Properties/AttributesScope.cs @@ -0,0 +1,54 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using System.Collections.Generic; +using Unity.Properties.Internal; + +namespace Unity.Properties +{ + /// + /// Scope for using a given set of attributes. + /// + public readonly struct AttributesScope : IDisposable + { + readonly IAttributes m_Target; + readonly List m_Previous; + + /// + /// Initializes a new instance of the struct assigns the given attributes to the specified target. + /// + /// The target to set the attributes for. + /// The source to copy attributes from. + public AttributesScope(IProperty target, IProperty source) + { + m_Target = target as IAttributes; + m_Previous = (target as IAttributes)?.Attributes; + + if (null != m_Target) + m_Target.Attributes = (source as IAttributes)?.Attributes; + } + + /// + /// Initializes a new instance of the struct assigns the given attributes to the specified target. + /// + /// The target to set the attributes for. + /// The attributes to set. + internal AttributesScope(IAttributes target, List attributes) + { + m_Target = target; + m_Previous = target.Attributes; + target.Attributes = attributes; + } + + /// + /// Re-assigns the original attributes to the target. + /// + public void Dispose() + { + if (null != m_Target) + m_Target.Attributes = m_Previous; + } + } +} diff --git a/Modules/Properties/Runtime/Properties/DelegateProperty.cs b/Modules/Properties/Runtime/Properties/DelegateProperty.cs new file mode 100644 index 0000000000..35f20e1ba6 --- /dev/null +++ b/Modules/Properties/Runtime/Properties/DelegateProperty.cs @@ -0,0 +1,79 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; + +namespace Unity.Properties +{ + /// + /// Represents the method that will handle getting a type for a specified . + /// + /// The container which holds the data. + /// The strongly typed container. + /// The strongly typed value to get. + public delegate TValue PropertyGetter(ref TContainer container); + + /// + /// Represents the method that will handle setting a specified for a specified . + /// + /// The container on which to set the data. + /// The value to set. + /// The strongly typed container. + /// The strongly typed value to set. + public delegate void PropertySetter(ref TContainer container, TValue value); + + /// + /// Represents a value property. + /// + /// + /// A is the default way to construct properties. + /// + /// The container type this property will access data on. + /// The value type for this property. + public class DelegateProperty : Property + { + readonly PropertyGetter m_Getter; + readonly PropertySetter m_Setter; + + /// + public override string Name { get; } + + /// + public override bool IsReadOnly => null == m_Setter; + + /// + /// Initializes a new instance of the class. + /// + /// The property name. + /// The delegate to use when accessing the property value. + /// The delegate to use when setting the property value. + public DelegateProperty( + string name, + PropertyGetter getter, + PropertySetter setter = null) + { + Name = name; + m_Getter = getter ?? throw new ArgumentException(nameof(getter)); + m_Setter = setter; + } + + /// + public override TValue GetValue(ref TContainer container) + { + return m_Getter(ref container); + } + + /// + /// The property is read-only. + public override void SetValue(ref TContainer container, TValue value) + { + if (IsReadOnly) + { + throw new InvalidOperationException("Property is ReadOnly."); + } + + m_Setter(ref container, value); + } + } +} diff --git a/Modules/Properties/Runtime/Properties/ICollectionElementProperty.cs b/Modules/Properties/Runtime/Properties/ICollectionElementProperty.cs new file mode 100644 index 0000000000..3293ab83ed --- /dev/null +++ b/Modules/Properties/Runtime/Properties/ICollectionElementProperty.cs @@ -0,0 +1,70 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +namespace Unity.Properties +{ + /// + /// Base interface for working with a collection element property. + /// + public interface ICollectionElementProperty + { + + } + + /// + /// Interface over a property representing a list element. + /// + public interface IListElementProperty : ICollectionElementProperty + { + /// + /// The index of this property in the list. + /// + int Index { get; } + } + + /// + /// Interface over a property representing a set element. + /// + public interface ISetElementProperty : ICollectionElementProperty + { + /// + /// The key of this property in the set. + /// + object ObjectKey { get; } + } + + /// + /// Interface over a property representing a set element. + /// + public interface ISetElementProperty : ISetElementProperty + { + /// + /// The key of this property in the set. + /// + TKey Key { get; } + } + + /// + /// Interface over a property representing a untyped dictionary element. + /// + public interface IDictionaryElementProperty : ICollectionElementProperty + { + /// + /// The key of this property in the dictionary. + /// + object ObjectKey { get; } + } + + /// + /// Interface over a property representing a typed dictionary element. + /// + /// The key type. + public interface IDictionaryElementProperty : IDictionaryElementProperty + { + /// + /// The key of this property in the dictionary. + /// + TKey Key { get; } + } +} diff --git a/Modules/Properties/Runtime/Properties/Internal/IAttributes.cs b/Modules/Properties/Runtime/Properties/Internal/IAttributes.cs new file mode 100644 index 0000000000..c8d7fb7eda --- /dev/null +++ b/Modules/Properties/Runtime/Properties/Internal/IAttributes.cs @@ -0,0 +1,38 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using System.Collections.Generic; + +namespace Unity.Properties.Internal +{ + /// + /// Interface for attaching attributes to an object. This is an internal interface. + /// + interface IAttributes + { + /// + /// Gets access the the internal storage. + /// + List Attributes { get; set; } + + /// + /// Adds an attribute to this object. + /// + /// The attribute to add. + void AddAttribute(Attribute attribute); + + /// + /// Adds a set of attributes to this object. + /// + /// + void AddAttributes(IEnumerable attributes); + + /// + /// Sets the attributes for the duration of the scope. + /// + /// + AttributesScope CreateAttributesScope(IAttributes attributes); + } +} diff --git a/Modules/Properties/Runtime/Properties/Property.cs b/Modules/Properties/Runtime/Properties/Property.cs new file mode 100644 index 0000000000..c8a6ad0ab8 --- /dev/null +++ b/Modules/Properties/Runtime/Properties/Property.cs @@ -0,0 +1,271 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using System.Collections.Generic; +using Unity.Properties.Internal; + +namespace Unity.Properties +{ + /// + /// Base interface for working with properties. + /// + /// + /// This is used to pass or store properties without knowing the underlying container or value type. + /// * + /// * + /// + public interface IProperty + { + /// + /// Gets the name of the property. + /// + string Name { get; } + + /// + /// Gets a value indicating whether the property is read-only or not. + /// + bool IsReadOnly { get; } + + /// + /// Returns the declared value type of the property. + /// + /// The declared value type. + Type DeclaredValueType(); + + /// + /// Returns true if the property has any attributes of the given type. + /// + /// The attribute type to check for. + /// if the property has the given attribute type; otherwise, . + bool HasAttribute() + where TAttribute : Attribute; + + /// + /// Returns the first attribute of the given type. + /// + /// The attribute type to get. + /// The attribute of the given type for this property. + TAttribute GetAttribute() + where TAttribute : Attribute; + + /// + /// Returns all attribute of the given type. + /// + /// The attribute type to get. + /// An for all attributes of the given type. + IEnumerable GetAttributes() + where TAttribute : Attribute; + + /// + /// Returns all attribute for this property. + /// + /// An for all attributes. + IEnumerable GetAttributes(); + } + + /// + /// Base interface for working with properties. + /// + /// + /// This is used to pass or store properties without knowing the underlying value type. + /// * + /// + /// The container type this property operates on. + public interface IProperty : IProperty, IPropertyAccept + { + /// + /// Returns the property value of a specified container. + /// + /// The container whose property value will be returned. + /// The property value of the given container. + object GetValue(ref TContainer container); + + /// + /// Sets the property value of a specified container. + /// + /// The container whose property value will be set. + /// The new property value. + /// if the value was set; otherwise, . + void SetValue(ref TContainer container, object value); + } + + /// + /// Base class for implementing properties. This is an abstract class. + /// + /// + /// A is used as an accessor to the underlying data of a container. + /// + /// The container type this property operates on. + /// The value type for this property. + public abstract class Property : IProperty, IAttributes + { + List m_Attributes; + + /// + /// Collection of attributes for this . + /// + List IAttributes.Attributes + { + get => m_Attributes; + set => m_Attributes = value; + } + + /// + /// Gets the name of the property. + /// + public abstract string Name { get; } + + /// + /// Gets a value indicating whether the property is read-only or not. + /// + public abstract bool IsReadOnly { get; } + + /// + /// Returns the declared value type of the property. + /// + /// The declared value type. + public Type DeclaredValueType() => typeof(TValue); + + /// + /// Call this method to invoke with the strongly typed container and value. + /// + /// The visitor being run. + /// The container being visited. + public void Accept(IPropertyVisitor visitor, ref TContainer container) => visitor.Visit(this, ref container); + + /// + /// Returns the property value of a specified container. + /// + /// The container whose property value will be returned. + /// The property value of the given container. + object IProperty.GetValue(ref TContainer container) => GetValue(ref container); + + /// + /// Sets the property value of a specified container. + /// + /// The container whose property value will be set. + /// The new property value. + /// if the value was set; otherwise, . + void IProperty.SetValue(ref TContainer container, object value) => SetValue(ref container, TypeConversion.Convert(ref value)); + + /// + /// Returns the property value of a specified container. + /// + /// The container whose property value will be returned. + /// The property value of the given container. + public abstract TValue GetValue(ref TContainer container); + + /// + /// Sets the property value of a specified container. + /// + /// The container whose property value will be set. + /// The new property value. + public abstract void SetValue(ref TContainer container, TValue value); + + /// + /// Adds an attribute to the property. + /// + /// The attribute to add. + protected void AddAttribute(Attribute attribute) => ((IAttributes) this).AddAttribute(attribute); + + /// + /// Adds a set of attributes to the property. + /// + /// The attributes to add. + protected void AddAttributes(IEnumerable attributes) => ((IAttributes) this).AddAttributes(attributes); + + /// + void IAttributes.AddAttribute(Attribute attribute) + { + if (null == attribute || attribute.GetType() == typeof(CreatePropertyAttribute)) return; + if (null == m_Attributes) m_Attributes = new List(); + m_Attributes.Add(attribute); + } + + /// + /// Adds a set of attributes to the property. + /// + /// The attributes to add. + void IAttributes.AddAttributes(IEnumerable attributes) + { + if (null == m_Attributes) m_Attributes = new List(); + + foreach (var attribute in attributes) + { + if (null == attribute || attribute.GetType() == typeof(CreatePropertyAttribute)) + continue; + + m_Attributes.Add(attribute); + } + } + + /// + /// Returns true if the property has any attributes of the given type. + /// + /// The attribute type to check for. + /// if the property has the given attribute type; otherwise, . + public bool HasAttribute() where TAttribute : Attribute + { + for (var i = 0; i < m_Attributes?.Count; i++) + { + if (m_Attributes[i] is TAttribute) + { + return true; + } + } + + return default; + } + + /// + /// Returns the first attribute of the given type. + /// + /// The attribute type to get. + /// The attribute of the given type for this property. + public TAttribute GetAttribute() where TAttribute : Attribute + { + for (var i = 0; i < m_Attributes?.Count; i++) + { + if (m_Attributes[i] is TAttribute typed) + { + return typed; + } + } + + return default; + } + + /// + /// Returns all attribute of the given type. + /// + /// The attribute type to get. + /// An for all attributes of the given type. + public IEnumerable GetAttributes() where TAttribute : Attribute + { + for (var i = 0; i < m_Attributes?.Count; i++) + { + if (m_Attributes[i] is TAttribute typed) + { + yield return typed; + } + } + } + + /// + /// Returns all attribute for this property. + /// + /// An for all attributes. + public IEnumerable GetAttributes() + { + for (var i = 0; i < m_Attributes?.Count; i++) + { + yield return m_Attributes[i]; + } + } + + /// + AttributesScope IAttributes.CreateAttributesScope(IAttributes attributes) => new AttributesScope(this, attributes?.Attributes); + } +} diff --git a/Modules/Properties/Runtime/Properties/PropertyPath.cs b/Modules/Properties/Runtime/Properties/PropertyPath.cs new file mode 100644 index 0000000000..78ec57dbbd --- /dev/null +++ b/Modules/Properties/Runtime/Properties/PropertyPath.cs @@ -0,0 +1,919 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Text; + +namespace Unity.Properties +{ + /// + /// A specifies a type for a . + /// + public enum PropertyPathPartKind + { + /// + /// Represents a named part of the path. + /// + Name, + + /// + /// Represents an indexed part of the path. + /// + Index, + + /// + /// Represents a keyed part of the path. + /// + Key + } + + /// + /// A represents a single element of the path. + /// + /// + /// -> ".{name}" + /// -> "[{index}]" + /// -> "[{key}]" + /// + public readonly struct PropertyPathPart : IEquatable + { + readonly PropertyPathPartKind m_Kind; + readonly string m_Name; + readonly int m_Index; + readonly object m_Key; + + /// + /// Returns true if the part is . + /// + public bool IsName => Kind == PropertyPathPartKind.Name; + + /// + /// Returns true if the part is . + /// + public bool IsIndex => Kind == PropertyPathPartKind.Index; + + /// + /// Returns true if the part is . + /// + public bool IsKey => Kind == PropertyPathPartKind.Key; + + /// + /// The for this path. This determines how algorithms will resolve the path. + /// + public PropertyPathPartKind Kind => m_Kind; + + /// + /// The Name of the part. This will only be set when using + /// + public string Name + { + get + { + CheckKind(PropertyPathPartKind.Name); + return m_Name; + } + } + + /// + /// The Index of the part. This will only be set when using + /// + public int Index + { + get + { + CheckKind(PropertyPathPartKind.Index); + return m_Index; + } + } + + /// + /// The Key of the part. This will only be set when using + /// + public object Key + { + get + { + CheckKind(PropertyPathPartKind.Key); + return m_Key; + } + } + + /// + /// Initializes a new with the specified name. + /// + /// The name of the part. + public PropertyPathPart(string name) + { + m_Kind = PropertyPathPartKind.Name; + m_Name = name; + m_Index = -1; + m_Key = null; + } + + /// + /// Initializes a new with the specified index. + /// + /// The index of the part. + public PropertyPathPart(int index) + { + m_Kind = PropertyPathPartKind.Index; + m_Name = string.Empty; + m_Index = index; + m_Key = null; + } + + /// + /// Initializes a new with the specified key. + /// + /// The key of the part. + public PropertyPathPart(object key) + { + m_Kind = PropertyPathPartKind.Key; + m_Name = string.Empty; + m_Index = -1; + m_Key = key; + } + + // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local + [MethodImpl(MethodImplOptions.AggressiveInlining)] + void CheckKind(PropertyPathPartKind type) + { + if (type != Kind) throw new InvalidOperationException(); + } + + /// + public override string ToString() + { + return Kind switch + { + PropertyPathPartKind.Name => m_Name, + PropertyPathPartKind.Index => "[" + m_Index + "]", + PropertyPathPartKind.Key => "[\"" + m_Key + "\"]", + _ => throw new ArgumentOutOfRangeException() + }; + } + + /// + /// Indicates whether this instance and a specified object are equal. + /// + /// The object to compare with the current instance. + /// if obj and this instance are the same type and represent the same value; otherwise, . + public bool Equals(PropertyPathPart other) + { + return m_Kind == other.m_Kind && m_Name == other.m_Name && m_Index == other.m_Index && Equals(m_Key, other.m_Key); + } + + /// + public override bool Equals(object obj) + { + return obj is PropertyPathPart other && Equals(other); + } + + /// + [SuppressMessage("ReSharper", "NonReadonlyMemberInGetHashCode")] + public override int GetHashCode() + { + unchecked + { + var hashCode = (int) m_Kind; + + hashCode = m_Kind switch + { + PropertyPathPartKind.Name => (hashCode * 397) ^ (m_Name != null ? m_Name.GetHashCode() : 0), + PropertyPathPartKind.Index => (hashCode * 397) ^ m_Index, + PropertyPathPartKind.Key => (hashCode * 397) ^ (m_Key != null ? m_Key.GetHashCode() : 0), + _ => throw new ArgumentOutOfRangeException() + }; + return hashCode; + } + } + } + + /// + /// A is used to store a reference to a single property within a tree. + /// + /// + /// The path is stored as an array of parts and can be easily queried for algorithms. + /// + public readonly struct PropertyPath : IEquatable + { + internal const int k_InlineCount = 4; + + readonly PropertyPathPart m_Part0; + readonly PropertyPathPart m_Part1; + readonly PropertyPathPart m_Part2; + readonly PropertyPathPart m_Part3; + readonly int m_InlinePartsCount; + readonly PropertyPathPart[] m_AdditionalParts; + + /// + /// Gets the number of parts contained in the . + /// + public int Length { get; } + + /// + /// Gets if there is any part contained in the . + /// + public bool IsEmpty => Length == 0; + + /// + /// Gets the at the given index. + /// + public PropertyPathPart this[int index] + { + get + { + switch (index) + { + case 0: + { + if (Length < 1) + throw new IndexOutOfRangeException(); + return m_Part0; + } + case 1: + { + if (Length < 2) + throw new IndexOutOfRangeException(); + return m_Part1; + } + case 2: + { + if (Length < 3) + throw new IndexOutOfRangeException(); + return m_Part2; + } + case 3: + { + if (Length < 4) + throw new IndexOutOfRangeException(); + return m_Part3; + } + default: return m_AdditionalParts[index - k_InlineCount]; + } + } + } + + /// + /// Initializes a new instance of the based on the given property string. + /// + /// The string path to initialize this instance with. + public PropertyPath(string path) + { + var p = ConstructFromPath(path); + m_Part0 = p.m_Part0; + m_Part1 = p.m_Part1; + m_Part2 = p.m_Part2; + m_Part3 = p.m_Part3; + m_AdditionalParts = p.m_AdditionalParts; + m_InlinePartsCount = p.m_InlinePartsCount; + Length = m_InlinePartsCount + (m_AdditionalParts?.Length ?? 0); + } + + PropertyPath(in PropertyPathPart part) + { + m_Part0 = part; + m_Part1 = default; + m_Part2 = default; + m_Part3 = default; + m_AdditionalParts = default; + m_InlinePartsCount = 1; + Length = 1; + } + + PropertyPath(in PropertyPathPart part0, in PropertyPathPart part1) + { + m_Part0 = part0; + m_Part1 = part1; + m_Part2 = default; + m_Part3 = default; + m_AdditionalParts = default; + m_InlinePartsCount = 2; + Length = 2; + } + + PropertyPath(in PropertyPathPart part0, in PropertyPathPart part1, in PropertyPathPart part2) + { + m_Part0 = part0; + m_Part1 = part1; + m_Part2 = part2; + m_Part3 = default; + m_AdditionalParts = default; + m_InlinePartsCount = 3; + Length = 3; + } + + PropertyPath(in PropertyPathPart part0, in PropertyPathPart part1, in PropertyPathPart part2, in PropertyPathPart part3) + { + m_Part0 = part0; + m_Part1 = part1; + m_Part2 = part2; + m_Part3 = part3; + m_AdditionalParts = default; + m_InlinePartsCount = 4; + Length = 4; + } + + internal PropertyPath(List parts) + { + m_Part0 = default; + m_Part1 = default; + m_Part2 = default; + m_Part3 = default; + m_InlinePartsCount = 0; + m_AdditionalParts = parts.Count > k_InlineCount + ? new PropertyPathPart[parts.Count - k_InlineCount] + : default; + + for (var i = 0; i < parts.Count; ++i) + { + switch (i) + { + case 0: + m_Part0 = parts[i]; + ++m_InlinePartsCount; + break; + case 1: + m_Part1 = parts[i]; + ++m_InlinePartsCount; + break; + case 2: + m_Part2 = parts[i]; + ++m_InlinePartsCount; + break; + case 3: + m_Part3 = parts[i]; + ++m_InlinePartsCount; + break; + default: + // ReSharper disable once PossibleNullReferenceException + m_AdditionalParts[i - k_InlineCount] = parts[i]; + break; + } + } + Length = parts.Count; + } + + /// + /// Returns a new from the provided . + /// + /// The + /// A new + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PropertyPath FromPart(in PropertyPathPart part) + => new PropertyPath(part); + + /// + /// Returns a new from the provided name. + /// + /// The name of the . + /// A new + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PropertyPath FromName(string name) + => new PropertyPath(new PropertyPathPart(name)); + + /// + /// Returns a new from the provided index. + /// + /// The index of the . + /// A new + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PropertyPath FromIndex(int index) + => new PropertyPath(new PropertyPathPart(index)); + + /// + /// Returns a new from the provided key. + /// + /// The key of the . + /// A new + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PropertyPath FromKey(object key) + => new PropertyPath(new PropertyPathPart(key)); + + /// + /// Returns a new combining the parts of the two given . + /// + /// The + /// The to append. + /// A new + public static PropertyPath Combine(in PropertyPath path, in PropertyPath pathToAppend) + { + if (path.IsEmpty) + return pathToAppend; + + if (pathToAppend.IsEmpty) + return path; + + var firstPathLength = path.Length; + var secondPathLength = pathToAppend.Length; + var totalCount = firstPathLength + secondPathLength; + if (totalCount <= k_InlineCount) + { + var secondIndex = 0; + var part0 = path[0]; + var part1 = firstPathLength > 1 ? path[1] : pathToAppend[secondIndex++]; + var part2 = totalCount > 2 ? (firstPathLength > 2 ? path[2] : pathToAppend[secondIndex++]) : default; + var part3 = totalCount > 3 ? (firstPathLength > 3 ? path[3] : pathToAppend[secondIndex]) : default; + + switch (totalCount) + { + case 2: return new PropertyPath(part0, part1); + case 3: return new PropertyPath(part0, part1, part2); + case 4: return new PropertyPath(part0, part1, part2, part3); + } + } + + var parts = UnityEngine.Pool.ListPool.Get(); + try + { + GetParts(path, parts); + GetParts(pathToAppend, parts); + return new PropertyPath(parts); + } + finally + { + UnityEngine.Pool.ListPool.Release(parts); + } + } + + /// + /// Returns a new combining the parts of the two given . + /// + /// The + /// The string path to append. + /// A new + public static PropertyPath Combine(in PropertyPath path, string pathToAppend) + { + if (string.IsNullOrEmpty(pathToAppend)) + return path; + + var other = new PropertyPath(pathToAppend); + return Combine(path, other); + } + + /// + /// Returns a new combining the given and . + /// + /// The + /// The part to add. + /// A new + public static PropertyPath AppendPart(in PropertyPath path, in PropertyPathPart part) + { + if (path.IsEmpty) + return new PropertyPath(part); + + switch (path.Length + 1) + { + case 2: + return new PropertyPath(path[0], part); + case 3: + return new PropertyPath(path[0], path[1], part); + case 4: + return new PropertyPath(path[0], path[1], path[2], part); + default: + var parts = UnityEngine.Pool.ListPool.Get(); + try + { + GetParts(path, parts); + parts.Add(part); + return new PropertyPath(parts); + } + finally + { + UnityEngine.Pool.ListPool.Release(parts); + } + } + } + + /// + /// Returns a new combining the given and an name-type + /// . + /// + /// The + /// The part name to add. + /// A new + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PropertyPath AppendName(in PropertyPath path, string name) + => AppendPart(path, new PropertyPathPart(name)); + + /// + /// Returns a new combining the given and an index-type + /// . + /// + /// The + /// The index to add. + /// A new + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PropertyPath AppendIndex(in PropertyPath path, int index) + => AppendPart(path, new PropertyPathPart(index)); + + /// + /// Returns a new combining the given and an key-type + /// . + /// + /// The + /// The key to add. + /// A new + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PropertyPath AppendKey(in PropertyPath path, object key) + => AppendPart(path, new PropertyPathPart(key)); + + /// + /// Returns a new combining the given and a + /// whose type will be based on the property interfaces. + /// + /// The + /// The property to add. + /// A new + public static PropertyPath AppendProperty(in PropertyPath path, IProperty property) + { + return property switch + { + IListElementProperty listElementProperty => AppendPart(path, new PropertyPathPart(listElementProperty.Index)), + ISetElementProperty setElementProperty => AppendPart(path, new PropertyPathPart(setElementProperty.ObjectKey)), + IDictionaryElementProperty dictionaryElementProperty => AppendPart(path, new PropertyPathPart(dictionaryElementProperty.ObjectKey)), + _ => AppendPart(path, new PropertyPathPart(property.Name)) + }; + } + + /// + /// Returns a new that will not include the last . + /// + /// The + /// A new + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PropertyPath Pop(in PropertyPath path) + => SubPath(path, 0, path.Length - 1); + + /// + /// Returns a new containing the starting at the given + /// start index. + /// + /// The + /// The zero-based index where the sub path should start. + /// A new + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PropertyPath SubPath(in PropertyPath path, int startIndex) + => SubPath(path, startIndex, path.Length - startIndex); + + /// + /// Returns a new containing the given number of + /// starting at the given start index. + /// + /// The + /// The zero-based index where the sub path should start. + /// The number of parts to include. + /// A new + /// + public static PropertyPath SubPath(in PropertyPath path, int startIndex, int length) + { + var count = path.Length; + if (startIndex < 0) + throw new ArgumentOutOfRangeException(nameof(startIndex)); + if (startIndex > count) + throw new ArgumentOutOfRangeException(nameof(startIndex)); + if (length < 0) + throw new ArgumentOutOfRangeException(nameof(length)); + if (startIndex > count - length) + throw new ArgumentOutOfRangeException(nameof(length)); + if (length == 0) + return default; + + if (startIndex == 0 && length == count) + return path; + + switch (length) + { + case 1: return new PropertyPath(path[startIndex]); + case 2: return new PropertyPath(path[startIndex], path[startIndex + 1]); + case 3: return new PropertyPath(path[startIndex], path[startIndex + 1], path[startIndex + 2]); + case 4: return new PropertyPath(path[startIndex], path[startIndex + 1], path[startIndex + 2], path[startIndex + 3]); + } + + var parts = UnityEngine.Pool.ListPool.Get(); + + try + { + for (var i = startIndex; i < startIndex + length; ++i) + parts.Add(path[i]); + return new PropertyPath(parts); + } + finally + { + UnityEngine.Pool.ListPool.Release(parts); + } + } + + /// + public override string ToString() + { + if (Length == 0) + return string.Empty; + + if (Length == 1 && m_Part0.IsName) + return m_Part0.Name; + + var builder = new StringBuilder(32); + + if (Length > 0) + AppendToBuilder(m_Part0, builder); + if (Length > 1) + AppendToBuilder(m_Part1, builder); + if (Length > 2) + AppendToBuilder(m_Part2, builder); + if (Length > 3) + AppendToBuilder(m_Part3, builder); + + if (Length > k_InlineCount) + { + foreach (var part in m_AdditionalParts) + { + AppendToBuilder(part, builder); + } + } + + return builder.ToString(); + } + + static void AppendToBuilder(in PropertyPathPart part, StringBuilder builder) + { + switch (part.Kind) + { + case PropertyPathPartKind.Name: + if (builder.Length > 0) + builder.Append('.'); + builder.Append(part.ToString()); + break; + + case PropertyPathPartKind.Index: + case PropertyPathPartKind.Key: + builder.Append(part.ToString()); + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + static void GetParts(in PropertyPath path, List parts) + { + var count = path.Length; + for (var i = 0; i < count; ++i) + parts.Add(path[i]); + } + + static PropertyPath ConstructFromPath(string path) + { + if (string.IsNullOrEmpty(path)) + return default; + + const int matchAny = 0; + const int matchName = 1; + const int matchIndexOrKey = 2; + const int matchIndex = 3; + const int matchKey = 4; + + var index = matchAny; + var length = path.Length; + + var state = 0; + + void TrimStart() + { + while (index < length && path[index] == ' ') + ++index; + } + + void ReadNext() + { + if (index == length) + { + state = matchAny; + return; + } + + switch (path[index]) + { + case '.': + ++index; + state = matchAny; + return; + case '[': + state = matchIndexOrKey; + return; + default: + throw new ArgumentException($"{nameof(PropertyPath)}: Invalid '{path[index]}' character encountered at index '{index}'."); + } + } + + var parts = UnityEngine.Pool.ListPool.Get(); + + try + { + parts.Clear(); + while (index < length) + { + switch (state) + { + case matchAny: + TrimStart(); + + if (index == length) + break; + + if (path[index] == '.') + throw new ArgumentException($"{nameof(PropertyPath)}: Invalid '{path[index]}' character encountered at index '{index}'."); + + if (path[index] == '[') + { + state = matchIndexOrKey; + continue; + } + + if (path[index] == '"') + throw new ArgumentException($"{nameof(PropertyPath)}: Invalid '{path[index]}' character encountered at index '{index}'."); + + state = matchName; + continue; + case matchName: + { + var startIndex = index; + while (index < length) + { + if (path[index] == '.' || path[index] == '[') + break; + ++index; + } + + if (startIndex == index) + throw new ArgumentException($"Invalid {nameof(PropertyPath)}: Name is empty."); + + if (index == length) + { + parts.Add(new PropertyPathPart(path.Substring(startIndex))); + state = matchAny; + continue; + } + + parts.Add(new PropertyPathPart(path.Substring(startIndex, index-startIndex))); + + ReadNext(); + continue; + } + case matchIndexOrKey: + if (path[index] != '[') + throw new ArgumentException($"{nameof(PropertyPath)}: Invalid '{path[index]}' character encountered at index '{index}'."); + + if (index + 1 < length && path[index + 1] == '"') + { + state = matchKey; + continue; + } + + state = matchIndex; + continue; + case matchIndex: + { + if (path[index] != '[') + throw new ArgumentException($"{nameof(PropertyPath)}: Invalid '{path[index]}' character encountered at index '{index}'."); + ++index; + + var startIndex = index; + + while (index < length) + { + var ci = path[index]; + if (ci == ']') + break; + ++index; + } + + if (path[index] != ']') + throw new ArgumentException($"{nameof(PropertyPath)}: Invalid '{path[index]}' character encountered at index '{index}'."); + + var indexStr = path.Substring(startIndex, index-startIndex); + if (!int.TryParse(indexStr, out var partIndex)) + throw new ArgumentException($"Indices in {nameof(PropertyPath)} must be a numeric value."); + + if (partIndex < 0) + throw new ArgumentException($"Invalid {nameof(PropertyPath)}: Negative indices are not supported."); + + parts.Add(new PropertyPathPart(partIndex)); + ++index; + + if (index == length) + break; + + ReadNext(); + continue; + } + + case matchKey: + { + if (path[index] != '[') + throw new ArgumentException($"{nameof(PropertyPath)}: Invalid '{path[index]}' character encountered at index '{index}'."); + ++index; + + if (path[index] != '"') + throw new ArgumentException($"{nameof(PropertyPath)}: Invalid '{path[index]}' character encountered at index '{index}'."); + ++index; + + var startIndex = index; + + while (index < length) + { + var ci = path[index]; + if (ci == '"') + break; + ++index; + } + + if (path[index] != '"') + throw new ArgumentException($"{nameof(PropertyPath)}: Invalid '{path[index]}' character encountered at index '{index}'."); + + if (index + 1 < length && path[index + 1] == ']') + { + var keyStr = path.Substring(startIndex, index - startIndex); + parts.Add(new PropertyPathPart((object) keyStr)); + index += 2; // "] + + ReadNext(); + continue; + } + + throw new ArgumentException($"Invalid {nameof(PropertyPath)}: No matching end quote for key."); + } + } + } + + return new PropertyPath(parts); + } + finally + { + UnityEngine.Pool.ListPool.Release(parts); + } + } + + /// + /// Indicates whether this instance and a specified object are equal. + /// + /// The object to compare with the current instance. + /// if obj and this instance are the same type and represent the same value; otherwise, . + public bool Equals(PropertyPath other) + { + if (Length != other.Length) + return false; + + for (var i = 0; i < Length; ++i) + { + if (!this[i].Equals(other[i])) + { + return false; + } + } + + return true; + } + + /// + public override bool Equals(object obj) + { + if (obj is PropertyPath path) + return Equals(path); + return false; + } + + /// + [SuppressMessage("ReSharper", "NonReadonlyMemberInGetHashCode")] + public override int GetHashCode() + { + var hashcode = 19; + var count = Length; + + if (count == 0) + return hashcode; + + if (count > 0) + hashcode = hashcode * 31 + m_Part0.GetHashCode(); + if (count > 1) + hashcode = hashcode * 31 + m_Part1.GetHashCode(); + if (count > 2) + hashcode = hashcode * 31 + m_Part2.GetHashCode(); + if (count > 3) + hashcode = hashcode * 31 + m_Part3.GetHashCode(); + + if (count <= k_InlineCount) + return hashcode; + + foreach (var part in m_AdditionalParts) + { + hashcode = hashcode * 31 + part.GetHashCode(); + } + + return hashcode; + } + } +} diff --git a/Modules/Properties/Runtime/Properties/ReflectedMemberProperty.cs b/Modules/Properties/Runtime/Properties/ReflectedMemberProperty.cs new file mode 100644 index 0000000000..f826e0505f --- /dev/null +++ b/Modules/Properties/Runtime/Properties/ReflectedMemberProperty.cs @@ -0,0 +1,302 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using System.Collections.Generic; +using System.Reflection; +using Unity.Collections; + +using System.Reflection.Emit; + +namespace Unity.Properties +{ + /// + /// Common interface between and for getting and setting values. + /// + interface IMemberInfo + { + /// + /// Gets the reflected name. + /// + string Name { get; } + + /// + /// Gets the value indicating if this member is read-only. + /// + bool IsReadOnly { get; } + + /// + /// Gets the value type for the getter and setter. + /// + Type ValueType { get; } + + /// + /// Gets the value of the member for the given object. + /// + /// The object whose member value will be returned. + /// An object containing the value of the member reflected by this instance. + object GetValue(object obj); + + /// + /// Sets the value of the member for the given object to the given value. + /// + /// The object whose member value will be set. + /// The value to assign to the member. + void SetValue(object obj, object value); + + /// + /// Retrieves a collection of custom attributes that are applied to this member. + /// + /// A collection of the custom attributes that are applied this member, or an empty collection if no such attributes exist. + IEnumerable GetCustomAttributes(); + } + + readonly struct FieldMember : IMemberInfo + { + internal readonly FieldInfo m_FieldInfo; + + /// + /// Initializes a new instance. + /// + /// The backing object. + public FieldMember(FieldInfo fieldInfo) + { + m_FieldInfo = fieldInfo; + } + + /// + public string Name => m_FieldInfo.Name; + + /// + public bool IsReadOnly => m_FieldInfo.IsInitOnly; + + /// + public Type ValueType => m_FieldInfo.FieldType; + + /// + public object GetValue(object obj) => m_FieldInfo.GetValue(obj); + + /// + public void SetValue(object obj, object value) => m_FieldInfo.SetValue(obj, value); + + /// + public IEnumerable GetCustomAttributes() => m_FieldInfo.GetCustomAttributes(); + } + + readonly struct PropertyMember : IMemberInfo + { + internal readonly PropertyInfo m_PropertyInfo; + + /// + public string Name => m_PropertyInfo.Name; + + /// + public bool IsReadOnly => !m_PropertyInfo.CanWrite; + + /// + public Type ValueType => m_PropertyInfo.PropertyType; + + /// + /// Initializes a new instance. + /// + /// The backing object. + public PropertyMember(PropertyInfo propertyInfo) => m_PropertyInfo = propertyInfo; + + /// + public object GetValue(object obj) => m_PropertyInfo.GetValue(obj); + + /// + public void SetValue(object obj, object value) => m_PropertyInfo.SetValue(obj, value); + + /// + public IEnumerable GetCustomAttributes() => m_PropertyInfo.GetCustomAttributes(); + } + + /// + /// A provides strongly typed access to an underlying or object. + /// + /// + /// The implementation uses slow reflection calls internally. This is intended to be used as an intermediate solution for quick editor iteration. + /// + /// The container type for this property. + /// The value type for this property. + public class ReflectedMemberProperty : Property + { + readonly IMemberInfo m_Info; + readonly bool m_IsStructContainerType; + + delegate TValue GetStructValueAction(ref TContainer container); + delegate void SetStructValueAction(ref TContainer container, TValue value); + delegate TValue GetClassValueAction(TContainer container); + delegate void SetClassValueAction(TContainer container, TValue value); + + GetStructValueAction m_GetStructValueAction; + SetStructValueAction m_SetStructValueAction; + GetClassValueAction m_GetClassValueAction; + SetClassValueAction m_SetClassValueAction; + + /// + public override string Name { get; } + + /// + public override bool IsReadOnly { get; } + + /// + /// Initializes a new instance for the specified . + /// + /// The system reflection field info. + /// Use this name property--this might override the MemberInfo name + public ReflectedMemberProperty(FieldInfo info, string name) : this(new FieldMember(info), name) + { + + } + + /// + /// Initializes a new instance for the specified . + /// + /// The system reflection property info. + /// Use this name property--this might override the MemberInfo name + public ReflectedMemberProperty(PropertyInfo info, string name) : this(new PropertyMember(info), name) + { + + } + + /// + /// Initializes a new instance. This is an internal constructor. + /// + /// The reflected info object backing this property. + /// Use this name property--this might override the MemberInfo name + internal ReflectedMemberProperty(IMemberInfo info, string name) + { + Name = name; + m_Info = info; + m_IsStructContainerType = TypeTraits.IsValueType; + + AddAttributes(info.GetCustomAttributes()); + var isReadOnly = m_Info.IsReadOnly || HasAttribute(); + IsReadOnly = isReadOnly; + + if (m_Info is FieldMember fieldMember) + { + // TODO: optimize for NET_STANDARD, where DynamicMethod is not available by default + var fieldInfo = fieldMember.m_FieldInfo; + + // getter + var dynamicMethod = new DynamicMethod(string.Empty, fieldInfo.FieldType, new Type[] + { + m_IsStructContainerType ? fieldInfo.ReflectedType.MakeByRefType() : fieldInfo.ReflectedType + }, true); + + var ilGenerator = dynamicMethod.GetILGenerator(); + ilGenerator.Emit(OpCodes.Ldarg_0); + ilGenerator.Emit(OpCodes.Ldfld, fieldInfo); + ilGenerator.Emit(OpCodes.Ret); + + if (m_IsStructContainerType) + m_GetStructValueAction = (GetStructValueAction)dynamicMethod.CreateDelegate(typeof(GetStructValueAction)); + else + m_GetClassValueAction = (GetClassValueAction)dynamicMethod.CreateDelegate(typeof(GetClassValueAction)); + + // settter + if (!isReadOnly) + { + dynamicMethod = new DynamicMethod(string.Empty, typeof(void), new Type[] + { + m_IsStructContainerType ? fieldInfo.ReflectedType.MakeByRefType() : fieldInfo.ReflectedType, + fieldInfo.FieldType + }, true); + + ilGenerator = dynamicMethod.GetILGenerator(); + ilGenerator.Emit(OpCodes.Ldarg_0); + ilGenerator.Emit(OpCodes.Ldarg_1); + ilGenerator.Emit(OpCodes.Stfld, fieldInfo); + ilGenerator.Emit(OpCodes.Ret); + + if (m_IsStructContainerType) + m_SetStructValueAction = (SetStructValueAction)dynamicMethod.CreateDelegate(typeof(SetStructValueAction)); + else + m_SetClassValueAction = (SetClassValueAction)dynamicMethod.CreateDelegate(typeof(SetClassValueAction)); + } + } + else if (m_Info is PropertyMember propertyMember) + { + if (m_IsStructContainerType) + { + var getMethod = propertyMember.m_PropertyInfo.GetGetMethod(true); + m_GetStructValueAction = (GetStructValueAction)Delegate.CreateDelegate(typeof(GetStructValueAction), getMethod); + if (!isReadOnly) + { + var setMethod = propertyMember.m_PropertyInfo.GetSetMethod(true); + m_SetStructValueAction = (SetStructValueAction)Delegate.CreateDelegate(typeof(SetStructValueAction), setMethod); + } + } + else + { + var getMethod = propertyMember.m_PropertyInfo.GetGetMethod(true); + m_GetClassValueAction = (GetClassValueAction)Delegate.CreateDelegate(typeof(GetClassValueAction), getMethod); + if (!isReadOnly) + { + var setMethod = propertyMember.m_PropertyInfo.GetSetMethod(true); + m_SetClassValueAction = (SetClassValueAction)Delegate.CreateDelegate(typeof(SetClassValueAction), setMethod); + } + } + } + } + + /// + public override TValue GetValue(ref TContainer container) + { + if (m_IsStructContainerType) + { + return m_GetStructValueAction == null + ? (TValue)m_Info.GetValue(container) // boxing + : m_GetStructValueAction(ref container); // no boxing + } + else + { + return m_GetClassValueAction == null + ? (TValue)m_Info.GetValue(container) // boxing + : m_GetClassValueAction(container); // no boxing + } + } + + /// + public override void SetValue(ref TContainer container, TValue value) + { + if (IsReadOnly) + { + throw new InvalidOperationException("Property is ReadOnly."); + } + + if (m_IsStructContainerType) + { + if (m_SetStructValueAction == null) + { + // boxing + var boxed = (object)container; + m_Info.SetValue(boxed, value); + container = (TContainer)boxed; + } + else + { + // no boxing + m_SetStructValueAction(ref container, value); + } + } + else + { + if (m_SetClassValueAction == null) + { + // boxing + m_Info.SetValue(container, value); + } + else + { + // no boxing + m_SetClassValueAction(container, value); + } + } + } + } +} diff --git a/Modules/Properties/Runtime/PropertyBags/ArrayPropertyBag.cs b/Modules/Properties/Runtime/PropertyBags/ArrayPropertyBag.cs new file mode 100644 index 0000000000..32e0ea2516 --- /dev/null +++ b/Modules/Properties/Runtime/PropertyBags/ArrayPropertyBag.cs @@ -0,0 +1,24 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; + +namespace Unity.Properties +{ + /// + /// An implementation for a built in array of . + /// + /// The element type. + public sealed class ArrayPropertyBag : IndexedCollectionPropertyBag + { + /// + protected override InstantiationKind InstantiationKind => InstantiationKind.PropertyBagOverride; + + /// + protected override TElement[] InstantiateWithCount(int count) => new TElement[count]; + + /// + protected override TElement[] Instantiate() => Array.Empty(); + } +} diff --git a/Modules/Properties/Runtime/PropertyBags/ContainerPropertyBag.cs b/Modules/Properties/Runtime/PropertyBags/ContainerPropertyBag.cs new file mode 100644 index 0000000000..2f097420bb --- /dev/null +++ b/Modules/Properties/Runtime/PropertyBags/ContainerPropertyBag.cs @@ -0,0 +1,63 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using System.Collections.Generic; + +namespace Unity.Properties +{ + /// + /// Base class for implementing a static property bag for a specified container type. This is an abstract class. + /// + /// + /// A is used to describe and traverse the properties for a specified type. + /// + /// In order for properties to operate on a type, a must exist and be pre-registered for that type. + /// + /// _NOTE_ In editor use cases property bags can be generated dynamically through reflection. (see Unity.Properties.Reflection) + /// + /// The container type. + public abstract class ContainerPropertyBag : PropertyBag, INamedProperties + { + static ContainerPropertyBag() + { + if (!TypeTraits.IsContainer(typeof(TContainer))) + { + throw new InvalidOperationException($"Failed to create a property bag for Type=[{typeof(TContainer)}]. The type is not a valid container type."); + } + } + + readonly List> m_PropertiesList = new List>(); + readonly Dictionary> m_PropertiesHash = new Dictionary>(); + + /// + /// Adds a to the property bag. + /// + /// The to add. + /// The value type for the given property. + protected void AddProperty(Property property) + { + m_PropertiesList.Add(property); + m_PropertiesHash.Add(property.Name, property); + } + + /// + public override PropertyCollection GetProperties() + => new PropertyCollection(m_PropertiesList); + + /// + public override PropertyCollection GetProperties(ref TContainer container) + => new PropertyCollection(m_PropertiesList); + + /// + /// Gets the property associated with the specified name. + /// + /// The container hosting the data. + /// The name of the property to get. + /// When this method returns, contains the property associated with the specified name, if the name is found; otherwise, null. + /// if the contains a property with the specified name; otherwise, . + public bool TryGetProperty(ref TContainer container, string name, out IProperty property) + => m_PropertiesHash.TryGetValue(name, out property); + } +} diff --git a/Modules/Properties/Runtime/PropertyBags/DictionaryPropertyBag.cs b/Modules/Properties/Runtime/PropertyBags/DictionaryPropertyBag.cs new file mode 100644 index 0000000000..cccf9f91a8 --- /dev/null +++ b/Modules/Properties/Runtime/PropertyBags/DictionaryPropertyBag.cs @@ -0,0 +1,22 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System.Collections.Generic; + +namespace Unity.Properties +{ + /// + /// An implementation for a type. + /// + /// The key type. + /// The value type. + public class DictionaryPropertyBag : KeyValueCollectionPropertyBag, TKey, TValue> + { + /// + protected override InstantiationKind InstantiationKind => InstantiationKind.PropertyBagOverride; + + /// + protected override Dictionary Instantiate() => new Dictionary(); + } +} diff --git a/Modules/Properties/Runtime/PropertyBags/HashSetPropertyBag.cs b/Modules/Properties/Runtime/PropertyBags/HashSetPropertyBag.cs new file mode 100644 index 0000000000..02c81e486d --- /dev/null +++ b/Modules/Properties/Runtime/PropertyBags/HashSetPropertyBag.cs @@ -0,0 +1,21 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System.Collections.Generic; + +namespace Unity.Properties +{ + /// + /// An implementation for a type. + /// + /// The element type. + public class HashSetPropertyBag : SetPropertyBagBase, TElement> + { + /// + protected override InstantiationKind InstantiationKind => InstantiationKind.PropertyBagOverride; + + /// + protected override HashSet Instantiate() => new HashSet(); + } +} diff --git a/Modules/Properties/Runtime/PropertyBags/IPropertyBag.cs b/Modules/Properties/Runtime/PropertyBags/IPropertyBag.cs new file mode 100644 index 0000000000..10dd73c1e7 --- /dev/null +++ b/Modules/Properties/Runtime/PropertyBags/IPropertyBag.cs @@ -0,0 +1,154 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System.Collections.Generic; + +namespace Unity.Properties +{ + /// + /// This interface provides access to an of a by index. + /// + /// The container type to access. + public interface IIndexedProperties + { + /// + /// Gets the property associated with the specified index. + /// + /// The container hosting the data. + /// The index of the property to get. + /// When this method returns, contains the property associated with the specified index, if the name is found; otherwise, null. + /// if the contains a property for the specified index; otherwise, . + bool TryGetProperty(ref TContainer container, int index, out IProperty property); + } + + /// + /// This interface provides access to an of a by name. + /// + /// The container type to access. + public interface INamedProperties + { + /// + /// Gets the property associated with the specified name. + /// + /// The container hosting the data. + /// The name of the property to get. + /// When this method returns, contains the property associated with the specified name, if the name is found; otherwise, null. + /// if the contains a property with the specified name; otherwise, . + bool TryGetProperty(ref TContainer container, string name, out IProperty property); + } + + /// + /// This interface provides access to an of a by a key. + /// + /// The container type to access. + /// The key type to access the property with. + public interface IKeyedProperties + { + /// + /// Gets the property associated with the specified name. + /// + /// The container hosting the data. + /// The key to lookup. + /// When this method returns, contains the property associated with the specified name, if the name is found; otherwise, null. + /// if the contains a property with the specified name; otherwise, . + bool TryGetProperty(ref TContainer container, TKey key, out IProperty property); + } + + /// + /// Base untyped interface for implementing property bags. + /// + public interface IPropertyBag + { + /// + /// Call this method to invoke with the strongly typed container type. + /// + /// The visitor being run. + void Accept(ITypeVisitor visitor); + + /// + /// Call this method to invoke with the strongly typed container for the given object. + /// + /// The visitor to invoke the visit callback on. + /// The container being visited. + void Accept(IPropertyBagVisitor visitor, ref object container); + } + + /// + /// Base typed interface for implementing property bags. + /// + public interface IPropertyBag : IPropertyBag + { + /// + /// Returns an enumerator that iterates through all static properties for the type. + /// + /// + /// This should return a subset properties returned by . + /// + /// A structure for all properties. + PropertyCollection GetProperties(); + + /// + /// Returns an enumerator that iterates through all static and dynamic properties for the given container. + /// + /// + /// This should return all static properties returned by in addition to any dynamic properties. + /// If the container is a collection type all elements will be iterated. + /// + /// The container hosting the data. + /// A structure for all properties. + PropertyCollection GetProperties(ref TContainer container); + + /// + /// Creates and returns a new instance of . + /// + /// A new instance of . + TContainer CreateInstance(); + + /// + /// Tries to create a new instance of . + /// + /// When this method returns, contains the created instance, if type construction succeeded; otherwise, the default value for . + /// if a new instance of type was created; otherwise, . + bool TryCreateInstance(out TContainer instance); + + /// + /// Call this method to invoke with the strongly typed container. + /// + /// The visitor being run. + /// The container being visited. + void Accept(IPropertyBagVisitor visitor, ref TContainer container); + } + + /// + /// Base untyped interface for implementing collection based property bags. + /// + public interface ICollectionPropertyBag : IPropertyBag, ICollectionPropertyBagAccept + where TCollection : ICollection + { + } + + /// + /// Base typed interface for implementing list based property bags. + /// + public interface IListPropertyBag : ICollectionPropertyBag, IListPropertyBagAccept, IListPropertyAccept, IIndexedProperties + where TList : IList + { + } + + /// + /// Base typed interface for implementing set based property bags. + /// + public interface ISetPropertyBag : ICollectionPropertyBag, ISetPropertyBagAccept, ISetPropertyAccept, IKeyedProperties + where TSet : ISet + { + } + + /// + /// Base typed interface for implementing dictionary based property bags. + /// + public interface IDictionaryPropertyBag : ICollectionPropertyBag>, IDictionaryPropertyBagAccept, IDictionaryPropertyAccept, IKeyedProperties + where TDictionary : IDictionary + { + } +} diff --git a/Modules/Properties/Runtime/PropertyBags/IndexedCollectionPropertyBag.cs b/Modules/Properties/Runtime/PropertyBags/IndexedCollectionPropertyBag.cs new file mode 100644 index 0000000000..e392e5f548 --- /dev/null +++ b/Modules/Properties/Runtime/PropertyBags/IndexedCollectionPropertyBag.cs @@ -0,0 +1,214 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System.Collections; +using System.Collections.Generic; + +namespace Unity.Properties +{ + readonly struct IndexedCollectionPropertyBagEnumerable + { + readonly IIndexedCollectionPropertyBagEnumerator m_Impl; + readonly TContainer m_Container; + + public IndexedCollectionPropertyBagEnumerable(IIndexedCollectionPropertyBagEnumerator impl, TContainer container) + { + m_Impl = impl; + m_Container = container; + } + + public IndexedCollectionPropertyBagEnumerator GetEnumerator() + => new IndexedCollectionPropertyBagEnumerator(m_Impl, m_Container); + } + + struct IndexedCollectionPropertyBagEnumerator : IEnumerator> + { + readonly IIndexedCollectionPropertyBagEnumerator m_Impl; + readonly IndexedCollectionSharedPropertyState m_Previous; + + TContainer m_Container; + int m_Position; + + internal IndexedCollectionPropertyBagEnumerator(IIndexedCollectionPropertyBagEnumerator impl, TContainer container) + { + m_Impl = impl; + m_Container = container; + m_Previous = impl.GetSharedPropertyState(); + m_Position = -1; + } + + /// + public IProperty Current => m_Impl.GetSharedProperty(); + + /// + object IEnumerator.Current => Current; + + /// + public bool MoveNext() + { + m_Position++; + + if (m_Position < m_Impl.GetCount(ref m_Container)) + { + m_Impl.SetSharedPropertyState(new IndexedCollectionSharedPropertyState { Index = m_Position, IsReadOnly = false }); + return true; + } + + m_Impl.SetSharedPropertyState(m_Previous); + return false; + } + + /// + public void Reset() + { + m_Position = -1; + m_Impl.SetSharedPropertyState(m_Previous); + } + + /// + public void Dispose() + { + } + } + + interface IIndexedCollectionPropertyBagEnumerator + { + public int GetCount(ref TContainer container); + public IProperty GetSharedProperty(); + public IndexedCollectionSharedPropertyState GetSharedPropertyState(); + public void SetSharedPropertyState(IndexedCollectionSharedPropertyState state); + } + + struct IndexedCollectionSharedPropertyState + { + public int Index; + public bool IsReadOnly; + } + + /// + /// An implementation for a generic collection of elements which can be accessed by index. This is based on the interface. + /// + /// The collection type. + /// The element type. + public class IndexedCollectionPropertyBag : PropertyBag, IListPropertyBag, IConstructorWithCount, IIndexedCollectionPropertyBagEnumerator + where TList : IList + { + class ListElementProperty : Property, IListElementProperty + { + internal int m_Index; + internal bool m_IsReadOnly; + + /// + public int Index => m_Index; + + /// + public override string Name => Index.ToString(); + + /// + public override bool IsReadOnly => m_IsReadOnly; + + /// + public override TElement GetValue(ref TList container) => container[m_Index]; + + /// + public override void SetValue(ref TList container, TElement value) => container[m_Index] = value; + } + + /// + /// Shared instance of a list element property. We re-use the same instance to avoid allocations. + /// + readonly ListElementProperty m_Property = new ListElementProperty(); + + /// + public override PropertyCollection GetProperties() + { + return PropertyCollection.Empty; + } + + /// + public override PropertyCollection GetProperties(ref TList container) + { + return new PropertyCollection(new IndexedCollectionPropertyBagEnumerable(this, container)); + } + + /// + /// Gets the property associated with the specified index. + /// + /// The container hosting the data. + /// The index of the property to get. + /// When this method returns, contains the property associated with the specified index, if the name is found; otherwise, null. + /// if the contains a property for the specified index; otherwise, . + public bool TryGetProperty(ref TList container, int index, out IProperty property) + { + if ((uint) index >= (uint) container.Count) + { + property = null; + return false; + } + + property = new ListElementProperty + { + m_Index = index, + m_IsReadOnly = false + }; + + return true; + } + + void ICollectionPropertyBagAccept.Accept(ICollectionPropertyBagVisitor visitor, ref TList container) + { + visitor.Visit(this, ref container); + } + + void IListPropertyBagAccept.Accept(IListPropertyBagVisitor visitor, ref TList list) + { + visitor.Visit(this, ref list); + } + + void IListPropertyAccept.Accept(IListPropertyVisitor visitor, Property property, ref TContainer container, ref TList list) + { + using (new AttributesScope(m_Property, property)) + { + visitor.Visit(property, ref container, ref list); + } + } + + TList IConstructorWithCount.InstantiateWithCount(int count) + { + return InstantiateWithCount(count); + } + + /// + /// Implement this method to provide custom type instantiation with a count value for the container type. + /// + /// + /// You MUST also override to return for this method to be called. + /// + protected virtual TList InstantiateWithCount(int count) + { + return default; + } + + int IIndexedCollectionPropertyBagEnumerator.GetCount(ref TList container) + { + return container.Count; + } + + IProperty IIndexedCollectionPropertyBagEnumerator.GetSharedProperty() + { + return m_Property; + } + + IndexedCollectionSharedPropertyState IIndexedCollectionPropertyBagEnumerator.GetSharedPropertyState() + { + return new IndexedCollectionSharedPropertyState { Index = m_Property.m_Index, IsReadOnly = m_Property.IsReadOnly }; + } + + void IIndexedCollectionPropertyBagEnumerator.SetSharedPropertyState(IndexedCollectionSharedPropertyState state) + { + m_Property.m_Index = state.Index; + m_Property.m_IsReadOnly = state.IsReadOnly; + } + } +} diff --git a/Modules/Properties/Runtime/PropertyBags/Internal/DefaultPropertyBags.cs b/Modules/Properties/Runtime/PropertyBags/Internal/DefaultPropertyBags.cs new file mode 100644 index 0000000000..572b4d2e95 --- /dev/null +++ b/Modules/Properties/Runtime/PropertyBags/Internal/DefaultPropertyBags.cs @@ -0,0 +1,280 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using UnityEngine; + +namespace Unity.Properties.Internal +{ + static class DefaultPropertyBagInitializer + { + internal static void Initialize() + { + PropertyBag.Register(new Vector2IntPropertyBag()); + PropertyBag.Register(new Vector3IntPropertyBag()); + PropertyBag.Register(new RectPropertyBag()); + PropertyBag.Register(new RectIntPropertyBag()); + PropertyBag.Register(new BoundsPropertyBag()); + PropertyBag.Register(new BoundsIntPropertyBag()); + PropertyBag.Register(new SystemVersionPropertyBag()); + } + } + + class Vector2IntPropertyBag : ContainerPropertyBag + { + public Vector2IntPropertyBag() + { + AddProperty(new XProperty()); + AddProperty(new YProperty()); + } + + class XProperty : Property + { + public override string Name => nameof(Vector2Int.x); + public override bool IsReadOnly => false; + public override int GetValue(ref Vector2Int container) => container.x; + public override void SetValue(ref Vector2Int container, int value) => container.x = value; + } + + class YProperty : Property + { + public override string Name => nameof(Vector2Int.y); + public override bool IsReadOnly => false; + public override int GetValue(ref Vector2Int container) => container.y; + public override void SetValue(ref Vector2Int container, int value) => container.y = value; + } + } + + class Vector3IntPropertyBag : ContainerPropertyBag + { + public Vector3IntPropertyBag() + { + AddProperty(new XProperty()); + AddProperty(new YProperty()); + AddProperty(new ZProperty()); + } + + class XProperty : Property + { + public override string Name => nameof(Vector3Int.x); + public override bool IsReadOnly => false; + public override int GetValue(ref Vector3Int container) => container.x; + public override void SetValue(ref Vector3Int container, int value) => container.x = value; + } + + class YProperty : Property + { + public override string Name => nameof(Vector3Int.y); + public override bool IsReadOnly => false; + public override int GetValue(ref Vector3Int container) => container.y; + public override void SetValue(ref Vector3Int container, int value) => container.y = value; + } + + class ZProperty : Property + { + public override string Name => nameof(Vector3Int.z); + public override bool IsReadOnly => false; + public override int GetValue(ref Vector3Int container) => container.z; + public override void SetValue(ref Vector3Int container, int value) => container.z = value; + } + } + + class RectPropertyBag : ContainerPropertyBag + { + public RectPropertyBag() + { + AddProperty(new XProperty()); + AddProperty(new YProperty()); + AddProperty(new WidthProperty()); + AddProperty(new HeightProperty()); + } + + class XProperty : Property + { + public override string Name => nameof(Rect.x); + public override bool IsReadOnly => false; + public override float GetValue(ref Rect container) => container.x; + public override void SetValue(ref Rect container, float value) => container.x = value; + } + + class YProperty : Property + { + public override string Name => nameof(Rect.y); + public override bool IsReadOnly => false; + public override float GetValue(ref Rect container) => container.y; + public override void SetValue(ref Rect container, float value) => container.y = value; + } + + class WidthProperty : Property + { + public override string Name => nameof(Rect.width); + public override bool IsReadOnly => false; + public override float GetValue(ref Rect container) => container.width; + public override void SetValue(ref Rect container, float value) => container.width = value; + } + + class HeightProperty : Property + { + public override string Name => nameof(Rect.height); + public override bool IsReadOnly => false; + public override float GetValue(ref Rect container) => container.height; + public override void SetValue(ref Rect container, float value) => container.height = value; + } + } + + class RectIntPropertyBag : ContainerPropertyBag + { + public RectIntPropertyBag() + { + AddProperty(new XProperty()); + AddProperty(new YProperty()); + AddProperty(new WidthProperty()); + AddProperty(new HeightProperty()); + } + + class XProperty : Property + { + public override string Name => nameof(RectInt.x); + public override bool IsReadOnly => false; + public override int GetValue(ref RectInt container) => container.x; + public override void SetValue(ref RectInt container, int value) => container.x = value; + } + + class YProperty : Property + { + public override string Name => nameof(RectInt.y); + public override bool IsReadOnly => false; + public override int GetValue(ref RectInt container) => container.y; + public override void SetValue(ref RectInt container, int value) => container.y = value; + } + + class WidthProperty : Property + { + public override string Name => nameof(RectInt.width); + public override bool IsReadOnly => false; + public override int GetValue(ref RectInt container) => container.width; + public override void SetValue(ref RectInt container, int value) => container.width = value; + } + + class HeightProperty : Property + { + public override string Name => nameof(RectInt.height); + public override bool IsReadOnly => false; + public override int GetValue(ref RectInt container) => container.height; + public override void SetValue(ref RectInt container, int value) => container.height = value; + } + } + + class BoundsPropertyBag : ContainerPropertyBag + { + public BoundsPropertyBag() + { + AddProperty(new CenterProperty()); + AddProperty(new ExtentsProperty()); + } + + class CenterProperty : Property + { + public override string Name => nameof(Bounds.center); + public override bool IsReadOnly => false; + public override Vector3 GetValue(ref Bounds container) => container.center; + public override void SetValue(ref Bounds container, Vector3 value) => container.center = value; + } + + class ExtentsProperty : Property + { + public override string Name => nameof(Bounds.extents); + public override bool IsReadOnly => false; + public override Vector3 GetValue(ref Bounds container) => container.extents; + public override void SetValue(ref Bounds container, Vector3 value) => container.extents = value; + } + } + + class BoundsIntPropertyBag : ContainerPropertyBag + { + public BoundsIntPropertyBag() + { + AddProperty(new PositionProperty()); + AddProperty(new SizeProperty()); + } + + class PositionProperty : Property + { + public override string Name => nameof(BoundsInt.position); + public override bool IsReadOnly => false; + public override Vector3Int GetValue(ref BoundsInt container) => container.position; + public override void SetValue(ref BoundsInt container, Vector3Int value) => container.position = value; + } + + class SizeProperty : Property + { + public override string Name => nameof(BoundsInt.size); + public override bool IsReadOnly => false; + public override Vector3Int GetValue(ref BoundsInt container) => container.size; + public override void SetValue(ref BoundsInt container, Vector3Int value) => container.size = value; + } + } + + class SystemVersionPropertyBag : ContainerPropertyBag + { + public SystemVersionPropertyBag() + { + AddProperty(new MajorProperty()); + AddProperty(new MinorProperty()); + AddProperty(new BuildProperty()); + AddProperty(new RevisionProperty()); + } + + class MajorProperty : Property + { + public MajorProperty() + { + AddAttribute(new MinAttribute(0)); + } + + public override string Name => nameof(System.Version.Major); + public override bool IsReadOnly => true; + public override int GetValue(ref System.Version container) => container.Major; + public override void SetValue(ref System.Version container, int value) {} + } + + class MinorProperty : Property + { + public MinorProperty() + { + AddAttribute(new MinAttribute(0)); + } + + public override string Name => nameof(System.Version.Minor); + public override bool IsReadOnly => true; + public override int GetValue(ref System.Version container) => container.Minor; + public override void SetValue(ref System.Version container, int value) {} + } + + class BuildProperty : Property + { + public BuildProperty() + { + AddAttribute(new MinAttribute(0)); + } + + public override string Name => nameof(System.Version.Build); + public override bool IsReadOnly => true; + public override int GetValue(ref System.Version container) => container.Build; + public override void SetValue(ref System.Version container, int value) {} + } + + class RevisionProperty : Property + { + public RevisionProperty() + { + AddAttribute(new MinAttribute(0)); + } + + public override string Name => nameof(System.Version.Revision); + public override bool IsReadOnly => true; + public override int GetValue(ref System.Version container) => container.Revision; + public override void SetValue(ref System.Version container, int value) {} + } + } +} diff --git a/Modules/Properties/Runtime/PropertyBags/Internal/PropertyBagStore.cs b/Modules/Properties/Runtime/PropertyBags/Internal/PropertyBagStore.cs new file mode 100644 index 0000000000..b3048cb25c --- /dev/null +++ b/Modules/Properties/Runtime/PropertyBags/Internal/PropertyBagStore.cs @@ -0,0 +1,243 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Unity.Properties.Internal +{ + interface IPropertyBagRegister + { + /// + /// Statically registers the property bag through the . + /// + void Register(); + } + + /// + /// Static class used to store all property bags. This is an internal class. + /// + /// + /// This storage is used to resolve types by internal properties algorithms. + /// + static class PropertyBagStore + { + static PropertyBagStore() + { + s_PropertyBagProvider = new ReflectedPropertyBagProvider(); + DefaultPropertyBagInitializer.Initialize(); + } + + internal struct TypedStore + { + public static IPropertyBag PropertyBag; + } + + static readonly System.Collections.Concurrent.ConcurrentDictionary s_PropertyBags = new System.Collections.Concurrent.ConcurrentDictionary(); + static readonly List s_RegisteredTypes = new List(); + internal static event Action NewTypeRegistered; + + /// + /// Instance of the dynamic property bag provider. This is used to allow an external assembly to generate property bags for us. + /// + static ReflectedPropertyBagProvider s_PropertyBagProvider = null; + + internal static bool HasProvider => null != s_PropertyBagProvider; + + internal static List AllTypes => s_RegisteredTypes; + + /// + /// Adds a to the store. + /// + /// The to add. + /// The container type this describes. + internal static void AddPropertyBag(IPropertyBag propertyBag) + { + if (!TypeTraits.IsContainer) + { + throw new Exception($"PropertyBagStore Type=[{typeof(TContainer)}] is not a valid container type. Type can not be primitive, enum or string."); + } + + if (TypeTraits.IsAbstractOrInterface) + { + throw new Exception($"PropertyBagStore Type=[{typeof(TContainer)}] is not a valid container type. Type can not be abstract or interface."); + } + + if (null != TypedStore.PropertyBag) + { + if (propertyBag.GetType().GetCustomAttributes().Any()) + { + return; + } + } + + TypedStore.PropertyBag = propertyBag; + if (!s_PropertyBags.ContainsKey(typeof(TContainer))) + { + s_RegisteredTypes.Add(typeof(TContainer)); + } + + s_PropertyBags[typeof(TContainer)] = propertyBag; + NewTypeRegistered?.Invoke(typeof(TContainer), propertyBag); + } + + /// + /// Gets the strongly typed for the given . + /// + /// The container type to resolve the property bag for. + /// The resolved property bag, strongly typed. + internal static IPropertyBag GetPropertyBag() + { + if (null != TypedStore.PropertyBag) + { + return TypedStore.PropertyBag; + } + + var untyped = GetPropertyBag(typeof(TContainer)); + + if (null == untyped) + { + return null; + } + + if (!(untyped is IPropertyBag typed)) + { + throw new InvalidOperationException($"PropertyBag type container type mismatch."); + } + + return typed; + } + + /// + /// Gets an interface to the for the given type. + /// + /// + /// The returned can be used to get the strongly typed generic using the method. + /// + /// The container type to resolve the property bag for. + /// The resolved property bag. + internal static IPropertyBag GetPropertyBag(Type type) + { + if (s_PropertyBags.TryGetValue(type, out var propertyBag)) + { + return propertyBag; + } + + if (!TypeTraits.IsContainer(type)) + { + return null; + } + + if (type.IsArray && type.GetArrayRank() != 1) + { + return null; + } + + if (type.IsInterface || type.IsAbstract) + { + return null; + } + + if (type == typeof(object)) + { + return null; + } + + if (null != s_PropertyBagProvider) + { + propertyBag = s_PropertyBagProvider.CreatePropertyBag(type); + + if (null == propertyBag) + { + + s_PropertyBags.TryAdd(type, null); + + } + else + { + (propertyBag as IPropertyBagRegister)?.Register(); + return propertyBag; + } + } + + return null; + } + + /// + /// Returns true if the given type has a static property bag registered. + /// + /// + /// if the property bag exists; otherwise, . + internal static bool Exists() + { + return null != TypedStore.PropertyBag; + } + + internal static bool Exists(Type type) + { + return s_PropertyBags.ContainsKey(type); + } + + /// + /// Returns true if the given type is backed by a property bag. + /// + /// The value to check. + /// The container type to check. + /// if the property bag exists; otherwise, . + internal static bool Exists(ref TContainer value) + { + if (!TypeTraits.CanBeNull) + { + return GetPropertyBag() != null; + } + + // We can't reliably determine if there is a property bag. + if (EqualityComparer.Default.Equals(value, default(TContainer))) + { + return false; + } + + return GetPropertyBag(value.GetType()) != null; + } + + /// + /// Gets a property bag for the concrete type of the given value. + /// + /// The value type to retrieve a property bag for. + /// When this method returns, contains the property bag associated with the specified value, if the bag is found; otherwise, null. + /// + /// if the property bag was found for the specified value; otherwise, . + internal static bool TryGetPropertyBagForValue(ref TValue value, out IPropertyBag propertyBag) + { + // early out for primitive types that don't have associated containers + // note: GetPropertyBag checks for TypeTraits.IsContainerType(type) already + if (!TypeTraits.IsContainer) + { + propertyBag = null; + return false; + } + + // We can not recurse on a null value. + if (TypeTraits.CanBeNull) + { + if (EqualityComparer.Default.Equals(value, default)) + { + propertyBag = GetPropertyBag(); + return null != propertyBag; + } + } + + if (TypeTraits.IsValueType) + { + propertyBag = GetPropertyBag(); + return null != propertyBag; + } + + propertyBag = GetPropertyBag(value.GetType()); + return null != propertyBag; + } + } +} diff --git a/Modules/Properties/Runtime/PropertyBags/KeyValueCollectionPropertyBag.cs b/Modules/Properties/Runtime/PropertyBags/KeyValueCollectionPropertyBag.cs new file mode 100644 index 0000000000..2b1c3ec19b --- /dev/null +++ b/Modules/Properties/Runtime/PropertyBags/KeyValueCollectionPropertyBag.cs @@ -0,0 +1,163 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System.Collections; +using System.Collections.Generic; + +namespace Unity.Properties +{ + /// + /// An implementation for a generic collection of key/value pairs using the interface. + /// + /// The key/value collection type. + /// The key type. + /// The value type. + public class KeyValueCollectionPropertyBag : PropertyBag, IDictionaryPropertyBag + where TDictionary : IDictionary + { + class KeyValuePairProperty : Property>, IDictionaryElementProperty + { + public override string Name => Key.ToString(); + public override bool IsReadOnly => false; + + public override KeyValuePair GetValue(ref TDictionary container) + { + return new KeyValuePair(Key, container[Key]); + } + + public override void SetValue(ref TDictionary container, KeyValuePair value) + { + container[value.Key] = value.Value; + } + + public TKey Key { get; internal set; } + public object ObjectKey => Key; + } + + /// + /// Collection used to dynamically return the same instance pointing to a different . + /// + readonly struct Enumerable : IEnumerable> + { + class Enumerator : IEnumerator> + { + readonly TDictionary m_Dictionary; + readonly KeyValuePairProperty m_Property; + readonly TKey m_Previous; + readonly List m_Keys; + int m_Position; + + public Enumerator(TDictionary dictionary, KeyValuePairProperty property) + { + m_Dictionary = dictionary; + m_Property = property; + m_Previous = property.Key; + m_Position = -1; + m_Keys = UnityEngine.Pool.ListPool.Get(); + m_Keys.AddRange(m_Dictionary.Keys); + } + + /// + public IProperty Current => m_Property; + + /// + object IEnumerator.Current => Current; + + /// + public bool MoveNext() + { + m_Position++; + + if (m_Position < m_Dictionary.Count) + { + m_Property.Key = m_Keys[m_Position]; + return true; + } + + m_Property.Key = m_Previous; + return false; + } + + /// + public void Reset() + { + m_Position = -1; + m_Property.Key = m_Previous; + } + + /// + public void Dispose() + { + UnityEngine.Pool.ListPool.Release(m_Keys); + } + } + + readonly TDictionary m_Dictionary; + readonly KeyValuePairProperty m_Property; + + public Enumerable(TDictionary dictionary, KeyValuePairProperty property) + { + m_Dictionary = dictionary; + m_Property = property; + } + + /// + IEnumerator IEnumerable.GetEnumerator() + => new Enumerator(m_Dictionary, m_Property); + + /// + IEnumerator> IEnumerable>.GetEnumerator() + => new Enumerator(m_Dictionary, m_Property); + } + + /// + public override PropertyCollection GetProperties() + { + return PropertyCollection.Empty; + } + + /// + public override PropertyCollection GetProperties(ref TDictionary container) + { + return new PropertyCollection(new Enumerable(container, m_KeyValuePairProperty)); + } + + /// + /// Shared instance of a dictionary element property. We re-use the same instance to avoid allocations. + /// + readonly KeyValuePairProperty m_KeyValuePairProperty = new KeyValuePairProperty(); + + void ICollectionPropertyBagAccept.Accept(ICollectionPropertyBagVisitor visitor, ref TDictionary container) + { + visitor.Visit(this, ref container); + } + + void IDictionaryPropertyBagAccept.Accept(IDictionaryPropertyBagVisitor visitor, ref TDictionary container) + { + visitor.Visit(this, ref container); + } + + void IDictionaryPropertyAccept.Accept(IDictionaryPropertyVisitor visitor, Property property, ref TContainer container, + ref TDictionary dictionary) + { + using (new AttributesScope(m_KeyValuePairProperty, property)) + { + visitor.Visit(property, ref container, ref dictionary); + } + } + + /// + bool IKeyedProperties.TryGetProperty(ref TDictionary container, object key, out IProperty property) + { + if (container.ContainsKey((TKey)key)) + { + property = new KeyValuePairProperty { Key = (TKey)key }; + return true; + } + + property = default; + return false; + } + } +} diff --git a/Modules/Properties/Runtime/PropertyBags/KeyValuePairPropertyBag.cs b/Modules/Properties/Runtime/PropertyBags/KeyValuePairPropertyBag.cs new file mode 100644 index 0000000000..cf29305d07 --- /dev/null +++ b/Modules/Properties/Runtime/PropertyBags/KeyValuePairPropertyBag.cs @@ -0,0 +1,71 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System.Collections.Generic; + +namespace Unity.Properties +{ + /// + /// A implementation for a generic key/value pair. + /// + /// The key type. + /// The value type. + public class KeyValuePairPropertyBag : PropertyBag>, INamedProperties> + { + static readonly DelegateProperty, TKey> s_KeyProperty = + new DelegateProperty, TKey>( + nameof(KeyValuePair.Key), + (ref KeyValuePair container) => container.Key, + null); + + static readonly DelegateProperty, TValue> s_ValueProperty = + new DelegateProperty, TValue>( + nameof(KeyValuePair.Value), + (ref KeyValuePair container) => container.Value, + null); + + /// + public override PropertyCollection> GetProperties() + { + return new PropertyCollection>(GetPropertiesEnumerable()); + } + + /// + public override PropertyCollection> GetProperties(ref KeyValuePair container) + { + return new PropertyCollection>(GetPropertiesEnumerable()); + } + + static IEnumerable>> GetPropertiesEnumerable() + { + yield return s_KeyProperty; + yield return s_ValueProperty; + } + + /// + /// Gets the property associated with the specified name. + /// + /// The container hosting the data. + /// The name of the property to get. + /// When this method returns, contains the property associated with the specified name, if the name is found; otherwise, null. + /// if the contains a property with the specified name; otherwise, . + public bool TryGetProperty(ref KeyValuePair container, string name, out IProperty> property) + { + if (name == nameof(KeyValuePair.Key)) + { + property = s_KeyProperty; + return true; + } + + if (name == nameof(KeyValuePair.Value)) + { + property = s_ValueProperty; + return true; + } + + property = default; + return false; + } + } +} diff --git a/Modules/Properties/Runtime/PropertyBags/ListPropertyBag.cs b/Modules/Properties/Runtime/PropertyBags/ListPropertyBag.cs new file mode 100644 index 0000000000..831da1c8f5 --- /dev/null +++ b/Modules/Properties/Runtime/PropertyBags/ListPropertyBag.cs @@ -0,0 +1,24 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System.Collections.Generic; + +namespace Unity.Properties +{ + /// + /// A implementation for a type. + /// + /// The element type. + public class ListPropertyBag : IndexedCollectionPropertyBag, TElement> + { + /// + protected override InstantiationKind InstantiationKind => InstantiationKind.PropertyBagOverride; + + /// + protected override List InstantiateWithCount(int count) => new List(count); + + /// + protected override List Instantiate() => new List(); + } +} diff --git a/Modules/Properties/Runtime/PropertyBags/PropertyBag+Accept.cs b/Modules/Properties/Runtime/PropertyBags/PropertyBag+Accept.cs new file mode 100644 index 0000000000..640d9daf0a --- /dev/null +++ b/Modules/Properties/Runtime/PropertyBags/PropertyBag+Accept.cs @@ -0,0 +1,44 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; + +namespace Unity.Properties +{ + public static partial class PropertyBag + { + /// + /// Accepts visitation for the given property bag and tries to invoke the most specialized visitor first. + /// + /// The property bag to visit. + /// The visitor or specialized visitor to invoke. + /// The container being visited. + /// The container type. + /// The given property bag is null. + public static void AcceptWithSpecializedVisitor(IPropertyBag properties, IPropertyBagVisitor visitor, ref TContainer container) + { + if (null == properties) + throw new ArgumentNullException(nameof(properties)); + + switch (properties) + { + case IDictionaryPropertyBagAccept accept when visitor is IDictionaryPropertyBagVisitor typedVisitor: + accept.Accept(typedVisitor, ref container); + break; + case IListPropertyBagAccept accept when visitor is IListPropertyBagVisitor typedVisitor: + accept.Accept(typedVisitor, ref container); + break; + case ISetPropertyBagAccept accept when visitor is ISetPropertyBagVisitor typedVisitor: + accept.Accept(typedVisitor, ref container); + break; + case ICollectionPropertyBagAccept accept when visitor is ICollectionPropertyBagVisitor typedVisitor: + accept.Accept(typedVisitor, ref container); + break; + default: + properties.Accept(visitor, ref container); + break; + } + } + } +} diff --git a/Modules/Properties/Runtime/PropertyBags/PropertyBag+Registration.cs b/Modules/Properties/Runtime/PropertyBags/PropertyBag+Registration.cs new file mode 100644 index 0000000000..35f61ee172 --- /dev/null +++ b/Modules/Properties/Runtime/PropertyBags/PropertyBag+Registration.cs @@ -0,0 +1,235 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System.Collections.Generic; +using Unity.Properties.Internal; + +namespace Unity.Properties +{ + public static partial class PropertyBag + { + /// + /// Registers a strongly typed for a type. + /// + /// The to register. + /// The container type this property bag describes. + public static void Register(PropertyBag propertyBag) + { + PropertyBagStore.AddPropertyBag(propertyBag); + } + + /// + /// Creates and registers a for a built in array type. + /// + /// + /// To generate AOT paths for visitors use instead. + /// + /// The element type to register. + public static void RegisterArray() + { + if (PropertyBagStore.TypedStore>.PropertyBag == null) + { + PropertyBagStore.AddPropertyBag(new ArrayPropertyBag()); + } + } + + /// + /// Creates and registers a for a built in array type. + /// + /// + /// The container is required to provide AOT code paths for . + /// + /// The container type to register. + /// The element type to register. + public static void RegisterArray() + { + RegisterArray(); + } + + /// + /// Creates and registers a for a type. + /// + /// + /// To generate AOT paths for visitors use instead. + /// + /// The element type to register. + public static void RegisterList() + { + if (PropertyBagStore.TypedStore>.PropertyBag == null) + { + PropertyBagStore.AddPropertyBag(new ListPropertyBag()); + } + } + + /// + /// Creates and registers a for a type. + /// + /// + /// The container is required to provide AOT code paths for . + /// + /// The container type to register. + /// The element type to register. + public static void RegisterList() + { + RegisterList(); + } + + /// + /// Creates and registers a for a type. + /// + /// + /// To generate AOT paths for visitors use instead. + /// + /// The element type to register. + public static void RegisterHashSet() + { + if (PropertyBagStore.TypedStore>>.PropertyBag == null) + { + PropertyBagStore.AddPropertyBag(new HashSetPropertyBag()); + } + } + + /// + /// Creates and registers a for a type. + /// + /// + /// The container is required to provide AOT code paths for . + /// + /// The container type to register. + /// The element type to register. + public static void RegisterHashSet() + { + RegisterHashSet(); + } + + /// + /// Creates and registers a for a type. + /// + /// + /// To generate AOT paths for visitors use instead. + /// + /// The key type to register. + /// The value type to register. + public static void RegisterDictionary() + { + if (PropertyBagStore.TypedStore>>.PropertyBag == null) + { + PropertyBagStore.AddPropertyBag(new DictionaryPropertyBag()); + } + } + + /// + /// Creates and registers a for a type. + /// + /// + /// The container is required to provide AOT code paths for . + /// + /// The container type to register. + /// The key type to register. + /// The value type to register. + public static void RegisterDictionary() + { + RegisterDictionary(); + } + + /// + /// Creates and registers a for the specified type. + /// + /// + /// To generate AOT paths for visitors use instead. + /// + /// The generic list type to register. + /// The element type to register. + public static void RegisterIList() + where TList : IList + { + if (PropertyBagStore.TypedStore>.PropertyBag == null) + { + PropertyBagStore.AddPropertyBag(new IndexedCollectionPropertyBag()); + } + } + + /// + /// Creates and registers a for the specified type. + /// + /// + /// The container is required to provide AOT code paths for . + /// + /// The container type to register. + /// The generic list type to register. + /// The element type to register. + public static void RegisterIList() + where TList : IList + { + RegisterIList(); + } + + /// + /// Creates and registers a for the specified type. + /// + /// + /// To generate AOT paths for visitors use instead. + /// + /// The generic set type to register. + /// The element type to register. + public static void RegisterISet() + where TSet : ISet + { + if (PropertyBagStore.TypedStore>.PropertyBag == null) + { + PropertyBagStore.AddPropertyBag(new SetPropertyBagBase()); + } + } + + /// + /// Creates and registers a for the specified type. + /// + /// + /// The container is required to provide AOT code paths for . + /// + /// The container type to register. + /// The generic set type to register. + /// The element type to register. + public static void RegisterISet() + where TSet : ISet + { + RegisterISet(); + } + + /// + /// Creates and registers a for the specified type. + /// + /// + /// To generate AOT paths for visitors use instead. + /// + /// The generic dictionary type to register. + /// The key type to register. + /// The value type to register. + public static void RegisterIDictionary() + where TDictionary : IDictionary + { + if (PropertyBagStore.TypedStore>.PropertyBag == null) + { + PropertyBagStore.AddPropertyBag(new KeyValueCollectionPropertyBag()); + PropertyBagStore.AddPropertyBag(new KeyValuePairPropertyBag()); + } + } + + /// + /// Creates and registers a for the specified type. + /// + /// + /// The container is required to provide AOT code paths for . + /// + /// The container type to register. + /// The generic dictionary type to register. + /// The key type to register. + /// The value type to register. + public static void RegisterIDictionary() + where TDictionary : IDictionary + { + RegisterIDictionary(); + } + } +} diff --git a/Modules/Properties/Runtime/PropertyBags/PropertyBag+TypeConstruction.cs b/Modules/Properties/Runtime/PropertyBags/PropertyBag+TypeConstruction.cs new file mode 100644 index 0000000000..495d791da7 --- /dev/null +++ b/Modules/Properties/Runtime/PropertyBags/PropertyBag+TypeConstruction.cs @@ -0,0 +1,26 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using Unity.Properties.Internal; + +namespace Unity.Properties +{ + static partial class PropertyBag + { + /// + /// Constructs a new instance of the given type. + /// + /// The container type to construct. + /// A new instance of . + public static TContainer CreateInstance() + { + var propertyBag = PropertyBagStore.GetPropertyBag(); + + if (null == propertyBag) + throw new MissingPropertyBagException(typeof(TContainer)); + + return propertyBag.CreateInstance(); + } + } +} diff --git a/Modules/Properties/Runtime/PropertyBags/PropertyBag.cs b/Modules/Properties/Runtime/PropertyBags/PropertyBag.cs new file mode 100644 index 0000000000..37334ede8f --- /dev/null +++ b/Modules/Properties/Runtime/PropertyBags/PropertyBag.cs @@ -0,0 +1,220 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using System.Collections.Generic; +using Unity.Properties.Internal; + +namespace Unity.Properties +{ + /// + /// The class provides access to registered property bag instances. + /// + public static partial class PropertyBag + { + /// + /// Gets an interface to the for the given type. + /// + /// + /// The returned can be used to get the strongly typed generic using the interface method. + /// + /// The container type to resolve the property bag for. + /// The resolved property bag. + public static IPropertyBag GetPropertyBag(Type type) + { + return PropertyBagStore.GetPropertyBag(type); + } + + /// + /// Gets the strongly typed for the given . + /// + /// The container type to resolve the property bag for. + /// The resolved property bag, strongly typed. + public static IPropertyBag GetPropertyBag() + { + return PropertyBagStore.GetPropertyBag(); + } + + /// + /// Gets a property bag for the concrete type of the given value. + /// + /// The value type to retrieve a property bag for. + /// When this method returns, contains the property bag associated with the specified value, if the bag is found; otherwise, null. + /// + /// if the property bag was found for the specified value; otherwise, . + public static bool TryGetPropertyBagForValue(ref TValue value, out IPropertyBag propertyBag) + { + return PropertyBagStore.TryGetPropertyBagForValue(ref value, out propertyBag); + } + + /// + /// Returns true if a property bag exists for the given type. + /// + /// The container type to check a property bag for. + /// if there is a property bag for the given type; otherwise, . + public static bool Exists() + { + return PropertyBagStore.Exists(); + } + + /// + /// Returns true if a property bag exists for the given type. + /// + /// The type to check for a property bag + /// if there is a property bag for the given type; otherwise, . + public static bool Exists(Type type) + { + return PropertyBagStore.Exists(type); + } + + /// + /// Returns all the that have a registered property bag. + /// + /// A list of types with a registered property bag. + public static IEnumerable GetAllTypesWithAPropertyBag() + { + return PropertyBagStore.AllTypes; + } + } + + /// + /// Base class for implementing a property bag for a specified container type. This is an abstract class. + /// + /// + /// This is used as the base class internally and should NOT be extended. + /// + /// When implementing custom property bags use: + /// * . + /// * . + /// + /// The container type. + public abstract class PropertyBag : IPropertyBag, IPropertyBagRegister, IConstructor + { + static PropertyBag() + { + if (!TypeTraits.IsContainer(typeof(TContainer))) + { + throw new InvalidOperationException($"Failed to create a property bag for Type=[{typeof(TContainer)}]. The type is not a valid container type."); + } + } + + /// + void IPropertyBagRegister.Register() + { + PropertyBagStore.AddPropertyBag(this); + } + + /// + /// Accepts visitation from a specified . + /// + /// The visitor handling visitation. + /// The visitor is null. + public void Accept(ITypeVisitor visitor) + { + if (null == visitor) + { + throw new ArgumentNullException(nameof(visitor)); + } + + visitor.Visit(); + } + + /// + /// Accepts visitation from a specified using an object as the container. + /// + /// The visitor handling the visitation. + /// The container being visited. + /// The container is null. + /// The container type does not match the property bag type. + void IPropertyBag.Accept(IPropertyBagVisitor visitor, ref object container) + { + if (null == container) + { + throw new ArgumentNullException(nameof(container)); + } + + if (!(container is TContainer typedContainer)) + { + throw new ArgumentException($"The given ContainerType=[{container.GetType()}] does not match the PropertyBagType=[{typeof(TContainer)}]"); + } + + PropertyBag.AcceptWithSpecializedVisitor(this, visitor, ref typedContainer); + + container = typedContainer; + } + + /// + /// Accepts visitation from a specified using a strongly typed container. + /// + /// The visitor handling the visitation. + /// The container being visited. + void IPropertyBag.Accept(IPropertyBagVisitor visitor, ref TContainer container) + { + visitor.Visit(this, ref container); + } + + /// + PropertyCollection IPropertyBag.GetProperties() + { + return GetProperties(); + } + + /// + PropertyCollection IPropertyBag.GetProperties(ref TContainer container) + { + return GetProperties(ref container); + } + + /// + InstantiationKind IConstructor.InstantiationKind => InstantiationKind; + + /// + TContainer IConstructor.Instantiate() + { + return Instantiate(); + } + + /// + /// Implement this method to return a that can enumerate through all properties for the . + /// + /// A structure which can enumerate each property. + public abstract PropertyCollection GetProperties(); + + /// + /// Implement this method to return a that can enumerate through all properties for the . + /// + /// The container hosting the data. + /// A structure which can enumerate each property. + public abstract PropertyCollection GetProperties(ref TContainer container); + + /// + /// Implement this property and return true to provide custom type instantiation for the container type. + /// + protected virtual InstantiationKind InstantiationKind { get; } = InstantiationKind.Activator; + + /// + /// Implement this method to provide custom type instantiation for the container type. + /// + /// + /// You MUST also override to return for this method to be called. + /// + protected virtual TContainer Instantiate() + { + return default; + } + + /// + /// Creates and returns a new instance of . + /// + /// A new instance of . + public TContainer CreateInstance() => TypeUtility.Instantiate(); + + /// + /// Tries to create a new instance of . + /// + /// When this method returns, contains the created instance, if type instantiation succeeded; otherwise, the default value for . + /// if a new instance of type was created; otherwise, . + public bool TryCreateInstance(out TContainer instance) => TypeUtility.TryInstantiate(out instance); + } +} diff --git a/Modules/Properties/Runtime/PropertyBags/PropertyCollection.cs b/Modules/Properties/Runtime/PropertyBags/PropertyCollection.cs new file mode 100644 index 0000000000..f6e74652a3 --- /dev/null +++ b/Modules/Properties/Runtime/PropertyBags/PropertyCollection.cs @@ -0,0 +1,229 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Unity.Properties +{ + /// + /// The struct provides enumerable access to all for a given . + /// + /// The container type which this collection exposes properties for. + public readonly struct PropertyCollection : IEnumerable> + { + enum EnumeratorType + { + Empty, + Enumerable, + List, + IndexedCollectionPropertyBag + } + + /// + /// An enumerator struct to enumerate all properties for the given . + /// + public struct Enumerator : IEnumerator> + { + readonly EnumeratorType m_Type; + + IEnumerator> m_Enumerator; + List>.Enumerator m_Properties; + IndexedCollectionPropertyBagEnumerator m_IndexedCollectionPropertyBag; + + /// + /// Gets the element in the collection at the current position of the enumerator. + /// + /// The element in the collection at the current position of the enumerator. + public IProperty Current { get; private set; } + + /// + /// Gets the element in the collection at the current position of the enumerator. + /// + /// The element in the collection at the current position of the enumerator. + object IEnumerator.Current => Current; + + /// + /// Passthrough enumerator. + /// + /// + internal Enumerator(IEnumerator> enumerator) + { + m_Type = EnumeratorType.Enumerable; + m_Enumerator = enumerator; + m_Properties = default; + m_IndexedCollectionPropertyBag = default; + Current = default; + } + + internal Enumerator(List>.Enumerator properties) + { + m_Type = EnumeratorType.List; + m_Enumerator = default; + m_Properties = properties; + m_IndexedCollectionPropertyBag = default; + Current = default; + } + + internal Enumerator(IndexedCollectionPropertyBagEnumerator enumerator) + { + m_Type = EnumeratorType.IndexedCollectionPropertyBag; + m_Enumerator = default; + m_Properties = default; + m_IndexedCollectionPropertyBag = enumerator; + Current = default; + } + + /// + /// Advances the enumerator to the next element of the collection. + /// + /// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection. + public bool MoveNext() + { + bool result; + + switch (m_Type) + { + case EnumeratorType.Empty: + return false; + case EnumeratorType.Enumerable: + result = m_Enumerator.MoveNext(); + Current = m_Enumerator.Current; + break; + case EnumeratorType.List: + result = m_Properties.MoveNext(); + Current = m_Properties.Current; + break; + case EnumeratorType.IndexedCollectionPropertyBag: + result = m_IndexedCollectionPropertyBag.MoveNext(); + Current = m_IndexedCollectionPropertyBag.Current; + break; + default: + throw new ArgumentOutOfRangeException(); + } + + return result; + } + + /// + /// Sets the enumerator to its initial position, which is before the first element in the collection. + /// + public void Reset() + { + switch (m_Type) + { + case EnumeratorType.Empty: + break; + case EnumeratorType.Enumerable: + m_Enumerator.Reset(); + break; + case EnumeratorType.List: + ((IEnumerator) m_Properties).Reset(); + break; + case EnumeratorType.IndexedCollectionPropertyBag: + m_IndexedCollectionPropertyBag.Reset(); + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + /// + /// Disposes of the underlying enumerator. + /// + public void Dispose() + { + switch (m_Type) + { + case EnumeratorType.Empty: + break; + case EnumeratorType.Enumerable: + m_Enumerator.Dispose(); + break; + case EnumeratorType.List: + // If we try to invoke the dispose call here we incur a boxing cost. + // Fortunately List.Enumerator has no dispose implementation. + break; + case EnumeratorType.IndexedCollectionPropertyBag: + m_IndexedCollectionPropertyBag.Dispose(); + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + } + + readonly EnumeratorType m_Type; + readonly IEnumerable> m_Enumerable; + readonly List> m_Properties; + readonly IndexedCollectionPropertyBagEnumerable m_IndexedCollectionPropertyBag; + + /// + /// Returns an empty collection of properties. + /// + public static PropertyCollection Empty { get; } = new PropertyCollection(); + + /// + /// Initializes a new instance of the struct which wraps the given enumerable. + /// + /// An of properties to wrap. + public PropertyCollection(IEnumerable> enumerable) + { + m_Type = EnumeratorType.Enumerable; + m_Enumerable = enumerable; + m_Properties = null; + m_IndexedCollectionPropertyBag = default; + } + + /// + /// Initializes a new instance of the struct which wraps the given properties list. + /// + /// A list of properties to wrap. + public PropertyCollection(List> properties) + { + m_Type = EnumeratorType.List; + m_Enumerable = null; + m_Properties = properties; + m_IndexedCollectionPropertyBag = default; + } + + internal PropertyCollection(IndexedCollectionPropertyBagEnumerable enumerable) + { + m_Type = EnumeratorType.IndexedCollectionPropertyBag; + m_Enumerable = null; + m_Properties = null; + m_IndexedCollectionPropertyBag = enumerable; + } + + /// + /// Returns an enumerator that iterates through the collection. + /// + /// An enumerator that can be used to iterate through the collection. + public Enumerator GetEnumerator() + { + switch (m_Type) + { + case EnumeratorType.Empty: + return default; + case EnumeratorType.Enumerable: + return new Enumerator(m_Enumerable.GetEnumerator()); + case EnumeratorType.List: + return new Enumerator(m_Properties.GetEnumerator()); + case EnumeratorType.IndexedCollectionPropertyBag: + return new Enumerator(m_IndexedCollectionPropertyBag.GetEnumerator()); + default: + throw new ArgumentOutOfRangeException(); + } + } + + /// + IEnumerator> IEnumerable>.GetEnumerator() + => GetEnumerator(); + + /// + IEnumerator IEnumerable.GetEnumerator() + => GetEnumerator(); + } +} diff --git a/Modules/Properties/Runtime/PropertyBags/SetPropertyBag.cs b/Modules/Properties/Runtime/PropertyBags/SetPropertyBag.cs new file mode 100644 index 0000000000..96dbd152a1 --- /dev/null +++ b/Modules/Properties/Runtime/PropertyBags/SetPropertyBag.cs @@ -0,0 +1,93 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using System.Collections.Generic; + +namespace Unity.Properties +{ + /// + /// A implementation for a generic set of elements using the interface. + /// + /// The collection type. + /// The element type. + public class SetPropertyBagBase : PropertyBag, ISetPropertyBag + where TSet : ISet + { + class SetElementProperty : Property, ISetElementProperty + { + internal TElement m_Value; + + public override string Name => m_Value.ToString(); + public override bool IsReadOnly => true; + + public override TElement GetValue(ref TSet container) => m_Value; + public override void SetValue(ref TSet container, TElement value) => throw new InvalidOperationException("Property is ReadOnly."); + + public TElement Key => m_Value; + public object ObjectKey => m_Value; + } + + /// + /// Shared instance of a set element property. We re-use the same instance to avoid allocations. + /// + readonly SetElementProperty m_Property = new SetElementProperty(); + + public override PropertyCollection GetProperties() + { + return PropertyCollection.Empty; + } + + public override PropertyCollection GetProperties(ref TSet container) + { + return new PropertyCollection(GetPropertiesEnumerable(container)); + } + + IEnumerable> GetPropertiesEnumerable(TSet container) + { + foreach (var element in container) + { + m_Property.m_Value = element; + yield return m_Property; + } + } + + void ICollectionPropertyBagAccept.Accept(ICollectionPropertyBagVisitor visitor, ref TSet container) + { + visitor.Visit(this, ref container); + } + + void ISetPropertyBagAccept.Accept(ISetPropertyBagVisitor visitor, ref TSet container) + { + visitor.Visit(this, ref container); + } + + void ISetPropertyAccept.Accept(ISetPropertyVisitor visitor, Property property, ref TContainer container, ref TSet dictionary) + { + using (new AttributesScope(m_Property, property)) + { + visitor.Visit(property, ref container, ref dictionary); + } + } + + /// + /// Gets the property associated with the specified name. + /// + /// The container hosting the data. + /// The key to lookup. + /// When this method returns, contains the property associated with the specified name, if the name is found; otherwise, null. + /// if the contains a property with the specified name; otherwise, . + public bool TryGetProperty(ref TSet container, object key, out IProperty property) + { + if (container.Contains((TElement) key)) + { + property = new SetElementProperty {m_Value = (TElement) key}; + return true; + } + + property = default; + return false; + } + } +} diff --git a/Modules/Properties/Runtime/PropertyVisitors/Adapters/IExcludePropertyAdapter.cs b/Modules/Properties/Runtime/PropertyVisitors/Adapters/IExcludePropertyAdapter.cs new file mode 100644 index 0000000000..eca62fcebb --- /dev/null +++ b/Modules/Properties/Runtime/PropertyVisitors/Adapters/IExcludePropertyAdapter.cs @@ -0,0 +1,91 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +namespace Unity.Properties +{ + /// + /// Implement this interface to filter visitation for a specific and pair. + /// + /// The container type being visited. + /// The value type being visited. + public interface IExcludePropertyAdapter : IPropertyVisitorAdapter + { + /// + /// Invoked when the visitor encounters specific a and pair. + /// + /// The context being visited. + /// The container being visited. + /// The value being visited. + /// if visitation should be skipped, otherwise. + bool IsExcluded(in ExcludeContext context, ref TContainer container, ref TValue value); + } + + /// + /// Implement this interface to filter visitation for a specific type. + /// + /// The value type. + public interface IExcludePropertyAdapter : IPropertyVisitorAdapter + { + /// + /// Invoked when the visitor encounters specific a . + /// + /// The context being visited. + /// The container being visited. + /// The value being visited. + /// The container type being visited. + /// if visitation should be skipped, otherwise. + bool IsExcluded(in ExcludeContext context, ref TContainer container, ref TValue value); + } + + /// + /// Implement this interface to filter visitation. + /// + public interface IExcludePropertyAdapter : IPropertyVisitorAdapter + { + /// + /// Invoked when the visitor encounters any property. + /// + /// The context being visited. + /// The container being visited. + /// The value being visited. + /// The container type being visited. + /// The value type being visited. + /// if visitation should be skipped, otherwise. + bool IsExcluded(in ExcludeContext context, ref TContainer container, ref TValue value); + } + + /// + /// Implement this interface to filter visitation for a specific and pair. + /// + /// The container type being visited. + /// The value type being visited. + public interface IExcludeContravariantPropertyAdapter : IPropertyVisitorAdapter + { + /// + /// Invoked when the visitor encounters specific a and pair. + /// + /// The context being visited. + /// The container being visited. + /// The value being visited. + /// if visitation should be skipped, otherwise. + bool IsExcluded(in ExcludeContext context, ref TContainer container, TValue value); + } + + /// + /// Implement this interface to filter visitation for a specific type. + /// + /// The value type. + public interface IExcludeContravariantPropertyAdapter : IPropertyVisitorAdapter + { + /// + /// Invoked when the visitor encounters any property. + /// + /// The context being visited. + /// The container being visited. + /// The value being visited. + /// The container type being visited. + /// if visitation should be skipped, otherwise. + bool IsExcluded(in ExcludeContext context, ref TContainer container, TValue value); + } +} diff --git a/Modules/Properties/Runtime/PropertyVisitors/Adapters/IVisitPrimitivesPropertyAdapter.cs b/Modules/Properties/Runtime/PropertyVisitors/Adapters/IVisitPrimitivesPropertyAdapter.cs new file mode 100644 index 0000000000..9ff658b131 --- /dev/null +++ b/Modules/Properties/Runtime/PropertyVisitors/Adapters/IVisitPrimitivesPropertyAdapter.cs @@ -0,0 +1,25 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +namespace Unity.Properties +{ + /// + /// Implement this interface to intercept the visitation of any primitive type. + /// + public interface IVisitPrimitivesPropertyAdapter : + IVisitPropertyAdapter, + IVisitPropertyAdapter, + IVisitPropertyAdapter, + IVisitPropertyAdapter, + IVisitPropertyAdapter, + IVisitPropertyAdapter, + IVisitPropertyAdapter, + IVisitPropertyAdapter, + IVisitPropertyAdapter, + IVisitPropertyAdapter, + IVisitPropertyAdapter, + IVisitPropertyAdapter + { + } +} diff --git a/Modules/Properties/Runtime/PropertyVisitors/Adapters/IVisitPropertyAdapter.cs b/Modules/Properties/Runtime/PropertyVisitors/Adapters/IVisitPropertyAdapter.cs new file mode 100644 index 0000000000..24e4258fd3 --- /dev/null +++ b/Modules/Properties/Runtime/PropertyVisitors/Adapters/IVisitPropertyAdapter.cs @@ -0,0 +1,106 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +namespace Unity.Properties +{ + /// + /// Implement this interface to intercept the visitation for a specific and pair. + /// + /// + /// * + /// * + /// + /// The container type being visited. + /// The value type being visited. + public interface IVisitPropertyAdapter : IPropertyVisitorAdapter + { + /// + /// Invoked when the visitor encounters specific a and pair. + /// + /// The context being visited. + /// The container being visited. + /// The value being visited. + void Visit(in VisitContext context, ref TContainer container, ref TValue value); + } + + /// + /// Implement this interface to intercept the visitation for a specific type. + /// + /// + /// + /// + /// + /// The value type being visited. + public interface IVisitPropertyAdapter : IPropertyVisitorAdapter + { + /// + /// Invoked when the visitor encounters specific type with any container. + /// + /// The context being visited. + /// The container being visited. + /// The value being visited. + /// The container type being visited. + void Visit(in VisitContext context, ref TContainer container, ref TValue value); + } + + /// + /// Implement this interface to handle visitation for all properties. + /// + /// + /// + /// + /// + public interface IVisitPropertyAdapter : IPropertyVisitorAdapter + { + /// + /// Invoked when the visitor encounters any property. + /// + /// The context being visited. + /// The container being visited. + /// The value being visited. + /// The value type being visited. + /// The container type being visited. + void Visit(in VisitContext context, ref TContainer container, ref TValue value); + } + + /// + /// Implement this interface to intercept the visitation for a specific and pair. + /// + /// + /// * + /// * + /// + /// The container type being visited. + /// The value type being visited. + public interface IVisitContravariantPropertyAdapter : IPropertyVisitorAdapter + { + /// + /// Invoked when the visitor encounters specific a and pair. + /// + /// The context being visited. + /// The container being visited. + /// The value being visited. + void Visit(in VisitContext context, ref TContainer container, TValue value); + } + + /// + /// Implement this interface to intercept the visitation for a specific type. + /// + /// + /// + /// + /// + /// The value type being visited. + public interface IVisitContravariantPropertyAdapter : IPropertyVisitorAdapter + { + /// + /// Invoked when the visitor encounters specific type with any container. + /// + /// The context being visited. + /// The container being visited. + /// The value being visited. + /// The container type being visited. + void Visit(in VisitContext context, ref TContainer container, TValue value); + } +} diff --git a/Modules/Properties/Runtime/PropertyVisitors/ConcreteTypeVisitor.cs b/Modules/Properties/Runtime/PropertyVisitors/ConcreteTypeVisitor.cs new file mode 100644 index 0000000000..d4494aaffa --- /dev/null +++ b/Modules/Properties/Runtime/PropertyVisitors/ConcreteTypeVisitor.cs @@ -0,0 +1,25 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +namespace Unity.Properties +{ + /// + /// Base class to implement a visitor responsible for getting an object's concrete type as a generic. + /// + /// + /// It is required that the visited object is a container type with a property bag. + /// + public abstract class ConcreteTypeVisitor : IPropertyBagVisitor + { + /// + /// Implement this method to receive the strongly typed callback for a given container. + /// + /// The reference to the container. + /// The container type. + protected abstract void VisitContainer(ref TContainer container); + + void IPropertyBagVisitor.Visit(IPropertyBag properties, ref TContainer container) + => VisitContainer(ref container); + } +} diff --git a/Modules/Properties/Runtime/PropertyVisitors/ExcludeContext.cs b/Modules/Properties/Runtime/PropertyVisitors/ExcludeContext.cs new file mode 100644 index 0000000000..ffa4cc89d1 --- /dev/null +++ b/Modules/Properties/Runtime/PropertyVisitors/ExcludeContext.cs @@ -0,0 +1,61 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +namespace Unity.Properties +{ + /// + /// Context object used during visitation to determine if a property should be visited or not. + /// + /// The container type of the . + /// The value type of the . + public readonly struct ExcludeContext + { + internal static ExcludeContext FromProperty( + PropertyVisitor visitor, + Property property) + { + return new ExcludeContext(visitor, property); + } + + readonly PropertyVisitor m_Visitor; + + /// + /// The property being visited. + /// + public Property Property { get; } + + ExcludeContext(PropertyVisitor visitor, Property property) + { + m_Visitor = visitor; + Property = property; + } + } + + /// + /// Context object used during visitation to determine if a property should be visited or not. + /// + /// The container type of the . + public readonly struct ExcludeContext + { + internal static ExcludeContext FromProperty( + PropertyVisitor visitor, + Property property) + { + return new ExcludeContext(visitor, property); + } + + readonly PropertyVisitor m_Visitor; + + /// + /// The property being visited. + /// + public IProperty Property { get; } + + ExcludeContext(PropertyVisitor visitor, IProperty property) + { + m_Visitor = visitor; + Property = property; + } + } +} diff --git a/Modules/Properties/Runtime/PropertyVisitors/IAccept.cs b/Modules/Properties/Runtime/PropertyVisitors/IAccept.cs new file mode 100644 index 0000000000..902f7692b9 --- /dev/null +++ b/Modules/Properties/Runtime/PropertyVisitors/IAccept.cs @@ -0,0 +1,147 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +namespace Unity.Properties +{ + /// + /// Interface for accepting collection property bags visitation. + /// + public interface ICollectionPropertyBagAccept + { + /// + /// Call this method to invoke with the strongly typed container. + /// + /// The visitor being run. + /// The container being visited. + void Accept(ICollectionPropertyBagVisitor visitor, ref TContainer container); + } + + /// + /// Interface for accepting list property bags visitation. + /// + public interface IListPropertyBagAccept + { + /// + /// Call this method to invoke with the strongly typed container. + /// + /// The visitor being run. + /// The container being visited. + void Accept(IListPropertyBagVisitor visitor, ref TContainer container); + } + + /// + /// Interface for accepting list property bags visitation. + /// + public interface ISetPropertyBagAccept + { + /// + /// Call this method to invoke with the strongly typed container. + /// + /// The visitor being run. + /// The container being visited. + void Accept(ISetPropertyBagVisitor visitor, ref TContainer container); + } + + /// + /// Interface for accepting list property bags visitation. + /// + public interface IDictionaryPropertyBagAccept + { + /// + /// Call this method to invoke with the strongly typed container. + /// + /// The visitor being run. + /// The container being visited. + void Accept(IDictionaryPropertyBagVisitor visitor, ref TContainer container); + } + + /// + /// Interface for accepting property visitation. + /// + public interface IPropertyAccept + { + /// + /// Call this method to invoke with the strongly typed container and value. + /// + /// The visitor being run. + /// The container being visited. + void Accept(IPropertyVisitor visitor, ref TContainer container); + } + + /// + /// Interface for accepting collection property visitation. + /// + public interface ICollectionPropertyAccept + { + /// + /// Call this method to invoke . + /// + /// + /// This method is used to join the container type and element type. + /// + /// The visitor being run. + /// The property being visited. + /// The container being visited. + /// The collection value + /// The container type. + void Accept(ICollectionPropertyVisitor visitor, Property property, ref TContainer container, ref TCollection collection); + } + + /// + /// Interface for accepting list property visitation. + /// + public interface IListPropertyAccept + { + /// + /// Call this method to invoke . + /// + /// + /// This method is used to join the container type and element type. + /// + /// The visitor being run. + /// The property being visited. + /// The container being visited. + /// The list value. + /// The container type. + void Accept(IListPropertyVisitor visitor, Property property, ref TContainer container, ref TList list); + } + + /// + /// Interface for accepting hash set property visitation. + /// + public interface ISetPropertyAccept + { + /// + /// Call this method to invoke . + /// + /// + /// This method is used to join the container, the key and the value type. + /// + /// The visitor being run. + /// The property being visited. + /// The container being visited. + /// The set value. + /// The container type. + void Accept(ISetPropertyVisitor visitor, Property property, ref TContainer container, ref TSet set); + } + + /// + /// Interface for accepting dictionary property visitation. + /// + public interface IDictionaryPropertyAccept + { + /// + /// Call this method to invoke . + /// + /// + /// This method is used to join the container, the key and the value type. + /// + /// The visitor being run. + /// The property being visited. + /// The container being visited. + /// The dictionary value. + /// The container type. + void Accept(IDictionaryPropertyVisitor visitor, Property property, ref TContainer container, ref TDictionary dictionary); + } +} diff --git a/Modules/Properties/Runtime/PropertyVisitors/IVisitor.cs b/Modules/Properties/Runtime/PropertyVisitors/IVisitor.cs new file mode 100644 index 0000000000..a100aa3361 --- /dev/null +++ b/Modules/Properties/Runtime/PropertyVisitors/IVisitor.cs @@ -0,0 +1,220 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System.Collections.Generic; + +namespace Unity.Properties +{ + /// + /// Interface used to receive a visitation callbacks for a specific type. + /// + public interface ITypeVisitor + { + /// + /// Implement this method to accept visitation for container type. + /// + /// The container type. + void Visit(); + } + + /// + /// Interface used to receive a visitation callbacks for property bags. + /// + public interface IPropertyBagVisitor + { + /// + /// Implement this method to accept visitation for a property bag and container. + /// + /// + /// This method is invoked by . + /// + /// The properties of the container. + /// The container being visited. + /// The container type. + void Visit(IPropertyBag properties, ref TContainer container); + } + + /// + /// Interface for visiting property bags. + /// + public interface ICollectionPropertyBagVisitor + { + /// + /// Implement this method to accept visitation for a collection of properties. + /// + /// + /// This method is invoked by . + /// + /// The properties of the container. + /// The container being visited. + /// The list type. + /// The element type. + void Visit(ICollectionPropertyBag properties, ref TCollection container) + where TCollection : ICollection; + } + + /// + /// Interface for visiting property bags. + /// + public interface IListPropertyBagVisitor + { + /// + /// Implement this method to accept visitation for a collection of properties. + /// + /// + /// This method is invoked by . + /// + /// The properties of the container. + /// The container being visited. + /// The list type. + /// The element type. + void Visit(IListPropertyBag properties, ref TList container) + where TList : IList; + } + + /// + /// Interface for visiting property bags. + /// + public interface ISetPropertyBagVisitor + { + /// + /// Implement this method to accept visitation for a collection of properties. + /// + /// + /// This method is invoked by . + /// + /// The properties of the container. + /// The container being visited. + /// The set type. + /// The value type. + void Visit(ISetPropertyBag properties, ref TSet container) + where TSet : ISet; + } + + /// + /// Interface for visiting property bags. + /// + public interface IDictionaryPropertyBagVisitor + { + /// + /// Implement this method to accept visitation for a collection of properties. + /// + /// + /// This method is invoked by . + /// + /// The properties of the container. + /// The container being visited. + /// The dictionary type. + /// The key type. + /// The value type. + void Visit(IDictionaryPropertyBag properties, ref TDictionary container) + where TDictionary : IDictionary; + } + + /// + /// Interface for receiving strongly typed property callbacks. + /// + public interface IPropertyVisitor + { + /// + /// Implement this method to accept visitation for a specific property. + /// + /// + /// This method is invoked by + /// + /// The property being visited. + /// The container being visited. + /// The container type. + /// The value type. + void Visit(Property property, ref TContainer container); + } + + /// + /// Interface for receiving strongly typed property callbacks for collections. + /// + public interface ICollectionPropertyVisitor + { + /// + /// Implement this method to accept visitation for a specialized collection property. + /// + /// + /// This method is invoked by + /// + /// The property being visited. + /// The container being visited. + /// The collection value. + /// The container type. + /// The collection value type. + /// The collection element type. + void Visit(Property property, ref TContainer container, ref TCollection collection) + where TCollection : ICollection; + } + + /// + /// Interface for receiving strongly typed property callbacks for lists. + /// + public interface IListPropertyVisitor + { + /// + /// Implement this method to accept visitation for a specialized list property. + /// + /// + /// This method is invoked by + /// + /// The property being visited. + /// The container being visited. + /// The list value. + /// The container type. + /// The list value type. + /// The collection element type. + void Visit(Property property, ref TContainer container, ref TList list) + where TList : IList; + } + + /// + /// Interface for receiving strongly typed property callbacks for sets. + /// + public interface ISetPropertyVisitor + { + /// + /// Implement this method to accept visitation for a specialized set property. + /// + /// + /// This method is invoked by + /// + /// The property being visited. + /// The container being visited. + /// The hash set value. + /// The container type. + /// The set value type. + /// The value type. + void Visit(Property property, ref TContainer container, ref TSet set) + where TSet : ISet; + } + + /// + /// Interface for receiving strongly typed property callbacks for dictionaries. + /// + /// + /// + /// + public interface IDictionaryPropertyVisitor + { + /// + /// Implement this method to accept visitation for a specialized dictionary property. + /// + /// + /// This method is invoked by + /// + /// The property being visited. + /// The container being visited. + /// The dictionary value. + /// The container type. + /// The dictionary value type. + /// The key type. + /// The value type. + void Visit(Property property, ref TContainer container, ref TDictionary dictionary) + where TDictionary : IDictionary; + } +} diff --git a/Modules/Properties/Runtime/PropertyVisitors/Internal/ReadOnlyAdapterCollection.cs b/Modules/Properties/Runtime/PropertyVisitors/Internal/ReadOnlyAdapterCollection.cs new file mode 100644 index 0000000000..9e54f62a48 --- /dev/null +++ b/Modules/Properties/Runtime/PropertyVisitors/Internal/ReadOnlyAdapterCollection.cs @@ -0,0 +1,56 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System.Collections.Generic; + +namespace Unity.Properties.Internal +{ + readonly struct ReadOnlyAdapterCollection + { + public struct Enumerator + { + List m_Adapters; + int m_Index; + public IPropertyVisitorAdapter Current { get; private set; } + + public Enumerator(ReadOnlyAdapterCollection collection) + { + m_Adapters = collection.m_Adapters; + m_Index = 0; + Current = default; + } + + public bool MoveNext() + { + if (null == m_Adapters) + return false; + + if (m_Index >= m_Adapters.Count) + return false; + + Current = m_Adapters[m_Index]; + ++m_Index; + return true; + } + + void Reset() + { + m_Index = 0; + Current = default; + } + } + + readonly List m_Adapters; + + public ReadOnlyAdapterCollection(List adapters) + { + m_Adapters = adapters; + } + + public Enumerator GetEnumerator() + { + return new Enumerator(this); + } + } +} diff --git a/Modules/Properties/Runtime/PropertyVisitors/PathVisitor.cs b/Modules/Properties/Runtime/PropertyVisitors/PathVisitor.cs new file mode 100644 index 0000000000..f01b5e026c --- /dev/null +++ b/Modules/Properties/Runtime/PropertyVisitors/PathVisitor.cs @@ -0,0 +1,164 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using System.Collections.Generic; + +namespace Unity.Properties +{ + /// + /// Helper visitor to visit a single property using a specified . + /// + public abstract class PathVisitor : IPropertyBagVisitor, IPropertyVisitor + { + readonly struct PropertyScope : IDisposable + { + readonly PathVisitor m_Visitor; + readonly IProperty m_Property; + + public PropertyScope(PathVisitor visitor, IProperty property) + { + m_Visitor = visitor; + m_Property = m_Visitor.Property; + m_Visitor.Property = property; + } + + public void Dispose() => m_Visitor.Property = m_Property; + } + + int m_PathIndex; + + /// + /// The path to visit. + /// + public PropertyPath Path { get; set; } + + /// + /// Resets the state of the visitor. + /// + public virtual void Reset() + { + m_PathIndex = 0; + Path = default; + ReturnCode = VisitReturnCode.Ok; + ReadonlyVisit = false; + } + + /// + /// Returns the property for the currently visited container. + /// + IProperty Property { get; set; } + + /// + /// Returns whether or not the visitor will write back values along the path. + /// + public bool ReadonlyVisit { get; set; } + + /// + /// Returns the error code encountered while visiting the provided path. + /// + public VisitReturnCode ReturnCode { get; protected set; } + + void IPropertyBagVisitor.Visit(IPropertyBag properties, ref TContainer container) + { + var part = Path[m_PathIndex++]; + + IProperty property; + + switch (part.Kind) + { + case PropertyPathPartKind.Name: + { + if (properties is INamedProperties named && named.TryGetProperty(ref container, part.Name, out property)) + { + property.Accept(this, ref container); + } + else + { + ReturnCode = VisitReturnCode.InvalidPath; + } + } + break; + + case PropertyPathPartKind.Index: + { + if (properties is IIndexedProperties indexable && indexable.TryGetProperty(ref container, part.Index, out property)) + { + using ((property as Internal.IAttributes).CreateAttributesScope(Property as Internal.IAttributes)) + { + property.Accept(this, ref container); + } + } + else + { + ReturnCode = VisitReturnCode.InvalidPath; + } + } + break; + + case PropertyPathPartKind.Key: + { + if (properties is IKeyedProperties keyable && keyable.TryGetProperty(ref container, part.Key, out property)) + { + using ((property as Internal.IAttributes).CreateAttributesScope(Property as Internal.IAttributes)) + { + property.Accept(this, ref container); + } + } + else + { + ReturnCode = VisitReturnCode.InvalidPath; + } + } + break; + + default: + ReturnCode = VisitReturnCode.InvalidPath; + break; + } + } + + void IPropertyVisitor.Visit(Property property, ref TContainer container) + { + var value = property.GetValue(ref container); + + if (m_PathIndex >= Path.Length) + { + VisitPath(property, ref container, ref value); + } + else if (PropertyBag.TryGetPropertyBagForValue(ref value, out _)) + { + if (TypeTraits.CanBeNull && EqualityComparer.Default.Equals(value, default)) + { + ReturnCode = VisitReturnCode.InvalidPath; + return; + } + using (new PropertyScope(this, property)) + { + + PropertyContainer.Accept(this, ref value); + } + + if (!property.IsReadOnly && !ReadonlyVisit) + property.SetValue(ref container, value); + } + else + { + ReturnCode = VisitReturnCode.InvalidPath; + } + } + + /// + /// Method called when the visitor has successfully visited the provided path. + /// + /// + /// + /// + /// + /// + protected virtual void VisitPath(Property property, ref TContainer container, ref TValue value) + { + } + } +} diff --git a/Modules/Properties/Runtime/PropertyVisitors/PropertyVisitor.cs b/Modules/Properties/Runtime/PropertyVisitors/PropertyVisitor.cs new file mode 100644 index 0000000000..dcbf4cfec0 --- /dev/null +++ b/Modules/Properties/Runtime/PropertyVisitors/PropertyVisitor.cs @@ -0,0 +1,309 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System.Collections.Generic; +using Unity.Properties.Internal; + +namespace Unity.Properties +{ + /// + /// The base interface representing an adapter. + /// + public interface IPropertyVisitorAdapter + { + + } + + /// + /// Base class for implementing algorithms using properties. This is an abstract class. + /// + public abstract class PropertyVisitor : + IPropertyBagVisitor, + IListPropertyBagVisitor, + IDictionaryPropertyBagVisitor, + IPropertyVisitor, + ICollectionPropertyVisitor, + IListPropertyVisitor, + ISetPropertyVisitor, + IDictionaryPropertyVisitor + { + readonly List m_Adapters = new List(); + + /// + /// Adds an adapter to the visitor. + /// + /// The adapter to add. + public void AddAdapter(IPropertyVisitorAdapter adapter) => m_Adapters.Add(adapter); + + /// + /// Removes an adapter from the visitor. + /// + /// The adapter to remove. + public void RemoveAdapter(IPropertyVisitorAdapter adapter) => m_Adapters.Remove(adapter); + + /// + void IPropertyBagVisitor.Visit(IPropertyBag properties, ref TContainer container) + { + foreach (var property in properties.GetProperties(ref container)) + { + property.Accept(this, ref container); + } + } + + /// + void IListPropertyBagVisitor.Visit(IListPropertyBag properties, ref TList container) + { + foreach (var property in properties.GetProperties(ref container)) + { + property.Accept(this, ref container); + } + } + + /// + void IDictionaryPropertyBagVisitor.Visit(IDictionaryPropertyBag properties, ref TDictionary container) + { + foreach (var property in properties.GetProperties(ref container)) + { + property.Accept(this, ref container); + } + } + + /// + void IPropertyVisitor.Visit(Property property, ref TContainer container) + { + var value = property.GetValue(ref container); + + // Give adapters a chance to exclude. + if (IsExcluded(property, new ReadOnlyAdapterCollection(m_Adapters).GetEnumerator(), ref container, ref value)) + return; + + // Give the explicit overrides a chance to exclude. + if (IsExcluded(property, ref container, ref value)) + return; + + ContinueVisitation(property, new ReadOnlyAdapterCollection(m_Adapters).GetEnumerator(), ref container, ref value); + if (!property.IsReadOnly) property.SetValue(ref container, value); + } + + internal void ContinueVisitation(Property property, ref TContainer container, ref TValue value) + { + if (PropertyBagStore.TryGetPropertyBagForValue(ref value, out var valuePropertyBag)) + { + switch (valuePropertyBag) + { + case IDictionaryPropertyAccept accept: + accept.Accept(this, property, ref container, ref value); + return; + case IListPropertyAccept accept: + accept.Accept(this, property, ref container, ref value); + return; + case ISetPropertyAccept accept: + accept.Accept(this, property, ref container, ref value); + return; + case ICollectionPropertyAccept accept: + accept.Accept(this, property, ref container, ref value); + return; + } + } + + VisitProperty(property, ref container, ref value); + } + + /// + void ICollectionPropertyVisitor.Visit(Property property, ref TContainer container, ref TCollection collection) + { + VisitCollection(property, ref container, ref collection); + } + + /// + void IListPropertyVisitor.Visit(Property property, ref TContainer container, ref TList list) + { + VisitList(property, ref container, ref list); + } + + /// + void ISetPropertyVisitor.Visit(Property property, ref TContainer container, ref TSet set) + { + VisitSet(property, ref container, ref set); + } + + /// + void IDictionaryPropertyVisitor.Visit(Property property, ref TContainer container, ref TDictionary dictionary) + { + VisitDictionary(property, ref container, ref dictionary); + } + + /// + /// Called before visiting each property to determine if the property should be visited. + /// + /// + /// This method is called after all have had a chance to run. + /// + /// The property providing access to the data. + /// The container being visited. + /// The value being visited. + /// The container type. + /// The value type. + /// if the property should be skipped; otherwise, . + protected virtual bool IsExcluded(Property property, ref TContainer container, ref TValue value) + { + return false; + } + + /// + /// Called when visiting any leaf property with no specialized handling. + /// + /// The property providing access to the value. + /// The container being visited. + /// The value being visited. + /// The container type. + /// The value type. + protected virtual void VisitProperty(Property property, ref TContainer container, ref TValue value) + { + PropertyContainer.TryAccept(this, ref value); + } + + /// + /// Called when visiting any non-specialized collection property. + /// + /// + /// When visiting a specialized collection the appropriate method will be called. + /// * + /// + /// The property providing access to the collection. + /// The container being visited. + /// The collection being visited. + /// The container type. + /// The collection type. + /// The element type. + protected virtual void VisitCollection(Property property, ref TContainer container, ref TCollection value) + where TCollection : ICollection + { + VisitProperty(property, ref container, ref value); + } + + /// + /// Called when visiting a specialized list property. + /// + /// The property providing access to the list. + /// The container being visited. + /// The list being visited. + /// The container type. + /// The list type. + /// The element type. + protected virtual void VisitList(Property property, ref TContainer container, ref TList value) + where TList : IList + { + VisitCollection(property, ref container, ref value); + } + + /// + /// Called when visiting a specialized set property. + /// + /// The property providing access to the list. + /// The container being visited. + /// The list being visited. + /// The container type. + /// The set type. + /// The element type. + protected virtual void VisitSet(Property property, ref TContainer container, ref TSet value) + where TSet : ISet + { + VisitCollection(property, ref container, ref value); + } + + /// + /// Called when visiting a specialized dictionary property. + /// + /// The property providing access to the dictionary. + /// The container being visited. + /// The dictionary being visited. + /// The container type. + /// The dictionary type. + /// The key type. + /// The value type. + protected virtual void VisitDictionary(Property property, ref TContainer container, ref TDictionary value) + where TDictionary : IDictionary + { + VisitCollection>(property, ref container, ref value); + } + + bool IsExcluded(Property property, ReadOnlyAdapterCollection.Enumerator enumerator, ref TContainer container, ref TValue value) + { + while (enumerator.MoveNext()) + { + var adapter = enumerator.Current; + + switch (adapter) + { + case IExcludePropertyAdapter typed: + if (typed.IsExcluded(ExcludeContext.FromProperty(this, property), ref container, ref value)) + return true; + break; + case IExcludeContravariantPropertyAdapter typed: + { + var excluded = typed.IsExcluded(ExcludeContext.FromProperty(this, property), ref container, value); + value = property.GetValue(ref container); + if (excluded) + return true; + } + break; + case IExcludePropertyAdapter typed: + if (typed.IsExcluded(ExcludeContext.FromProperty(this, property), ref container, ref value)) + return true; + break; + case IExcludeContravariantPropertyAdapter typed: + { + var excluded = typed.IsExcluded(ExcludeContext.FromProperty(this, property), ref container, value); + value = property.GetValue(ref container); + if (excluded) + return true; + } + break; + case IExcludePropertyAdapter typed: + if (typed.IsExcluded(ExcludeContext.FromProperty(this, property), ref container, ref value)) + return true; + break; + } + } + return false; + } + + internal void ContinueVisitation(Property property, ReadOnlyAdapterCollection.Enumerator enumerator, ref TContainer container, ref TValue value) + { + while (enumerator.MoveNext()) + { + var adapter = enumerator.Current; + switch (adapter) + { + case IVisitPropertyAdapter typed: + typed.Visit(VisitContext.FromProperty(this, enumerator, property), ref container, ref value); + return; + case IVisitContravariantPropertyAdapter typed: + typed.Visit(VisitContext.FromProperty(this, enumerator, property), ref container, value); + value = property.GetValue(ref container); + return; + case IVisitPropertyAdapter typed: + typed.Visit(VisitContext.FromProperty(this, enumerator, property), ref container, ref value); + return; + case IVisitContravariantPropertyAdapter typed: + typed.Visit(VisitContext.FromProperty(this, enumerator, property), ref container, value); + value = property.GetValue(ref container); + return; + case IVisitPropertyAdapter typed: + typed.Visit(VisitContext.FromProperty(this, enumerator, property), ref container, ref value); + return; + } + } + + ContinueVisitationWithoutAdapters(property, enumerator, ref container, ref value); + } + + /// + internal void ContinueVisitationWithoutAdapters(Property property, ReadOnlyAdapterCollection.Enumerator enumerator, ref TContainer container, ref TValue value) + { + ContinueVisitation(property, ref container, ref value); + } + } +} diff --git a/Modules/Properties/Runtime/PropertyVisitors/VisitContext.cs b/Modules/Properties/Runtime/PropertyVisitors/VisitContext.cs new file mode 100644 index 0000000000..523a0a04d3 --- /dev/null +++ b/Modules/Properties/Runtime/PropertyVisitors/VisitContext.cs @@ -0,0 +1,132 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using Unity.Properties.Internal; + +namespace Unity.Properties +{ + /// + /// Context object used during visitation when a is visited. + /// + /// The container type of the . + /// The value type of the . + public readonly struct VisitContext + { + internal static VisitContext FromProperty( + PropertyVisitor visitor, + ReadOnlyAdapterCollection.Enumerator enumerator, + Property property) + { + return new VisitContext(visitor, enumerator, property); + } + + readonly ReadOnlyAdapterCollection.Enumerator m_Enumerator; + readonly PropertyVisitor m_Visitor; + + /// + /// The property being visited. + /// + public Property Property { get; } + + VisitContext( + PropertyVisitor visitor, + ReadOnlyAdapterCollection.Enumerator enumerator, + Property property) + { + m_Visitor = visitor; + m_Enumerator = enumerator; + Property = property; + } + + /// + /// Continues visitation through the next visitation adapter. + /// + /// The container being visited. + /// The value being visited. + public void ContinueVisitation(ref TContainer container, ref TValue value) + { + m_Visitor.ContinueVisitation(Property, m_Enumerator, ref container, ref value); + } + + /// + /// Continues visitation while skipping the next visitation adapters. + /// + /// The container being visited. + /// The value being visited. + public void ContinueVisitationWithoutAdapters(ref TContainer container, ref TValue value) + { + m_Visitor.ContinueVisitationWithoutAdapters(Property, m_Enumerator, ref container, ref value); + } + } + + /// + /// Context object used during visitation when a is visited. + /// + /// The container type of the . + public readonly struct VisitContext + { + delegate void VisitDelegate(PropertyVisitor visitor, ReadOnlyAdapterCollection.Enumerator enumerator, IProperty property, ref TContainer container); + delegate void VisitWithoutAdaptersDelegate(PropertyVisitor visitor, IProperty property, ref TContainer container); + + internal static VisitContext FromProperty( + PropertyVisitor visitor, + ReadOnlyAdapterCollection.Enumerator enumerator, + Property property) + { + return new VisitContext(visitor, enumerator, property, (PropertyVisitor v, ReadOnlyAdapterCollection.Enumerator e, IProperty p, ref TContainer c) => + { + var typedProperty = (Property) p; + var value = typedProperty.GetValue(ref c); + v.ContinueVisitation(typedProperty, e, ref c, ref value); + }, (PropertyVisitor v, IProperty p, ref TContainer c) => + { + var typedProperty = (Property) p; + var value = typedProperty.GetValue(ref c); + v.ContinueVisitation(typedProperty, ref c, ref value); + }); + } + + readonly ReadOnlyAdapterCollection.Enumerator m_Enumerator; + readonly PropertyVisitor m_Visitor; + readonly VisitDelegate m_Continue; + readonly VisitWithoutAdaptersDelegate m_ContinueWithoutAdapters; + + /// + /// The property being visited. + /// + public IProperty Property { get; } + + VisitContext( + PropertyVisitor visitor, + ReadOnlyAdapterCollection.Enumerator enumerator, + IProperty property, + VisitDelegate continueVisitation, + VisitWithoutAdaptersDelegate continueVisitationWithoutAdapters) + { + m_Visitor = visitor; + m_Enumerator = enumerator; + Property = property; + m_Continue = continueVisitation; + m_ContinueWithoutAdapters = continueVisitationWithoutAdapters; + } + + /// + /// Continues visitation through the next visitation adapter. + /// + /// The container being visited. + public void ContinueVisitation(ref TContainer container) + { + m_Continue(m_Visitor, m_Enumerator, Property, ref container); + } + + /// + /// Continues visitation while skipping the next visitation adapters. + /// + /// The container being visited. + public void ContinueVisitationWithoutAdapters(ref TContainer container) + { + m_ContinueWithoutAdapters(m_Visitor, Property, ref container); + } + } +} diff --git a/Modules/Properties/Runtime/Reflection/Internal/ReflectedPropertyBag.cs b/Modules/Properties/Runtime/Reflection/Internal/ReflectedPropertyBag.cs new file mode 100644 index 0000000000..40f56ef14c --- /dev/null +++ b/Modules/Properties/Runtime/Reflection/Internal/ReflectedPropertyBag.cs @@ -0,0 +1,33 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using UnityEngine; + +namespace Unity.Properties.Internal +{ + class ReflectedPropertyBagAttribute : Attribute{} + + [ReflectedPropertyBag] + class ReflectedPropertyBag : ContainerPropertyBag + { + internal new void AddProperty(Property property) + { + var container = default(TContainer); + + if (TryGetProperty(ref container, property.Name, out var existing)) + { + if (existing.DeclaredValueType() == typeof(TValue)) + { + // Property with the same name and value type, it's safe to ignore. + return; + } + Debug.LogWarning($"Detected multiple return types for PropertyBag=[{TypeUtility.GetTypeDisplayName(typeof(TContainer))}] Property=[{property.Name}]. The property will use the most derived Type=[{TypeUtility.GetTypeDisplayName(existing.DeclaredValueType())}] and IgnoreType=[{TypeUtility.GetTypeDisplayName(property.DeclaredValueType())}]."); + return; + } + + base.AddProperty(property); + } + } +} diff --git a/Modules/Properties/Runtime/Reflection/Internal/ReflectedPropertyBagProvider.cs b/Modules/Properties/Runtime/Reflection/Internal/ReflectedPropertyBagProvider.cs new file mode 100644 index 0000000000..114fc8c9ad --- /dev/null +++ b/Modules/Properties/Runtime/Reflection/Internal/ReflectedPropertyBagProvider.cs @@ -0,0 +1,243 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using UnityEngine; +using UnityEngine.Scripting; + +namespace Unity.Properties.Internal +{ + class ReflectedPropertyBagProvider + { + readonly MethodInfo m_CreatePropertyMethod; + readonly MethodInfo m_CreatePropertyBagMethod; + readonly MethodInfo m_CreateIndexedCollectionPropertyBagMethod; + readonly MethodInfo m_CreateSetPropertyBagMethod; + readonly MethodInfo m_CreateKeyValueCollectionPropertyBagMethod; + readonly MethodInfo m_CreateKeyValuePairPropertyBagMethod; + readonly MethodInfo m_CreateArrayPropertyBagMethod; + readonly MethodInfo m_CreateListPropertyBagMethod; + readonly MethodInfo m_CreateHashSetPropertyBagMethod; + readonly MethodInfo m_CreateDictionaryPropertyBagMethod; + + public ReflectedPropertyBagProvider() + { + m_CreatePropertyMethod = typeof(ReflectedPropertyBagProvider).GetMethod(nameof(CreateProperty), BindingFlags.Instance | BindingFlags.NonPublic); + m_CreatePropertyBagMethod = typeof(ReflectedPropertyBagProvider).GetMethods(BindingFlags.Instance | BindingFlags.Public).First(x => x.Name == nameof(CreatePropertyBag) && x.IsGenericMethod); + + // Generic interface property bag types (e.g. IList, ISet, IDictionary) + m_CreateIndexedCollectionPropertyBagMethod = typeof(ReflectedPropertyBagProvider).GetMethod(nameof(CreateIndexedCollectionPropertyBag), BindingFlags.Instance | BindingFlags.NonPublic); + m_CreateSetPropertyBagMethod = typeof(ReflectedPropertyBagProvider).GetMethod(nameof(CreateSetPropertyBag), BindingFlags.Instance | BindingFlags.NonPublic); + m_CreateKeyValueCollectionPropertyBagMethod = typeof(ReflectedPropertyBagProvider).GetMethod(nameof(CreateKeyValueCollectionPropertyBag), BindingFlags.Instance | BindingFlags.NonPublic); + m_CreateKeyValuePairPropertyBagMethod = typeof(ReflectedPropertyBagProvider).GetMethod(nameof(CreateKeyValuePairPropertyBag), BindingFlags.Instance | BindingFlags.NonPublic); + + // Concrete collection property bag types (e.g. List, HashSet, Dictionary + m_CreateArrayPropertyBagMethod = typeof(ReflectedPropertyBagProvider).GetMethod(nameof(CreateArrayPropertyBag), BindingFlags.Instance | BindingFlags.NonPublic); + m_CreateListPropertyBagMethod = typeof(ReflectedPropertyBagProvider).GetMethod(nameof(CreateListPropertyBag), BindingFlags.Instance | BindingFlags.NonPublic); + m_CreateHashSetPropertyBagMethod = typeof(ReflectedPropertyBagProvider).GetMethod(nameof(CreateHashSetPropertyBag), BindingFlags.Instance | BindingFlags.NonPublic); + m_CreateDictionaryPropertyBagMethod = typeof(ReflectedPropertyBagProvider).GetMethod(nameof(CreateDictionaryPropertyBag), BindingFlags.Instance | BindingFlags.NonPublic); + } + + public IPropertyBag CreatePropertyBag(Type type) + { + if (type.IsGenericTypeDefinition) return null; + return (IPropertyBag) m_CreatePropertyBagMethod.MakeGenericMethod(type).Invoke(this, null); + } + + public IPropertyBag CreatePropertyBag() + { + if (!TypeTraits.IsContainer || TypeTraits.IsObject) + { + throw new InvalidOperationException("Invalid container type."); + } + + if (typeof(TContainer).IsArray) + { + if (typeof(TContainer).GetArrayRank() != 1) + { + throw new InvalidOperationException("Properties does not support multidimensional arrays."); + } + + return (IPropertyBag) m_CreateArrayPropertyBagMethod.MakeGenericMethod(typeof(TContainer).GetElementType()).Invoke(this, new object[0]); + } + + if (typeof(TContainer).IsGenericType && typeof(TContainer).GetGenericTypeDefinition().IsAssignableFrom(typeof(List<>))) + return (IPropertyBag) m_CreateListPropertyBagMethod.MakeGenericMethod(typeof(TContainer).GetGenericArguments().First()).Invoke(this, new object[0]); + + if (typeof(TContainer).IsGenericType && typeof(TContainer).GetGenericTypeDefinition().IsAssignableFrom(typeof(HashSet<>))) + return (IPropertyBag) m_CreateHashSetPropertyBagMethod.MakeGenericMethod(typeof(TContainer).GetGenericArguments().First()).Invoke(this, new object[0]); + + if (typeof(TContainer).IsGenericType && typeof(TContainer).GetGenericTypeDefinition().IsAssignableFrom(typeof(Dictionary<,>))) + return (IPropertyBag) m_CreateDictionaryPropertyBagMethod.MakeGenericMethod(typeof(TContainer).GetGenericArguments().First(), typeof(TContainer).GetGenericArguments().ElementAt(1)).Invoke(this, new object[0]); + + if (typeof(TContainer).IsGenericType && typeof(TContainer).GetGenericTypeDefinition().IsAssignableFrom(typeof(IList<>))) + return (IPropertyBag) m_CreateIndexedCollectionPropertyBagMethod.MakeGenericMethod(typeof(TContainer), typeof(TContainer).GetGenericArguments().First()).Invoke(this, new object[0]); + + if (typeof(TContainer).IsGenericType && typeof(TContainer).GetGenericTypeDefinition().IsAssignableFrom(typeof(ISet<>))) + return (IPropertyBag) m_CreateSetPropertyBagMethod.MakeGenericMethod(typeof(TContainer), typeof(TContainer).GetGenericArguments().First()).Invoke(this, new object[0]); + + if (typeof(TContainer).IsGenericType && typeof(TContainer).GetGenericTypeDefinition().IsAssignableFrom(typeof(IDictionary<,>))) + return (IPropertyBag) m_CreateKeyValueCollectionPropertyBagMethod.MakeGenericMethod(typeof(TContainer), typeof(TContainer).GetGenericArguments().First(), typeof(TContainer).GetGenericArguments().ElementAt(1)).Invoke(this, new object[0]); + + if (typeof(TContainer).IsGenericType && typeof(TContainer).GetGenericTypeDefinition().IsAssignableFrom(typeof(KeyValuePair<,>))) + { + var types = typeof(TContainer).GetGenericArguments().ToArray(); + return (IPropertyBag) m_CreateKeyValuePairPropertyBagMethod.MakeGenericMethod(types[0], types[1]).Invoke(this, new object[0]); + } + + var propertyBag = new ReflectedPropertyBag(); + + foreach (var member in GetPropertyMembers(typeof(TContainer))) + { + IMemberInfo info; + + switch (member) + { + case FieldInfo field: + info = new FieldMember(field); + break; + case PropertyInfo property: + info = new PropertyMember(property); + break; + default: + throw new InvalidOperationException(); + } + + m_CreatePropertyMethod.MakeGenericMethod(typeof(TContainer), info.ValueType).Invoke(this, new object[] + { + info, + propertyBag + }); + } + + return propertyBag; + } + + [Preserve] + void CreateProperty(IMemberInfo member, ReflectedPropertyBag propertyBag) + { + if (typeof(TValue).IsPointer) + { + return; + } + + propertyBag.AddProperty(new ReflectedMemberProperty(member, member.Name)); + } + + [Preserve] IPropertyBag CreateIndexedCollectionPropertyBag() where TList : IList + => new IndexedCollectionPropertyBag(); + + [Preserve] IPropertyBag CreateSetPropertyBag() where TSet : ISet + => new SetPropertyBagBase(); + + [Preserve] IPropertyBag CreateKeyValueCollectionPropertyBag() where TDictionary : IDictionary + => new KeyValueCollectionPropertyBag(); + + [Preserve] IPropertyBag> CreateKeyValuePairPropertyBag() + => new KeyValuePairPropertyBag(); + + [Preserve] IPropertyBag CreateArrayPropertyBag() + => new ArrayPropertyBag(); + + [Preserve] IPropertyBag> CreateListPropertyBag() + => new ListPropertyBag(); + + [Preserve] IPropertyBag> CreateHashSetPropertyBag() + => new HashSetPropertyBag(); + + [Preserve] IPropertyBag> CreateDictionaryPropertyBag() + => new DictionaryPropertyBag(); + + static IEnumerable GetPropertyMembers(Type type) + { + do + { + var members = type.GetMembers(BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic).OrderBy(x => x.MetadataToken); + + foreach (var member in members) + { + if (member.MemberType != MemberTypes.Field && member.MemberType != MemberTypes.Property) + { + continue; + } + + if (member.DeclaringType != type) + { + continue; + } + + if (!IsValidMember(member)) + { + continue; + } + + // Gather all possible attributes we care about. + var hasDontCreatePropertyAttribute = member.GetCustomAttribute() != null; + var hasCreatePropertyAttribute = member.GetCustomAttribute() != null; + var hasNonSerializedAttribute = member.GetCustomAttribute() != null; + var hasSerializedFieldAttribute = member.GetCustomAttribute() != null; + + if (hasDontCreatePropertyAttribute) + { + // This attribute trumps all others. No matter what a property should NOT be generated. + continue; + } + + if (hasCreatePropertyAttribute) + { + // The user explicitly requests an attribute, one will be generated, regardless of serialization attributes. + yield return member; + continue; + } + + if (hasNonSerializedAttribute) + { + // If property generation was not explicitly specified lets keep behaviour consistent with Unity. + continue; + } + + if (hasSerializedFieldAttribute) + { + // If property generation was not explicitly specified lets keep behaviour consistent with Unity. + yield return member; + continue; + } + + // No attributes were specified, if this is a public field we will generate one by implicitly. + if (member is FieldInfo field && field.IsPublic) + { + yield return member; + } + } + + type = type.BaseType; + } + while (type != null && type != typeof(object)); + } + + static bool IsValidMember(MemberInfo memberInfo) + { + switch (memberInfo) + { + case FieldInfo fieldInfo: + return !fieldInfo.IsStatic && IsValidPropertyType(fieldInfo.FieldType); + case PropertyInfo propertyInfo: + return null != propertyInfo.GetMethod && !propertyInfo.GetMethod.IsStatic && IsValidPropertyType(propertyInfo.PropertyType); + } + + return false; + } + + static bool IsValidPropertyType(Type type) + { + if (type.IsPointer) + return false; + + return !type.IsGenericType || type.GetGenericArguments().All(IsValidPropertyType); + } + } +} diff --git a/Modules/Properties/Runtime/Utility/TypeConversion.cs b/Modules/Properties/Runtime/Utility/TypeConversion.cs new file mode 100644 index 0000000000..6ed1e2ee53 --- /dev/null +++ b/Modules/Properties/Runtime/Utility/TypeConversion.cs @@ -0,0 +1,680 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using System.Collections.Generic; +using System.Globalization; +using Unity.Collections.LowLevel.Unsafe; + +namespace Unity.Properties +{ + readonly struct ConversionRegistry : IEqualityComparer + { + class ConverterKeyComparer : IEqualityComparer + { + public bool Equals(ConverterKey x, ConverterKey y) + { + return x.SourceType == y.SourceType && x.DestinationType == y.DestinationType; + } + + public int GetHashCode(ConverterKey obj) + { + return ((obj.SourceType != null ? obj.SourceType.GetHashCode() : 0) * 397) ^ (obj.DestinationType != null ? obj.DestinationType.GetHashCode() : 0); + } + } + + static readonly ConverterKeyComparer Comparer = new ConverterKeyComparer(); + + readonly struct ConverterKey + { + public readonly Type SourceType; + public readonly Type DestinationType; + + public ConverterKey(Type source, Type destination) + { + SourceType = source; + DestinationType = destination; + } + } + + readonly Dictionary m_Converters; + + ConversionRegistry(Dictionary storage) + { + m_Converters = storage; + } + + public int ConverterCount => m_Converters?.Count ?? 0; + + public static ConversionRegistry Create() + { + return new ConversionRegistry(new Dictionary(Comparer)); + } + + public void Register(Type source, Type destination, Delegate converter) + { + m_Converters[new ConverterKey(source, destination)] = converter ?? throw new ArgumentException(nameof(converter)); + } + + public void Unregister(Type source, Type destination) + { + m_Converters.Remove(new ConverterKey(source, destination)); + } + + public Delegate GetConverter(Type source, Type destination) + { + var key = new ConverterKey(source, destination); + return m_Converters.TryGetValue(key, out var converter) + ? converter + : null; + } + + public bool TryGetConverter(Type source, Type destination, out Delegate converter) + { + converter = GetConverter(source, destination); + return null != converter; + } + + public void GetAllTypesConvertingToType(Type type, List result) + { + foreach (var key in m_Converters.Keys) + { + if (key.DestinationType == type) + result.Add(key.SourceType); + } + } + + public bool Equals(ConversionRegistry x, ConversionRegistry y) + { + return x.m_Converters == y.m_Converters; + } + + public int GetHashCode(ConversionRegistry obj) + { + return (obj.m_Converters != null ? obj.m_Converters.GetHashCode() : 0); + } + } + + /// + /// Represents the method that will handle converting an object of type to an object of type . + /// + /// The source value to be converted. + /// The source type to convert from. + /// The destination type to convert to. + public delegate TDestination TypeConverter(ref TSource value); + + /// + /// Helper class to handle type conversion during properties API calls. + /// + public static class TypeConversion + { + static readonly ConversionRegistry s_GlobalConverters = ConversionRegistry.Create(); + + static TypeConversion() + { + PrimitiveConverters.Register(); + } + + /// + /// Registers a new converter from to . + /// + /// Conversion delegate + /// Type of the source object. + /// Type of the destination object. + public static void Register(TypeConverter converter) + { + s_GlobalConverters.Register(typeof(TSource), typeof(TDestination), converter); + } + + /// + /// Converts the specified value from to . + /// + /// The source value to convert. + /// The source type to convert from. + /// The destination type to convert to. + /// The value converted to the type. + /// No converter is registered for the given types. + public static TDestination Convert(ref TSource value) + { + if (!TryConvert(ref value, out var destination)) + { + throw new InvalidOperationException($"TypeConversion no converter has been registered for SrcType=[{typeof(TSource)}] to DstType=[{typeof(TDestination)}]"); + } + + return destination; + } + + /// + /// Converts the specified value from to . + /// + /// The source value to convert. + /// When this method returns, contains the converted destination value if the conversion succeeded; otherwise, default. + /// The source type to convert from. + /// The destination type to convert to. + /// if the conversion succeeded; otherwise, . + public static bool TryConvert(ref TSource source, out TDestination destination) + { + if (s_GlobalConverters.TryGetConverter(typeof(TSource), typeof(TDestination), out var converter)) + { + destination = ((TypeConverter) converter)(ref source); + return true; + } + + if (typeof(TSource).IsValueType && typeof(TSource) == typeof(TDestination)) + { + destination = UnsafeUtility.As(ref source); + return true; + } + + if (TypeTraits.IsNullable) + { + // Both types are nullable types, but the underlying types don't match. In some cases, this is supported in C# (int? => float?), + // but we don't support this case. + if (TypeTraits.IsNullable && Nullable.GetUnderlyingType(typeof(TDestination)) != Nullable.GetUnderlyingType(typeof(TSource))) + { + destination = default; + return false; + } + + var underlyingType = Nullable.GetUnderlyingType(typeof(TDestination)); + if (underlyingType.IsEnum) + { + var enumUnderlyingType = Enum.GetUnderlyingType(underlyingType); + var value = System.Convert.ChangeType(source, enumUnderlyingType); + destination = (TDestination) Enum.ToObject(underlyingType, value); + return true; + } + + if (source == null) + { + destination = default; + return true; + } + + destination = (TDestination) System.Convert.ChangeType(source, underlyingType); + return true; + } + + // Conversion from T? => T. + if (TypeTraits.IsNullable && typeof(TDestination) == Nullable.GetUnderlyingType(typeof(TSource))) + { + // This conversion would result in an InvalidOperationException. + // i.e. int v = (int)(default(int?)); + if (null == source) + { + destination = default; + return false; + } + destination = (TDestination) (object) source; + return true; + } + + if (TypeTraits.IsUnityObject) + { + if (TryConvertToUnityEngineObject(source, out destination)) + { + return true; + } + } + + if (TypeTraits.IsEnum) + { + if (typeof(TSource) == typeof(string)) + { + try + { + destination = (TDestination) Enum.Parse(typeof(TDestination), (string) (object) source); + } + catch (ArgumentException) + { + destination = default; + return false; + } + + return true; + } + + if (IsNumericType(typeof(TSource))) + { + destination = UnsafeUtility.As(ref source); + return true; + } + } + + // Could be boxing :( + if (source is TDestination assignable) + { + destination = assignable; + return true; + } + + if (typeof(TDestination).IsAssignableFrom(typeof(TSource))) + { + destination = (TDestination) (object) source; + return true; + } + + destination = default; + return false; + } + + static bool TryConvertToUnityEngineObject(TSource source, out TDestination destination) + { + if (!typeof(UnityEngine.Object).IsAssignableFrom(typeof(TDestination))) + { + destination = default; + return false; + } + + if (typeof(UnityEngine.Object).IsAssignableFrom(typeof(TSource))) + { + if (null == source) + { + destination = default; + return true; + } + + if (typeof(TDestination) == typeof(UnityEngine.Object)) + { + destination = (TDestination)(object)source; + return true; + } + } + + if (TryConvert(ref source, out UnityEngine.Object obj) && + obj is TDestination d) + { + destination = d; + return true; + } + + destination = default; + return false; + } + + static bool IsNumericType(Type t) + { + switch (Type.GetTypeCode(t)) + { + case TypeCode.Byte: + case TypeCode.SByte: + case TypeCode.UInt16: + case TypeCode.UInt32: + case TypeCode.UInt64: + case TypeCode.Int16: + case TypeCode.Int32: + case TypeCode.Int64: + case TypeCode.Decimal: + case TypeCode.Double: + case TypeCode.Single: + return true; + default: + return false; + } + } + + static class PrimitiveConverters + { + public static void Register() + { + // signed integral types + RegisterInt8Converters(); + RegisterInt16Converters(); + RegisterInt32Converters(); + RegisterInt64Converters(); + + // unsigned integral types + RegisterUInt8Converters(); + RegisterUInt16Converters(); + RegisterUInt32Converters(); + RegisterUInt64Converters(); + + // floating point types + RegisterFloat32Converters(); + RegisterFloat64Converters(); + + // .net types + RegisterBooleanConverters(); + RegisterCharConverters(); + RegisterStringConverters(); + RegisterObjectConverters(); + + // support System.Guid by default + s_GlobalConverters.Register(typeof(string), typeof(Guid), (TypeConverter)((ref string g) => new Guid(g))); + } + + static void RegisterInt8Converters() + { + s_GlobalConverters.Register(typeof(sbyte), typeof(char), (TypeConverter)((ref sbyte v) => (char) v)); + s_GlobalConverters.Register(typeof(sbyte), typeof(bool), (TypeConverter)((ref sbyte v) => v != 0)); + s_GlobalConverters.Register(typeof(sbyte), typeof(short), (TypeConverter)((ref sbyte v) => (short) v)); + s_GlobalConverters.Register(typeof(sbyte), typeof(int), (TypeConverter)((ref sbyte v) => (int) v)); + s_GlobalConverters.Register(typeof(sbyte), typeof(long), (TypeConverter)((ref sbyte v) => (long) v)); + s_GlobalConverters.Register(typeof(sbyte), typeof(byte), (TypeConverter)((ref sbyte v) => (byte) v)); + s_GlobalConverters.Register(typeof(sbyte), typeof(ushort), (TypeConverter)((ref sbyte v) => (ushort) v)); + s_GlobalConverters.Register(typeof(sbyte), typeof(uint), (TypeConverter)((ref sbyte v) => (uint) v)); + s_GlobalConverters.Register(typeof(sbyte), typeof(ulong), (TypeConverter)((ref sbyte v) => (ulong) v)); + s_GlobalConverters.Register(typeof(sbyte), typeof(float), (TypeConverter)((ref sbyte v) => (float) v)); + s_GlobalConverters.Register(typeof(sbyte), typeof(double), (TypeConverter)((ref sbyte v) => (double) v)); + s_GlobalConverters.Register(typeof(sbyte), typeof(object), (TypeConverter)((ref sbyte v) => (object) v)); + } + + static void RegisterInt16Converters() + { + s_GlobalConverters.Register(typeof(short), typeof(sbyte), (TypeConverter)((ref short v) => (sbyte) v)); + s_GlobalConverters.Register(typeof(short), typeof(char), (TypeConverter)((ref short v) => (char) v)); + s_GlobalConverters.Register(typeof(short), typeof(bool), (TypeConverter)((ref short v) => v != 0)); + s_GlobalConverters.Register(typeof(short), typeof(int), (TypeConverter)((ref short v) => (int) v)); + s_GlobalConverters.Register(typeof(short), typeof(long), (TypeConverter)((ref short v) => (long) v)); + s_GlobalConverters.Register(typeof(short), typeof(byte), (TypeConverter)((ref short v) => (byte) v)); + s_GlobalConverters.Register(typeof(short), typeof(ushort), (TypeConverter)((ref short v) => (ushort) v)); + s_GlobalConverters.Register(typeof(short), typeof(uint), (TypeConverter)((ref short v) => (uint) v)); + s_GlobalConverters.Register(typeof(short), typeof(ulong), (TypeConverter)((ref short v) => (ulong) v)); + s_GlobalConverters.Register(typeof(short), typeof(float), (TypeConverter)((ref short v) => (float) v)); + s_GlobalConverters.Register(typeof(short), typeof(double), (TypeConverter)((ref short v) => (double) v)); + s_GlobalConverters.Register(typeof(short), typeof(object), (TypeConverter)((ref short v) => (object) v)); + } + + static void RegisterInt32Converters() + { + s_GlobalConverters.Register(typeof(int), typeof(sbyte), (TypeConverter)((ref int v) => (sbyte) v)); + s_GlobalConverters.Register(typeof(int), typeof(char), (TypeConverter)((ref int v) => (char) v)); + s_GlobalConverters.Register(typeof(int), typeof(bool), (TypeConverter)((ref int v) => v != 0)); + s_GlobalConverters.Register(typeof(int), typeof(short), (TypeConverter)((ref int v) => (short) v)); + s_GlobalConverters.Register(typeof(int), typeof(long), (TypeConverter)((ref int v) => (long) v)); + s_GlobalConverters.Register(typeof(int), typeof(byte), (TypeConverter)((ref int v) => (byte) v)); + s_GlobalConverters.Register(typeof(int), typeof(ushort), (TypeConverter)((ref int v) => (ushort) v)); + s_GlobalConverters.Register(typeof(int), typeof(uint), (TypeConverter)((ref int v) => (uint) v)); + s_GlobalConverters.Register(typeof(int), typeof(ulong), (TypeConverter)((ref int v) => (ulong) v)); + s_GlobalConverters.Register(typeof(int), typeof(float), (TypeConverter)((ref int v) => (float) v)); + s_GlobalConverters.Register(typeof(int), typeof(double), (TypeConverter)((ref int v) => (double) v)); + s_GlobalConverters.Register(typeof(int), typeof(object), (TypeConverter)((ref int v) => (object) v)); + } + + static void RegisterInt64Converters() + { + s_GlobalConverters.Register(typeof(long), typeof(sbyte), (TypeConverter)((ref long v) => (sbyte) v)); + s_GlobalConverters.Register(typeof(long), typeof(char), (TypeConverter)((ref long v) => (char) v)); + s_GlobalConverters.Register(typeof(long), typeof(bool), (TypeConverter)((ref long v) => v != 0)); + s_GlobalConverters.Register(typeof(long), typeof(short), (TypeConverter)((ref long v) => (short) v)); + s_GlobalConverters.Register(typeof(long), typeof(int), (TypeConverter)((ref long v) => (int) v)); + s_GlobalConverters.Register(typeof(long), typeof(byte), (TypeConverter)((ref long v) => (byte) v)); + s_GlobalConverters.Register(typeof(long), typeof(ushort), (TypeConverter)((ref long v) => (ushort) v)); + s_GlobalConverters.Register(typeof(long), typeof(uint), (TypeConverter)((ref long v) => (uint) v)); + s_GlobalConverters.Register(typeof(long), typeof(ulong), (TypeConverter)((ref long v) => (ulong) v)); + s_GlobalConverters.Register(typeof(long), typeof(float), (TypeConverter)((ref long v) => (float) v)); + s_GlobalConverters.Register(typeof(long), typeof(double), (TypeConverter)((ref long v) => (double) v)); + s_GlobalConverters.Register(typeof(long), typeof(object), (TypeConverter)((ref long v) => (object) v)); + } + + static void RegisterUInt8Converters() + { + s_GlobalConverters.Register(typeof(byte), typeof(sbyte), (TypeConverter)((ref byte v) => (sbyte) v)); + s_GlobalConverters.Register(typeof(byte), typeof(char), (TypeConverter)((ref byte v) => (char) v)); + s_GlobalConverters.Register(typeof(byte), typeof(bool), (TypeConverter)((ref byte v) => v != 0)); + s_GlobalConverters.Register(typeof(byte), typeof(short), (TypeConverter)((ref byte v) => (short) v)); + s_GlobalConverters.Register(typeof(byte), typeof(int), (TypeConverter)((ref byte v) => (int) v)); + s_GlobalConverters.Register(typeof(byte), typeof(long), (TypeConverter)((ref byte v) => (long) v)); + s_GlobalConverters.Register(typeof(byte), typeof(ushort), (TypeConverter)((ref byte v) => (ushort) v)); + s_GlobalConverters.Register(typeof(byte), typeof(uint), (TypeConverter)((ref byte v) => (uint) v)); + s_GlobalConverters.Register(typeof(byte), typeof(ulong), (TypeConverter)((ref byte v) => (ulong) v)); + s_GlobalConverters.Register(typeof(byte), typeof(float), (TypeConverter)((ref byte v) => (float) v)); + s_GlobalConverters.Register(typeof(byte), typeof(double), (TypeConverter)((ref byte v) => (double) v)); + s_GlobalConverters.Register(typeof(byte), typeof(object), (TypeConverter)((ref byte v) => (object) v)); + } + + static void RegisterUInt16Converters() + { + s_GlobalConverters.Register(typeof(ushort), typeof(sbyte), (TypeConverter)((ref ushort v) => (sbyte) v)); + s_GlobalConverters.Register(typeof(ushort), typeof(char), (TypeConverter)((ref ushort v) => (char) v)); + s_GlobalConverters.Register(typeof(ushort), typeof(bool), (TypeConverter)((ref ushort v) => v != 0)); + s_GlobalConverters.Register(typeof(ushort), typeof(short), (TypeConverter)((ref ushort v) => (short) v)); + s_GlobalConverters.Register(typeof(ushort), typeof(int), (TypeConverter)((ref ushort v) => (int) v)); + s_GlobalConverters.Register(typeof(ushort), typeof(long), (TypeConverter)((ref ushort v) => (long) v)); + s_GlobalConverters.Register(typeof(ushort), typeof(byte), (TypeConverter)((ref ushort v) => (byte) v)); + s_GlobalConverters.Register(typeof(ushort), typeof(uint), (TypeConverter)((ref ushort v) => (uint) v)); + s_GlobalConverters.Register(typeof(ushort), typeof(ulong), (TypeConverter)((ref ushort v) => (ulong) v)); + s_GlobalConverters.Register(typeof(ushort), typeof(float), (TypeConverter)((ref ushort v) => (float) v)); + s_GlobalConverters.Register(typeof(ushort), typeof(double), (TypeConverter)((ref ushort v) => (double) v)); + s_GlobalConverters.Register(typeof(ushort), typeof(object), (TypeConverter)((ref ushort v) => (object) v)); + } + + static void RegisterUInt32Converters() + { + s_GlobalConverters.Register(typeof(uint), typeof(sbyte), (TypeConverter)((ref uint v) => (sbyte) v)); + s_GlobalConverters.Register(typeof(uint), typeof(char), (TypeConverter)((ref uint v) => (char) v)); + s_GlobalConverters.Register(typeof(uint), typeof(bool), (TypeConverter)((ref uint v) => v != 0)); + s_GlobalConverters.Register(typeof(uint), typeof(short), (TypeConverter)((ref uint v) => (short) v)); + s_GlobalConverters.Register(typeof(uint), typeof(int), (TypeConverter)((ref uint v) => (int) v)); + s_GlobalConverters.Register(typeof(uint), typeof(long), (TypeConverter)((ref uint v) => (long) v)); + s_GlobalConverters.Register(typeof(uint), typeof(byte), (TypeConverter)((ref uint v) => (byte) v)); + s_GlobalConverters.Register(typeof(uint), typeof(ushort), (TypeConverter)((ref uint v) => (ushort) v)); + s_GlobalConverters.Register(typeof(uint), typeof(ulong), (TypeConverter)((ref uint v) => (ulong) v)); + s_GlobalConverters.Register(typeof(uint), typeof(float), (TypeConverter)((ref uint v) => (float) v)); + s_GlobalConverters.Register(typeof(uint), typeof(double), (TypeConverter)((ref uint v) => (double) v)); + s_GlobalConverters.Register(typeof(uint), typeof(object), (TypeConverter)((ref uint v) => (object) v)); + } + + static void RegisterUInt64Converters() + { + s_GlobalConverters.Register(typeof(ulong), typeof(sbyte), (TypeConverter)((ref ulong v) => (sbyte) v)); + s_GlobalConverters.Register(typeof(ulong), typeof(char), (TypeConverter)((ref ulong v) => (char) v)); + s_GlobalConverters.Register(typeof(ulong), typeof(bool), (TypeConverter)((ref ulong v) => v != 0)); + s_GlobalConverters.Register(typeof(ulong), typeof(short), (TypeConverter)((ref ulong v) => (short) v)); + s_GlobalConverters.Register(typeof(ulong), typeof(int), (TypeConverter)((ref ulong v) => (int) v)); + s_GlobalConverters.Register(typeof(ulong), typeof(long), (TypeConverter)((ref ulong v) => (long) v)); + s_GlobalConverters.Register(typeof(ulong), typeof(byte), (TypeConverter)((ref ulong v) => (byte) v)); + s_GlobalConverters.Register(typeof(ulong), typeof(ushort), (TypeConverter)((ref ulong v) => (ushort) v)); + s_GlobalConverters.Register(typeof(ulong), typeof(uint), (TypeConverter)((ref ulong v) => (uint) v)); + s_GlobalConverters.Register(typeof(ulong), typeof(float), (TypeConverter)((ref ulong v) => (float) v)); + s_GlobalConverters.Register(typeof(ulong), typeof(double), (TypeConverter)((ref ulong v) => (double) v)); + s_GlobalConverters.Register(typeof(ulong), typeof(object), (TypeConverter)((ref ulong v) => (object) v)); + } + + static void RegisterFloat32Converters() + { + s_GlobalConverters.Register(typeof(float), typeof(sbyte), (TypeConverter)((ref float v) => (sbyte) v)); + s_GlobalConverters.Register(typeof(float), typeof(char), (TypeConverter)((ref float v) => (char) v)); + s_GlobalConverters.Register(typeof(float), typeof(bool), (TypeConverter)((ref float v) => Math.Abs(v) > float.Epsilon)); + s_GlobalConverters.Register(typeof(float), typeof(short), (TypeConverter)((ref float v) => (short) v)); + s_GlobalConverters.Register(typeof(float), typeof(int), (TypeConverter)((ref float v) => (int) v)); + s_GlobalConverters.Register(typeof(float), typeof(long), (TypeConverter)((ref float v) => (long) v)); + s_GlobalConverters.Register(typeof(float), typeof(byte), (TypeConverter)((ref float v) => (byte) v)); + s_GlobalConverters.Register(typeof(float), typeof(ushort), (TypeConverter)((ref float v) => (ushort) v)); + s_GlobalConverters.Register(typeof(float), typeof(uint), (TypeConverter)((ref float v) => (uint) v)); + s_GlobalConverters.Register(typeof(float), typeof(ulong), (TypeConverter)((ref float v) => (ulong) v)); + s_GlobalConverters.Register(typeof(float), typeof(double), (TypeConverter)((ref float v) => (double) v)); + s_GlobalConverters.Register(typeof(float), typeof(object), (TypeConverter)((ref float v) => (object) v)); + } + + static void RegisterFloat64Converters() + { + s_GlobalConverters.Register(typeof(double), typeof(sbyte), (TypeConverter)((ref double v) => (sbyte) v)); + s_GlobalConverters.Register(typeof(double), typeof(char), (TypeConverter)((ref double v) => (char) v)); + s_GlobalConverters.Register(typeof(double), typeof(bool), (TypeConverter)((ref double v) => Math.Abs(v) > double.Epsilon)); + s_GlobalConverters.Register(typeof(double), typeof(short), (TypeConverter)((ref double v) => (short) v)); + s_GlobalConverters.Register(typeof(double), typeof(int), (TypeConverter)((ref double v) => (int) v)); + s_GlobalConverters.Register(typeof(double), typeof(long), (TypeConverter)((ref double v) => (long) v)); + s_GlobalConverters.Register(typeof(double), typeof(byte), (TypeConverter)((ref double v) => (byte) v)); + s_GlobalConverters.Register(typeof(double), typeof(ushort), (TypeConverter)((ref double v) => (ushort) v)); + s_GlobalConverters.Register(typeof(double), typeof(uint), (TypeConverter)((ref double v) => (uint) v)); + s_GlobalConverters.Register(typeof(double), typeof(ulong), (TypeConverter)((ref double v) => (ulong) v)); + s_GlobalConverters.Register(typeof(double), typeof(float), (TypeConverter)((ref double v) => (float) v)); + s_GlobalConverters.Register(typeof(double), typeof(object), (TypeConverter)((ref double v) => (object) v)); + } + + static void RegisterBooleanConverters() + { + s_GlobalConverters.Register(typeof(bool), typeof(char), (TypeConverter)((ref bool v) => v ? (char) 1 : (char) 0)); + s_GlobalConverters.Register(typeof(bool), typeof(sbyte), (TypeConverter)((ref bool v) => v ? (sbyte) 1 : (sbyte) 0)); + s_GlobalConverters.Register(typeof(bool), typeof(short), (TypeConverter)((ref bool v) => v ? (short) 1 : (short) 0)); + s_GlobalConverters.Register(typeof(bool), typeof(int), (TypeConverter)((ref bool v) => v ? 1 : 0)); + s_GlobalConverters.Register(typeof(bool), typeof(long), (TypeConverter)((ref bool v) => v ? (long) 1 : (long) 0)); + s_GlobalConverters.Register(typeof(bool), typeof(byte), (TypeConverter)((ref bool v) => v ? (byte) 1 : (byte) 0)); + s_GlobalConverters.Register(typeof(bool), typeof(ushort), (TypeConverter)((ref bool v) => v ? (ushort) 1 : (ushort) 0)); + s_GlobalConverters.Register(typeof(bool), typeof(uint), (TypeConverter)((ref bool v) => v ? (uint) 1 : (uint) 0)); + s_GlobalConverters.Register(typeof(bool), typeof(ulong), (TypeConverter)((ref bool v) => v ? (ulong) 1 : (ulong) 0)); + s_GlobalConverters.Register(typeof(bool), typeof(float), (TypeConverter)((ref bool v) => v ? (float) 1 : (float) 0)); + s_GlobalConverters.Register(typeof(bool), typeof(double), (TypeConverter)((ref bool v) => v ? (double) 1 : (double) 0)); + s_GlobalConverters.Register(typeof(bool), typeof(object), (TypeConverter)((ref bool v) => (object)v)); + } + + static void RegisterCharConverters() + { + s_GlobalConverters.Register(typeof(string), typeof(char), (TypeConverter) ((ref string v) => + { + if (v.Length != 1) + { + throw new Exception("Not a valid char"); + } + + return v[0]; + })); + s_GlobalConverters.Register(typeof(char), typeof(bool), (TypeConverter)((ref char v) => v != (char) 0)); + s_GlobalConverters.Register(typeof(char), typeof(sbyte), (TypeConverter)((ref char v) => (sbyte)v)); + s_GlobalConverters.Register(typeof(char), typeof(short), (TypeConverter)((ref char v) => (short)v)); + s_GlobalConverters.Register(typeof(char), typeof(int), (TypeConverter)((ref char v) => (int)v)); + s_GlobalConverters.Register(typeof(char), typeof(long), (TypeConverter)((ref char v) => (long)v)); + s_GlobalConverters.Register(typeof(char), typeof(byte), (TypeConverter)((ref char v) => (byte)v)); + s_GlobalConverters.Register(typeof(char), typeof(ushort), (TypeConverter)((ref char v) => (ushort)v)); + s_GlobalConverters.Register(typeof(char), typeof(uint), (TypeConverter)((ref char v) => (uint)v)); + s_GlobalConverters.Register(typeof(char), typeof(ulong), (TypeConverter)((ref char v) => (ulong)v)); + s_GlobalConverters.Register(typeof(char), typeof(float), (TypeConverter)((ref char v) => (float)v)); + s_GlobalConverters.Register(typeof(char), typeof(double), (TypeConverter)((ref char v) => (double)v)); + s_GlobalConverters.Register(typeof(char), typeof(object), (TypeConverter)((ref char v) => (object)v)); + s_GlobalConverters.Register(typeof(char), typeof(string), (TypeConverter)((ref char v) => v.ToString())); + } + + static void RegisterStringConverters() + { + s_GlobalConverters.Register(typeof(string), typeof(char), (TypeConverter) ((ref string v) => !string.IsNullOrEmpty(v) ? v[0] : '\0')); + s_GlobalConverters.Register(typeof(char), typeof(string), (TypeConverter)((ref char v) => v.ToString())); + s_GlobalConverters.Register(typeof(string), typeof(bool), (TypeConverter)((ref string v) => + { + if (bool.TryParse(v, out var r)) + return r; + + return double.TryParse(v, out var fromDouble) + ? Convert(ref fromDouble) + : default; + })); + s_GlobalConverters.Register(typeof(bool), typeof(string), (TypeConverter)((ref bool v) => v.ToString())); + s_GlobalConverters.Register(typeof(string), typeof(sbyte), (TypeConverter) ((ref string v) => + { + if (sbyte.TryParse(v, out var r)) + return r; + + return double.TryParse(v, out var fromDouble) + ? Convert(ref fromDouble) + : default; + })); + s_GlobalConverters.Register(typeof(sbyte), typeof(string), (TypeConverter)((ref sbyte v) => v.ToString())); + s_GlobalConverters.Register(typeof(string), typeof(short), (TypeConverter) ((ref string v) => + { + if (short.TryParse(v, out var r)) + return r; + + return double.TryParse(v, out var fromDouble) + ? Convert(ref fromDouble) + : default; + })); + s_GlobalConverters.Register(typeof(short), typeof(string), (TypeConverter)((ref short v) => v.ToString())); + s_GlobalConverters.Register(typeof(string), typeof(int), (TypeConverter) ((ref string v) => + { + if (int.TryParse(v, out var r)) + return r; + + return double.TryParse(v, out var fromDouble) + ? Convert(ref fromDouble) + : default; + })); + s_GlobalConverters.Register(typeof(int), typeof(string), (TypeConverter)((ref int v) => v.ToString())); + s_GlobalConverters.Register(typeof(string), typeof(long), (TypeConverter)((ref string v) => + { + if (long.TryParse(v, out var r)) + return r; + + return double.TryParse(v, out var fromDouble) + ? Convert(ref fromDouble) + : default; + })); + s_GlobalConverters.Register(typeof(long), typeof(string), (TypeConverter)((ref long v) => v.ToString())); + s_GlobalConverters.Register(typeof(string), typeof(byte), (TypeConverter) ((ref string v) => + { + if (byte.TryParse(v, out var r)) + return r; + + return double.TryParse(v, out var fromDouble) + ? Convert(ref fromDouble) + : default; + })); + s_GlobalConverters.Register(typeof(byte), typeof(string), (TypeConverter) ((ref byte v) => v.ToString())); + s_GlobalConverters.Register(typeof(string), typeof(ushort), (TypeConverter) ((ref string v) => + { + if (ushort.TryParse(v, out var r)) + return r; + + return double.TryParse(v, out var fromDouble) + ? Convert(ref fromDouble) + : default; + })); + s_GlobalConverters.Register(typeof(ushort), typeof(string), (TypeConverter)((ref ushort v) => v.ToString())); + s_GlobalConverters.Register(typeof(string), typeof(uint), (TypeConverter) ((ref string v) => + { + if (uint.TryParse(v, out var r)) + return r; + + return double.TryParse(v, out var fromDouble) + ? Convert(ref fromDouble) + : default; + })); + s_GlobalConverters.Register(typeof(uint), typeof(string), (TypeConverter)((ref uint v) => v.ToString())); + s_GlobalConverters.Register(typeof(string), typeof(ulong), (TypeConverter)((ref string v) => + { + if (ulong.TryParse(v, out var r)) + { + return r; + } + + return double.TryParse(v, out var fromDouble) + ? Convert(ref fromDouble) + : default; + })); + s_GlobalConverters.Register(typeof(ulong), typeof(string), (TypeConverter)((ref ulong v) => v.ToString())); + s_GlobalConverters.Register(typeof(string), typeof(float), (TypeConverter)((ref string v) => + { + if (float.TryParse(v, out var r)) + return r; + + return double.TryParse(v, out var fromDouble) + ? Convert(ref fromDouble) + : default; + })); + s_GlobalConverters.Register(typeof(float), typeof(string), (TypeConverter)((ref float v) => v.ToString(CultureInfo.InvariantCulture))); + s_GlobalConverters.Register(typeof(string), typeof(double), (TypeConverter)((ref string v) => + { + double.TryParse(v, out var r); + return r; + })); + s_GlobalConverters.Register(typeof(double), typeof(string), (TypeConverter)((ref double v) => v.ToString(CultureInfo.InvariantCulture))); + } + + static void RegisterObjectConverters() + { + s_GlobalConverters.Register(typeof(object), typeof(char), (TypeConverter)((ref object v) => v is char value ? value : default)); + s_GlobalConverters.Register(typeof(object), typeof(bool), (TypeConverter)((ref object v) => v is bool value ? value : default)); + s_GlobalConverters.Register(typeof(object), typeof(sbyte), (TypeConverter)((ref object v) => v is sbyte value ? value : default)); + s_GlobalConverters.Register(typeof(object), typeof(short), (TypeConverter)((ref object v) => v is short value ? value : default)); + s_GlobalConverters.Register(typeof(object), typeof(int), (TypeConverter)((ref object v) => v is int value ? value : default)); + s_GlobalConverters.Register(typeof(object), typeof(long), (TypeConverter)((ref object v) => v is long value ? value : default)); + s_GlobalConverters.Register(typeof(object), typeof(byte), (TypeConverter)((ref object v) => v is byte value ? value : default)); + s_GlobalConverters.Register(typeof(object), typeof(ushort), (TypeConverter)((ref object v) => v is ushort value ? value : default)); + s_GlobalConverters.Register(typeof(object), typeof(uint), (TypeConverter)((ref object v) => v is uint value ? value : default)); + s_GlobalConverters.Register(typeof(object), typeof(ulong), (TypeConverter)((ref object v) => v is ulong value ? value : default)); + s_GlobalConverters.Register(typeof(object), typeof(float), (TypeConverter)((ref object v) => v is float value ? value : default)); + s_GlobalConverters.Register(typeof(object), typeof(double), (TypeConverter)((ref object v) => v is double value ? value : default)); + } + } + } +} diff --git a/Modules/Properties/Runtime/Utility/TypeTraits.cs b/Modules/Properties/Runtime/Utility/TypeTraits.cs new file mode 100644 index 0000000000..a098acd244 --- /dev/null +++ b/Modules/Properties/Runtime/Utility/TypeTraits.cs @@ -0,0 +1,148 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using System.Reflection; + +namespace Unity.Properties +{ + /// + /// Helper class to avoid paying the cost of runtime type lookups. + /// + public static class TypeTraits + { + /// + /// Returns if the given type can be treated as a container. i.e. not primitive, pointer, enum or string. + /// + /// The type to test. + /// if the given type is a container type; otherwise. + /// The given type is null. + public static bool IsContainer(Type type) + { + if (null == type) + throw new ArgumentNullException(nameof(type)); + return !(type.IsPrimitive || type.IsPointer || type.IsEnum || type == typeof(string)); + } + } + + /// + /// Helper class to avoid paying the cost of runtime type lookups. + /// + /// This is also used to abstract underlying type info in the runtime (e.g. RuntimeTypeHandle vs StaticTypeReg) + /// + public static class TypeTraits + { + /// + /// Gets a value indicating whether is a value type. + /// + public static bool IsValueType { get; } + + /// + /// Gets a value indicating whether is a primitive type. + /// + public static bool IsPrimitive { get; } + + /// + /// Gets a value indicating whether is an interface type. + /// + public static bool IsInterface { get; } + + /// + /// Gets a value indicating whether is an abstract type. + /// + public static bool IsAbstract { get; } + + /// + /// Gets a value indicating whether is an array type. + /// + public static bool IsArray { get; } + + /// + /// Gets a value indicating whether is a multidimensional array type. + /// + public static bool IsMultidimensionalArray { get; } + + /// + /// Gets a value indicating whether is an enum type. + /// + public static bool IsEnum { get; } + + /// + /// Gets a value indicating whether is an flags enum type. + /// + public static bool IsEnumFlags { get; } + + /// + /// Gets a value indicating whether is a nullable type. + /// + public static bool IsNullable { get; } + + /// + /// Gets a value indicating whether is type. + /// + public static bool IsObject { get; } + + /// + /// Gets a value indicating whether is type. + /// + public static bool IsString { get; } + + /// + /// Gets a value indicating whether is a property container type. + /// + public static bool IsContainer { get; } + + /// + /// Gets a value indicating whether can be null. i.e. The type is an object or nullable. + /// + public static bool CanBeNull { get; } + + /// + /// Gets a value indicating whether is a primitive or type. + /// + public static bool IsPrimitiveOrString { get; } + + /// + /// Gets a value indicating whether is an abstract or interface type. + /// + public static bool IsAbstractOrInterface { get; } + + /// + /// Gets a value indicating whether is a type. + /// + public static bool IsUnityObject { get; } + + /// + /// Gets a value indicating whether is a type. + /// + public static bool IsLazyLoadReference { get; } + + static TypeTraits() + { + var type = typeof(T); + IsValueType = type.IsValueType; + IsPrimitive = type.IsPrimitive; + IsInterface = type.IsInterface; + IsAbstract = type.IsAbstract; + IsArray = type.IsArray; + IsEnum = type.IsEnum; + + IsEnumFlags = IsEnum && null != type.GetCustomAttribute(); + IsNullable = Nullable.GetUnderlyingType(typeof(T)) != null; + IsMultidimensionalArray = IsArray && typeof(T).GetArrayRank() != 1; + IsObject = type == typeof(object); + IsString = type == typeof(string); + IsContainer = TypeTraits.IsContainer(type); + + CanBeNull = !IsValueType; + IsPrimitiveOrString = IsPrimitive || IsString; + IsAbstractOrInterface = IsAbstract || IsInterface; + + CanBeNull |= IsNullable; + + IsLazyLoadReference = type.IsGenericType && type.GetGenericTypeDefinition() == typeof(UnityEngine.LazyLoadReference<>); + IsUnityObject = typeof(UnityEngine.Object).IsAssignableFrom(type); + } + } +} diff --git a/Modules/Properties/Runtime/Utility/TypeUtility.cs b/Modules/Properties/Runtime/Utility/TypeUtility.cs new file mode 100644 index 0000000000..29553a5b4c --- /dev/null +++ b/Modules/Properties/Runtime/Utility/TypeUtility.cs @@ -0,0 +1,736 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using System.Collections.Generic; +using System.Text; +using UnityEngine.Scripting; + +namespace Unity.Properties +{ + /// + /// Describes how a new instance is created. + /// + public enum InstantiationKind + { + /// + /// The type instantiation will be done using . + /// + Activator, + + /// + /// The type instantiation will be done via a method override in + /// + PropertyBagOverride, + + /// + /// Not type instantiation should be performed for this type. + /// + NotInstantiatable + } + + interface IConstructor + { + /// + /// Returns if the type can be instantiated. + /// + InstantiationKind InstantiationKind { get; } + } + + /// + /// The provides a type instantiation implementation for a given type. This is an internal interface. + /// + /// The type to be instantiated. + interface IConstructor : IConstructor + { + /// + /// Construct an instance of and returns it. + /// + /// A new instance of type . + T Instantiate(); + } + + /// + /// The provides type instantiation for a collection type with a count. This is an internal interface. + /// + /// The type to be instantiated. + interface IConstructorWithCount : IConstructor + { + /// + /// Construct an instance of and returns it. + /// + /// A new instance of type . + T InstantiateWithCount(int count); + } + + /// + /// Utility class around . + /// + public static class TypeUtility + { + interface ITypeConstructor + { + /// + /// Returns if the type can be instantiated. + /// + bool CanBeInstantiated { get; } + + /// + /// Construct an instance of the underlying type. + /// + /// A new instance of concrete type. + object Instantiate(); + } + + interface ITypeConstructor : ITypeConstructor + { + /// + /// Construct an instance of and returns it. + /// + /// A new instance of type . + new T Instantiate(); + + /// + /// Sets an explicit instantiation method for the type. + /// + /// The instantiation method. + /// The type to set the explicit instantiation method. + void SetExplicitConstructor(Func constructor); + } + + class TypeConstructor : ITypeConstructor + { + /// + /// An explicit user defined constructor for . + /// + Func m_ExplicitConstructor; + + /// + /// An implicit constructor relying on + /// + Func m_ImplicitConstructor; + + /// + /// An explicit constructor provided by an interface implementation. This is used to provide type construction through property bags. + /// + IConstructor m_OverrideConstructor; + + /// + bool ITypeConstructor.CanBeInstantiated + { + get + { + if (null != m_ExplicitConstructor) + return true; + + if (null != m_OverrideConstructor) + { + if (m_OverrideConstructor.InstantiationKind == InstantiationKind.NotInstantiatable) + return false; + + if (m_OverrideConstructor.InstantiationKind == InstantiationKind.PropertyBagOverride) + return true; + } + + return null != m_ImplicitConstructor; + } + } + + public TypeConstructor() + { + // Try to get a construction provider through the property bag. + m_OverrideConstructor = Internal.PropertyBagStore.GetPropertyBag() as IConstructor; + + SetImplicitConstructor(); + } + + void SetImplicitConstructor() + { + var type = typeof(T); + + if (type.IsValueType) + { + m_ImplicitConstructor = CreateValueTypeInstance; + return; + } + + if (type.IsAbstract) + { + return; + } + + if (typeof(UnityEngine.ScriptableObject).IsAssignableFrom(type)) + { + m_ImplicitConstructor = CreateScriptableObjectInstance; + return; + } + + if (null != type.GetConstructor(Array.Empty())) + { + m_ImplicitConstructor = CreateClassInstance; + } + } + + static T CreateValueTypeInstance() + { + return default; + } + + static T CreateScriptableObjectInstance() + { + return (T) (object) UnityEngine.ScriptableObject.CreateInstance(typeof(T)); + } + + static T CreateClassInstance() + { + return Activator.CreateInstance(); + } + + /// + public void SetExplicitConstructor(Func constructor) + { + m_ExplicitConstructor = constructor; + } + + /// + T ITypeConstructor.Instantiate() + { + // First try an explicit constructor set by users. + if (null != m_ExplicitConstructor) + return m_ExplicitConstructor.Invoke(); + + // Try custom constructor provided by the property bag. + if (null != m_OverrideConstructor) + { + if (m_OverrideConstructor.InstantiationKind == InstantiationKind.NotInstantiatable) + throw new InvalidOperationException($"The type '{typeof(T).Name}' is not constructable."); + + if (m_OverrideConstructor.InstantiationKind == InstantiationKind.PropertyBagOverride) + { + return m_OverrideConstructor.Instantiate(); + } + } + + // Use the implicit construction provided by Activator. + if (null != m_ImplicitConstructor) + return m_ImplicitConstructor.Invoke(); + + throw new InvalidOperationException($"The type '{typeof(T).Name}' is not constructable."); + } + + /// + object ITypeConstructor.Instantiate() => ((ITypeConstructor) this).Instantiate(); + } + + /// + /// The class can be used when we can't fully resolve a for a given type. + /// This can happen if a given type has no property bag and we don't have a strong type to work with. + /// + class NonConstructable : ITypeConstructor + { + bool ITypeConstructor.CanBeInstantiated => false; + public object Instantiate() => throw new InvalidOperationException($"The type is not instantiatable."); + } + + /// + /// The represents a strongly typed reference to a type constructor. + /// + /// The type the constructor can initialize. + /// + /// Any types in this set are also present in the set. + /// + struct Cache + { + /// + /// Reference to the strongly typed for this type. This allows direct access without any dictionary lookups. + /// + public static ITypeConstructor TypeConstructor; + } + + /// + /// The is used to + /// + class TypeConstructorVisitor : ITypeVisitor + { + public ITypeConstructor TypeConstructor; + + public void Visit() + => TypeConstructor = CreateTypeConstructor(); + } + + /// + /// Provides untyped references to the implementations. + /// + /// + /// Any types in this set are also present in the . + /// + static readonly Dictionary s_TypeConstructors = new Dictionary(); + + static readonly System.Reflection.MethodInfo s_CreateTypeConstructor; + + static readonly Dictionary s_CachedResolvedName; + static readonly UnityEngine.Pool.ObjectPool s_Builders; + + static TypeUtility() + { + s_CachedResolvedName = new Dictionary(); + s_Builders = new UnityEngine.Pool.ObjectPool(()=> new StringBuilder(), null, sb => sb.Clear()); + + SetExplicitInstantiationMethod(() => string.Empty); + foreach (var method in typeof(TypeUtility).GetMethods(System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static)) + { + if (method.Name != nameof(CreateTypeConstructor) || !method.IsGenericMethod) + continue; + + s_CreateTypeConstructor = method; + break; + } + + if (null == s_CreateTypeConstructor) + throw new InvalidProgramException(); + } + + /// + /// Utility method to get the name of a type which includes the parent type(s). + /// + /// The we want the name of. + /// The display name of the type. + public static string GetTypeDisplayName(Type type) + { + if (s_CachedResolvedName.TryGetValue(type, out var name)) + return name; + + var index = 0; + name = GetTypeDisplayName(type, type.GetGenericArguments(), ref index); + s_CachedResolvedName[type] = name; + return name; + } + + static string GetTypeDisplayName(Type type, IReadOnlyList args, ref int argIndex) + { + if (type == typeof(int)) + return "int"; + if (type == typeof(uint)) + return "uint"; + if (type == typeof(short)) + return "short"; + if (type == typeof(ushort)) + return "ushort"; + if (type == typeof(byte)) + return "byte"; + if (type == typeof(char)) + return "char"; + if (type == typeof(bool)) + return "bool"; + if (type == typeof(long)) + return "long"; + if (type == typeof(ulong)) + return "ulong"; + if (type == typeof(float)) + return "float"; + if (type == typeof(double)) + return "double"; + if (type == typeof(string)) + return "string"; + + var name = type.Name; + + if (type.IsGenericParameter) + { + return name; + } + + if (type.IsNested) + { + name = $"{GetTypeDisplayName(type.DeclaringType, args, ref argIndex)}.{name}"; + } + + if (!type.IsGenericType) + return name; + + var tickIndex = name.IndexOf('`'); + + var count = type.GetGenericArguments().Length; + + if (tickIndex > -1) + { + count = int.Parse(name.Substring(tickIndex + 1)); + name = name.Remove(tickIndex); + } + + var genericTypeNames = s_Builders.Get(); + try + { + for (var i = 0; i < count && argIndex < args.Count; i++, argIndex++) + { + if (i != 0) genericTypeNames.Append(", "); + genericTypeNames.Append(GetTypeDisplayName(args[argIndex])); + } + + if (genericTypeNames.Length > 0) + { + name = $"{name}<{genericTypeNames}>"; + } + } + finally + { + s_Builders.Release(genericTypeNames); + } + + return name; + } + + /// + /// Utility method to return the base type. + /// + /// The for which we want the base type. + /// The base type. + public static Type GetRootType(this Type type) + { + if (type.IsInterface) + return null; + + var baseType = type.IsValueType ? typeof(ValueType) : typeof(object); + while (baseType != type.BaseType) + { + type = type.BaseType; + } + + return type; + } + + /// + /// Creates a new strongly typed for the specified . + /// + /// + /// This method will attempt to use properties to get the strongly typed reference. If no property bag exists it will fallback to a reflection based approach. + /// + /// The type to create a constructor for. + /// A for the specified type. + [Preserve] + static ITypeConstructor CreateTypeConstructor(Type type) + { + var properties = Internal.PropertyBagStore.GetPropertyBag(type); + + // Attempt to use properties double dispatch to call the strongly typed create method. This avoids expensive reflection calls. + if (null != properties) + { + var visitor = new TypeConstructorVisitor(); + properties.Accept(visitor); + return visitor.TypeConstructor; + } + + if (type.ContainsGenericParameters) + { + var constructor = new NonConstructable(); + s_TypeConstructors[type] = constructor; + return constructor; + } + + // This type has no property bag associated with it. Fallback to reflection to create our type constructor. + return s_CreateTypeConstructor + .MakeGenericMethod(type) + .Invoke(null, null) as ITypeConstructor; + } + + /// + /// Creates a new strongly typed for the specified . + /// + /// The type to create a constructor for. + /// A for the specified type. + static ITypeConstructor CreateTypeConstructor() + { + var constructor = new TypeConstructor(); + Cache.TypeConstructor = constructor; + s_TypeConstructors[typeof(T)] = constructor; + return constructor; + } + + /// + /// Gets the internal for the specified . + /// + /// + /// This method will return null if the type is not constructable on the current platform. + /// + /// The type to get a constructor for. + /// A for the specified type. + static ITypeConstructor GetTypeConstructor(Type type) + { + return s_TypeConstructors.TryGetValue(type, out var constructor) + ? constructor + : CreateTypeConstructor(type); + } + + /// + /// Gets the internal for the specified . + /// + /// + /// This method will return null if the type is not constructable on the current platform. + /// + /// The type to create a constructor for. + /// A for the specified type. + static ITypeConstructor GetTypeConstructor() + { + return null != Cache.TypeConstructor + ? Cache.TypeConstructor + : CreateTypeConstructor(); + } + + /// + /// Returns if the specified type is instantiatable. + /// + /// + /// Instantiatable is defined as either having a default or implicit constructor or having a registered instantiation method. + /// + /// The type to query. + /// if the given type is instantiatable. + public static bool CanBeInstantiated(Type type) + => GetTypeConstructor(type).CanBeInstantiated; + + /// + /// Returns if type is instantiatable. + /// + /// + /// Instantiatable is defined as either having a default or implicit constructor or having a registered instantiation method. + /// + /// The type to query. + /// if type is instantiatable. + public static bool CanBeInstantiated() + => GetTypeConstructor().CanBeInstantiated; + + /// + /// Sets an explicit instantiation method for the type. + /// + /// The instantiation method. + /// The type to set the explicit instantiation method. + public static void SetExplicitInstantiationMethod(Func constructor) + => GetTypeConstructor().SetExplicitConstructor(constructor); + + /// + /// Creates a new instance of the specified . + /// + /// The type we want to create a new instance of. + /// A new instance of the . + /// The specified has no available instantiation method. + public static T Instantiate() + { + var constructor = GetTypeConstructor(); + + CheckCanBeInstantiated(constructor); + + return constructor.Instantiate(); + } + + /// + /// Creates a new instance of the specified . + /// + /// When this method returns, contains the created instance, if type instantiation succeeded; otherwise, the default value for . + /// The type to create an instance of. + /// if a new instance of type was created; otherwise, . + public static bool TryInstantiate(out T instance) + { + var constructor = GetTypeConstructor(); + + if (constructor.CanBeInstantiated) + { + instance = constructor.Instantiate(); + return true; + } + + instance = default; + return false; + } + + /// + /// Creates a new instance of the given type type and returns it as . + /// + /// The type we want to create a new instance of. + /// The type we want to create a new instance of. + /// a new instance of the type. + /// Thrown when the given type is not assignable to . + public static T Instantiate(Type derivedType) + { + var constructor = GetTypeConstructor(derivedType); + + CheckIsAssignableFrom(typeof(T), derivedType); + CheckCanBeInstantiated(constructor, derivedType); + + return (T) constructor.Instantiate(); + } + + /// + /// Tries to create a new instance of the given type type and returns it as . + /// + /// The type we want to create a new instance of. + /// When this method returns, contains the created instance, if type instantiation succeeded; otherwise, the default value for . + /// The type we want to create a new instance of. + /// if a new instance of the given type could be created. + public static bool TryInstantiate(Type derivedType, out T value) + { + if (!typeof(T).IsAssignableFrom(derivedType)) + { + value = default; + value = default; + return false; + } + + var constructor = GetTypeConstructor(derivedType); + + if (!constructor.CanBeInstantiated) + { + value = default; + return false; + } + + value = (T) constructor.Instantiate(); + return true; + } + + /// + /// Creates a new instance of an array with the given count. + /// + /// The size of the array to instantiate. + /// The array type to instantiate. + /// The array newly created array. + /// Thrown is count is negative or if is not an array type. + public static TArray InstantiateArray(int count = 0) + { + if (count < 0) + { + throw new ArgumentException($"{nameof(TypeUtility)}: Cannot construct an array with {nameof(count)}={count}"); + } + + var properties = Internal.PropertyBagStore.GetPropertyBag(); + + if (properties is IConstructorWithCount constructor) + { + return constructor.InstantiateWithCount(count); + } + + var type = typeof(TArray); + + if (!type.IsArray) + { + throw new ArgumentException($"{nameof(TypeUtility)}: Cannot construct an array, since {typeof(TArray).Name} is not an array type."); + } + + var elementType = type.GetElementType(); + if (null == elementType) + { + throw new ArgumentException($"{nameof(TypeUtility)}: Cannot construct an array, since {typeof(TArray).Name}.{nameof(Type.GetElementType)}() returned null."); + } + + return (TArray) (object) Array.CreateInstance(elementType, count); + } + + /// + /// Tries to create a new instance of an array with the given count. + /// + /// The count the array should have. + /// When this method returns, contains the created instance, if type instantiation succeeded; otherwise, the default value for . + /// The array type. + /// if the type was instantiated; otherwise, . + /// Thrown is count is negative or if is not an array type. + public static bool TryInstantiateArray(int count, out TArray instance) + { + if (count < 0) + { + instance = default; + return false; + } + + var properties = Internal.PropertyBagStore.GetPropertyBag(); + + if (properties is IConstructorWithCount constructor) + { + try + { + instance = constructor.InstantiateWithCount(count); + return true; + } + catch + { + // continue + } + } + + var type = typeof(TArray); + + if (!type.IsArray) + { + instance = default; + return false; + } + + var elementType = type.GetElementType(); + + if (null == elementType) + { + instance = default; + return false; + } + + instance = (TArray) (object) Array.CreateInstance(elementType, count); + return true; + } + + /// + /// Creates a new instance of an array with the given type and given count. + /// + /// The type we want to create a new instance of. + /// The size of the array to instantiate. + /// The array type to instantiate. + /// The array newly created array. + /// Thrown is count is negative or if is not an array type. + public static TArray InstantiateArray(Type derivedType, int count = 0) + { + if (count < 0) + { + throw new ArgumentException($"{nameof(TypeUtility)}: Cannot instantiate an array with {nameof(count)}={count}"); + } + + var properties = Internal.PropertyBagStore.GetPropertyBag(derivedType); + + if (properties is IConstructorWithCount constructor) + { + return constructor.InstantiateWithCount(count); + } + + var type = typeof(TArray); + + if (!type.IsArray) + { + throw new ArgumentException($"{nameof(TypeUtility)}: Cannot instantiate an array, since {typeof(TArray).Name} is not an array type."); + } + + var elementType = type.GetElementType(); + if (null == elementType) + { + throw new ArgumentException($"{nameof(TypeUtility)}: Cannot instantiate an array, since {typeof(TArray).Name}.{nameof(Type.GetElementType)}() returned null."); + } + + return (TArray) (object) Array.CreateInstance(elementType, count); + } + + static void CheckIsAssignableFrom(Type type, Type derivedType) + { + if (!type.IsAssignableFrom(derivedType)) + throw new ArgumentException($"Could not create instance of type `{derivedType.Name}` and convert to `{type.Name}`: The given type is not assignable to target type."); + } + + static void CheckCanBeInstantiated(ITypeConstructor constructor) + { + if (!constructor.CanBeInstantiated) + throw new InvalidOperationException($"Type `{typeof(T).Name}` could not be instantiated. A parameter-less constructor or an explicit construction method is required."); + } + + static void CheckCanBeInstantiated(ITypeConstructor constructor, Type type) + { + if (!constructor.CanBeInstantiated) + { + throw new InvalidOperationException($"Type `{type.Name}` could not be instantiated. A parameter-less constructor or an explicit construction method is required."); + } + } + } +} diff --git a/Modules/PropertiesEditor/Module/PropertiesEditorModule.cs b/Modules/PropertiesEditor/Module/PropertiesEditorModule.cs new file mode 100644 index 0000000000..1099270b0d --- /dev/null +++ b/Modules/PropertiesEditor/Module/PropertiesEditorModule.cs @@ -0,0 +1,21 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +namespace Unity.Properties.Editor +{ + [UnityEditor.InitializeOnLoad] + static class PropertiesEditorModule + { + static PropertiesEditorModule() + { + UnityEditor.Build.BuildDefines.getScriptCompilationDefinesDelegates += AddPropertiesBuildDefines; + } + + static void AddPropertiesBuildDefines(UnityEditor.BuildTarget target, + System.Collections.Generic.HashSet defines) + { + defines.Add("USE_PROPERTIES_MODULE"); + } + } +} diff --git a/Modules/Terrain/Public/Terrain.bindings.cs b/Modules/Terrain/Public/Terrain.bindings.cs index a8845d98b7..9acd303857 100644 --- a/Modules/Terrain/Public/Terrain.bindings.cs +++ b/Modules/Terrain/Public/Terrain.bindings.cs @@ -128,6 +128,8 @@ public sealed partial class Terrain : Behaviour extern public bool collectDetailPatches { get; set; } + extern public bool ignoreQualitySettings { get; set; } + extern public TerrainRenderFlags editorRenderFlags { get; set; } extern public Vector3 GetPosition(); diff --git a/Modules/TerrainEditor/TerrainInspector.cs b/Modules/TerrainEditor/TerrainInspector.cs index ec686e3c9a..48d0305aa4 100644 --- a/Modules/TerrainEditor/TerrainInspector.cs +++ b/Modules/TerrainEditor/TerrainInspector.cs @@ -180,6 +180,8 @@ internal class Styles public readonly GUIContent drawTerrain = EditorGUIUtility.TrTextContent("Draw", "Toggle the rendering of terrain"); public readonly GUIContent enableHeightmapRayTracing = EditorGUIUtility.TrTextContent("Enable Ray Tracing", "When enabling this option, RayTracingAccelerationStructure.CullInstances function will populate the acceleration structure with terrain geometries. The option is disabled if the system configuration doesn't support Ray Tracing. Check SystemInfo.supportsRayTracing."); public readonly GUIContent drawInstancedTerrain = EditorGUIUtility.TrTextContent("Draw Instanced" , "Toggle terrain instancing rendering"); + public readonly GUIContent qualitySettings = EditorGUIUtility.TrTextContent("Quality Settings"); + public readonly GUIContent ignoreQualitySettings = EditorGUIUtility.TrTextContent("Ignore Quality Settings", "Toggle whether this terrain should ignore the current active quality settings' terrain overrides."); public readonly GUIContent pixelError = EditorGUIUtility.TrTextContent("Pixel Error", "The accuracy of the mapping between the terrain maps (heightmap, textures, etc.) and the generated terrain; higher values indicate lower accuracy but lower rendering overhead."); public readonly GUIContent baseMapDist = EditorGUIUtility.TrTextContent("Base Map Dist.", "The maximum distance at which terrain textures will be displayed at full resolution. Beyond this distance, a lower resolution composite image will be used for efficiency."); public readonly GUIContent castShadows = EditorGUIUtility.TrTextContent("Cast Shadows", "Does the terrain cast shadows?"); @@ -1026,6 +1028,15 @@ public static int AspectSelectionGridImageAndText(int selected, GUIContent[] tex }, approxSize, emptyString, out doubleClick); } + private void ShowOverrideBar() + { + var rect = EditorGUILayout.s_LastRect; + var prevMargin = EditorGUIUtility.leftMarginCoord; + EditorGUIUtility.leftMarginCoord = 2; + EditorGUI.DrawOverrideBackgroundApplicable(rect); + EditorGUIUtility.leftMarginCoord = prevMargin; + } + public void ShowDetailStats() { GUILayout.Space(3); @@ -1047,6 +1058,7 @@ public void ShowDetailStats() private bool m_ShowBasicTerrainSettings = true; private bool m_ShowTreeAndDetailSettings = true; private bool m_ShowGrassWindSettings = true; + private bool m_ShowTerrainQualitySettings = true; private static void MarkDirty(Terrain terrain) { @@ -1099,6 +1111,7 @@ private string GetTerrainShaderName() public void ShowSettings() { TerrainData terrainData = m_Terrain.terrainData; + TerrainQualityOverrides overrideFlags = QualitySettings.terrainQualityOverrides; m_ShowBasicTerrainSettings = EditorGUILayout.BeginFoldoutHeaderGroup(m_ShowBasicTerrainSettings, styles.basicTerrain); @@ -1125,8 +1138,25 @@ public void ShowSettings() else EditorGUILayout.Toggle(styles.enableHeightmapRayTracing, false); GUI.enabled = true; - var heightmapPixelError = EditorGUILayout.Slider(styles.pixelError, m_Terrain.heightmapPixelError, 1, 200); // former string formatting: "" - var basemapDistance = TerrainInspectorUtility.PowerSlider(styles.baseMapDist, m_Terrain.basemapDistance, 0.0f, 20000.0f, 2.0f); + + float heightmapPixelError = m_Terrain.heightmapPixelError; + bool overridePixelError = overrideFlags.HasFlag(TerrainQualityOverrides.PixelError) && !m_Terrain.ignoreQualitySettings; + using (new EditorGUI.DisabledScope(overridePixelError)) + { + heightmapPixelError = EditorGUILayout.Slider(styles.pixelError, m_Terrain.heightmapPixelError, 1, 200); + if (overridePixelError) + ShowOverrideBar(); + } + + float basemapDistance = m_Terrain.basemapDistance; + bool overrideBasemapDistance = overrideFlags.HasFlag(TerrainQualityOverrides.BasemapDistance) && !m_Terrain.ignoreQualitySettings; + using (new EditorGUI.DisabledScope(overrideBasemapDistance)) + { + basemapDistance = TerrainInspectorUtility.PowerSlider(styles.baseMapDist, m_Terrain.basemapDistance, 0.0f, 20000.0f, 2.0f); + if (overrideBasemapDistance) + ShowOverrideBar(); + } + var shadowCastingMode = (ShadowCastingMode)EditorGUILayout.EnumPopup(styles.castShadows, m_Terrain.shadowCastingMode); var reflectionProbeUsage = (ReflectionProbeUsage)EditorGUILayout.EnumPopup(styles.reflectionProbes, m_Terrain.reflectionProbeUsage); @@ -1213,12 +1243,61 @@ public void ShowSettings() using (new EditorGUI.DisabledScope(!bakeLightProbesForTrees)) deringLightProbesForTrees = EditorGUILayout.Toggle(styles.deringLightProbesForTrees, deringLightProbesForTrees); var preserveTreePrototypeLayers = EditorGUILayout.Toggle(styles.preserveTreePrototypeLayers, m_Terrain.preserveTreePrototypeLayers); - var detailObjectDistance = EditorGUILayout.Slider(styles.detailObjectDistance, m_Terrain.detailObjectDistance, 0, 250); // former string formatting: "" - var detailObjectDensity = EditorGUILayout.Slider(styles.detailObjectDensity, m_Terrain.detailObjectDensity, 0.0f, 1.0f); - var treeDistance = EditorGUILayout.Slider(styles.treeDistance, m_Terrain.treeDistance, 0, 5000); // former string formatting: "" - var treeBillboardDistance = EditorGUILayout.Slider(styles.treeBillboardDistance, m_Terrain.treeBillboardDistance, 5, 2000); // former string formatting: "" - var treeCrossFadeLength = EditorGUILayout.Slider(styles.treeCrossFadeLength, m_Terrain.treeCrossFadeLength, 0, 200); // former string formatting: "" - var treeMaximumFullLODCount = EditorGUILayout.IntSlider(styles.treeMaximumFullLODCount, m_Terrain.treeMaximumFullLODCount, 0, 10000); + + float detailObjectDistance = m_Terrain.detailObjectDistance; + bool overrideDetailDistance = overrideFlags.HasFlag(TerrainQualityOverrides.DetailDistance) && !m_Terrain.ignoreQualitySettings; + using (new EditorGUI.DisabledScope(overrideDetailDistance)) + { + detailObjectDistance = EditorGUILayout.Slider(styles.detailObjectDistance, m_Terrain.detailObjectDistance, 0, 1000); // former string formatting: "" + if (overrideDetailDistance) + ShowOverrideBar(); + } + + float detailObjectDensity = m_Terrain.detailObjectDensity; + bool overrideDetailDensity = overrideFlags.HasFlag(TerrainQualityOverrides.DetailDensity) && !m_Terrain.ignoreQualitySettings; + using (new EditorGUI.DisabledScope(overrideDetailDensity)) + { + detailObjectDensity = EditorGUILayout.Slider(styles.detailObjectDensity, m_Terrain.detailObjectDensity, 0.0f, 1.0f); + if (overrideDetailDensity) + ShowOverrideBar(); + } + + var treeDistance = m_Terrain.treeDistance; + bool overrideTreeDistance = overrideFlags.HasFlag(TerrainQualityOverrides.TreeDistance) && !m_Terrain.ignoreQualitySettings; + using (new EditorGUI.DisabledScope(overrideTreeDistance)) + { + treeDistance = EditorGUILayout.Slider(styles.treeDistance, m_Terrain.treeDistance, 0, 5000); // former string formatting: "" + if (overrideTreeDistance) + ShowOverrideBar(); + } + + var treeBillboardDistance = m_Terrain.treeBillboardDistance; + bool overrideTreeBillboardDistance = overrideFlags.HasFlag(TerrainQualityOverrides.BillboardStart) && !m_Terrain.ignoreQualitySettings; + using (new EditorGUI.DisabledScope(overrideTreeBillboardDistance)) + { + treeBillboardDistance = EditorGUILayout.Slider(styles.treeBillboardDistance, m_Terrain.treeBillboardDistance, 5, 2000); // former string formatting: "" + if (overrideTreeBillboardDistance) + ShowOverrideBar(); + } + + var treeCrossFadeLength = m_Terrain.treeCrossFadeLength; + bool overrideCrossFadeLength = overrideFlags.HasFlag(TerrainQualityOverrides.FadeLength) && !m_Terrain.ignoreQualitySettings; + using (new EditorGUI.DisabledScope(overrideCrossFadeLength)) + { + treeCrossFadeLength = EditorGUILayout.Slider(styles.treeCrossFadeLength, m_Terrain.treeCrossFadeLength, 0, 200); // former string formatting: "" + if (overrideCrossFadeLength) + ShowOverrideBar(); + } + + var treeMaximumFullLODCount = m_Terrain.treeMaximumFullLODCount; + bool overrideTreeMaximumFullLODCount = overrideFlags.HasFlag(TerrainQualityOverrides.MaxTrees) && !m_Terrain.ignoreQualitySettings; + using (new EditorGUI.DisabledScope(overrideTreeMaximumFullLODCount)) + { + treeMaximumFullLODCount = EditorGUILayout.IntSlider(styles.treeMaximumFullLODCount, m_Terrain.treeMaximumFullLODCount, 0, 10000); + if (overrideTreeMaximumFullLODCount) + ShowOverrideBar(); + } + var detailScatterMode = (DetailScatterMode)EditorGUILayout.EnumPopup(styles.detailScatterMode, m_Terrain.terrainData.detailScatterMode); // Only do this check once per frame. @@ -1319,6 +1398,21 @@ public void ShowSettings() RenderLightingFields(); ShowRenderingLayerMask(); + + m_ShowTerrainQualitySettings = EditorGUILayout.BeginFoldoutHeaderGroup(m_ShowTerrainQualitySettings, styles.qualitySettings); + if (m_ShowTerrainQualitySettings) + { + ++EditorGUI.indentLevel; + EditorGUI.BeginChangeCheck(); + var ignoreQualitySettings = EditorGUILayout.Toggle(styles.ignoreQualitySettings, m_Terrain.ignoreQualitySettings); + if (EditorGUI.EndChangeCheck()) + { + Undo.RecordObject(m_Terrain, "Terrain property change"); + m_Terrain.ignoreQualitySettings = ignoreQualitySettings; + MarkDirty(m_Terrain); + } + --EditorGUI.indentLevel; + } } // this is a non-serializedProperty version of RendererEditorBase.DrawRenderingLayer() diff --git a/Modules/XR/Subsystems/Display/XRDisplaySubsystem.bindings.cs b/Modules/XR/Subsystems/Display/XRDisplaySubsystem.bindings.cs index 14765348b5..af04f92297 100644 --- a/Modules/XR/Subsystems/Display/XRDisplaySubsystem.bindings.cs +++ b/Modules/XR/Subsystems/Display/XRDisplaySubsystem.bindings.cs @@ -55,6 +55,16 @@ public bool singlePassRenderingDisabled extern public bool sRGB { get; set; } extern public float occlusionMaskScale { get; set;} + [Flags] + public enum FoveatedRenderingFlags + { + None = 0, + GazeAllowed = 1 << 0 + } + + extern public float foveatedRenderingLevel { get; set; } + extern public FoveatedRenderingFlags foveatedRenderingFlags { get; set; } + public enum LateLatchNode { Head = 0, @@ -183,6 +193,8 @@ public struct XRRenderPass public int cullingPassIndex; + public IntPtr foveatedRenderingInfo; + [NativeMethod(Name = "XRRenderPassScriptApi::GetRenderParameter", IsFreeFunction = true, HasExplicitThis = true, ThrowsException = true)] [NativeConditional("ENABLE_XR")] extern public void GetRenderParameter(Camera camera, int renderParameterIndex, out XRRenderParameter renderParameter); @@ -219,6 +231,7 @@ public struct XRBlitParams public int srcTexArraySlice; public Rect srcRect; public Rect destRect; + public IntPtr foveatedRenderingInfo; } [NativeHeader("Modules/XR/Subsystems/Display/XRDisplaySubsystem.bindings.h")] diff --git a/Projects/CSharp/UnityEditor.csproj b/Projects/CSharp/UnityEditor.csproj index 6f5cf90c7d..4d1814f9ea 100644 --- a/Projects/CSharp/UnityEditor.csproj +++ b/Projects/CSharp/UnityEditor.csproj @@ -6811,6 +6811,9 @@ Modules\Progress\VisualProgressItem.cs + + Modules\PropertiesEditor\Module\PropertiesEditorModule.cs + Modules\QuickSearch\Editor\FuzzySearch.cs diff --git a/Projects/CSharp/UnityEngine.csproj b/Projects/CSharp/UnityEngine.csproj index 0f55113494..17f4c41967 100644 --- a/Projects/CSharp/UnityEngine.csproj +++ b/Projects/CSharp/UnityEngine.csproj @@ -1792,6 +1792,156 @@ Modules\Physics\ScriptBindings\QueryCommand.deprecated.cs + + Modules\Properties\Runtime\Algorithms\PropertyContainer+Accept.cs + + + Modules\Properties\Runtime\Algorithms\PropertyContainer+GetProperty.cs + + + Modules\Properties\Runtime\Algorithms\PropertyContainer+GetValue.cs + + + Modules\Properties\Runtime\Algorithms\PropertyContainer+Path.cs + + + Modules\Properties\Runtime\Algorithms\PropertyContainer+SetValue.cs + + + Modules\Properties\Runtime\Algorithms\PropertyContainer.cs + + + Modules\Properties\Runtime\Algorithms\VisitReturnCode.cs + + + Modules\Properties\Runtime\AssemblyInfo.cs + + + Modules\Properties\Runtime\Attributes.cs + + + Modules\Properties\Runtime\Exceptions.cs + + + Modules\Properties\Runtime\Properties\AttributesScope.cs + + + Modules\Properties\Runtime\Properties\DelegateProperty.cs + + + Modules\Properties\Runtime\Properties\ICollectionElementProperty.cs + + + Modules\Properties\Runtime\Properties\Internal\IAttributes.cs + + + Modules\Properties\Runtime\Properties\Property.cs + + + Modules\Properties\Runtime\Properties\PropertyPath.cs + + + Modules\Properties\Runtime\Properties\ReflectedMemberProperty.cs + + + Modules\Properties\Runtime\PropertyBags\ArrayPropertyBag.cs + + + Modules\Properties\Runtime\PropertyBags\ContainerPropertyBag.cs + + + Modules\Properties\Runtime\PropertyBags\DictionaryPropertyBag.cs + + + Modules\Properties\Runtime\PropertyBags\HashSetPropertyBag.cs + + + Modules\Properties\Runtime\PropertyBags\IPropertyBag.cs + + + Modules\Properties\Runtime\PropertyBags\IndexedCollectionPropertyBag.cs + + + Modules\Properties\Runtime\PropertyBags\Internal\DefaultPropertyBags.cs + + + Modules\Properties\Runtime\PropertyBags\Internal\PropertyBagStore.cs + + + Modules\Properties\Runtime\PropertyBags\KeyValueCollectionPropertyBag.cs + + + Modules\Properties\Runtime\PropertyBags\KeyValuePairPropertyBag.cs + + + Modules\Properties\Runtime\PropertyBags\ListPropertyBag.cs + + + Modules\Properties\Runtime\PropertyBags\PropertyBag+Accept.cs + + + Modules\Properties\Runtime\PropertyBags\PropertyBag+Registration.cs + + + Modules\Properties\Runtime\PropertyBags\PropertyBag+TypeConstruction.cs + + + Modules\Properties\Runtime\PropertyBags\PropertyBag.cs + + + Modules\Properties\Runtime\PropertyBags\PropertyCollection.cs + + + Modules\Properties\Runtime\PropertyBags\SetPropertyBag.cs + + + Modules\Properties\Runtime\PropertyVisitors\Adapters\IExcludePropertyAdapter.cs + + + Modules\Properties\Runtime\PropertyVisitors\Adapters\IVisitPrimitivesPropertyAdapter.cs + + + Modules\Properties\Runtime\PropertyVisitors\Adapters\IVisitPropertyAdapter.cs + + + Modules\Properties\Runtime\PropertyVisitors\ConcreteTypeVisitor.cs + + + Modules\Properties\Runtime\PropertyVisitors\ExcludeContext.cs + + + Modules\Properties\Runtime\PropertyVisitors\IAccept.cs + + + Modules\Properties\Runtime\PropertyVisitors\IVisitor.cs + + + Modules\Properties\Runtime\PropertyVisitors\Internal\ReadOnlyAdapterCollection.cs + + + Modules\Properties\Runtime\PropertyVisitors\PathVisitor.cs + + + Modules\Properties\Runtime\PropertyVisitors\PropertyVisitor.cs + + + Modules\Properties\Runtime\PropertyVisitors\VisitContext.cs + + + Modules\Properties\Runtime\Reflection\Internal\ReflectedPropertyBag.cs + + + Modules\Properties\Runtime\Reflection\Internal\ReflectedPropertyBagProvider.cs + + + Modules\Properties\Runtime\Utility\TypeConversion.cs + + + Modules\Properties\Runtime\Utility\TypeTraits.cs + + + Modules\Properties\Runtime\Utility\TypeUtility.cs + Modules\ScreenCapture\ScriptBindings\ScreenCapture.bindings.cs diff --git a/README.md b/README.md index 169830c857..aeacd3e82d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -## Unity 2022.2.0a17 C# reference source code +## Unity 2022.2.0a18 C# reference source code The C# part of the Unity engine and editor source code. May be used for reference purposes only. diff --git a/Runtime/Export/Device/SystemInfo.cs b/Runtime/Export/Device/SystemInfo.cs index 3927288536..dbdd679769 100644 --- a/Runtime/Export/Device/SystemInfo.cs +++ b/Runtime/Export/Device/SystemInfo.cs @@ -67,6 +67,8 @@ public static class SystemInfo public static Rendering.RenderingThreadingMode renderingThreadingMode => ShimManager.systemInfoShim.renderingThreadingMode; + public static FoveatedRenderingCaps foveatedRenderingCaps => ShimManager.systemInfoShim.foveatedRenderingCaps; + public static bool hasHiddenSurfaceRemovalOnGPU => ShimManager.systemInfoShim.hasHiddenSurfaceRemovalOnGPU; public static bool hasDynamicUniformArrayIndexingInFragmentShaders => diff --git a/Runtime/Export/Graphics/GraphicsEnums.cs b/Runtime/Export/Graphics/GraphicsEnums.cs index bc7c7546fc..79890776e6 100644 --- a/Runtime/Export/Graphics/GraphicsEnums.cs +++ b/Runtime/Export/Graphics/GraphicsEnums.cs @@ -1948,6 +1948,22 @@ public enum SinglePassStereoMode Multiview } + // Must match c++ FoveatedRenderingCaps from GfxDeviceTypes.h + [Flags] + public enum FoveatedRenderingCaps + { + None = 0, + FoveationImage = 1 << 0, + NonUniformRaster = 1 << 1, + } + + // Must match c++ FoveatedRenderingMode from GfxDeviceTypes.h + public enum FoveatedRenderingMode + { + Disabled = 0, + Enabled = 1, + } + //Needs to line up with the common elements of the c++ version of this enum found GfxDeviceTypes.h public enum CommandBufferExecutionFlags { diff --git a/Runtime/Export/Graphics/GraphicsManagers.bindings.cs b/Runtime/Export/Graphics/GraphicsManagers.bindings.cs index 264540db34..6a345c5634 100644 --- a/Runtime/Export/Graphics/GraphicsManagers.bindings.cs +++ b/Runtime/Export/Graphics/GraphicsManagers.bindings.cs @@ -14,6 +14,20 @@ namespace UnityEngine { + [Flags] + public enum TerrainQualityOverrides + { + None = 0, + PixelError = 1, + BasemapDistance = 2, + DetailDensity = 4, + DetailDistance = 8, + TreeDistance = 16, + BillboardStart = 32, + FadeLength = 64, + MaxTrees = 128 + } + [NativeHeader("Runtime/Camera/RenderSettings.h")] [NativeHeader("Runtime/Graphics/QualitySettingsTypes.h")] [StaticAccessor("GetRenderSettings()", StaticAccessorType.Dot)] @@ -114,6 +128,16 @@ private QualitySettings() {} extern public static bool useLegacyDetailDistribution { get; set; } extern public static float resolutionScalingFixedDPIFactor { get; set; } + extern public static TerrainQualityOverrides terrainQualityOverrides { get; set; } + extern public static float terrainPixelError { get; set; } + extern public static float terrainDetailDensityScale { get; set; } + extern public static float terrainBasemapDistance { get; set; } + extern public static float terrainDetailDistance { get; set; } + extern public static float terrainTreeDistance { get; set; } + extern public static float terrainBillboardStart { get; set; } + extern public static float terrainFadeLength { get; set; } + extern public static float terrainMaxTrees { get; set; } + [NativeName("RenderPipeline")] extern private static ScriptableObject INTERNAL_renderPipeline { get; set; } public static RenderPipelineAsset renderPipeline { diff --git a/Runtime/Export/Graphics/RenderingCommandBuffer.bindings.cs b/Runtime/Export/Graphics/RenderingCommandBuffer.bindings.cs index d3089d442a..8d16fe2626 100644 --- a/Runtime/Export/Graphics/RenderingCommandBuffer.bindings.cs +++ b/Runtime/Export/Graphics/RenderingCommandBuffer.bindings.cs @@ -428,6 +428,9 @@ public void RequestAsyncReadbackIntoNativeSlice(ref NativeSlice output, Te [FreeFunction("RenderingCommandBuffer_Bindings::SetComputeBufferParam", HasExplicitThis = true)] extern private void Internal_SetComputeBufferParam([NotNull] ComputeShader computeShader, int kernelIndex, int nameID, ComputeBuffer buffer); + [FreeFunction("RenderingCommandBuffer_Bindings::SetComputeBufferParam", HasExplicitThis = true)] + extern private void Internal_SetComputeGraphicsBufferHandleParam([NotNull] ComputeShader computeShader, int kernelIndex, int nameID, GraphicsBufferHandle bufferHandle); + [FreeFunction("RenderingCommandBuffer_Bindings::SetComputeBufferParam", HasExplicitThis = true)] extern private void Internal_SetComputeGraphicsBufferParam([NotNull] ComputeShader computeShader, int kernelIndex, int nameID, GraphicsBuffer buffer); @@ -437,6 +440,9 @@ public void RequestAsyncReadbackIntoNativeSlice(ref NativeSlice output, Te [FreeFunction("RenderingCommandBuffer_Bindings::SetComputeConstantBufferParam", HasExplicitThis = true)] extern private void Internal_SetComputeConstantGraphicsBufferParam([NotNull] ComputeShader computeShader, int nameID, GraphicsBuffer buffer, int offset, int size); + [FreeFunction("RenderingCommandBuffer_Bindings::SetComputeParamsFromMaterial", HasExplicitThis = true)] + extern private void Internal_SetComputeParamsFromMaterial([NotNull] ComputeShader computeShader, int kernelIndex, Material material); + [FreeFunction("RenderingCommandBuffer_Bindings::Internal_DispatchCompute", HasExplicitThis = true, ThrowsException = true)] extern private void Internal_DispatchCompute([NotNull] ComputeShader computeShader, int kernelIndex, int threadGroupsX, int threadGroupsY, int threadGroupsZ); @@ -916,6 +922,12 @@ public void ClearRenderTarget(bool clearDepth, bool clearColor, Color background [FreeFunction("RenderingCommandBuffer_Bindings::SetInstanceMultiplier", HasExplicitThis = true)] extern public void SetInstanceMultiplier(uint multiplier); + [FreeFunction("RenderingCommandBuffer_Bindings::SetFoveatedRenderingMode", HasExplicitThis = true)] + extern public void SetFoveatedRenderingMode(FoveatedRenderingMode foveatedRenderingMode); + + [FreeFunction("RenderingCommandBuffer_Bindings::ConfigureFoveatedRendering", HasExplicitThis = true)] + extern public void ConfigureFoveatedRendering(IntPtr platformData); + public void SetRenderTarget(RenderTargetIdentifier rt) { ValidateAgainstExecutionFlags(CommandBufferExecutionFlags.None, CommandBufferExecutionFlags.AsyncCompute); diff --git a/Runtime/Export/Graphics/RenderingCommandBuffer.cs b/Runtime/Export/Graphics/RenderingCommandBuffer.cs index 8ea2eb8a8e..3f6c53648f 100644 --- a/Runtime/Export/Graphics/RenderingCommandBuffer.cs +++ b/Runtime/Export/Graphics/RenderingCommandBuffer.cs @@ -195,6 +195,16 @@ public void SetComputeBufferParam(ComputeShader computeShader, int kernelIndex, Internal_SetComputeBufferParam(computeShader, kernelIndex, Shader.PropertyToID(name), buffer); } + public void SetComputeBufferParam(ComputeShader computeShader, int kernelIndex, int nameID, GraphicsBufferHandle bufferHandle) + { + Internal_SetComputeGraphicsBufferHandleParam(computeShader, kernelIndex, nameID, bufferHandle); + } + + public void SetComputeBufferParam(ComputeShader computeShader, int kernelIndex, string name, GraphicsBufferHandle bufferHandle) + { + Internal_SetComputeGraphicsBufferHandleParam(computeShader, kernelIndex, Shader.PropertyToID(name), bufferHandle); + } + public void SetComputeBufferParam(ComputeShader computeShader, int kernelIndex, int nameID, GraphicsBuffer buffer) { Internal_SetComputeGraphicsBufferParam(computeShader, kernelIndex, nameID, buffer); @@ -225,6 +235,11 @@ public void SetComputeConstantBufferParam(ComputeShader computeShader, string na Internal_SetComputeConstantGraphicsBufferParam(computeShader, Shader.PropertyToID(name), buffer, offset, size); } + public void SetComputeParamsFromMaterial(ComputeShader computeShader, int kernelIndex, Material material) + { + Internal_SetComputeParamsFromMaterial(computeShader, kernelIndex, material); + } + // Execute a compute shader. public void DispatchCompute(ComputeShader computeShader, int kernelIndex, int threadGroupsX, int threadGroupsY, int threadGroupsZ) { diff --git a/Runtime/Export/Shaders/Material.bindings.cs b/Runtime/Export/Shaders/Material.bindings.cs index 8b7a9afd87..7b72be1a4b 100644 --- a/Runtime/Export/Shaders/Material.bindings.cs +++ b/Runtime/Export/Shaders/Material.bindings.cs @@ -192,6 +192,9 @@ public Vector2 mainTextureScale [FreeFunction("MaterialScripting::SetShaderKeywords", HasExplicitThis = true)] extern private void SetShaderKeywords(string[] names); public string[] shaderKeywords { get { return GetShaderKeywords(); } set { SetShaderKeywords(value); } } + [FreeFunction("MaterialScripting::GetPropertyNames", HasExplicitThis = true)] + extern private string[] GetPropertyNamesImpl(int propertyType); + extern public int ComputeCRC(); [FreeFunction("MaterialScripting::GetTexturePropertyNames", HasExplicitThis = true)] @@ -227,8 +230,6 @@ public void GetTexturePropertyNameIDs(List outNames) } - // TODO: get buffer is missing - [NativeName("SetIntFromScript")] extern private void SetIntImpl(int name, int value); [NativeName("SetFloatFromScript")] extern private void SetFloatImpl(int name, float value); [NativeName("SetColorFromScript")] extern private void SetColorImpl(int name, Color value); @@ -245,6 +246,8 @@ public void GetTexturePropertyNameIDs(List outNames) [NativeName("GetColorFromScript")] extern private Color GetColorImpl(int name); [NativeName("GetMatrixFromScript")] extern private Matrix4x4 GetMatrixImpl(int name); [NativeName("GetTextureFromScript")] extern private Texture GetTextureImpl(int name); + [NativeName("GetBufferFromScript")] extern private GraphicsBufferHandle GetBufferImpl(int name); + [NativeName("GetConstantBufferFromScript")] extern private GraphicsBufferHandle GetConstantBufferImpl(int name); [FreeFunction(Name = "MaterialScripting::SetFloatArray", HasExplicitThis = true)] extern private void SetFloatArrayImpl(int name, float[] values, int count); [FreeFunction(Name = "MaterialScripting::SetVectorArray", HasExplicitThis = true)] extern private void SetVectorArrayImpl(int name, Vector4[] values, int count); diff --git a/Runtime/Export/Shaders/Material.cs b/Runtime/Export/Shaders/Material.cs index eb9714a96c..6e79dac6c0 100644 --- a/Runtime/Export/Shaders/Material.cs +++ b/Runtime/Export/Shaders/Material.cs @@ -7,6 +7,19 @@ namespace UnityEngine { + // Note: We must manually adapt the managed enum PropertyType into the native enum ShaderPropertySheetType to 'hide' useless enum values to user. + // Keep them in sync. + public enum MaterialPropertyType + { + Float, + Int, + Vector, + Matrix, + Texture, + ConstantBuffer, + ComputeBuffer, + } + public partial class Material { // at some point we were having generic methods so all set/set-array/get/get-array could be routed through one impl where we were doing all the checks @@ -162,6 +175,8 @@ private void ExtractMatrixArray(int name, List values) public Matrix4x4 GetMatrix(int nameID) { return GetMatrixImpl(nameID); } public Texture GetTexture(string name) { return GetTextureImpl(Shader.PropertyToID(name)); } public Texture GetTexture(int nameID) { return GetTextureImpl(nameID); } + public GraphicsBufferHandle GetBuffer(string name) { return GetBufferImpl(Shader.PropertyToID(name)); } + public GraphicsBufferHandle GetConstantBuffer(string name) { return GetConstantBufferImpl(Shader.PropertyToID(name)); } public float[] GetFloatArray(string name) { return GetFloatArray(Shader.PropertyToID(name)); } public float[] GetFloatArray(int nameID) { return GetFloatArrayCountImpl(nameID) != 0 ? GetFloatArrayImpl(nameID) : null; } @@ -190,5 +205,7 @@ private void ExtractMatrixArray(int name, List values) public Vector2 GetTextureOffset(int nameID) { Vector4 st = GetTextureScaleAndOffsetImpl(nameID); return new Vector2(st.z, st.w); } public Vector2 GetTextureScale(string name) { return GetTextureScale(Shader.PropertyToID(name)); } public Vector2 GetTextureScale(int nameID) { Vector4 st = GetTextureScaleAndOffsetImpl(nameID); return new Vector2(st.x, st.y); } + + public string[] GetPropertyNames(MaterialPropertyType type) { return GetPropertyNamesImpl((int)type); } } } diff --git a/Runtime/Export/StaticShim/SystemInfoShimBase.cs b/Runtime/Export/StaticShim/SystemInfoShimBase.cs index 9286b904ee..8e63528e6b 100644 --- a/Runtime/Export/StaticShim/SystemInfoShimBase.cs +++ b/Runtime/Export/StaticShim/SystemInfoShimBase.cs @@ -68,6 +68,8 @@ internal class SystemInfoShimBase public virtual Rendering.RenderingThreadingMode renderingThreadingMode => UnityEngine.SystemInfo.renderingThreadingMode; + public virtual FoveatedRenderingCaps foveatedRenderingCaps => UnityEngine.SystemInfo.foveatedRenderingCaps; + public virtual bool hasHiddenSurfaceRemovalOnGPU => UnityEngine.SystemInfo.hasHiddenSurfaceRemovalOnGPU; public virtual bool hasDynamicUniformArrayIndexingInFragmentShaders => diff --git a/Runtime/Export/SystemInfo/SystemInfo.bindings.cs b/Runtime/Export/SystemInfo/SystemInfo.bindings.cs index 171e04f583..58fd413af5 100644 --- a/Runtime/Export/SystemInfo/SystemInfo.bindings.cs +++ b/Runtime/Export/SystemInfo/SystemInfo.bindings.cs @@ -221,6 +221,11 @@ public static Rendering.RenderingThreadingMode renderingThreadingMode get { return GetRenderingThreadingMode(); } } + public static FoveatedRenderingCaps foveatedRenderingCaps + { + get { return GetFoveatedRenderingCaps(); } + } + public static bool hasHiddenSurfaceRemovalOnGPU { get { return HasHiddenSurfaceRemovalOnGPU(); } @@ -753,6 +758,9 @@ public static bool supportsIndirectArgumentsBuffer [FreeFunction("ScriptingGraphicsCaps::GetRenderingThreadingMode")] static extern Rendering.RenderingThreadingMode GetRenderingThreadingMode(); + [FreeFunction("ScriptingGraphicsCaps::GetFoveatedRenderingCaps")] + static extern FoveatedRenderingCaps GetFoveatedRenderingCaps(); + [FreeFunction("ScriptingGraphicsCaps::HasHiddenSurfaceRemovalOnGPU")] static extern bool HasHiddenSurfaceRemovalOnGPU(); From 22d3640efdca2e48f1a837d7fa8bed1859f994a9 Mon Sep 17 00:00:00 2001 From: Unity Technologies Date: Tue, 12 Jul 2022 23:24:29 +0000 Subject: [PATCH 02/94] Unity 2022.2.0b1 C# reference source code --- .../SpriteAtlasImporterInspector.cs | 2 + Editor/Mono/EditorGUI.cs | 5 +- Editor/Mono/GUI/Tools/EditorToolUtility.cs | 2 +- Editor/Mono/GUI/Tools/ToolManager.cs | 2 +- .../Mono/SceneModeWindows/LightingWindow.cs | 5 +- .../BeeDriver/UnityScriptUpdater.cs | 97 ++++++++++++++----- .../Mono/UIElements/Controls/PropertyField.cs | 1 + .../Library/BuilderLibraryProjectScanner.cs | 10 +- .../Core/Controls/BaseListView.cs | 2 + .../Core/GameObjects/PanelSettings.cs | 13 ++- .../Core/GameObjects/UIDocument.cs | 10 ++ .../Editor/Debugger/StylesDebugger.cs | 8 ++ .../External/EditorGameServiceExtension.cs | 63 ++++++++++-- .../Editor/Services/Upm/UpmVersionList.cs | 5 +- .../Runtime/Utility/TypeConversion.cs | 3 +- README.md | 2 +- .../Application/Application.bindings.cs | 4 + Runtime/Export/Application/Application.cs | 47 ++++++++- Runtime/Export/AssemblyInfo.cs | 1 + Runtime/Export/Camera/Camera.bindings.cs | 18 +++- Runtime/Export/Device/Application.cs | 6 ++ .../Export/RenderPipeline/RenderPipeline.cs | 34 ++++++- .../RenderPipeline/RenderPipelineManager.cs | 6 +- .../Export/StaticShim/ApplicationShimBase.cs | 6 ++ 24 files changed, 297 insertions(+), 55 deletions(-) diff --git a/Editor/Mono/2D/SpriteAtlas/SpriteAtlasImporterInspector.cs b/Editor/Mono/2D/SpriteAtlas/SpriteAtlasImporterInspector.cs index ee8e7ea0b2..f0b1b78c72 100644 --- a/Editor/Mono/2D/SpriteAtlas/SpriteAtlasImporterInspector.cs +++ b/Editor/Mono/2D/SpriteAtlas/SpriteAtlasImporterInspector.cs @@ -412,6 +412,8 @@ protected override void Apply() base.Apply(); } + protected override bool useAssetDrawPreview { get { return false; } } + protected void PackPreviewGUI() { EditorGUILayout.Space(); diff --git a/Editor/Mono/EditorGUI.cs b/Editor/Mono/EditorGUI.cs index 093ca2cbdd..28044f5ccb 100644 --- a/Editor/Mono/EditorGUI.cs +++ b/Editor/Mono/EditorGUI.cs @@ -2972,11 +2972,10 @@ internal static GenericMenu FillPropertyContextMenu(SerializedProperty property, Object targetObject = property.serializedObject.targetObject; - var shouldDisplayPrefabContextMenuItems = property.prefabOverride || (linkedProperty?.prefabOverride ?? false); + bool shouldDisplayPrefabContextMenuItems = property.prefabOverride || (linkedProperty?.prefabOverride ?? false); // Only display the custom apply/revert menu for GameObjects/Components that are not part of a Prefab instance & variant. - var shouldDisplayApplyRevertProviderContextMenuItems = targetObject is IApplyRevertPropertyContextMenuItemProvider - && PrefabUtility.HasApplicableObjectOverrides(targetObject, false); + var shouldDisplayApplyRevertProviderContextMenuItems = targetObject is IApplyRevertPropertyContextMenuItemProvider; if (shouldDisplayPrefabContextMenuItems || shouldDisplayApplyRevertProviderContextMenuItems) { diff --git a/Editor/Mono/GUI/Tools/EditorToolUtility.cs b/Editor/Mono/GUI/Tools/EditorToolUtility.cs index c7b74a0836..41314e7b8f 100644 --- a/Editor/Mono/GUI/Tools/EditorToolUtility.cs +++ b/Editor/Mono/GUI/Tools/EditorToolUtility.cs @@ -135,7 +135,7 @@ internal static EditorTool GetEditorToolWithEnum(Tool type, EditorToolContext ct // Tool types can resolve to either global or instance tools if (IsComponentTool(resolved)) { - var instance = EditorToolManager.GetComponentTool(resolved); + var instance = EditorToolManager.GetComponentTool(resolved, true); if (instance == null) { Debug.LogError($"{context} resolved Tool.{type} to a Component tool of type `{resolved}`, but " + diff --git a/Editor/Mono/GUI/Tools/ToolManager.cs b/Editor/Mono/GUI/Tools/ToolManager.cs index 991df48130..c3605a87cb 100644 --- a/Editor/Mono/GUI/Tools/ToolManager.cs +++ b/Editor/Mono/GUI/Tools/ToolManager.cs @@ -24,7 +24,7 @@ public static void SetActiveContext(Type context) if (EditorToolUtility.IsComponentEditor(ctx)) { - var instance = EditorToolManager.GetComponentContext(ctx); + var instance = EditorToolManager.GetComponentContext(ctx, true); if (instance == null) throw new InvalidOperationException("The current selection does not contain any objects editable " + $"by the component tool of type: {context}"); diff --git a/Editor/Mono/SceneModeWindows/LightingWindow.cs b/Editor/Mono/SceneModeWindows/LightingWindow.cs index ef49c49293..797c83323d 100644 --- a/Editor/Mono/SceneModeWindows/LightingWindow.cs +++ b/Editor/Mono/SceneModeWindows/LightingWindow.cs @@ -363,7 +363,7 @@ void Buttons() GUILayout.BeginHorizontal(); GUILayout.FlexibleSpace(); - bool iterative = (m_WorkflowMode.intValue == (int)Lightmapping.GIWorkflowMode.Iterative) && entitiesPackage; + bool iterative = (m_WorkflowMode.intValue == (int)Lightmapping.GIWorkflowMode.Iterative); Rect rect = GUILayoutUtility.GetRect(Styles.continuousBakeLabel, GUIStyle.none); @@ -378,6 +378,9 @@ void Buttons() if (EditorGUI.EndChangeCheck()) { + if (entitiesPackage) + iterative = false; + m_WorkflowMode.intValue = (int)(iterative ? Lightmapping.GIWorkflowMode.Iterative : Lightmapping.GIWorkflowMode.OnDemand); } diff --git a/Editor/Mono/Scripting/ScriptCompilation/BeeDriver/UnityScriptUpdater.cs b/Editor/Mono/Scripting/ScriptCompilation/BeeDriver/UnityScriptUpdater.cs index 1ebb8ffc0d..041ca2d4de 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/BeeDriver/UnityScriptUpdater.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/BeeDriver/UnityScriptUpdater.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using System.Text.RegularExpressions; using Bee.BeeDriver; using Bee.Serialization; using NiceIO; @@ -23,6 +24,10 @@ class UnityScriptUpdater : SourceFileUpdaterBase NPath ProjectRoot { get; } RunnableProgram _scriptUpdaterProgram { get; } + // this is the return code used by ScriptUpdater to report that + // some updates were not applied. + const int k_UpdatesToFilesInSameProjectWereNotApplied = 3; + public UnityScriptUpdater(NPath projectRoot) { ProjectRoot = projectRoot; @@ -94,58 +99,102 @@ public override Task StartIfYouCanFixProblemsInTheseMessages(in NodeFin var tempOutputDirectory = new NPath($"Temp/ScriptUpdater/{Math.Abs(nodeFinishedMessage.Node.OutputFile.GetHashCode())}").EnsureDirectoryExists(); var updateTxtFile = tempOutputDirectory.MakeAbsolute().Combine("updates.txt").DeleteIfExists(); + var updaterMessagesToConsoleFile = tempOutputDirectory.MakeAbsolute().Combine($"messages_{new Random().Next()}.txt").DeleteIfExists(); var args = new[] { - "cs", $"\"{EditorApplication.applicationContentsPath}\"", $"\"{tempOutputDirectory}\"", $"\"{_configurationSourcesFilter}\"", - assemblyInfo.ScriptUpdaterRsp.ToNPath().InQuotes() + assemblyInfo.ScriptUpdaterRsp.ToNPath().InQuotes(), + updaterMessagesToConsoleFile.InQuotes() }; var runningScriptUpdater = _scriptUpdaterProgram.Start(ProjectRoot.ToString(), args); - return AwaitScriptUpdaterResults(runningScriptUpdater, updateTxtFile, containsUpdatableCompilerMessage, nodeFinishedMessage.Node.OutputFile); + return AwaitScriptUpdaterResults(runningScriptUpdater, updateTxtFile, updaterMessagesToConsoleFile, containsUpdatableCompilerMessage, nodeFinishedMessage.Node.OutputFile); } - async Task AwaitScriptUpdaterResults(RunningProgram runningScriptUpdater, NPath updateTxtFile, CanUpdateAny containsUpdatableCompilerMessage, string nodeOutputFile) + async Task AwaitScriptUpdaterResults(RunningProgram runningScriptUpdater, NPath updateTxtFile, NPath updaterMessagesToConsoleFile, CanUpdateAny containsUpdatableCompilerMessage, string nodeOutputFile) { //when the user asks us to cancel a build, we do not want to leave stray unity script updaters laying around. we will //just wait for them to finish. var noToken = CancellationToken.None; var scriptUpdaterResult = await runningScriptUpdater.WaitForExitAsync(noToken); - if (scriptUpdaterResult.ExitCode != 0) - return ErrorResult( - $"Script updater for {nodeOutputFile} failed with exitcode {scriptUpdaterResult.ExitCode} and stdout: {scriptUpdaterResult.Output}"); + var messages = ResultsFromUpdaterImportantMessages(updaterMessagesToConsoleFile); + if (scriptUpdaterResult.ExitCode == k_UpdatesToFilesInSameProjectWereNotApplied) + { + return CollectResultsIfAny(messages, nodeOutputFile, updateTxtFile, CanUpdateAny.Maybe); + } + else if (scriptUpdaterResult.ExitCode != 0) + return ErrorResult($"Script updater for {nodeOutputFile} failed with exitcode {scriptUpdaterResult.ExitCode} and stdout: {scriptUpdaterResult.Output}"); if (!updateTxtFile.FileExists()) return ErrorResult($"Script updater for {nodeOutputFile} failed to produce updates.txt file"); - var updateLines = updateTxtFile.ReadAllLines(); - var updates = updateLines.Select(ParseLineIntoUpdate).ToArray(); - if (updates.Contains(null)) - return ErrorResult($"Script updater for {nodeOutputFile} emitted an invalid line to updates.txt"); - - return new Results() - { - Messages = (containsUpdatableCompilerMessage == CanUpdateAny.Certainly && updates.Length == 0) - ? new[] - { - new BeeDriverResult.Message( - $"Script updater for {nodeOutputFile} expected to be able to make an update, but wasn't able to", - BeeDriverResult.MessageKind.Warning) - } - : Array.Empty(), - ProducedUpdates = updates.ToArray() - }; + return CollectResultsIfAny(messages, nodeOutputFile, updateTxtFile, containsUpdatableCompilerMessage); static Results ErrorResult(string message) => new() { Messages = new[] {new BeeDriverResult.Message(message, BeeDriverResult.MessageKind.Error)}, ProducedUpdates = Array.Empty() }; + + BeeDriverResult.Message[] ResultsFromUpdaterImportantMessages(NPath updaterMessagesToConsoleFile) + { + try + { + var lines = Regex.Split(updaterMessagesToConsoleFile.ReadAllText(), "^(?Warning|Error):", RegexOptions.Multiline, TimeSpan.FromSeconds(5)); + if (lines.Length <= 1) // first split line is always empty.. + return Array.Empty(); + + // first line = warning/error, second line = actual message + var messages = new List((lines.Length - 1)/2); + for (int i = 1; i < lines.Length; i+=2) + { + var messageKind = lines[i] switch + { + "Error" => BeeDriverResult.MessageKind.Error, + "Warning" => BeeDriverResult.MessageKind.Warning, + _ => BeeDriverResult.MessageKind.Warning, + }; + messages.Add(new BeeDriverResult.Message(lines[i + 1], messageKind)); + } + + return messages.ToArray(); + } + catch(System.IO.IOException) + { + return Array.Empty(); + } + } + + static Results CollectResultsIfAny(BeeDriverResult.Message[] messages, string nodeOutputFile, NPath updateTxtFile, CanUpdateAny containsUpdatableCompilerMessage) + { + var updates = Array.Empty(); + if (updateTxtFile.FileExists()) + { + var updateLines = updateTxtFile.ReadAllLines(); + + updates = updateLines.Select(ParseLineIntoUpdate).ToArray(); + if (updates.Contains(null)) + return ErrorResult($"Script updater for {nodeOutputFile} emitted an invalid line to {updateTxtFile}"); + + if (containsUpdatableCompilerMessage == CanUpdateAny.Certainly && updates.Length == 0) + { + var temp = new List(messages); + temp.Add(new BeeDriverResult.Message($"Script updater for {nodeOutputFile} expected to be able to make an update, but wasn't able to", BeeDriverResult.MessageKind.Warning)); + messages = temp.ToArray(); + } + } + + return new Results() + { + Messages = messages, + ProducedUpdates = updates + }; + } } internal static Update ParseLineIntoUpdate(string line) diff --git a/Editor/Mono/UIElements/Controls/PropertyField.cs b/Editor/Mono/UIElements/Controls/PropertyField.cs index 8a6cddfb5d..d0cd664f15 100644 --- a/Editor/Mono/UIElements/Controls/PropertyField.cs +++ b/Editor/Mono/UIElements/Controls/PropertyField.cs @@ -560,6 +560,7 @@ VisualElement ConfigureListView(ListView listView, SerializedProperty property) listView.bindingPath = property.propertyPath; listView.viewDataKey = property.propertyPath; listView.name = "unity-list-" + property.propertyPath; + listView.headerFoldout.viewDataKey = property.propertyPath; listView.Bind(property.serializedObject); return listView; } diff --git a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Library/BuilderLibraryProjectScanner.cs b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Library/BuilderLibraryProjectScanner.cs index 91e5a3953b..9841110f82 100644 --- a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Library/BuilderLibraryProjectScanner.cs +++ b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Library/BuilderLibraryProjectScanner.cs @@ -183,7 +183,7 @@ public void ImportFactoriesFromSource(BuilderLibraryItem sourceCategory) } else { - AddCategoriesToStack(sourceCategory, categoryStack, split); + AddCategoriesToStack(sourceCategory, categoryStack, split, "csharp-"); if (categoryStack.Count == 0) sourceCategory.AddChild(newItem); else @@ -194,7 +194,7 @@ public void ImportFactoriesFromSource(BuilderLibraryItem sourceCategory) sourceCategory.AddChildren(emptyNamespaceControls); } - static void AddCategoriesToStack(BuilderLibraryItem sourceCategory, List categoryStack, string[] split) + static void AddCategoriesToStack(BuilderLibraryItem sourceCategory, List categoryStack, string[] split, string idNamePrefix) { if (categoryStack.Count > split.Length) { @@ -224,7 +224,7 @@ static void AddCategoriesToStack(BuilderLibraryItem sourceCategory, List EnableFooter(value); } + internal Foldout headerFoldout => m_Foldout; + void EnableFooter(bool enabled) { EnableInClassList(listViewWithFooterUssClassName, enabled); diff --git a/ModuleOverrides/com.unity.ui/Core/GameObjects/PanelSettings.cs b/ModuleOverrides/com.unity.ui/Core/GameObjects/PanelSettings.cs index dbb2b5a39e..1dea2b8756 100644 --- a/ModuleOverrides/com.unity.ui/Core/GameObjects/PanelSettings.cs +++ b/ModuleOverrides/com.unity.ui/Core/GameObjects/PanelSettings.cs @@ -504,7 +504,7 @@ private void Reset() { // We assume users will want their UIDocument to look as closely as possible to what they look like in the UIBuilder. // This is no guarantee, but it's the best we can do at the moment. - referenceDpi = Screen.dpi; + referenceDpi = ScreenDPI; scaleMode = PanelScaleMode.ConstantPhysicalSize; themeUss = GetOrCreateDefaultTheme?.Invoke(); m_AtlasBlitShader = m_RuntimeShader = m_RuntimeWorldShader = null; @@ -529,6 +529,7 @@ private void OnEnable() } } + UpdateScreenDPI(); InitializeShaders(); } @@ -542,6 +543,13 @@ internal void DisposePanel() m_PanelAccess.DisposePanel(); } + private float ScreenDPI { get; set; } + + internal void UpdateScreenDPI() + { + ScreenDPI = Screen.dpi; + } + private void ApplyThemeStyleSheet(VisualElement root = null) { if (!m_PanelAccess.isInitialized) @@ -591,8 +599,9 @@ internal void ApplyPanelSettings() { Rect oldTargetRect = m_TargetRect; float oldResolvedScaling = m_ResolvedScale; + m_TargetRect = GetDisplayRect(); // Expensive to evaluate, so cache - m_ResolvedScale = ResolveScale(m_TargetRect, Screen.dpi); // dpi should be constant across all displays + m_ResolvedScale = ResolveScale(m_TargetRect, ScreenDPI); // dpi should be constant across all displays if (visualTree.style.width.value == 0 || // TODO is this check valid? This prevents having to resize the game view! m_ResolvedScale != oldResolvedScaling || diff --git a/ModuleOverrides/com.unity.ui/Core/GameObjects/UIDocument.cs b/ModuleOverrides/com.unity.ui/Core/GameObjects/UIDocument.cs index 9ca863102b..2722285fb4 100644 --- a/ModuleOverrides/com.unity.ui/Core/GameObjects/UIDocument.cs +++ b/ModuleOverrides/com.unity.ui/Core/GameObjects/UIDocument.cs @@ -578,6 +578,16 @@ internal void ReactToHierarchyChanged() SetupRootClassList(); } + + + private void OnGUI() + { + if (m_PanelSettings != null) + { + m_PanelSettings.UpdateScreenDPI(); + } + } + private void SetupVisualTreeAssetTracker() { if (m_RootVisualElement == null) diff --git a/ModuleOverrides/com.unity.ui/Editor/Debugger/StylesDebugger.cs b/ModuleOverrides/com.unity.ui/Editor/Debugger/StylesDebugger.cs index 351241079c..cd346379c0 100644 --- a/ModuleOverrides/com.unity.ui/Editor/Debugger/StylesDebugger.cs +++ b/ModuleOverrides/com.unity.ui/Editor/Debugger/StylesDebugger.cs @@ -172,6 +172,8 @@ private void DrawProperties() textElement.text = EditorGUILayout.TextField("Text", textElement.text); } + m_SelectedElement.viewDataKey = EditorGUILayout.TextField("View Data Key", m_SelectedElement.viewDataKey); + m_SelectedElement.pickingMode = (PickingMode)EditorGUILayout.EnumPopup("Picking Mode", m_SelectedElement.pickingMode); if (m_SelectedElement.pseudoStates != 0) @@ -185,6 +187,12 @@ private void DrawProperties() EditorGUILayout.LabelField("Focusable", m_SelectedElement.focusable.ToString()); + if (m_SelectedElement is IBindable bindableElement) + { + using (new EditorGUI.DisabledScope(true)) + bindableElement.bindingPath = EditorGUILayout.TextField("Binding Path", bindableElement.bindingPath); + } + m_SelectedElement.usageHints = (UsageHints)EditorGUILayout.EnumFlagsField("Usage Hints", m_SelectedElement.usageHints); EditorGUILayout.LabelField("Layout", m_SelectedElement.layout.ToString()); diff --git a/Modules/PackageManagerUI/Editor/External/EditorGameServiceExtension.cs b/Modules/PackageManagerUI/Editor/External/EditorGameServiceExtension.cs index 6c06feeaea..47dd733418 100644 --- a/Modules/PackageManagerUI/Editor/External/EditorGameServiceExtension.cs +++ b/Modules/PackageManagerUI/Editor/External/EditorGameServiceExtension.cs @@ -212,25 +212,32 @@ public static string GetDashboardUrl(IPackageVersion version) if (editorGameService.ContainsKey(k_ProjectDashboardUrlType)) { var dashboardUrlType = editorGameService[k_ProjectDashboardUrlType] as string; + string formattedDashboardUrl = ""; switch (dashboardUrlType) { case k_OrganizationKeyAndProjectGuid: - if (!string.IsNullOrEmpty(cloudProjectSettings.projectId) && - !string.IsNullOrEmpty(cloudProjectSettings.organizationKey)) + if (ProjectSettingsHasProjectId() && + ProjectSettingsHasOrgKey() && + TryFormatDashboardUrl(dashboardUrl, version, out formattedDashboardUrl, + cloudProjectSettings.organizationKey, cloudProjectSettings.projectId)) { - return string.Format(dashboardUrl, cloudProjectSettings.organizationKey, cloudProjectSettings.projectId); + return formattedDashboardUrl; } break; case k_OrganizationKey: - if (!string.IsNullOrEmpty(cloudProjectSettings.organizationKey)) + if (ProjectSettingsHasOrgKey() && + TryFormatDashboardUrl(dashboardUrl, version, out formattedDashboardUrl, + cloudProjectSettings.organizationKey)) { - return string.Format(dashboardUrl, cloudProjectSettings.organizationKey); + return formattedDashboardUrl; } break; case k_ProjectGuid: - if (!string.IsNullOrEmpty(cloudProjectSettings.projectId)) + if (ProjectSettingsHasProjectId() && + TryFormatDashboardUrl(dashboardUrl, version, out formattedDashboardUrl, + cloudProjectSettings.projectId)) { - return string.Format(dashboardUrl, cloudProjectSettings.projectId); + return formattedDashboardUrl; } break; } @@ -251,5 +258,47 @@ public static string GetUseCasesUrl(IPackageVersion version) return string.Empty; return editorGameService[k_UseCasesUrl] as string; } + + private static bool ProjectSettingsHasProjectId() + { + return !string.IsNullOrEmpty(cloudProjectSettings.projectId); + } + + private static bool ProjectSettingsHasOrgKey() + { + return !string.IsNullOrEmpty(cloudProjectSettings.organizationKey); + } + + /// + /// Tries to format the dashboard url while providing exception checks + /// + /// The string to format + /// IPackageVersion contaning package information + /// The result string + /// All the arguments to format into the string + /// False when generating an exception, True otherwise + private static bool TryFormatDashboardUrl(string url, IPackageVersion packageVersion, out string result, + params object[] input) + { + result = ""; + + try + { + result = string.Format(url, input); + } + catch (FormatException e) + { + Debug.LogWarning("The projectDashboardUrl in package " + packageVersion.name + " does not follow a" + + " valid format. " + e); + return false; + } + catch (Exception e) + { + Debug.LogError(e); + return false; + } + + return true; + } } } diff --git a/Modules/PackageManagerUI/Editor/Services/Upm/UpmVersionList.cs b/Modules/PackageManagerUI/Editor/Services/Upm/UpmVersionList.cs index 80d07af9dd..b57e9ca382 100644 --- a/Modules/PackageManagerUI/Editor/Services/Upm/UpmVersionList.cs +++ b/Modules/PackageManagerUI/Editor/Services/Upm/UpmVersionList.cs @@ -258,7 +258,10 @@ public UpmVersionList(PackageInfo searchInfo, PackageInfo installedInfo, bool is AddInstalledVersion(new UpmPackageVersion(installedInfo, true, isUnityPackage)); } m_InstalledIndex = m_Versions.FindIndex(v => v.isInstalled); - SetLifecycleVersions(mainInfo?.unityLifecycle?.version, mainInfo?.unityLifecycle?.nextVersion); + var recommendedVersion = mainInfo?.unityLifecycle?.recommendedVersion; + if (String.IsNullOrEmpty(recommendedVersion)) + recommendedVersion = mainInfo?.unityLifecycle?.version; + SetLifecycleVersions(recommendedVersion, mainInfo?.unityLifecycle?.nextVersion); UpdateExtraPackageInfos(extraVersions, isUnityPackage); } diff --git a/Modules/Properties/Runtime/Utility/TypeConversion.cs b/Modules/Properties/Runtime/Utility/TypeConversion.cs index 6ed1e2ee53..c7c5aed4c9 100644 --- a/Modules/Properties/Runtime/Utility/TypeConversion.cs +++ b/Modules/Properties/Runtime/Utility/TypeConversion.cs @@ -267,7 +267,8 @@ static bool TryConvertToUnityEngineObject(TSource source, return false; } - if (typeof(UnityEngine.Object).IsAssignableFrom(typeof(TSource))) + if (typeof(UnityEngine.Object).IsAssignableFrom(typeof(TSource)) || + source is UnityEngine.Object) { if (null == source) { diff --git a/README.md b/README.md index aeacd3e82d..2c1ba3a43c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -## Unity 2022.2.0a18 C# reference source code +## Unity 2022.2.0b1 C# reference source code The C# part of the Unity engine and editor source code. May be used for reference purposes only. diff --git a/Runtime/Export/Application/Application.bindings.cs b/Runtime/Export/Application/Application.bindings.cs index 9c0111f586..ae9d283525 100644 --- a/Runtime/Export/Application/Application.bindings.cs +++ b/Runtime/Export/Application/Application.bindings.cs @@ -58,6 +58,10 @@ extern public static bool isLoadingLevel get; } + // for testing + [FreeFunction("UpdateMemoryUsage")] + internal static extern void SimulateMemoryUsage(ApplicationMemoryUsage usage); + [Obsolete("Streaming was a Unity Web Player feature, and is removed. This function is deprecated and always returns 1.0 for valid level indices.")] public static float GetStreamProgressForLevel(int levelIndex) { diff --git a/Runtime/Export/Application/Application.cs b/Runtime/Export/Application/Application.cs index 04fa1d3cdb..70b5817802 100644 --- a/Runtime/Export/Application/Application.cs +++ b/Runtime/Export/Application/Application.cs @@ -10,6 +10,28 @@ namespace UnityEngine { + public enum ApplicationMemoryUsage + { + Unknown = 0, + Low = 1, + Medium = 2, + High = 3, + Critical = 4, + } + + public struct ApplicationMemoryUsageChange + { + public ApplicationMemoryUsage memoryUsage { get; private set; } + + public ApplicationMemoryUsageChange(ApplicationMemoryUsage usage) + { + memoryUsage = usage; + } + + // If platform specific info is required, implement something like this, don't add per-platform items + // public T GetDetails() where T : PlatformMemoryUsageDetails + } + public enum StackTraceLogType { None, @@ -74,9 +96,32 @@ partial class Application public delegate void LowMemoryCallback(); public static event LowMemoryCallback lowMemory; + public delegate void MemoryUsageChangedCallback(in ApplicationMemoryUsageChange usage); + public static event MemoryUsageChangedCallback memoryUsageChanged; + [RequiredByNativeCode] - internal static void CallLowMemory() + internal static void CallLowMemory(ApplicationMemoryUsage usage) { + var onChanged = memoryUsageChanged; + if (onChanged != null) + { + var change = new ApplicationMemoryUsageChange(usage); + onChanged(change); + } + + switch (usage) + { + case ApplicationMemoryUsage.Unknown: + case ApplicationMemoryUsage.Low: + case ApplicationMemoryUsage.Medium: + case ApplicationMemoryUsage.High: + return; + case ApplicationMemoryUsage.Critical: + break; + default: + throw new Exception($"Unknown application memory usage: {usage}"); + } + var handler = lowMemory; if (handler != null) handler(); diff --git a/Runtime/Export/AssemblyInfo.cs b/Runtime/Export/AssemblyInfo.cs index 1aae44113a..a2ca83df5c 100644 --- a/Runtime/Export/AssemblyInfo.cs +++ b/Runtime/Export/AssemblyInfo.cs @@ -100,6 +100,7 @@ [assembly: InternalsVisibleTo("Unity.Collections")] [assembly: InternalsVisibleTo("Unity.Runtime")] [assembly: InternalsVisibleTo("Unity.Core")] +[assembly: InternalsVisibleTo("UnityEngine.Core.Runtime.Tests")] [assembly: InternalsVisibleTo("Unity.InternalAPIEngineBridge.001")] [assembly: InternalsVisibleTo("Unity.InternalAPIEngineBridge.002")] diff --git a/Runtime/Export/Camera/Camera.bindings.cs b/Runtime/Export/Camera/Camera.bindings.cs index 5d8fc0d4e1..8b8ebffe36 100644 --- a/Runtime/Export/Camera/Camera.bindings.cs +++ b/Runtime/Export/Camera/Camera.bindings.cs @@ -326,6 +326,7 @@ public bool RenderToCubemap(RenderTexture cubemap, int faceMask, MonoOrStereosco return RenderToCubemapEyeImpl(cubemap, faceMask, stereoEye); } + [Obsolete("The RenderRequest struct is obsolete, use the function overload with RequestData of supported types such as RenderPipeline.StandardRequest", true)] public enum RenderRequestMode { None = 0, @@ -344,7 +345,7 @@ public enum RenderRequestMode DiffuseColor = 13 } - + [Obsolete("The RenderRequest struct is obsolete, use the function overload with RequestData of supported types such as RenderPipeline.StandardRequest", true)] public enum RenderRequestOutputSpace { ScreenSpace = -1, @@ -359,6 +360,7 @@ public enum RenderRequestOutputSpace UV8 = 8, } + [Obsolete("The RenderRequest struct is obsolete, use the function overload with RequestData of supported types such as RenderPipeline.StandardRequest", true)] public struct RenderRequest { readonly RenderRequestMode m_CameraRenderMode; @@ -391,6 +393,7 @@ public RenderRequest(RenderRequestMode mode, RenderRequestOutputSpace space, Ren [FreeFunction("CameraScripting::RenderWithShader", HasExplicitThis = true)] extern public void RenderWithShader(Shader shader, string replacementTag); [FreeFunction("CameraScripting::RenderDontRestore", HasExplicitThis = true)] extern public void RenderDontRestore(); + [Obsolete("SubmitRenderRequests is obsolete, use SubmitRenderRequest with RequestData of supported types such as RenderPipeline.StandardRequest", true)] public void SubmitRenderRequests(List renderRequests) { if (renderRequests == null || renderRequests.Count == 0) @@ -404,6 +407,19 @@ public void SubmitRenderRequests(List renderRequests) SubmitRenderRequestsInternal(renderRequests); } + public void SubmitRenderRequest(RequestData renderRequest) + { + if (renderRequest == null) + throw new ArgumentException($"{nameof(SubmitRenderRequests)} is invoked with invalid renderRequests"); + + if (GraphicsSettings.currentRenderPipeline == null) + { + Debug.LogWarning("Trying to invoke 'SubmitRenderRequests' when no SRP is set. A scriptable render pipeline is needed for this function call"); + return; + } + SubmitRenderRequestsInternal(renderRequest); + } + [FreeFunction("CameraScripting::SubmitRenderRequests", HasExplicitThis = true)] extern private void SubmitRenderRequestsInternal(object requests); [FreeFunction("CameraScripting::SetupCurrent")] extern public static void SetupCurrent(Camera cur); [FreeFunction("CameraScripting::CopyFrom", HasExplicitThis = true)] extern public void CopyFrom(Camera other); diff --git a/Runtime/Export/Device/Application.cs b/Runtime/Export/Device/Application.cs index 6339c7dcc0..956b74fa34 100644 --- a/Runtime/Export/Device/Application.cs +++ b/Runtime/Export/Device/Application.cs @@ -83,6 +83,12 @@ public static event UnityEngine.Application.LowMemoryCallback lowMemory remove => ShimManager.applicationShim.lowMemory -= value; } + public static event UnityEngine.Application.MemoryUsageChangedCallback memoryUsageChanged + { + add => ShimManager.applicationShim.memoryUsageChanged += value; + remove => ShimManager.applicationShim.memoryUsageChanged -= value; + } + public static event UnityAction onBeforeRender { add => ShimManager.applicationShim.onBeforeRender += value; diff --git a/Runtime/Export/RenderPipeline/RenderPipeline.cs b/Runtime/Export/RenderPipeline/RenderPipeline.cs index 4f08b4cd21..5524d3f17d 100644 --- a/Runtime/Export/RenderPipeline/RenderPipeline.cs +++ b/Runtime/Export/RenderPipeline/RenderPipeline.cs @@ -10,7 +10,8 @@ namespace UnityEngine.Rendering public abstract class RenderPipeline { protected abstract void Render(ScriptableRenderContext context, Camera[] cameras); - protected virtual void ProcessRenderRequests(ScriptableRenderContext context, Camera camera, List renderRequests) {} + protected virtual void ProcessRenderRequests(ScriptableRenderContext context, Camera camera, RequestData renderRequest) { } + protected virtual bool IsRenderRequestSupported(Camera camera, RequestData data) { return false; } protected static void BeginFrameRendering(ScriptableRenderContext context, Camera[] cameras) { @@ -55,12 +56,39 @@ internal void InternalRender(ScriptableRenderContext context, List camer Render(context, cameras); } - internal void InternalRenderWithRequests(ScriptableRenderContext context, List cameras, List renderRequests) + internal void InternalProcessRenderRequests(ScriptableRenderContext context, Camera camera, RequestData renderRequest) { if (disposed) throw new ObjectDisposedException(string.Format("{0} has been disposed. Do not call Render on disposed a RenderPipeline.", this)); - ProcessRenderRequests(context, (cameras == null || cameras.Count == 0) ? null : cameras[0], renderRequests); + ProcessRenderRequests(context, camera, renderRequest); + } + + public static bool SupportsRenderRequest(Camera camera, RequestData data) + { + if (GraphicsSettings.currentRenderPipeline != null) + { + if(RenderPipelineManager.currentPipeline == null) + { + RenderPipelineManager.PrepareRenderPipeline(GraphicsSettings.currentRenderPipeline); + } + return RenderPipelineManager.currentPipeline.IsRenderRequestSupported(camera, data); + } + + return false; + } + + public static void SubmitRenderRequest(Camera camera, RequestData data) + { + camera.SubmitRenderRequest(data); + } + + public class StandardRequest + { + public RenderTexture destination = null; + public int mipLevel = 0; + public CubemapFace face = CubemapFace.Unknown; + public int slice = 0; } public bool disposed { get; private set; } diff --git a/Runtime/Export/RenderPipeline/RenderPipelineManager.cs b/Runtime/Export/RenderPipeline/RenderPipelineManager.cs index 35bce73849..88e4da8970 100644 --- a/Runtime/Export/RenderPipeline/RenderPipelineManager.cs +++ b/Runtime/Export/RenderPipeline/RenderPipelineManager.cs @@ -106,7 +106,7 @@ static string GetCurrentPipelineAssetType() } [RequiredByNativeCode] - static void DoRenderLoop_Internal(RenderPipelineAsset pipe, IntPtr loopPtr, List renderRequests, AtomicSafetyHandle safety) + static void DoRenderLoop_Internal(RenderPipelineAsset pipe, IntPtr loopPtr, Object renderRequest, AtomicSafetyHandle safety) { PrepareRenderPipeline(pipe); @@ -118,10 +118,10 @@ static void DoRenderLoop_Internal(RenderPipelineAsset pipe, IntPtr loopPtr, List s_Cameras.Clear(); loop.GetCameras(s_Cameras); - if (renderRequests == null) + if (renderRequest == null) currentPipeline.InternalRender(loop, s_Cameras); else - currentPipeline.InternalRenderWithRequests(loop, s_Cameras, renderRequests); + currentPipeline.InternalProcessRenderRequests(loop, s_Cameras[0], renderRequest); s_Cameras.Clear(); } diff --git a/Runtime/Export/StaticShim/ApplicationShimBase.cs b/Runtime/Export/StaticShim/ApplicationShimBase.cs index 6096815a0a..0ba6eeeb76 100644 --- a/Runtime/Export/StaticShim/ApplicationShimBase.cs +++ b/Runtime/Export/StaticShim/ApplicationShimBase.cs @@ -96,6 +96,12 @@ public virtual event UnityEngine.Application.LowMemoryCallback lowMemory remove => UnityEngine.Application.lowMemory -= value; } + public virtual event UnityEngine.Application.MemoryUsageChangedCallback memoryUsageChanged + { + add => UnityEngine.Application.memoryUsageChanged += value; + remove => UnityEngine.Application.memoryUsageChanged -= value; + } + public virtual event UnityAction onBeforeRender { add => UnityEngine.Application.onBeforeRender += value; From f49b5a61bdcb8c3bd14840c9e387bf717fda9bfc Mon Sep 17 00:00:00 2001 From: Unity Technologies Date: Fri, 22 Jul 2022 13:54:37 +0000 Subject: [PATCH 03/94] Unity 2022.2.0b2 C# reference source code --- Editor/Mono/Scripting/ScriptCompilation/EditorCompilation.cs | 1 + Editor/Mono/UIElements/Bindings/BindingsInterface.cs | 1 + .../com.unity.ui/Editor/Bindings/BindingStyleHelpers.cs | 3 ++- README.md | 2 +- 4 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Editor/Mono/Scripting/ScriptCompilation/EditorCompilation.cs b/Editor/Mono/Scripting/ScriptCompilation/EditorCompilation.cs index c802bd551a..866352a1d5 100644 --- a/Editor/Mono/Scripting/ScriptCompilation/EditorCompilation.cs +++ b/Editor/Mono/Scripting/ScriptCompilation/EditorCompilation.cs @@ -1733,6 +1733,7 @@ public ScriptAssembly CreateScriptAssembly(AssemblyBuilder assemblyBuilder) if (assemblyBuilder.compilerOptions.RoslynAnalyzerDllPaths != null) scriptAssembly.CompilerOptions.RoslynAnalyzerDllPaths = assemblyBuilder.compilerOptions.RoslynAnalyzerDllPaths .Concat(scriptAssembly.CompilerOptions.RoslynAnalyzerDllPaths) + .Distinct() .ToArray(); if (!string.IsNullOrEmpty(assemblyBuilder.compilerOptions.RoslynAnalyzerRulesetPath)) diff --git a/Editor/Mono/UIElements/Bindings/BindingsInterface.cs b/Editor/Mono/UIElements/Bindings/BindingsInterface.cs index fe9ce068f9..4e525e359c 100644 --- a/Editor/Mono/UIElements/Bindings/BindingsInterface.cs +++ b/Editor/Mono/UIElements/Bindings/BindingsInterface.cs @@ -37,6 +37,7 @@ public static class BindingExtensions internal static readonly string prefabOverrideBarName = "unity-binding-prefab-override-bar"; internal static readonly string prefabOverrideBarContainerName = "unity-prefab-override-bars-container"; internal static readonly string prefabOverrideBarUssClassName = "unity-binding__prefab-override-bar"; + internal static readonly string prefabOverrideBarNotApplicableUssClassName = "unity-binding__prefab-override-bar-not-applicable"; internal static readonly string animationAnimatedUssClassName = "unity-binding--animation-animated"; internal static readonly string animationRecordedUssClassName = "unity-binding--animation-recorded"; internal static readonly string animationCandidateUssClassName = "unity-binding--animation-candidate"; diff --git a/ModuleOverrides/com.unity.ui/Editor/Bindings/BindingStyleHelpers.cs b/ModuleOverrides/com.unity.ui/Editor/Bindings/BindingStyleHelpers.cs index 3e600fcfca..f240ed0c07 100644 --- a/ModuleOverrides/com.unity.ui/Editor/Bindings/BindingStyleHelpers.cs +++ b/ModuleOverrides/com.unity.ui/Editor/Bindings/BindingStyleHelpers.cs @@ -229,7 +229,8 @@ private static void UpdatePrefabStateStyleFromProperty(VisualElement element, Se var prefabOverrideBar = new VisualElement(); prefabOverrideBar.name = BindingExtensions.prefabOverrideBarName; prefabOverrideBar.userData = element; - prefabOverrideBar.AddToClassList(BindingExtensions.prefabOverrideBarUssClassName); + string ussClass = PrefabUtility.CanPropertyBeAppliedToSource(prop) ? BindingExtensions.prefabOverrideBarUssClassName : BindingExtensions.prefabOverrideBarNotApplicableUssClassName; + prefabOverrideBar.AddToClassList(ussClass); barContainer.Add(prefabOverrideBar); element.SetProperty(BindingExtensions.prefabOverrideBarName, prefabOverrideBar); diff --git a/README.md b/README.md index 2c1ba3a43c..d6f0b38a07 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -## Unity 2022.2.0b1 C# reference source code +## Unity 2022.2.0b2 C# reference source code The C# part of the Unity engine and editor source code. May be used for reference purposes only. From 7e95e36dc923ef6dd576a6dc183c8969a7dc05e2 Mon Sep 17 00:00:00 2001 From: Unity Technologies Date: Thu, 28 Jul 2022 17:18:44 +0000 Subject: [PATCH 04/94] Unity 2022.2.0b3 C# reference source code --- .../IHVImageFormatImporter.bindings.cs | 12 + .../AssetPipeline/TextureImporter.bindings.cs | 3 +- .../TextureImporterTypes.bindings.cs | 11 +- .../IHVImageFormatImporterInspector.cs | 7 + .../TextureImporterInspector.cs | 88 +++ .../Mono/Inspector/QualitySettingsEditor.cs | 573 +++++++++++++++++- .../PlayerSettingsEmbeddedLinux.bindings.cs | 10 + .../Builder/Inspector/BuilderInspector.cs | 11 +- .../Inspector/BuilderInspectorHeader.cs | 5 +- .../Controllers/BaseListViewController.cs | 20 +- .../Controllers/CollectionViewController.cs | 2 + .../Core/Controls/BaseListView.cs | 90 ++- .../Core/Controls/InputField/BaseField.cs | 2 +- .../Core/Style/Generated/ComputedStyle.cs | 14 +- .../Core/Style/Generated/InlineStyleAccess.cs | 2 +- .../Core/Style/StylePropertyUtil.cs | 7 + .../Bindings/EditorListViewController.cs | 16 +- .../Editor/Bindings/ListViewBindings.cs | 27 +- .../Editor/Debugger/StylePropertyDebugger.cs | 124 ++-- Projects/CSharp/UnityEngine.csproj | 3 + README.md | 2 +- Runtime/Export/AssemblyInfo.cs | 1 + Runtime/Export/Debug/Debug.bindings.cs | 5 + Runtime/Export/Graphics/GraphicsEnums.cs | 10 +- .../Graphics/GraphicsManagers.bindings.cs | 37 +- Runtime/Export/Graphics/Texture.bindings.cs | 21 +- Runtime/Export/Graphics/Texture.cs | 47 +- Runtime/Export/Hmi/HmiPlatform.bindings.cs | 11 + 28 files changed, 1032 insertions(+), 129 deletions(-) create mode 100644 Runtime/Export/Hmi/HmiPlatform.bindings.cs diff --git a/Editor/Mono/AssetPipeline/IHVImageFormatImporter.bindings.cs b/Editor/Mono/AssetPipeline/IHVImageFormatImporter.bindings.cs index 76a0e47420..7b2efea6b6 100644 --- a/Editor/Mono/AssetPipeline/IHVImageFormatImporter.bindings.cs +++ b/Editor/Mono/AssetPipeline/IHVImageFormatImporter.bindings.cs @@ -74,5 +74,17 @@ public extern int streamingMipmapsPriority get; set; } + + public extern bool ignoreMipmapLimit + { + get; + set; + } + + public extern string mipmapLimitGroupName + { + get; + set; + } } } diff --git a/Editor/Mono/AssetPipeline/TextureImporter.bindings.cs b/Editor/Mono/AssetPipeline/TextureImporter.bindings.cs index 5e35aa50f7..c6b4e4d723 100644 --- a/Editor/Mono/AssetPipeline/TextureImporter.bindings.cs +++ b/Editor/Mono/AssetPipeline/TextureImporter.bindings.cs @@ -251,7 +251,8 @@ public static bool IsDefaultPlatformTextureFormatValid(TextureImporterType textu [NativeProperty("VTOnly")] public extern bool vtOnly { get; set; } - internal extern bool ignoreMasterTextureLimit { get; set; } + public extern bool ignoreMipmapLimit { get; set; } + public extern string mipmapLimitGroupName { get; set; } // Generate mip maps for the texture? public extern bool mipmapEnabled { get; set; } diff --git a/Editor/Mono/AssetPipeline/TextureImporterTypes.bindings.cs b/Editor/Mono/AssetPipeline/TextureImporterTypes.bindings.cs index b3cb72d8c6..73ae6c0280 100644 --- a/Editor/Mono/AssetPipeline/TextureImporterTypes.bindings.cs +++ b/Editor/Mono/AssetPipeline/TextureImporterTypes.bindings.cs @@ -6,6 +6,7 @@ using System.Runtime.InteropServices; using UnityEngine; using UnityEngine.Bindings; +using UnityEngine.Serialization; namespace UnityEditor { @@ -70,8 +71,8 @@ public sealed class TextureImporterSettings [SerializeField] int m_VTOnly; - [SerializeField] - int m_IgnoreMasterTextureLimit; + [SerializeField, FormerlySerializedAs("m_IgnoreMasterTextureLimit")] + int m_IgnoreMipmapLimit; [SerializeField] int m_NPOTScale; @@ -344,10 +345,10 @@ public bool vtOnly get { return m_VTOnly != 0; } set { m_VTOnly = value ? 1 : 0; } } - internal bool ignoreMasterTextureLimit + public bool ignoreMipmapLimit { - get { return m_IgnoreMasterTextureLimit != 0; } - set { m_IgnoreMasterTextureLimit = value ? 1 : 0; } + get { return m_IgnoreMipmapLimit != 0; } + set { m_IgnoreMipmapLimit = value ? 1 : 0; } } public TextureImporterNPOTScale npotScale diff --git a/Editor/Mono/ImportSettings/IHVImageFormatImporterInspector.cs b/Editor/Mono/ImportSettings/IHVImageFormatImporterInspector.cs index 384c5b7040..6735c17c46 100644 --- a/Editor/Mono/ImportSettings/IHVImageFormatImporterInspector.cs +++ b/Editor/Mono/ImportSettings/IHVImageFormatImporterInspector.cs @@ -21,6 +21,8 @@ internal class IHVImageFormatImporterInspector : AssetImporterEditor SerializedProperty m_WrapW; SerializedProperty m_StreamingMipmaps; SerializedProperty m_StreamingMipmapsPriority; + SerializedProperty m_IgnoreMipmapLimit; + SerializedProperty m_MipmapLimitGroupName; internal class Styles { @@ -51,6 +53,8 @@ public override void OnEnable() m_WrapW = serializedObject.FindProperty("m_TextureSettings.m_WrapW"); m_StreamingMipmaps = serializedObject.FindProperty("m_StreamingMipmaps"); m_StreamingMipmapsPriority = serializedObject.FindProperty("m_StreamingMipmapsPriority"); + m_IgnoreMipmapLimit = serializedObject.FindProperty("m_IgnoreMipmapLimit"); + m_MipmapLimitGroupName = serializedObject.FindProperty("m_MipmapLimitGroupName"); } // alas it looks impossible to share code so we copy paste from TextureImporterInspector.TextureSettingsGUI() @@ -107,6 +111,9 @@ public override void OnInspectorGUI() EditorGUILayout.PropertyField(m_StreamingMipmapsPriority, Styles.streamingMipmapsPriority); EditorGUI.indentLevel--; } + + TextureImporterInspector.DoMipmapLimitsGUI(m_IgnoreMipmapLimit, m_MipmapLimitGroupName); + serializedObject.ApplyModifiedProperties(); ApplyRevertGUI(); } diff --git a/Editor/Mono/ImportSettings/TextureImporterInspector.cs b/Editor/Mono/ImportSettings/TextureImporterInspector.cs index 7c51819e51..787e9d7230 100644 --- a/Editor/Mono/ImportSettings/TextureImporterInspector.cs +++ b/Editor/Mono/ImportSettings/TextureImporterInspector.cs @@ -277,6 +277,9 @@ internal class Styles public readonly GUIContent mipmapFadeOutToggle = EditorGUIUtility.TrTextContent("Fadeout to Gray"); public readonly GUIContent mipmapFadeOut = EditorGUIUtility.TrTextContent("Fade Range"); public readonly GUIContent readWrite = EditorGUIUtility.TrTextContent("Read/Write", "Enable to be able to access the raw pixel data from code."); + public readonly GUIContent useMipmapLimits = EditorGUIUtility.TrTextContent("Use Mipmap Limits", "Disable this if the number of mips to upload should not be limited by the quality settings. (effectively: always upload at full resolution, regardless of the global mipmap limit or mipmap limit groups)"); + public readonly GUIContent mipmapLimitGroupName = EditorGUIUtility.TrTextContent("Mipmap Limit Group", "Select a Mipmap Limit Group for this texture. If you do not add this texture to a Mipmap Limit Group, or Unity cannot find the group name you provide, Unity limits the number of mips it uploads to the maximum defined by the Global Texture Mipmap Limit (see Quality Settings). If Unity can find the Mipmap Limit Group you specify, it respects that group's limit."); + public readonly GUIContent mipmapLimitGroupWarning = EditorGUIUtility.TrTextContent("This texture takes the default mipmap limit settings because Unity cannot find the mipmap limit group you have designated. Consult your project's Quality Settings for a list of mipmap limit groups."); public readonly GUIContent streamingMipmaps = EditorGUIUtility.TrTextContent("Mip Streaming", "Only load larger mipmaps as needed to render the current game cameras. Requires texture streaming to be enabled in quality settings."); public readonly GUIContent streamingMipmapsPriority = EditorGUIUtility.TrTextContent("Priority", "Mipmap streaming priority when there's contention for resources. Positive numbers represent higher priority. Valid range is -128 to 127."); public readonly GUIContent vtOnly = EditorGUIUtility.TrTextContent("Virtual Texture Only", "Texture is optimized for use as a virtual texture and can only be used as a virtual texture."); @@ -432,6 +435,8 @@ void EnumPopup(SerializedProperty property, System.Type type, GUIContent label) SerializedProperty m_IsReadable; SerializedProperty m_StreamingMipmaps; SerializedProperty m_StreamingMipmapsPriority; + SerializedProperty m_IgnoreMipmapLimit; + SerializedProperty m_MipmapLimitGroupName; SerializedProperty m_VTOnly; SerializedProperty m_sRGBTexture; @@ -499,6 +504,8 @@ void CacheSerializedProperties() m_IsReadable = serializedObject.FindProperty("m_IsReadable"); m_StreamingMipmaps = serializedObject.FindProperty("m_StreamingMipmaps"); m_StreamingMipmapsPriority = serializedObject.FindProperty("m_StreamingMipmapsPriority"); + m_IgnoreMipmapLimit = serializedObject.FindProperty("m_IgnoreMipmapLimit"); + m_MipmapLimitGroupName = serializedObject.FindProperty("m_MipmapLimitGroupName"); m_VTOnly = serializedObject.FindProperty("m_VTOnly"); m_sRGBTexture = serializedObject.FindProperty("m_sRGBTexture"); m_EnableMipMap = serializedObject.FindProperty("m_EnableMipMap"); @@ -692,6 +699,7 @@ void SetSerializedPropertySettings(TextureImporterSettings settings) m_IsReadable.intValue = settings.readable ? 1 : 0; m_StreamingMipmaps.intValue = settings.streamingMipmaps ? 1 : 0; m_StreamingMipmapsPriority.intValue = settings.streamingMipmapsPriority; + m_IgnoreMipmapLimit.intValue = settings.ignoreMipmapLimit ? 1 : 0; m_VTOnly.intValue = settings.vtOnly ? 1 : 0; m_EnableMipMap.intValue = settings.mipmapEnabled ? 1 : 0; m_sRGBTexture.intValue = settings.sRGBTexture ? 1 : 0; @@ -775,6 +783,8 @@ internal TextureImporterSettings GetSerializedPropertySettings(TextureImporterSe settings.streamingMipmaps = m_StreamingMipmaps.intValue > 0; if (!m_StreamingMipmapsPriority.hasMultipleDifferentValues) settings.streamingMipmapsPriority = m_StreamingMipmapsPriority.intValue; + if (!m_IgnoreMipmapLimit.hasMultipleDifferentValues) + settings.ignoreMipmapLimit = m_IgnoreMipmapLimit.intValue > 0; if (!m_VTOnly.hasMultipleDifferentValues) settings.vtOnly = m_VTOnly.intValue > 0; @@ -1150,6 +1160,79 @@ private void SpriteGUI(TextureInspectorGUIElement guiElements) EditorGUI.indentLevel--; } + internal static void DoMipmapLimitsGUI(SerializedProperty ignoreMipmapLimitProp, SerializedProperty groupNameProp) + { + if (s_Styles == null) + s_Styles = new Styles(); + + // The property itself is "ignoreMipmapLimit". However, in the UI, we display it + // as "Use Mipmap Limits" as it makes more sense to hide the group names dropdown + // when "Use Mipmap Limits" is toggled off rather than when "Ignore Mipmap Limit" + // is toggled on. + Rect rect = EditorGUILayout.GetControlRect(true, EditorGUI.kSingleLineHeight); + GUIContent label = EditorGUI.BeginProperty(rect, s_Styles.useMipmapLimits, ignoreMipmapLimitProp); + bool useMipmapLimits = ignoreMipmapLimitProp.propertyType == SerializedPropertyType.Integer ? ignoreMipmapLimitProp.intValue == 0 : !ignoreMipmapLimitProp.boolValue; + using (var changed = new EditorGUI.ChangeCheckScope()) + { + useMipmapLimits = EditorGUI.Toggle(rect, label, useMipmapLimits); + if (changed.changed) + { + if (ignoreMipmapLimitProp.propertyType == SerializedPropertyType.Integer) + { + ignoreMipmapLimitProp.intValue = useMipmapLimits ? 0 : 1; + } + else + { + ignoreMipmapLimitProp.boolValue = !useMipmapLimits; + } + } + } + EditorGUI.EndProperty(); + + if (useMipmapLimits) + { + using (new EditorGUI.IndentLevelScope()) + { + List options = new List(); + options.Add(L10n.Tr("None (Use Global Mipmap Limit)")); + + // Add all known groups + var groupNames = TextureMipmapLimitGroups.GetGroups(); + if (groupNames.Length > 0) + { + options.Add(string.Empty); // Separator + options.AddRange(groupNames); + } + + // If a group is not known, make sure to add it to the options anyway + if (groupNameProp.stringValue != string.Empty && !options.Contains(groupNameProp.stringValue)) + { + options.Add(string.Empty); // Seperator + options.Add(groupNameProp.stringValue); + } + + rect = EditorGUILayout.GetControlRect(true, EditorGUI.kSingleLineHeight); + label = EditorGUI.BeginProperty(rect, s_Styles.mipmapLimitGroupName, groupNameProp); + using (var changed = new EditorGUI.ChangeCheckScope()) + { + int selectedIndex = groupNameProp.hasMultipleDifferentValues ? -1 : ((groupNameProp.stringValue == string.Empty) ? 0 : options.IndexOf(groupNameProp.stringValue)); + selectedIndex = EditorGUI.Popup(rect, label, selectedIndex, options.ToArray()); + if (changed.changed) + { + groupNameProp.stringValue = selectedIndex == 0 ? string.Empty : options[selectedIndex]; + } + } + EditorGUI.EndProperty(); + + bool displayGroupNameWarning = groupNameProp.stringValue.Length > 0 && !TextureMipmapLimitGroups.HasGroup(groupNameProp.stringValue); + if (displayGroupNameWarning) + { + EditorGUILayout.HelpBox(s_Styles.mipmapLimitGroupWarning.text, MessageType.Warning, true); + } + } + } + } + void MipMapGUI(TextureInspectorGUIElement guiElements) { ToggleFromInt(m_EnableMipMap, s_Styles.generateMipMaps); @@ -1164,6 +1247,11 @@ void MipMapGUI(TextureInspectorGUIElement guiElements) { EditorGUI.indentLevel++; + if ((TextureImporterShape)m_TextureShape.intValue == TextureImporterShape.Texture2D) + { + DoMipmapLimitsGUI(m_IgnoreMipmapLimit, m_MipmapLimitGroupName); + } + StreamingMipmapsGUI(); EditorGUILayout.Popup(m_MipMapMode, s_Styles.mipMapFilterOptions, s_Styles.mipMapFilter); diff --git a/Editor/Mono/Inspector/QualitySettingsEditor.cs b/Editor/Mono/Inspector/QualitySettingsEditor.cs index 4a9830edb9..5a920556e2 100644 --- a/Editor/Mono/Inspector/QualitySettingsEditor.cs +++ b/Editor/Mono/Inspector/QualitySettingsEditor.cs @@ -18,6 +18,41 @@ private class Content { public static readonly GUIContent kPlatformTooltip = EditorGUIUtility.TrTextContent("", "Allow quality setting on platform"); public static readonly GUIContent kAddQualityLevel = EditorGUIUtility.TrTextContent("Add Quality Level"); + + public static readonly GUIContent kGlobalTextureMipmapLimit = EditorGUIUtility.TrTextContent("Global Mipmap Limit", "The base texture quality level."); + + public static readonly GUIContent kTextureMipmapLimitGroupsHeader = EditorGUIUtility.TrTextContent("Mipmap Limit Groups", "Mipmap Limit Groups are used to control quality on a per-texture basis."); + public static readonly GUIContent[] kTextureMipmapLimitGroupsOverrideModeItems = + { + EditorGUIUtility.TrTextContent("Override Global Mipmap Limit: Full Resolution", "Global Mipmap Limit is ignored, upload at full resolution."), + EditorGUIUtility.TrTextContent("Override Global Mipmap Limit: Half Resolution", "Global Mipmap Limit is ignored, upload at half resolution."), + EditorGUIUtility.TrTextContent("Override Global Mipmap Limit: Quarter Resolution", "Global Mipmap Limit is ignored, upload at quarter resolution."), + EditorGUIUtility.TrTextContent("Override Global Mipmap Limit: Eighth Resolution", "Global Mipmap Limit is ignored, upload at eighth resolution.") + }; + public static readonly GUIContent[] kTextureMipmapLimitGroupsOffsetModeItems = + { + EditorGUIUtility.TrTextContent("Offset Global Mipmap Limit: -3", "Upload 3 mips less compared to the Global Mipmap Limit."), + EditorGUIUtility.TrTextContent("Offset Global Mipmap Limit: -2", "Upload 2 mips less compared to the Global Mipmap Limit."), + EditorGUIUtility.TrTextContent("Offset Global Mipmap Limit: -1", "Upload 1 mip less compared to the Global Mipmap Limit."), + EditorGUIUtility.TrTextContent("Use Global Mipmap Limit", "No offset or override occurs, simply use the Global Mipmap Limit. (Default)"), + EditorGUIUtility.TrTextContent("Offset Global Mipmap Limit: +1", "Upload 1 mip extra compared to the Global Mipmap Limit."), + EditorGUIUtility.TrTextContent("Offset Global Mipmap Limit: +2", "Upload 2 mips extra compared to the Global Mipmap Limit."), + EditorGUIUtility.TrTextContent("Offset Global Mipmap Limit: +3", "Upload 3 mips extra compared to the Global Mipmap Limit.") + }; + public static readonly GUIContent kTextureMipmapLimitGroupsOptions = EditorGUIUtility.TrIconContent("_Menu", "Show additional options"); + public static readonly GUIContent kTextureMipmapLimitGroupsOptionsIdentify = EditorGUIUtility.TrTextContent("Identify textures"); + public static readonly GUIContent kTextureMipmapLimitGroupsOptionsDuplicate = EditorGUIUtility.TrTextContent("Duplicate group"); + public static readonly GUIContent kTextureMipmapLimitGroupsOptionsRename = EditorGUIUtility.TrTextContent("Rename group"); + public static readonly GUIContent kTextureMipmapLimitGroupsAddButton = EditorGUIUtility.TrIconContent("Toolbar Plus", "Create a new mipmap limit group. Note that this adds a group to all quality levels, not only the active one!"); + public static readonly GUIContent kTextureMipmapLimitGroupsRemoveButton = EditorGUIUtility.TrIconContent("Toolbar Minus", "Remove mipmap limit group. Note that this removes the group from all quality levels, not only the active one!"); + + public static readonly string kTextureMipmapLimitGroupsDialogTitle = L10n.Tr("Mipmap Limit Groups"); + public static readonly string kTextureMipmapLimitGroupsDialogMessageOnRemove = L10n.Tr("Textures in your project may still be using '{0}'.\n\nSelect 'No' to remove '{0}' without modifying its associated textures. Relevant textures stay bound to '{0}' and fall back automatically to the global mipmap limit.\n\nSelect 'Yes' to remove '{0}' and reset the group property of associated textures to 'None'. This triggers a re-import and may take some time.\n\nSelect 'Cancel' if you do not wish to remove '{0}' anymore."); + public static readonly string kTextureMipmapLimitGroupsDialogMessageOnRename = L10n.Tr("Textures in your project may still be using '{0}'.\n\nSelect 'No' to rename '{0}' without modifying its associated textures. Relevant textures stay bound to '{0}' and fall back automatically to the global mipmap limit.\n\nSelect 'Yes' to rename '{0}' and update the group property of associated textures to '{1}'. This triggers a re-import and may take some time.\n\nSelect 'Cancel' if you do not wish to rename '{0}' anymore."); + public static readonly string kTextureMipmapLimitGroupsDialogMessageOnRenameFail = L10n.Tr("The mipmap limit group '{0}' already exists!\n'{1}' was not renamed."); + public static readonly string kTextureMipmapLimitGroupsDialogMessageOnUpdateAssetsError = L10n.Tr("An error occured while updating texture assets: {0}"); + public static readonly string kTextureMipmapLimitGroupsDialogMessageOnIdentifyFail = L10n.Tr("No textures are linked to the mipmap limit group '{0}'!"); + public static readonly GUIContent kStreamingMipmapsActive = EditorGUIUtility.TrTextContent("Texture Streaming", "When enabled, Unity only streams texture mipmaps relevant to the current Camera's position in a Scene. This reduces the total amount of memory Unity needs for textures. Individual textures must also have 'Streaming Mip Maps' enabled in their Import Settings."); public static readonly GUIContent kStreamingMipmapsMemoryBudget = EditorGUIUtility.TrTextContent("Memory Budget", "The amount of memory (in megabytes) to allocate for all loaded textures."); public static readonly GUIContent kStreamingMipmapsRenderersPerFrame = EditorGUIUtility.TrTextContent("Renderers Per Frame", "The number of renderers to process each frame. A lower number decreases the CPU load but delays mipmap loading."); @@ -68,10 +103,19 @@ private class Styles public static readonly GUIStyle kListOddBg = "ObjectPickerResultsEven"; public static readonly GUIStyle kDefaultDropdown = "QualitySettingsDefault"; + public static readonly GUIStyle kTextureMipmapLimitGroupsOptionsButton = new GUIStyle(EditorStyles.miniButton) { padding = new RectOffset() }; + public const int kMinToggleWidth = 15; public const int kMaxToggleWidth = 20; public const int kHeaderRowHeight = 20; public const int kLabelWidth = 80; + + public const int kTextureMipmapLimitGroupsLabelWidthOffset = -6; + public const int kTextureMipmapLimitGroupsOptionsWidth = 16; + public const int kTextureMipmapLimitGroupsOffsetTop = 2; + public const int kTextureMipmapLimitGroupsPaddingRight = 4; + + public static readonly Vector2 kTextureMipmapLimitGroupsOptionsMenuOffset = new Vector2(-134, 19); } public const int kMinAsyncRingBufferSize = 2; @@ -84,12 +128,32 @@ private class Styles private SerializedProperty m_PerPlatformDefaultQualityProperty; private List m_ValidPlatforms; + private SerializedProperty m_TextureMipmapLimitGroupNamesProperty; + private SerializedProperty m_TextureMipmapLimitGroupSettingsProperty; // Always refers to the active quality level + private ReorderableList m_TextureMipmapLimitGroupsList; + private bool m_TextureMipmapLimitGroupBeingRenamed = false; + private int m_TextureMipmapLimitGroupBeingRenamedIndex = -1; + private bool m_TextureMipmapLimitGroupsTextFieldNeedsFocus = false; + private bool m_TextureMipmapLimitGroupsRenameShowUpdatePrompt = true; + + private Editor m_PresetEditor; + private Presets.Preset m_QualitySettingsPreset; + public void OnEnable() { m_QualitySettings = new SerializedObject(target); m_QualitySettingsProperty = m_QualitySettings.FindProperty("m_QualitySettings"); m_PerPlatformDefaultQualityProperty = m_QualitySettings.FindProperty("m_PerPlatformDefaultQuality"); m_ValidPlatforms = BuildPlatforms.instance.GetValidPlatforms(); + + m_TextureMipmapLimitGroupNamesProperty = m_QualitySettings.FindProperty("m_TextureMipmapLimitGroupNames"); + m_TextureMipmapLimitGroupsList = new ReorderableList(m_QualitySettings, m_TextureMipmapLimitGroupNamesProperty, false, true, true, true); + // The ReorderableList uses the GroupNames property as an indicator for how many groups really do exist. + m_TextureMipmapLimitGroupsList.drawHeaderCallback = DrawTextureMipmapLimitGroupsHeader; + m_TextureMipmapLimitGroupsList.drawElementCallback = DrawTextureMipmapLimitGroupsElement; + m_TextureMipmapLimitGroupsList.drawFooterCallback = DrawTextureMipmapLimitGroupsFooter; + m_TextureMipmapLimitGroupsList.onAddCallback = AddTextureMipmapLimitGroup; + m_TextureMipmapLimitGroupsList.onRemoveCallback = RemoveTextureMipmapLimitGroup; } private struct QualitySetting @@ -567,7 +631,8 @@ public override void OnInspectorGUI() var shadowCascade4SplitProperty = currentSettings.FindPropertyRelative("shadowCascade4Split"); var shadowMaskUsageProperty = currentSettings.FindPropertyRelative("shadowmaskMode"); var skinWeightsProperty = currentSettings.FindPropertyRelative("skinWeights"); - var textureQualityProperty = currentSettings.FindPropertyRelative("textureQuality"); + var globalTextureMipmapLimitProperty = currentSettings.FindPropertyRelative("globalTextureMipmapLimit"); + m_TextureMipmapLimitGroupSettingsProperty = currentSettings.FindPropertyRelative("textureMipmapLimitSettings"); var anisotropicTexturesProperty = currentSettings.FindPropertyRelative("anisotropicTextures"); var antiAliasingProperty = currentSettings.FindPropertyRelative("antiAliasing"); var softParticlesProperty = currentSettings.FindPropertyRelative("softParticles"); @@ -646,7 +711,7 @@ public override void OnInspectorGUI() GUILayout.Label(EditorGUIUtility.TempContent("Textures"), EditorStyles.boldLabel); EditorGUI.BeginChangeCheck(); - EditorGUILayout.PropertyField(textureQualityProperty); + EditorGUILayout.PropertyField(globalTextureMipmapLimitProperty, Content.kGlobalTextureMipmapLimit); if (EditorGUI.EndChangeCheck() && usingSRP) { RenderPipelineManager.CleanupRenderPipeline(); @@ -655,6 +720,10 @@ public override void OnInspectorGUI() { MipStrippingHintGUI(); } + + EditorGUILayout.Space(3); + m_TextureMipmapLimitGroupsList.DoLayoutList(); + EditorGUILayout.PropertyField(anisotropicTexturesProperty); var streamingMipmapsActiveProperty = currentSettings.FindPropertyRelative("streamingMipmapsActive"); @@ -813,6 +882,506 @@ public override void OnInspectorGUI() m_QualitySettings.ApplyModifiedProperties(); } + void DrawTextureMipmapLimitGroupsHeader(Rect rect) + { + EditorGUI.PrefixLabel(rect, Content.kTextureMipmapLimitGroupsHeader); + + Event e = Event.current; + if (rect.Contains(e.mousePosition) && e.type == EventType.ContextClick) + { + GenericMenu menu = EditorGUI.FillPropertyContextMenu(m_TextureMipmapLimitGroupSettingsProperty); + + if (Presets.Preset.IsEditorTargetAPreset(target)) + { + // If we are dealing with presets, we will be able to find the "Include Property" and/or "Exclude Property" menu items. + // Our texture mipmap limit group names and group settings arrays are always separate properties entirely, which means + // that those menu items will not always function as one would expect out-of-the-box, so we apply some custom logic here. + GenericMenu.MenuItem includePropItem = menu.menuItems.Find(menu => menu.content.text == L10n.Tr("Include Property")); + GenericMenu.MenuItem excludePropItem = menu.menuItems.Find(menu => menu.content.text == L10n.Tr("Exclude Property")); + + if (m_PresetEditor is null) // Can cache the PresetEditor/preset asset since they won't change. + { + m_PresetEditor = (includePropItem?.func2?.Target ?? excludePropItem.func2.Target) as Editor; + m_QualitySettingsPreset = m_PresetEditor.target as Presets.Preset; + } + string groupSettingsArrayPropertyPath = (includePropItem?.userData ?? excludePropItem.userData) as string; + const string groupNamesArrayPropertyPath = "m_TextureMipmapLimitGroupNames"; + + // Whenever we include the group settings array of any quality level, it could be that the + // group names array is not included. (if we used "Exclude all properties", for example) + // In that case, forcefully include the group names array. (including only the settings will + // not transfer group names over and can replace the settings of unrelated groups!) + if (includePropItem is not null) + { + var includePropertyMethod = includePropItem.func2.Method; + + includePropItem.func2 = null; + includePropItem.func = () => + { + includePropertyMethod.Invoke(m_PresetEditor, new object[] { groupSettingsArrayPropertyPath }); + + if (m_QualitySettingsPreset.excludedProperties.Contains(groupNamesArrayPropertyPath)) + { + if (!m_QualitySettingsPreset.excludedProperties.Any(p => p == groupSettingsArrayPropertyPath || groupSettingsArrayPropertyPath.StartsWith(p + ".", System.StringComparison.Ordinal))) + { + // If and only if the group names array was excluded in the first place, + // and the group settings array was successfully included, then include + // the group names array too. + includePropertyMethod.Invoke(m_PresetEditor, new object[] { groupNamesArrayPropertyPath }); + } + } + }; + } + + // Depending on how many group settings arrays are still included after the + // current one gets excluded, we take different paths. + // 1 or more group settings arrays included -> group names stay included too. + // 0 group settings arrays included -> exclude group names. + if (excludePropItem is not null) + { + var excludePropertyMethod = excludePropItem.func2.Method; + + excludePropItem.func2 = null; + excludePropItem.func = () => + { + excludePropertyMethod.Invoke(m_PresetEditor, new object[] { groupSettingsArrayPropertyPath }); + + // If the group names array was included (it can be manually excluded!), + // then check if we've got all group settings arrays excluded. If that is + // the case, exclude the group names array too as described earlier. + if (!m_QualitySettingsPreset.excludedProperties.Contains(groupNamesArrayPropertyPath)) + { + bool areAllGroupSettingsExcluded = true; + int counter = 0; + while (areAllGroupSettingsExcluded && counter < m_QualitySettingsProperty.arraySize) + { + string propertyPathToCheck = $"m_QualitySettings.Array.data[{counter}].textureMipmapLimitSettings"; + if (!m_QualitySettingsPreset.excludedProperties.Any(p => p == propertyPathToCheck || propertyPathToCheck.StartsWith(p + ".", System.StringComparison.Ordinal))) + { + areAllGroupSettingsExcluded = false; + } + ++counter; + } + if (areAllGroupSettingsExcluded) + { + excludePropertyMethod.Invoke(m_PresetEditor, new object[] { groupNamesArrayPropertyPath }); + } + } + }; + } + } + + e.Use(); + menu.ShowAsContext(); + } + } + + void DrawTextureMipmapLimitGroupsElement(Rect rect, int index, bool isActive, bool isFocused) + { + rect.y += Styles.kTextureMipmapLimitGroupsOffsetTop; // Elements need to be manually centered due to the usage of Rects + Rect labelPosition = new Rect(rect.x, rect.y, EditorGUIUtility.labelWidth + Styles.kTextureMipmapLimitGroupsLabelWidthOffset, EditorGUI.lineHeight); + Rect optionsPosition = new Rect(rect.xMax - Styles.kTextureMipmapLimitGroupsOptionsWidth, rect.y, Styles.kTextureMipmapLimitGroupsOptionsWidth, rect.height); + Rect dropdownPosition = new Rect(labelPosition.xMax + Styles.kTextureMipmapLimitGroupsPaddingRight, rect.y, + rect.width - labelPosition.width - Styles.kTextureMipmapLimitGroupsPaddingRight - (optionsPosition.width + Styles.kTextureMipmapLimitGroupsPaddingRight), rect.height); + + string groupName = m_TextureMipmapLimitGroupNamesProperty.GetArrayElementAtIndex(index).stringValue; + SerializedProperty groupSettingsProp = m_TextureMipmapLimitGroupSettingsProperty.GetArrayElementAtIndex(index); + bool isOffset = groupSettingsProp.FindPropertyRelative("limitBiasMode").intValue == 0; + int mipmapLimit = groupSettingsProp.FindPropertyRelative("limitBias").intValue; + + DoTextureMipmapLimitGroupNameLabel(labelPosition, EditorGUIUtility.TempContent(groupName, L10n.Tr("Mipmap Limit Group") + $" {index}"), index, groupName); + DoTextureMipmapLimitGroupsSettingsDropdown(dropdownPosition, isOffset, mipmapLimit, index, groupName); + DoTextureMipmapLimitGroupsOptions(optionsPosition, index, groupName); + } + + void DrawTextureMipmapLimitGroupsFooter(Rect rect) + { + GUIContent toolbarPlus = ReorderableList.defaultBehaviours.iconToolbarPlus; + GUIContent toolbarMinus = ReorderableList.defaultBehaviours.iconToolbarMinus; + + // Temporarily replace tooltips + ReorderableList.defaultBehaviours.iconToolbarPlus = Content.kTextureMipmapLimitGroupsAddButton; + ReorderableList.defaultBehaviours.iconToolbarMinus = Content.kTextureMipmapLimitGroupsRemoveButton; + ReorderableList.defaultBehaviours.DrawFooter(rect, m_TextureMipmapLimitGroupsList); + ReorderableList.defaultBehaviours.iconToolbarPlus = toolbarPlus; + ReorderableList.defaultBehaviours.iconToolbarMinus = toolbarMinus; + } + + void DoTextureMipmapLimitGroupNameLabel(Rect rect, GUIContent label, int index, string groupName) + { + if (m_TextureMipmapLimitGroupBeingRenamed && index == m_TextureMipmapLimitGroupBeingRenamedIndex) + { + DoTextureMipmapLimitGroupNameTextField(rect, label); + } + else + { + GUI.Label(rect, label, EditorStyles.label); + Event e = Event.current; + if (rect.Contains(e.mousePosition) && e.type == EventType.ContextClick) + { + ShowTextureMipmapLimitGroupsContextMenu(new Rect(e.mousePosition, Vector2.zero), Vector2.zero, index, groupName); + } + } + } + + void DoTextureMipmapLimitGroupNameTextField(Rect rect, GUIContent label) + { + const string controlName = "TextFieldTextureMipmapLimitGroup"; + + using (var changed = new EditorGUI.ChangeCheckScope()) + { + GUI.SetNextControlName(controlName); + string newName = EditorGUI.DelayedTextField(rect, label.text, EditorStyles.textField); + + Event e = Event.current; + if (m_TextureMipmapLimitGroupsTextFieldNeedsFocus) + { + if (e.type == EventType.Repaint) // Wait until all other events are out of the way + { + EditorGUI.s_DelayedTextEditor.text = label.text; // Should already be the case, but just to make sure. + EditorGUI.s_DelayedTextEditor.SelectAll(); + EditorGUI.FocusTextInControl(controlName); + m_TextureMipmapLimitGroupsTextFieldNeedsFocus = false; + } + } + else + { + if (changed.changed) + { + EndRenamingTextureMipmapLimitGroup(newName); + } + // If clicking out, the rename is NOT cancelled. + else if (e.isMouse && !rect.Contains(e.mousePosition)) + { + EndRenamingTextureMipmapLimitGroup(EditorGUI.s_DelayedTextEditor.text); + } + else if (!EditorGUIUtility.editingTextField) + { + EndRenamingTextureMipmapLimitGroup(); + } + } + } + } + + void DoTextureMipmapLimitGroupsSettingsDropdown(Rect rect, bool isOffset, int mipmapLimit, int index, string groupName) + { + const int limitValueToItemsArrayIndexOffset = 3; + GUIContent content = (isOffset ? (mipmapLimit > -4 && mipmapLimit < 4) : (mipmapLimit >= 0 && mipmapLimit < 4)) // Is limit within array bounds? + ? (isOffset ? Content.kTextureMipmapLimitGroupsOffsetModeItems[mipmapLimit + limitValueToItemsArrayIndexOffset] : Content.kTextureMipmapLimitGroupsOverrideModeItems[mipmapLimit]) + : EditorGUIUtility.TrTextContent($"{(isOffset ? "Offset" : "Override")} Global Mipmap Limit: {((mipmapLimit >= 0) ? $"+{mipmapLimit}" : $"{mipmapLimit}")}", "Custom User Setting"); + + if (EditorGUI.Button(rect, content, EditorStyles.popup)) + { + m_TextureMipmapLimitGroupsList.index = index; + + GenericMenu menu = new GenericMenu(); + for (int j = 0; j < Content.kTextureMipmapLimitGroupsOffsetModeItems.Length; ++j) + { + int limitValueFromItemsArrayIndex = j - limitValueToItemsArrayIndexOffset; + menu.AddItem(Content.kTextureMipmapLimitGroupsOffsetModeItems[j], isOffset ? mipmapLimit == limitValueFromItemsArrayIndex : false, + () => SetTextureMipmapLimitGroupSettings(index, limitValueFromItemsArrayIndex, TextureMipmapLimitBiasMode.OffsetGlobalLimit)); + } + menu.AddSeparator(string.Empty); + for (int j = 0; j < Content.kTextureMipmapLimitGroupsOverrideModeItems.Length; ++j) + { + int limit = j; + menu.AddItem(Content.kTextureMipmapLimitGroupsOverrideModeItems[j], !isOffset ? mipmapLimit == limit : false, + () => SetTextureMipmapLimitGroupSettings(index, limit, TextureMipmapLimitBiasMode.OverrideGlobalLimit)); + } + + menu.DropDown(rect); + } + } + + void DoTextureMipmapLimitGroupsOptions(Rect rect, int index, string groupName) + { + if (EditorGUI.Button(rect, Content.kTextureMipmapLimitGroupsOptions, Styles.kTextureMipmapLimitGroupsOptionsButton)) + { + m_TextureMipmapLimitGroupsList.index = index; + EndRenamingTextureMipmapLimitGroup(); + + ShowTextureMipmapLimitGroupsContextMenu(rect, Styles.kTextureMipmapLimitGroupsOptionsMenuOffset, index, groupName); + } + } + + void ShowTextureMipmapLimitGroupsContextMenu(Rect rect, Vector2 offset, int index, string groupName) + { + GenericMenu menu = new GenericMenu(); + menu.AddItem(Content.kTextureMipmapLimitGroupsOptionsIdentify, false, () => IdentifyAssetsUsingTextureMipmapLimitGroup(groupName)); + menu.AddSeparator(string.Empty); + menu.AddItem(Content.kTextureMipmapLimitGroupsOptionsDuplicate, false, () => DuplicateTextureMipmapLimitGroup(index)); + menu.AddItem(Content.kTextureMipmapLimitGroupsOptionsRename, false, () => StartRenamingTextureMipmapLimitGroup(index)); + menu.DropDown(new Rect(rect.position + offset, Vector2.zero)); + } + + void AddTextureMipmapLimitGroup(ReorderableList list) + { + int newGroupIndex = (++m_TextureMipmapLimitGroupNamesProperty.arraySize) - 1; + string newGroupName = GetNewTextureMipmapLimitGroupName(); + m_TextureMipmapLimitGroupNamesProperty.GetArrayElementAtIndex(newGroupIndex).stringValue = newGroupName; + + // For all quality levels, we need to add default settings for the new group. + for (int i = 0; i < m_QualitySettingsProperty.arraySize; ++i) + { + SerializedProperty settingsArrProp = m_QualitySettingsProperty.GetArrayElementAtIndex(i).FindPropertyRelative("textureMipmapLimitSettings"); + settingsArrProp.arraySize++; + SerializedProperty settingsProp = settingsArrProp.GetArrayElementAtIndex(newGroupIndex); + settingsProp.FindPropertyRelative("limitBias").intValue = 0; + settingsProp.FindPropertyRelative("limitBiasMode").intValue = 0; + } + + m_QualitySettings.ApplyModifiedProperties(); + StartRenamingTextureMipmapLimitGroup(newGroupIndex, true); + } + + void RemoveTextureMipmapLimitGroup(ReorderableList list) + { + int indexOfGroupToRemove = list.index; + string nameOfGroupToRemove = m_TextureMipmapLimitGroupNamesProperty.GetArrayElementAtIndex(indexOfGroupToRemove).stringValue; + + bool isRemoveOperationCancelled = false; + + if (!Presets.Preset.IsEditorTargetAPreset(target)) + { + int selection = EditorUtility.DisplayDialogComplex(Content.kTextureMipmapLimitGroupsDialogTitle, + string.Format(Content.kTextureMipmapLimitGroupsDialogMessageOnRemove, nameOfGroupToRemove), + L10n.Tr("No"), L10n.Tr("Cancel"), L10n.Tr("Yes")); + + switch (selection) + { + case 2: // Yes + UpdateTextureAssetsLinkedToOldTextureMipmapLimitGroup(nameOfGroupToRemove, string.Empty); + break; + + case 0: // No + break; + + case 1: // Cancel + isRemoveOperationCancelled = true; + break; + } + } + + if (!isRemoveOperationCancelled) + { + m_TextureMipmapLimitGroupNamesProperty.DeleteArrayElementAtIndex(indexOfGroupToRemove); + + // For all quality levels, we need to remove the settings of the deleted group. + for (int i = 0; i < m_QualitySettingsProperty.arraySize; ++i) + { + SerializedProperty settingsArrProp = m_QualitySettingsProperty.GetArrayElementAtIndex(i).FindPropertyRelative("textureMipmapLimitSettings"); + settingsArrProp.DeleteArrayElementAtIndex(indexOfGroupToRemove); + } + + m_QualitySettings.ApplyModifiedProperties(); + m_TextureMipmapLimitGroupsList.m_Selection = new List(); + InspectorWindow.RepaintAllInspectors(); + } + + EndRenamingTextureMipmapLimitGroup(); + } + + void DuplicateTextureMipmapLimitGroup(int indexOfGroupToDuplicate) + { + int newGroupIndex = (++m_TextureMipmapLimitGroupNamesProperty.arraySize) - 1; + string newGroupName = GetNewTextureMipmapLimitGroupName(); + m_TextureMipmapLimitGroupNamesProperty.GetArrayElementAtIndex(newGroupIndex).stringValue = newGroupName; + + // For all quality levels, we need to duplicate the settings of the other group. + for (int i = 0; i < m_QualitySettingsProperty.arraySize; ++i) + { + SerializedProperty settingsArrProp = m_QualitySettingsProperty.GetArrayElementAtIndex(i).FindPropertyRelative("textureMipmapLimitSettings"); + settingsArrProp.arraySize++; + + SerializedProperty newSettingsProp = settingsArrProp.GetArrayElementAtIndex(newGroupIndex); + SerializedProperty settingsPropToDuplicate = settingsArrProp.GetArrayElementAtIndex(indexOfGroupToDuplicate); + newSettingsProp.FindPropertyRelative("limitBias").intValue = settingsPropToDuplicate.FindPropertyRelative("limitBias").intValue; + newSettingsProp.FindPropertyRelative("limitBiasMode").intValue = settingsPropToDuplicate.FindPropertyRelative("limitBiasMode").intValue; + } + + m_QualitySettings.ApplyModifiedProperties(); + StartRenamingTextureMipmapLimitGroup(newGroupIndex, true); + } + + void UpdateTextureAssetsLinkedToOldTextureMipmapLimitGroup(string oldName, string newName) + { + try + { + string[] guids = AssetDatabase.FindAssets("t:texture"); + AssetDatabase.StartAssetEditing(); + + for (int i = 0; i < guids.Length; ++i) + { + AssetImporter importer = AssetImporter.GetAtPath(AssetDatabase.GUIDToAssetPath(guids[i])); + + if (importer is TextureImporter) + { + TextureImporter texImporter = importer as TextureImporter; + if (texImporter.textureShape == TextureImporterShape.Texture2D && texImporter.mipmapLimitGroupName == oldName) + { + texImporter.mipmapLimitGroupName = newName; + importer.SaveAndReimport(); + } + } + else if (importer is IHVImageFormatImporter) + { + IHVImageFormatImporter ihvImporter = importer as IHVImageFormatImporter; + if (ihvImporter.mipmapLimitGroupName == oldName) + { + ihvImporter.mipmapLimitGroupName = newName; + importer.SaveAndReimport(); + } + } + } + } + catch (System.Exception e) + { + EditorUtility.DisplayDialog(Content.kTextureMipmapLimitGroupsDialogTitle, + string.Format(Content.kTextureMipmapLimitGroupsDialogMessageOnUpdateAssetsError, e.Message), + L10n.Tr("OK")); + } + finally + { + AssetDatabase.StopAssetEditing(); + } + } + + // Acts on the current quality level + void SetTextureMipmapLimitGroupSettings(int groupIndex, int mipmapLimit, TextureMipmapLimitBiasMode mode) + { + SerializedProperty settingsToModify = m_TextureMipmapLimitGroupSettingsProperty.GetArrayElementAtIndex(groupIndex); + + settingsToModify.FindPropertyRelative("limitBias").intValue = mipmapLimit; + settingsToModify.FindPropertyRelative("limitBiasMode").intValue = (int)mode; + + m_QualitySettings.ApplyModifiedProperties(); + } + + void IdentifyAssetsUsingTextureMipmapLimitGroup(string groupNameToIdentify) + { + List newSelection = new List(); + string[] guids = AssetDatabase.FindAssets("t:texture"); + + for (int i = 0; i < guids.Length; ++i) + { + TextureImporter importer = AssetImporter.GetAtPath(AssetDatabase.GUIDToAssetPath(guids[i])) as TextureImporter; + if (importer is not null && importer.textureShape == TextureImporterShape.Texture2D && importer.mipmapLimitGroupName == groupNameToIdentify) + { + newSelection.Add(AssetDatabase.LoadAssetAtPath(importer.assetPath)); + } + } + + if (newSelection.Count > 0) + { + Selection.objects = newSelection.ToArray(); + } + else + { + EditorUtility.DisplayDialog(Content.kTextureMipmapLimitGroupsDialogTitle, + string.Format(Content.kTextureMipmapLimitGroupsDialogMessageOnIdentifyFail, groupNameToIdentify), + L10n.Tr("OK")); + } + } + + void StartRenamingTextureMipmapLimitGroup(int index, bool isNewGroup = false) + { + m_TextureMipmapLimitGroupsList.index = index; + m_TextureMipmapLimitGroupsTextFieldNeedsFocus = true; + m_TextureMipmapLimitGroupBeingRenamed = true; + m_TextureMipmapLimitGroupBeingRenamedIndex = index; + m_TextureMipmapLimitGroupsRenameShowUpdatePrompt = !Presets.Preset.IsEditorTargetAPreset(target) && !isNewGroup; + + if (isNewGroup) + { + InspectorWindow.RepaintAllInspectors(); + } + } + + void EndRenamingTextureMipmapLimitGroup(string newName = "") + { + if (!m_TextureMipmapLimitGroupBeingRenamed) + { + return; + } + m_TextureMipmapLimitGroupBeingRenamed = false; + + if (newName != string.Empty) + { + string toRename = m_TextureMipmapLimitGroupNamesProperty.GetArrayElementAtIndex(m_TextureMipmapLimitGroupBeingRenamedIndex).stringValue; + if (toRename == newName) + { + return; + } + + for (int i = 0; i < m_TextureMipmapLimitGroupNamesProperty.arraySize; ++i) + { + if (m_TextureMipmapLimitGroupNamesProperty.GetArrayElementAtIndex(i).stringValue == newName) + { + EditorUtility.DisplayDialog(Content.kTextureMipmapLimitGroupsDialogTitle, + string.Format(Content.kTextureMipmapLimitGroupsDialogMessageOnRenameFail, newName, toRename), + L10n.Tr("OK")); + return; + } + } + + bool applyModifiedProperties = true; + if (m_TextureMipmapLimitGroupsRenameShowUpdatePrompt) + { + int selection = EditorUtility.DisplayDialogComplex(Content.kTextureMipmapLimitGroupsDialogTitle, + string.Format(Content.kTextureMipmapLimitGroupsDialogMessageOnRename, toRename, newName), + L10n.Tr("No"), L10n.Tr("Cancel"), L10n.Tr("Yes")); + + switch (selection) + { + case 2: // Yes + UpdateTextureAssetsLinkedToOldTextureMipmapLimitGroup(toRename, newName); + break; + + case 0: // No + break; + + case 1: // Cancel + applyModifiedProperties = false; + break; + } + } + + if (applyModifiedProperties) + { + m_TextureMipmapLimitGroupNamesProperty.GetArrayElementAtIndex(m_TextureMipmapLimitGroupBeingRenamedIndex).stringValue = newName; + m_QualitySettings.ApplyModifiedProperties(); + InspectorWindow.RepaintAllInspectors(); + } + } + + m_TextureMipmapLimitGroupBeingRenamedIndex = -1; + GUI.FocusControl(string.Empty); + } + + string GetNewTextureMipmapLimitGroupName() + { + string newName = L10n.Tr("New Group"); + string[] existingNames = GetAllKnownTextureMipmapLimitGroupNames(); + + int counter = 0; + while (existingNames.Any(existingName => existingName == newName)) + { + newName = L10n.Tr("New Group") + string.Format(" ({0})", ++counter); + } + + return newName; + } + + string[] GetAllKnownTextureMipmapLimitGroupNames() + { + string[] existingNames = new string[m_TextureMipmapLimitGroupNamesProperty.arraySize]; + for (int i = 0; i < m_TextureMipmapLimitGroupNamesProperty.arraySize; ++i) + { + existingNames[i] = m_TextureMipmapLimitGroupNamesProperty.GetArrayElementAtIndex(i).stringValue; + } + return existingNames; + } + [SettingsProvider] internal static SettingsProvider CreateProjectSettingsProvider() { diff --git a/Editor/Mono/PlayerSettingsEmbeddedLinux.bindings.cs b/Editor/Mono/PlayerSettingsEmbeddedLinux.bindings.cs index c4e8ef8f36..f1c5ad3ae2 100644 --- a/Editor/Mono/PlayerSettingsEmbeddedLinux.bindings.cs +++ b/Editor/Mono/PlayerSettingsEmbeddedLinux.bindings.cs @@ -2,6 +2,7 @@ // Copyright (c) Unity Technologies. For terms of use, see // https://unity3d.com/legal/licenses/Unity_Reference_Only_License +using UnityEngine; using UnityEngine.Bindings; using UnityObject = UnityEngine.Object; @@ -19,6 +20,15 @@ public sealed partial class EmbeddedLinux [NativeProperty("ForceSRGBBlit")] public static extern bool forceSRGBBlit { get; set; } + + [NativeProperty("EnableGamepadInput")] + public static extern bool enableGamepadInput { get; set; } + + [NativeProperty("CpuConfiguration")] + public static extern int[] cpuConfiguration { get; set; } + + [NativeProperty("HmiLoadingImage")] + public static extern Texture2D hmiLoadingImage { get; set; } } } } diff --git a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Inspector/BuilderInspector.cs b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Inspector/BuilderInspector.cs index 6f27bde4b4..721c18c825 100644 --- a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Inspector/BuilderInspector.cs +++ b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Inspector/BuilderInspector.cs @@ -60,6 +60,7 @@ struct CachedScrollPosition // Header BuilderInspectorHeader m_HeaderSection; + internal BuilderInspectorHeader headerSection => m_HeaderSection; // Sections BuilderInspectorCanvas m_CanvasSection; @@ -331,7 +332,7 @@ void ResetSections() public void UpdateFieldStatus(VisualElement field, StyleProperty property) { var valueInfo = FieldValueInfo.Get(this, field, property); - + field.SetProperty(BuilderConstants.InspectorFieldValueInfoVEPropertyName, valueInfo); UpdateFieldStatusIconAndStyling(field, valueInfo); UpdateFieldTooltip(field, valueInfo); @@ -377,7 +378,7 @@ void UpdateFieldTooltip(VisualElement field, FieldValueInfo valueInfo) } static string GetFieldStatusIndicatorTooltip(FieldValueInfo info) - { + { if (info.valueSource.type == FieldValueSourceInfoType.Default) return BuilderConstants.FieldStatusIndicatorDefaultTooltip; if (info.valueBinding.type == FieldValueBindingInfoType.USSVariable) @@ -397,7 +398,7 @@ static string GetFieldTooltip(VisualElement field, FieldValueInfo info) { if (info.type == FieldValueInfoType.None) return ""; - + var tooltipFormat = BuilderConstants.FieldTooltipWithoutValueFormatString; var valueDataText = ""; var valueDefinitionDataText = ""; @@ -412,7 +413,7 @@ static string GetFieldTooltip(VisualElement field, FieldValueInfo info) if (info.valueBinding.type == FieldValueBindingInfoType.USSVariable) valueDataText = $"\n{GetVariableTooltip(info.valueBinding.variable)}"; } - + // source if (info.valueSource.type.IsFromUSSSelector()) valueDefinitionDataText = $"\n{GetMatchingStyleSheetRuleSourceTooltip(info.valueSource.matchedRule)}"; @@ -423,7 +424,7 @@ static string GetFieldTooltip(VisualElement field, FieldValueInfo info) static string GetMatchingStyleSheetRuleSourceTooltip(MatchedRule matchedRule) { var displayPath = matchedRule.displayPath; - + // Remove line number var index = displayPath.IndexOf(':'); diff --git a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Inspector/BuilderInspectorHeader.cs b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Inspector/BuilderInspectorHeader.cs index bbccc89585..f385bda3fc 100644 --- a/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Inspector/BuilderInspectorHeader.cs +++ b/External/MirroredPackageSources/com.unity.ui.builder/Editor/Builder/Inspector/BuilderInspectorHeader.cs @@ -79,7 +79,10 @@ public void Refresh() return; } - m_Header.style.display = DisplayStyle.Flex; + if (m_Selection.selectionType == BuilderSelectionType.Nothing) + { + return; + } m_TextField.UnregisterValueChangedCallback(m_SelectorNameChangeCallback); m_TextField.UnregisterCallback(m_SelectorEnterKeyDownCallback); diff --git a/ModuleOverrides/com.unity.ui/Core/Collections/Controllers/BaseListViewController.cs b/ModuleOverrides/com.unity.ui/Core/Collections/Controllers/BaseListViewController.cs index 09840afaf6..3bf87218ed 100644 --- a/ModuleOverrides/com.unity.ui/Core/Collections/Controllers/BaseListViewController.cs +++ b/ModuleOverrides/com.unity.ui/Core/Collections/Controllers/BaseListViewController.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.Linq; using UnityEngine.Pool; namespace UnityEngine.UIElements @@ -190,6 +191,20 @@ public virtual void RemoveItems(List indices) RaiseOnSizeChanged(); } + /// + /// Removes all items from the source. + /// + public virtual void ClearItems() + { + if (itemsSource == null) + return; + EnsureItemSourceCanBeResized(); + var itemsSourceIndices = Enumerable.Range(0, itemsSource.Count - 1); + itemsSource.Clear(); + RaiseItemsRemoved(itemsSourceIndices); + RaiseOnSizeChanged(); + } + /// /// Invokes the event. /// @@ -265,7 +280,10 @@ void Swap(int lhs, int rhs) void EnsureItemSourceCanBeResized() { - if (itemsSource == null || itemsSource.IsFixedSize && !itemsSource.GetType().IsArray) + var itemsSourceType = itemsSource?.GetType(); + + var itemsSourceIsArray = itemsSourceType?.IsArray ?? false; + if (itemsSource == null || itemsSource.IsFixedSize && !itemsSourceIsArray) throw new InvalidOperationException("Cannot add or remove items from source, because it is null or its size is fixed."); } } diff --git a/ModuleOverrides/com.unity.ui/Core/Collections/Controllers/CollectionViewController.cs b/ModuleOverrides/com.unity.ui/Core/Collections/Controllers/CollectionViewController.cs index 29eb0a605f..3d96ea70fb 100644 --- a/ModuleOverrides/com.unity.ui/Core/Collections/Controllers/CollectionViewController.cs +++ b/ModuleOverrides/com.unity.ui/Core/Collections/Controllers/CollectionViewController.cs @@ -95,6 +95,8 @@ public virtual int GetItemsCount() return m_ItemsSource?.Count ?? 0; } + internal virtual int GetItemsMinCount() => GetItemsCount(); + /// /// Returns the index for the specified id. /// diff --git a/ModuleOverrides/com.unity.ui/Core/Controls/BaseListView.cs b/ModuleOverrides/com.unity.ui/Core/Controls/BaseListView.cs index 7e41adc0f9..6554b04091 100644 --- a/ModuleOverrides/com.unity.ui/Core/Controls/BaseListView.cs +++ b/ModuleOverrides/com.unity.ui/Core/Controls/BaseListView.cs @@ -149,7 +149,7 @@ public bool showFoldoutHeader } SetupArraySizeField(); - UpdateEmpty(); + UpdateListViewLabel(); if (showAddRemoveFooter) { @@ -269,6 +269,9 @@ private void RemoveItems(List indices) void OnArraySizeFieldChanged(ChangeEvent evt) { + if (m_ArraySizeField.showMixedValue && TextInputBaseField.mixedValueString == evt.newValue) + return; + if (!int.TryParse(evt.newValue, out var value) || value < 0) { m_ArraySizeField.SetValueWithoutNotify(evt.previousValue); @@ -276,6 +279,10 @@ void OnArraySizeFieldChanged(ChangeEvent evt) } var count = viewController.GetItemsCount(); + + if (count == 0 && value == viewController.GetItemsMinCount()) + return; + if (value > count) { viewController.AddItems(value - count); @@ -288,42 +295,61 @@ void OnArraySizeFieldChanged(ChangeEvent evt) viewController.RemoveItem(i); } } + else if (value == 0) + { + // Special case: list view cannot show arrays with more than n elements (see m_IsOverMultiEditLimit) + // when multiple objects are selected (so count is already 0 even though array size field shows + // a different value) and user wants to reset the number of items for that selection + viewController.ClearItems(); + m_IsOverMultiEditLimit = false; + } + + UpdateListViewLabel(); } - void UpdateArraySizeField() + internal void UpdateArraySizeField() { - if (!HasValidDataAndBindings()) + if (!HasValidDataAndBindings() || m_ArraySizeField == null) return; - - m_ArraySizeField?.SetValueWithoutNotify(viewController.GetItemsCount().ToString()); + if (!m_ArraySizeField.showMixedValue) + m_ArraySizeField.SetValueWithoutNotify(viewController.GetItemsMinCount().ToString()); + footer?.SetEnabled(!m_IsOverMultiEditLimit); } + Label m_ListViewLabel; - Label m_EmptyListLabel; - - void UpdateEmpty() + internal void UpdateListViewLabel() { if (!HasValidDataAndBindings()) return; - if (itemsSource.Count == 0 && !sourceIncludesArraySize) - { - if (m_EmptyListLabel != null) - return; + var noItemsCount = itemsSource.Count == 0 && !sourceIncludesArraySize; - m_EmptyListLabel = new Label("List is Empty"); // TODO localize - m_EmptyListLabel.AddToClassList(emptyLabelUssClassName); - scrollView.contentViewport.Add(m_EmptyListLabel); + if (m_IsOverMultiEditLimit) + { + m_ListViewLabel ??= new Label(); + m_ListViewLabel.text = m_MaxMultiEditStr; + scrollView.contentViewport.Add(m_ListViewLabel); + } + else if (noItemsCount) + { + m_ListViewLabel ??= new Label(); + m_ListViewLabel.text = k_EmptyListStr; + scrollView.contentViewport.Add(m_ListViewLabel); } else { - m_EmptyListLabel?.RemoveFromHierarchy(); - m_EmptyListLabel = null; + m_ListViewLabel?.RemoveFromHierarchy(); + m_ListViewLabel = null; } + + m_ListViewLabel?.EnableInClassList(emptyLabelUssClassName, noItemsCount); + m_ListViewLabel?.EnableInClassList(overMaxMultiEditLimitClassName, m_IsOverMultiEditLimit); } void OnAddClicked() { AddItems(1); + if (binding == null) { SetSelection(itemsSource.Count - 1); @@ -337,6 +363,9 @@ void OnAddClicked() ScrollToItem(-1); }).ExecuteLater(100); } + + if (HasValidDataAndBindings() && m_ArraySizeField != null) + m_ArraySizeField.showMixedValue = false; } void OnRemoveClicked() @@ -351,17 +380,31 @@ void OnRemoveClicked() var index = itemsSource.Count - 1; viewController.RemoveItem(index); } + + if (HasValidDataAndBindings() && m_ArraySizeField != null) + m_ArraySizeField.showMixedValue = false; } // Foldout Header Foldout m_Foldout; TextField m_ArraySizeField; + internal TextField arraySizeField => m_ArraySizeField; + bool m_IsOverMultiEditLimit; + int m_MaxMultiEditCount; + internal void SetOverMaxMultiEditLimit(bool isOverLimit, int maxMultiEditCount) + { + m_IsOverMultiEditLimit = isOverLimit; + m_MaxMultiEditCount = maxMultiEditCount; + m_MaxMultiEditStr = $"This field cannot display arrays with more than {m_MaxMultiEditCount} elements when multiple objects are selected."; + } // Add/Remove Buttons Footer VisualElement m_Footer; Button m_AddButton; Button m_RemoveButton; + internal VisualElement footer => m_Footer; + // View Controller callbacks Action> m_ItemAddedCallback; Action> m_ItemRemovedCallback; @@ -485,6 +528,14 @@ internal override ListViewDragger CreateDragger() /// public static readonly string emptyLabelUssClassName = ussClassName + "__empty-label"; + /// + /// /// The USS class name for label displayed when ListView is trying to edit too many items. + /// + /// + /// Unity adds this USS class to the label displayed if the ListView is trying to edit too many items at once. + /// + public static readonly string overMaxMultiEditLimitClassName = ussClassName + "__over-max-multi-edit-limit-label"; + /// /// The USS class name for reorderable animated ListView elements. /// @@ -577,6 +628,9 @@ internal override ListViewDragger CreateDragger() internal static readonly string footerAddButtonName = ussClassName + "__add-button"; internal static readonly string footerRemoveButtonName = ussClassName + "__remove-button"; + string m_MaxMultiEditStr; + static readonly string k_EmptyListStr = "List is empty"; + /// /// Creates a with all default properties. The /// must all be set for the BaseListView to function properly. @@ -600,7 +654,7 @@ public BaseListView(IList itemsSource, float itemHeight = ItemHeightUnset) private protected override void PostRefresh() { UpdateArraySizeField(); - UpdateEmpty(); + UpdateListViewLabel(); base.PostRefresh(); } } diff --git a/ModuleOverrides/com.unity.ui/Core/Controls/InputField/BaseField.cs b/ModuleOverrides/com.unity.ui/Core/Controls/InputField/BaseField.cs index 690c0725a2..c9d03fb711 100644 --- a/ModuleOverrides/com.unity.ui/Core/Controls/InputField/BaseField.cs +++ b/ModuleOverrides/com.unity.ui/Core/Controls/InputField/BaseField.cs @@ -91,7 +91,7 @@ internal static List ParseChoiceList(string choicesFromBag) /// private const int kIndentPerLevel = 15; - protected static readonly string mixedValueString = "\u2014"; + protected internal static readonly string mixedValueString = "\u2014"; protected internal static readonly PropertyName serializedPropertyCopyName = "SerializedPropertyCopyName"; static CustomStyleProperty s_LabelWidthRatioProperty = new CustomStyleProperty("--unity-property-field-label-width-ratio"); diff --git a/ModuleOverrides/com.unity.ui/Core/Style/Generated/ComputedStyle.cs b/ModuleOverrides/com.unity.ui/Core/Style/Generated/ComputedStyle.cs index ff202dd414..aa70edf1c1 100644 --- a/ModuleOverrides/com.unity.ui/Core/Style/Generated/ComputedStyle.cs +++ b/ModuleOverrides/com.unity.ui/Core/Style/Generated/ComputedStyle.cs @@ -1043,7 +1043,7 @@ public void ApplyPropertyAnimation(VisualElement ve, StylePropertyId id, Length break; case StylePropertyId.FontSize: inheritedData.Write().fontSize = newValue; - ve.IncrementVersion(VersionChangeType.Layout | VersionChangeType.StyleSheet); + ve.IncrementVersion(VersionChangeType.Layout | VersionChangeType.Repaint | VersionChangeType.StyleSheet); break; case StylePropertyId.Height: layoutData.Write().height = newValue; @@ -3384,13 +3384,8 @@ public static VersionChangeType CompareChanges(ref ComputedStyle x, ref Computed changes |= VersionChangeType.Color; } - if (x.fontSize != y.fontSize || - x.whiteSpace != y.whiteSpace) - { - changes |= VersionChangeType.Layout; - } - if ((changes & (VersionChangeType.Layout | VersionChangeType.Repaint)) == 0 && (x.unityFont != y.unityFont || + x.fontSize != y.fontSize || x.unityFontDefinition != y.unityFontDefinition || x.unityFontStyleAndWeight != y.unityFontStyleAndWeight || x.unityTextOutlineWidth != y.unityTextOutlineWidth || @@ -3408,6 +3403,11 @@ public static VersionChangeType CompareChanges(ref ComputedStyle x, ref Computed { changes |= VersionChangeType.Repaint; } + + if (x.whiteSpace != y.whiteSpace) + { + changes |= VersionChangeType.Layout; + } } if (!x.transformData.ReferenceEquals(y.transformData)) diff --git a/ModuleOverrides/com.unity.ui/Core/Style/Generated/InlineStyleAccess.cs b/ModuleOverrides/com.unity.ui/Core/Style/Generated/InlineStyleAccess.cs index dc615e8736..0a5f4d8280 100644 --- a/ModuleOverrides/com.unity.ui/Core/Style/Generated/InlineStyleAccess.cs +++ b/ModuleOverrides/com.unity.ui/Core/Style/Generated/InlineStyleAccess.cs @@ -495,7 +495,7 @@ StyleLength IStyle.fontSize { if (SetStyleValue(StylePropertyId.FontSize, value)) { - ve.IncrementVersion(VersionChangeType.Styles | VersionChangeType.StyleSheet | VersionChangeType.Layout); + ve.IncrementVersion(VersionChangeType.Styles | VersionChangeType.StyleSheet | VersionChangeType.Layout | VersionChangeType.Repaint); } } } diff --git a/ModuleOverrides/com.unity.ui/Core/Style/StylePropertyUtil.cs b/ModuleOverrides/com.unity.ui/Core/Style/StylePropertyUtil.cs index f64a7a1a5b..80fb4beee4 100644 --- a/ModuleOverrides/com.unity.ui/Core/Style/StylePropertyUtil.cs +++ b/ModuleOverrides/com.unity.ui/Core/Style/StylePropertyUtil.cs @@ -3,6 +3,8 @@ // https://unity3d.com/legal/licenses/Unity_Reference_Only_License +using System.Collections.Generic; + namespace UnityEngine.UIElements.StyleSheets { internal static partial class StylePropertyUtil @@ -11,5 +13,10 @@ public static bool IsAnimatable(StylePropertyId id) { return s_AnimatableProperties.Contains(id); } + + public static IEnumerable AllPropertyIds() + { + return s_IdToName.Keys; + } } } diff --git a/ModuleOverrides/com.unity.ui/Editor/Bindings/EditorListViewController.cs b/ModuleOverrides/com.unity.ui/Editor/Bindings/EditorListViewController.cs index a02c258158..d01622bfc4 100644 --- a/ModuleOverrides/com.unity.ui/Editor/Bindings/EditorListViewController.cs +++ b/ModuleOverrides/com.unity.ui/Editor/Bindings/EditorListViewController.cs @@ -2,8 +2,8 @@ // Copyright (c) Unity Technologies. For terms of use, see // https://unity3d.com/legal/licenses/Unity_Reference_Only_License -using System; using System.Collections.Generic; +using System.Linq; using UnityEngine.Pool; using UnityEngine.UIElements; @@ -18,6 +18,11 @@ public override int GetItemsCount() return serializedObjectList?.Count ?? 0; } + internal override int GetItemsMinCount() + { + return serializedObjectList?.ArrayProperty.minArraySize ?? 0; + } + public override void AddItems(int itemCount) { var previousCount = GetItemsCount(); @@ -89,6 +94,15 @@ public override void RemoveItem(int index) RaiseOnSizeChanged(); } + public override void ClearItems() + { + var itemsSourceIndices = Enumerable.Range(0, GetItemsMinCount() - 1); + serializedObjectList.ArrayProperty.arraySize = 0; + serializedObjectList.ApplyChanges(); + RaiseItemsRemoved(itemsSourceIndices); + RaiseOnSizeChanged(); + } + public override void Move(int srcIndex, int destIndex) { if (view.sourceIncludesArraySize) diff --git a/ModuleOverrides/com.unity.ui/Editor/Bindings/ListViewBindings.cs b/ModuleOverrides/com.unity.ui/Editor/Bindings/ListViewBindings.cs index 0905c35427..c56f64d568 100644 --- a/ModuleOverrides/com.unity.ui/Editor/Bindings/ListViewBindings.cs +++ b/ModuleOverrides/com.unity.ui/Editor/Bindings/ListViewBindings.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; -using UnityEngine; using UnityEngine.UIElements; using System.Collections; @@ -158,7 +157,15 @@ void UpdateArraySize() m_ArraySize = m_DataList.ArraySize; m_ListViewArraySize = m_ArraySize.intValue; m_LastSourceIncludesArraySize = listView.sourceIncludesArraySize; + + var isOverMaxMultiEditLimit = m_DataList.IsOverMaxMultiEditLimit; + listView.footer?.SetEnabled(!isOverMaxMultiEditLimit); + listView.SetOverMaxMultiEditLimit(isOverMaxMultiEditLimit, m_DataList.ArrayProperty.serializedObject.maxArraySizeForMultiEditing); + listView.RefreshItems(); + + if (listView.arraySizeField != null) + listView.arraySizeField.showMixedValue = m_ArraySize.hasMultipleDifferentValues; } public override void Release() @@ -219,7 +226,14 @@ public override void Update() isUpdating = true; - if (listView.sourceIncludesArraySize != m_LastSourceIncludesArraySize) + var currentArraySize = m_ArraySize.intValue; + var listViewShowsMixedValue = listView.arraySizeField is {showMixedValue: true}; + if (listViewShowsMixedValue || + (listView.arraySizeField == null || int.Parse(listView.arraySizeField.value) == currentArraySize) && + listView.sourceIncludesArraySize == m_LastSourceIncludesArraySize) + return; + if (currentArraySize != m_ListViewArraySize || + listView.sourceIncludesArraySize != m_LastSourceIncludesArraySize) { UpdateArraySize(); } @@ -333,6 +347,8 @@ object ICollection.SyncRoot get { return (properties as ICollection).SyncRoot; } } + internal bool IsOverMaxMultiEditLimit => ArrayProperty.minArraySize > ArrayProperty.serializedObject.maxArraySizeForMultiEditing && ArrayProperty.serializedObject.isEditingMultipleObjects; + public int Add(object value) { throw new NotImplementedException(); @@ -425,8 +441,11 @@ public void RemoveAt(int index) for (var i = index + 1; i < ArraySize.intValue; i++) { var nextProperty = ArrayProperty.GetArrayElementAtIndex(i); - currentProperty.isExpanded = nextProperty.isExpanded; - currentProperty = nextProperty; + if (nextProperty != null) + { + currentProperty.isExpanded = nextProperty.isExpanded; + currentProperty = nextProperty; + } } ArrayProperty.DeleteArrayElementAtIndex(index); diff --git a/ModuleOverrides/com.unity.ui/Editor/Debugger/StylePropertyDebugger.cs b/ModuleOverrides/com.unity.ui/Editor/Debugger/StylePropertyDebugger.cs index 03df68f726..14d34b4041 100644 --- a/ModuleOverrides/com.unity.ui/Editor/Debugger/StylePropertyDebugger.cs +++ b/ModuleOverrides/com.unity.ui/Editor/Debugger/StylePropertyDebugger.cs @@ -9,13 +9,9 @@ using UnityEngine; using UnityEngine.UIElements; using UnityEngine.UIElements.StyleSheets; -using UnityEditor; -using UnityEditor.UIElements; -using UnityEditor.UIElements.Debugger; using UnityEngine.Assertions; using UnityEngine.TextCore.Text; using Cursor = UnityEngine.UIElements.Cursor; -using Toolbar = UnityEditor.UIElements.Toolbar; namespace UnityEditor.UIElements.Debugger { @@ -44,6 +40,12 @@ internal class StylePropertyDebugger : VisualElement private VisualElement m_SelectedElement; + internal bool showAll + { + get => m_ShowAll; + set => m_ShowAll = value; + } + static StylePropertyDebugger() { // Retrieve all style property infos @@ -130,9 +132,10 @@ private void FindInlineStyles() } } - foreach (var sv in m_SelectedElement.inlineStyleAccess.m_Values) + foreach (var id in StylePropertyUtil.AllPropertyIds()) { - m_PropertySpecificityDictionary[sv.id] = StyleDebug.InlineSpecificity; + if (m_SelectedElement.inlineStyleAccess.IsValueSet(id)) + m_PropertySpecificityDictionary[id] = StyleDebug.InlineSpecificity; } } @@ -177,11 +180,9 @@ private void RefreshFields() continue; var id = propertyInfo.id; - object val = StyleDebug.GetComputedStyleValue(m_SelectedElement.computedStyle, id); - Type type = propertyInfo.type; + var val = StyleDebug.GetComputedStyleValue(m_SelectedElement.computedStyle, id); - int specificity = 0; - m_PropertySpecificityDictionary.TryGetValue(id, out specificity); + m_PropertySpecificityDictionary.TryGetValue(id, out var specificity); StyleField sf = null; m_IdToFieldDictionary.TryGetValue(id, out sf); @@ -244,7 +245,7 @@ public StyleField(VisualElement selectedElement, StylePropertyInfo propInfo, obj m_PropertyName = propInfo.name; m_SpecificityLabel = new Label(); - m_SpecificityLabel.AddToClassList("unity-style-field__specifity-label"); + m_SpecificityLabel.AddToClassList("unity-style-field__specificity-label"); RefreshPropertyValue(value, specificity); } @@ -253,33 +254,32 @@ public void RefreshPropertyValue(object val, int specificity) { if (val is float floatValue) { - FloatField field = GetOrCreateField(); + var field = GetOrCreateField(); if (!IsFocused(field)) field.SetValueWithoutNotify(floatValue); } else if (val is int intValue) { - IntegerField field = GetOrCreateField(); + var field = GetOrCreateField(); if (!IsFocused(field)) field.SetValueWithoutNotify(intValue); } else if (val is Length lengthValue) { - StyleLengthField field = GetOrCreateField(); + var field = GetOrCreateField(); if (!IsFocused(field)) field.SetValueWithoutNotify(new StyleLength(lengthValue)); } else if (val is Color colorValue) { - ColorField field = GetOrCreateField(); + var field = GetOrCreateField(); if (!IsFocused(field)) field.SetValueWithoutNotify(colorValue); } // Note: val may be null in case of reference type like "Font" else if (m_PropertyInfo.type == typeof(StyleFont)) { - ObjectField field; - field = GetOrCreateObjectField(); + var field = GetOrCreateObjectField(); if (!IsFocused(field)) field.SetValueWithoutNotify(val as Font); } @@ -351,11 +351,11 @@ public void RefreshPropertyValue(object val, int specificity) } else { - int mouseId = cursorValue.defaultCursorId; + var mouseId = cursorValue.defaultCursorId; var uiField = new EnumField(m_PropertyName, (MouseCursor)mouseId); uiField.RegisterValueChangedCallback(e => { - int cursorId = Convert.ToInt32(e.newValue); + var cursorId = Convert.ToInt32(e.newValue); var cursor = new Cursor() { defaultCursorId = cursorId }; SetPropertyValue(new StyleCursor(cursor)); }); @@ -366,15 +366,15 @@ public void RefreshPropertyValue(object val, int specificity) } else if (val is TextShadow textShadow) { - ColorField colorFieldfield = GetOrCreateFields(m_PropertyName, 0); + var colorFieldfield = GetOrCreateFields(m_PropertyName, 0); if (!IsFocused(colorFieldfield)) colorFieldfield.SetValueWithoutNotify(textShadow.color); - Vector2Field vector2Field = GetOrCreateFields("offset", 1); + var vector2Field = GetOrCreateFields("offset", 1); if (!IsFocused(vector2Field)) vector2Field.SetValueWithoutNotify(textShadow.offset); - FloatField floatField = GetOrCreateFields("blur", 2); + var floatField = GetOrCreateFields("blur", 2); if (!IsFocused(floatField)) floatField.SetValueWithoutNotify(textShadow.blurRadius); @@ -383,7 +383,7 @@ public void RefreshPropertyValue(object val, int specificity) } else if (val is Rotate rotate) { - FloatField floatField = GetOrCreateFields(m_PropertyName, 0); + var floatField = GetOrCreateFields(m_PropertyName, 0); if (!IsFocused(floatField)) floatField.SetValueWithoutNotify(rotate.angle.value); Add(m_SpecificityLabel); @@ -391,7 +391,7 @@ public void RefreshPropertyValue(object val, int specificity) } else if (val is Scale scale) { - Vector3Field vector3Field = GetOrCreateFields(m_PropertyName, 0); + var vector3Field = GetOrCreateFields(m_PropertyName, 0); if (!IsFocused(vector3Field)) vector3Field.SetValueWithoutNotify(scale.value); Add(m_SpecificityLabel); @@ -399,13 +399,13 @@ public void RefreshPropertyValue(object val, int specificity) } else if (val is Translate translate) { - StyleLengthField fieldx = GetOrCreateFields(m_PropertyName, 0); + var fieldx = GetOrCreateFields(m_PropertyName, 0); if (!IsFocused(fieldx)) fieldx.SetValueWithoutNotify(translate.x); - StyleLengthField fieldy = GetOrCreateFields("y", 1); + var fieldy = GetOrCreateFields("y", 1); if (!IsFocused(fieldy)) fieldy.SetValueWithoutNotify(translate.x); - FloatField floatField = GetOrCreateFields("z", 2); + var floatField = GetOrCreateFields("z", 2); if (!IsFocused(floatField)) floatField.SetValueWithoutNotify(translate.z); Add(m_SpecificityLabel); @@ -414,13 +414,13 @@ public void RefreshPropertyValue(object val, int specificity) } else if (val is TransformOrigin transformOrigin) { - StyleLengthField fieldx = GetOrCreateFields(m_PropertyName, 0); + var fieldx = GetOrCreateFields(m_PropertyName, 0); if (!IsFocused(fieldx)) fieldx.SetValueWithoutNotify(transformOrigin.x); - StyleLengthField fieldy = GetOrCreateFields("y", 1); + var fieldy = GetOrCreateFields("y", 1); if (!IsFocused(fieldy)) fieldy.SetValueWithoutNotify(transformOrigin.x); - FloatField floatField = GetOrCreateFields("z", 2); + var floatField = GetOrCreateFields("z", 2); if (!IsFocused(floatField)) floatField.SetValueWithoutNotify(transformOrigin.z); Add(m_SpecificityLabel); @@ -429,35 +429,37 @@ public void RefreshPropertyValue(object val, int specificity) } else if (val is Enum) { - Enum enumValue = (Enum)val; - EnumField field = GetOrCreateEnumField(enumValue); + var enumValue = (Enum)val; + var field = GetOrCreateEnumField(enumValue); if (!IsFocused(field)) field.SetValueWithoutNotify(enumValue); + Add(m_SpecificityLabel); } else if (val is BackgroundPosition backgroundPosition) { - Enum keyword = backgroundPosition.keyword; - EnumField field = GetOrCreateEnumField(keyword); + var keyword = backgroundPosition.keyword; + var field = GetOrCreateEnumField(keyword); if (!IsFocused(field)) field.SetValueWithoutNotify(keyword); - StyleLengthField fieldx = GetOrCreateFields("x", 2); - if (!IsFocused(fieldx)) - fieldx.SetValueWithoutNotify(backgroundPosition.offset); + var propertyName = m_PropertyName.EndsWith("x") ? "x" : "y"; + var fieldX = GetOrCreateFields(propertyName, 1); + if (!IsFocused(fieldX)) + fieldX.SetValueWithoutNotify(backgroundPosition.offset); Add(m_SpecificityLabel); SetSpecificity(specificity); } else if (val is BackgroundRepeat backgroundRepeat) { - Enum enumValue1 = backgroundRepeat.x; - EnumField field1 = GetOrCreateEnumField(enumValue1); + var enumValue1 = backgroundRepeat.x; + var field1 = GetOrCreateEnumField(enumValue1); if (!IsFocused(field1)) field1.SetValueWithoutNotify(enumValue1); - Enum enumValue2 = backgroundRepeat.y; - bool isCreation = childCount == 2; - EnumField field2 = GetOrCreateFields("y", 2); + var enumValue2 = backgroundRepeat.y; + var isCreation = childCount == 1; + var field2 = GetOrCreateFields("y", 1); if (isCreation) field2.Init(enumValue2); if (!IsFocused(field2)) @@ -468,18 +470,18 @@ public void RefreshPropertyValue(object val, int specificity) } else if (val is BackgroundSize backgroundSize) { - Enum type = backgroundSize.sizeType; - EnumField field = GetOrCreateEnumField(type); + var type = backgroundSize.sizeType; + var field = GetOrCreateEnumField(type); if (!IsFocused(field)) field.SetValueWithoutNotify(type); - StyleLengthField fieldx = GetOrCreateFields("x", 2); - if (!IsFocused(fieldx)) - fieldx.SetValueWithoutNotify(backgroundSize.x); + var fieldX = GetOrCreateFields("x", 1); + if (!IsFocused(fieldX)) + fieldX.SetValueWithoutNotify(backgroundSize.x); - StyleLengthField fieldy = GetOrCreateFields("y", 3); - if (!IsFocused(fieldy)) - fieldy.SetValueWithoutNotify(backgroundSize.y); + var fieldY = GetOrCreateFields("y", 2); + if (!IsFocused(fieldY)) + fieldY.SetValueWithoutNotify(backgroundSize.y); Add(m_SpecificityLabel); SetSpecificity(specificity); @@ -510,8 +512,8 @@ private static bool IsFocused(VisualElement ve) private ObjectField GetOrCreateObjectField() { - bool isCreation = childCount == 0; - ObjectField field = GetOrCreateField(); + var isCreation = childCount == 0; + var field = GetOrCreateField(); if (isCreation) field.objectType = typeof(T); return field; @@ -519,8 +521,8 @@ private ObjectField GetOrCreateObjectField() private EnumField GetOrCreateEnumField(Enum enumValue) { - bool isCreation = childCount == 0; - EnumField field = GetOrCreateField(); + var isCreation = childCount == 0; + var field = GetOrCreateFields(m_PropertyName, 0); if (isCreation) field.Init(enumValue); return field; @@ -577,7 +579,7 @@ private void SetField(ObjectField field) private void SetSpecificity(int specificity) { - string specificityString = ""; + var specificityString = ""; switch (specificity) { case StyleDebug.UnitySpecificity: @@ -601,8 +603,8 @@ private void SetSpecificity(int specificity) private void SetPropertyValue(object newValue, int childIndex = -1) { - object val = StyleDebug.GetComputedStyleValue(m_SelectedElement.computedStyle, m_PropertyInfo.id); - Type type = m_PropertyInfo.type; + var val = StyleDebug.GetComputedStyleValue(m_SelectedElement.computedStyle, m_PropertyInfo.id); + var type = m_PropertyInfo.type; if (newValue == null) { @@ -661,7 +663,7 @@ private void SetPropertyValue(object newValue, int childIndex = -1) { backgroundPosition.keyword = (BackgroundPositionKeyword)newValue; } - else if (childIndex == 2) + else if (childIndex == 1) { if (newValue is StyleLength l) { @@ -677,7 +679,7 @@ private void SetPropertyValue(object newValue, int childIndex = -1) { backgroundRepeat.x = (Repeat)newValue; } - else if (childIndex == 2) + else if (childIndex == 1) { backgroundRepeat.y = (Repeat)newValue; } @@ -690,14 +692,14 @@ private void SetPropertyValue(object newValue, int childIndex = -1) { backgroundSize.sizeType = (BackgroundSizeType)newValue; } - else if (childIndex == 2) + else if (childIndex == 1) { if (newValue is StyleLength l) { backgroundSize.x = l.value; } } - else if (childIndex == 3) + else if (childIndex == 2) { if (newValue is StyleLength l) { diff --git a/Projects/CSharp/UnityEngine.csproj b/Projects/CSharp/UnityEngine.csproj index 17f4c41967..10b73dd830 100644 --- a/Projects/CSharp/UnityEngine.csproj +++ b/Projects/CSharp/UnityEngine.csproj @@ -2893,6 +2893,9 @@ Runtime\Export\Hashing\SpookyHash.cs + + Runtime\Export\Hmi\HmiPlatform.bindings.cs + Runtime\Export\IMGUI\GUIElement.bindings.cs diff --git a/README.md b/README.md index d6f0b38a07..585a036ef0 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -## Unity 2022.2.0b2 C# reference source code +## Unity 2022.2.0b3 C# reference source code The C# part of the Unity engine and editor source code. May be used for reference purposes only. diff --git a/Runtime/Export/AssemblyInfo.cs b/Runtime/Export/AssemblyInfo.cs index a2ca83df5c..e5c3583d4c 100644 --- a/Runtime/Export/AssemblyInfo.cs +++ b/Runtime/Export/AssemblyInfo.cs @@ -24,6 +24,7 @@ [assembly: InternalsVisibleTo("UnityEditor.GameCoreCommon.Extensions")] [assembly: InternalsVisibleTo("UnityEditor.WindowsStandalone.Extensions")] [assembly: InternalsVisibleTo("UnityEditor.WebGL.Extensions")] +[assembly: InternalsVisibleTo("UnityEditor.EmbeddedLinux.Extensions")] [assembly: InternalsVisibleTo("Assembly-CSharp-Editor-testable")] [assembly: InternalsVisibleTo("Assembly-CSharp-Editor-firstpass-testable")] [assembly: InternalsVisibleTo("UnityEditor.iOS.Extensions.Common")] diff --git a/Runtime/Export/Debug/Debug.bindings.cs b/Runtime/Export/Debug/Debug.bindings.cs index aca64bc7a9..2efed92192 100644 --- a/Runtime/Export/Debug/Debug.bindings.cs +++ b/Runtime/Export/Debug/Debug.bindings.cs @@ -161,9 +161,13 @@ internal static void LogInfo(string message, string fileName, int lineNumber, in LogInformation(message, fileName, lineNumber, columnNumber); } + [ThreadAndSerializationSafe] internal static extern void LogCompilerMessage(string message, string fileName, int lineNumber, int columnNumber, bool forEditor, bool isError, int identifier); + [ThreadAndSerializationSafe] private static extern void LogCompilerWarning(string message, string fileName, int lineNumber, int columnNumber); + [ThreadAndSerializationSafe] private static extern void LogCompilerError(string message, string fileName, int lineNumber, int columnNumber); + [ThreadAndSerializationSafe] private static extern void LogInformation(string message, string fileName, int lineNumber, int columnNumber); // Clears errors from the developer console. @@ -178,6 +182,7 @@ internal static void LogInfo(string message, string fileName, int lineNumber, in // A variant of Debug.Log that logs an error message to the console. public static void LogException(Exception exception, Object context) { unityLogger.LogException(exception, context); } + [ThreadAndSerializationSafe] internal static extern void LogPlayerBuildError(string message, string file, int line, int column); // A variant of Debug.Log that logs a warning message to the console. diff --git a/Runtime/Export/Graphics/GraphicsEnums.cs b/Runtime/Export/Graphics/GraphicsEnums.cs index 79890776e6..c601b548ee 100644 --- a/Runtime/Export/Graphics/GraphicsEnums.cs +++ b/Runtime/Export/Graphics/GraphicsEnums.cs @@ -572,7 +572,7 @@ namespace Rendering // All usage of TextureCreationFlags should remain undocumented for the time being. // This will hopefully be documented as part of the push to move GraphicsFormat out of Experimental. - // keep this in sync with the TextureCreationFlags enum in Texture.h + // keep this in sync with the TextureCreationFlags enum in Format.h [Flags] public enum TextureCreationFlags { @@ -587,6 +587,7 @@ public enum TextureCreationFlags //IsNativeTexture = 1 << 8, // this is only used internally. //DontCheckGraphicsCaps = 1 << 9, // this is only used internally. DontUploadUponCreate = 1 << 10, + IgnoreMipmapLimit = 1 << 11, } // Keep in sync with FormatUsage in Runtime/Graphics/Format.h @@ -928,6 +929,13 @@ public enum LineAlignment Local = 1, TransformZ = 1, } + + // Keep in sync with MipmapLimitBiasMode in Runtime/Graphics/Texture.h + public enum TextureMipmapLimitBiasMode + { + OffsetGlobalLimit = 0, + OverrideGlobalLimit = 1, + } } // namespace UnityEngine diff --git a/Runtime/Export/Graphics/GraphicsManagers.bindings.cs b/Runtime/Export/Graphics/GraphicsManagers.bindings.cs index 6a345c5634..d3ce7d7a33 100644 --- a/Runtime/Export/Graphics/GraphicsManagers.bindings.cs +++ b/Runtime/Export/Graphics/GraphicsManagers.bindings.cs @@ -86,6 +86,32 @@ public static Cubemap customReflection [StaticAccessor("RenderSettingsScripting", StaticAccessorType.DoubleColon)] extern internal static void Reset(); } + // Keep in sync with MipmapLimitSettings in Runtime\Graphics\Texture.h + public struct TextureMipmapLimitSettings + { + public TextureMipmapLimitBiasMode limitBiasMode { get; set; } + public int limitBias { get; set; } + }; + + [NativeHeader("Runtime/Graphics/QualitySettings.h")] + [StaticAccessor("GetQualitySettings()", StaticAccessorType.Dot)] + public static class TextureMipmapLimitGroups + { + [NativeName("CreateTextureMipmapLimitGroup")] + [NativeThrows] + extern public static void CreateGroup([NotNull] string groupName); + + [NativeName("RemoveTextureMipmapLimitGroup")] + [NativeThrows] + extern public static void RemoveGroup([NotNull] string groupName); + + [NativeName("GetTextureMipmapLimitGroupNames")] + extern public static string[] GetGroups(); + + [NativeName("HasTextureMipmapLimitGroup")] + extern public static bool HasGroup([NotNull] string groupName); + } + [NativeHeader("Runtime/Graphics/QualitySettings.h")] [StaticAccessor("GetQualitySettings()", StaticAccessorType.Dot)] public sealed partial class QualitySettings : Object @@ -107,7 +133,9 @@ private QualitySettings() {} [NativeProperty("LODBias")] extern public static float lodBias { get; set; } [NativeProperty("AnisotropicTextures")] extern public static AnisotropicFiltering anisotropicFiltering { get; set; } - extern public static int masterTextureLimit { get; set; } + [Obsolete("masterTextureLimit has been deprecated. Use globalTextureMipmapLimit instead (UnityUpgradable) -> globalTextureMipmapLimit", false)] + [NativeProperty("GlobalTextureMipmapLimit")] extern public static int masterTextureLimit { get; set; } + extern public static int globalTextureMipmapLimit { get; set; } extern public static int maximumLODLevel { get; set; } extern public static bool enableLODCrossFade { get; set; } extern public static int particleRaycastBudget { get; set; } @@ -122,6 +150,13 @@ private QualitySettings() {} [NativeName("SetLODSettings")] extern public static void SetLODSettings(float lodBias, int maximumLODLevel, bool setDirty = true); + [NativeName("SetTextureMipmapLimitSettings")] + [NativeThrows] + extern public static void SetTextureMipmapLimitSettings(string groupName, TextureMipmapLimitSettings textureMipmapLimitSettings); + + [NativeName("GetTextureMipmapLimitSettings")] + [NativeThrows] + extern public static TextureMipmapLimitSettings GetTextureMipmapLimitSettings(string groupName); extern public static bool realtimeReflectionProbes { get; set; } extern public static bool billboardsFaceCameraPosition { get; set; } diff --git a/Runtime/Export/Graphics/Texture.bindings.cs b/Runtime/Export/Graphics/Texture.bindings.cs index 1f74a3e465..eed6b871c2 100644 --- a/Runtime/Export/Graphics/Texture.bindings.cs +++ b/Runtime/Export/Graphics/Texture.bindings.cs @@ -22,7 +22,10 @@ public partial class Texture : Object { protected Texture() {} - [NativeProperty("GlobalMasterTextureLimit")] extern public static int masterTextureLimit { get; set; } + [Obsolete("masterTextureLimit has been deprecated. Use globalMipmapLimit instead (UnityUpgradable) -> globalMipmapLimit", false)] + [NativeProperty("ActiveGlobalMipmapLimit")] extern public static int masterTextureLimit { get; set; } + [Obsolete("globalMipmapLimit is not supported. Use QualitySettings.globalTextureMipmapLimit or Mipmap Limit Groups instead.", false)] + [NativeProperty("ActiveGlobalMipmapLimit")] extern public static int globalMipmapLimit { get; set; } extern public int mipmapCount { [NativeName("GetMipmapCount")] get; } @@ -184,6 +187,16 @@ public sealed partial class Texture2D : Texture { extern public TextureFormat format { [NativeName("GetTextureFormat")] get; } + extern public bool ignoreMipmapLimit { + [NativeName("IgnoreMipmapLimit")] get; + [NativeName("SetIgnoreMipmapLimitAndReload")] set; + } + + extern public string mipmapLimitGroup + { + [NativeName("GetMipmapLimitGroupName")] get; + } + [StaticAccessor("builtintex", StaticAccessorType.DoubleColon)] extern public static Texture2D whiteTexture { get; } [StaticAccessor("builtintex", StaticAccessorType.DoubleColon)] extern public static Texture2D blackTexture { get; } [StaticAccessor("builtintex", StaticAccessorType.DoubleColon)] extern public static Texture2D redTexture { get; } @@ -194,10 +207,10 @@ public sealed partial class Texture2D : Texture extern public void Compress(bool highQuality); [FreeFunction("Texture2DScripting::Create")] - extern private static bool Internal_CreateImpl([Writable] Texture2D mono, int w, int h, int mipCount, GraphicsFormat format, TextureCreationFlags flags, IntPtr nativeTex); - private static void Internal_Create([Writable] Texture2D mono, int w, int h, int mipCount, GraphicsFormat format, TextureCreationFlags flags, IntPtr nativeTex) + extern private static bool Internal_CreateImpl([Writable] Texture2D mono, int w, int h, int mipCount, GraphicsFormat format, TextureCreationFlags flags, IntPtr nativeTex, string mipmapLimitGroupName); + private static void Internal_Create([Writable] Texture2D mono, int w, int h, int mipCount, GraphicsFormat format, TextureCreationFlags flags, IntPtr nativeTex, string mipmapLimitGroupName) { - if (!Internal_CreateImpl(mono, w, h, mipCount, format, flags, nativeTex)) + if (!Internal_CreateImpl(mono, w, h, mipCount, format, flags, nativeTex, mipmapLimitGroupName)) throw new UnityException("Failed to create texture because of invalid parameters."); } diff --git a/Runtime/Export/Graphics/Texture.cs b/Runtime/Export/Graphics/Texture.cs index f15385237d..a9d5b89201 100644 --- a/Runtime/Export/Graphics/Texture.cs +++ b/Runtime/Export/Graphics/Texture.cs @@ -653,10 +653,10 @@ internal bool ValidateFormat(GraphicsFormat format, int width, int height) return isValid; } - internal Texture2D(int width, int height, GraphicsFormat format, TextureCreationFlags flags, int mipCount, IntPtr nativeTex) + internal Texture2D(int width, int height, GraphicsFormat format, TextureCreationFlags flags, int mipCount, IntPtr nativeTex, string mipmapLimitGroupName) { if (ValidateFormat(format, width, height)) - Internal_Create(this, width, height, mipCount, format, flags, nativeTex); + Internal_Create(this, width, height, mipCount, format, flags, nativeTex, mipmapLimitGroupName); } [uei.ExcludeFromDocs] @@ -667,23 +667,35 @@ public Texture2D(int width, int height, DefaultFormat format, TextureCreationFla [uei.ExcludeFromDocs] public Texture2D(int width, int height, DefaultFormat format, int mipCount, TextureCreationFlags flags) - : this(width, height, SystemInfo.GetGraphicsFormat(format), flags, mipCount, IntPtr.Zero) + : this(width, height, SystemInfo.GetGraphicsFormat(format), flags, mipCount, IntPtr.Zero, null) + { + } + + [uei.ExcludeFromDocs] + public Texture2D(int width, int height, DefaultFormat format, int mipCount, string mipmapLimitGroupName, TextureCreationFlags flags) + : this(width, height, SystemInfo.GetGraphicsFormat(format), flags, mipCount, IntPtr.Zero, mipmapLimitGroupName) { } [uei.ExcludeFromDocs] public Texture2D(int width, int height, GraphicsFormat format, TextureCreationFlags flags) - : this(width, height, format, flags, Texture.GenerateAllMips, IntPtr.Zero) + : this(width, height, format, flags, Texture.GenerateAllMips, IntPtr.Zero, null) { } [uei.ExcludeFromDocs] public Texture2D(int width, int height, GraphicsFormat format, int mipCount, TextureCreationFlags flags) - : this(width, height, format, flags, mipCount, IntPtr.Zero) + : this(width, height, format, flags, mipCount, IntPtr.Zero, null) { } - internal Texture2D(int width, int height, TextureFormat textureFormat, int mipCount, bool linear, IntPtr nativeTex, bool createUninitialized) + [uei.ExcludeFromDocs] + public Texture2D(int width, int height, GraphicsFormat format, int mipCount, string mipmapLimitGroupName, TextureCreationFlags flags) + : this(width, height, format, flags, mipCount, IntPtr.Zero, mipmapLimitGroupName) + { + } + + internal Texture2D(int width, int height, TextureFormat textureFormat, int mipCount, bool linear, IntPtr nativeTex, bool createUninitialized, bool ignoreMipmapLimit, string mipmapLimitGroupName) { if (!ValidateFormat(textureFormat, width, height)) return; @@ -694,38 +706,45 @@ internal Texture2D(int width, int height, TextureFormat textureFormat, int mipCo flags |= TextureCreationFlags.Crunch; if (createUninitialized) flags |= TextureCreationFlags.DontUploadUponCreate | TextureCreationFlags.DontInitializePixels; - Internal_Create(this, width, height, mipCount, format, flags, nativeTex); + if (ignoreMipmapLimit) + flags |= TextureCreationFlags.IgnoreMipmapLimit; + Internal_Create(this, width, height, mipCount, format, flags, nativeTex, mipmapLimitGroupName); } public Texture2D(int width, int height, [uei.DefaultValue("TextureFormat.RGBA32")] TextureFormat textureFormat, [uei.DefaultValue("-1")] int mipCount, [uei.DefaultValue("false")] bool linear) - : this(width, height, textureFormat, mipCount, linear, IntPtr.Zero, false) + : this(width, height, textureFormat, mipCount, linear, IntPtr.Zero, false, false, null) { } public Texture2D(int width, int height, [uei.DefaultValue("TextureFormat.RGBA32")] TextureFormat textureFormat, [uei.DefaultValue("-1")] int mipCount, [uei.DefaultValue("false")] bool linear, [uei.DefaultValue("false")] bool createUninitialized) - : this(width, height, textureFormat, mipCount, linear, IntPtr.Zero, createUninitialized) + : this(width, height, textureFormat, mipCount, linear, IntPtr.Zero, createUninitialized, false, null) + { + } + + public Texture2D(int width, int height, [uei.DefaultValue("TextureFormat.RGBA32")] TextureFormat textureFormat, [uei.DefaultValue("-1")] int mipCount, [uei.DefaultValue("false")] bool linear, [uei.DefaultValue("false")] bool createUninitialized, [uei.DefaultValue("false")] bool ignoreMipmapLimit, [uei.DefaultValue("null")] string mipmapLimitGroupName) + : this(width, height, textureFormat, mipCount, linear, IntPtr.Zero, createUninitialized, ignoreMipmapLimit, mipmapLimitGroupName) { } public Texture2D(int width, int height, [uei.DefaultValue("TextureFormat.RGBA32")] TextureFormat textureFormat, [uei.DefaultValue("true")] bool mipChain, [uei.DefaultValue("false")] bool linear) - : this(width, height, textureFormat, mipChain ? Texture.GenerateAllMips : 1, linear, IntPtr.Zero, false) + : this(width, height, textureFormat, mipChain ? Texture.GenerateAllMips : 1, linear, IntPtr.Zero, false, false, null) { } public Texture2D(int width, int height, [uei.DefaultValue("TextureFormat.RGBA32")] TextureFormat textureFormat, [uei.DefaultValue("true")] bool mipChain, [uei.DefaultValue("false")] bool linear, [uei.DefaultValue("false")] bool createUninitialized) - : this(width, height, textureFormat, mipChain ? Texture.GenerateAllMips : 1, linear, IntPtr.Zero, createUninitialized) + : this(width, height, textureFormat, mipChain ? Texture.GenerateAllMips : 1, linear, IntPtr.Zero, createUninitialized, false, null) { } [uei.ExcludeFromDocs] public Texture2D(int width, int height, TextureFormat textureFormat, bool mipChain) - : this(width, height, textureFormat, mipChain ? Texture.GenerateAllMips : 1, false, IntPtr.Zero, false) + : this(width, height, textureFormat, mipChain ? Texture.GenerateAllMips : 1, false, IntPtr.Zero, false, false, null) { } [uei.ExcludeFromDocs] public Texture2D(int width, int height) - : this(width, height, TextureFormat.RGBA32, Texture.GenerateAllMips, false, IntPtr.Zero, false) + : this(width, height, TextureFormat.RGBA32, Texture.GenerateAllMips, false, IntPtr.Zero, false, false, null) { } @@ -733,7 +752,7 @@ public static Texture2D CreateExternalTexture(int width, int height, TextureForm { if (nativeTex == IntPtr.Zero) throw new ArgumentException("nativeTex can not be null"); - return new Texture2D(width, height, format, mipChain ? -1 : 1, linear, nativeTex, false); + return new Texture2D(width, height, format, mipChain ? -1 : 1, linear, nativeTex, false, false, null); } [uei.ExcludeFromDocs] diff --git a/Runtime/Export/Hmi/HmiPlatform.bindings.cs b/Runtime/Export/Hmi/HmiPlatform.bindings.cs new file mode 100644 index 0000000000..646600c564 --- /dev/null +++ b/Runtime/Export/Hmi/HmiPlatform.bindings.cs @@ -0,0 +1,11 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using UnityEngine.Bindings; +using UnityEngine.Scripting; +using UnityEngine.Internal; + +namespace UnityEngine +{ +} From 482afa43bb61b8d7c1bdeedfc87d381128ba5721 Mon Sep 17 00:00:00 2001 From: Unity Technologies Date: Fri, 5 Aug 2022 02:14:17 +0000 Subject: [PATCH 05/94] Unity 2022.2.0b4 C# reference source code --- Editor/Mono/BuildPipeline/NamedBuildTarget.cs | 2 - Editor/Mono/BuildTarget.cs | 1 + Editor/Mono/BuildTargetConverter.cs | 2 - Editor/Mono/BuildTargetGroup.cs | 1 + Editor/Mono/ConsoleWindow.cs | 35 +- Editor/Mono/PlayerSettings.bindings.cs | 3 +- .../ScriptCompilation/CustomScriptAssembly.cs | 1 + .../MultiColumnTreeViewController.cs | 2 +- .../Controllers/TreeViewController.cs | 2 +- .../Core/Controls/BaseListView.cs | 28 +- .../Controls/BaseVerticalCollectionView.cs | 23 +- .../Core/Controls/InputField/TextField.cs | 2 +- .../MultiColumnCollectionHeader.cs | 34 +- .../Core/Controls/TwoPaneSplitView.cs | 7 + .../com.unity.ui/Core/Events/LinkTagEvent.cs | 273 +++ .../com.unity.ui/Core/Text/UITKTextHandle.cs | 231 ++- .../Delegates/EditorDelegateRegistration.cs | 1 - .../ArticulatonBodyAnchorTransformTool.cs | 12 +- .../TextCoreTextEngine/Managed/LinkInfo.cs | 22 +- .../Managed/TextGenerator.cs | 322 ++-- .../Managed/TextGeneratorUtilities.cs | 1547 +++++------------ .../TextCoreTextEngine/Managed/TextHandle.cs | 175 +- .../Managed/TextMarkupTagsCommon.cs | 1 + .../Managed/TextProcessingCommon.cs | 254 +++ .../Managed/TextSettings.cs | 11 + .../TextCoreTextEngine/Managed/TextStyle.cs | 12 +- .../ScriptBindings/VFXEnums.bindings.cs | 22 +- .../ScriptBindings/VFXManager.bindings.cs | 59 +- .../ScriptBindings/VisualEffect.bindings.cs | 63 +- .../VisualEffectResource.bindings.cs | 9 +- .../VisualEffectUtility.bindings.cs | 10 - Projects/CSharp/UnityEditor.csproj | 2 +- Projects/CSharp/UnityEngine.csproj | 6 + README.md | 2 +- Runtime/Export/BaseClass.cs | 2 +- .../Export/Lumin/UsesLuminPlatformLevel.cs | 1 + Runtime/Export/Lumin/UsesLuminPrivilege.cs | 1 + .../Unity.CompilationPipeline.Common.dll | Bin 38 files changed, 1727 insertions(+), 1454 deletions(-) create mode 100644 ModuleOverrides/com.unity.ui/Core/Events/LinkTagEvent.cs create mode 100644 Modules/TextCoreTextEngine/Managed/TextProcessingCommon.cs rename artifacts/Stevedore/{unity-compiler_20ac => unity-compiler_b9dd}/Unity.CompilationPipeline.Common/Unity.CompilationPipeline.Common.dll (100%) diff --git a/Editor/Mono/BuildPipeline/NamedBuildTarget.cs b/Editor/Mono/BuildPipeline/NamedBuildTarget.cs index 6130437d89..b658f95fd1 100644 --- a/Editor/Mono/BuildPipeline/NamedBuildTarget.cs +++ b/Editor/Mono/BuildPipeline/NamedBuildTarget.cs @@ -105,8 +105,6 @@ public static NamedBuildTarget FromBuildTargetGroup(BuildTargetGroup buildTarget return NamedBuildTarget.QNX; // Build targets that are not explicitly listed - case BuildTargetGroup.Lumin: - return new NamedBuildTarget("Lumin"); case BuildTargetGroup.GameCoreXboxSeries: return new NamedBuildTarget("GameCoreScarlett"); case BuildTargetGroup.GameCoreXboxOne: diff --git a/Editor/Mono/BuildTarget.cs b/Editor/Mono/BuildTarget.cs index 25d38dd15e..c8b14e96d5 100644 --- a/Editor/Mono/BuildTarget.cs +++ b/Editor/Mono/BuildTarget.cs @@ -118,6 +118,7 @@ public enum BuildTarget Switch = 38, + [System.Obsolete("Lumin has been removed in 2022.2")] Lumin = 39, Stadia = 40, diff --git a/Editor/Mono/BuildTargetConverter.cs b/Editor/Mono/BuildTargetConverter.cs index 1bc2a4e4eb..2ce8d32295 100644 --- a/Editor/Mono/BuildTargetConverter.cs +++ b/Editor/Mono/BuildTargetConverter.cs @@ -42,8 +42,6 @@ internal static class BuildTargetConverter return RuntimePlatform.tvOS; case BuildTarget.WebGL: return RuntimePlatform.WebGLPlayer; - case BuildTarget.Lumin: - return RuntimePlatform.Lumin; case BuildTarget.GameCoreXboxSeries: return RuntimePlatform.GameCoreXboxSeries; case BuildTarget.GameCoreXboxOne: diff --git a/Editor/Mono/BuildTargetGroup.cs b/Editor/Mono/BuildTargetGroup.cs index 3fd8a1415f..bedf3cb862 100644 --- a/Editor/Mono/BuildTargetGroup.cs +++ b/Editor/Mono/BuildTargetGroup.cs @@ -95,6 +95,7 @@ public enum BuildTargetGroup Switch = 27, + [Obsolete("Lumin has been removed in 2022.2")] Lumin = 28, Stadia = 29, diff --git a/Editor/Mono/ConsoleWindow.cs b/Editor/Mono/ConsoleWindow.cs index e9fa620986..7d066230d5 100644 --- a/Editor/Mono/ConsoleWindow.cs +++ b/Editor/Mono/ConsoleWindow.cs @@ -29,14 +29,7 @@ internal class ConsoleWindow : EditorWindow, IHasCustomMenu private static Font m_MonospaceFont; private static int m_DefaultFontSize; private static List s_MethodsToHideInCallstack = null; - private static Dictionary s_GenericMethodSignatureRegex = null; - - internal struct HideInCallstackGenericMethodKey - { - internal string namespaceName; - internal string className; - internal string methodName; - } + private static Dictionary s_GenericMethodSignatureRegex = null; //TODO: move this out of here internal class Constants @@ -908,7 +901,7 @@ internal static string StacktraceWithHyperlinks(string stacktraceText, int calls return textWithHyperlinks.ToString(); } - internal static string GetCallstackFormattedSignatureFromGenericMethod(Dictionary methodSignatureRegex, MethodInfo method, string line) + internal static string GetCallstackFormattedSignatureFromGenericMethod(Dictionary methodSignatureRegex, MethodInfo method, string line) { if (string.IsNullOrEmpty(line) || method == null || methodSignatureRegex == null) return null; @@ -917,14 +910,7 @@ internal static string GetCallstackFormattedSignatureFromGenericMethod(Dictionar if (classType == null) return null; - var ns = classType.Namespace; - var key = new HideInCallstackGenericMethodKey() - { - namespaceName = ns, - className = classType.Name, - methodName = method.Name - }; - if (!methodSignatureRegex.TryGetValue(key, out Regex regex)) + if (!methodSignatureRegex.TryGetValue(method, out Regex regex)) return null; if (regex == null) @@ -1004,7 +990,7 @@ internal static string GetCallstackFormattedScriptingExceptionSignature(MethodIn return sb.ToString(); } - internal static string[] StripCallstack(Mode mode, Dictionary methodSignatureRegex, List methodsToHideInCallstack, string[] lines) + internal static string[] StripCallstack(Mode mode, Dictionary methodSignatureRegex, List methodsToHideInCallstack, string[] lines) { if (methodsToHideInCallstack == null || methodSignatureRegex == null || lines == null) return lines; @@ -1140,14 +1126,14 @@ private static void OnStripLoggingCallstackButtonValueChange() SetFlag(ConsoleFlags.StripLoggingCallstack, s_StripLoggingCallstack); } - internal static (List, Dictionary) InitializeHideInCallstackMethodsCache() + internal static (List, Dictionary) InitializeHideInCallstackMethodsCache() { var methods = TypeCache.GetMethodsWithAttribute(); if (methods.Count == 0) return (null, null); var methodsToHideInCallstack = new List(); - var genericMethodSignatureRegexes = new Dictionary(); + var genericMethodSignatureRegexes = new Dictionary(); foreach (var method in methods) { methodsToHideInCallstack.Add(method); @@ -1161,13 +1147,8 @@ internal static (List, Dictionary /// - /// The default values if true. + /// The default value is true. /// When this property is set to true, Unity displays the collection size as the first item in the list, but does /// not make it an actual list item that is part of the list index. If you query for list index 0, /// Unity returns the first real list item, and not the collection size. - /// If is set to true, the collection size field will be included in the header instead. - /// This property is usually used to debug a ListView, because it indicates whether the data source is - /// linked correctly. In production, the collection size is rarely displayed as a line item in a ListView. + /// If is set to true, the collection size field is included in the header instead. + /// You can use this property to debug a ListView because the property indicates whether the data source is + /// linked correctly. In production, the collection size rarely displays as a line item in a ListView. /// > /// public bool showBoundCollectionSize @@ -108,14 +108,14 @@ public bool showBoundCollectionSize bool m_ShowFoldoutHeader; /// - /// This property controls whether the list view will display a header, in the form of a foldout that can be expanded or collapsed. + /// This property controls whether the list view displays a header, in the form of a foldout that can be expanded or collapsed. /// /// - /// The default values if false. + /// The default value is false. /// When this property is set to true, Unity adds a foldout in the hierarchy of the list view and moves - /// the scroll view inside that newly created foldout. The text of this foldout can be changed with + /// the scroll view inside that newly created foldout. You can change the text of this foldout with /// property on the ListView. - /// If is set to true, the header will include a TextField to control + /// If is set to true, the header includes a TextField to control /// the array size, instead of using the field as part of the list. /// > public bool showFoldoutHeader @@ -198,11 +198,11 @@ public string headerTitle /// This property controls whether a footer will be added to the list view. /// /// - /// The default values if false. + /// The default value is false. /// When this property is set to true, Unity adds a footer under the scroll view. /// This footer contains two buttons: - /// A "+" button. When clicked, adds a single item at the end of the list view. - /// A "-" button. When clicked, removes all selected items, or the last item if none are selected. + /// A "+" button. When clicked, it adds a single item at the end of the list view. + /// A "-" button. When clicked, it removes all selected items, or the last item if none are selected. /// public bool showAddRemoveFooter { @@ -473,9 +473,9 @@ void OnItemsSourceSizeChanged() /// This property controls the drag and drop mode for the list view. /// /// - /// The default values if Simple. + /// The default value is Simple. /// When this property is set to Animated, Unity adds drag handles in front of every item and the drag and - /// drop manipulation will push items with an animation as the reordering happens. + /// drop manipulation pushes items with an animation when the reordering happens. /// Multiple item reordering is only supported with the Simple drag mode. /// public ListViewReorderMode reorderMode @@ -486,7 +486,7 @@ public ListViewReorderMode reorderMode if (value != m_ReorderMode) { m_ReorderMode = value; - InitializeDragAndDropController(); + InitializeDragAndDropController(reorderable); reorderModeChanged?.Invoke(); Rebuild(); } diff --git a/ModuleOverrides/com.unity.ui/Core/Controls/BaseVerticalCollectionView.cs b/ModuleOverrides/com.unity.ui/Core/Controls/BaseVerticalCollectionView.cs index 692ff52179..2b63394c81 100644 --- a/ModuleOverrides/com.unity.ui/Core/Controls/BaseVerticalCollectionView.cs +++ b/ModuleOverrides/com.unity.ui/Core/Controls/BaseVerticalCollectionView.cs @@ -172,6 +172,10 @@ public event Action> onSelectedIndicesChange /// /// Called when an item is moved in the itemsSource. /// + /// + /// This callback receives two ids, the first being the id being moved, the second being the destination id. + /// In the case of a tree, the destination is the parent id. + /// public event Action itemIndexChanged; /// @@ -354,7 +358,7 @@ public bool showBorder /// Gets or sets a value that indicates whether the user can drag list items to reorder them. /// /// - /// The default values is false. + /// The default value is false. /// Set this value to true to allow the user to drag and drop the items in the list. The collection view /// provides a default controller to allow standard behavior. It also automatically handles reordering /// the items in the data source. @@ -367,7 +371,9 @@ public bool reorderable if (m_Dragger?.dragAndDropController == null) { if (value) - InitializeDragAndDropController(); + { + InitializeDragAndDropController(true); + } return; } @@ -430,8 +436,8 @@ public AlternatingRowBackground showAlternatingRowBackgrounds /// Takes a value from the enum. /// /// - /// The default values is FixedHeight. - /// When using fixed height, you need to specify the property. + /// The default value is FixedHeight. + /// When using fixed height, specify the property. /// Fixed height is more performant but offers less flexibility on content. /// When using DynamicHeight, the collection will wait for the actual height to be computed. /// Dynamic height is more flexible but less performant. @@ -592,7 +598,7 @@ internal virtual ListViewDragger CreateDragger() return new ListViewDragger(this); } - internal void InitializeDragAndDropController() + internal void InitializeDragAndDropController(bool enableReordering) { if (m_Dragger != null) { @@ -603,6 +609,7 @@ internal void InitializeDragAndDropController() m_Dragger = CreateDragger(); m_Dragger.dragAndDropController = CreateDragAndDropController(); + m_Dragger.dragAndDropController.enableReordering = enableReordering; } internal abstract ICollectionDragAndDropController CreateDragAndDropController(); @@ -613,12 +620,6 @@ internal void SetDragAndDropController(ICollectionDragAndDropController dragAndD m_Dragger.dragAndDropController = dragAndDropController; } - //Used for unit testing - internal ICollectionDragAndDropController GetDragAndDropController() - { - return m_Dragger?.dragAndDropController; - } - /// /// The USS class name for BaseVerticalCollectionView elements. /// diff --git a/ModuleOverrides/com.unity.ui/Core/Controls/InputField/TextField.cs b/ModuleOverrides/com.unity.ui/Core/Controls/InputField/TextField.cs index fb9ee44924..a5c83d7be8 100644 --- a/ModuleOverrides/com.unity.ui/Core/Controls/InputField/TextField.cs +++ b/ModuleOverrides/com.unity.ui/Core/Controls/InputField/TextField.cs @@ -141,7 +141,7 @@ public override string value public override void SetValueWithoutNotify(string newValue) { base.SetValueWithoutNotify(newValue); - textEdition.UpdateText(rawValue); + ((INotifyValueChanged)textInput.textElement).SetValueWithoutNotify(rawValue); } [EventInterest(typeof(BlurEvent))] diff --git a/ModuleOverrides/com.unity.ui/Core/Controls/MultiColumn/MultiColumnCollectionHeader.cs b/ModuleOverrides/com.unity.ui/Core/Controls/MultiColumn/MultiColumnCollectionHeader.cs index 7b8f4f379f..2638f6d560 100644 --- a/ModuleOverrides/com.unity.ui/Core/Controls/MultiColumn/MultiColumnCollectionHeader.cs +++ b/ModuleOverrides/com.unity.ui/Core/Controls/MultiColumn/MultiColumnCollectionHeader.cs @@ -21,8 +21,6 @@ class ViewState : ISerializationCallbackReceiver { bool m_HasPersistedData; - public MultiColumnCollectionHeader header { get; set; } - /// /// State of columns. /// @@ -45,8 +43,8 @@ struct ColumnState /// /// Saves the state of the specified header control. /// - /// The header control of which state will be saved. - void Save(MultiColumnCollectionHeader header) + /// The header control of which state to save. + internal void Save(MultiColumnCollectionHeader header) { m_SortDescriptions.Clear(); m_OrderedColumnStates.Clear(); @@ -62,23 +60,20 @@ void Save(MultiColumnCollectionHeader header) m_OrderedColumnStates.Add(columnState); } + + m_HasPersistedData = true; } /// /// Applies the state of the specified header control. /// - public void Apply() + internal void Apply(MultiColumnCollectionHeader header) { - if (m_HasPersistedData) - { - Apply(header); - } - } + if (!m_HasPersistedData) + return; - void Apply(MultiColumnCollectionHeader header) - { - int minCount = Math.Min(m_OrderedColumnStates.Count, header.columns.Count); - int nextValidOrderedIndex = 0; + var minCount = Math.Min(m_OrderedColumnStates.Count, header.columns.Count); + var nextValidOrderedIndex = 0; for (var orderedIndex = 0; (orderedIndex < m_OrderedColumnStates.Count) && (nextValidOrderedIndex < minCount); orderedIndex++) { @@ -125,11 +120,7 @@ void Apply(MultiColumnCollectionHeader header) public void OnBeforeSerialize() { - if (header != null) - { - m_HasPersistedData = true; - Save(header); - } + m_HasPersistedData = true; } public void OnAfterDeserialize() @@ -834,8 +825,7 @@ internal override void OnViewDataReady() var key = GetFullHierarchicalViewDataKey(); m_ViewState = GetOrCreateViewData(m_ViewState, key); - m_ViewState.header = this; - m_ViewState.Apply(); + m_ViewState.Apply(this); } finally { @@ -847,6 +837,8 @@ void SaveViewState() { if (m_ApplyingViewState) return; + + m_ViewState?.Save(this); SaveViewData(); } diff --git a/ModuleOverrides/com.unity.ui/Core/Controls/TwoPaneSplitView.cs b/ModuleOverrides/com.unity.ui/Core/Controls/TwoPaneSplitView.cs index 85a9c881b3..00971ca9c7 100644 --- a/ModuleOverrides/com.unity.ui/Core/Controls/TwoPaneSplitView.cs +++ b/ModuleOverrides/com.unity.ui/Core/Controls/TwoPaneSplitView.cs @@ -403,6 +403,13 @@ void UpdateLayout(bool updateFixedPane, bool updateDragLine) { if (m_CollapseMode) return; + + // Don't try to update the layout if the split view is not displayed. This is because the resolved width and height + // will be 0, which will effectively reset the layout. + if (resolvedStyle.display == DisplayStyle.None || + resolvedStyle.visibility == Visibility.Hidden) + return; + var maxLength = resolvedStyle.width; var fixedPaneLength = m_FixedPane.resolvedStyle.width; var fixedPaneMargins = m_FixedPane.resolvedStyle.marginLeft + m_FixedPane.resolvedStyle.marginRight; diff --git a/ModuleOverrides/com.unity.ui/Core/Events/LinkTagEvent.cs b/ModuleOverrides/com.unity.ui/Core/Events/LinkTagEvent.cs new file mode 100644 index 0000000000..4cb9c7602c --- /dev/null +++ b/ModuleOverrides/com.unity.ui/Core/Events/LinkTagEvent.cs @@ -0,0 +1,273 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +namespace UnityEngine.UIElements.Experimental +{ + /// + /// This event is sent when a pointer enters a link tag. + /// + [EventCategory(EventCategory.EnterLeave)] + public class PointerOverLinkTagEvent : PointerEventBase + { + static PointerOverLinkTagEvent() + { + SetCreateFunction(() => new PointerOverLinkTagEvent()); + } + + /// + /// LinkTag corresponding linkID. + /// + public string linkID { get; private set; } + + /// + /// LinkTag corresponding text. + /// + public string linkText { get; private set; } + + /// + /// Resets the event members to their initial values. + /// + protected override void Init() + { + base.Init(); + LocalInit(); + } + + void LocalInit() + { + propagation = EventPropagation.Bubbles | EventPropagation.TricklesDown; + } + + /// + /// Gets an event from the event pool and initializes it with the given values. Use this function instead of creating new events. Events obtained using this method need to be released back to the pool. You can use `Dispose()` to release them. + /// + /// An initialized event. + public static PointerOverLinkTagEvent GetPooled(IPointerEvent evt, string linkID, string linkText) + { + PointerOverLinkTagEvent e = GetPooled(evt); + e.linkID = linkID; + e.linkText = linkText; + return e; + } + + /// + /// Constructor. + /// + public PointerOverLinkTagEvent() + { + LocalInit(); + } + } + + /// + /// This event is sent when a pointer changes state on a link tag. + /// + [EventCategory(EventCategory.PointerMove)] + public class PointerMoveLinkTagEvent : PointerEventBase + { + static PointerMoveLinkTagEvent() + { + SetCreateFunction(() => new PointerMoveLinkTagEvent()); + } + + /// + /// LinkTag corresponding linkID. + /// + public string linkID { get; private set; } + + /// + /// LinkTag corresponding text. + /// + public string linkText { get; private set; } + + /// + /// Resets the event members to their initial values. + /// + protected override void Init() + { + base.Init(); + LocalInit(); + } + + void LocalInit() + { + propagation = EventPropagation.Bubbles | EventPropagation.TricklesDown; + } + + /// + /// Gets an event from the event pool and initializes it with the given values. Use this function instead of creating new events. Events obtained using this method need to be released back to the pool. You can use `Dispose()` to release them. + /// + /// An initialized event. + public static PointerMoveLinkTagEvent GetPooled(IPointerEvent evt, string linkID, string linkText) + { + PointerMoveLinkTagEvent e = GetPooled(evt); + e.linkID = linkID; + e.linkText = linkText; + return e; + } + + /// + /// Constructor. + /// + public PointerMoveLinkTagEvent() + { + LocalInit(); + } + } + + /// + /// This event is sent when a pointer exits a link tag. + /// + [EventCategory(EventCategory.EnterLeave)] + public class PointerOutLinkTagEvent : PointerEventBase + { + static PointerOutLinkTagEvent() + { + SetCreateFunction(() => new PointerOutLinkTagEvent()); + } + + /// + /// Resets the event members to their initial values. + /// + protected override void Init() + { + base.Init(); + LocalInit(); + } + + void LocalInit() + { + propagation = EventPropagation.Bubbles | EventPropagation.TricklesDown; + } + + /// + /// Gets an event from the event pool and initializes it with the given values. Use this function instead of creating new events. Events obtained using this method need to be released back to the pool. You can use `Dispose()` to release them. + /// + /// An initialized event. + public static PointerOutLinkTagEvent GetPooled(IPointerEvent evt, string linkID) + { + PointerOutLinkTagEvent e = GetPooled(evt); + return e; + } + + /// + /// Constructor. + /// + public PointerOutLinkTagEvent() + { + LocalInit(); + } + } + + /// + /// This event is sent when a pointer is pressed on a Link tag. + /// + public sealed class PointerDownLinkTagEvent : PointerEventBase + { + static PointerDownLinkTagEvent() + { + SetCreateFunction(() => new PointerDownLinkTagEvent()); + } + + /// + /// LinkTag corresponding linkID. + /// + public string linkID { get; private set; } + + /// + /// LinkTag corresponding text. + /// + public string linkText { get; private set; } + + /// + /// Resets the event members to their initial values. + /// + protected override void Init() + { + base.Init(); + LocalInit(); + } + + void LocalInit() + { + propagation = EventPropagation.Bubbles | EventPropagation.TricklesDown; + } + + /// + /// Gets an event from the event pool and initializes it with the given values. Use this function instead of creating new events. Events obtained using this method need to be released back to the pool. You can use `Dispose()` to release them. + /// + /// An initialized event. + public static PointerDownLinkTagEvent GetPooled(IPointerEvent evt, string linkID, string linkText) + { + PointerDownLinkTagEvent e = GetPooled(evt); + e.linkID = linkID; + e.linkText = linkText; + return e; + } + + /// + /// Constructor. Avoid creating new event instances. Instead, use GetPooled() to get an instance from a pool of reusable event instances. + /// + public PointerDownLinkTagEvent() + { + LocalInit(); + } + } + + + /// + /// This event is sent when a pointer's last pressed button is released on a link tag. + /// + public class PointerUpLinkTagEvent : PointerEventBase + { + static PointerUpLinkTagEvent() + { + SetCreateFunction(() => new PointerUpLinkTagEvent()); + } + + /// + /// LinkTag corresponding linkID. + /// + public string linkID { get; private set; } + + /// + /// LinkTag corresponding text. + /// + public string linkText { get; private set; } + + /// + /// Resets the event members to their initial values. + /// + protected override void Init() + { + base.Init(); + LocalInit(); + } + + void LocalInit() + { + propagation = EventPropagation.Bubbles | EventPropagation.TricklesDown; + } + + /// + /// Gets an event from the event pool and initializes it with the given values. Use this function instead of creating new events. Events obtained using this method need to be released back to the pool. You can use `Dispose()` to release them. + /// + /// An initialized event. + public static PointerUpLinkTagEvent GetPooled(IPointerEvent evt, string linkID, string linkText) + { + PointerUpLinkTagEvent e = GetPooled(evt); + e.linkID = linkID; + e.linkText = linkText; + return e; + } + + /// + /// Constructor. + /// + public PointerUpLinkTagEvent() + { + LocalInit(); + } + } +} diff --git a/ModuleOverrides/com.unity.ui/Core/Text/UITKTextHandle.cs b/ModuleOverrides/com.unity.ui/Core/Text/UITKTextHandle.cs index fea45f35b3..77960b5f26 100644 --- a/ModuleOverrides/com.unity.ui/Core/Text/UITKTextHandle.cs +++ b/ModuleOverrides/com.unity.ui/Core/Text/UITKTextHandle.cs @@ -57,8 +57,235 @@ public TextInfo Update() } textGenerationSettings.screenRect = new Rect(Vector2.zero, size); + Update(textGenerationSettings); + HandleATag(); + HandleLinkTag(); - return Update(textGenerationSettings); + return textInfo; + } + + void ATagOnPointerUp(PointerUpEvent pue) + { + var pos = pue.localPosition - new Vector3(m_TextElement.contentRect.min.x, m_TextElement.contentRect.min.y); + var intersectingLink = FindIntersectingLink(pos); + if (intersectingLink < 0) + return; + + var link = textInfo.linkInfo[intersectingLink]; + if (link.hashCode == (int)MarkupTag.HREF) + { + if (link.linkId != null && link.linkIdLength > 0) + { + var href = link.GetLinkId(); + if (Uri.IsWellFormedUriString(href, UriKind.Absolute)) + Application.OpenURL(href); + } + } + } + + internal bool isOverridingCursor = false; + void ATagOnPointerOver(PointerOverEvent _) + { + isOverridingCursor = false; + } + void ATagOnPointerMove(PointerMoveEvent pme) + { + var pos = pme.localPosition - new Vector3(m_TextElement.contentRect.min.x, m_TextElement.contentRect.min.y); + var intersectingLink = FindIntersectingLink(pos); + var cursorManager = (m_TextElement.panel as BaseVisualElementPanel)?.cursorManager; + if (intersectingLink >= 0) + { + var link = textInfo.linkInfo[intersectingLink]; + if (link.hashCode == (int)MarkupTag.HREF) + { + if (!isOverridingCursor) + { + isOverridingCursor = true; + // defaultCursorId maps to the UnityEditor.MouseCursor enum where 4 is the link cursor. + cursorManager?.SetCursor(new Cursor { defaultCursorId = 4 }); + } + + return; + } + } + if (isOverridingCursor) + { + cursorManager?.SetCursor(m_TextElement.computedStyle.cursor); + isOverridingCursor = false; + } + } + + void ATagOnPointerOut(PointerOutEvent _) + { + isOverridingCursor = false; + } + + internal void LinkTagOnPointerDown(PointerDownEvent pde) + { + var pos = pde.localPosition - new Vector3(m_TextElement.contentRect.min.x, m_TextElement.contentRect.min.y); + var intersectingLink = FindIntersectingLink(pos); + if (intersectingLink < 0) + return; + + var link = textInfo.linkInfo[intersectingLink]; + if (link.hashCode != (int)MarkupTag.HREF) + { + if (link.linkId != null && link.linkIdLength > 0) + { + using (Experimental.PointerDownLinkTagEvent e = Experimental.PointerDownLinkTagEvent.GetPooled(pde, link.GetLinkId(), link.GetLinkText(textInfo))) + { + e.target = m_TextElement; + m_TextElement.SendEvent(e); + } + } + } + } + + internal void LinkTagOnPointerUp(PointerUpEvent pue) + { + var pos = pue.localPosition - new Vector3(m_TextElement.contentRect.min.x, m_TextElement.contentRect.min.y); + var intersectingLink = FindIntersectingLink(pos); + if (intersectingLink < 0) + return; + + var link = textInfo.linkInfo[intersectingLink]; + if (link.hashCode != (int)MarkupTag.HREF) + { + if (link.linkId != null && link.linkIdLength > 0) + { + using (Experimental.PointerUpLinkTagEvent e = Experimental.PointerUpLinkTagEvent.GetPooled(pue, link.GetLinkId(), link.GetLinkText(textInfo))) + { + e.target = m_TextElement; + m_TextElement.SendEvent(e); + } + } + } + } + + // Used in automated test + internal int currentLinkIDHash = -1; + internal void LinkTagOnPointerMove(PointerMoveEvent pme) + { + var pos = pme.localPosition - new Vector3(m_TextElement.contentRect.min.x, m_TextElement.contentRect.min.y); + var intersectingLink = FindIntersectingLink(pos); + if (intersectingLink >= 0) + { + var link = textInfo.linkInfo[intersectingLink]; + if (link.hashCode != (int)MarkupTag.HREF) + { + // PointerOver + if (currentLinkIDHash == -1) + { + currentLinkIDHash = link.hashCode; + using (Experimental.PointerOverLinkTagEvent e = Experimental.PointerOverLinkTagEvent.GetPooled(pme, link.GetLinkId(), link.GetLinkText(textInfo))) + { + e.target = m_TextElement; + m_TextElement.SendEvent(e); + } + + return; + } + // PointerMove + if (currentLinkIDHash == link.hashCode) + { + using (Experimental.PointerMoveLinkTagEvent e = Experimental.PointerMoveLinkTagEvent.GetPooled(pme, link.GetLinkId(), link.GetLinkText(textInfo))) + { + e.target = m_TextElement; + m_TextElement.SendEvent(e); + } + + return; + } + } + } + + // PointerOut + if (currentLinkIDHash != -1) + { + currentLinkIDHash = -1; + using (Experimental.PointerOutLinkTagEvent e = Experimental.PointerOutLinkTagEvent.GetPooled(pme, String.Empty)) + { + e.target = m_TextElement; + m_TextElement.SendEvent(e); + } + } + } + + void LinkTagOnPointerOut(PointerOutEvent poe) + { + if (currentLinkIDHash != -1) + { + using (Experimental.PointerOutLinkTagEvent e = Experimental.PointerOutLinkTagEvent.GetPooled(poe, String.Empty)) + { + e.target = m_TextElement; + m_TextElement.SendEvent(e); + } + + currentLinkIDHash = -1; + } + } + + // Used by our automated tests. + internal bool hasLinkTag = false; + void HandleLinkTag() + { + for(int i = 0; i < textInfo.linkCount; i++) + { + var linkInfo = textInfo.linkInfo[i]; + if (linkInfo.hashCode != (int)MarkupTag.HREF) + { + m_TextElement.RegisterCallback(LinkTagOnPointerDown, TrickleDown.TrickleDown); + m_TextElement.RegisterCallback(LinkTagOnPointerUp, TrickleDown.TrickleDown); + m_TextElement.RegisterCallback(LinkTagOnPointerMove, TrickleDown.TrickleDown); + m_TextElement.RegisterCallback(LinkTagOnPointerOut, TrickleDown.TrickleDown); + hasLinkTag = true; + return; + } + } + + if (hasLinkTag) + { + hasLinkTag = false; + m_TextElement.UnregisterCallback(LinkTagOnPointerDown, TrickleDown.TrickleDown); + m_TextElement.UnregisterCallback(LinkTagOnPointerUp, TrickleDown.TrickleDown); + m_TextElement.UnregisterCallback(LinkTagOnPointerMove, TrickleDown.TrickleDown); + m_TextElement.UnregisterCallback(LinkTagOnPointerOut, TrickleDown.TrickleDown); + } + } + + // Used by our automated tests. + internal bool hasATag = false; + void HandleATag() + { + for(int i = 0; i < textInfo.linkCount; i++) + { + var linkInfo = textInfo.linkInfo[i]; + if (linkInfo.hashCode == (int)MarkupTag.HREF) + { + m_TextElement.RegisterCallback(ATagOnPointerUp, TrickleDown.TrickleDown); + // Switching the cursor to the Link cursor has been disable at runtime until OS cursor support is available at runtime. + if (m_TextElement.panel.contextType == ContextType.Editor) + { + m_TextElement.RegisterCallback(ATagOnPointerMove, TrickleDown.TrickleDown); + m_TextElement.RegisterCallback(ATagOnPointerOver, TrickleDown.TrickleDown); + m_TextElement.RegisterCallback(ATagOnPointerOut, TrickleDown.TrickleDown); + } + hasATag = true; + return; + } + } + + if (hasATag) + { + hasATag = false; + m_TextElement.UnregisterCallback(ATagOnPointerUp, TrickleDown.TrickleDown); + if (m_TextElement.panel.contextType == ContextType.Editor) + { + m_TextElement.UnregisterCallback(ATagOnPointerMove, TrickleDown.TrickleDown); + m_TextElement.UnregisterCallback(ATagOnPointerOver, TrickleDown.TrickleDown); + m_TextElement.UnregisterCallback(ATagOnPointerOut, TrickleDown.TrickleDown); + } + } } TextOverflowMode GetTextOverflowMode() @@ -80,7 +307,7 @@ TextOverflowMode GetTextOverflowMode() return TextOverflowMode.Overflow; } - void ConvertUssToTextGenerationSettings(UnityEngine.TextCore.Text.TextGenerationSettings tgs) + internal void ConvertUssToTextGenerationSettings(UnityEngine.TextCore.Text.TextGenerationSettings tgs) { var style = m_TextElement.computedStyle; diff --git a/ModuleOverrides/com.unity.ui/Editor/Delegates/EditorDelegateRegistration.cs b/ModuleOverrides/com.unity.ui/Editor/Delegates/EditorDelegateRegistration.cs index ed6ab32fde..0bab0e6dbc 100644 --- a/ModuleOverrides/com.unity.ui/Editor/Delegates/EditorDelegateRegistration.cs +++ b/ModuleOverrides/com.unity.ui/Editor/Delegates/EditorDelegateRegistration.cs @@ -23,7 +23,6 @@ static EditorDelegateRegistration() PanelSettings.GetOrCreateDefaultTheme = PanelSettingsCreator.GetOrCreateDefaultTheme; DropdownUtility.MakeDropdownFunc = CreateGenericOSMenu; PanelSettings.SetPanelSettingsAssetDirty = EditorUtility.SetDirty; - } private static GenericOSMenu CreateGenericOSMenu() diff --git a/Modules/PhysicsEditor/ArticulatonBodyAnchorTransformTool.cs b/Modules/PhysicsEditor/ArticulatonBodyAnchorTransformTool.cs index 4af01bb5cf..ba9c6497c7 100644 --- a/Modules/PhysicsEditor/ArticulatonBodyAnchorTransformTool.cs +++ b/Modules/PhysicsEditor/ArticulatonBodyAnchorTransformTool.cs @@ -69,12 +69,16 @@ private void DisplayProperAnchorHandle(ArticulationBody body, ref Vector3 anchor { // Anchors are relative to the body here, and that includes scale. // However, we don't want to pass scale to DrawingScope - because that transforms the gizmos themselves. - // For that reason, we add and remove scale manually when drawing. - var bodySpace = Matrix4x4.TRS(Vector3.zero, body.transform.rotation, Vector3.one); + // For that reason, we add and remove scale to the position manually when drawing. + Vector3 lossyScale = body.transform.lossyScale; + Vector3 inverseScale = new Vector3(1 / lossyScale.x, 1 / lossyScale.y, 1 / lossyScale.z); + var bodySpace = Matrix4x4.TRS(body.transform.position, body.transform.rotation, Vector3.one); using (new Handles.DrawingScope(bodySpace)) { - anchorRot = Handles.RotationHandle(anchorRot, body.transform.TransformPoint(anchorPos)); - anchorPos = body.transform.InverseTransformPoint(Handles.PositionHandle(body.transform.TransformPoint(anchorPos), anchorRot)); + Vector3 scaledPos = Vector3.Scale(anchorPos, lossyScale); + anchorRot = Handles.RotationHandle(anchorRot, scaledPos); + // We use the scaled vector to position the gizmo, but we need to unscale it before applying the position when editing + anchorPos = Vector3.Scale(Handles.PositionHandle(scaledPos, anchorRot), inverseScale); } } } diff --git a/Modules/TextCoreTextEngine/Managed/LinkInfo.cs b/Modules/TextCoreTextEngine/Managed/LinkInfo.cs index 746ff063d1..3e41bc86a3 100644 --- a/Modules/TextCoreTextEngine/Managed/LinkInfo.cs +++ b/Modules/TextCoreTextEngine/Managed/LinkInfo.cs @@ -2,6 +2,8 @@ // Copyright (c) Unity Technologies. For terms of use, see // https://unity3d.com/legal/licenses/Unity_Reference_Only_License +using System; + namespace UnityEngine.TextCore.Text { /// @@ -17,6 +19,8 @@ struct LinkInfo public int linkTextLength; internal char[] linkId; + string m_LinkIdString; + string m_LinkTextString; internal void SetLinkId(char[] text, int startIndex, int length) { @@ -24,6 +28,10 @@ internal void SetLinkId(char[] text, int startIndex, int length) for (int i = 0; i < length; i++) linkId[i] = text[startIndex + i]; + + linkIdLength = length; + m_LinkIdString = null; + m_LinkTextString = null; } /// @@ -32,12 +40,11 @@ internal void SetLinkId(char[] text, int startIndex, int length) /// public string GetLinkText(TextInfo textInfo) { - string text = string.Empty; + if (string.IsNullOrEmpty(m_LinkTextString)) + for (int i = linkTextfirstCharacterIndex; i < linkTextfirstCharacterIndex + linkTextLength; i++) + m_LinkTextString += textInfo.textElementInfo[i].character; - for (int i = linkTextfirstCharacterIndex; i < linkTextfirstCharacterIndex + linkTextLength; i++) - text += textInfo.textElementInfo[i].character; - - return text; + return m_LinkTextString; } /// @@ -46,7 +53,10 @@ public string GetLinkText(TextInfo textInfo) /// public string GetLinkId() { - return new string(linkId, 0, linkIdLength); + if (string.IsNullOrEmpty(m_LinkIdString)) + m_LinkIdString = new string(linkId, 0, linkIdLength); + + return m_LinkIdString; } } } diff --git a/Modules/TextCoreTextEngine/Managed/TextGenerator.cs b/Modules/TextCoreTextEngine/Managed/TextGenerator.cs index 65d4ef004a..93084eb08c 100644 --- a/Modules/TextCoreTextEngine/Managed/TextGenerator.cs +++ b/Modules/TextCoreTextEngine/Managed/TextGenerator.cs @@ -51,6 +51,7 @@ internal class TextGenerationSettings public bool extraPadding; public bool parseControlCharacters = true; public bool isOrthographic = true; + public bool tagNoParsing = false; public float characterSpacing; public float wordSpacing; @@ -425,7 +426,7 @@ public static Vector2 GetPreferredValues(TextGenerationSettings settings, TextIn /// /// Array containing the Unicode characters to be parsed. /// - internal UnicodeChar[] m_TextProcessingArray = new UnicodeChar[8]; + internal TextProcessingElement[] m_TextProcessingArray = new TextProcessingElement[8]; /// /// The number of Unicode characters that have been parsed and contained in the m_InternalParsingBuffer @@ -464,7 +465,7 @@ bool vertexBufferAutoSizeReduction /// The source text that contains the missing character. /// The font asset that is missing the requested characters. /// The text component where the requested character is missing. - public delegate void MissingCharacterEventCallback(int unicode, int stringIndex, TextInfo text, FontAsset fontAsset); + public delegate void MissingCharacterEventCallback(uint unicode, int stringIndex, TextInfo text, FontAsset fontAsset); /// /// Event delegate to be called when the requested Unicode character is missing from the font asset. @@ -555,7 +556,7 @@ bool vertexBufferAutoSizeReduction WordWrapState m_SavedLastValidState = new WordWrapState(); WordWrapState m_SavedSoftLineBreakState = new WordWrapState(); TextElementType m_TextElementType; - bool m_IsParsingText; + bool m_isTextLayoutPhase; int m_SpriteIndex; Color32 m_SpriteColor; TextElement m_CachedTextElement; @@ -681,7 +682,7 @@ void GenerateTextMesh(TextGenerationSettings generationSettings, TextInfo textIn m_SizeStack.SetDefault(m_CurrentFontSize); float fontSizeDelta = 0; - int charCode = 0; // Holds the character code of the currently being processed character. + uint charCode = 0; // Holds the character code of the currently being processed character. m_FontStyleInternal = generationSettings.fontStyle; // Set the default style. m_FontWeightInternal = (m_FontStyleInternal & FontStyles.Bold) == FontStyles.Bold ? TextFontWeight.Bold : generationSettings.fontWeight; @@ -839,7 +840,7 @@ void GenerateTextMesh(TextGenerationSettings generationSettings, TextInfo textIn #region Parse Rich Text Tag if (generationSettings.richText && charCode == '<') { - m_IsParsingText = true; + m_isTextLayoutPhase = true; m_TextElementType = TextElementType.Character; int endTagIndex; @@ -866,7 +867,7 @@ void GenerateTextMesh(TextGenerationSettings generationSettings, TextInfo textIn int previousMaterialIndex = m_CurrentMaterialIndex; bool isUsingAltTypeface = textInfo.textElementInfo[m_CharacterCount].isUsingAlternateTypeface; - m_IsParsingText = false; + m_isTextLayoutPhase = false; // Handle potential character substitutions #region Character Substitutions @@ -874,7 +875,7 @@ void GenerateTextMesh(TextGenerationSettings generationSettings, TextInfo textIn if (characterToSubstitute.index == m_CharacterCount) { - charCode = (int)characterToSubstitute.unicode; + charCode = characterToSubstitute.unicode; m_TextElementType = TextElementType.Character; isInjectedCharacter = true; @@ -971,7 +972,7 @@ void GenerateTextMesh(TextGenerationSettings generationSettings, TextInfo textIn // Sprites are assigned in the E000 Private Area + sprite Index if (charCode == '<') - charCode = 57344 + m_SpriteIndex; + charCode = 57344 + (uint)m_SpriteIndex; else m_SpriteColor = Color.white; @@ -3441,7 +3442,7 @@ protected int RestoreWordWrappingState(ref WordWrapState state, TextInfo textInf return index; } - protected bool ValidateHtmlTag(UnicodeChar[] chars, int startIndex, out int endIndex, TextGenerationSettings generationSettings, TextInfo textInfo) + protected bool ValidateHtmlTag(TextProcessingElement[] chars, int startIndex, out int endIndex, TextGenerationSettings generationSettings, TextInfo textInfo) { TextSettings textSettings = generationSettings.textSettings; @@ -3459,7 +3460,7 @@ protected bool ValidateHtmlTag(UnicodeChar[] chars, int startIndex, out int endI for (int i = startIndex; i < chars.Length && chars[i].unicode != 0 && tagCharCount < m_HtmlTag.Length && chars[i].unicode != '<'; i++) { - int unicode = chars[i].unicode; + uint unicode = chars[i].unicode; if (unicode == '>') // ASCII Code of End HTML tag '>' { @@ -3538,7 +3539,7 @@ protected bool ValidateHtmlTag(UnicodeChar[] chars, int startIndex, out int endI m_XmlAttribute[attributeIndex].valueLength = 0; } - else if (attributeFlag != 2) + else { m_XmlAttribute[attributeIndex].valueLength += 1; } @@ -4189,11 +4190,37 @@ protected bool ValidateHtmlTag(UnicodeChar[] chars, int startIndex, out int endI return true; case MarkupTag.A: - return false; + if (m_isTextLayoutPhase && !m_IsCalculatingPreferredValues) + { + if (m_XmlAttribute[1].nameHashCode == (int)MarkupTag.HREF) + { + // Make sure linkInfo array is of appropriate size. + int index = textInfo.linkCount; + + if (index + 1 > textInfo.linkInfo.Length) + TextInfo.Resize(ref textInfo.linkInfo, index + 1); + + textInfo.linkInfo[index].hashCode = (int)MarkupTag.HREF; + textInfo.linkInfo[index].linkTextfirstCharacterIndex = m_CharacterCount; + textInfo.linkInfo[index].linkIdFirstCharacterIndex = startIndex + m_XmlAttribute[1].valueStartIndex; + textInfo.linkInfo[index].SetLinkId(m_HtmlTag, m_XmlAttribute[1].valueStartIndex, m_XmlAttribute[1].valueLength); + } + } + return true; + case MarkupTag.SLASH_A: + if (m_isTextLayoutPhase && !m_IsCalculatingPreferredValues) + { + int index = textInfo.linkCount; + + textInfo.linkInfo[index].linkTextLength = m_CharacterCount - textInfo.linkInfo[index].linkTextfirstCharacterIndex; + + textInfo.linkCount += 1; + } return true; + case MarkupTag.LINK: - if (m_IsParsingText && !m_IsCalculatingPreferredValues) + if (m_isTextLayoutPhase && !m_IsCalculatingPreferredValues) { int index = textInfo.linkCount; @@ -4204,12 +4231,11 @@ protected bool ValidateHtmlTag(UnicodeChar[] chars, int startIndex, out int endI textInfo.linkInfo[index].linkTextfirstCharacterIndex = m_CharacterCount; textInfo.linkInfo[index].linkIdFirstCharacterIndex = startIndex + m_XmlAttribute[0].valueStartIndex; - textInfo.linkInfo[index].linkIdLength = m_XmlAttribute[0].valueLength; textInfo.linkInfo[index].SetLinkId(m_HtmlTag, m_XmlAttribute[0].valueStartIndex, m_XmlAttribute[0].valueLength); } return true; case MarkupTag.SLASH_LINK: - if (m_IsParsingText && !m_IsCalculatingPreferredValues) + if (m_isTextLayoutPhase && !m_IsCalculatingPreferredValues) { if (textInfo.linkCount < textInfo.linkInfo.Length) { @@ -4453,7 +4479,7 @@ protected bool ValidateHtmlTag(UnicodeChar[] chars, int startIndex, out int endI } return true; case MarkupTag.SLASH_CHARACTER_SPACE: - if (!m_IsParsingText) return true; + if (!m_isTextLayoutPhase) return true; // Adjust xAdvance to remove extra space from last character. if (m_CharacterCount > 0) @@ -4648,7 +4674,7 @@ protected bool ValidateHtmlTag(UnicodeChar[] chars, int startIndex, out int endI m_SpriteIndex = (int)m_AttributeParameterValues[0]; - if (m_IsParsingText) + if (m_isTextLayoutPhase) { // It is possible for a sprite to get animated when it ends up being truncated. // Should consider moving the animation of the sprite after text geometry upload. @@ -4864,21 +4890,19 @@ protected bool ValidateHtmlTag(UnicodeChar[] chars, int startIndex, out int endI case MarkupTag.ACTION: int actionID = m_XmlAttribute[0].valueHashCode; - if (m_IsParsingText) + if (m_isTextLayoutPhase) { m_ActionStack.Add(actionID); Debug.Log("Action ID: [" + actionID + "] First character index: " + m_CharacterCount); - - } - //if (m_IsParsingText) + //if (m_isTextLayoutPhase) //{ // TMP_Action action = TMP_Action.GetAction(m_XmlAttribute[0].valueHashCode); //} return true; case MarkupTag.SLASH_ACTION: - if (m_IsParsingText) + if (m_isTextLayoutPhase) { Debug.Log("Action ID: [" + m_ActionStack.CurrentItem() + "] Last character index: " + (m_CharacterCount - 1)); } @@ -5020,9 +5044,14 @@ void SaveGlyphVertexInfo(float padding, float stylePadding, Color32 vertexColor, // Alpha is the lower of the vertex color or tag color alpha used. vertexColor.a = m_FontColor32.a < vertexColor.a ? m_FontColor32.a : vertexColor.a; + bool isColorGlyph = false; + // Handle Vertex Colors & Vertex Color Gradient - if (generationSettings.fontColorGradient == null) + if (generationSettings.fontColorGradient == null || isColorGlyph) { + // Special handling for color glyphs + vertexColor = isColorGlyph ? new Color32(255, 255, 255, vertexColor.a) : vertexColor; + textInfo.textElementInfo[m_CharacterCount].vertexBottomLeft.color = vertexColor; textInfo.textElementInfo[m_CharacterCount].vertexTopLeft.color = vertexColor; textInfo.textElementInfo[m_CharacterCount].vertexTopRight.color = vertexColor; @@ -5057,7 +5086,7 @@ void SaveGlyphVertexInfo(float padding, float stylePadding, Color32 vertexColor, } } - if (m_ColorGradientPreset != null) + if (m_ColorGradientPreset != null && !isColorGlyph) { if (m_ColorGradientPresetIsTinted) { @@ -5205,8 +5234,7 @@ void DrawUnderlineMesh(Vector3 start, Vector3 end, ref int index, float startSca if (m_Underline.character == null) { if (textSettings.displayWarnings) - Debug.LogWarning("Unable to add underline since the primary Font Asset doesn't contain the underline character."); - + Debug.LogWarning("Unable to add underline or strikethrough since the character [0x5F] used by these features is not present in the Font Asset assigned to this text object."); return; } @@ -5510,7 +5538,7 @@ void DisableMasking() m_IsMaskingEnabled = false; } - internal int SetArraySizes(UnicodeChar[] textProcessingArray, TextGenerationSettings generationSettings, TextInfo textInfo) + internal int SetArraySizes(TextProcessingElement[] textProcessingArray, TextGenerationSettings generationSettings, TextInfo textInfo) { TextSettings textSettings = generationSettings.textSettings; @@ -5518,7 +5546,7 @@ internal int SetArraySizes(UnicodeChar[] textProcessingArray, TextGenerationSett m_TotalCharacterCount = 0; m_IsUsingBold = false; - m_IsParsingText = false; + m_isTextLayoutPhase = false; m_TagNoParsing = false; m_FontStyleInternal = generationSettings.fontStyle; m_FontStyleStack.Clear(); @@ -5603,7 +5631,7 @@ internal int SetArraySizes(UnicodeChar[] textProcessingArray, TextGenerationSett if (textInfo.textElementInfo == null || m_TotalCharacterCount >= textInfo.textElementInfo.Length) TextInfo.Resize(ref textInfo.textElementInfo, m_TotalCharacterCount + 1, true); - int unicode = textProcessingArray[i].unicode; + uint unicode = textProcessingArray[i].unicode; int prevMaterialIndex = m_CurrentMaterialIndex; // PARSE XML TAGS @@ -5708,10 +5736,10 @@ internal int SetArraySizes(UnicodeChar[] textProcessingArray, TextGenerationSett DoMissingGlyphCallback(unicode, textProcessingArray[i].stringIndex, m_CurrentFontAsset, textInfo); // Save the original unicode character - int srcGlyph = unicode; + uint srcGlyph = unicode; // Try replacing the missing glyph character by the Settings file Missing Glyph or Square (9633) character. - unicode = textProcessingArray[i].unicode = textSettings.missingCharacterUnicode == 0 ? k_Square : textSettings.missingCharacterUnicode; + unicode = textProcessingArray[i].unicode = (uint)textSettings.missingCharacterUnicode == 0 ? k_Square : (uint)textSettings.missingCharacterUnicode; // Check for the missing glyph character in the currently assigned font asset and its fallbacks character = FontAssetUtilities.GetCharacterFromFontAsset((uint)unicode, m_CurrentFontAsset, true, m_FontStyleInternal, m_FontWeightInternal, out isUsingAlternativeTypeface); @@ -5944,7 +5972,24 @@ internal int SetArraySizes(UnicodeChar[] textProcessingArray, TextGenerationSett internal TextElement GetTextElement(TextGenerationSettings generationSettings, uint unicode, FontAsset fontAsset, FontStyles fontStyle, TextFontWeight fontWeight, out bool isUsingAlternativeTypeface) { + //Debug.Log("Unicode: " + unicode.ToString("X8")); + TextSettings textSettings = generationSettings.textSettings; + // if (m_EmojiFallbackSupport && TextGeneratorUtilities.IsEmoji(unicode)) + // { + // if (TMP_Settings.emojiFallbackTextAssets != null && TMP_Settings.emojiFallbackTextAssets.Count > 0) + // { + // TMP_TextElement textElement = TMP_FontAssetUtilities.GetTextElementFromTextAssets(unicode, fontAsset, TMP_Settings.emojiFallbackTextAssets, true, fontStyle, fontWeight, out isUsingAlternativeTypeface); + // + // if (textElement != null) + // { + // // Add character to font asset lookup cache + // //fontAsset.AddCharacterToLookupCache(unicode, character); + // + // return textElement; + // } + // } + // } Character character = FontAssetUtilities.GetCharacterFromFontAsset(unicode, fontAsset, false, fontStyle, fontWeight, out isUsingAlternativeTypeface); @@ -6131,14 +6176,7 @@ protected void GetUnderlineSpecialCharacter(TextGenerationSettings generationSet Character character = FontAssetUtilities.GetCharacterFromFontAsset(0x5F, fontAsset, false, m_FontStyleInternal, m_FontWeightInternal, out isUsingAlternativeTypeface); if (character != null) - { m_Underline = new SpecialCharacter(character, 0); - } - else - { - if (textSettings.displayWarnings) - Debug.LogWarning("The character used for Underline is not available in font asset [" + fontAsset.name + "]."); - } } /// @@ -6366,7 +6404,7 @@ protected virtual Vector2 CalculatePreferredValues(ref float fontSize, Vector2 m // Parse through Character buffer to read HTML tags and begin creating mesh. for (int i = 0; i < m_TextProcessingArray.Length && m_TextProcessingArray[i].unicode != 0; i++) { - int charCode = m_TextProcessingArray[i].unicode; + uint charCode = m_TextProcessingArray[i].unicode; // Skip characters that have been substituted. if (charCode == 0x1A) @@ -6376,7 +6414,7 @@ protected virtual Vector2 CalculatePreferredValues(ref float fontSize, Vector2 m #region Parse Rich Text Tag if (generationSettings.richText && charCode == k_LesserThan) // '<' { - m_IsParsingText = true; + m_isTextLayoutPhase = true; m_TextElementType = TextElementType.Character; int endTagIndex; @@ -6401,7 +6439,7 @@ protected virtual Vector2 CalculatePreferredValues(ref float fontSize, Vector2 m int prevMaterialIndex = m_CurrentMaterialIndex; bool isUsingAltTypeface = textInfo.textElementInfo[m_CharacterCount].isUsingAlternateTypeface; - m_IsParsingText = false; + m_isTextLayoutPhase = false; // Handle potential character substitutions #region Character Substitutions @@ -6409,7 +6447,7 @@ protected virtual Vector2 CalculatePreferredValues(ref float fontSize, Vector2 m if (characterToSubstitute.index == m_CharacterCount) { - charCode = (int)characterToSubstitute.unicode; + charCode = characterToSubstitute.unicode; m_TextElementType = TextElementType.Character; isInjectedCharacter = true; @@ -6502,7 +6540,7 @@ protected virtual Vector2 CalculatePreferredValues(ref float fontSize, Vector2 m // Sprites are assigned in the E000 Private Area + sprite Index if (charCode == k_LesserThan) - charCode = 57344 + m_SpriteIndex; + charCode = 57344 + (uint)m_SpriteIndex; // The sprite scale calculations are based on the font asset assigned to the text object. if (m_CurrentSpriteAsset.faceInfo.pointSize > 0) @@ -7077,27 +7115,23 @@ protected virtual Vector2 CalculatePreferredValues(ref float fontSize, Vector2 m #region Save Word Wrapping State if ((textWrapMode != TextWrappingMode.NoWrap && textWrapMode != TextWrappingMode.PreserveWhitespaceNoWrap) || generationSettings.overflowMode == TextOverflowMode.Truncate || generationSettings.overflowMode == TextOverflowMode.Ellipsis) { + bool shouldSaveHardLineBreak = false; + bool shouldSaveSoftLineBreak = false; + if ((isWhiteSpace || charCode == k_ZeroWidthSpace || charCode == 0x2D || charCode == 0xAD) && (!m_IsNonBreakingSpace || ignoreNonBreakingSpace) && charCode != 0xA0 && charCode != k_FigureSpace && charCode != k_NonBreakingHyphen && charCode != k_NarrowNoBreakSpace && charCode != k_WordJoiner) { - // We store the state of numerous variables for the most recent Space, LineFeed or Carriage Return to enable them to be restored - // for Word Wrapping. - SaveWordWrappingState(ref internalWordWrapState, i, m_CharacterCount, textInfo); - isFirstWordOfLine = false; + // Ignore Hyphen (0x2D) when preceded by a whitespace + if ((charCode == 0x2D && m_CharacterCount > 0 && char.IsWhiteSpace(textInfo.textElementInfo[m_CharacterCount - 1].character)) == false) + { + isFirstWordOfLine = false; + shouldSaveHardLineBreak = true; - // Reset soft line breaking point since we now have a valid hard break point. - internalSoftLineBreak.previousWordBreak = -1; + // Reset soft line breaking point since we now have a valid hard break point. + internalSoftLineBreak.previousWordBreak = -1; + } } - // Handling for East Asian languages - else if (m_IsNonBreakingSpace == false && - ((charCode > k_HangulJamoStart && charCode < k_HangulJamoEnd || /* Hangul Jamo */ - charCode > k_HangulJameExtendedStart && charCode < k_HangulJameExtendedEnd || /* Hangul Jamo Extended-A */ - charCode > k_HangulSyllablesStart && charCode < k_HangulSyllablesEnd) && /* Hangul Syllables */ - textSettings.lineBreakingRules.useModernHangulLineBreakingRules == false || - - (charCode > k_CjkStart && charCode < k_CjkEnd || /* CJK */ - charCode > k_CjkIdeographsStart && charCode < k_CjkIdeographsEnd || /* CJK Compatibility Ideographs */ - charCode > k_CjkFormsStart && charCode < k_CjkFormsEnd || /* CJK Compatibility Forms */ - charCode > k_CjkHalfwidthStart && charCode < k_CjkHalfwidthEnd))) /* CJK Halfwidth */ + // Handling for East Asian scripts + else if (m_IsNonBreakingSpace == false && (TextGeneratorUtilities.IsHangul((uint)charCode) && textSettings.useModernHangulLineBreakingRules == false || TextGeneratorUtilities.IsCJK((uint)charCode))) { bool isCurrentLeadingCharacter = textSettings.lineBreakingRules.leadingCharactersLookup.Contains((uint)charCode); bool isNextFollowingCharacter = m_CharacterCount < totalCharacterCount - 1 && textSettings.lineBreakingRules.leadingCharactersLookup.Contains(m_InternalTextElementInfo[m_CharacterCount + 1].character); @@ -7106,17 +7140,17 @@ protected virtual Vector2 CalculatePreferredValues(ref float fontSize, Vector2 m { if (isNextFollowingCharacter == false) { - SaveWordWrappingState(ref internalWordWrapState, i, m_CharacterCount, textInfo); isFirstWordOfLine = false; + shouldSaveHardLineBreak = true; } if (isFirstWordOfLine) { // Special handling for non-breaking space and soft line breaks if (isWhiteSpace) - SaveWordWrappingState(ref internalSoftLineBreak, i, m_CharacterCount, textInfo); + shouldSaveSoftLineBreak = true; - SaveWordWrappingState(ref internalWordWrapState, i, m_CharacterCount, textInfo); + shouldSaveHardLineBreak = true; } } else @@ -7125,9 +7159,9 @@ protected virtual Vector2 CalculatePreferredValues(ref float fontSize, Vector2 m { // Special handling for non-breaking space and soft line breaks if (isWhiteSpace) - SaveWordWrappingState(ref m_SavedSoftLineBreakState, i, m_CharacterCount, textInfo); + shouldSaveSoftLineBreak = true; - SaveWordWrappingState(ref m_SavedWordWrapState, i, m_CharacterCount, textInfo); + shouldSaveHardLineBreak = true; } } } @@ -7135,10 +7169,18 @@ protected virtual Vector2 CalculatePreferredValues(ref float fontSize, Vector2 m { // Special handling for non-breaking space and soft line breaks if (isWhiteSpace && charCode != 0xA0 || (charCode == 0xAD && isSoftHyphenIgnored == false)) - SaveWordWrappingState(ref internalSoftLineBreak, i, m_CharacterCount, textInfo); + shouldSaveSoftLineBreak = true; - SaveWordWrappingState(ref internalWordWrapState, i, m_CharacterCount, textInfo); + shouldSaveHardLineBreak = true; } + + // Save potential Hard lines break + if (shouldSaveHardLineBreak) + SaveWordWrappingState(ref internalWordWrapState, i, m_CharacterCount, textInfo); + + // Save potential Soft line break + if (shouldSaveSoftLineBreak) + SaveWordWrappingState(ref internalSoftLineBreak, i, m_CharacterCount, textInfo); } #endregion Save Word Wrapping State @@ -7330,11 +7372,13 @@ void PopulateTextProcessingArray(TextGenerationSettings generationSettings) int writeIndex = 0; int styleHashCode = m_TextStyleStacks[0].Pop(); - TextStyle style = TextGeneratorUtilities.GetStyle(generationSettings, styleHashCode); + TextStyle textStyle = TextGeneratorUtilities.GetStyle(generationSettings, styleHashCode); // Insert Opening Style - if (style != null && style.hashCode != (int)MarkupTag.NORMAL) - TextGeneratorUtilities.InsertOpeningStyleTag(style, 0, ref m_TextProcessingArray, ref writeIndex, ref m_TextStyleStackDepth, ref m_TextStyleStacks, ref generationSettings); + if (textStyle != null && textStyle.hashCode != (int)MarkupTag.NORMAL) + TextGeneratorUtilities.InsertOpeningStyleTag(textStyle, ref m_TextProcessingArray, ref writeIndex, ref m_TextStyleStackDepth, ref m_TextStyleStacks, ref generationSettings); + + var tagNoParsing = generationSettings.tagNoParsing; int readIndex = 0; for (; readIndex < srcLength; readIndex++) @@ -7346,36 +7390,19 @@ void PopulateTextProcessingArray(TextGenerationSettings generationSettings) // TODO: Since we do not set the TextInputSource at this very moment, we have defaulted this conditional to check for // TextInputSource.TextString whereas in TMP, it checks for TextInputSource.TextInputBox - if (generationSettings.inputSource == TextInputSource.TextString && c == '\\' && readIndex < srcLength - 1) + if (/*generationSettings.inputSource == TextInputSource.TextString && */ c == '\\' && readIndex < srcLength - 1) { switch (m_TextBackingArray[readIndex + 1]) { case 92: // \ escape if (!generationSettings.parseControlCharacters) break; - if (srcLength <= readIndex + 2) break; - - if (writeIndex + 2 > m_TextProcessingArray.Length) TextGeneratorUtilities.ResizeInternalArray(ref m_TextProcessingArray); - - m_TextProcessingArray[writeIndex].unicode = (int)m_TextBackingArray[readIndex + 1]; - m_TextProcessingArray[writeIndex].stringIndex = readIndex; - m_TextProcessingArray[writeIndex].length = 1; - - m_TextProcessingArray[writeIndex + 1].unicode = (int)m_TextBackingArray[readIndex + 2]; - m_TextProcessingArray[writeIndex + 1].stringIndex = readIndex; - m_TextProcessingArray[writeIndex + 1].length = 1; - - readIndex += 2; - writeIndex += 2; - continue; + readIndex += 1; + break; case 110: // \n LineFeed if (!generationSettings.parseControlCharacters) break; - if (writeIndex == m_TextProcessingArray.Length) TextGeneratorUtilities.ResizeInternalArray(ref m_TextProcessingArray); - - m_TextProcessingArray[writeIndex].unicode = 10; - m_TextProcessingArray[writeIndex].stringIndex = readIndex; - m_TextProcessingArray[writeIndex].length = 1; + m_TextProcessingArray[writeIndex] = new TextProcessingElement { elementType = TextProcessingElementType.TextCharacterElement, stringIndex = readIndex, length = 1, unicode = 10 }; readIndex += 1; writeIndex += 1; @@ -7383,11 +7410,7 @@ void PopulateTextProcessingArray(TextGenerationSettings generationSettings) case 114: // \r Carriage Return if (!generationSettings.parseControlCharacters) break; - if (writeIndex == m_TextProcessingArray.Length) TextGeneratorUtilities.ResizeInternalArray(ref m_TextProcessingArray); - - m_TextProcessingArray[writeIndex].unicode = 13; - m_TextProcessingArray[writeIndex].stringIndex = readIndex; - m_TextProcessingArray[writeIndex].length = 1; + m_TextProcessingArray[writeIndex] = new TextProcessingElement { elementType = TextProcessingElementType.TextCharacterElement, stringIndex = readIndex, length = 1, unicode = 13 }; readIndex += 1; writeIndex += 1; @@ -7395,11 +7418,7 @@ void PopulateTextProcessingArray(TextGenerationSettings generationSettings) case 116: // \t Tab if (!generationSettings.parseControlCharacters) break; - if (writeIndex == m_TextProcessingArray.Length) TextGeneratorUtilities.ResizeInternalArray(ref m_TextProcessingArray); - - m_TextProcessingArray[writeIndex].unicode = 9; - m_TextProcessingArray[writeIndex].stringIndex = readIndex; - m_TextProcessingArray[writeIndex].length = 1; + m_TextProcessingArray[writeIndex] = new TextProcessingElement { elementType = TextProcessingElementType.TextCharacterElement, stringIndex = readIndex, length = 1, unicode = 9 }; readIndex += 1; writeIndex += 1; @@ -7407,11 +7426,7 @@ void PopulateTextProcessingArray(TextGenerationSettings generationSettings) case 118: // \v Vertical tab used as soft line break if (!generationSettings.parseControlCharacters) break; - if (writeIndex == m_TextProcessingArray.Length) TextGeneratorUtilities.ResizeInternalArray(ref m_TextProcessingArray); - - m_TextProcessingArray[writeIndex].unicode = 11; - m_TextProcessingArray[writeIndex].stringIndex = readIndex; - m_TextProcessingArray[writeIndex].length = 1; + m_TextProcessingArray[writeIndex] = new TextProcessingElement { elementType = TextProcessingElementType.TextCharacterElement, stringIndex = readIndex, length = 1, unicode = 11 }; readIndex += 1; writeIndex += 1; @@ -7419,11 +7434,7 @@ void PopulateTextProcessingArray(TextGenerationSettings generationSettings) case 117: // \u0000 for UTF-16 Unicode if (srcLength > readIndex + 5 && TextGeneratorUtilities.IsValidUTF16(m_TextBackingArray, readIndex + 2)) { - if (writeIndex == m_TextProcessingArray.Length) TextGeneratorUtilities.ResizeInternalArray(ref m_TextProcessingArray); - - m_TextProcessingArray[writeIndex].unicode = TextGeneratorUtilities.GetUTF16(m_TextBackingArray, readIndex + 2); - m_TextProcessingArray[writeIndex].stringIndex = readIndex; - m_TextProcessingArray[writeIndex].length = 6; + m_TextProcessingArray[writeIndex] = new TextProcessingElement { elementType = TextProcessingElementType.TextCharacterElement, stringIndex = readIndex, length = 6, unicode = TextGeneratorUtilities.GetUTF16(m_TextBackingArray, readIndex + 2) }; readIndex += 5; writeIndex += 1; @@ -7433,11 +7444,7 @@ void PopulateTextProcessingArray(TextGenerationSettings generationSettings) case 85: // \U00000000 for UTF-32 Unicode if (srcLength > readIndex + 9 && TextGeneratorUtilities.IsValidUTF32(m_TextBackingArray, readIndex + 2)) { - if (writeIndex == m_TextProcessingArray.Length) TextGeneratorUtilities.ResizeInternalArray(ref m_TextProcessingArray); - - m_TextProcessingArray[writeIndex].unicode = TextGeneratorUtilities.GetUTF32(m_TextBackingArray, readIndex + 2); - m_TextProcessingArray[writeIndex].stringIndex = readIndex; - m_TextProcessingArray[writeIndex].length = 10; + m_TextProcessingArray[writeIndex] = new TextProcessingElement { elementType = TextProcessingElementType.TextCharacterElement, stringIndex = readIndex, length = 10, unicode = TextGeneratorUtilities.GetUTF32(m_TextBackingArray, readIndex + 2) }; readIndex += 9; writeIndex += 1; @@ -7447,14 +7454,10 @@ void PopulateTextProcessingArray(TextGenerationSettings generationSettings) } } - // Handle surrogate pair conversion in string, StringBuilder and char[] source. + // Handle surrogate pair conversion if (c >= CodePoint.HIGH_SURROGATE_START && c <= CodePoint.HIGH_SURROGATE_END && srcLength > readIndex + 1 && m_TextBackingArray[readIndex + 1] >= CodePoint.LOW_SURROGATE_START && m_TextBackingArray[readIndex + 1] <= CodePoint.LOW_SURROGATE_END) { - if (writeIndex == m_TextProcessingArray.Length) TextGeneratorUtilities.ResizeInternalArray(ref m_TextProcessingArray); - - m_TextProcessingArray[writeIndex].unicode = (int)TextGeneratorUtilities.ConvertToUTF32(c, m_TextBackingArray[readIndex + 1]); - m_TextProcessingArray[writeIndex].stringIndex = readIndex; - m_TextProcessingArray[writeIndex].length = 2; + m_TextProcessingArray[writeIndex] = new TextProcessingElement { elementType = TextProcessingElementType.TextCharacterElement, stringIndex = readIndex, length = 2, unicode = TextGeneratorUtilities.ConvertToUTF32(c, m_TextBackingArray[readIndex + 1]) }; readIndex += 1; writeIndex += 1; @@ -7469,57 +7472,69 @@ void PopulateTextProcessingArray(TextGenerationSettings generationSettings) switch ((MarkupTag)hashCode) { + case MarkupTag.NO_PARSE: + tagNoParsing = true; + break; + case MarkupTag.SLASH_NO_PARSE: + tagNoParsing = false; + break; case MarkupTag.BR: + if (tagNoParsing) break; if (writeIndex == m_TextProcessingArray.Length) TextGeneratorUtilities.ResizeInternalArray(ref m_TextProcessingArray); - m_TextProcessingArray[writeIndex].unicode = 10; - m_TextProcessingArray[writeIndex].stringIndex = readIndex; - m_TextProcessingArray[writeIndex].length = 4; - + m_TextProcessingArray[writeIndex] = new TextProcessingElement { elementType = TextProcessingElementType.TextCharacterElement, stringIndex = readIndex, length = 4, unicode = 10 }; writeIndex += 1; readIndex += 3; continue; case MarkupTag.CR: + if (tagNoParsing) break; if (writeIndex == m_TextProcessingArray.Length) TextGeneratorUtilities.ResizeInternalArray(ref m_TextProcessingArray); - m_TextProcessingArray[writeIndex].unicode = 13; - m_TextProcessingArray[writeIndex].stringIndex = readIndex; - m_TextProcessingArray[writeIndex].length = 4; - + m_TextProcessingArray[writeIndex] = new TextProcessingElement { elementType = TextProcessingElementType.TextCharacterElement, stringIndex = readIndex, length = 4, unicode = 13 }; writeIndex += 1; readIndex += 3; continue; case MarkupTag.NBSP: + if (tagNoParsing) break; if (writeIndex == m_TextProcessingArray.Length) TextGeneratorUtilities.ResizeInternalArray(ref m_TextProcessingArray); - m_TextProcessingArray[writeIndex].unicode = 160; - m_TextProcessingArray[writeIndex].stringIndex = readIndex; - m_TextProcessingArray[writeIndex].length = 6; - + m_TextProcessingArray[writeIndex] = new TextProcessingElement { elementType = TextProcessingElementType.TextCharacterElement, stringIndex = readIndex, length = 6, unicode = 0xA0 }; writeIndex += 1; readIndex += 5; continue; case MarkupTag.ZWSP: + if (tagNoParsing) break; if (writeIndex == m_TextProcessingArray.Length) TextGeneratorUtilities.ResizeInternalArray(ref m_TextProcessingArray); - m_TextProcessingArray[writeIndex].unicode = k_ZeroWidthSpace; - m_TextProcessingArray[writeIndex].stringIndex = readIndex; - m_TextProcessingArray[writeIndex].length = 6; - + m_TextProcessingArray[writeIndex] = new TextProcessingElement { elementType = TextProcessingElementType.TextCharacterElement, stringIndex = readIndex, length = 6, unicode = 0x200B }; writeIndex += 1; readIndex += 5; continue; + case MarkupTag.ZWJ: + if (tagNoParsing) break; + if (writeIndex == m_TextProcessingArray.Length) TextGeneratorUtilities.ResizeInternalArray(ref m_TextProcessingArray); + + m_TextProcessingArray[writeIndex] = new TextProcessingElement { elementType = TextProcessingElementType.TextCharacterElement, stringIndex = readIndex, length = 5, unicode = 0x200D }; + writeIndex += 1; + readIndex += 4; + continue; case MarkupTag.SHY: + if (tagNoParsing) break; if (writeIndex == m_TextProcessingArray.Length) TextGeneratorUtilities.ResizeInternalArray(ref m_TextProcessingArray); - m_TextProcessingArray[writeIndex].unicode = 0xAD; - m_TextProcessingArray[writeIndex].stringIndex = readIndex; - m_TextProcessingArray[writeIndex].length = 5; + m_TextProcessingArray[writeIndex] = new TextProcessingElement { elementType = TextProcessingElementType.TextCharacterElement, stringIndex = readIndex, length = 5, unicode = 0xAD }; writeIndex += 1; readIndex += 4; continue; + case MarkupTag.A: + // Additional check + if (m_TextBackingArray.Count > readIndex + 4 && m_TextBackingArray[readIndex + 3] == 'h' && m_TextBackingArray[readIndex + 4] == 'r') + TextGeneratorUtilities.InsertOpeningTextStyle(TextGeneratorUtilities.GetStyle(generationSettings, (int)MarkupTag.A), ref m_TextProcessingArray, ref writeIndex, ref m_TextStyleStackDepth, ref m_TextStyleStacks, ref generationSettings); + break; case MarkupTag.STYLE: + if (tagNoParsing) break; + int openWriteIndex = writeIndex; if (TextGeneratorUtilities.ReplaceOpeningStyleTag(ref m_TextBackingArray, readIndex, out int srcOffset, ref m_TextProcessingArray, ref writeIndex, ref m_TextStyleStackDepth, ref m_TextStyleStacks, ref generationSettings)) { @@ -7534,9 +7549,14 @@ void PopulateTextProcessingArray(TextGenerationSettings generationSettings) continue; } break; + case MarkupTag.SLASH_A: + TextGeneratorUtilities.InsertClosingTextStyle(TextGeneratorUtilities.GetStyle(generationSettings, (int)MarkupTag.A), ref m_TextProcessingArray, ref writeIndex, ref m_TextStyleStackDepth, ref m_TextStyleStacks, ref generationSettings); + break; case MarkupTag.SLASH_STYLE: + if (tagNoParsing) break; + int closeWriteIndex = writeIndex; - TextGeneratorUtilities.ReplaceClosingStyleTag(ref m_TextBackingArray, readIndex, ref m_TextProcessingArray, ref writeIndex, ref m_TextStyleStackDepth, ref m_TextStyleStacks, ref generationSettings); + TextGeneratorUtilities.ReplaceClosingStyleTag(ref m_TextProcessingArray, ref writeIndex, ref m_TextStyleStackDepth, ref m_TextStyleStacks, ref generationSettings); // Update potential text elements added by the closing style. for (; closeWriteIndex < writeIndex; closeWriteIndex++) @@ -7548,13 +7568,21 @@ void PopulateTextProcessingArray(TextGenerationSettings generationSettings) readIndex += 7; continue; } + + // Validate potential text markup element + // if (TryGetTextMarkupElement(m_TextBackingArray.Text, ref readIndex, out TextProcessingElement markupElement)) + // { + // m_TextProcessingArray[writeIndex] = markupElement; + // writeIndex += 1; + // continue; + // } } + // Lookup character and glyph data + // TODO: Add future implementation for character and glyph lookups if (writeIndex == m_TextProcessingArray.Length) TextGeneratorUtilities.ResizeInternalArray(ref m_TextProcessingArray); - m_TextProcessingArray[writeIndex].unicode = (int)c; - m_TextProcessingArray[writeIndex].stringIndex = readIndex; - m_TextProcessingArray[writeIndex].length = 1; + m_TextProcessingArray[writeIndex] = new TextProcessingElement { elementType = TextProcessingElementType.TextCharacterElement, stringIndex = readIndex, length = 1, unicode = c }; writeIndex += 1; } @@ -7562,7 +7590,7 @@ void PopulateTextProcessingArray(TextGenerationSettings generationSettings) m_TextStyleStackDepth = 0; // Insert Closing Style - if (style != null && style.hashCode != (int)MarkupTag.NORMAL) + if (textStyle != null && textStyle.hashCode != (int)MarkupTag.NORMAL) TextGeneratorUtilities.InsertClosingStyleTag(ref m_TextProcessingArray, ref writeIndex, ref m_TextStyleStackDepth, ref m_TextStyleStacks, ref generationSettings); if (writeIndex == m_TextProcessingArray.Length) TextGeneratorUtilities.ResizeInternalArray(ref m_TextProcessingArray); @@ -7651,7 +7679,7 @@ void InsertNewLine(int i, float baseScale, float currentElementScale, float curr m_XAdvance = 0 + m_TagIndent; } - protected void DoMissingGlyphCallback(int unicode, int stringIndex, FontAsset fontAsset, TextInfo textInfo) + protected void DoMissingGlyphCallback(uint unicode, int stringIndex, FontAsset fontAsset, TextInfo textInfo) { // Event to allow users to modify the content of the text info before the text is rendered. OnMissingCharacter?.Invoke(unicode, stringIndex, textInfo, fontAsset); diff --git a/Modules/TextCoreTextEngine/Managed/TextGeneratorUtilities.cs b/Modules/TextCoreTextEngine/Managed/TextGeneratorUtilities.cs index 453d398637..6176906f12 100644 --- a/Modules/TextCoreTextEngine/Managed/TextGeneratorUtilities.cs +++ b/Modules/TextCoreTextEngine/Managed/TextGeneratorUtilities.cs @@ -45,10 +45,12 @@ internal struct RichTextTagAttribute public TagUnitType unitType; } + // TODO TextProcessingElement is defined twice in TMP... [System.Diagnostics.DebuggerDisplay("Unicode ({unicode}) '{(char)unicode}'")] - internal struct UnicodeChar + internal struct TextProcessingElement { - public int unicode; + public TextProcessingElementType elementType; + public uint unicode; public int stringIndex; public int length; } @@ -58,6 +60,11 @@ internal struct UnicodeChar /// struct TextBackingContainer { + public uint[] Text + { + get { return m_Array; } + } + public int Capacity { get { return m_Array.Length; } @@ -467,7 +474,7 @@ public static Color32 HexCharsToColor(char[] hexChars, int startIndex, int lengt /// /// /// - public static int HexToInt(char hex) + public static uint HexToInt(char hex) { switch (hex) { @@ -624,167 +631,167 @@ public static Vector2 PackUV(float x, float y, float scale) return output; } - /// - /// Method to store the content of a string into an integer array. - /// - /// - /// - /// - /// - public static void StringToCharArray(string sourceText, ref int[] charBuffer, ref TextProcessingStack styleStack, TextGenerationSettings generationSettings) - { - if (sourceText == null) - { - charBuffer[0] = 0; - return; - } - - if (charBuffer == null) - charBuffer = new int[8]; - - // Clear the Style stack. - styleStack.SetDefault(0); - - int writeIndex = 0; - - for (int i = 0; i < sourceText.Length; i++) - { - if (sourceText[i] == 92 && sourceText.Length > i + 1) - { - switch ((int)sourceText[i + 1]) - { - case 85: // \U00000000 for UTF-32 Unicode - if (sourceText.Length > i + 9) - { - if (writeIndex == charBuffer.Length) - ResizeInternalArray(ref charBuffer); - - charBuffer[writeIndex] = GetUtf32(sourceText, i + 2); - i += 9; - writeIndex += 1; - continue; - } - break; - case 92: // \ escape - if (!generationSettings.parseControlCharacters) - break; - - if (sourceText.Length <= i + 2) - break; - - if (writeIndex + 2 > charBuffer.Length) - ResizeInternalArray(ref charBuffer); - - charBuffer[writeIndex] = sourceText[i + 1]; - charBuffer[writeIndex + 1] = sourceText[i + 2]; - i += 2; - writeIndex += 2; - continue; - case 110: // \n LineFeed - if (!generationSettings.parseControlCharacters) - break; - - if (writeIndex == charBuffer.Length) - ResizeInternalArray(ref charBuffer); - - charBuffer[writeIndex] = (char)10; - i += 1; - writeIndex += 1; - continue; - case 114: // \r - if (!generationSettings.parseControlCharacters) - break; - - if (writeIndex == charBuffer.Length) - ResizeInternalArray(ref charBuffer); - - charBuffer[writeIndex] = (char)13; - i += 1; - writeIndex += 1; - continue; - case 116: // \t Tab - if (!generationSettings.parseControlCharacters) - break; - - if (writeIndex == charBuffer.Length) - ResizeInternalArray(ref charBuffer); - - charBuffer[writeIndex] = (char)9; - i += 1; - writeIndex += 1; - continue; - case 117: // \u0000 for UTF-16 Unicode - if (sourceText.Length > i + 5) - { - if (writeIndex == charBuffer.Length) - ResizeInternalArray(ref charBuffer); - - charBuffer[writeIndex] = (char)GetUtf16(sourceText, i + 2); - i += 5; - writeIndex += 1; - continue; - } - break; - } - } - - // Handle UTF-32 in the input text (string). // Not sure this is needed // - if (Char.IsHighSurrogate(sourceText[i]) && Char.IsLowSurrogate(sourceText[i + 1])) - { - if (writeIndex == charBuffer.Length) - ResizeInternalArray(ref charBuffer); - - charBuffer[writeIndex] = Char.ConvertToUtf32(sourceText[i], sourceText[i + 1]); - i += 1; - writeIndex += 1; - continue; - } - - //// Handle inline replacement of and
tags. - if (sourceText[i] == 60 && generationSettings.richText) - { - if (IsTagName(ref sourceText, "
", i)) - { - if (writeIndex == charBuffer.Length) - ResizeInternalArray(ref charBuffer); - - charBuffer[writeIndex] = 10; - writeIndex += 1; - i += 3; - - continue; - } - if (IsTagName(ref sourceText, "", i)) - { - ReplaceClosingStyleTag(ref charBuffer, ref writeIndex, ref styleStack, ref generationSettings); - - // Strip even if style is invalid. - i += 7; - continue; - } - } - - if (writeIndex == charBuffer.Length) - ResizeInternalArray(ref charBuffer); - - charBuffer[writeIndex] = sourceText[i]; - writeIndex += 1; - } - - if (writeIndex == charBuffer.Length) - ResizeInternalArray(ref charBuffer); - - charBuffer[writeIndex] = (char)0; - } + // /// + // /// Method to store the content of a string into an integer array. + // /// + // /// + // /// + // /// + // /// + // public static void StringToCharArray(string sourceText, ref int[] charBuffer, ref TextProcessingStack styleStack, TextGenerationSettings generationSettings) + // { + // if (sourceText == null) + // { + // charBuffer[0] = 0; + // return; + // } + // + // if (charBuffer == null) + // charBuffer = new int[8]; + // + // // Clear the Style stack. + // styleStack.SetDefault(0); + // + // int writeIndex = 0; + // + // for (int i = 0; i < sourceText.Length; i++) + // { + // if (sourceText[i] == 92 && sourceText.Length > i + 1) + // { + // switch ((int)sourceText[i + 1]) + // { + // case 85: // \U00000000 for UTF-32 Unicode + // if (sourceText.Length > i + 9) + // { + // if (writeIndex == charBuffer.Length) + // ResizeInternalArray(ref charBuffer); + // + // charBuffer[writeIndex] = GetUtf32(sourceText, i + 2); + // i += 9; + // writeIndex += 1; + // continue; + // } + // break; + // case 92: // \ escape + // if (!generationSettings.parseControlCharacters) + // break; + // + // if (sourceText.Length <= i + 2) + // break; + // + // if (writeIndex + 2 > charBuffer.Length) + // ResizeInternalArray(ref charBuffer); + // + // charBuffer[writeIndex] = sourceText[i + 1]; + // charBuffer[writeIndex + 1] = sourceText[i + 2]; + // i += 2; + // writeIndex += 2; + // continue; + // case 110: // \n LineFeed + // if (!generationSettings.parseControlCharacters) + // break; + // + // if (writeIndex == charBuffer.Length) + // ResizeInternalArray(ref charBuffer); + // + // charBuffer[writeIndex] = (char)10; + // i += 1; + // writeIndex += 1; + // continue; + // case 114: // \r + // if (!generationSettings.parseControlCharacters) + // break; + // + // if (writeIndex == charBuffer.Length) + // ResizeInternalArray(ref charBuffer); + // + // charBuffer[writeIndex] = (char)13; + // i += 1; + // writeIndex += 1; + // continue; + // case 116: // \t Tab + // if (!generationSettings.parseControlCharacters) + // break; + // + // if (writeIndex == charBuffer.Length) + // ResizeInternalArray(ref charBuffer); + // + // charBuffer[writeIndex] = (char)9; + // i += 1; + // writeIndex += 1; + // continue; + // case 117: // \u0000 for UTF-16 Unicode + // if (sourceText.Length > i + 5) + // { + // if (writeIndex == charBuffer.Length) + // ResizeInternalArray(ref charBuffer); + // + // charBuffer[writeIndex] = (char)GetUtf16(sourceText, i + 2); + // i += 5; + // writeIndex += 1; + // continue; + // } + // break; + // } + // } + // + // // Handle UTF-32 in the input text (string). // Not sure this is needed // + // if (Char.IsHighSurrogate(sourceText[i]) && Char.IsLowSurrogate(sourceText[i + 1])) + // { + // if (writeIndex == charBuffer.Length) + // ResizeInternalArray(ref charBuffer); + // + // charBuffer[writeIndex] = Char.ConvertToUtf32(sourceText[i], sourceText[i + 1]); + // i += 1; + // writeIndex += 1; + // continue; + // } + // + // //// Handle inline replacement of and
tags. + // if (sourceText[i] == 60 && generationSettings.richText) + // { + // if (IsTagName(ref sourceText, "
", i)) + // { + // if (writeIndex == charBuffer.Length) + // ResizeInternalArray(ref charBuffer); + // + // charBuffer[writeIndex] = 10; + // writeIndex += 1; + // i += 3; + // + // continue; + // } + // if (IsTagName(ref sourceText, "", i)) + // { + // ReplaceClosingStyleTag(ref charBuffer, ref writeIndex, ref styleStack, ref generationSettings); + // + // // Strip even if style is invalid. + // i += 7; + // continue; + // } + // } + // + // if (writeIndex == charBuffer.Length) + // ResizeInternalArray(ref charBuffer); + // + // charBuffer[writeIndex] = sourceText[i]; + // writeIndex += 1; + // } + // + // if (writeIndex == charBuffer.Length) + // ResizeInternalArray(ref charBuffer); + // + // charBuffer[writeIndex] = (char)0; + // } public static void ResizeInternalArray(ref T[] array) { @@ -842,78 +849,104 @@ static bool IsTagName(ref int[] text, string tag, int index) return true; } + internal static void InsertOpeningTextStyle(TextStyle style, ref TextProcessingElement[] charBuffer, ref int writeIndex, ref int textStyleStackDepth, ref TextProcessingStack[] textStyleStacks, ref TextGenerationSettings generationSettings) + { + // Return if we don't have a valid style. + if (style == null) + return; + + // Increase style depth + textStyleStackDepth += 1; + + // Push style hashcode onto stack + textStyleStacks[textStyleStackDepth].Push(style.hashCode); + + // Replace ", i)) - { - ReplaceClosingStyleTag(ref charBuffer, ref writeIndex, ref styleStack, ref generationSettings); + /// + /// Method to handle inline replacement of style tag by opening style definition. + /// + /// + /// + /// + /// + /// + /// + public static void ReplaceOpeningStyleTag(ref TextProcessingElement[] charBuffer, ref int writeIndex, ref int textStyleStackDepth, ref TextProcessingStack[] textStyleStacks, ref TextGenerationSettings generationSettings) + { + // Get style from the Style Stack + int styleHashCode = textStyleStacks[textStyleStackDepth + 1].Pop(); + TextStyle style = GetStyle(generationSettings, styleHashCode); - // Strip even if style is invalid. - i += 7; - continue; - } - } + // Return if we don't have a valid style. + if (style == null) return; - if (writeIndex == charBuffer.Length) - ResizeInternalArray(ref charBuffer); + // Increase style depth + textStyleStackDepth += 1; - charBuffer[writeIndex] = c; - writeIndex += 1; - } + // Replace ", i)) - { - ReplaceClosingStyleTag(ref charBuffer, ref writeIndex, ref styleStack, ref generationSettings); - - // Strip even if style is invalid. - i += 7; - continue; - } - } + InsertTextStyleInTextProcessingArray(ref charBuffer, ref writeIndex, styleDefinition, ref textStyleStackDepth, ref textStyleStacks, ref generationSettings); - if (writeIndex == charBuffer.Length) - ResizeInternalArray(ref charBuffer); - - charBuffer[writeIndex] = c; - writeIndex += 1; - } + textStyleStackDepth -= 1; return true; } + /// /// Method to handle inline replacement of style tag by closing style definition. /// + /// + /// /// /// - /// /// - public static void ReplaceClosingStyleTag(ref int[] charBuffer, ref int writeIndex, ref TextProcessingStack styleStack, ref TextGenerationSettings generationSettings) + public static void ReplaceClosingStyleTag(ref TextProcessingElement[] charBuffer, ref int writeIndex, ref int textStyleStackDepth, ref TextProcessingStack[] textStyleStacks, ref TextGenerationSettings generationSettings) { // Get style from the Style Stack - int hashCode = styleStack.CurrentItem(); - TextStyle style = GetStyle(generationSettings, hashCode); - - styleStack.Remove(); + int styleHashCode = textStyleStacks[textStyleStackDepth + 1].Pop(); + TextStyle style = GetStyle(generationSettings, styleHashCode); // Return if we don't have a valid style. - if (style == null) - return; + if (style == null) return; - int styleLength = style.styleClosingTagArray.Length; + // Increase style depth + textStyleStackDepth += 1; // Replace ", i)) - { - ReplaceClosingStyleTag(ref charBuffer, ref writeIndex, ref styleStack, ref generationSettings); - - // Strip even if style is invalid. - i += 7; - continue; - } - } + InsertTextStyleInTextProcessingArray(ref charBuffer, ref writeIndex, styleDefinition, ref textStyleStackDepth, ref textStyleStacks, ref generationSettings); - if (writeIndex == charBuffer.Length) - ResizeInternalArray(ref charBuffer); - - charBuffer[writeIndex] = c; - writeIndex += 1; - } + textStyleStackDepth -= 1; } /// @@ -1066,25 +1020,55 @@ public static void ReplaceClosingStyleTag(ref int[] charBuffer, ref int writeInd /// /// /// - public static bool InsertOpeningStyleTag(TextStyle style, int srcIndex, ref UnicodeChar[] charBuffer, ref int writeIndex, ref int textStyleStackDepth, ref TextProcessingStack[] textStyleStacks, ref TextGenerationSettings generationSettings) + internal static void InsertOpeningStyleTag(TextStyle style, ref TextProcessingElement[] charBuffer, ref int writeIndex, ref int textStyleStackDepth, ref TextProcessingStack[] textStyleStacks, ref TextGenerationSettings generationSettings) { // Return if we don't have a valid style. - if (style == null) return false; + if (style == null) return; textStyleStacks[0].Push(style.hashCode); - int styleLength = style.styleOpeningTagArray.Length; + // Replace