Files

619 lines
22 KiB
C#

using System;
using System.Linq;
using System.Collections.Generic;
using System.Globalization;
using Microsoft.CSharp;
using System.CodeDom;
using UnityEngine;
using System.Security.Cryptography;
using System.IO;
using System.IO.Compression;
using VRC.Udon.Graph;
using VRC.Udon.Graph.Interfaces;
using System.Text.RegularExpressions;
namespace VRC.Udon.Editor.ProgramSources.UdonGraphProgram.UI
{
public static class UdonGraphExtensions
{
private static readonly Dictionary<string, string> FriendlyNameCache;
static UdonGraphExtensions()
{
FriendlyNameCache = new Dictionary<string, string>();
StartsWithCache = new Dictionary<(string s, string prefix), bool>();
}
#region Serialization Utilities
private const byte ZIP_VERSION = 0;
public static string ZipString(string str)
{
using (MemoryStream output = new MemoryStream())
{
using (DeflateStream gzip =
new DeflateStream(output, System.IO.Compression.CompressionLevel.Optimal)) //, CompressionMode.Compress
{
using (StreamWriter writer =
new StreamWriter(gzip, System.Text.Encoding.UTF8))
{
writer.Write(str);
}
}
List<byte> outputList = output.ToArray().ToList();
outputList.Insert(0, ZIP_VERSION); //Version Number
return Convert.ToBase64String(outputList.ToArray());
}
}
public static string UnZipString(string input)
{
List<byte> inputList = new List<byte>(Convert.FromBase64String(input));
if (inputList[0] != ZIP_VERSION) //Version Number
{
return "";
}
inputList.RemoveAt(0);
using (MemoryStream inputStream = new MemoryStream(inputList.ToArray()))
{
using (DeflateStream gzip =
new DeflateStream(inputStream, CompressionMode.Decompress))
{
using (StreamReader reader =
new StreamReader(gzip, System.Text.Encoding.UTF8))
{
return reader.ReadToEnd();
}
}
}
}
#endregion
#region Color Utilities
public static class NodeColors
{
public static readonly Color Base = new Color(77f / 255f, 157f / 255f, 1);
public static readonly Color Const = new Color(0.4f, 0.14f, 1f);
public static readonly Color Variable = new Color(1f, 0.86f, 0.3f);
public static readonly Color Function = new Color(1f, 0.42f, 0.14f);
public static readonly Color Event = new Color(0.53f, 1f, 0.3f);
public static readonly Color Return = new Color(0.89f, 0.2f, 0.2f);
}
private static readonly MD5 _md5Hasher = MD5.Create();
private static readonly Dictionary<Type, Color> _typeColors = new Dictionary<Type, Color>();
public static Color MapTypeToColor(Type type)
{
if (type == null)
{
return Color.white;
}
if (type.IsPrimitive)
{
return new Color(0.12f, 0.53f, 0.9f);
}
if (typeof(UnityEngine.Object).IsAssignableFrom(type))
{
return new Color(0.9f, 0.23f, 0.39f);
}
if (type.IsValueType)
{
return NodeColors.Variable;
}
if (_typeColors.ContainsKey(type))
{
return _typeColors[type];
}
byte[] hashed = _md5Hasher.ComputeHash(type.ToString() == "T"
? System.Text.Encoding.UTF8.GetBytes("T")
: System.Text.Encoding.UTF8.GetBytes(type.Name));
int iValue = BitConverter.ToInt32(hashed, 0);
//TODO: Make this provide more varied colors
Color color = Color.HSVToRGB((iValue & 0xff) / 255f, .69f, 1f);
_typeColors.Add(type, color);
return color;
}
#endregion
#region Documentation Utilities
public static bool ShouldShowDocumentationLink(UdonNodeDefinition definition)
{
List<string> specialNames = new List<string>
{
"Block",
"Branch",
"For",
"While",
"Foreach",
"Get_Variable",
"Set_Variable",
"Event_Custom",
"Event_OnDataStorageAdded",
"Event_OnDataStorageChanged",
"Event_OnDataStorageRemoved",
"Event_OnDrop",
"Event_Interact",
"Event_OnNetworkReady",
"Event_OnOwnershipTransferred",
"Event_OnPickup",
"Event_OnPickupUseDown",
"Event_OnPickupUseUp",
"Event_OnPlayerJoined",
"Event_OnPlayerLeft",
"Event_OnSpawn",
"Event_OnStationEntered",
"Event_OnStationExited",
"Event_OnVideoEnd",
"Event_OnVideoPause",
"Event_OnVideoPlay",
"Event_OnVideoStart",
"VRCUdonCommonInterfacesIUdonEventReceiver.__SendCustomEvent__SystemString__SystemVoid",
"VRCUdonCommonInterfacesIUdonEventReceiver.__SetHeapVariable__SystemString_SystemObject__SystemVoid",
"VRCUdonCommonInterfacesIUdonEventReceiver.__GetHeapVariable__SystemString__SystemObject",
"Const_VRCUdonCommonInterfacesIUdonEventReceiver",
};
// Don't show for any of these
return !(definition.type == null ||
definition.type.Namespace == null ||
specialNames.Contains(definition.fullName) ||
(!definition.type.Namespace.Contains("UnityEngine") &&
!definition.type.Namespace.Contains("System")));
}
public static string GetDocumentationLink(UdonNodeDefinition definition)
{
if (definition.fullName.StartsWithCached("Event_"))
{
string url = "https://docs.unity3d.com/2018.4/Documentation/ScriptReference/MonoBehaviour.";
url += definition.name;
url += ".html";
return url;
}
if (definition.fullName.Contains("Array.__ctor"))
{
//I couldn't find the array constructor documentation
return "https://docs.microsoft.com/en-us/dotnet/api/system.array?view=netframework-4.8";
}
if (definition.fullName.Contains("Array.__Get"))
{
return "https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/member-access-operators#indexer-operator-";
}
if (definition.fullName.Contains(".__Equals__SystemObject"))
{
return "https://docs.microsoft.com/en-us/dotnet/api/system.object.equals?view=netframework-4.8";
}
if (definition.name.Contains("[]"))
{
string url = "https://docs.microsoft.com/en-us/dotnet/api/system.array.";
url += definition.name.Split(' ')[1];
url += "?view=netframework-4.8";
return url;
}
if (definition.type.Namespace.Contains("UnityEngine"))
{
string url = "https://docs.unity3d.com/2018.4/Documentation/ScriptReference/";
if (definition.type.Namespace != "UnityEngine")
{
url += definition.type.Namespace.Replace("UnityEngine.", "");
url += ".";
}
url += definition.type.Name;
if (definition.fullName.Contains("__get_") || definition.fullName.Contains("__set_"))
{
if (definition.fullName.Contains("__get_"))
{
url += "-" + definition.name.Split(new[] { "get_" }, StringSplitOptions.None)[1];
}
else
{
url += "-" + definition.name.Split(new[] { "set_" }, StringSplitOptions.None)[1];
}
url += ".html";
return url;
}
if (definition.fullName.Contains("Const_") || definition.fullName.Contains("Type_") || definition.fullName.Contains("Variable_"))
{
url += ".html";
return url;
}
{
// Methods
url += "." + definition.name.Split(' ')[1];
url += ".html";
return url;
}
}
if (definition.type.Namespace.Contains("System"))
{
string url = "https://docs.microsoft.com/en-us/dotnet/api/system.";
url += definition.type.Name;
if (definition.fullName.Contains("__get_") || definition.fullName.Contains("__set_"))
{
url += "." + definition.name.Split(' ')[1].Replace("get_", "").Replace("set_", "");
url += "?view=netframework-4.8";
return url;
}
if (definition.name == "ctor")
{
url += ".-ctor";
url += "?view=netframework-4.8#System_";
url += definition.type.Name + "__ctor_";
foreach (var pType in definition.Inputs)
{
url += "System_" + pType.type.Name.Replace('[', '_').Replace(']', '_') + "_";
}
return url;
}
if (definition.fullName.Contains("Const_") || definition.fullName.Contains("Type_"))
{
url += "?view=netframework-4.8";
return url;
}
{
// Methods
// not entirely sure what case this catches, but we were always doing the split before, and it was breaking if we didn't have . in the name.
if (definition.name.Contains('.'))
{
url += "." + definition.name.Split(' ')[1];
}
url += "?view=netframework-4.8";
return url;
}
}
return "";
}
#endregion
public static string FriendlyNameify(this string typeString)
{
if (typeString == null)
{
return null;
}
if (FriendlyNameCache.ContainsKey(typeString))
{
return FriendlyNameCache[typeString];
}
string originalString = typeString;
typeString = typeString.Replace("Single", "float");
typeString = typeString.Replace("Int32", "int");
typeString = typeString.Replace("String", "string");
typeString = typeString.Replace("VRCUdonCommonInterfacesIUdonEventReceiver", "UdonBehaviour");
typeString = typeString.Replace("UdonCommonInterfacesIUdonEventReceiver", "UdonBehaviour");
typeString = typeString.Replace("IUdonEventReceiver", "UdonBehaviour");
typeString = typeString.Replace("Const_VRCUdonCommonInterfacesIUdonEventReceiver", "UdonBehaviour");
typeString = typeString.Replace("Array", "[]");
typeString = typeString.Replace("SDK3VideoComponentsBaseBase", "");
typeString = typeString.Replace("SDKBase", "");
typeString = typeString.Replace("SDK3Components", "");
typeString = typeString.Replace("VRCVRC", "VRC");
typeString = typeString.Replace("TMPro", "");
// ReSharper disable once StringLiteralTypo
if (typeString.Replace("ector", "").Contains("ctor")) //Handle "Vector/vector"
{
typeString = typeString.ReplaceLast("ctor", "constructor");
}
if (typeString == "IUdonEventReceiver")
{
typeString = "UdonBehaviour";
}
FriendlyNameCache.Add(originalString, typeString);
return typeString;
}
private static readonly Dictionary<(string s, string prefix), bool> StartsWithCache;
public static bool StartsWithCached(this string s, string prefix)
{
if (StartsWithCache.ContainsKey((s, prefix)))
{
return StartsWithCache[(s, prefix)];
}
bool doesStartWith = s.StartsWith(prefix);
StartsWithCache.Add((s, prefix), doesStartWith);
return doesStartWith;
}
public static string UppercaseFirst(this string s)
{
if (string.IsNullOrEmpty(s))
{
return string.Empty;
}
char[] a = s.ToCharArray();
a[0] = char.ToUpper(a[0]);
return new string(a);
}
public static string ReplaceFirst(this string text, string search, string replace)
{
int pos = text.IndexOf(search, StringComparison.Ordinal);
if (pos < 0)
{
return text;
}
return text.Substring(0, pos) + replace + text.Substring(pos + search.Length);
}
public static Type SlotTypeConverter(Type type, string fullName)
{
if (type == null)
{
return typeof(object);
}
if (fullName.Contains("IUdonEventReceiver") && type == typeof(UnityEngine.Object))
{
return typeof(UdonBehaviour);
}
return type;
}
public static string FriendlyTypeName(Type t)
{
if (t == null)
{
return "Flow";
}
if (!t.IsPrimitive)
{
if (t == typeof(UnityEngine.Object))
{
return "Unity Object";
}
return t.Name;
}
using (CSharpCodeProvider provider = new CSharpCodeProvider())
{
CodeTypeReference typeRef = new CodeTypeReference(t);
return provider.GetTypeOutput(typeRef);
}
}
public static string ReplaceLast(this string source, string find, string replace)
{
int place = source.LastIndexOf(find, StringComparison.Ordinal);
if (place == -1)
return source;
string result = source.Remove(place, find.Length).Insert(place, replace);
return result;
}
public static string PrettyBaseName(string baseIdentifier)
{
string result = baseIdentifier.Replace("UnityEngine", "").Replace("System", "");
string[] resultSplit = result.Split(new[] { "__" }, StringSplitOptions.None);
if (resultSplit.Length >= 2)
{
result = $"{resultSplit[0]}{resultSplit[1]}";
}
result = result.FriendlyNameify();
result = result.Replace("op_", "");
result = result.Replace("_", " ");
return result;
}
public static string PrettyString(string s)
{
switch (s)
{
case "op_Equality":
s = "==";
break;
case "op_Inequality":
s = "!=";
break;
case "op_Addition":
s = "+";
break;
case "VRCUdonCommonInterfacesIUdonEventReceiver":
s = "UdonBehaviour";
break;
// ReSharper disable once RedundantEmptySwitchSection
default:
break;
}
return s;
}
public static string ParseByCase(string strInput)
{
string strOutput = "";
int intCurrentCharPos = 0;
int intLastCharPos = strInput.Length - 1;
for (intCurrentCharPos = 0; intCurrentCharPos <= intLastCharPos; intCurrentCharPos++)
{
char chrCurrentInputChar = strInput[intCurrentCharPos];
char chrPreviousInputChar = chrCurrentInputChar;
if (intCurrentCharPos > 0)
{
chrPreviousInputChar = strInput[intCurrentCharPos - 1];
}
if (char.IsUpper(chrCurrentInputChar) && char.IsLower(chrPreviousInputChar))
{
strOutput += " ";
}
strOutput += chrCurrentInputChar;
}
return strOutput;
}
public static string PrettyFullName(Graph.UdonNodeDefinition nodeDefinition, bool keepLong = false)
{
string fullName = nodeDefinition.fullName;
string result;
if (keepLong)
{
result = fullName.Replace("UnityEngine", "UnityEngine.").Replace("System", "System.");
}
else
{
result = fullName.Replace("UnityEngine", "").Replace("System", "");
}
string[] resultSplit = result.Split(new[] { "__" }, StringSplitOptions.None);
if (resultSplit.Length >= 3)
{
string outName = "";
if (nodeDefinition.type != typeof(void))
{
if (nodeDefinition.Outputs.Count > 0)
{
outName = string.Join(", ", nodeDefinition.Outputs.Select(o => o.name));
}
}
result = nodeDefinition.Inputs.Count > 0
? $"{resultSplit[0]}{resultSplit[1]}({string.Join(", ", nodeDefinition.Inputs.Select(s => s.name))}{outName})"
: $"{resultSplit[0]}{resultSplit[1]}({resultSplit[2].Replace("_", ", ")}{outName})";
}
else if (resultSplit.Length >= 2)
{
result = $"{resultSplit[0]}{resultSplit[1]}()";
}
if (!keepLong)
{
result = result.FriendlyNameify();
result = result.Replace("op_", "");
result = result.Replace("_", " ");
}
return result;
}
public static string GetSimpleNameForRegistry(Graph.Interfaces.INodeRegistry registry)
{
string registryName = registry.ToString().Replace("NodeRegistry", "").FriendlyNameify();
registryName = registryName.Substring(registryName.LastIndexOf(".") + 1);
registryName = registryName.Replace("UnityEngine", "");
return registryName;
}
private static Dictionary<UdonNodeDefinition, INodeRegistry> _definitionToRegistryLookup;
public static INodeRegistry GetRegistryForDefinition(UdonNodeDefinition definition)
{
// Create lookup if needed
if(_definitionToRegistryLookup == null)
{
_definitionToRegistryLookup = new Dictionary<UdonNodeDefinition, INodeRegistry>();
foreach (var registry in UdonEditorManager.Instance.GetNodeRegistries())
{
foreach (var nodeDefinition in registry.Value.GetNodeDefinitions())
{
_definitionToRegistryLookup.Add(nodeDefinition, registry.Value);
}
}
}
// Return found Registry or Null
if (_definitionToRegistryLookup.ContainsKey(definition))
{
return _definitionToRegistryLookup[definition];
}
else
{
return null;
}
}
public static string ToLowerFirstChar(this string input)
{
string newString = input;
if (!String.IsNullOrEmpty(newString) && Char.IsUpper(newString[0]))
newString = Char.ToLower(newString[0]) + newString.Substring(1);
return newString;
}
public static string SanitizeVariableName(this string result)
{
result = result.Replace(" ", "");
if (char.IsNumber(result[0]))
{
result = $"A{result}";
}
Regex rgx = new Regex("[^a-zA-Z0-9 _]");
result = rgx.Replace(result, "");
return result;
}
public static char EscapeLikeALiteral(string src)
{
switch (src)
{
//case "\\'": return '\'';
//case "\\"": return '\"';
case "\\0": return '\0';
case "\\a": return '\a';
case "\\b": return '\b';
case "\\f": return '\f';
case "\\n": return '\n';
case "\\r": return '\r';
case "\\t": return '\t';
case "\\v": return '\v';
case "\\": return '\\';
default:
throw new InvalidOperationException($"src was {src}");
}
}
public static string UnescapeLikeALiteral(char src)
{
switch (src)
{
//case "\\'": return '\'';
//case "\\"": return '\"';
case '\0': return "\\0";
case '\a': return "\\a";
case '\b': return "\\b";
case '\f': return "\\f";
case '\n': return "\\n";
case '\r': return "\\r";
case '\t': return "\\t";
case '\v': return "\\v";
//case '\\': return "\\";
default:
return src.ToString();
}
}
}
}