Skip to content

Conversation

@Moe-Baker
Copy link

@Moe-Baker Moe-Baker commented Aug 8, 2025

Description

Implemented a source generator to supplement AttributeInjector's reflection usage, while maintaining the old reflection API for the time being, but I'm open to removing it and updating the PR if needed.

The source generator is implemented as a Roslyn source generator, all in accordance with the Unity documentation for Unity 2021.3.
The source generator is fully linked with the Unity project and is working as expected.
The motivation was to improve the performance of Reflex's Inject functionality, which relies entirely on reflection.

The source-generated injection system works in a rather simple way; it will look for a IAttributeInjectionContract interface on the type being injected to, if that interface exists, the method inside the interface is invoked, if not, the old reflection-based system is run.
The source generator's job is to automatically implement that interface and populate its method call; it accomplishes this by looking for any type that is decorated with the [SourceGeneratorInjectable] attribute, wherein the decorated needs to be:

  • Public alongside all its containing types.
  • Partial alongside all its containing types.

Custom diagnostics were also implemented for:

  • Error | Types decorated with [SourceGeneratorInjectable] must be partial alongside their containing types.
  • Error | Types decorated with [SourceGeneratorInjectable] must be public alongside their containing types.
  • Error | Properties decorated with [Inject] must have a setter.
  • Warning | Methods decorated with [Inject] should return void.
  • Error | Fields decorated with [Inject] must not be readonly.

The addition of the source generator was accomplished by creating a new C# solution "Reflex.Generator", the solution has 3 projects, the generator project, a mock project & a tests project.
The generator (Reflex.Generator.Injector) comes with two publish profiles, one of them (Send to Unity) will publish the generator and send it to the appropriate directory within the Unity project.
The generator should be sent to Unity whenever a code change is done within it; the publish profile will handle this entire operation, just needs to be called from VS/Rider.

No new dependencies are required.

Type of change

Please delete options that are not relevant.

  • New feature (non-breaking change which adds functionality)
  • This change requires a documentation update

How Has This Been Tested?

  • Implemented a new test for Unity project, a SourceGeneratedInjectorTests.
  • Implemented tests within the source generator project "Reflex.Generator.Injector.Tests".
  • Modified benchmark setup to showcase 3 different Reflex scenarios (direct resolution from a container, reflection injection, source-generated injection).
Screenshot 2025-08-08 205028

Ran all Unity tests after implementing the feature, 4 failed, swapped to main branch to test without my changes, same 4 tests failed, I'm not sure why, but it doesn't seem to be related to my changes.

Test Configuration:

  • Firmware version: Unity v6000.1.10f1
  • Hardware: Ryzen 5700X | RX 6900XT
  • Toolchain: Visual Studio v17.13.6
  • SDK: What SDK?

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • I have checked my code and corrected any misspellings
  • I have checked that Reflex.GettingStarted still runs nicely

Needed to modify .gitignore too as it would ignore required project files.
Thought about moving everything inside the repository into a "Reflex.Unity" Folder, but that seemed extreme, and it would break compatibility with older version UPM installs.
Create two profiles
- Local Publish, publishes locally to "Reflex.Generator\Reflex.Generator.Injector\bin\Publish", only for testing,
- Send to Unity, publishes directly to "Assets\Reflex\Injectors\Source-Generator", must be used to update Unity's source generator after any change in the source generator project.
Published through the "Send to Unity" publish profile defined in the Reflex.Generator.Injector project.
The DLL is the only important part, for it to function as a source generator it needs to be labeled "RoslynAnalyzer" inside of Unity.
New tests pass as expected.
Strangely though, 4 old tests fail, I switched branches to the main and ran the same tests still failed, not sure why, maybe because I'm using Unity 6.
Nonetheless, it doesn't seem to be my fault, but I suppose the pull request will make it clear.
Bug was caused by scanning global namespace of compilation instead of global namespace of assembly.
Updated Unity source generator DLL.
…r if Failed

Test will cause a compilation error if MockParent.MockUsage object doesn't implement the IAttributeInjectionContract interface.
Use only a single interface method to inject all of (fields, properties, methods).
The 3 separate method calls were unnecessary as we'd never want to inject only one type of class member.
Separated the Reflex benchmark into 3 benchmark
-  Container Resolve (Original)
- Attribute Reflection Inject
- Attribute Generator Inject
@gustavopsantos
Copy link
Owner

@Moe-Baker This is really impressive, 1.36/1.87 = 0.72%, so almost 30% faster. I wonder if we could achieve similar results by "baking" the reflection without using source generators, like for instance using Delegate.CreateDelegate or even expression trees.

@Moe-Baker
Copy link
Author

From my experience, Delegate.CreateDelegate & Expression Trees will give similar performance, but have harsher limitations, as they both wont work with AOT compilation, so no IL2CPP.
To my knowledge, source generation is the only option that can support every platform and give this performance level.

@AlonTalmi
Copy link

Any update @gustavopsantos ?

Also 1 suggestion is to change IAttributeInjectionContract's name because it might be useful for people who want to implement custom injection method

@AlonTalmi
Copy link

Also, the pdb file is missing it's meta file. Which throws an error when installing through UPM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants