Eastrall / Rosalina

Rosalina is a code generation tool for Unity's UI documents. It generates C# code-behind script based on a UXML template.
MIT License
136 stars 18 forks source link

Source generator for Unity 2021 #15

Open trondtactile opened 2 years ago

trondtactile commented 2 years ago

Unity 2021 supports Source Generators:

Instead of having to manually click "generate", this would just generate the code when compiling, meaning your assets would always be in sync with your generated code.

Eastrall commented 2 years ago

Rosalina uses an AssetProcessor from Unity to detect when a UIDocument has changed to generate the associated binding script. This process is automatic and triggered when the .uxml is saved. https://github.com/Eastrall/Rosalina/blob/main/Editor/Scripts/RosalinaAssetProcessor.cs#L12

I don't know much about source generators, but if it's a compile-time (or build) action, this will not fit into the code generation process since the binding script should be always synchronized with the UIDocument. This is why I used an AssetProcessor to do so.

Of course, you can "force" the binding script generation through the Rosalina > Generate UI bindings but this is only useful if there is a problem with the AssetProcessor. Not mandatory to use it everytime 😉

trondtactile commented 2 years ago

It's really nice that this is automatic on asset change! Did not notice that.

One upside with Source Generators is that their output is not actual files in the project, but just compiled classes. And when using frameworks with "code behind" generation, the classes are indeed reflected on asset change.

However, as Unity is a special little snowflake, maybe this approach would not work very well, as one does not "build" Unity in order to compile it.

Eastrall commented 2 years ago

Indeed, if it was a "pure" C# project, this would be a nice solution. Closing this for now, but keeping the idea in the back of my mind. 😉

blepmlem commented 2 years ago

So I took a stab at running Rosalina as a Source Generator, and... it's really working great. There is no real problem in regards to picking up on changes in the Uxml files, since the changes are picked up as soon as you do anything in your IDE (which you'd want to do anyway if you've added/changed a named VisualElement). It's fantastic to have all bindings up to date in your IDE at all times without the extra recompile! I can supply the code when I've cleaned it up a bit. image

Eastrall commented 2 years ago

That's great @blepmlem ! Would love to see how you achieve to implement source generators within Unity. Feel free to open a PR and contribute 😄

Reopening the issue for better tracking.

blepmlem commented 2 years ago

My fork is located at https://github.com/blepmlem/Rosalina/tree/source-generator I've modified it quite a bit for my own purposes, but the general Source Generator work should be pretty universal! (I look for scripts implementing a specific interface, and don't do anything with MonoBehaviours and UIDocuments since I use this tool for both editor and runtime UI 😄)

blepmlem commented 2 years ago

The gist of it is that I implement an ISyntaxReceiver for finding the relevant scripts to create partial classes for, then pass the information to the RosalinaBindingsGenerator, it does its thing as usual, and the output code is added to the compilation process.

trondtactile commented 2 years ago

Wow, that is awesome! I'll certainly try to get this capability into my fork (unless it can be added to this repo somehow).

Eastrall commented 2 years ago

@blepmlem this is a great Proof Of Concept you made here! I am going checkout your code and see how it works, in order to understand properly what's going on within Unity and the source generator. Aditionnaly, this would solve the "Pathing support" described in issue #13 by @siglocpp

Eastrall commented 2 years ago

So, I've been doing some researches and tests around the source generator feature and it seems that it works great in a "native" C# environment. Let me explain.

According to the Unity documentation (https://docs.unity3d.com/Manual/roslyn-analyzers.html), to create a Roslyn analyzer or a Source Generator, you'll need to create a .NET library targeting netstandard2.0 and then generate your code as you want. (In our case, using the RosalinaGenerator) Then copy/paste the generate dll into a Plugins (or whatever the folder's name) folder and add the RoslynAnalyzer label to this asset and disable both Editor and Standalone platforms on the asset configuration.

In our use case, everytime we make a change to a UXML file, the RosalinaAssetProcessor will have to trigger a dotnet build command of source generator project to generate a dll file with the generate bindings, copy/paste the generated dll into the Plugins folder, set the RoslynAnalyzer label and unselect the Editor and Standalone options manually.

While I believe all of the steps I mentioned above are possible using an Unity script ; but while I was testing I noticed that when I regenerate the DLL with new generated code and import it into Unity again (overriding existing dll), changes were not visible, even if when forcing the reimport process using "Right Click -> "Reimport" on the asset dll. Thus, there is a "risk" that the users using Rosalina experiment some weird behaviors.

What I can suggest for now, let's keep this issue active and see if Unity intend to "natively" support Source Geneartors inside Unity itself using the .NET APIs in the future.

To avoid having the generated files next to the UXML files, in the mean time, we could follow issue #13 and put all generated files into a Assets/Rosalina/AutoGenerated folder to avoid file polution next to the UXML file.

trondtactile commented 2 years ago

Are you sure this is the intended meaning of the docs page? From what I can read, yes, the source generator needs to be a DLL with a specific label, but it requires no more modification.

In my mind what you need to do, is trigger a recompile (CompilationPipeline.RequestScriptCompilation();) in the asset importer, and then the compilation pipeline will trigger the source generator. And the custom source generator will then simply look up all uxml files and generate code from them.

But perhaps you attempted this, and it does not work this way?

Eastrall commented 2 years ago

Well I just followed the official documentation and they say:

  1. Build your source generator for release. To do this, go to Build and select the Batch Build option.
  2. In your source generator’s project folder, find the bin/Release/netstandard2.0/ExampleSourceGenerator.dll file.
  3. Copy this file into your Unity project, inside the Assets folder.
  4. Inside the Asset Browser, click on the .dll file to open the Plugin Inspector window.
  5. Go to Select platforms for plugin and disable Any Platform.
  6. Go to Include Platforms and disable Editor and Standalone.
  7. Go to Asset Labels and open the Asset Labels sub-menu.
  8. Create and assign a new label called RoslynAnalyzer. To do this, enter “RoslynAnalyzer” into the text input window in the Asset Labels sub-menu. This label must match exactly and is case sensitive. After you create the label for the first analyzer, The label appears in the Asset Labels sub-menu. You can click on the name of the label in the menu to assign it to other analyzers.
  9. To test the source generator is working, ........

With the current state of Rosalina, we already have "code generation" using Roslyn and when we make changes to a UXML file, the Unity AssetProcessor is triggered and generates the C# files: https://github.com/Eastrall/Rosalina/blob/2d9bffd56736058132e380c7365087ecaf62f03d/Editor/Scripts/RosalinaAssetProcessor.cs#L8-L12 At the end of the process the AssetDatabase is refreshed: https://github.com/Eastrall/Rosalina/blob/2d9bffd56736058132e380c7365087ecaf62f03d/Editor/Scripts/RosalinaAssetProcessor.cs#L44

With this solution, we actually see the generated code into your asset files, but with source generators, the source code is directly "injected" into the final assembly (in the use case described in the Unity official documentation, a netstandard2.0 dll.) So I think this is actually intended by Unity to work this way.

Eastrall commented 2 years ago

I have been doing more research on source generators and it seems that Unity is working on source generators according to the "Engineering" roadmap: https://unity.com/roadmap/unity-platform/engineering

image

I don't know if it's going to be integrated like a netstandard2.0 project within Visual Studio. I'll suggest we wait and see where Unity is going with this feature. 😉