Reflex icon indicating copy to clipboard operation
Reflex copied to clipboard

Source generated injection

Open Moe-Baker opened this issue 4 months ago • 4 comments

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.

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

How Has This Been Tested?

  • [x] Implemented a new test for Unity project, a SourceGeneratedInjectorTests.
  • [x] Implemented tests within the source generator project "Reflex.Generator.Injector.Tests".
  • [x] 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:

  • [x] My code follows the style guidelines of this project
  • [x] I have performed a self-review of my own code
  • [x] I have commented my code, particularly in hard-to-understand areas
  • [x] I have made corresponding changes to the documentation
  • [x] My changes generate no new warnings
  • [x] 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
  • [x] I have checked my code and corrected any misspellings
  • [x] I have checked that Reflex.GettingStarted still runs nicely

Moe-Baker avatar Aug 08 '25 19:08 Moe-Baker

@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.

gustavopsantos avatar Aug 12 '25 01:08 gustavopsantos

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.

Moe-Baker avatar Aug 12 '25 04:08 Moe-Baker

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 avatar Dec 03 '25 21:12 AlonTalmi

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

AlonTalmi avatar Dec 07 '25 09:12 AlonTalmi