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); }
/// <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 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); }