[]>>
283 | ```
284 |
285 |
286 |
287 | Samples
288 | =======
289 |
290 | `SceneBuildIndexGenerator`
291 | --------------------------
292 |
293 | This sample allows you to handle scene index more efficiently.
294 |
295 | To use this sample, add `[UnitySourceGenerator(typeof(SceneBuildIndexGenerator))]` attribute to your class. after that, you can use the following enum and helper methods.
296 |
297 |
298 | - enum `SceneBuildIndex`
299 |
300 | - enum consists of scene file names which registered in build settings.
301 | - easy to use with Unity inspector. note that class field doesn't track build index changes.
302 |
303 | - static class `SceneBuildIndexResolver`
304 |
305 | - `GetByName(string sceneFileNameWithoutExtension)`
306 | - get build index by scene name or throws if scene is not found.
307 | - this method ensures the scene must be included in build and also track build index changes.
308 | ```csharp
309 | // use like this in entry point to validate scene existence
310 | SceneBuildIndex ImportantSceneIndex = SceneBuildIndexResolver.GetByName("Scene_Must_Be_Included_in_Build");
311 | ```
312 |
313 | - `GetListByPrefix(string fileNamePrefix)`
314 | - Get list of index which starts with prefix.
315 |
316 | - `GetListByPath(string assetsPath)`
317 | - Path must be started with **"Assets/"**.
318 |
319 |
320 |
321 | Utility Functions for Build Event
322 | =================================
323 |
324 | There are utility functions to perform source code generation on build event.
325 |
326 | 日本語 / JA
327 |
328 | `IPostprocessBuildWithReport` も実装しようかと思ったものの、ビルドイベントに勝手に処理追加するのはなんか訳わからんが動かんの原因だし、BuildReport として渡される全てのファイル名を走査する処理は効率も良くない。ということで。
329 |
330 |
331 |
332 |
333 | ```csharp
334 | // generate code for specified generator type
335 | USGUtility.ForceGenerateByType(typeof(MinimalGenerator));
336 |
337 | // or for the emitter type
338 | USGUtility.ForceGenerateByType(typeof(ClassHasUSGAttribute));
339 | ```
340 |
341 |
342 |
343 | Technical Notes
344 | ===============
345 |
346 | As of C# 9.0, it doesn't allow to define `abstract static` methods in interface, USG reports error when source generator class doesn't implement required static methods.
347 |
348 | 日本語 / JA
349 |
350 | 理想はアトリビュートとインターフェイスによるフィルタリングですが、Unity 2021 は C# 9.0 で `abstract static` を含んだインターフェイスが使えません。
351 |
352 | しょうがないのでメソッドのシグネチャを確認して存在しなければエラーをコンソールに出します。
353 |
354 |
355 |
356 |
357 | 
358 |
359 |
360 |
361 | ## Naming Convention
362 |
363 | - Generator/target class name and filename must be matched.
364 | - ~~Class name must be unique in whole project.~~
365 | - ~~Classes are ignored if defined in assembly which name starts with:~~
366 | - ~~`Unity` (no trailing dot)~~
367 | - ~~`System.`~~
368 | - ~~`Mono.`~~
369 |
370 |
371 | 日本語 / JA
372 |
373 | - ジェネレーター・対象クラスの名前はファイル名と一致
374 | - ~~ジェネレータクラスの名前はプロジェクト内で一意~~
375 | - ~~クラスが以下で始まる名前のアセンブリで宣言されている場合は対象としない~~
376 | - ~~`Unity` (末尾ドット無し)~~
377 | - ~~`System.`~~
378 | - ~~`Mono.`~~
379 |
380 |
381 |
382 |
383 |
384 | ## `` Tag
385 |
386 | USG automatically adds document tag at the beginning of generated file. You can remove this document tag by `sb.Clear()` in `Emit()` method.
387 |
388 |
389 | 日本語 / JA
390 |
391 | 渡される `StringBuilder` の冒頭にはドキュメントタグが入ってます。不要なら `sb.Clear()` してください。
392 |
393 |
394 |
395 |
396 |
397 | Unity Editor Integration
398 | ========================
399 |
400 | ## Installation
401 |
402 | Use the following git URL in Unity Package Manager (UPM).
403 |
404 | - Latest: https://github.com/sator-imaging/Unity-AltSourceGenerator.git
405 | - v3.0.0: https://github.com/sator-imaging/Unity-AltSourceGenerator.git#v3.0.0
406 | - v2.0.1: https://github.com/sator-imaging/Unity-AltSourceGenerator.git#v2.0.1
407 |
408 |
409 | ## USG Control Panel & Window
410 |
411 | - `Main Menu > Edit > Project Settings > Alternative Source Generator`
412 |
413 | 
414 |
415 |
416 | - **On**
417 | - Include generator in auto run sequence.
418 |
419 | - **Run**
420 | - Run generator on demand. Note that `OverwriteIfFileExists` setting on attribute is ignored.
421 |
422 | - **Link**
423 | - Click name to unveil generator script file in project window.
424 | - Down arrow icon (▼) will show referencing emitters below.
425 | - Linked chain icon (🔗) to unveil emitted file in `USG.g` folder.
426 | - 🗑️
427 | - Delete *emitted* file from `USG.g` folder.
428 |
429 | - `Main Menu > Tools > Alternative Source Generator`
430 | - open as a window.
431 |
432 | 
433 |
434 |
435 |
436 | ## Context Menu
437 |
438 | 日本語 / JA
439 |
440 | 手動でソースコード生成イベントの発火も可能です。「ジェネレーターのスクリプトファイル」か「生成されたファイル」を選択して、Project ウインドウで `Reimport` か `Unity Source Generator` 以下のメニューを実行します。
441 |
442 | ジェネレーターとして参照されているファイルを Reimport した場合は、関連するクラスすべてが再生成されます。`Force Generate...` はクラスアトリビュートの設定に関わらず強制的に上書き生成します。
443 |
444 |
445 |
446 | There is an ability to invoke source code generation by hand. With generator script file or generated file selected in Project window:
447 |
448 |
449 | - `Reimport`
450 | - This command respects `OverwriteIfFileExists` setting by generator class attribute.
451 | - Classes referencing selected generator will also be re-generated.
452 |
453 | - `Unity Source Generator > Force Generate`
454 | - This command will force re-generate source code even if overwrite setting is disabled.
455 |
456 |
457 | 
458 |
459 |
460 |
461 | Troubleshooting
462 | ===============
463 |
464 | #### Generator script update is not applied to generated file.
465 |
466 | Usually, this problem happens when Unity automatically reloads updated scripts WITHOUT Editor window getting focus. To solve the problem:
467 |
468 | 1. Close Unity and Visual Studio.
469 | 1. Restart Unity.
470 | 1. Launch Visual Studio by double clicking `.cs` script in Unity Editor.
471 |
472 | > *NOTE*: There is experimental feature to suspend auto reloading while Unity Editor in background (doesn't get focused). Open `Edit > Project Settings > Alternative Source Generator` to enable it.
473 |
474 |
475 |
476 |
477 |
478 | Copyright
479 | =========
480 |
481 | Copyright © 2023-2024 Sator Imaging, all rights reserved.
482 |
483 |
484 |
485 | License
486 | =======
487 |
488 |
489 |
490 | The above copyright notice and this permission notice shall be included in all
491 | copies or substantial portions of the Software.
492 |
493 | ```text
494 | MIT License
495 |
496 | Copyright (c) 2023-2024 Sator Imaging
497 |
498 | Permission is hereby granted, free of charge, to any person obtaining a copy
499 | of this software and associated documentation files (the "Software"), to deal
500 | in the Software without restriction, including without limitation the rights
501 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
502 | copies of the Software, and to permit persons to whom the Software is
503 | furnished to do so, subject to the following conditions:
504 |
505 | The above copyright notice and this permission notice shall be included in all
506 | copies or substantial portions of the Software.
507 |
508 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
509 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
510 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
511 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
512 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
513 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
514 | SOFTWARE.
515 | ```
516 |
517 |
518 |
519 |
520 |
521 |
522 |
523 |
524 |
525 |
526 |
527 | # Devnote
528 |
529 | ## TODO
530 |
531 | - `GenerateOnce` attribute parameter
532 | - currently USG generates same class/enum multiple times when multiple classes refer enum/singleton generator.
533 | - ex. `SceneBuildIndex` must be referred only once in project to avoid conflict
534 | - v4: remove obsolete functions
535 | - support C# 11 language features (waiting for Unity 6 update!!)
536 | - see also: [CHANGELOG](CHANGELOG.md#unreleased)
537 |
538 |
539 |
545 |
546 |
547 |
562 |
--------------------------------------------------------------------------------
/Editor/USGEngine.cs:
--------------------------------------------------------------------------------
1 | #if UNITY_EDITOR
2 |
3 | using System;
4 | using System.Buffers;
5 | using System.Collections.Generic;
6 | using System.IO;
7 | using System.Linq;
8 | using System.Reflection;
9 | using System.Text;
10 | using UnityEditor;
11 | using UnityEditor.Compilation;
12 | using UnityEngine;
13 |
14 | namespace SatorImaging.UnitySourceGenerator.Editor
15 | {
16 | ///
17 | /// > [!WARNING]
18 | /// > Works only on Unity Editor
19 | ///
20 | public sealed class USGEngine : AssetPostprocessor
21 | {
22 | const int BUFFER_LENGTH = 1024 * 64;
23 | const int BUFFER_MAX_CHAR_LENGTH = BUFFER_LENGTH / 3; // worst case of UTF-8
24 | const string GENERATOR_PREFIX = ".";
25 | const string GENERATOR_EXT = ".g";
26 | const string GENERATOR_DIR = @"/USG.g"; // don't append last slash. used to determine file is generated one or not.
27 | const string ASSETS_DIR_NAME = "Assets";
28 | const string ASSETS_DIR_SLASH = ASSETS_DIR_NAME + "/";
29 | const string TARGET_FILE_EXT = @".cs";
30 | const string PATH_PREFIX_TO_IGNORE = @"Packages/";
31 | private const string METHOD_NAME_OUTPUT_FILE_NAME = "OutputFileName";
32 | private const string METHOD_NAME_EMIT = "Emit";
33 | readonly static char[] DIR_SEPARATORS = new char[] { '\\', '/' };
34 |
35 |
36 | static bool IsAppropriateTarget(string filePath)
37 | {
38 | if (!filePath.EndsWith(TARGET_FILE_EXT, StringComparison.OrdinalIgnoreCase) ||
39 | !filePath.StartsWith(ASSETS_DIR_SLASH, StringComparison.OrdinalIgnoreCase))
40 | {
41 | return false;
42 | }
43 | return true;
44 | }
45 |
46 |
47 | static readonly ProjectSettingsData _settings = ProjectSettingsData.instance;
48 |
49 | static void OnPostprocessAllAssets(
50 | string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
51 | {
52 | // NOTE: Do NOT handle deleted assets because Unity tracking changes perfectly.
53 | // Even if delete file while Unity shutted down, asset deletion event happens on next Unity launch.
54 | // As a result, delete/import event loops infinitely and file cannot be deleted.
55 | // NOTE: [DidReloadScripts] is executed before AssetPostprocessor, cannot be used.
56 |
57 | // TODO: Unity sometimes reloads scripts in background automatically.
58 | // (it happens when Save All command was done in Visual Studio, for example.)
59 | // In this situation, code generation will be done with script data right before saving.
60 | // so that generated code is not what expected, and this behaviour cannot be solved on C#.
61 | // Using [DidReloadScripts] or EditorApplication.delayCall, It works fine with Reimport
62 | // menu command but OnPostprocessAllAssets event doesn't work as expected.
63 | // (script runs with static field cleared even though .Clear() is only in ProcessingFiles().
64 | // it's weird that event happens and asset paths retrieved but hashset items gone.)
65 | // --> https://docs.unity3d.com/2021.3/Documentation/Manual/DomainReloading.html
66 |
67 | if (!_settings.AutoEmitOnScriptUpdate)
68 | return;
69 |
70 | // NOTE: Use project-wide ScriptableObject as a temporary storage.
71 | for (int i = 0; i < importedAssets.Length; i++)
72 | {
73 | if (_settings.PathsToSkipImportEvent.TryRemove(importedAssets[i]))
74 | continue;
75 | if (_settings.AutoEmitDisabledPaths.Contains(importedAssets[i], StringComparer.Ordinal))
76 | continue;
77 | if (!IsAppropriateTarget(importedAssets[i]))
78 | continue;
79 |
80 | _settings.ImportedScriptPaths.TryAddUnique(importedAssets[i]);
81 | }
82 | _settings.PathsToSkipImportEvent.Clear();
83 | _settings.Save();
84 |
85 | // NOTE: processing files are done in CompilationPipeline callback.
86 | }
87 |
88 |
89 | // NOTE: event registration is done in InitializeOnLoad
90 | static void OnCompilationFinished(object context)
91 | {
92 | if (!_settings.AutoEmitOnScriptUpdate)
93 | return;
94 |
95 | try
96 | {
97 | RunGenerators(_settings.ImportedScriptPaths.ToArray());//, false);
98 | }
99 | catch
100 | {
101 | _settings.ImportedScriptPaths.Clear();
102 | _settings.Save();
103 | throw;
104 | }
105 | }
106 |
107 |
108 | static bool RunGenerators(string[] targetPaths)
109 | {
110 | bool somethingUpdated = false;
111 | try
112 | {
113 | var pathsToImportSet = new HashSet();
114 |
115 | for (int i = 0; i < targetPaths.Length; i++)
116 | {
117 | if (!TryGetTargetOrGeneratorClassByPath(targetPaths[i], out var targetOrGeneratorCls))
118 | continue;
119 |
120 | // NOTE: need to search both target and generator
121 | foreach (var info in _generatorInfoList
122 | .Where(x => x.TargetClass == targetOrGeneratorCls || x.Attribute.GeneratorClass == targetOrGeneratorCls))
123 | {
124 | if (TryEmit(info))
125 | {
126 | somethingUpdated = true;
127 | pathsToImportSet.Add(GetGeneratorOutputPath(info));
128 | }
129 | }
130 | }
131 |
132 | //import
133 | if (!BuildPipeline.isBuildingPlayer)
134 | {
135 | foreach (var path in pathsToImportSet)
136 | {
137 | _settings.PathsToSkipImportEvent.TryAddUnique(path);
138 | AssetDatabase.ImportAsset(path);
139 | somethingUpdated = true;
140 | }
141 |
142 | if (somethingUpdated)
143 | //// need a delay?
144 | //EditorApplication.delayCall += () =>
145 | AssetDatabase.Refresh();
146 | }
147 | }
148 | finally
149 | {
150 | for (int i = 0; i < targetPaths.Length; i++)
151 | {
152 | _settings.PathsToIgnoreOverwriteSettingOnAttribute.TryRemove(targetPaths[i]);
153 | }
154 | }
155 |
156 | return somethingUpdated;
157 | }
158 |
159 |
160 | static bool TryGetTargetOrGeneratorClassByPath(string assetsRelPath, out Type targetOrGeneratorCls)
161 | {
162 | if (!File.Exists(assetsRelPath))
163 | throw new FileNotFoundException(assetsRelPath);
164 |
165 | targetOrGeneratorCls = null;
166 |
167 | var generatorClsName = Path.GetFileNameWithoutExtension(assetsRelPath);
168 |
169 | // NOTE: File naming convention
170 | // Emitter: ...g.cs
171 | // SelfGen: ..g.cs
172 | if (!generatorClsName.EndsWith(GENERATOR_EXT, StringComparison.OrdinalIgnoreCase))
173 | {
174 | if (AssetDatabase.LoadAssetAtPath(assetsRelPath) is not MonoScript mono)
175 | throw new NotSupportedException("path is not script file: " + assetsRelPath);
176 |
177 | targetOrGeneratorCls = mono.GetClass();
178 | }
179 | else // try find generator for .g.cs file
180 | {
181 | // NOTE: When generated code has error, fix it and save will invoke Unity
182 | // import event and then same error code will be generated again.
183 | // Emit from generated file should only work when forced.
184 | // (delaying code generation won't solve this behaviour...? return anyway)
185 | if (!_settings.PathsToIgnoreOverwriteSettingOnAttribute.Contains(assetsRelPath, StringComparer.Ordinal))
186 | return false;
187 |
188 | generatorClsName = Path.GetFileNameWithoutExtension(generatorClsName);
189 | generatorClsName = Path.GetExtension(generatorClsName);
190 | if (generatorClsName.Length == 0)
191 | return false;
192 | generatorClsName = generatorClsName.Substring(1);
193 |
194 | var found = _generatorInfoList.FirstOrDefault(x => x.Attribute.GeneratorClass.Name == generatorClsName);
195 | if (found == default)
196 | return false;
197 |
198 | targetOrGeneratorCls = found.Attribute.GeneratorClass;
199 | }
200 |
201 | return true;
202 | }
203 |
204 |
205 | static bool TryEmit(CachedGeneratorInfo info)
206 | {
207 | var assetsRelPath = USGUtility.GetAssetPathByType(info.TargetClass);
208 | if (assetsRelPath == null)
209 | throw new FileNotFoundException("target class not found.");
210 |
211 | var generatorCls = info.Attribute.GeneratorClass;
212 | string outputPath = GetGeneratorOutputPath(info);
213 |
214 | var context = new USGContext
215 | {
216 | TargetClass = info.TargetClass,
217 | AssetPath = assetsRelPath.Replace('\\', '/'),
218 | OutputPath = outputPath.Replace('\\', '/'),
219 | };
220 |
221 | var sb = new StringBuilder();
222 | sb.AppendLine($"// {generatorCls.Name}");
223 |
224 | var isSaveFile = false;
225 | try
226 | {
227 | isSaveFile = (bool)info.EmitMethod.Invoke(null, new object[] { context, sb });
228 | }
229 | catch
230 | {
231 | //Debug.LogError($"[{nameof(UnitySourceGenerator)}] Unhandled Error on Emit(): {generatorCls}");
232 | throw;
233 | }
234 |
235 | //save??
236 | if (!isSaveFile || sb == null || string.IsNullOrWhiteSpace(context.OutputPath))
237 | return false;
238 |
239 | // NOTE: overwrite check must be done after Emit() due to allowing output path modification.
240 | // TODO: code generation happens but file is not written when overwrite is disabled.
241 | // any way to skip code generation?
242 | if (File.Exists(context.OutputPath))
243 | {
244 | if (!info.Attribute.OverwriteIfFileExists &&
245 | !_settings.PathsToIgnoreOverwriteSettingOnAttribute.Contains(assetsRelPath, StringComparer.Ordinal))
246 | return false;
247 | }
248 |
249 |
250 | var outputDir = Path.GetDirectoryName(context.OutputPath);
251 | if (!Directory.Exists(outputDir))
252 | Directory.CreateDirectory(outputDir);
253 |
254 | #if UNITY_2021_3_OR_NEWER
255 |
256 | // OPTIMIZE: use sb.GetChunks() in future release of Unity. 2021 LTS doesn't support it.
257 | using (var fs = new FileStream(context.OutputPath, FileMode.Create, FileAccess.Write))
258 | {
259 | //Span buffer = stackalloc byte[BUFFER_LENGTH];
260 | var rentalBuffer = ArrayPool.Shared.Rent(BUFFER_LENGTH);
261 | try
262 | {
263 | Span buffer = rentalBuffer;
264 | var span = sb.ToString().AsSpan();
265 | int len, written;
266 | for (int start = 0; start < span.Length; start += BUFFER_MAX_CHAR_LENGTH)
267 | {
268 | len = BUFFER_MAX_CHAR_LENGTH;
269 | if (len + start > span.Length)
270 | len = span.Length - start;
271 |
272 | written = info.Attribute.OutputFileEncoding.GetBytes(span.Slice(start, len), buffer);
273 | fs.Write(buffer.Slice(0, written));
274 | }
275 | fs.Flush();
276 | }
277 | finally
278 | {
279 | ArrayPool.Shared.Return(rentalBuffer);
280 | }
281 | }
282 |
283 | #else
284 | File.WriteAllText(context.OutputPath, sb.ToString(), info.Attribute.OutputFileEncoding);
285 | #endif
286 |
287 | Debug.Log($"[{nameof(UnitySourceGenerator)}] Generated: {context.OutputPath}",
288 | AssetDatabase.LoadAssetAtPath(context.OutputPath));
289 | return true;
290 | }
291 |
292 |
293 | /* entry ================================================================ */
294 |
295 | ///Run specified generator upon request. Designed for use in Unity build event.
296 | ///Path need to be started with "Assets/"
297 | ///Set true to run referencing emitters immediately. For use in build event.
298 | ///true if file is written
299 | [Obsolete("use USGUtility.ForceGenerateByType() instead.")]
300 | public static bool ProcessFile(string assetsRelPath, bool ignoreOverwriteSettingOnAttribute
301 | /* TODO: remove for future release */
302 | , bool autoRunReferencingEmittersNow = false)
303 | => Process(new string[] { assetsRelPath }, ignoreOverwriteSettingOnAttribute);
304 |
305 |
306 | internal static bool Process(string[] assetsRelPaths, bool ignoreOverwriteSettingOnAttribute)
307 | {
308 | if (ignoreOverwriteSettingOnAttribute)
309 | {
310 | for (int i = 0; i < assetsRelPaths.Length; i++)
311 | _settings.PathsToIgnoreOverwriteSettingOnAttribute.TryAddUnique(assetsRelPaths[i]);
312 | }
313 | return RunGenerators(assetsRelPaths);
314 | }
315 |
316 |
317 | /* utility ================================================================ */
318 |
319 | ///throw if failed.
320 | internal static string GetGeneratorOutputPath(CachedGeneratorInfo info)
321 | {
322 | var fileName = info.OutputFileName;
323 | if (fileName == null || fileName.Length == 0)
324 | throw new Exception("cannot retrieve output path.");
325 |
326 | string dirPath = USGUtility.GetAssetPathByType(info.TargetClass);
327 | if (dirPath == null)
328 | throw new FileNotFoundException("generator script file is not found.");
329 |
330 | dirPath = Path.GetDirectoryName(dirPath).Replace('\\', '/');
331 | if (!dirPath.EndsWith(GENERATOR_DIR, StringComparison.OrdinalIgnoreCase))
332 | dirPath += GENERATOR_DIR;
333 |
334 | return dirPath + '/' + fileName;
335 | }
336 |
337 |
338 | /* typedef ================================================================ */
339 |
340 | internal sealed class CachedGeneratorInfo
341 | {
342 | public Type TargetClass { get; internal init; }
343 | public UnitySourceGeneratorAttribute Attribute { get; internal init; }
344 |
345 | public string OutputFileName { get; internal set; }
346 | public MethodInfo EmitMethod { get; internal set; }
347 | public MethodInfo OutputFileNameMethod { get; internal set; }
348 | }
349 |
350 |
351 | /* initialize ================================================================ */
352 |
353 | static readonly BindingFlags METHOD_FLAGS
354 | = BindingFlags.NonPublic
355 | | BindingFlags.Public
356 | | BindingFlags.Static
357 | ;
358 |
359 |
360 | readonly static List _generatorInfoList = new();
361 | internal static IReadOnlyList GeneratorInfoList => _generatorInfoList;
362 |
363 |
364 | [InitializeOnLoadMethod]
365 | static void InitializeOnLoad()
366 | {
367 | CompilationPipeline.compilationFinished -= OnCompilationFinished;
368 | CompilationPipeline.compilationFinished += OnCompilationFinished;
369 |
370 |
371 | // fantastic UnityEditor.TypeCache system!!
372 | var generatorInfos = TypeCache.GetTypesWithAttribute()
373 | .SelectMany(static t =>
374 | {
375 | var attrs = t.GetCustomAttributes(false);
376 | var ret = new CachedGeneratorInfo[attrs.Count()];
377 | for (int i = 0; i < ret.Length; i++)
378 | {
379 | ret[i] = new CachedGeneratorInfo
380 | {
381 | TargetClass = t,
382 | Attribute = attrs.ElementAt(i),
383 | };
384 | }
385 | return ret;
386 | })
387 | ;
388 |
389 |
390 | foreach (var generatorInfo in generatorInfos)
391 | {
392 | // NOTE: self-emit generators which initialized without generator type parameter,
393 | // need to fill it correctly.
394 | generatorInfo.Attribute._generatorClass ??= generatorInfo.TargetClass;
395 |
396 | var generatorCls = generatorInfo.Attribute.GeneratorClass;
397 | var outputMethod = generatorCls.GetMethod(METHOD_NAME_OUTPUT_FILE_NAME, METHOD_FLAGS, null, Type.EmptyTypes, null);
398 | var emitMethod = generatorCls.GetMethod(METHOD_NAME_EMIT, METHOD_FLAGS, null, new Type[] { typeof(USGContext), typeof(StringBuilder) }, null);
399 |
400 | if (outputMethod == null || emitMethod == null)
401 | {
402 | Debug.LogError($"[{nameof(UnitySourceGenerator)}] Required static method(s) not found: {generatorCls}");
403 | continue;
404 | }
405 |
406 | generatorInfo.EmitMethod = emitMethod;
407 | generatorInfo.OutputFileNameMethod = outputMethod;
408 |
409 | //filename??
410 | if (!TryBuildOutputFileName(generatorInfo))
411 | {
412 | Debug.LogError($"[{nameof(UnitySourceGenerator)}] Output file name is invalid: {generatorInfo.OutputFileName}");
413 | continue;
414 | }
415 |
416 | //register!!
417 | _generatorInfoList.Add(generatorInfo);
418 | }
419 | }
420 |
421 |
422 | static bool TryBuildOutputFileName(CachedGeneratorInfo info)
423 | {
424 | info.OutputFileName = (string)info.OutputFileNameMethod?.Invoke(null, null);
425 | if (string.IsNullOrWhiteSpace(info.OutputFileName))
426 | return false;
427 |
428 | string fileName = Path.GetFileNameWithoutExtension(info.OutputFileName);
429 | string fileExt = Path.GetExtension(info.OutputFileName);
430 | info.OutputFileName = fileName + GENERATOR_PREFIX + info.TargetClass.Name;
431 | if (info.Attribute.GeneratorClass != info.TargetClass)
432 | info.OutputFileName += GENERATOR_PREFIX + info.Attribute.GeneratorClass.Name;
433 | info.OutputFileName += GENERATOR_EXT + fileExt;
434 |
435 | return true;
436 | }
437 |
438 |
439 | }
440 | }
441 | #endif
442 |
--------------------------------------------------------------------------------