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}