com.phantomsxr.visualscriptingextension
Version:
Visual Scripting allows you to create the logic for your Unity projects without writing actual code. Whereas traditional scripting in Unity is done in the C# programming language, Visual Scripting is a visual “language” that lets you diagram your instruct
387 lines (311 loc) • 14.1 kB
Plain Text
using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Xml.Linq;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.Scripting;
using UnityEditor.Build;
using UnityEditor.Build.Reporting;
using UnityEditor.Callbacks;
using JetBrains.Annotations;
namespace Unity.VisualScripting
{
internal class AotCommentStub : AotStubWriter
{
public AotCommentStub(string wrapper) : base(null)
{
stubMethodComment = wrapper;
stubMethodName = "";
}
public override string stubMethodComment { get; }
public override string stubMethodName { get; }
public override IEnumerable<CodeStatement> GetStubStatements()
{
return Enumerable.Empty<CodeStatement>();
}
}
//Intended to be invoked through build-time reflection
public class AotPreBuilder : IPreprocessBuildWithReport
{
private static AotPreBuilder instance;
public int callbackOrder => 1;
private string linkerPath => Path.Combine(BoltCore.Paths.persistentGenerated, "link.xml");
private string aotStubsPath => Path.Combine(BoltCore.Paths.persistentGenerated, "AotStubs.cs");
[]
public AotPreBuilder()
{
instance = this;
}
//[PostProcessBuild]
// public static void OnPostProcessBuild(BuildTarget target, string pathToBuiltProject)
// {
// if (instance == null || BoltCore.instance == null || !VSUsageUtility.isVisualScriptingUsed)
// return;
//
// instance.DeleteAotStubs();
// }
[]
public static void GenerateFromInternalMenu()
{
if (instance == null)
{
instance = new AotPreBuilder();
}
instance.GenerateAotStubs();
}
public void OnPreprocessBuild(BuildReport report)
{
// // If the user isn't using Visual Scripting, we don't do any of this
// if (!VSUsageUtility.isVisualScriptingUsed)
// return;
// if (!PluginContainer.initialized)
// PluginContainer.Initialize();
// if (instance == null || BoltCore.instance == null)
// {
// UnityEngine.Debug.Log($"Aborting AOT Prebuild, reason: {(instance == null ? "No instance" : BoltCore.instance == null ? "No BoltCore.instance" : "???")}");
// return;
// }
//
// if (PlayerSettings.GetScriptingBackend(report.summary.platformGroup) != ScriptingImplementation.IL2CPP)
// {
// return;
// }
//
// GenerateAotStubs();
}
private void GenerateAotStubs()
{
try
{
GenerateLinker();
GenerateStubScript(aotStubsPath);
}
catch (Exception ex)
{
Debug.LogException(ex);
DeleteAotStubs();
}
}
private void DeleteAotStubs()
{
PathUtility.DeleteProjectFileIfExists(linkerPath, true);
PathUtility.DeleteProjectFileIfExists(aotStubsPath, true);
}
// Automatically generates the link.xml file to prevent stripping.
// Currently only used for plugin assemblies, because blanket preserving
// all setting assemblies sometimes causes the IL2CPP process to fail.
// For settings assemblies, the AOT stubs are good enough to fool
// the static code analysis without needing this full coverage.
// https://docs.unity3d.com/Manual/iphone-playerSizeOptimization.html
// However, for FullSerializer, we need to preserve our custom assemblies.
// This is mostly because IL2CPP will attempt to transform non-public
// property setters used in deserialization into read-only accessors
// that return false on PropertyInfo.CanWrite, but only in stripped builds.
// Therefore, in stripped builds, FS will skip properties that should be
// deserialized without any error (and that took hours of debugging to figure out).
private void GenerateLinker()
{
var linker = new XDocument();
var linkerNode = new XElement("linker");
if (!PluginContainer.initialized)
PluginContainer.Initialize();
foreach (var pluginAssembly in PluginContainer.plugins
.SelectMany(plugin => plugin.GetType()
.GetAttributes<PluginRuntimeAssemblyAttribute>()
.Select(a => a.assemblyName))
.Distinct())
{
var assemblyNode = new XElement("assembly");
var fullnameAttribute = new XAttribute("fullname", pluginAssembly);
var preserveAttribute = new XAttribute("preserve", "all");
assemblyNode.Add(fullnameAttribute);
assemblyNode.Add(preserveAttribute);
linkerNode.Add(assemblyNode);
}
linker.Add(linkerNode);
PathUtility.CreateDirectoryIfNeeded(BoltCore.Paths.transientGenerated);
PathUtility.DeleteProjectFileIfExists(linkerPath, true);
// Using ToString instead of Save to omit the <?xml> declaration,
// which doesn't appear in the Unity documentation page for the linker.
File.WriteAllText(linkerPath, linker.ToString());
}
private IEnumerable<AotStubWriter> FindAllDistinctProjectStubs()
{
return FindAllProjectStubs().Distinct();
}
private IEnumerable<AotStubWriter> FindAllProjectStubs()
{
EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo();
// Settings
EditorUtility.DisplayProgressBar("AOT Pre-Build", "Finding AOT stubs in settings...", 0);
yield return new AotCommentStub("--------------- Setting Stubs");
foreach (var settingStub in FindAllSettingsStubs())
{
yield return AotStubWriterProvider.instance.GetDecorator(settingStub);
}
// Plugins
EditorUtility.DisplayProgressBar("AOT Pre-Build", "Finding AOT stubs in plugins...", 0);
yield return new AotCommentStub("--------------- Plugin Stubs");
foreach (var pluginStub in FindAllPluginStubs())
{
yield return AotStubWriterProvider.instance.GetDecorator(pluginStub);
}
// Assets
EditorUtility.DisplayProgressBar("AOT Pre-Build", "Finding AOT stubs in assets...", 0);
yield return new AotCommentStub("--------------- Asset Stubs");
foreach (var assetStub in FindAllAssetStubs())
{
yield return AotStubWriterProvider.instance.GetDecorator(assetStub);
}
// Scenes
var activeScenePath = SceneManager.GetActiveScene().path;
var scenePaths = EditorBuildSettings.scenes.Select(s => s.path).ToArray();
var sceneIndex = 0;
foreach (var scenePath in scenePaths)
{
EditorUtility.DisplayProgressBar("AOT Pre-Build", $"Finding AOT stubs in '{scenePath}'...", (float)sceneIndex++ / scenePaths.Length);
if (string.IsNullOrEmpty(scenePath))
{
continue;
}
try
{
EditorSceneManager.OpenScene(scenePath, OpenSceneMode.Single);
}
catch (Exception ex)
{
Debug.LogWarning($"Failed to open scene '{scenePath}' during AOT pre-build, skipping.\n{ex}");
}
yield return new AotCommentStub("--------------- Scene Stubs");
foreach (var sceneStub in FindAllSceneStubs())
{
yield return AotStubWriterProvider.instance.GetDecorator(sceneStub);
}
}
if (!string.IsNullOrEmpty(activeScenePath))
{
EditorSceneManager.OpenScene(activeScenePath);
}
EditorUtility.ClearProgressBar();
}
private IEnumerable<object> FindAllSettingsStubs()
{
// Include all custom operators for the formula node and generic math nodes
// Also include all user defined conversion operators for the conversion utility
var codebaseSubset = Codebase.Subset(Codebase.settingsTypes, TypeFilter.Any.Configured(), MemberFilter.Any.Configured());
codebaseSubset.Cache();
return codebaseSubset.members
.Select(m => m.info)
.OfType<MethodInfo>()
.Where(m => m.IsOperator() || m.IsUserDefinedConversion());
}
private IEnumerable<object> FindAllPluginStubs()
{
return PluginContainer.plugins.SelectMany(p => p.aotStubs);
}
private IEnumerable<object> FindAllAssetStubs()
{
var visited = new HashSet<object>();
return LinqUtility.Concat<object>
(
AssetUtility.GetAllAssetsOfType<IAotStubbable>()
.SelectMany(aot => aot.GetAotStubs(visited)),
AssetUtility.GetAllAssetsOfType<GameObject>()
.SelectMany(go => go.GetComponents<IAotStubbable>()
.SelectMany(component => component.GetAotStubs(visited)))
);
}
private IEnumerable<object> FindAllSceneStubs()
{
var visited = new HashSet<object>();
return UnityObjectUtility.FindObjectsOfTypeIncludingInactive<IAotStubbable>()
.SelectMany(aot => aot.GetAotStubs(visited));
}
private void GenerateStubScript(string scriptFilePath)
{
var scriptDirectory = Path.GetDirectoryName(scriptFilePath);
PathUtility.CreateDirectoryIfNeeded(scriptDirectory);
PathUtility.DeleteProjectFileIfExists(scriptFilePath, true);
using (var scriptWriter = new StreamWriter(scriptFilePath))
{
FindAndWriteAotStubs(scriptWriter);
}
AssetDatabase.Refresh();
}
internal void FindAndWriteAotStubs(TextWriter writer)
{
var stubWriters = FindAllDistinctProjectStubs();
GenerateCodeAndWriteToStream(stubWriters, writer);
}
private void GenerateCodeAndWriteToStream(IEnumerable<AotStubWriter> stubWriters, TextWriter writer)
{
var unit = CreateCodeCompileUnitFromStubWriters(stubWriters);
GenerateStubCodeFromCompileUnit(unit, writer);
}
private CodeCompileUnit CreateCodeCompileUnitFromStubWriters(IEnumerable<AotStubWriter> stubWriters)
{
Ensure.That(nameof(stubWriters)).IsNotNull(stubWriters);
var unit = new CodeCompileUnit();
var @namespace = new CodeNamespace("Unity.VisualScripting.Generated.Aot");
unit.Namespaces.Add(@namespace);
var @class = new CodeTypeDeclaration("AotStubs")
{
IsClass = true
};
@class.CustomAttributes.Add(new CodeAttributeDeclaration(new CodeTypeReference(typeof(PreserveAttribute))));
@namespace.Types.Add(@class);
var usedMethodNames = new HashSet<string>();
foreach (var stubWriter in stubWriters)
{
if (stubWriter.skip)
{
continue;
}
var methodName = stubWriter.stubMethodName;
var i = 0;
while (usedMethodNames.Contains(methodName))
{
methodName = stubWriter.stubMethodName + "_" + i++;
}
usedMethodNames.Add(methodName);
@class.Comments.Add(new CodeCommentStatement(stubWriter.stubMethodComment));
if (stubWriter.stub != null)
{
var @method = new CodeMemberMethod
{
Name = methodName,
ReturnType = new CodeTypeReference(typeof(void)),
Attributes = MemberAttributes.Public | MemberAttributes.Static
};
@method.CustomAttributes.Add(new CodeAttributeDeclaration(new CodeTypeReference(typeof(PreserveAttribute), CodeTypeReferenceOptions.GlobalReference)));
@method.Comments.Add(new CodeCommentStatement(stubWriter.stubMethodComment));
@method.Statements.AddRange(stubWriter.GetStubStatements().ToArray());
@class.Members.Add(@method);
}
}
return unit;
}
private void GenerateStubCodeFromCompileUnit(CodeCompileUnit unit, TextWriter writer)
{
using var provider = CodeDomProvider.CreateProvider("CSharp");
var options = new CodeGeneratorOptions
{
BracingStyle = "C",
IndentString = "\t",
BlankLinesBetweenMembers = true,
ElseOnClosing = false,
VerbatimOrder = true
};
provider.GenerateCodeFromCompileUnit(new CodeSnippetCompileUnit("#pragma warning disable 219"), writer,
options); // Disable unused variable warning
provider.GenerateCodeFromCompileUnit(unit, writer, options);
}
}
}