Beispiel #1
0
        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);
            }
        }
        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;
            }
        }
Beispiel #3
0
        /// <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");
            }
        }
Beispiel #4
0
        private static bool UpgradeScripts(UdonSharpProgramAsset[] programAssets)
        {
            if (programAssets.Length == 0)
            {
                return(false);
            }

            if (programAssets.All(e => e.ScriptVersion >= UdonSharpProgramVersion.CurrentVersion))
            {
                return(false);
            }

            CompilationContext compilationContext = new CompilationContext(new UdonSharpCompileOptions());

            ModuleBinding[] bindings = compilationContext.LoadSyntaxTreesAndCreateModules(CompilationContext.GetAllFilteredSourcePaths(false), UdonSharpUtils.GetProjectDefines(false));

            CSharpCompilation compilation = CSharpCompilation.Create(
                $"UdonSharpRoslynUpgradeAssembly{_assemblyCounter++}",
                bindings.Select(e => e.tree),
                CompilationContext.GetMetadataReferences(),
                new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));

            bool scriptUpgraded  = false;
            bool versionUpgraded = false;

            foreach (var programAsset in programAssets)
            {
                string        assetPath = AssetDatabase.GetAssetPath(programAsset.sourceCsScript);
                ModuleBinding binding   = bindings.FirstOrDefault(e => e.filePath == assetPath);

                if (binding == null)
                {
                    continue;
                }

                if (programAsset.ScriptVersion < UdonSharpProgramVersion.V1SerializationUpdate)
                {
                    SyntaxTree    bindingTree  = binding.tree;
                    SemanticModel bindingModel = compilation.GetSemanticModel(bindingTree);

                    SerializationUpdateSyntaxRewriter rewriter = new SerializationUpdateSyntaxRewriter(bindingModel);

                    SyntaxNode newRoot = rewriter.Visit(bindingTree.GetRoot());

                    if (rewriter.Modified)
                    {
                        try
                        {
                            File.WriteAllText(binding.filePath, newRoot.ToFullString(), Encoding.UTF8);
                            scriptUpgraded = true;

                            UdonSharpUtils.Log($"Upgraded field serialization attributes on U# script '{binding.filePath}'", programAsset.sourceCsScript);
                        }
                        catch (Exception e)
                        {
                            UdonSharpUtils.LogError($"Could not upgrade U# script, exception: {e}");
                        }
                    }
                    // We expect this to come through a second time after scripts have been updated and change the version on the asset.
                    else
                    {
                        programAsset.ScriptVersion = UdonSharpProgramVersion.V1SerializationUpdate;
                        EditorUtility.SetDirty(programAsset);
                        versionUpgraded = true;
                    }
                }
            }

            if (scriptUpgraded)
            {
                AssetDatabase.Refresh();
                return(true);
            }

            if (versionUpgraded)
            {
                UdonSharpCompilerV1.CompileSync(new UdonSharpCompileOptions());
            }

            return(false);
        }