Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Question C# Code Generation with RoslynAnalyzer question

Discussion in 'Scripting' started by rejemy, Sep 2, 2023.

  1. rejemy

    rejemy

    Joined:
    Nov 1, 2013
    Posts:
    13
    I'm developing an experimental c# code generation plugin for my project what will auto-generate some boilerplate custom serialization code. I have it running, and it seems to be generating code, but something in my codebase is causing it to generate bad code. I can see the errors on the build console, and the errors start with:

    DistributedEntityGenerator/SourceGenerator.DistributedEntityGenerator/ImpunityDistributedMethodsGenerated.cs(5,7): error CS0246: (etc etc C# error here)

    I'd love to be able to look at the generated code to see what the problem is, but I can't find a file with that name anywhere in my project. Is there a way to have Unity output the generated source to the filesystem somewhere? Or alternatively, is there a way to have my code analyzer output log info to the Unity console when it runs?

    I'm pretty excited about the possibilities of using generated code in my Unity projects, so I hope I can get this figured out!
     
  2. rejemy

    rejemy

    Joined:
    Nov 1, 2013
    Posts:
    13
    I should add that this is with Unity 2022.3 with Visual Studio for Mac.
     
  3. tsukimi

    tsukimi

    Joined:
    Dec 10, 2014
    Posts:
    50
    I don' know how to do it in Unity, but maybe you can create a test project in your Generator solution, let it depends on the generator prj, and write test codes(which is similar to your code in Unity).
    Not sure if it works with Visual Studio for Mac, but you can inspect the generated code in Visual Studio, or emit the code to real file by <EmitCompilerGeneratedFiles>(plz search). (I'm not sure if you can do this in Unity projects, so it's better to do it in normal C#/.Net sln)

    If the class code is generated properly, then you can type the class name in any other place in your script, use "Go to Definition" (or any similar feature, I think Visual Studio have one) to look into the generated files. This also applies to Unity project.
     
    Last edited: Sep 2, 2023
  4. wideeyenow_unity

    wideeyenow_unity

    Joined:
    Oct 7, 2020
    Posts:
    728
    I remember searching at one point, for a way to save a class, here are my notes on it:
    Code (CSharp):
    1. using System.Runtime.Serialization.Formatters.Binary;
    2. using System.IO;
    3. using UnityEngine;
    4.  
    5. public static class SaveLoad
    6. {
    7.     public static void SaveData(ClassName className)
    8.     {
    9.         BinaryFormatter formatter = new BinaryFormatter();
    10.         string path = Application.persistentDataPath + "/FileName.fileType";
    11.  
    12.         FileStream stream = new FileStream(path, FileMode.Create);
    13.  
    14.         ClassName classData = new ClassName(className);
    15.  
    16.         formatter.Serialize(stream, classData);
    17.         stream.Close();
    18.     }
    19.  
    20.     public static ClassData LoadData()
    21.     {
    22.         string path = Application.persistentDataPath + "/FileName.fileType";
    23.  
    24.         if(File.Exists(path))
    25.         {
    26.             BinaryFormatter formatter = new BinaryFormatter();
    27.             FileStream stream = new FileStream(path, FileMode.Open);
    28.  
    29.             ClassData data = formatter.Deserialize(stream) as ClassData;
    30.  
    31.             stream.Close();
    32.  
    33.             return data;
    34.         } else
    35.         {
    36.             Debug.LogError("Error: Save file not found in " + path);
    37.             return null;
    38.         }
    39.     }
    40. }
    41. ========================================
    42. // ClassData.cs
    43. public class ClassData
    44. {
    45.     public string characterName;
    46.     public int strength;
    47.     public int intelligence;
    48.     public int agility;
    49.  
    50.     public ClassData(ClassName className)
    51.     {
    52.         characterName = className.name;
    53.         strength = className.strength;
    54.         intelligence = className.intelligence;
    55.         agility = className.agility;
    56.     }
    57. }
    58. ==========================================
    59. // ClassName.cs
    60. public class ClassName : Monobehavior
    61. {
    62.     public string name;
    63.     public int strength;
    64.     public int intelligence;
    65.     public int agility;
    66.    
    67.     void Start()
    68.     {
    69.         ClassData data = SaveLoad.LoadData();
    70.  
    71.         name = data.characterName;
    72.         strength = data.strength;
    73.         intelligence = data.intelligence;
    74.         agility = data.agility;
    75.     }
    76.    
    77.     public void SaveGame()
    78.     {
    79.         SaveLoad.SaveData(this);
    80.     }
    81. }
    But if I remember, the static class, as long as it's within the project, should have anything else call it no problem. So just make the generated code call this(probably before the errors) so at least something will be saved.

    And another thing that comes to mind, is in one of my projects I had a ton of resources(wood, iron, etc...) that were scripts in a folder, and I made a way to get all those scripts and turn them into classes during runtime:
    Code (CSharp):
    1. void CreateResourceList()
    2. {
    3.     FileInfo[] files;
    4.     DirectoryInfo dir = new DirectoryInfo("Assets/Scripts/Resources");
    5.     files = dir.GetFiles("*.cs");
    6.     int rssCount = files.Length;
    7.     string[] strings = new string[rssCount];
    8.  
    9.     for (int i = 0; i < rssCount; i++)
    10.     {
    11.         strings[i] = files[i].Name;
    12.         string[] results = strings[i].Split('.');
    13.         GameObject obj = Instantiate(objList.empty, objList.transform);
    14.         obj.AddComponent(Type.GetType(results[0]));
    15.         obj.name = results[0];
    16.     }
    17. }
    So hopefully something I mentioned, can spark an idea that might help.

    Out of curiosity, are you making a Project Spark clone? If so, I'd love to help in any way possible. :)
     
    Sluggy likes this.
  5. Lurking-Ninja

    Lurking-Ninja

    Joined:
    Jan 20, 2015
    Posts:
    9,904
    Add the
    SourceGenerator
    label to the DLL the same way you need to add the
    RoslynAnalyzer
    label when setting up. It will cause the errors to be routed to the unity console in theory.
    You can also self-report if needed to insert something like this:
    Code (CSharp):
    1. var msg = new DiagnosticDescriptor("test", "Title", "Message", "Category", DiagnosticSeverity.Error, true);
    2. context.ReportDiagnostic(Diagnostic.Create(msg, Location.None));
    AFAIK only the
    Error
    severity works, BTW.

    In Rider I can simply hit the CTRL-B (GoTo Declaration or Usages) and will open up the generated partial class. I don't work on Mac, so I have zero idea about VS for Mac though.
     
  6. wideeyenow_unity

    wideeyenow_unity

    Joined:
    Oct 7, 2020
    Posts:
    728
    Also just thought there may be a way to make those generated classes Serializable, in some way. But I'm not too familiar with Serialization, in this regard.
     
  7. rejemy

    rejemy

    Joined:
    Nov 1, 2013
    Posts:
    13
    Thanks for all the great tips, everyone! I finally found the source of the issue: I assumed that the Code Generator would only execute once, in the main assembly (Assembly-CSharp). Turns out it actually runs at least 4 times for each compile, against 4 different assemblies: UnityEngine.TestRunner, UnityEditor.TestRunner, Unity.VisualStudio.Editor, and then finally Assembly-CSharp.

    I was adding a source to all those assemblies, which was causing problems. Limiting it to only add source to Assembly-CSharp fixed it.
     
  8. BSimonSweet

    BSimonSweet

    Joined:
    Aug 17, 2022
    Posts:
    51
    I don't know what IDE you're using, but on Rider, you can access the generated from the Solution Explorer, in the Dependencies of an Assembly :

    upload_2023-9-4_9-44-18.png
     
    tsukimi likes this.
  9. gabriel-bunch

    gabriel-bunch

    Joined:
    May 23, 2023
    Posts:
    3
    Hey there!

    I think I might be having this problem as well.
    How do you limit the generated source to be added only to the main Assembly-CSharp?

    Thanks!
     
  10. Lurking-Ninja

    Lurking-Ninja

    Joined:
    Jan 20, 2015
    Posts:
    9,904
    You can ask for the currently compiled assembly in the semantic portion of the generator. It depends on if you're writing incremental or not. In incremental (which I exclusively use) it looks like this (not working, example couple of lines from a real generator):
    Code (CSharp):
    1. private static EnumDeclarationSyntax GetSemanticTargetForGeneration(
    2.             GeneratorSyntaxContext context, CancellationToken token)
    3.         {
    4.             if (Common.CheckAssembly(context)) return null;
    5.  
    This
    GetSemanticTargetForGeneration
    is called in the
    transform
    section in the
    CreateSyntaxProvider
    call.
    And the
    CheckAssembly
    method is like this:
    Code (CSharp):
    1.         internal static bool CheckAssembly(GeneratorSyntaxContext context) =>
    2.             string.IsNullOrEmpty(context.SemanticModel.Compilation.AssemblyName)
    3.             || !context.SemanticModel.Compilation.AssemblyName.Equals("your target assembly name goes here");
    4.  
    Also, it is highly recommended to put your code you want to extend with source generator into your own assembly using asmdef file (and the name you give there will be the name you're checking against here).
    This way you can exclude a lot of code. Which adds less overhead to Unity's compilation.

    I'm doing this way.
     
    gabriel-bunch likes this.
  11. gabriel-bunch

    gabriel-bunch

    Joined:
    May 23, 2023
    Posts:
    3
    Thanks a lot for the response @Lurking-Ninja ! Will let my friend that was actually having problems with the source generator know this :)