Skip to content

Commit b6eb074

Browse files
committed
v1.1.0
1 parent 8596f24 commit b6eb074

10 files changed

+193
-56
lines changed

Editor/Attributes.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@ namespace SatorImaging.UnitySourceGenerator
66
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
77
public sealed class UnitySourceGeneratorAttribute : Attribute
88
{
9-
public UnitySourceGeneratorAttribute()
9+
public UnitySourceGeneratorAttribute(Type generatorClass = null)
1010
{
11+
GeneratorClass = generatorClass;
1112
}
1213

1314
public bool OverwriteIfFileExists { get; set; } = false;
15+
public Type GeneratorClass { get; set; }
1416

1517
}
1618
}

Editor/Interfaces.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// NOTE: "abstract static" definition in interface requires C# 11.0 or later.
2+
// following code is just for reference for future enhancement.
3+
#if UNITY_2025_1_OR_NEWER
4+
5+
using System.Text;
6+
7+
namespace SatorImaging.UnitySourceGenerator
8+
{
9+
public interface IUnitySourceGenerator
10+
{
11+
///<summary>Return true if write StringBuilder content to file.</summary>
12+
abstract static bool Emit(in USGContext context, in StringBuilder sb);
13+
14+
///<summary>Return just only filename with extension. SourceGenerator will automatically arrange output path.</summary>
15+
abstract static string OutputFileName();
16+
17+
}
18+
}
19+
20+
#endif

Editor/Interfaces.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Editor/USGContext.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
using System.Collections;
2-
using System.Collections.Generic;
1+
using System;
32
using UnityEngine;
43

54

65
namespace SatorImaging.UnitySourceGenerator
76
{
87
public class USGContext
98
{
9+
public Type TargetClass;
1010
public string AssetPath;
1111
public string OutputPath;
1212

Editor/USGEngine.cs

Lines changed: 91 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ namespace SatorImaging.UnitySourceGenerator
1515
{
1616
public class USGEngine : AssetPostprocessor
1717
{
18-
///<summary>This will be disabled after import event automatically.</summary>
18+
///<summary>This will be disabled after Unity Editor import event automatically.</summary>
1919
public static bool IgnoreOverwriteSettingByAttribute = false;
2020

2121

@@ -71,22 +71,42 @@ static void OnPostprocessAllAssets(
7171
if (s_processingJobQueued) return;
7272
s_processingJobQueued = true;
7373

74-
AssetDatabase.Refresh(); // load updated generator class script before event
75-
EditorApplication.delayCall += ProcessingFiles;
74+
// NOTE: need to stack jobs. nesting delayCall's causes error.
75+
EditorApplication.delayCall += static () =>
76+
{
77+
ProcessingFiles();
78+
s_updatedGeneratorJob?.Invoke();
79+
s_updatedGeneratorJob = null;
80+
81+
IgnoreOverwriteSettingByAttribute = false; // always turn it off.
82+
s_processingJobQueued = false;
83+
};
7684
}
7785

86+
static Action s_updatedGeneratorJob = null;
87+
readonly static HashSet<string> s_updatedGeneratorNames = new();
7888
static void ProcessingFiles()
7989
{
8090
foreach (string path in s_targetFilePaths)
8191
{
8292
ProcessFile(path);
8393
}
8494

95+
// TODO: more efficient way to process related targets
96+
foreach (var generatorName in s_updatedGeneratorNames)
97+
{
98+
foreach (var info in s_typeNameToInfo.Values)
99+
{
100+
if (info.TargetClass == null) continue;
101+
if (info.Attribute.GeneratorClass?.Name != generatorName) continue;
102+
103+
s_updatedGeneratorJob += () => USGUtility.ForceGenerate(info.TargetClass.Name, false);
104+
}
105+
}
106+
s_updatedGeneratorNames.Clear();
107+
85108
if (s_targetFilePaths.Count() > 0) AssetDatabase.Refresh();
86109
s_targetFilePaths.Clear();
87-
88-
IgnoreOverwriteSettingByAttribute = false; // always turn it off.
89-
s_processingJobQueued = false;
90110
}
91111

92112

@@ -119,6 +139,22 @@ public static void ProcessFile(string assetsRelPath)
119139
var info = s_typeNameToInfo[clsName];
120140
if (info == null) return;
121141

142+
// TODO: more streamlined.
143+
if (info.TargetClass == null)
144+
{
145+
s_updatedGeneratorNames.Add(clsName);
146+
return;
147+
}
148+
149+
150+
if (!TryBuildOutputFileName(info))
151+
{
152+
Debug.LogError($"[{nameof(UnitySourceGenerator)}] Output file name is invalid: {info.OutputFileName}");
153+
return;
154+
}
155+
156+
var generatorCls = info.Attribute.GeneratorClass ?? info.TargetClass;
157+
122158

123159
// build path
124160
string outputPath = Path.Combine(s_projectDirPath, Path.GetDirectoryName(assetsRelPath)).Replace('\\', '/');
@@ -129,14 +165,15 @@ public static void ProcessFile(string assetsRelPath)
129165

130166
var context = new USGContext
131167
{
168+
TargetClass = info.TargetClass,
132169
AssetPath = assetsRelPath.Replace('\\', '/'),
133170
OutputPath = outputPath.Replace('\\', '/'),
134171
};
135172

136173

137174
// do it.
138175
var sb = new StringBuilder();
139-
sb.AppendLine($"// <auto-generated>{info.Type.Name}</auto-generated>");
176+
sb.AppendLine($"// <auto-generated>{generatorCls.Name}</auto-generated>");
140177

141178
var isSaveFile = false;
142179
try
@@ -145,7 +182,7 @@ public static void ProcessFile(string assetsRelPath)
145182
}
146183
catch
147184
{
148-
Debug.LogError($"[{nameof(UnitySourceGenerator)}] Unhandled Error on Emit(): {info.Type}");
185+
Debug.LogError($"[{nameof(UnitySourceGenerator)}] Unhandled Error on Emit(): {generatorCls}");
149186
throw;
150187
}
151188

@@ -159,7 +196,7 @@ public static void ProcessFile(string assetsRelPath)
159196

160197

161198
if (File.Exists(context.OutputPath) &&
162-
(!info.OverwriteIfFileExists && !IgnoreOverwriteSettingByAttribute)
199+
(!info.Attribute.OverwriteIfFileExists && !IgnoreOverwriteSettingByAttribute)
163200
)
164201
{
165202
return;
@@ -183,14 +220,14 @@ public static void ProcessFile(string assetsRelPath)
183220

184221
class CachedTypeInfo
185222
{
186-
public Type Type;
187-
188-
//from attribute
189-
public bool OverwriteIfFileExists;
223+
// TODO: more streamlined.
224+
///<summary>null if generator that only referenced from other classes.</summary>
225+
public Type TargetClass;
226+
public UnitySourceGeneratorAttribute Attribute;
190227

191-
//from method
192-
public MethodInfo EmitMethod;
193228
public string OutputFileName;
229+
public MethodInfo EmitMethod;
230+
public MethodInfo OutputFileNameMethod;
194231
}
195232

196233

@@ -246,8 +283,8 @@ static void CollectTargets()
246283
var attr = t.GetCustomAttribute<UnitySourceGeneratorAttribute>(false);
247284
return new CachedTypeInfo
248285
{
249-
Type = t,
250-
OverwriteIfFileExists = attr.OverwriteIfFileExists,
286+
TargetClass = t,
287+
Attribute = attr,
251288
};
252289
})
253290
;
@@ -258,48 +295,67 @@ static void CollectTargets()
258295
{
259296
//Debug.Log($"[{nameof(UnitySourceGenerator)}] Processing...: {info.ClassName}");
260297

261-
var outputMethod = info.Type.GetMethod("OutputFileName", METHOD_FLAGS, null, Type.EmptyTypes, null);
262-
var emitMethod = info.Type.GetMethod("Emit", METHOD_FLAGS, null, new Type[] { typeof(USGContext), typeof(StringBuilder) }, null);
298+
var generatorCls = info.Attribute.GeneratorClass ?? info.TargetClass;
299+
var outputMethod = generatorCls.GetMethod("OutputFileName", METHOD_FLAGS, null, Type.EmptyTypes, null);
300+
var emitMethod = generatorCls.GetMethod("Emit", METHOD_FLAGS, null, new Type[] { typeof(USGContext), typeof(StringBuilder) }, null);
263301

264302
if (outputMethod == null || emitMethod == null)
265303
{
266-
Debug.LogError($"[{nameof(UnitySourceGenerator)}] Required static method(s) not found: {info.Type}");
304+
Debug.LogError($"[{nameof(UnitySourceGenerator)}] Required static method(s) not found: {generatorCls}");
267305
continue;
268306
}
269307

270-
271308
info.EmitMethod = emitMethod;
309+
info.OutputFileNameMethod = outputMethod;
272310

273311
//filename??
274-
info.OutputFileName = (string)outputMethod.Invoke(null, null);
275-
if (string.IsNullOrWhiteSpace(info.OutputFileName))
312+
if (!TryBuildOutputFileName(info))
276313
{
277314
Debug.LogError($"[{nameof(UnitySourceGenerator)}] Output file name is invalid: {info.OutputFileName}");
278315
continue;
279316
}
280317

281318

282-
//build filename
283-
string fileName = Path.GetFileNameWithoutExtension(info.OutputFileName);
284-
string fileExt = Path.GetExtension(info.OutputFileName);
285-
string outputFileName = fileName + GENERATOR_PREFIX + info.Type.Name + GENERATOR_EXT + fileExt;
319+
s_typeNameToInfo.TryAdd(info.TargetClass.Name, info);
320+
if (generatorCls != info.TargetClass)
321+
{
322+
// TODO: more streamlined.
323+
//Debug.Log($"[USG] Generator found: {generatorCls.Name}");
286324

287-
info.OutputFileName = outputFileName;
325+
var genInfo = new CachedTypeInfo
326+
{
327+
TargetClass = null,
328+
OutputFileName = null,
329+
EmitMethod = null,
330+
OutputFileNameMethod = null,
331+
Attribute = info.Attribute,
332+
};
288333

289-
//once again
290-
if (string.IsNullOrWhiteSpace(info.OutputFileName))
291-
{
292-
Debug.LogError($"[{nameof(UnitySourceGenerator)}] Output file name is invalid: {info.OutputFileName}");
293-
continue;
334+
s_typeNameToInfo.TryAdd(generatorCls.Name, genInfo);
294335
}
295336

296-
s_typeNameToInfo.TryAdd(info.Type.Name, info);
297-
298337

299338
}//foreach
300339
}
301340

302341

342+
static bool TryBuildOutputFileName(CachedTypeInfo info)
343+
{
344+
info.OutputFileName = (string)info.OutputFileNameMethod?.Invoke(null, null);
345+
if (string.IsNullOrWhiteSpace(info.OutputFileName))
346+
return false;
347+
348+
string fileName = Path.GetFileNameWithoutExtension(info.OutputFileName);
349+
string fileExt = Path.GetExtension(info.OutputFileName);
350+
info.OutputFileName = fileName + GENERATOR_PREFIX + info.TargetClass.Name;
351+
if (info.Attribute.GeneratorClass != null)
352+
info.OutputFileName += GENERATOR_PREFIX + info.Attribute.GeneratorClass.Name;
353+
info.OutputFileName += GENERATOR_EXT + fileExt;
354+
355+
return true;
356+
}
357+
358+
303359
}
304360
}
305361
#endif

Sample/MethodGeneratorSample.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using SatorImaging.UnitySourceGenerator;
2+
3+
namespace Sample
4+
{
5+
// NOTE: Copy this file to Assets/ folder to enable source generator.
6+
// USG process files in Assets/ folder only.
7+
[UnitySourceGenerator(typeof(Sample.PanicMethodGenerator), OverwriteIfFileExists = false)]
8+
internal partial class MethodGeneratorSample
9+
{
10+
}
11+
12+
}

Sample/MethodGeneratorSample.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Sample/MinimalGenerator.cs

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -23,24 +23,6 @@ static bool Emit(USGContext context, StringBuilder sb)
2323
// return true to tell USG to write content into OutputPath. false to do nothing.
2424
return true;
2525
}
26-
}
27-
28-
29-
// NOTE: "abstract static" definition in interface requires C# 11.0 or later.
30-
// following code is just for reference for future enhancement.
31-
#if UNITY_2025_1_OR_NEWER
32-
33-
public interface IUnitySourceGenerator
34-
{
35-
///<summary>Return true if write StringBuilder content to file.</summary>
36-
abstract static bool Emit(in USGContext context, in StringBuilder sb);
37-
38-
///<summary>Return just only filename with extension. SourceGenerator will automatically arrange output path.</summary>
39-
abstract static string OutputFileName();
4026

4127
}
42-
43-
#endif
44-
45-
4628
}

Sample/PanicMethodGenerator.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using System.Text;
2+
using SatorImaging.UnitySourceGenerator;
3+
4+
namespace Sample
5+
{
6+
// HOW TO USE: Add the following attribute to *target* class.
7+
// Note that target class must be defined as partial.
8+
// [UnitySourceGenerator(typeof(Sample.PanicMethodGenerator)]
9+
public class PanicMethodGenerator
10+
{
11+
static string OutputFileName() => "PanicMethod.cs"; // -> PanicMethod.<TargetClass>.<GeneratorClass>.g.cs
12+
13+
static bool Emit(USGContext context, StringBuilder sb)
14+
{
15+
if (!context.TargetClass.IsClass || context.TargetClass.IsAbstract)
16+
return false; // return false to tell USG doesn't write file.
17+
18+
// code generation
19+
sb.Append($@"
20+
namespace {context.TargetClass.Namespace}
21+
{{
22+
internal partial class {context.TargetClass.Name}
23+
{{
24+
public void Panic() => throw new System.Exception();
25+
}}
26+
}}
27+
");
28+
return true;
29+
}
30+
31+
}
32+
}

Sample/PanicMethodGenerator.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)