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 Source Generators? How to use it and how to go about to do this?

Discussion in 'Scripting' started by Guedez, Aug 31, 2023.

  1. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    823
    How do I get started using those? I've tried installing CodeAnalysis through NuGet and it uninstalled on it's own when using JetBrains Rider editor.

    But more specifically on how to use code generators (or if a similar solution exists):
    I am about to make a XML database that works on burst (configure an item, like an axe in XML, set it's damage numbers and whatnot, then access it in Unity inside burst context, etc)

    Here is the draft of what I am thinking of, most of the magic will have to happen in the generation of the contents of the variable
    BurstDatabase.AllOfTheData
    , everything else is not exactly noteworthy besides the fact that it will have A LOT of boilerplate code that I want to do away with source generators.
    In the draft, surrounded by big comment blocks, is the code I'd like help figuring out how to generate automatically

    Code (CSharp):
    1. public interface IDatabaseData { //declares a type as readable from XML
    2.     public string XMLName { get; }
    3.     public string DatabaseName { get; }//requires to ensure unique references, repeats override data to enable mods to change stuff
    4. }
    5.  
    6. public partial struct MyTestStruct2 : IDatabaseData {
    7.     public string XMLName => "MyStruct2"; //xml tags like <MyStruct2><Data1>0</Data1></MyStruct2> are read as this object
    8.  
    9.     //source generated?
    10.     /////////////////////////////////////////////////////////////////////////////////////////////
    11.     public uint DatabaseID;
    12.     public string DatabaseName => BurstDatabase.Managed.GetName(DatabaseID);
    13.     /////////////////////////////////////////////////////////////////////////////////////////////
    14.  
    15.     public uint Data1;
    16. }
    17.  
    18. //[IDatabaseData("MyStruct")] this could also work instead of a interface, I guess. Maybe preferable?
    19. public partial struct MyTestStruct : IDatabaseData {
    20.     public string XMLName => "MyStruct";
    21.    
    22.     //source generated?
    23.     /////////////////////////////////////////////////////////////////////////////////////////////
    24.     public uint DatabaseID;
    25.     public string DatabaseName => BurstDatabase.Managed.GetName(DatabaseID);
    26.     /////////////////////////////////////////////////////////////////////////////////////////////
    27.  
    28.     //Accessible in burst context
    29.     public uint Data1;
    30.     public uint Data2;
    31.     public uint Data3;
    32.     public uint Data4;
    33.     public uint Data5;
    34.  
    35.     //How to make these two be source generated?
    36.     /////////////////////////////////////////////////////////////////////////////////////////////
    37.     public uint _MyTestStruct2;
    38.     public MyTestStruct2 MyTestStruct2 => BurstDatabase.Instance.LoadResource<MyTestStruct2>(_MyTestStruct2);
    39.     /////////////////////////////////////////////////////////////////////////////////////////////
    40.  
    41.     //Can only be accessed in managed context
    42.     /////////////////////////////////////////////////////////////////////////////////////////////
    43.     public uint _MySpriteRef;
    44.     public Sprite MySprite => BurstDatabase.Managed.LoadResource<Sprite>(_MySpriteRef);
    45.     /////////////////////////////////////////////////////////////////////////////////////////////
    46. }
    47.  
    48. public struct BurstDatabase {
    49.     public static BurstDatabase Instance;
    50.     public static BurstDatabaseManaged Managed;
    51.  
    52.     public NativeArray<byte> AllOfTheData; //built using a ton of managed and reflection, reading XML files and compiling it,
    53.  
    54.     //then serialized to disk until the database changes (mod install/uninstall)
    55.     public NativeHashMap<uint, int> IndexForReference; //same as above
    56.  
    57.     public T LoadResource<T>(uint ResourceRef) where T : unmanaged {
    58.         int TSize = UnsafeUtility.SizeOf<T>();
    59.         return AllOfTheData.GetSubArray(IndexForReference[ResourceRef], TSize).Reinterpret<T>(TSize)[0];
    60.     }
    61. }
    62.  
    63. public class BurstDatabaseManaged {
    64.     public Dictionary<uint, string> References; //loaded from file generated on the same method as above
    65.     public Dictionary<uint, string> Names; //loaded from file generated on the same method as above
    66.  
    67.     public T LoadResource<T>(uint ResourceRef) where T : Object {
    68.         return Resources.Load<T>(References[ResourceRef]);
    69.     }
    70.  
    71.     public string GetName(uint NameRef) {
    72.         return Names[NameRef];
    73.     }
    74. }
     
  2. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    3,899
    What do you want to generate?
    Early last year, unaware of source generators, I wrote my own little library to generate C# scripts. Of course I did not aim for it to be full fledged, but it does support all access modifiers, readonly, static, class, struct, and so on - pretty complete for general Unity coding uses overall.

    Then, I was surprised to eventually learn about source generators and understandably
    this needs to support every nook and cranny of C# but still … the verbosity of that and the technicality of its terms is just insane. I could generate scripts with at most 20% of that code and it was totally readable code too.

    So … maybe you are better off just to StringBuilder your way into generating scripts. ;)
     
    Guedez likes this.
  3. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,769
    Hell, there's one hiding inside the InputSystem code-base, called CodeWriter. Can't remember if it's internal, but you can still just view the script and write something very similar.
     
    Guedez and CodeSmile like this.
  4. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    3,899
    Mine is called ScriptBuilder, if I remember correctly it may even be a subclass of StringBuilder. ;)
     
    Guedez likes this.
  5. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    823
    Code (CSharp):
    1. [DatabaseData("MyStruct")]
    2. [DatabaseAuto(typeof(Sprite), "MySprite")]
    3. [DatabaseAuto(typeof(MyTestStruct2), "MyTestStructGetter")]
    4. public partial struct MyTestStruct {
    5.     public uint Data1;
    6.     public uint Data2;
    7.     public uint Data3;
    8.     public uint Data4;
    9.     public uint Data5;
    10. }
    Generated this:
    Code (CSharp):
    1. public partial struct MyTestStruct {
    2.     public uint DatabaseID;
    3.     public string DatabaseName => BurstDatabase.Managed.GetName(DatabaseID);
    4.  
    5.     public uint _MySprite;
    6.     public UnityEngine.Sprite MySprite => BurstDatabase.Managed.LoadResource<UnityEngine.Sprite>(_MySprite);
    7.  
    8.     public uint _MyTestStructGetter;
    9.     public MyTestStruct2 MyTestStructGetter => BurstDatabase.Instance.LoadResource<MyTestStruct2>(_MyTestStructGetter);
    10. }
    This works great with one exception. The generated code is always 1 compilation behind. It seems
    UnityEditor.AssemblyReloadEvents.beforeAssemblyReload
    is not a good place to add new sources.
    The current code:
    Code (CSharp):
    1. public class BurstDatabaseLoader : MonoBehaviour {
    2.  
    3.     private static readonly string C = @"
    4. public partial struct BBBBBBBB {";
    5.  
    6.     private static readonly string P = @"
    7.    public uint _AAAAAAAA;
    8.    public TTTTTTTT AAAAAAAA => BurstDatabase.MMMMMMMM.LoadResource<TTTTTTTT>(_AAAAAAAA);
    9. ";
    10.  
    11.     private static readonly string D = @"
    12.    public uint DatabaseID;
    13.    public string DatabaseName => BurstDatabase.Managed.GetName(DatabaseID);
    14. ";
    15.  
    16.     static BurstDatabaseLoader() {
    17.         UnityEditor.AssemblyReloadEvents.beforeAssemblyReload += CompileCode;
    18.     }
    19.     private static void CompileCode() {
    20.         StringBuilder StringBuilder = new StringBuilder();
    21.         foreach (Type type in GetTypesWithHelpAttribute()) {
    22.             StringBuilder.Append(C.Replace("BBBBBBBB", type.Name));
    23.             StringBuilder.Append(D);
    24.             foreach (DatabaseAutoAttribute DatabaseAutoAttribute in type.GetCustomAttributes<DatabaseAutoAttribute>()) {
    25.                 Type T = DatabaseAutoAttribute.Type;
    26.                 StringBuilder.Append(P
    27.                     .Replace("AAAAAAAA", DatabaseAutoAttribute.Name)
    28.                     .Replace("TTTTTTTT", T.FullName)
    29.                     .Replace("MMMMMMMM", T.IsStruct() ? "Instance" : "Managed")
    30.                 );
    31.             }
    32.             StringBuilder.Append("}\n");
    33.         }
    34.         File.WriteAllText("Assets/_Generated.cs", StringBuilder.ToString());
    35.         AssetDatabase.Refresh();
    36.     }
    37.  
    38.  
    39.     static IEnumerable<Type> GetTypesWithHelpAttribute() {
    40.         Assembly assembly = Assembly.GetExecutingAssembly();
    41.         Type attributeType = typeof(DatabaseData);
    42.         foreach (Type type in assembly.GetTypes()) {
    43.             if (type.GetCustomAttributes(attributeType, true).Length > 0) {
    44.                 yield return type;
    45.             }
    46.         }
    47.     }