Last active
July 30, 2023 19:49
-
-
Save JLChnToZ/69822a2e6f5b0fd8ca9dd8c6989a6257 to your computer and use it in GitHub Desktop.
Udon Assembly Low Level Builder for VRChat, helper for generating Udon Assembly programmatically.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Text; | |
using System.Text.RegularExpressions; | |
using System.Collections.Generic; | |
using System.Reflection; | |
using UnityEngine; | |
using VRC.Udon; | |
using VRC.Udon.Common; | |
using VRC.Udon.Common.Interfaces; | |
using VRC.Udon.Graph; | |
using VRC.Udon.Editor; | |
using VRC.Udon.EditorBindings; | |
namespace JLChnToZ.VRC.UdonLowLevel { | |
public sealed class UdonAssemblyBuilder { | |
static readonly Regex autoVariableStripper = new Regex("[\\W_]+", RegexOptions.Compiled); | |
static readonly object constNull = new object(), constThis = new object(), constThisGameObject = new object(), constThisTransform = new object(); | |
public const uint ReturnAddress = uint.MaxValue - 7; | |
readonly Dictionary<UdonOpt, string> entryPoints = new Dictionary<UdonOpt, string>(); | |
readonly HashSet<string> entryPointNames = new HashSet<string>(); | |
readonly Dictionary<VariableName, VariableDefinition> variableDefs = new Dictionary<VariableName, VariableDefinition>(); | |
readonly HashSet<string> uniqueExterns = new HashSet<string>(); | |
readonly Dictionary<object, VariableName> constants = new Dictionary<object, VariableName>(); | |
UdonOpt firstOpt, lastOpt; | |
string nextEntryPointName; | |
string assembly; | |
UdonEditorInterface editorInterface; | |
IUdonProgram program; | |
public uint Size => lastOpt != null ? lastOpt.offset + lastOpt.Size : 0; | |
public UdonOpt LastOpt => lastOpt; | |
public readonly bool typeCheck; | |
public VariableName ReturnVariable => UdonBehaviour.ReturnVariableName; | |
public VariableName NullVariable => GetConstant(constNull); | |
public VariableName ThisVariable => GetConstant(constThis); | |
public VariableName ThisGameObjectVariable => GetConstant(constThisGameObject); | |
public VariableName ThisTransformVariable => GetConstant(constThisTransform); | |
public UdonAssemblyBuilder() : this(true) {} | |
public UdonAssemblyBuilder(bool typeCheck) { | |
this.typeCheck = typeCheck; | |
} | |
public void DefineVariable<T>( | |
VariableName variableName, | |
VariableAttributes attributes = VariableAttributes.None, | |
T value = default | |
) => DefineVariable(variableName, new VariableDefinition(typeof(T), attributes, value)); | |
public void DefineVariable( | |
VariableName variableName, | |
Type type = null, | |
VariableAttributes attributes = VariableAttributes.None, | |
object value = null | |
) => DefineVariable(variableName, new VariableDefinition(type, attributes, value)); | |
public void DefineVariable(VariableName variableName, VariableDefinition definition) { | |
if (!variableName.IsValid) | |
throw new ArgumentNullException(nameof(variableName)); | |
if (variableName.TryGetPredefinedType(out var fixedType)) { | |
definition.type = fixedType; | |
definition.attributes &= VariableAttributes.Public; | |
} | |
var value = definition.value; | |
if (value == constNull || | |
value == constThis || | |
value == constThisGameObject || | |
value == constThisTransform) | |
definition.value = null; | |
else if (definition.type != null && definition.type != typeof(void)) { | |
definition.type = definition.type.GetUdonSupportedType(); | |
if (value == null) { | |
if (definition.type.IsValueType) definition.value = Activator.CreateInstance(definition.type); | |
} else if (!definition.type.IsInstanceOfType(value)) | |
throw new ArgumentException("Type mismatch"); | |
} | |
if (definition.attributes.HasFlag(VariableAttributes.Constant)) { | |
var value2 = value ?? constNull; | |
if (!constants.ContainsKey(value2)) constants[value2] = variableName; | |
} | |
if (definition.type == null) definition.type = typeof(object); | |
if (variableDefs.TryGetValue(variableName, out var existing) && | |
existing.attributes.HasFlag(VariableAttributes.Constant) && | |
(definition.type != existing.type || definition.value != existing.value)) | |
throw new ArgumentException("Attempt to modify existing constant type."); | |
variableDefs[variableName] = definition; | |
if (entryPointNames.Contains(VariableChangedEvent.EVENT_PREFIX + variableName.key)) | |
variableDefs[variableName.ChangeEventOldVariable] = new VariableDefinition(definition.type, VariableAttributes.Constant); | |
assembly = null; | |
} | |
public bool TryGetVariable(VariableName variableName, out VariableDefinition result) { | |
if (variableDefs.TryGetValue(variableName, out result)) return true; | |
if (variableName.TryGetPredefinedType(out var fixedType)) { | |
DefineVariable(variableName, fixedType); | |
result = variableDefs[variableName]; | |
return true; | |
} | |
return false; | |
} | |
public void DefineEvent(string name) => nextEntryPointName = name; | |
public void DefineEvent(string name, UdonOpt opt) { | |
entryPointNames.Add(name); | |
entryPoints[opt] = name; | |
assembly = null; | |
} | |
public void DefineVariableChangeEvent(VariableName target) { | |
EnsureVariableChangeVariableDefined(target); | |
DefineEvent(VariableChangedEvent.EVENT_PREFIX + target.key); | |
} | |
public void DefineVariableChangeEvent(VariableName target, UdonOpt opt) { | |
EnsureVariableChangeVariableDefined(target); | |
DefineEvent(VariableChangedEvent.EVENT_PREFIX + target.key, opt); | |
} | |
void EnsureVariableChangeVariableDefined(VariableName target) { | |
if (!target.IsValid) | |
throw new ArgumentNullException(nameof(target)); | |
if (!variableDefs.TryGetValue(target, out var definition)) | |
throw new ArgumentException($"Variable {target} is not defined."); | |
if (typeCheck && (definition.attributes & (VariableAttributes.Public | VariableAttributes.Sync)) == 0) | |
throw new ArgumentException($"Variable {target} is not public or syncd."); | |
var oldVariable = target.ChangeEventOldVariable; | |
if (variableDefs.TryGetValue(oldVariable, out var newDefinition)) { | |
if (typeCheck) { | |
if (newDefinition.type != definition.type) | |
throw new ArgumentException($"Variable {oldVariable} is already defined with different type."); | |
if (newDefinition.attributes != VariableAttributes.Constant) | |
throw new ArgumentException($"Variable {oldVariable} is already defined with different attributes."); | |
} | |
} else | |
DefineVariable(oldVariable, definition.type); | |
} | |
private VariableName GetConstant(object value) { | |
if (value == null) value = constNull; | |
if (!constants.TryGetValue(value, out var varName)) { | |
string baseName; | |
var attributes = VariableAttributes.Constant; | |
Type type; | |
if (value == constNull) { | |
type = typeof(void); | |
baseName = "__const_nil"; | |
} else if (value == constThis) { | |
type = typeof(UdonBehaviour); | |
baseName = "__const_this"; | |
attributes |= VariableAttributes.DefaultThis; | |
} else if (value == constThisGameObject) { | |
type = typeof(GameObject); | |
baseName = "__const_thisGameObject"; | |
attributes |= VariableAttributes.DefaultThis; | |
} else if (value == constThisTransform) { | |
type = typeof(Transform); | |
baseName = "__const_thisTransform"; | |
attributes |= VariableAttributes.DefaultThis; | |
} else { | |
type = value.GetType(); | |
var temp = autoVariableStripper.Replace($"{type.Name}_{value}", "_").TrimEnd(); | |
if (temp.Length > 64) temp = temp.Substring(0, 64); | |
baseName = $"__const_{temp}"; | |
} | |
varName = baseName; | |
int i = 0; | |
while (variableDefs.ContainsKey(varName)) | |
varName = $"{baseName}_{i++}"; | |
DefineVariable(varName, type, attributes, value); | |
} | |
return varName; | |
} | |
public void Emit(UdonOpt opt) { | |
if (opt == null || opt.next != null) | |
throw new ArgumentException("Opt is null or already connected."); | |
if (!string.IsNullOrEmpty(nextEntryPointName)) | |
DefineEvent(nextEntryPointName, opt); | |
nextEntryPointName = null; | |
assembly = null; | |
if (lastOpt == null) { | |
firstOpt = lastOpt = opt; | |
} else { | |
opt.offset = lastOpt.offset + lastOpt.Size; | |
lastOpt.next = opt; | |
lastOpt = opt; | |
} | |
} | |
public NopOpt EmitNop() { | |
var opt = new NopOpt(); | |
Emit(opt); | |
return opt; | |
} | |
public CopyOpt EmitCopy() { | |
var opt = new CopyOpt(); | |
Emit(opt); | |
return opt; | |
} | |
public PopOpt EmitPop() { | |
var opt = new PopOpt(); | |
Emit(opt); | |
return opt; | |
} | |
public CopyOpt EmitCopy(object varFrom, VariableName varTo) { | |
var varFromName = varFrom is VariableName name ? name.key : GetConstant(varFrom); | |
EmitPush(varFromName, null); | |
if (typeCheck) DoTypeCheck(varTo, variableDefs[varFromName].type); | |
EmitPush(varTo, null); | |
return EmitCopy(); | |
} | |
public CopyOpt EmitCopyOffset(VariableName dest, int relativeOffset = 0) { | |
const uint Opt_SIZE = PushOpt.SIZE * 2 + CopyOpt.SIZE; | |
if (!variableDefs.ContainsKey(dest)) DefineVariable<uint>(dest); | |
return EmitCopy((uint)(Opt_SIZE + Size + relativeOffset), dest); | |
} | |
public PushOpt EmitPush(object parameter) => | |
EmitPush(parameter is VariableName name ? name.key : GetConstant(parameter), null); | |
public PushOpt EmitPush(VariableName variableName) => | |
EmitPush(variableName, null); | |
private PushOpt EmitPush(VariableName variableName, Type type, bool strict = false) { | |
DoTypeCheck(variableName, type, strict); | |
var Opt = new PushOpt(variableName); | |
Emit(Opt); | |
return Opt; | |
} | |
public JumpOpt EmitJump(UdonOpt dest) { | |
var opt = new JumpOpt(dest); | |
Emit(opt); | |
return opt; | |
} | |
public JumpOpt EmitJump(uint dest = ReturnAddress) { | |
var Opt = new JumpOpt(dest); | |
Emit(Opt); | |
return Opt; | |
} | |
public JumpIndirectOpt EmitJumpIndirect(VariableName address) { | |
DoTypeCheck(address, typeof(uint), true); | |
var Opt = new JumpIndirectOpt(address); | |
Emit(Opt); | |
return Opt; | |
} | |
public JumpIfFalseOpt EmitJumpIfFalse(UdonOpt dest) { | |
var Opt = new JumpIfFalseOpt(dest); | |
Emit(Opt); | |
return Opt; | |
} | |
public JumpIfFalseOpt EmitJumpIfFalse(uint dest = ReturnAddress) { | |
var Opt = new JumpIfFalseOpt(dest); | |
Emit(Opt); | |
return Opt; | |
} | |
public JumpIfFalseOpt EmitJumpIfFalse(VariableName paramName, UdonOpt dest) { | |
EmitPush(paramName, typeof(bool), true); | |
return EmitJumpIfFalse(dest); | |
} | |
public JumpIfFalseOpt EmitJumpIfFalse(VariableName paramName, uint dest = ReturnAddress) { | |
EmitPush(paramName, typeof(bool), true); | |
return EmitJumpIfFalse(dest); | |
} | |
private void DoTypeCheck(VariableName variableName, Type type, bool strict = false) { | |
if (!variableName.IsValid) throw new ArgumentNullException(nameof(variableName)); | |
if (variableDefs.TryGetValue(variableName, out var def)) { | |
if (typeCheck && type != null && (strict ? def.type != type : !TypeHelper.IsTypeCompatable(def.type, type))) | |
throw new ArgumentException($"Type mismatch: expected variable `{variableName}` to be `{type}` but got `{def.type}`."); | |
} else DefineVariable(variableName, type); | |
} | |
public ExternOpt EmitExtern(MethodInfo method) => | |
EmitExtern(method.GetUdonSignature()); | |
public ExternOpt EmitExtern(string methodName) { | |
if (string.IsNullOrEmpty(methodName)) | |
throw new ArgumentNullException(nameof(methodName)); | |
if (typeCheck && UdonEditorManager.Instance.GetNodeDefinition(methodName) == null) | |
throw new ArgumentException($"Method `{methodName}` does not exists!"); | |
uniqueExterns.Add(methodName); | |
var Opt = new ExternOpt(methodName); | |
Emit(Opt); | |
return Opt; | |
} | |
public ExternOpt EmitExtern(MethodInfo method, params object[] parameters) => | |
EmitExtern(method.GetUdonSignature(), parameters); | |
public ExternOpt EmitExtern(string methodName, params object[] parameters) { | |
if (string.IsNullOrEmpty(methodName)) | |
throw new ArgumentNullException(nameof(methodName)); | |
if (typeCheck) { | |
var nodeDef = UdonEditorManager.Instance.GetNodeDefinition(methodName); | |
if (nodeDef == null) | |
throw new ArgumentException($"Method `{methodName}` does not exists!"); | |
if (parameters == null ? nodeDef.parameters.Count != 0 : parameters.Length != nodeDef.parameters.Count) | |
throw new ArgumentException($"Invalid number of parameters for `{methodName}`: expected {nodeDef.parameters.Count} but got {parameters?.Length ?? 0}."); | |
if (parameters != null) | |
for (int i = 0; i < parameters.Length; i++) { | |
var parameter = parameters[i]; | |
Type type; | |
VariableDefinition varDef; | |
if (parameter is VariableName name) { | |
if (variableDefs.TryGetValue(name, out varDef)) type = varDef.type; | |
else { | |
type = parameter?.GetType(); | |
variableDefs.Add(name, varDef = new VariableDefinition(type, value: parameter)); | |
} | |
} else { | |
name = GetConstant(parameter); | |
varDef = variableDefs[name]; | |
type = varDef.type; | |
} | |
var paramDef = nodeDef.parameters[i]; | |
switch (paramDef.parameterType) { | |
case UdonNodeParameter.ParameterType.IN: | |
if (!TypeHelper.IsTypeAssignable(paramDef.type, type)) | |
throw new ArgumentException($"Type mismatch: Expected parameter {i} of `{methodName}` to be `{paramDef.type}` but got `{type}`."); | |
break; | |
case UdonNodeParameter.ParameterType.OUT: | |
if (varDef.attributes.HasFlag(VariableAttributes.Constant)) | |
throw new ArgumentException($"Parameter {i} of `{methodName}` is an output but assigned a constant `{parameter}`."); | |
if (type == null) { | |
varDef.type = paramDef.type; | |
variableDefs[name] = varDef; | |
} else if (!TypeHelper.IsTypeCompatable(paramDef.type, type)) | |
throw new ArgumentException($"Type mismatch: Expected parameter {i} of `{methodName}` to be `{paramDef.type}` but got `{type}`."); | |
break; | |
case UdonNodeParameter.ParameterType.IN_OUT: | |
if (varDef.attributes.HasFlag(VariableAttributes.Constant)) | |
throw new ArgumentException($"Parameter {i} of `{methodName}` is a reference but assigned a constant `{parameter}`."); | |
if (type == null) { | |
varDef.type = paramDef.type; | |
variableDefs[name] = varDef; | |
} else if (type != paramDef.type) | |
throw new ArgumentException($"Type mismatch: Expected parameter {i} of `{methodName}` to be `{paramDef.type}` but got `{type}`."); | |
break; | |
} | |
parameters[i] = name; | |
} | |
} | |
if (parameters != null) foreach (var parameter in parameters) EmitPush(parameter); | |
return EmitExtern(methodName); | |
} | |
public string Compile() { | |
if (string.IsNullOrEmpty(assembly)) { | |
var sb = new StringBuilder(); | |
sb.AppendLine(".data_start"); | |
foreach (var kv in variableDefs) { | |
if (kv.Value.attributes.HasFlag(VariableAttributes.Public)) | |
sb.AppendLine($".export {kv.Key}"); | |
switch (kv.Value.attributes & VariableAttributes.Sync) { | |
case VariableAttributes.SyncNone: | |
sb.AppendLine($".sync {kv.Key}, none"); | |
break; | |
case VariableAttributes.SyncLinear: | |
sb.AppendLine($".sync {kv.Key}, linear"); | |
break; | |
case VariableAttributes.SyncSmooth: | |
sb.AppendLine($".sync {kv.Key}, smooth"); | |
break; | |
} | |
} | |
foreach (var kv in variableDefs) | |
sb.AppendLine($"{kv.Key}: %{kv.Value.type.GetUdonSignature()}, {(kv.Value.attributes.HasFlag(VariableAttributes.DefaultThis) ? "this" : "null")}"); | |
sb.AppendLine(".data_end"); | |
sb.AppendLine(".code_start"); | |
for (var opt = firstOpt; opt != null; opt = opt.next) { | |
if (entryPoints.TryGetValue(opt, out var exportName)) { | |
sb.AppendLine($".export {exportName}"); | |
sb.AppendLine($"{exportName}:"); | |
} | |
sb.AppendLine(opt.ToString()); | |
} | |
sb.AppendLine(".code_end"); | |
assembly = sb.ToString(); | |
} | |
return assembly; | |
} | |
public IUdonProgram Assemble() { | |
Compile(); | |
editorInterface = new UdonEditorInterface( | |
null, new HeapFactory((uint)(variableDefs.Count + uniqueExterns.Count)), | |
null, null, null, null, null, null, null | |
); | |
program = editorInterface.Assemble(assembly); | |
var symbolTable = program.SymbolTable; | |
var heap = program.Heap; | |
foreach (var kv in variableDefs) | |
if (!kv.Value.attributes.HasFlag(VariableAttributes.DefaultThis) && kv.Value.type != typeof(void)) | |
heap.SetHeapVariable(symbolTable.GetAddressFromSymbol(kv.Key.key), kv.Value.value, kv.Value.type); | |
return program; | |
} | |
public string[] Disassemble() { | |
if (editorInterface == null || program == null) Assemble(); | |
return editorInterface.DisassembleProgram(program); | |
} | |
} | |
public static class TypeHelper { | |
static readonly HashSet<Type> supportedTypes; | |
static readonly Dictionary<MemberInfo, string> udonSignatures; | |
static readonly Dictionary<VariableName, Type> predefinedVariableTypes; | |
static TypeHelper() { | |
supportedTypes = new HashSet<Type>(); | |
predefinedVariableTypes = new Dictionary<VariableName, Type> { | |
[UdonBehaviour.ReturnVariableName] = typeof(object), | |
}; | |
udonSignatures = new Dictionary<MemberInfo, string>(); | |
foreach (var def in UdonEditorManager.Instance.GetNodeDefinitions()) { | |
if (def.fullName.StartsWith("Type_")) { | |
if (!def.fullName.EndsWith("Ref") && !udonSignatures.ContainsKey(def.type)) | |
udonSignatures[def.type] = def.fullName.Substring(5); | |
supportedTypes.Add(def.type); | |
continue; | |
} | |
if (def.fullName.StartsWith("Event_")) { | |
var eventName = $"{char.ToLower(def.fullName[6])}{def.fullName.Substring(7)}"; | |
foreach (var parameter in def.parameters) | |
if (parameter.parameterType == UdonNodeParameter.ParameterType.OUT) | |
predefinedVariableTypes[$"{eventName}{char.ToUpper(parameter.name[0])}{parameter.name.Substring(1)}"] = parameter.type; | |
continue; | |
} | |
} | |
} | |
public static Type GetUdonSupportedType(this Type type) { | |
if (type == null) return null; | |
if (supportedTypes.Contains(type)) return type; | |
bool isByRef = type.IsByRef, isArray = type.IsArray, isEnum = type.IsEnum; | |
if (isByRef || isArray) type = type.GetElementType(); | |
else if (isEnum) type = Enum.GetUnderlyingType(type); | |
if (type.IsInterface) return null; | |
foreach (var subType in type.GetInterfaces()) { | |
var tryType = subType; | |
if (isByRef) | |
tryType = tryType.MakeByRefType(); | |
else if (isArray) | |
tryType = tryType.MakeArrayType(); | |
tryType = tryType.GetUdonSupportedType(); | |
if (tryType != null) return tryType; | |
} | |
var parentType = type.BaseType; | |
if (parentType != null) { | |
if (isByRef) | |
parentType = parentType.MakeByRefType(); | |
else if (isArray) | |
parentType = parentType.MakeArrayType(); | |
parentType = parentType.GetUdonSupportedType(); | |
if (parentType != null) return parentType; | |
} | |
return null; | |
} | |
public static string GetUdonSignature(this Type type) { | |
if (udonSignatures.TryGetValue(type, out var typeName)) return typeName; | |
if (type.IsByRef) { | |
typeName = type.GetElementType().GetUdonSignature(); | |
if (!string.IsNullOrEmpty(typeName)) { | |
typeName += "Ref"; | |
udonSignatures[type] = typeName; | |
} | |
return typeName; | |
} | |
if (type.IsArray) { | |
typeName = type.GetElementType().GetUdonSignature(); | |
if (!string.IsNullOrEmpty(typeName)) { | |
typeName += "Array"; | |
udonSignatures[type] = typeName; | |
} | |
return typeName; | |
} | |
return "SystemObject"; | |
} | |
public static string GetUdonSignature(this MethodInfo method) { | |
if (method == null) | |
throw new ArgumentNullException(nameof(method)); | |
if (!udonSignatures.TryGetValue(method, out var signature)) { | |
var sb = new StringBuilder(); | |
sb.Append(method.DeclaringType.GetUdonSignature()); | |
sb.Append(".__"); | |
sb.Append(method.Name); | |
var parameters = method.GetParameters(); | |
if (parameters.Length > 0) { | |
sb.Append('_'); | |
foreach (var parameter in parameters) | |
sb.Append('_').Append(parameter.ParameterType.GetUdonSignature()); | |
} | |
sb.Append("__"); | |
sb.Append(method.ReturnType.GetUdonSignature()); | |
udonSignatures[method] = signature = sb.ToString(); | |
} | |
return signature; | |
} | |
public static bool TryGetPredefinedType(this VariableName varName, out Type type) => | |
predefinedVariableTypes.TryGetValue(varName, out type) && type != null; | |
public static bool IsTypeAssignable(Type from, Type to) { | |
if (to == null || to == typeof(void)) return from == null || !from.IsValueType; | |
return from.IsAssignableFrom(to); | |
} | |
public static bool IsTypeCompatable(Type from, Type to) { | |
if (from == null || from == typeof(void)) return to == null || !to.IsValueType; | |
if (to == null || to == typeof(void)) return from == null || !from.IsValueType; | |
return from.IsAssignableFrom(to) || to.IsAssignableFrom(from); | |
} | |
} | |
public struct VariableName : IEquatable<VariableName> { | |
public readonly string key; | |
public bool IsValid => !string.IsNullOrEmpty(key); | |
public VariableName(string key) => this.key = key; | |
public bool Equals(VariableName other) => string.Equals(key, other.key); | |
public override bool Equals(object obj) => obj is VariableName other && Equals(other); | |
public override int GetHashCode() => key?.GetHashCode() ?? 0; | |
public override string ToString() => key ?? ""; | |
public static implicit operator VariableName(string key) => new VariableName(key); | |
public VariableName ChangeEventOldVariable => VariableChangedEvent.OLD_VALUE_PREFIX + key; | |
} | |
[Flags] | |
public enum VariableAttributes { | |
None = 0x0, | |
Public = 0x1, | |
SyncNone = 0x2, | |
SyncLinear = 0x4, | |
SyncSmooth = 0x8, | |
Sync = SyncNone | SyncLinear | SyncSmooth, | |
DefaultThis = 0x10, | |
Constant = 0x20, | |
} | |
public struct VariableDefinition { | |
public VariableAttributes attributes; | |
public Type type; | |
public object value; | |
public VariableDefinition(Type type = null, VariableAttributes attributes = VariableAttributes.None, object value = null) { | |
this.type = type ?? value?.GetType() ?? typeof(object); | |
this.attributes = attributes; | |
this.value = value ?? (type != null && type != typeof(void) && type.IsValueType ? Activator.CreateInstance(type) : null); | |
} | |
} | |
public abstract class UdonOpt { | |
public abstract uint Size { get; } | |
internal UdonOpt next; | |
internal uint offset; | |
} | |
public class NopOpt : UdonOpt { | |
public const uint SIZE = 4U; | |
public override uint Size => SIZE; | |
public override string ToString() => "NOP"; | |
} | |
public class PopOpt : UdonOpt { | |
public const uint SIZE = 4U; | |
public override uint Size => SIZE; | |
public override string ToString() => "POP"; | |
} | |
public class CopyOpt : UdonOpt { | |
public const uint SIZE = 4U; | |
public override uint Size => SIZE; | |
public override string ToString() => "COPY"; | |
} | |
public class PushOpt : UdonOpt { | |
public const uint SIZE = 8U; | |
public override uint Size => SIZE; | |
public VariableName dest; | |
public PushOpt(VariableName dest) => this.dest = dest; | |
public override string ToString() => $"PUSH, {dest}"; | |
} | |
public abstract class JumpOptBase : UdonOpt { | |
public const uint SIZE = 8U; | |
public override uint Size => SIZE; | |
public UdonOpt destination; | |
protected uint destinationAddr; | |
protected JumpOptBase(UdonOpt destination) => | |
this.destination = destination; | |
protected JumpOptBase(uint destinationAddr) => | |
this.destinationAddr = destinationAddr; | |
public uint DestinationAddr => destination != null ? destination.offset + destination.Size : destinationAddr; | |
} | |
public class JumpOpt : JumpOptBase { | |
public JumpOpt(UdonOpt destination) : base(destination) {} | |
public JumpOpt(uint destinationAddr) : base(destinationAddr) {} | |
public override string ToString() => $"JUMP, 0x{DestinationAddr:X8}"; | |
} | |
public class JumpIndirectOpt : UdonOpt { | |
public const uint SIZE = 8U; | |
public override uint Size => SIZE; | |
public readonly VariableName varName; | |
public JumpIndirectOpt(VariableName varName) => this.varName = varName; | |
public override string ToString() => $"JUMP_INDIRECT, {varName}"; | |
} | |
public class JumpIfFalseOpt : JumpOpt { | |
public JumpIfFalseOpt(UdonOpt destination) : base(destination) {} | |
public JumpIfFalseOpt(uint destinationAddr) : base(destinationAddr) {} | |
public override string ToString() => $"JUMP_IF_FALSE, 0x{DestinationAddr:X8}"; | |
} | |
public class ExternOpt : UdonOpt { | |
public const uint SIZE = 8U; | |
public override uint Size => SIZE; | |
public readonly string methodName; | |
public ExternOpt(string methodName) => this.methodName = methodName; | |
public override string ToString() => $"EXTERN, \"{methodName}\""; | |
} | |
internal class HeapFactory : IUdonHeapFactory { | |
public uint DefaultHeapSize { get; set; } | |
public HeapFactory(uint defaultHeapSize = 512U) => DefaultHeapSize = defaultHeapSize; | |
public IUdonHeap ConstructUdonHeap() => new UdonHeap(DefaultHeapSize); | |
IUdonHeap IUdonHeapFactory.ConstructUdonHeap(uint heapSize) => new UdonHeap(Math.Max(heapSize, DefaultHeapSize)); | |
} | |
internal class UdonNodeDefinitionCompararer : IComparer<UdonNodeDefinition>, IEqualityComparer<UdonNodeDefinition> { | |
public static readonly UdonNodeDefinitionCompararer instance = new UdonNodeDefinitionCompararer(); | |
public int Compare(UdonNodeDefinition lhs, UdonNodeDefinition rhs) { | |
if (lhs == rhs) return 0; | |
if (lhs.parameters.Count != rhs.parameters.Count) return 0; | |
int score = 0; | |
for (int i = lhs.parameters.Count - 1; i >= 0; i--) { | |
var lhsParam = lhs.parameters[i]; | |
var rhsParam = rhs.parameters[i]; | |
if (lhsParam.parameterType == UdonNodeParameter.ParameterType.OUT && | |
rhsParam.parameterType == UdonNodeParameter.ParameterType.OUT) | |
continue; | |
var lhsType = lhsParam.type; | |
var rhsType = rhsParam.type; | |
if (lhsType == rhsType) continue; | |
if (lhsType.IsArray && rhsType.IsArray) { | |
score += lhsType.GetElementType().IsValueType ? rhsType.GetElementType().IsValueType ? 0 : -1 : 1; | |
continue; | |
} | |
if (lhsType.IsValueType || rhsType.IsValueType) { | |
score += lhsType.IsValueType ? rhsType.IsValueType ? 0 : -1 : 1; | |
continue; | |
} | |
if (TypeHelper.IsTypeAssignable(lhsType, rhsType)) { | |
while (rhsType != lhsType && rhsType != null) { | |
score++; | |
rhsType = rhsType.BaseType; | |
} | |
continue; | |
} | |
if (TypeHelper.IsTypeAssignable(rhsType, lhsType)) { | |
while (rhsType != lhsType && lhsType != null) { | |
score--; | |
lhsType = lhsType.BaseType; | |
} | |
continue; | |
} | |
} | |
return score > 0 ? 1 : score < 0 ? -1 : 0; | |
} | |
public bool Equals(UdonNodeDefinition lhs, UdonNodeDefinition rhs) { | |
if (lhs == null) return rhs != null; | |
if (rhs == null || lhs.parameters.Count != rhs.parameters.Count) return false; | |
for (int i = lhs.parameters.Count - 1; i >= 0; i--) { | |
var lhsParam = lhs.parameters[i]; | |
var rhsParam = rhs.parameters[i]; | |
if (lhsParam.parameterType != rhsParam.parameterType) | |
return false; | |
if (lhsParam.parameterType == UdonNodeParameter.ParameterType.OUT) | |
continue; | |
if (lhsParam.type != rhsParam.type) | |
return false; | |
} | |
return true; | |
} | |
public int GetHashCode(UdonNodeDefinition target) { | |
if (target == null) return 0; | |
int result = 0; | |
for (int i = target.parameters.Count - 1; i >= 0; i--) { | |
var parameter = target.parameters[i]; | |
if (parameter.parameterType == UdonNodeParameter.ParameterType.OUT) continue; | |
result ^= parameter.type.GetHashCode(); | |
} | |
return result; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment