/// <summary> /// If you're considering commenting any section of this out, try enabling the force compile in the U# settings first. /// This is here to prevent you from corrupting your project files. /// If scripts are left uncompiled from Unity's side when uploading, there is a chance to corrupt your assemblies which can cause all of your UdonBehaviours to lose their variables if handled wrong. /// </summary> /// <param name="requestedBuildType"></param> /// <returns></returns> public bool OnBuildRequested(VRCSDKRequestedBuildType requestedBuildType) { UdonSharpSettings settings = UdonSharpSettings.GetSettings(); bool shouldForceCompile = settings.shouldForceCompile; // Unity doesn't like this and will throw errors if it ends up compiling scripts. But it seems to work. // This is marked experimental for now since I don't know if it will break horribly in some case. if (shouldForceCompile) { AssetDatabase.Refresh(ImportAssetOptions.ForceUpdate | ImportAssetOptions.ForceSynchronousImport); } else { AssetDatabase.Refresh(ImportAssetOptions.ForceUpdate); if (EditorApplication.isCompiling) { UdonSharpUtils.LogWarning("Scripts are in the process of compiling, please retry build after scripts have compiled."); UdonSharpUtils.ShowEditorNotification("Scripts are in the process of compiling, please retry build after scripts have compiled."); return(false); } } return(true); }
public bool OnBuildRequested(VRCSDKRequestedBuildType requestedBuildType) { if (requestedBuildType == VRCSDKRequestedBuildType.Avatar) { return(true); } if (UdonSharpSettings.GetSettings().disableUploadCompile) { return(true); } UdonSharpCompilerV1.CompileSync(new UdonSharpCompileOptions() { IsEditorBuild = false }); UdonSharpEditorCache.SaveAllCache(); if (UdonSharpProgramAsset.AnyUdonSharpScriptHasError()) { UdonSharpUtils.LogError("Failed to compile UdonSharp scripts for build, check error log for details."); UdonSharpUtils.ShowEditorNotification("Failed to compile UdonSharp scripts for build, check error log for details."); return(false); } if (UdonSharpEditorManager.RunAllUpgrades()) { UdonSharpUtils.LogWarning(UdonSharpEditorManager.UPGRADE_MESSAGE); return(false); } return(true); }
private static void RunBehaviourSetup(UdonSharpBehaviour behaviour, bool withUndo) { UdonBehaviour backingBehaviour = GetBackingUdonBehaviour(behaviour); // Handle components pasted across different behaviours if (backingBehaviour && backingBehaviour.gameObject != behaviour.gameObject) { backingBehaviour = null; } // Handle pasting components on the same behaviour, assumes pasted components are always the last in the list. if (backingBehaviour) { int refCount = 0; UdonSharpBehaviour[] behaviours = backingBehaviour.GetComponents <UdonSharpBehaviour>(); foreach (UdonSharpBehaviour udonSharpBehaviour in behaviours) { if (GetBackingUdonBehaviour(udonSharpBehaviour) == backingBehaviour) { refCount++; } } if (refCount > 1 && behaviour == behaviours.Last()) { backingBehaviour = null; } } bool isPartOfPrefabInstance = PrefabUtility.IsPartOfPrefabInstance(behaviour) && PrefabUtility.GetCorrespondingObjectFromSource(behaviour) != behaviour; if (backingBehaviour == null) { if (isPartOfPrefabInstance) { UdonSharpUtils.LogWarning("Cannot setup behaviour on prefab instance, original prefab asset needs setup"); return; } SetIgnoreEvents(true); try { backingBehaviour = withUndo ? Undo.AddComponent <UdonBehaviour>(behaviour.gameObject) : behaviour.gameObject.AddComponent <UdonBehaviour>(); #pragma warning disable CS0618 // Type or member is obsolete backingBehaviour.SynchronizePosition = false; backingBehaviour.AllowCollisionOwnershipTransfer = false; #pragma warning restore CS0618 // Type or member is obsolete MoveComponentRelativeToComponent(backingBehaviour, behaviour, false); SetBackingUdonBehaviour(behaviour, backingBehaviour); SetBehaviourVersion(backingBehaviour, UdonSharpBehaviourVersion.CurrentVersion); SetSceneBehaviourUpgraded(backingBehaviour); // UdonSharpUtils.Log($"Created behaviour {backingBehaviour}", behaviour); } finally { SetIgnoreEvents(false); } _proxyBehaviourLookup.Add(backingBehaviour, behaviour); UdonSharpUtils.SetDirty(behaviour); UdonSharpUtils.SetDirty(backingBehaviour); } // Handle U# behaviours that have been added to a prefab via Added Component > Apply To Prefab, but have not had their backing behaviour added // if (isPartOfPrefabInstance && // backingBehaviour != null && // !PrefabUtility.IsPartOfPrefabInstance(backingBehaviour)) // { // PropertyModification[] modifications = PrefabUtility.GetPropertyModifications(behaviour); // // if (modifications != null) // { // // } // } UdonSharpProgramAsset programAsset = GetUdonSharpProgramAsset(behaviour); if (backingBehaviour.programSource == null) { backingBehaviour.programSource = programAsset; if (backingBehaviour.programSource == null) { UdonSharpUtils.LogError($"Unable to find valid U# program asset associated with script '{behaviour}'", behaviour); } UdonSharpUtils.SetDirty(backingBehaviour); } if (_serializedProgramAssetField.GetValue(backingBehaviour) == null) { SerializedObject componentAsset = new SerializedObject(backingBehaviour); SerializedProperty serializedProgramAssetProperty = componentAsset.FindProperty("serializedProgramAsset"); serializedProgramAssetProperty.objectReferenceValue = programAsset.SerializedProgramAsset; if (withUndo) { componentAsset.ApplyModifiedProperties(); } else { componentAsset.ApplyModifiedPropertiesWithoutUndo(); } } if (backingBehaviour.enabled != behaviour.enabled) { if (withUndo) { Undo.RecordObject(backingBehaviour, "Enabled change"); } backingBehaviour.enabled = behaviour.enabled; if (!withUndo) { UdonSharpUtils.SetDirty(backingBehaviour); } } #if UDONSHARP_DEBUG backingBehaviour.hideFlags &= ~HideFlags.HideInInspector; #else backingBehaviour.hideFlags |= HideFlags.HideInInspector; #endif ((UdonSharpProgramAsset)backingBehaviour.programSource)?.UpdateProgram(); }
internal static void UpgradeSceneBehaviours(IEnumerable <UdonBehaviour> behaviours) { if (EditorApplication.isPlaying) { return; } // Create proxies if they do not exist foreach (UdonBehaviour udonBehaviour in behaviours) { if (!IsUdonSharpBehaviour(udonBehaviour)) { continue; } if (PrefabUtility.IsPartOfPrefabInstance(udonBehaviour) && PrefabUtility.IsAddedComponentOverride(udonBehaviour)) { continue; } if (GetProxyBehaviour(udonBehaviour) == null) { if (PrefabUtility.IsPartOfPrefabInstance(udonBehaviour) && PrefabUtility.GetCorrespondingObjectFromSource(udonBehaviour) != udonBehaviour) { UdonSharpUtils.LogError($"Cannot upgrade scene behaviour '{udonBehaviour}' since its prefab must be upgraded.", udonBehaviour); continue; } Type udonSharpBehaviourType = GetUdonSharpBehaviourType(udonBehaviour); if (!udonSharpBehaviourType.IsSubclassOf(typeof(UdonSharpBehaviour))) { UdonSharpUtils.LogError($"Class script referenced by program asset '{udonBehaviour.programSource}' is not an UdonSharpBehaviour", udonBehaviour.programSource); continue; } UdonSharpBehaviour newProxy = (UdonSharpBehaviour)udonBehaviour.gameObject.AddComponent(udonSharpBehaviourType); newProxy.enabled = udonBehaviour.enabled; SetBackingUdonBehaviour(newProxy, udonBehaviour); if (!PrefabUtility.IsAddedComponentOverride(udonBehaviour)) { MoveComponentRelativeToComponent(newProxy, udonBehaviour, true); } else { UdonSharpUtils.LogWarning($"Cannot reorder internal UdonBehaviour for '{udonBehaviour}' during upgrade because it is on a prefab instance.", udonBehaviour.gameObject); } UdonSharpUtils.SetDirty(newProxy); } if (GetBehaviourVersion(udonBehaviour) == UdonSharpBehaviourVersion.V0) { SetBehaviourVersion(udonBehaviour, UdonSharpBehaviourVersion.V0DataUpgradeNeeded); } } // Copy data over from UdonBehaviour to UdonSharpBehaviour foreach (UdonBehaviour udonBehaviour in behaviours) { if (!IsUdonSharpBehaviour(udonBehaviour)) { continue; } bool needsPrefabInstanceUpgrade = false; // Checks if the version is below V1 or if it needs the prefab instance upgrade UdonSharpBehaviourVersion behaviourVersion = GetBehaviourVersion(udonBehaviour); if (behaviourVersion >= UdonSharpBehaviourVersion.V1) { // Check if the prefab instance has a prefab that was upgraded causing the string data to be copied, but has a delta'd UnityEngine.Object storage array if (PrefabUtility.IsPartOfPrefabInstance(udonBehaviour) && !HasSceneBehaviourUpgradeFlag(udonBehaviour)) { UdonBehaviour prefabSource = PrefabUtility.GetCorrespondingObjectFromSource(udonBehaviour); if (prefabSource && BehaviourRequiresBackwardsCompatibilityPersistence(prefabSource)) { PropertyModification[] modifications = PrefabUtility.GetPropertyModifications(udonBehaviour); if (modifications != null && modifications.Any(e => e.propertyPath.StartsWith("publicVariablesUnityEngineObjects", StringComparison.Ordinal))) { needsPrefabInstanceUpgrade = true; } } } if (!needsPrefabInstanceUpgrade) { continue; } } UdonSharpBehaviour proxy = GetProxyBehaviour(udonBehaviour); if (proxy == null) { UdonSharpUtils.LogWarning($"UdonSharpBehaviour '{udonBehaviour}' could not be upgraded since it is missing a proxy", udonBehaviour); continue; } CopyUdonToProxy(proxy, ProxySerializationPolicy.RootOnly); // Nuke out old data now because we want only the C# side to own the data from this point on ClearBehaviourVariables(udonBehaviour, true); SetBehaviourVersion(udonBehaviour, UdonSharpBehaviourVersion.V1); SetSceneBehaviourUpgraded(udonBehaviour); if (needsPrefabInstanceUpgrade) { UdonSharpUtils.Log($"Scene behaviour '{udonBehaviour.name}' needed UnityEngine.Object upgrade pass", udonBehaviour); } UdonSharpUtils.SetDirty(proxy); UdonSharpUtils.Log($"Upgraded scene behaviour '{udonBehaviour.name}'", udonBehaviour); } }
/// <summary> /// Runs a two pass upgrade of a set of prefabs, assumes all dependencies of the prefabs are included, otherwise the process could fail to maintain references. /// First creates a new UdonSharpBehaviour proxy script and hooks it to a given UdonBehaviour. Then in a second pass goes over all behaviours and serializes their data into the C# proxy and wipes their old data out. /// </summary> /// <param name="prefabRootEnumerable"></param> internal static void UpgradePrefabs(IEnumerable <GameObject> prefabRootEnumerable) { if (UdonSharpProgramAsset.IsAnyProgramAssetSourceDirty() || UdonSharpProgramAsset.IsAnyProgramAssetOutOfDate()) { UdonSharpCompilerV1.CompileSync(); } GameObject[] prefabRoots = prefabRootEnumerable.ToArray(); bool NeedsNewProxy(UdonBehaviour udonBehaviour) { if (!IsUdonSharpBehaviour(udonBehaviour)) { return(false); } // The behaviour originates from a parent prefab so we don't want to modify this copy of the prefab if (PrefabUtility.GetCorrespondingObjectFromOriginalSource(udonBehaviour) != udonBehaviour) { if (!PrefabUtility.IsPartOfPrefabInstance(udonBehaviour)) { UdonSharpUtils.LogWarning($"Nested prefab with UdonSharpBehaviours detected during prefab upgrade: {udonBehaviour.gameObject}, nested prefabs are not eligible for automatic upgrade."); } return(false); } if (GetProxyBehaviour(udonBehaviour)) { return(false); } return(true); } bool NeedsSerializationUpgrade(UdonBehaviour udonBehaviour) { if (!IsUdonSharpBehaviour(udonBehaviour)) { return(false); } if (NeedsNewProxy(udonBehaviour)) { return(true); } if (GetBehaviourVersion(udonBehaviour) == UdonSharpBehaviourVersion.V0DataUpgradeNeeded) { return(true); } return(false); } HashSet <GameObject> phase1FixupPrefabRoots = new HashSet <GameObject>(); // Phase 1 Pruning - Add missing proxy behaviours foreach (GameObject prefabRoot in prefabRoots) { if (!prefabRoot.GetComponentsInChildren <UdonBehaviour>(true).Any(NeedsNewProxy)) { continue; } string prefabPath = AssetDatabase.GetAssetPath(prefabRoot); if (!prefabPath.IsNullOrWhitespace()) { phase1FixupPrefabRoots.Add(prefabRoot); } } HashSet <GameObject> phase2FixupPrefabRoots = new HashSet <GameObject>(phase1FixupPrefabRoots); // Phase 2 Pruning - Check for behaviours that require their data ownership to be transferred Udon -> C# foreach (GameObject prefabRoot in prefabRoots) { foreach (UdonBehaviour udonBehaviour in prefabRoot.GetComponentsInChildren <UdonBehaviour>(true)) { if (NeedsSerializationUpgrade(udonBehaviour)) { string prefabPath = AssetDatabase.GetAssetPath(prefabRoot); if (!prefabPath.IsNullOrWhitespace()) { phase2FixupPrefabRoots.Add(prefabRoot); } break; } } } // Now we have a set of prefabs that we can actually load and run the two upgrade phases on. // Todo: look at merging the two passes since we don't actually need to load prefabs into scenes apparently // Early out and avoid the edit scope if (phase1FixupPrefabRoots.Count == 0 && phase2FixupPrefabRoots.Count == 0) { return; } if (phase2FixupPrefabRoots.Count > 0) { UdonSharpUtils.Log($"Running upgrade process on {phase2FixupPrefabRoots.Count} prefabs: {string.Join(", ", phase2FixupPrefabRoots.Select(e => e.name))}"); } using (new UdonSharpEditorManager.AssetEditScope()) { foreach (GameObject prefabRoot in phase1FixupPrefabRoots) { try { foreach (UdonBehaviour udonBehaviour in prefabRoot.GetComponentsInChildren <UdonBehaviour>(true)) { if (!NeedsNewProxy(udonBehaviour)) { continue; } UdonSharpBehaviour newProxy = (UdonSharpBehaviour)udonBehaviour.gameObject.AddComponent(GetUdonSharpBehaviourType(udonBehaviour)); newProxy.enabled = udonBehaviour.enabled; SetBackingUdonBehaviour(newProxy, udonBehaviour); MoveComponentRelativeToComponent(newProxy, udonBehaviour, true); SetBehaviourVersion(udonBehaviour, UdonSharpBehaviourVersion.V0DataUpgradeNeeded); } // Phase2 is a superset of phase 1 upgrades, and AssetEditScope prevents flushing to disk anyways so just don't save here. // PrefabUtility.SavePrefabAsset(prefabRoot); // UdonSharpUtils.Log($"Ran prefab upgrade phase 1 on {prefabRoot}"); } catch (Exception e) { UdonSharpUtils.LogError($"Encountered exception while upgrading prefab {prefabRoot}, report exception to Merlin: {e}"); } } foreach (GameObject prefabRoot in phase2FixupPrefabRoots) { try { foreach (UdonBehaviour udonBehaviour in prefabRoot.GetComponentsInChildren <UdonBehaviour>(true)) { if (!NeedsSerializationUpgrade(udonBehaviour)) { continue; } CopyUdonToProxy(GetProxyBehaviour(udonBehaviour), ProxySerializationPolicy.RootOnly); // We can't remove this data for backwards compatibility :'( // If we nuke the data, the unity object array on the underlying storage may change. // Which means that if people have copies of this prefab in the scene with no object reference changes, their data will also get nuked which we do not want. // Public variable data on the prefabs will never be touched again by U# after upgrading // We will probably provide an optional upgrade process that strips this extra data, and takes into account all scenes in the project // foreach (string publicVarSymbol in udonBehaviour.publicVariables.VariableSymbols.ToArray()) // udonBehaviour.publicVariables.RemoveVariable(publicVarSymbol); SetBehaviourVersion(udonBehaviour, UdonSharpBehaviourVersion.V1); SetBehaviourUpgraded(udonBehaviour); } PrefabUtility.SavePrefabAsset(prefabRoot); // UdonSharpUtils.Log($"Ran prefab upgrade phase 2 on {prefabRoot}"); } catch (Exception e) { UdonSharpUtils.LogError($"Encountered exception while upgrading prefab {prefabRoot}, report exception to Merlin: {e}"); } } UdonSharpUtils.Log("Prefab upgrade pass finished"); } }
private static void TickCompile() { if (CurrentJob == null) return; if (!CurrentJob.Task.IsCompleted) { CompilationContext.CompilePhase currentPhase = CurrentJob.Context.CurrentPhase; float phaseProgress = CurrentJob.Context.PhaseProgress; float totalProgress = (phaseProgress / (int) CompilationContext.CompilePhase.Count) + ((int) currentPhase / (float)(int)CompilationContext.CompilePhase.Count); UdonSharpUtils.ShowAsyncProgressBar($"U#: {currentPhase}", totalProgress); return; } if (!CurrentJob.CompileOptions.DisableLogging) { foreach (var diagnostic in CurrentJob.Context.Diagnostics) { string filePath = ""; if (diagnostic.Location != null) filePath = CurrentJob.Context.TranslateLocationToFileName(diagnostic.Location); LinePosition? linePosition = diagnostic.Location?.GetLineSpan().StartLinePosition; int line = (linePosition?.Line ?? 0) + 1; int character = (linePosition?.Character ?? 0) + 1; string fileStr = $"{filePath ?? "Unknown File"}({line},{character})"; string logStr = $"{fileStr}: {diagnostic.Message}"; switch (diagnostic.Severity) { case DiagnosticSeverity.Error: UdonSharpUtils.LogBuildError(diagnostic.Message, filePath, line, character); break; case DiagnosticSeverity.Warning: UdonSharpUtils.LogWarning(logStr); break; case DiagnosticSeverity.Log: UdonSharpUtils.Log(logStr); break; } } } // Translate the diagnostic types and apply them to the cache, todo: consider merging the two structures UdonSharpEditorCache.CompileDiagnostic[] diagnostics = new UdonSharpEditorCache.CompileDiagnostic[CurrentJob.Context.Diagnostics.Count]; CompilationContext.CompileDiagnostic[] compileDiagnostics = CurrentJob.Context.Diagnostics.ToArray(); for (int i = 0; i < diagnostics.Length; ++i) { LinePosition diagLine = compileDiagnostics[i]?.Location?.GetLineSpan().StartLinePosition ?? LinePosition.Zero; diagnostics[i] = new UdonSharpEditorCache.CompileDiagnostic() { severity = compileDiagnostics[i].Severity, message = compileDiagnostics[i].Message, file = CurrentJob.Context.TranslateLocationToFileName(compileDiagnostics[i].Location) ?? "", line = diagLine.Line, character = diagLine.Character, }; } UdonSharpEditorCache.Instance.LastCompileDiagnostics = diagnostics; if (CurrentJob.Task.IsFaulted) { UdonSharpUtils.LogError("internal compiler error, dumping exceptions. Please report to Merlin"); if (CurrentJob.Task.Exception != null) { foreach (Exception innerException in CurrentJob.Task.Exception.InnerExceptions) { UdonSharpUtils.LogError(innerException); } } CleanupCompile(); return; } if (CurrentJob.Context.ErrorCount > 0) { CleanupCompile(); return; } foreach (ModuleBinding rootBinding in CurrentJob.Context.ModuleBindings) { if (rootBinding.programAsset == null) continue; rootBinding.programAsset.ApplyProgram(); UdonSharpEditorCache.Instance.SetUASMStr(rootBinding.programAsset, rootBinding.assembly); rootBinding.programAsset.CompiledVersion = UdonSharpProgramVersion.CurrentVersion; EditorUtility.SetDirty(rootBinding.programAsset); } try { UdonSharpEditorCache.Instance.RehashAllScripts(); UdonSharpEditorManager.RunPostBuildSceneFixup(); } catch (Exception e) { UdonSharpUtils.LogError($"Exception while running post build fixup:\n{e}"); CleanupCompile(); return; } UdonSharpEditorCache.Instance.LastBuildType = CurrentJob.CompileOptions.IsEditorBuild ? UdonSharpEditorCache.DebugInfoType.Editor : UdonSharpEditorCache.DebugInfoType.Client; int scriptCount = CurrentJob.Context.ModuleBindings.Count(e => e.programAsset != null); UdonSharpUtils.Log($"Compile of {scriptCount} script{(scriptCount != 1 ? "s" : "")} finished in {CurrentJob.CompileTimer.Elapsed:mm\\:ss\\.fff}"); CleanupCompile(); if (_compileQueued) { Compile(_queuedOptions); _compileQueued = false; _queuedOptions = null; } }
public void OnEnable() { errorState = BehaviourInspectorErrorState.Success; bool needsUpgradePass = false; foreach (Object target in targets) { UdonBehaviour targetBehaviour = (UdonBehaviour)target; if (!UdonSharpEditorUtility.IsUdonSharpBehaviour(targetBehaviour)) { UdonSharpUtils.LogWarning($"UdonBehaviour '{targetBehaviour}' is not using a valid U# program asset", targetBehaviour); errorState = BehaviourInspectorErrorState.NoValidUSharpProgram; break; } if (UdonSharpEditorUtility.GetProxyBehaviour(targetBehaviour) != null) { continue; } needsUpgradePass = true; if (PrefabUtility.IsPartOfPrefabInstance(targetBehaviour) && !PrefabUtility.IsAddedComponentOverride(targetBehaviour)) { UdonSharpUtils.LogWarning($"UdonBehaviour '{targetBehaviour}' needs upgrade on source prefab asset.", targetBehaviour); errorState = BehaviourInspectorErrorState.PrefabNeedsUpgrade; break; } } if (!needsUpgradePass || errorState != BehaviourInspectorErrorState.Success) { return; } foreach (Object target in targets) { UdonBehaviour targetBehaviour = (UdonBehaviour)target; UdonSharpBehaviour udonSharpBehaviour = UdonSharpEditorUtility.GetProxyBehaviour(targetBehaviour); // Needs setup if (udonSharpBehaviour == null) { UdonSharpEditorUtility.SetIgnoreEvents(true); try { udonSharpBehaviour = (UdonSharpBehaviour)Undo.AddComponent(targetBehaviour.gameObject, UdonSharpEditorUtility.GetUdonSharpBehaviourType(targetBehaviour)); UdonSharpEditorUtility.SetBackingUdonBehaviour(udonSharpBehaviour, targetBehaviour); UdonSharpEditorUtility.MoveComponentRelativeToComponent(udonSharpBehaviour, targetBehaviour, true); UdonSharpEditorUtility.SetBehaviourVersion(targetBehaviour, UdonSharpBehaviourVersion.CurrentVersion); UdonSharpEditorUtility.SetSceneBehaviourUpgraded(targetBehaviour); } finally { UdonSharpEditorUtility.SetIgnoreEvents(false); } } udonSharpBehaviour.enabled = targetBehaviour.enabled; #if !UDONSHARP_DEBUG targetBehaviour.hideFlags = HideFlags.HideInInspector; #else targetBehaviour.hideFlags = HideFlags.None; #endif } }
public override SyntaxNode VisitFieldDeclaration(FieldDeclarationSyntax node) { FieldDeclarationSyntax fieldDeclaration = (FieldDeclarationSyntax)base.VisitFieldDeclaration(node); var typeInfo = model.GetTypeInfo(node.Declaration.Type); if (typeInfo.Type == null) { UdonSharpUtils.LogWarning($"Could not find symbol for {node}"); return(fieldDeclaration); } ITypeSymbol rootType = typeInfo.Type; while (rootType.TypeKind == TypeKind.Array) { rootType = ((IArrayTypeSymbol)rootType).ElementType; } if (rootType.TypeKind == TypeKind.Error || rootType.TypeKind == TypeKind.Unknown) { UdonSharpUtils.LogWarning($"Type {typeInfo.Type} for field '{fieldDeclaration.Declaration}' is invalid"); return(fieldDeclaration); } IFieldSymbol firstFieldSymbol = (IFieldSymbol)model.GetDeclaredSymbol(node.Declaration.Variables.First()); rootType = firstFieldSymbol.Type; // If the field is not serialized or is using Odin already, we don't need to do anything. if (!IsFieldSerializedWithoutOdin(firstFieldSymbol)) { return(fieldDeclaration); } // Getting the type may fail if it's a user type that hasn't compiled on the C# side yet. For now we skip it, but we should do a simplified check for jagged arrays if (!TypeSymbol.TryGetSystemType(rootType, out Type systemType)) { return(fieldDeclaration); } // If Unity can serialize the type, we're good if (UnitySerializationUtility.GuessIfUnityWillSerialize(systemType)) { return(fieldDeclaration); } // Common type that gets picked up as serialized but shouldn't be // todo: Add actual checking for if a type is serializable, which isn't consistent. Unity/System library types in large part are serializable but don't have the System.Serializable tag, but types outside those assemblies need the tag to be serialized. if (systemType == typeof(VRCPlayerApi) || systemType == typeof(VRCPlayerApi[])) { return(fieldDeclaration); } Modified = true; NameSyntax odinSerializeName = IdentifierName("VRC"); odinSerializeName = QualifiedName(odinSerializeName, IdentifierName("Udon")); odinSerializeName = QualifiedName(odinSerializeName, IdentifierName("Serialization")); odinSerializeName = QualifiedName(odinSerializeName, IdentifierName("OdinSerializer")); odinSerializeName = QualifiedName(odinSerializeName, IdentifierName("OdinSerialize")); // Somehow it seems like there's literally no decent way to maintain the indent on inserted code so we'll just inline the comment because Roslyn is dumb SyntaxTrivia commentTrivia = Comment(" /* UdonSharp auto-upgrade: serialization */ "); AttributeListSyntax newAttribList = AttributeList(SeparatedList(new [] { Attribute(odinSerializeName) })).WithTrailingTrivia(commentTrivia); SyntaxList <AttributeListSyntax> attributeList = fieldDeclaration.AttributeLists; if (attributeList.Count > 0) { SyntaxTriviaList trailingTrivia = attributeList.Last().GetTrailingTrivia(); trailingTrivia = trailingTrivia.Insert(0, commentTrivia); attributeList.Replace(attributeList[attributeList.Count - 1], attributeList[attributeList.Count - 1].WithoutTrailingTrivia()); newAttribList = newAttribList.WithTrailingTrivia(trailingTrivia); } else { newAttribList = newAttribList.WithLeadingTrivia(fieldDeclaration.GetLeadingTrivia()); fieldDeclaration = fieldDeclaration.WithoutLeadingTrivia(); } attributeList = attributeList.Add(newAttribList); return(fieldDeclaration.WithAttributeLists(attributeList)); }