/// <summary> /// Creates a new UdonAssemblyProgramAsset from an UdonSharpProgramAsset for the sake of portability. Most info used for the inspector gets stripped so this isn't a great solution for remotely complex assets. /// </summary> /// <param name="udonSharpProgramAsset">The source program asset</param> /// <param name="savePath">The save path for the asset file. Save path is only needed here because Udon needs a GUID for saving the serialized program asset and it'd be a pain to break that requirement at the moment</param> /// <returns>The exported UdonAssemblyProgramAsset</returns> public static UdonAssemblyProgramAsset UdonSharpProgramToAssemblyProgram(UdonSharpProgramAsset udonSharpProgramAsset, string savePath) { if (EditorApplication.isPlaying) { throw new System.NotSupportedException("UdonSharpEditorUtility.UdonSharpProgramToAssemblyProgram() cannot be called in play mode"); } UdonAssemblyProgramAsset newProgramAsset = ScriptableObject.CreateInstance <UdonAssemblyProgramAsset>(); AssetDatabase.CreateAsset(newProgramAsset, savePath); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(ImportAssetOptions.ForceSynchronousImport); newProgramAsset = AssetDatabase.LoadAssetAtPath <UdonAssemblyProgramAsset>(savePath); FieldInfo assemblyField = typeof(UdonAssemblyProgramAsset).GetField("udonAssembly", BindingFlags.NonPublic | BindingFlags.Instance); udonSharpProgramAsset.CompileCsProgram(); assemblyField.SetValue(newProgramAsset, assemblyField.GetValue(udonSharpProgramAsset)); MethodInfo assembleMethod = typeof(UdonAssemblyProgramAsset).GetMethod("AssembleProgram", BindingFlags.NonPublic | BindingFlags.Instance); assembleMethod.Invoke(newProgramAsset, new object[] { }); IUdonProgram uSharpProgram = udonSharpProgramAsset.GetRealProgram(); FieldInfo assemblyProgramGetter = typeof(UdonProgramAsset).GetField("program", BindingFlags.NonPublic | BindingFlags.Instance); IUdonProgram assemblyProgram = (IUdonProgram)assemblyProgramGetter.GetValue(newProgramAsset); if (uSharpProgram == null || assemblyProgram == null) { return(null); } string[] symbols = uSharpProgram.SymbolTable.GetSymbols(); foreach (string symbol in symbols) { uint symbolAddress = uSharpProgram.SymbolTable.GetAddressFromSymbol(symbol); System.Type symbolType = uSharpProgram.Heap.GetHeapVariableType(symbolAddress); object symbolValue = uSharpProgram.Heap.GetHeapVariable(symbolAddress); assemblyProgram.Heap.SetHeapVariable(assemblyProgram.SymbolTable.GetAddressFromSymbol(symbol), symbolValue, symbolType); } EditorUtility.SetDirty(newProgramAsset); newProgramAsset.SerializedProgramAsset.StoreProgram(assemblyProgram); EditorUtility.SetDirty(newProgramAsset.SerializedProgramAsset); AssetDatabase.SaveAssets(); // This doesn't work unfortunately due to how Udon tries to locate the serialized asset when importing an assembly //string serializedAssetPath = $"{Path.GetDirectoryName(savePath)}/{Path.GetFileNameWithoutExtension(savePath)}_serialized.asset"; //AssetDatabase.MoveAsset(AssetDatabase.GetAssetPath(newProgramAsset.SerializedProgramAsset), serializedAssetPath); //AssetDatabase.SaveAssets(); return(newProgramAsset); }
private static T GetVariable <T>(UdonBehaviour behaviour, string variableKey) { T output; if (behaviour.publicVariables.TryGetVariableValue <T>(variableKey, out output)) { return(output); } // Try to get the default value if there's no custom value specified if (behaviour.programSource != null && behaviour.programSource is UdonSharpProgramAsset udonSharpProgramAsset) { udonSharpProgramAsset.UpdateProgram(); IUdonProgram program = udonSharpProgramAsset.GetRealProgram(); uint varAddress; if (program.SymbolTable.TryGetAddressFromSymbol(variableKey, out varAddress)) { if (program.Heap.TryGetHeapVariable <T>(varAddress, out output)) { return(output); } } } return(default);
private void DrawSerializationDebug() { EditorGUILayout.LabelField($"DataFormat: {(DataFormat)_serializationDataFormatSerializedProperty.enumValueIndex}"); if (string.IsNullOrEmpty(_serializedProgramBytesStringSerializedProperty.stringValue)) { return; } if (_serializationDataFormatSerializedProperty.enumValueIndex == (int)DataFormat.JSON) { using (new EditorGUI.DisabledScope(true)) { EditorGUILayout.TextArea(System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(_serializedProgramBytesStringSerializedProperty.stringValue))); } } else { using (new EditorGUI.DisabledScope(true)) { SerializedUdonProgramAsset serializedUdonProgramAsset = (SerializedUdonProgramAsset)target; IUdonProgram udonProgram = serializedUdonProgramAsset.RetrieveProgram(); byte[] serializedBytes = SerializationUtility.SerializeValue(udonProgram, DataFormat.JSON, out List <UnityEngine.Object> _); EditorGUILayout.TextArea(System.Text.Encoding.UTF8.GetString(serializedBytes)); } } }
private static T GetVariable <T>(UdonBehaviour behaviour, string variableKey) { T output; if (behaviour.publicVariables.TryGetVariableValue <T>(variableKey, out output)) { return(output); } // The type no longer matches exactly, but is trivially convertible // This will usually flow into a reassignment of the public variable type in SetVarInternal() when the value gets copied back to Udon if (behaviour.publicVariables.TryGetVariableValue(variableKey, out object outputObj) && !outputObj.IsUnityObjectNull() && outputObj is T) { return((T)outputObj); } // Try to get the default value if there's no custom value specified if (behaviour.programSource != null && behaviour.programSource is UdonSharpProgramAsset udonSharpProgramAsset) { udonSharpProgramAsset.UpdateProgram(); IUdonProgram program = udonSharpProgramAsset.GetRealProgram(); uint varAddress; if (program.SymbolTable.TryGetAddressFromSymbol(variableKey, out varAddress)) { if (program.Heap.TryGetHeapVariable <T>(varAddress, out output)) { return(output); } } } return(default);
public static void OnInit(UdonBehaviour behaviour, IUdonProgram program) { CyanEmuUdonHelper helper = behaviour.gameObject.AddComponent <CyanEmuUdonHelper>(); helper.SetUdonBehaviour(behaviour); NetworkReadyFieldInfo_.SetValue(behaviour, CyanEmuMain.IsNetworkReady()); }
public int AssignHeapConstants() { CompilationModule[] compiledModules = modules.Where(e => e.ErrorCount == 0).ToArray(); foreach (CompilationModule module in compiledModules) { IUdonProgram program = module.programAsset.GetRealProgram(); if (program != null) { foreach (SymbolDefinition symbol in module.moduleSymbols.GetAllUniqueChildSymbols()) { uint symbolAddress = program.SymbolTable.GetAddressFromSymbol(symbol.symbolUniqueName); if (symbol.symbolDefaultValue != null) { program.Heap.SetHeapVariable(symbolAddress, symbol.symbolDefaultValue, symbol.symbolCsType); } else if (symbol.symbolCsType.IsArray && (symbol.declarationType.HasFlag(SymbolDeclTypeFlags.Public))) // Initialize null array fields to a 0-length array like Unity does { program.Heap.SetHeapVariable(symbolAddress, System.Activator.CreateInstance(symbol.symbolCsType, new object[] { 0 }), symbol.symbolCsType); } } } } int fieldInitializerErrorCount = RunFieldInitalizers(compiledModules); foreach (CompilationModule module in compiledModules) { IUdonProgram program = module.programAsset.GetRealProgram(); if (program != null) { // Do not let users assign null to array fields, Unity does not allow this in its normal handling foreach (SymbolDefinition symbol in module.moduleSymbols.GetAllUniqueChildSymbols()) { uint symbolAddress = program.SymbolTable.GetAddressFromSymbol(symbol.symbolUniqueName); if (symbol.symbolCsType.IsArray && (symbol.declarationType.HasFlag(SymbolDeclTypeFlags.Public))) // Initialize null array fields to a 0-length array like Unity does { object currentArrayValue = program.Heap.GetHeapVariable(symbolAddress); if (currentArrayValue == null) { program.Heap.SetHeapVariable(symbolAddress, System.Activator.CreateInstance(symbol.symbolCsType, new object[] { 0 }), symbol.symbolCsType); } } } } } return(fieldInitializerErrorCount); }
public override void StoreProgram(IUdonProgram udonProgram) { byte[] serializedProgramBytes = SerializationUtility.SerializeValue(udonProgram, DEFAULT_SERIALIZATION_DATA_FORMAT, out programUnityEngineObjects); serializedProgramBytesString = Convert.ToBase64String(serializedProgramBytes); serializationDataFormat = DEFAULT_SERIALIZATION_DATA_FORMAT; #if UNITY_EDITOR UnityEditor.EditorUtility.SetDirty(this); #endif }
public sealed override void RunEditorUpdate(UdonBehaviour udonBehaviour, ref bool dirty) { if (program == null && serializedUdonProgramAsset != null) { program = serializedUdonProgramAsset.RetrieveProgram(); } if (program == null) { RefreshProgram(); } DrawProgramSourceGUI(udonBehaviour, ref dirty); if (dirty) { EditorUtility.SetDirty(this); } }
public static UdonAssemblyProgramAsset UdonSharpProgramToAssemblyProgram(UdonSharpProgramAsset udonSharpProgramAsset, string savePath) { if (EditorApplication.isPlaying) { throw new System.NotSupportedException("USharpEditorUtility.UdonSharpProgramToAssemblyProgram() cannot be called in play mode"); } UdonAssemblyProgramAsset newProgramAsset = ScriptableObject.CreateInstance <UdonAssemblyProgramAsset>(); AssetDatabase.CreateAsset(newProgramAsset, savePath); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(ImportAssetOptions.ForceSynchronousImport); newProgramAsset = AssetDatabase.LoadAssetAtPath <UdonAssemblyProgramAsset>(savePath); udonSharpProgramAsset.CompileCsProgram(); string programAssembly = UdonSharpEditorCache.Instance.GetUASMStr(udonSharpProgramAsset); // Strip comments/inline code StringBuilder asmBuilder = new StringBuilder(); using (StringReader reader = new StringReader(programAssembly)) { string line = reader.ReadLine(); while (line != null) { if (!string.IsNullOrWhiteSpace(line) && !line.TrimStart().StartsWith("#", System.StringComparison.Ordinal)) { asmBuilder.AppendFormat("{0}\n", line); } line = reader.ReadLine(); } } programAssembly = asmBuilder.ToString(); FieldInfo assemblyField = typeof(UdonAssemblyProgramAsset).GetField("udonAssembly", BindingFlags.NonPublic | BindingFlags.Instance); assemblyField.SetValue(newProgramAsset, programAssembly); IUdonProgram program = null; try { UdonSharp.HeapFactory heapFactory = new UdonSharp.HeapFactory(); UdonEditorInterface editorInterface = new UdonEditorInterface(null, heapFactory, null, null, null, null, null, null, null); heapFactory.FactoryHeapSize = udonSharpProgramAsset.GetSerializedUdonProgramAsset().RetrieveProgram().Heap.GetHeapCapacity(); program = editorInterface.Assemble(programAssembly); } catch (System.Exception e) { Debug.LogError(e); return(null); } FieldInfo assemblyProgramField = typeof(UdonProgramAsset).GetField("program", BindingFlags.NonPublic | BindingFlags.Instance); assemblyProgramField.SetValue(newProgramAsset, program); IUdonProgram uSharpProgram = udonSharpProgramAsset.GetRealProgram(); IUdonProgram assemblyProgram = (IUdonProgram)assemblyProgramField.GetValue(newProgramAsset); if (uSharpProgram == null || assemblyProgram == null) { return(null); } ImmutableArray <string> symbols = uSharpProgram.SymbolTable.GetSymbols(); foreach (string symbol in symbols) { uint symbolAddress = uSharpProgram.SymbolTable.GetAddressFromSymbol(symbol); System.Type symbolType = uSharpProgram.Heap.GetHeapVariableType(symbolAddress); object symbolValue = uSharpProgram.Heap.GetHeapVariable(symbolAddress); assemblyProgram.Heap.SetHeapVariable(assemblyProgram.SymbolTable.GetAddressFromSymbol(symbol), symbolValue, symbolType); } EditorUtility.SetDirty(newProgramAsset); newProgramAsset.SerializedProgramAsset.StoreProgram(assemblyProgram); EditorUtility.SetDirty(newProgramAsset.SerializedProgramAsset); AssetDatabase.SaveAssets(); // This doesn't work unfortunately due to how Udon tries to locate the serialized asset when importing an assembly //string serializedAssetPath = $"{Path.GetDirectoryName(savePath)}/{Path.GetFileNameWithoutExtension(savePath)}_serialized.asset"; //AssetDatabase.MoveAsset(AssetDatabase.GetAssetPath(newProgramAsset.SerializedProgramAsset), serializedAssetPath); //AssetDatabase.SaveAssets(); return(newProgramAsset); }
private int RunFieldInitalizers(CompilationModule[] compiledModules) { CompilationModule[] modulesToInitialize = compiledModules.Where(e => e.fieldsWithInitializers.Count > 0).ToArray(); // We don't need to run the costly compilation if the user hasn't defined any fields with initializers if (modulesToInitialize.Length == 0) { return(0); } int initializerErrorCount = 0; SyntaxTree[] initializerTrees = new SyntaxTree[modulesToInitialize.Length]; StringBuilder[] codeStringBuilders = new StringBuilder[modulesToInitialize.Length]; for (int moduleIdx = 0; moduleIdx < modulesToInitialize.Length; ++moduleIdx) { CompilationModule module = modulesToInitialize[moduleIdx]; CodeCompileUnit compileUnit = new CodeCompileUnit(); CodeNamespace ns = new CodeNamespace("FieldInitialzers"); compileUnit.Namespaces.Add(ns); foreach (var resolverUsingNamespace in module.resolver.usingNamespaces) { if (!string.IsNullOrEmpty(resolverUsingNamespace)) { ns.Imports.Add(new CodeNamespaceImport(resolverUsingNamespace)); } } CodeTypeDeclaration _class = new CodeTypeDeclaration($"Initializer{moduleIdx}"); ns.Types.Add(_class); CodeMemberMethod method = new CodeMemberMethod(); _class.Members.Add(method); method.Attributes = MemberAttributes.Public | MemberAttributes.Static; method.ReturnType = new CodeTypeReference(typeof(void)); method.Name = "DoInit"; method.Parameters.Add(new CodeParameterDeclarationExpression(typeof(IUdonProgram), "program")); foreach (var fieldDeclarationSyntax in module.fieldsWithInitializers) { var type = fieldDeclarationSyntax.Declaration.Type; int count = 0; bool isConst = fieldDeclarationSyntax.Modifiers.Any(t => t.ToString() == "const"); foreach (var variable in fieldDeclarationSyntax.Declaration.Variables) { FieldDefinition fieldDef = module.compiledClassDefinition?.fieldDefinitions?.Find(e => (e.fieldSymbol.declarationType == SymbolDeclTypeFlags.Private || e.fieldSymbol.declarationType == SymbolDeclTypeFlags.Public) && e.fieldSymbol.symbolOriginalName == variable.Identifier.ToString()); string typeQualifiedName = type.ToString(); if (fieldDef != null) { if (fieldDef.fieldSymbol.symbolCsType.Namespace.Length == 0) { typeQualifiedName = fieldDef.fieldSymbol.symbolCsType.Name; } else { typeQualifiedName = fieldDef.fieldSymbol.symbolCsType.Namespace + "." + fieldDef.fieldSymbol.symbolCsType.Name; } } if (variable.Initializer != null) { string name = variable.Identifier.ToString(); if (isConst) { _class.Members.Add(new CodeSnippetTypeMember($"const {typeQualifiedName} {name} {variable.Initializer};")); } else { method.Statements.Add(new CodeSnippetStatement($"{typeQualifiedName} {name} {variable.Initializer};")); } method.Statements.Add(new CodeSnippetStatement( $"program.Heap.SetHeapVariable(program.SymbolTable.GetAddressFromSymbol(\"{variable.Identifier}\"), {name});")); count++; } } } StringBuilder sb = new StringBuilder(); codeStringBuilders[moduleIdx] = sb; CSharpCodeProvider provider = new CSharpCodeProvider(); using (StringWriter streamWriter = new StringWriter(sb)) { provider.GenerateCodeFromCompileUnit(compileUnit, streamWriter, new CodeGeneratorOptions()); } SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(sb.ToString()); initializerTrees[moduleIdx] = syntaxTree; } var assemblies = System.AppDomain.CurrentDomain.GetAssemblies(); var references = new List <MetadataReference>(); for (int i = 0; i < assemblies.Length; i++) { if (!assemblies[i].IsDynamic && assemblies[i].Location.Length > 0) { references.Add(MetadataReference.CreateFromFile(assemblies[i].Location)); } } CSharpCompilation compilation = CSharpCompilation.Create( $"init{initAssemblyCounter++}", syntaxTrees: initializerTrees, references: references, options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); using (var memoryStream = new MemoryStream()) { EmitResult result = compilation.Emit(memoryStream); if (!result.Success) { // todo: make these errors point to the correct source files bool error = false; foreach (Diagnostic diagnostic in result.Diagnostics) { if (diagnostic.Severity == DiagnosticSeverity.Error) { Debug.LogError(diagnostic); error = true; initializerErrorCount++; } } if (error) { Debug.LogError($"Generated Source code: {string.Join("\n", codeStringBuilders.Select(e => e.ToString()))}"); } } else { memoryStream.Seek(0, SeekOrigin.Begin); Assembly assembly = Assembly.Load(memoryStream.ToArray()); for (int moduleIdx = 0; moduleIdx < modulesToInitialize.Length; ++moduleIdx) { CompilationModule module = modulesToInitialize[moduleIdx]; IUdonProgram program = module.programAsset.GetRealProgram(); System.Type cls = assembly.GetType($"FieldInitialzers.Initializer{moduleIdx}"); MethodInfo methodInfo = cls.GetMethod("DoInit", BindingFlags.Public | BindingFlags.Static); methodInfo.Invoke(null, new[] { program }); foreach (var fieldDeclarationSyntax in module.fieldsWithInitializers) { foreach (var variable in fieldDeclarationSyntax.Declaration.Variables) { string varName = variable.Identifier.ToString(); object heapValue = program.Heap.GetHeapVariable(program.SymbolTable.GetAddressFromSymbol(varName)); if (heapValue != null && UdonSharpUtils.IsUserDefinedType(heapValue.GetType())) { UdonSharpUtils.LogBuildError($"Field: '{varName}' UdonSharp does not yet support field initializers on user-defined types or jagged arrays", AssetDatabase.GetAssetPath(module.programAsset.sourceCsScript), 0, 0); } } } } } } return(initializerErrorCount); }
private void RunFieldInitalizers(CompilationModule module) { IUdonProgram program = module.programAsset.GetRealProgram(); // We don't need to run the costly compilation if the user hasn't defined any fields with initializers if (module.fieldsWithInitializers.Count == 0) { return; } CodeCompileUnit compileUnit = new CodeCompileUnit(); CodeNamespace ns = new CodeNamespace("FieldInitialzers"); compileUnit.Namespaces.Add(ns); foreach (var resolverUsingNamespace in module.resolver.usingNamespaces) { if (!string.IsNullOrEmpty(resolverUsingNamespace)) { ns.Imports.Add(new CodeNamespaceImport(resolverUsingNamespace)); } } CodeTypeDeclaration _class = new CodeTypeDeclaration("Initializer"); ns.Types.Add(_class); CodeMemberMethod method = new CodeMemberMethod(); _class.Members.Add(method); method.Attributes = MemberAttributes.Public | MemberAttributes.Static; method.ReturnType = new CodeTypeReference(typeof(void)); method.Name = "DoInit"; method.Parameters.Add(new CodeParameterDeclarationExpression(typeof(IUdonProgram), "program")); foreach (var fieldDeclarationSyntax in module.fieldsWithInitializers) { var type = fieldDeclarationSyntax.Declaration.Type; int count = 0; bool isConst = fieldDeclarationSyntax.Modifiers.Any(t => t.ToString() == "const"); foreach (var variable in fieldDeclarationSyntax.Declaration.Variables) { if (variable.Initializer != null) { string name = variable.Identifier.ToString(); if (isConst) { _class.Members.Add(new CodeSnippetTypeMember($"const {type} {name} {variable.Initializer};")); } else { method.Statements.Add(new CodeSnippetStatement($"{type} {name} {variable.Initializer};")); } method.Statements.Add(new CodeSnippetStatement( $"program.Heap.SetHeapVariable(program.SymbolTable.GetAddressFromSymbol(\"{variable.Identifier}\"), {name});")); count++; } } } CSharpCodeProvider provider = new CSharpCodeProvider(); StringBuilder sb = new StringBuilder(); using (StringWriter streamWriter = new StringWriter(sb)) { provider.GenerateCodeFromCompileUnit(compileUnit, streamWriter, new CodeGeneratorOptions()); } SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(sb.ToString()); var assemblies = System.AppDomain.CurrentDomain.GetAssemblies(); var references = new List <MetadataReference>(); for (int i = 0; i < assemblies.Length; i++) { if (!assemblies[i].IsDynamic && assemblies[i].Location.Length > 0) { references.Add(MetadataReference.CreateFromFile(assemblies[i].Location)); } } CSharpCompilation compilation = CSharpCompilation.Create( $"init{initAssemblyCounter++}", syntaxTrees: new[] { syntaxTree }, references: references, options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); using (var memoryStream = new MemoryStream()) { EmitResult result = compilation.Emit(memoryStream); if (!result.Success) { bool error = false; foreach (Diagnostic diagnostic in result.Diagnostics) { if (diagnostic.Severity == DiagnosticSeverity.Error) { Debug.LogError(diagnostic); error = true; } } if (error) { Debug.LogError($"Generated Source code: {sb}"); } } else { memoryStream.Seek(0, SeekOrigin.Begin); Assembly assembly = Assembly.Load(memoryStream.ToArray()); var cls = assembly.GetType("FieldInitialzers.Initializer"); MethodInfo methodInfo = cls.GetMethod("DoInit", BindingFlags.Public | BindingFlags.Static); methodInfo.Invoke(null, new[] { program }); } } }
public abstract void StoreProgram(IUdonProgram udonProgram);
/// <summary> /// プログラムからのディスアセンブル /// </summary> /// <param name="program">VRC.Udon.Common.Interfaces.IUdonProgram</param> /// <returns>string</returns> protected static string GetDisassembleCode(IUdonProgram program) { var disasm = new UAssemblyDisassembler(); return(String.Join("\n", disasm.DisassembleProgram(program))); }