ScriptCodeManager.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. //********************************** Banshee Engine (www.banshee3d.com) **************************************************//
  2. //**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
  3. using System;
  4. using System.IO;
  5. using System.Text;
  6. using System.Text.RegularExpressions;
  7. using bs;
  8. namespace bs.Editor
  9. {
  10. /** @addtogroup Script
  11. * @{
  12. */
  13. /// <summary>
  14. /// Handles various operations related to script code in the active project, like compilation and code editor syncing.
  15. /// </summary>
  16. public sealed class ScriptCodeManager
  17. {
  18. private bool isGameAssemblyDirty;
  19. private bool isEditorAssemblyDirty;
  20. private CompilerInstance compilerInstance;
  21. /// <summary>
  22. /// Constructs a new script code manager.
  23. /// </summary>
  24. internal ScriptCodeManager()
  25. {
  26. ProjectLibrary.OnEntryAdded += OnEntryAdded;
  27. ProjectLibrary.OnEntryRemoved += OnEntryRemoved;
  28. ProjectLibrary.OnEntryImported += OnEntryImported;
  29. // Check for missing or out of date assemblies
  30. DateTime lastModifiedGameScript = DateTime.MinValue;
  31. DateTime lastModifiedEditorScript = DateTime.MinValue;
  32. LibraryEntry[] scriptEntries = ProjectLibrary.Search("*.cs", new ResourceType[] { ResourceType.ScriptCode });
  33. for (int i = 0; i < scriptEntries.Length; i++)
  34. {
  35. if(scriptEntries[i].Type != LibraryEntryType.File)
  36. continue;
  37. FileEntry fileEntry = (FileEntry)scriptEntries[i];
  38. string absPath = Path.Combine(ProjectLibrary.ResourceFolder, fileEntry.Path);
  39. ScriptCodeImportOptions io = (ScriptCodeImportOptions) fileEntry.Options;
  40. if (io.EditorScript)
  41. lastModifiedEditorScript = File.GetLastWriteTime(absPath);
  42. else
  43. lastModifiedGameScript = File.GetLastWriteTime(absPath);
  44. }
  45. DateTime lastCompileTime = new DateTime(EditorApplication.PersistentData.lastCompileTime);
  46. if (lastModifiedGameScript != DateTime.MinValue)
  47. {
  48. string gameAssemblyPath = Path.Combine(EditorApplication.ScriptAssemblyPath,
  49. EditorApplication.ScriptGameAssemblyName);
  50. isGameAssemblyDirty = (!File.Exists(gameAssemblyPath) ||
  51. File.GetLastWriteTime(gameAssemblyPath) < lastModifiedGameScript) &&
  52. (lastModifiedGameScript > lastCompileTime);
  53. }
  54. if (lastModifiedEditorScript != DateTime.MinValue)
  55. {
  56. string editorAssemblyPath = Path.Combine(EditorApplication.ScriptAssemblyPath,
  57. EditorApplication.ScriptEditorAssemblyName);
  58. isEditorAssemblyDirty = (!File.Exists(editorAssemblyPath) ||
  59. File.GetLastWriteTime(editorAssemblyPath) < lastModifiedEditorScript) &&
  60. (lastModifiedEditorScript > lastCompileTime);
  61. }
  62. }
  63. /// <summary>
  64. /// Triggers required compilation or code editor syncing if needed.
  65. /// </summary>
  66. internal void Update()
  67. {
  68. if (EditorApplication.HasFocus && CodeEditor.IsSolutionDirty)
  69. CodeEditor.SyncSolution();
  70. if (EditorApplication.IsStopped && !ProjectLibrary.ImportInProgress)
  71. {
  72. if (compilerInstance == null)
  73. {
  74. if (EditorApplication.HasFocus)
  75. {
  76. string outputDir = EditorApplication.ScriptAssemblyPath;
  77. if (isGameAssemblyDirty)
  78. {
  79. compilerInstance = ScriptCompiler.CompileAsync(
  80. ScriptAssemblyType.Game, BuildManager.ActivePlatform, true, outputDir);
  81. EditorApplication.SetStatusCompiling(true);
  82. EditorApplication.PersistentData.lastCompileTime = DateTime.Now.Ticks;
  83. isGameAssemblyDirty = false;
  84. }
  85. else if (isEditorAssemblyDirty)
  86. {
  87. compilerInstance = ScriptCompiler.CompileAsync(
  88. ScriptAssemblyType.Editor, BuildManager.ActivePlatform, true, outputDir);
  89. EditorApplication.SetStatusCompiling(true);
  90. EditorApplication.PersistentData.lastCompileTime = DateTime.Now.Ticks;
  91. isEditorAssemblyDirty = false;
  92. }
  93. }
  94. }
  95. else
  96. {
  97. if (compilerInstance.IsDone)
  98. {
  99. Debug.Clear(DebugMessageType.CompilerWarning);
  100. Debug.Clear(DebugMessageType.CompilerError);
  101. LogWindow window = EditorWindow.GetWindow<LogWindow>();
  102. if (window != null)
  103. window.Refresh();
  104. if (compilerInstance.HasErrors)
  105. {
  106. foreach (var msg in compilerInstance.WarningMessages)
  107. Debug.LogMessage(FormMessage(msg), DebugMessageType.CompilerWarning);
  108. foreach (var msg in compilerInstance.ErrorMessages)
  109. Debug.LogMessage(FormMessage(msg), DebugMessageType.CompilerError);
  110. }
  111. compilerInstance.Dispose();
  112. compilerInstance = null;
  113. EditorApplication.SetStatusCompiling(false);
  114. EditorApplication.ReloadAssemblies();
  115. }
  116. }
  117. }
  118. }
  119. /// <summary>
  120. /// Triggered when a new resource is added to the project library.
  121. /// </summary>
  122. /// <param name="path">Path of the added resource, relative to the project's resource folder.</param>
  123. private void OnEntryAdded(string path)
  124. {
  125. if (IsCodeEditorFile(path))
  126. CodeEditor.MarkSolutionDirty();
  127. }
  128. /// <summary>
  129. /// Triggered when a resource is removed from the project library.
  130. /// </summary>
  131. /// <param name="path">Path of the removed resource, relative to the project's resource folder.</param>
  132. private void OnEntryRemoved(string path)
  133. {
  134. if (IsCodeEditorFile(path))
  135. CodeEditor.MarkSolutionDirty();
  136. }
  137. /// <summary>
  138. /// Triggered when a resource is (re)imported in the project library.
  139. /// </summary>
  140. /// <param name="path">Path of the imported resource, relative to the project's resource folder.</param>
  141. private void OnEntryImported(string path)
  142. {
  143. LibraryEntry entry = ProjectLibrary.GetEntry(path);
  144. if (entry == null || entry.Type != LibraryEntryType.File)
  145. return;
  146. FileEntry fileEntry = (FileEntry)entry;
  147. ResourceMeta[] resourceMetas = fileEntry.ResourceMetas;
  148. bool found = false;
  149. foreach (var meta in resourceMetas)
  150. {
  151. if (meta.ResType == ResourceType.ScriptCode)
  152. {
  153. found = true;
  154. break;
  155. }
  156. }
  157. if (!found)
  158. return;
  159. ScriptCode codeFile = ProjectLibrary.Load<ScriptCode>(path);
  160. if(codeFile == null)
  161. return;
  162. if(codeFile.EditorScript)
  163. isEditorAssemblyDirty = true;
  164. else
  165. isGameAssemblyDirty = true;
  166. }
  167. /// <summary>
  168. /// Checks is the resource at the provided path a file relevant to the code editor.
  169. /// </summary>
  170. /// <param name="path">Path to the resource, absolute or relative to the project's resources folder.</param>
  171. /// <returns>True if the file is relevant to the code editor, false otherwise.</returns>
  172. private bool IsCodeEditorFile(string path)
  173. {
  174. LibraryEntry entry = ProjectLibrary.GetEntry(path);
  175. if (entry != null && entry.Type == LibraryEntryType.File)
  176. {
  177. FileEntry fileEntry = (FileEntry)entry;
  178. ResourceMeta[] resourceMetas = fileEntry.ResourceMetas;
  179. foreach (var codeType in CodeEditor.CodeTypes)
  180. {
  181. foreach (var meta in resourceMetas)
  182. {
  183. if (meta.ResType == codeType)
  184. return true;
  185. }
  186. }
  187. }
  188. return false;
  189. }
  190. /// <summary>
  191. /// Converts data reported by the compiler into a readable string.
  192. /// </summary>
  193. /// <param name="msg">Message data as reported by the compiler.</param>
  194. /// <returns>Readable message string.</returns>
  195. private string FormMessage(CompilerMessage msg)
  196. {
  197. StringBuilder sb = new StringBuilder();
  198. if (msg.type == CompilerMessageType.Error)
  199. sb.AppendLine("Compiler error: " + msg.message);
  200. else
  201. sb.AppendLine("Compiler warning: " + msg.message);
  202. sb.AppendLine("\tin " + msg.file + "[" + msg.line + ":" + msg.column + "]");
  203. return sb.ToString();
  204. }
  205. /// <summary>
  206. /// Parses a log message and outputs a data object with a separate message and callstack entries. If the message
  207. /// is not a valid compiler message null is returned.
  208. /// </summary>
  209. /// <param name="message">Message to parse.</param>
  210. /// <returns>Parsed log message or null if not a valid compiler message.</returns>
  211. public static ParsedLogEntry ParseCompilerMessage(string message)
  212. {
  213. // Note: If modifying FormMessage method make sure to update this one as well to match the formattting
  214. // Check for error
  215. Regex regex = new Regex(@"Compiler error: (.*)\n\tin (.*)\[(.*):.*\]");
  216. var match = regex.Match(message);
  217. // Check for warning
  218. if (!match.Success)
  219. {
  220. regex = new Regex(@"Compiler warning: (.*)\n\tin (.*)\[(.*):.*\]");
  221. match = regex.Match(message);
  222. }
  223. // No match
  224. if (!match.Success)
  225. return null;
  226. ParsedLogEntry entry = new ParsedLogEntry();
  227. entry.callstack = new CallStackEntry[1];
  228. entry.message = match.Groups[1].Value;
  229. CallStackEntry callstackEntry = new CallStackEntry();
  230. callstackEntry.method = "";
  231. callstackEntry.file = match.Groups[2].Value;
  232. int.TryParse(match.Groups[3].Value, out callstackEntry.line);
  233. entry.callstack[0] = callstackEntry;
  234. return entry;
  235. }
  236. }
  237. /** @} */
  238. }