A game about forced loneliness, made by TACStudios
1using System; 2using System.Text; 3using System.Collections.Generic; 4using System.Diagnostics; 5using System.IO; 6using System.IO.IsolatedStorage; 7using System.Linq; 8using System.Text.RegularExpressions; 9using UnityEditor.Graphing; 10using UnityEditor.Graphing.Util; 11using UnityEditorInternal; 12using Debug = UnityEngine.Debug; 13using System.Reflection; 14using System.Runtime.Remoting.Metadata.W3cXsd2001; 15using UnityEditor.ProjectWindowCallback; 16using UnityEditor.ShaderGraph.Internal; 17using UnityEngine; 18using UnityEngine.Rendering; 19using Object = System.Object; 20 21namespace UnityEditor.ShaderGraph 22{ 23 // a structure used to track active variable dependencies in the shader code 24 // (i.e. the use of uv0 in the pixel shader means we need a uv0 interpolator, etc.) 25 struct Dependency 26 { 27 public string name; // the name of the thing 28 public string dependsOn; // the thing above depends on this -- it reads it / calls it / requires it to be defined 29 30 public Dependency(string name, string dependsOn) 31 { 32 this.name = name; 33 this.dependsOn = dependsOn; 34 } 35 }; 36 37 [System.AttributeUsage(System.AttributeTargets.Struct)] 38 class InterpolatorPack : System.Attribute 39 { 40 public InterpolatorPack() 41 { 42 } 43 } 44 45 // attribute used to flag a field as needing an HLSL semantic applied 46 // i.e. float3 position : POSITION; 47 // ^ semantic 48 [System.AttributeUsage(System.AttributeTargets.Field)] 49 class Semantic : System.Attribute 50 { 51 public string semantic; 52 53 public Semantic(string semantic) 54 { 55 this.semantic = semantic; 56 } 57 } 58 59 // attribute used to flag a field as being optional 60 // i.e. if it is not active, then we can omit it from the struct 61 [System.AttributeUsage(System.AttributeTargets.Field)] 62 class Optional : System.Attribute 63 { 64 public Optional() 65 { 66 } 67 } 68 69 // attribute used to override the HLSL type of a field with a custom type string 70 [System.AttributeUsage(System.AttributeTargets.Field)] 71 class OverrideType : System.Attribute 72 { 73 public string typeName; 74 75 public OverrideType(string typeName) 76 { 77 this.typeName = typeName; 78 } 79 } 80 81 // attribute used to force system generated fields to bottom of structs 82 [System.AttributeUsage(System.AttributeTargets.Field)] 83 class SystemGenerated : System.Attribute 84 { 85 public SystemGenerated() 86 { 87 } 88 } 89 90 // attribute used to disable a field using a preprocessor #if 91 [System.AttributeUsage(System.AttributeTargets.Field)] 92 class PreprocessorIf : System.Attribute 93 { 94 public string conditional; 95 96 public PreprocessorIf(string conditional) 97 { 98 this.conditional = conditional; 99 } 100 } 101 102 class NewGraphAction : EndNameEditAction 103 { 104 Target[] m_Targets; 105 public Target[] targets 106 { 107 get => m_Targets; 108 set => m_Targets = value; 109 } 110 111 BlockFieldDescriptor[] m_Blocks; 112 public BlockFieldDescriptor[] blocks 113 { 114 get => m_Blocks; 115 set => m_Blocks = value; 116 } 117 118 public override void Action(int instanceId, string pathName, string resourceFile) 119 { 120 var graph = new GraphData(); 121 graph.AddContexts(); 122 graph.InitializeOutputs(m_Targets, m_Blocks); 123 graph.AddCategory(CategoryData.DefaultCategory()); 124 125 graph.path = "Shader Graphs"; 126 FileUtilities.WriteShaderGraphToDisk(pathName, graph); 127 AssetDatabase.Refresh(); 128 129 UnityEngine.Object obj = AssetDatabase.LoadAssetAtPath<Shader>(pathName); 130 Selection.activeObject = obj; 131 } 132 } 133 134 static class GraphUtil 135 { 136 internal static bool CheckForRecursiveDependencyOnPendingSave(string saveFilePath, IEnumerable<SubGraphNode> subGraphNodes, string context = null) 137 { 138 var overwriteGUID = AssetDatabase.AssetPathToGUID(saveFilePath); 139 if (!string.IsNullOrEmpty(overwriteGUID)) 140 { 141 foreach (var sgNode in subGraphNodes) 142 { 143 var asset = sgNode?.asset; 144 if (asset == null) 145 { 146 // cannot read the asset; might be recursive but we can't tell... should we return "maybe"? 147 // I think to be minimally intrusive to the user we can assume "No" in this case, 148 // even though this may miss recursions in extraordinary cases. 149 // it's more important to allow the user to save their files than to catch 100% of recursions 150 continue; 151 } 152 else if ((asset.assetGuid == overwriteGUID) || asset.descendents.Contains(overwriteGUID)) 153 { 154 if (context != null) 155 { 156 Debug.LogWarning(context + " CANCELLED to avoid a generating a reference loop: the SubGraph '" + sgNode.asset.name + "' references the target file '" + saveFilePath + "'"); 157 EditorUtility.DisplayDialog( 158 context + " CANCELLED", 159 "Saving the file would generate a reference loop, because the SubGraph '" + sgNode.asset.name + "' references the target file '" + saveFilePath + "'", "Cancel"); 160 } 161 return true; 162 } 163 } 164 } 165 return false; 166 } 167 168 internal static string ConvertCamelCase(string text, bool preserveAcronyms) 169 { 170 if (string.IsNullOrEmpty(text)) 171 return string.Empty; 172 StringBuilder newText = new StringBuilder(text.Length * 2); 173 newText.Append(text[0]); 174 for (int i = 1; i < text.Length; i++) 175 { 176 if (char.IsUpper(text[i])) 177 if ((text[i - 1] != ' ' && !char.IsUpper(text[i - 1])) || 178 (preserveAcronyms && char.IsUpper(text[i - 1]) && 179 i < text.Length - 1 && !char.IsUpper(text[i + 1]))) 180 newText.Append(' '); 181 newText.Append(text[i]); 182 } 183 return newText.ToString(); 184 } 185 186 public static void CreateNewGraph() 187 { 188 var graphItem = ScriptableObject.CreateInstance<NewGraphAction>(); 189 graphItem.targets = null; 190 ProjectWindowUtil.StartNameEditingIfProjectWindowExists(0, graphItem, 191 string.Format("New Shader Graph.{0}", ShaderGraphImporter.Extension), null, null); 192 } 193 194 public static void CreateNewGraphWithOutputs(Target[] targets, BlockFieldDescriptor[] blockDescriptors) 195 { 196 var graphItem = ScriptableObject.CreateInstance<NewGraphAction>(); 197 graphItem.targets = targets; 198 graphItem.blocks = blockDescriptors; 199 ProjectWindowUtil.StartNameEditingIfProjectWindowExists(0, graphItem, 200 string.Format("New Shader Graph.{0}", ShaderGraphImporter.Extension), null, null); 201 } 202 203 public static bool TryGetMetadataOfType<T>(this Shader shader, out T obj) where T : ScriptableObject 204 { 205 obj = null; 206 if (!shader.IsShaderGraphAsset()) 207 return false; 208 209 var path = AssetDatabase.GetAssetPath(shader); 210 foreach (var asset in AssetDatabase.LoadAllAssetsAtPath(path)) 211 { 212 if (asset is T metadataAsset) 213 { 214 obj = metadataAsset; 215 return true; 216 } 217 } 218 219 return false; 220 } 221 222 // this will work on ALL shadergraph-built shaders, in memory or asset based 223 public static bool IsShaderGraph(this Material material) 224 { 225 var shaderGraphTag = material.GetTag("ShaderGraphShader", false, null); 226 return !string.IsNullOrEmpty(shaderGraphTag); 227 } 228 229 // NOTE: this ONLY works for ASSET based Shaders, if you created a temporary shader in memory, it won't work 230 public static bool IsShaderGraphAsset(this Shader shader) 231 { 232 var path = AssetDatabase.GetAssetPath(shader); 233 var importer = AssetImporter.GetAtPath(path); 234 return importer is ShaderGraphImporter; 235 } 236 237 [Obsolete("Use IsShaderGraphAsset instead", false)] 238 public static bool IsShaderGraph(this Shader shader) => shader.IsShaderGraphAsset(); 239 240 static void Visit(List<AbstractMaterialNode> outputList, Dictionary<string, AbstractMaterialNode> unmarkedNodes, AbstractMaterialNode node) 241 { 242 if (!unmarkedNodes.ContainsKey(node.objectId)) 243 return; 244 foreach (var slot in node.GetInputSlots<MaterialSlot>()) 245 { 246 foreach (var edge in node.owner.GetEdges(slot.slotReference)) 247 { 248 var inputNode = edge.outputSlot.node; 249 Visit(outputList, unmarkedNodes, inputNode); 250 } 251 } 252 unmarkedNodes.Remove(node.objectId); 253 outputList.Add(node); 254 } 255 256 static Dictionary<SerializationHelper.TypeSerializationInfo, SerializationHelper.TypeSerializationInfo> s_LegacyTypeRemapping; 257 258 public static Dictionary<SerializationHelper.TypeSerializationInfo, SerializationHelper.TypeSerializationInfo> GetLegacyTypeRemapping() 259 { 260 if (s_LegacyTypeRemapping == null) 261 { 262 s_LegacyTypeRemapping = new Dictionary<SerializationHelper.TypeSerializationInfo, SerializationHelper.TypeSerializationInfo>(); 263 foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) 264 { 265 foreach (var type in assembly.GetTypesOrNothing()) 266 { 267 if (type.IsAbstract) 268 continue; 269 foreach (var attribute in type.GetCustomAttributes(typeof(FormerNameAttribute), false)) 270 { 271 var legacyAttribute = (FormerNameAttribute)attribute; 272 var serializationInfo = new SerializationHelper.TypeSerializationInfo { fullName = legacyAttribute.fullName }; 273 s_LegacyTypeRemapping[serializationInfo] = SerializationHelper.GetTypeSerializableAsString(type); 274 } 275 } 276 } 277 } 278 279 return s_LegacyTypeRemapping; 280 } 281 282 /// <summary> 283 /// Sanitizes a supplied string such that it does not collide 284 /// with any other name in a collection. 285 /// </summary> 286 /// <param name="existingNames"> 287 /// A collection of names that the new name should not collide with. 288 /// </param> 289 /// <param name="duplicateFormat"> 290 /// The format applied to the name if a duplicate exists. 291 /// This must be a format string that contains `{0}` and `{1}` 292 /// once each. An example could be `{0} ({1})`, which will append ` (n)` 293 /// to the name for the n`th duplicate. 294 /// </param> 295 /// <param name="name"> 296 /// The name to be sanitized. 297 /// </param> 298 /// <returns> 299 /// A name that is distinct form any name in `existingNames`. 300 /// </returns> 301 internal static string SanitizeName(IEnumerable<string> existingNames, string duplicateFormat, string name, string disallowedPatternRegex = "\"") 302 { 303 name = Regex.Replace(name, disallowedPatternRegex, "_"); 304 return DeduplicateName(existingNames, duplicateFormat, name); 305 } 306 307 internal static string SanitizeCategoryName(string categoryName, string disallowedPatternRegex = "\"") 308 { 309 return Regex.Replace(categoryName, disallowedPatternRegex, "_"); 310 } 311 312 internal static string DeduplicateName(IEnumerable<string> existingNames, string duplicateFormat, string name) 313 { 314 if (!existingNames.Contains(name)) 315 return name; 316 317 string escapedDuplicateFormat = Regex.Escape(duplicateFormat); 318 319 // Escaped format will escape string interpolation, so the escape characters must be removed for these. 320 escapedDuplicateFormat = escapedDuplicateFormat.Replace(@"\{0}", @"{0}"); 321 escapedDuplicateFormat = escapedDuplicateFormat.Replace(@"\{1}", @"{1}"); 322 323 var baseRegex = new Regex(string.Format(escapedDuplicateFormat, @"^(.*)", @"(\d+)")); 324 325 var baseMatch = baseRegex.Match(name); 326 if (baseMatch.Success) 327 name = baseMatch.Groups[1].Value; 328 329 string baseNameExpression = string.Format(@"^{0}", Regex.Escape(name)); 330 var regex = new Regex(string.Format(escapedDuplicateFormat, baseNameExpression, @"(\d+)") + "$"); 331 332 var existingDuplicateNumbers = existingNames.Select(existingName => regex.Match(existingName)).Where(m => m.Success).Select(m => int.Parse(m.Groups[1].Value)).Where(n => n > 0).Distinct().ToList(); 333 334 var duplicateNumber = 1; 335 existingDuplicateNumbers.Sort(); 336 if (existingDuplicateNumbers.Any() && existingDuplicateNumbers.First() == 1) 337 { 338 duplicateNumber = existingDuplicateNumbers.Last() + 1; 339 for (var i = 1; i < existingDuplicateNumbers.Count; i++) 340 { 341 if (existingDuplicateNumbers[i - 1] != existingDuplicateNumbers[i] - 1) 342 { 343 duplicateNumber = existingDuplicateNumbers[i - 1] + 1; 344 break; 345 } 346 } 347 } 348 349 return string.Format(duplicateFormat, name, duplicateNumber); 350 } 351 352 public static bool WriteToFile(string path, string content) 353 { 354 try 355 { 356 File.WriteAllText(path, content); 357 return true; 358 } 359 catch (Exception e) 360 { 361 Debug.LogError(e); 362 return false; 363 } 364 } 365 366 public static void OpenFile(string path) 367 { 368 string filePath = Path.GetFullPath(path); 369 if (!File.Exists(filePath)) 370 { 371 Debug.LogError(string.Format("Path {0} doesn't exists", path)); 372 return; 373 } 374 375 string externalScriptEditor = ScriptEditorUtility.GetExternalScriptEditor(); 376 if (externalScriptEditor != "internal") 377 { 378 InternalEditorUtility.OpenFileAtLineExternal(filePath, 0); 379 } 380 else 381 { 382 Process p = new Process(); 383 p.StartInfo.FileName = filePath; 384 p.EnableRaisingEvents = true; 385 p.Exited += (Object obj, EventArgs args) => 386 { 387 if (p.ExitCode != 0) 388 Debug.LogWarningFormat("Unable to open {0}: Check external editor in preferences", filePath); 389 }; 390 p.Start(); 391 } 392 } 393 394 // 395 // Find all nodes of the given type downstream from the given node 396 // Returns a unique list. So even if a node can be reached through different paths it will be present only once. 397 // 398 public static List<NodeType> FindDownStreamNodesOfType<NodeType>(AbstractMaterialNode node) where NodeType : AbstractMaterialNode 399 { 400 // Should never be called without a node 401 Debug.Assert(node != null); 402 403 HashSet<AbstractMaterialNode> visitedNodes = new HashSet<AbstractMaterialNode>(); 404 List<NodeType> vtNodes = new List<NodeType>(); 405 Queue<AbstractMaterialNode> nodeStack = new Queue<AbstractMaterialNode>(); 406 nodeStack.Enqueue(node); 407 visitedNodes.Add(node); 408 409 while (nodeStack.Count > 0) 410 { 411 AbstractMaterialNode visit = nodeStack.Dequeue(); 412 413 // Flood fill through all the nodes 414 foreach (var slot in visit.GetInputSlots<MaterialSlot>()) 415 { 416 foreach (var edge in visit.owner.GetEdges(slot.slotReference)) 417 { 418 var inputNode = edge.outputSlot.node; 419 if (!visitedNodes.Contains(inputNode)) 420 { 421 nodeStack.Enqueue(inputNode); 422 visitedNodes.Add(inputNode); 423 } 424 } 425 } 426 427 // Extract vt node 428 if (visit is NodeType) 429 { 430 NodeType vtNode = visit as NodeType; 431 vtNodes.Add(vtNode); 432 } 433 } 434 435 return vtNodes; 436 } 437 } 438}