204 lines
9.1 KiB
C#
204 lines
9.1 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using FastScriptReload.Runtime;
|
|
using Microsoft.CodeAnalysis;
|
|
using Microsoft.CodeAnalysis.CSharp;
|
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
|
using UnityEditor;
|
|
using UnityEditorInternal;
|
|
using UnityEngine;
|
|
|
|
namespace FastScriptReload.Editor.Compilation.ScriptGenerationOverrides
|
|
{
|
|
[InitializeOnLoad]
|
|
public static class ScriptGenerationOverridesManager
|
|
{
|
|
private static float LoadOverridesFolderFilesEveryNSeconds = 5;
|
|
|
|
private static readonly string TemplateInterfaceDeclaration = @"
|
|
|
|
//New interface declaration, this is very useful in cases where code depends on some internal interfaces that re-compiled code can no longer access. Simply define them here and code will compile.
|
|
//You can add any type in that manner
|
|
public interface ITestNewInterface {
|
|
bool Test { get; set; }
|
|
}";
|
|
|
|
private static readonly string TemplateTopComment = @"// You can use this file to specify custom code overrides. Those will be applied to resulting code.
|
|
// This approach is very useful if your code is failing to compile due to one of the existing limitations.
|
|
//
|
|
// While I work on reducing limitations you can simply specify override with proper code to make sure you can continue working.
|
|
//
|
|
// 1) Simply define code with same structure as your original class, make sure to include any namespace.
|
|
// 2) Rename classes and types to have '<ClassPostfix>' postfix.
|
|
//
|
|
// eg. 'MyClassName' needs to be changed to MyClassName<ClassPostfix> otherwise it won't be properly connected.
|
|
//
|
|
// 3) Add any methods that you want to override, using same method signature. Whole method body will be replaced and no code adjustments will be run on it.
|
|
// 4) You can add any additional types - this is quite useful when you hit limitation with internal interfaces - where compiler can not access them due to protection modifiers.
|
|
// You can simply redefine those here, while not ideal it'll allow you to continue using Hot-Reload without modifying your code.
|
|
//
|
|
// Tool will now attempt to create a template file for you with first found class and first method as override, please adjust as necessary.
|
|
// It'll also create an example redefined interface.
|
|
// If you can't see anything please refer to the above and create overrides file manually.
|
|
//
|
|
// You can also refer to documentation section 'User defined script rewrite overrides'
|
|
";
|
|
|
|
public static DirectoryInfo UserDefinedScriptRewriteOverridesFolder { get; }
|
|
private static double _lastTimeOverridesFolderFilesRead;
|
|
|
|
public static List<UserDefinedScriptOverride> UserDefinedScriptOverrides { get; } = new List<UserDefinedScriptOverride>();
|
|
|
|
static ScriptGenerationOverridesManager()
|
|
{
|
|
//TODO: allow to customize later from code, eg for user that'd like to include in source control
|
|
UserDefinedScriptRewriteOverridesFolder = new DirectoryInfo(Application.persistentDataPath + @"\FastScriptReload\ScriptOverrides");
|
|
UpdateUserDefinedScriptOverridesFileCache();
|
|
EditorApplication.update += Update;
|
|
}
|
|
|
|
private static void Update()
|
|
{
|
|
var timeSinceStartup = EditorApplication.timeSinceStartup;
|
|
if (_lastTimeOverridesFolderFilesRead + LoadOverridesFolderFilesEveryNSeconds < timeSinceStartup)
|
|
{
|
|
_lastTimeOverridesFolderFilesRead = timeSinceStartup;
|
|
|
|
UpdateUserDefinedScriptOverridesFileCache();
|
|
}
|
|
}
|
|
|
|
private static void UpdateUserDefinedScriptOverridesFileCache()
|
|
{
|
|
UserDefinedScriptOverrides.Clear();
|
|
if (UserDefinedScriptRewriteOverridesFolder.Exists)
|
|
{
|
|
UserDefinedScriptOverrides.AddRange(UserDefinedScriptRewriteOverridesFolder.GetFiles().Select(f => new UserDefinedScriptOverride(f)));
|
|
}
|
|
}
|
|
|
|
public static void AddScriptOverride(MonoScript script)
|
|
{
|
|
EnsureOverrideFolderExists();
|
|
|
|
var overridenFile = new FileInfo(Path.Combine(UserDefinedScriptRewriteOverridesFolder.FullName, script.name + ".cs"));
|
|
if (!overridenFile.Exists)
|
|
{
|
|
var originalFile = new FileInfo(Path.Combine(Path.Combine(Application.dataPath + "//..", AssetDatabase.GetAssetPath(script))));
|
|
|
|
var templateString = string.Empty;
|
|
try
|
|
{
|
|
var fileCode = File.ReadAllText(originalFile.FullName);
|
|
var tree = CSharpSyntaxTree.ParseText(fileCode, new CSharpParseOptions(preprocessorSymbols: DynamicCompilationBase.ActiveScriptCompilationDefines));
|
|
var root = tree.GetRoot();
|
|
|
|
var firstType = root.DescendantNodes().OfType<ClassDeclarationSyntax>().FirstOrDefault();
|
|
if (firstType != null)
|
|
{
|
|
var members = new SyntaxList<MemberDeclarationSyntax>();
|
|
var firstMethod = firstType.DescendantNodes().OfType<MethodDeclarationSyntax>().FirstOrDefault(m => m.Body != null);
|
|
if (firstMethod != null)
|
|
{
|
|
var block = SyntaxFactory.Block();
|
|
block = block.AddStatements(SyntaxFactory.EmptyStatement().WithLeadingTrivia(
|
|
SyntaxFactory.Comment(@"/* Any code will be replaced with original method of same signature in same type*/"))
|
|
);
|
|
firstMethod = firstMethod
|
|
.WithBody(block)
|
|
.WithTriviaFrom(firstMethod);
|
|
members = members.Add(firstMethod);
|
|
}
|
|
|
|
root = root.ReplaceNode(firstType, firstType
|
|
.ReplaceToken(
|
|
firstType.Identifier,
|
|
SyntaxFactory.Identifier(firstType.Identifier.ValueText + AssemblyChangesLoader.ClassnamePatchedPostfix)
|
|
)
|
|
.WithMembers(members)).NormalizeWhitespace();
|
|
|
|
var interfaceDeclaration = CSharpSyntaxTree.ParseText(TemplateInterfaceDeclaration);
|
|
|
|
root = ((CompilationUnitSyntax)root).AddMembers(
|
|
interfaceDeclaration.GetRoot().DescendantNodes().OfType<InterfaceDeclarationSyntax>().First()
|
|
);
|
|
}
|
|
|
|
templateString = root.ToFullString();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Debug.LogError($"Unable to generate user defined script override template from your file, please refer to note at the start of the file. {e}");
|
|
}
|
|
|
|
if (!overridenFile.Exists)
|
|
{
|
|
File.WriteAllText(overridenFile.FullName,
|
|
TemplateTopComment.Replace("<ClassPostfix>", AssemblyChangesLoader.ClassnamePatchedPostfix) + templateString
|
|
);
|
|
UpdateUserDefinedScriptOverridesFileCache();
|
|
}
|
|
}
|
|
|
|
InternalEditorUtility.OpenFileAtLineExternal(overridenFile.FullName, 0);
|
|
}
|
|
|
|
public static bool TryRemoveScriptOverride(MonoScript originalScript)
|
|
{
|
|
EnsureOverrideFolderExists();
|
|
|
|
var overridenFile = new FileInfo(Path.Combine(UserDefinedScriptRewriteOverridesFolder.FullName, originalScript.name + ".cs"));
|
|
if (overridenFile.Exists)
|
|
{
|
|
return TryRemoveScriptOverride(overridenFile);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private static bool TryRemoveScriptOverride(FileInfo overridenFile)
|
|
{
|
|
try
|
|
{
|
|
overridenFile.Delete();
|
|
UpdateUserDefinedScriptOverridesFileCache();
|
|
}
|
|
catch (Exception)
|
|
{
|
|
Debug.Log($"Unable to remove: '{overridenFile.Name}' - make sure it's not locked / open in editor");
|
|
throw;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public static bool TryRemoveScriptOverride(UserDefinedScriptOverride scriptOverride)
|
|
{
|
|
return TryRemoveScriptOverride(scriptOverride.File);
|
|
}
|
|
|
|
public static bool TryGetScriptOverride(FileInfo changedFile, out FileInfo overridesFile)
|
|
{
|
|
overridesFile = UserDefinedScriptOverrides.FirstOrDefault(f => f.File.Name == changedFile.Name && f.File.Exists)?.File;
|
|
return overridesFile?.Exists ?? false;
|
|
}
|
|
|
|
private static void EnsureOverrideFolderExists()
|
|
{
|
|
if (!UserDefinedScriptRewriteOverridesFolder.Exists)
|
|
UserDefinedScriptRewriteOverridesFolder.Create();
|
|
}
|
|
}
|
|
|
|
public class UserDefinedScriptOverride
|
|
{
|
|
public FileInfo File { get; }
|
|
|
|
public UserDefinedScriptOverride(FileInfo file)
|
|
{
|
|
File = file;
|
|
}
|
|
}
|
|
} |