Skip to content

Commit ba22d3e

Browse files
committed
Improve script change detection (Fix #85)
1 parent 28537a3 commit ba22d3e

File tree

1 file changed

+135
-9
lines changed

1 file changed

+135
-9
lines changed

Assets.Scripts.Core.AssetManagement/AssetManager.cs

+135-9
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22
using Assets.Scripts.Core.Buriko;
33
using BGICompiler.Compiler;
44
using MOD.Scripts.Core.Audio;
5+
using Newtonsoft.Json;
56
using System;
67
using System.Collections.Generic;
78
using System.ComponentModel;
89
using System.IO;
910
using System.Linq;
11+
using System.Security.Cryptography;
1012
using UnityEngine;
1113

1214
namespace Assets.Scripts.Core.AssetManagement
@@ -225,12 +227,127 @@ public string PathToAssetWithName(string name, PathCascadeList artset)
225227
return null;
226228
}
227229

230+
class ScriptInfo
231+
{
232+
public string name;
233+
public DateTime lastWriteTime;
234+
public long length;
235+
public string md5String;
236+
237+
public static ScriptInfo TryGetOrNull(string path)
238+
{
239+
try
240+
{
241+
string md5String;
242+
using (var md5 = MD5.Create())
243+
{
244+
using (var stream = File.OpenRead(path))
245+
{
246+
md5String = BitConverter.ToString(md5.ComputeHash(stream)).Replace("-", "");
247+
}
248+
}
249+
250+
FileInfo fileInfo = new FileInfo(path);
251+
252+
return new ScriptInfo
253+
{
254+
name = Path.GetFileNameWithoutExtension(fileInfo.Name),
255+
lastWriteTime = fileInfo.LastWriteTime,
256+
length = fileInfo.Length,
257+
md5String = md5String,
258+
};
259+
}
260+
catch (Exception e)
261+
{
262+
Debug.LogError($"Failed to get script info for {path}: {e}");
263+
}
264+
265+
return null;
266+
}
267+
}
268+
269+
private bool ScriptNeedsCompile(Dictionary<string, ScriptInfo> oldInfoDictionary, ScriptInfo txt, ScriptInfo mg, string textDescription, string mgDescription)
270+
{
271+
// If the mg file doensn't exist or can't be accessed, do re-compile
272+
if(mg == null)
273+
{
274+
Debug.Log($"ScriptNeedsCompile(): Compiling {textDescription} as mg file {mgDescription} does not exist or can't be accessed");
275+
return true;
276+
}
277+
278+
// This implies the txt file doesn't exist, which should never happen but just try to recompile anyway in this case
279+
if(txt == null)
280+
{
281+
Debug.Log($"ScriptNeedsCompile(): WARNING: {textDescription} can't be accessed or does not exist - trying to compile anyway");
282+
return true;
283+
}
284+
285+
//// Compile if the .txt is newer than the .mg file
286+
//if (txt.lastWriteTime > mg.lastWriteTime)
287+
//{
288+
// Debug.Log($"ScriptNeedsCompile(): Compiling {textDescription} as it is newer than mg file {mgDescription} (txt: {txt.lastWriteTime}), mg: {mg.lastWriteTime}");
289+
// return true;
290+
//}
291+
292+
// Compare the current .txt file against the last recorded information about the .txt file
293+
if(oldInfoDictionary.TryGetValue(txt.name, out ScriptInfo oldInfo))
294+
{
295+
// Compile if the file size has changed since last time
296+
if(oldInfo.length != txt.length)
297+
{
298+
Debug.Log($"ScriptNeedsCompile(): Compiling {textDescription} as size differs from previous (old: {oldInfo.length} bytes, new: {txt.length} bytes)");
299+
return true;
300+
}
301+
302+
// Compile if the file's MD5 has changed
303+
if (oldInfo.md5String != txt.md5String)
304+
{
305+
Debug.Log($"ScriptNeedsCompile(): Compiling {textDescription} as MD5 differs from previous (old: {oldInfo.md5String}, new: {txt.md5String})");
306+
return true;
307+
}
308+
}
309+
else
310+
{
311+
// If the file hasn't been recorded in the dictionary, re-compile it
312+
Debug.Log($"ScriptNeedsCompile(): Compiling {textDescription} as it doesn't exist in the .txt info dictionary.");
313+
return true;
314+
}
315+
316+
return false;
317+
}
318+
228319
public void CompileFolder(string srcDir, string destDir)
229320
{
321+
JsonSerializer jsonSerializer = new JsonSerializer();
322+
string txtInfoDictionaryPath = Path.Combine(destDir, "txtInfoDictionary.json");
230323
string[] txtList1 = Directory.GetFiles(srcDir, "*.txt");
231324
string[] mgList1 = Directory.GetFiles(destDir, "*.mg");
232325
List<string> txtFilenameNoExtensionList = new List<string>();
233326

327+
Dictionary<string, ScriptInfo> oldTxtInfoDictionary = new Dictionary<string, ScriptInfo>();
328+
Dictionary<string, ScriptInfo> newTxtInfoDictionary = new Dictionary<string, ScriptInfo>();
329+
330+
try
331+
{
332+
if(File.Exists(txtInfoDictionaryPath))
333+
{
334+
using (StreamReader sw = new StreamReader(txtInfoDictionaryPath))
335+
using (JsonReader reader = new JsonTextReader(sw))
336+
{
337+
List<ScriptInfo> scriptInfoList = jsonSerializer.Deserialize<List<ScriptInfo>>(reader);
338+
foreach(ScriptInfo info in scriptInfoList)
339+
{
340+
oldTxtInfoDictionary[info.name] = info;
341+
}
342+
}
343+
344+
}
345+
}
346+
catch(Exception e)
347+
{
348+
Debug.LogError($"CompileFolder(): Failed to deserialize {txtInfoDictionaryPath}: {e}");
349+
}
350+
234351
string[] txtList = txtList1;
235352
foreach (string txtPath1 in txtList)
236353
{
@@ -242,16 +359,13 @@ public void CompileFolder(string srcDir, string destDir)
242359
string txtPath = txtPath1;
243360
string mgPath = Path.Combine(destDir, fileNameWithoutExtension) + ".mg";
244361

245-
// (Re-)compile scripts if:
246-
// - the corresponding .mg file doesn't exist
247-
// - the corresponding .mg file is newer than the source .txt file
248-
if (File.Exists(mgPath))
362+
ScriptInfo txtInfo = ScriptInfo.TryGetOrNull(txtPath);
363+
ScriptInfo mgInfo = File.Exists(mgPath) ? ScriptInfo.TryGetOrNull(mgPath) : null;
364+
365+
if (!ScriptNeedsCompile(oldTxtInfoDictionary, txtInfo, mgInfo, Path.GetFileName(txtPath), Path.GetFileName(mgPath)))
249366
{
250-
if (File.GetLastWriteTime(txtPath) <= File.GetLastWriteTime(mgPath))
251-
{
252-
continue;
253-
}
254-
Debug.Log($"Script {mgPath} last compiled {File.GetLastWriteTime(mgPath)} (source {txtPath} updated on {File.GetLastWriteTime(txtPath)})");
367+
newTxtInfoDictionary[txtInfo.name] = txtInfo;
368+
continue;
255369
}
256370

257371
// Try to compile the file, but if an exception occurs just ignore it and move on to the next script
@@ -260,6 +374,7 @@ public void CompileFolder(string srcDir, string destDir)
260374
{
261375
new BGItoMG(txtPath, mgPath);
262376
numCompileOK++;
377+
newTxtInfoDictionary[txtInfo.name] = txtInfo;
263378
}
264379
catch (Exception arg)
265380
{
@@ -269,6 +384,17 @@ public void CompileFolder(string srcDir, string destDir)
269384
}
270385
}
271386

387+
if(numCompileOK > 0 && numCompileFail == 0)
388+
{
389+
// save scriptInfoDictionary to file as at least one .txt file has changed and succesfully been compiled
390+
// we don't want to update the dictionary if any script file failed to compile as from then on it would never be updated
391+
using (StreamWriter sw = new StreamWriter(txtInfoDictionaryPath))
392+
using (JsonTextWriter writer = new JsonTextWriter(sw))
393+
{
394+
jsonSerializer.Serialize(writer, newTxtInfoDictionary.Values.ToList());
395+
}
396+
}
397+
272398
// Delete .mg files with no corresponding .txt file
273399
string[] mgList = mgList1;
274400
foreach (string mgPath in mgList)

0 commit comments

Comments
 (0)