Add source generator for dependency injection#5541
Conversation
|
Only providing memory based tests for now (there will definitely be performance improvements on the CPU side as you'd expect, but these will roughly track the reduction in allocations and GC object tracking): Testing is done by loading the game from a cold start, and entering song select (with only 9 beatmap sets loaded). Note that this also includes my other recent optimisations to
|
|
Before merging this, please give any suggestions for names, if any. It's going to be extremely hard to change things after the fact since this is all compiled. |
|
Which naming in particular? |
|
Code reviewBecause this is slated for a quick merge, I explicitly did not read any of the generator internals at this point. I probably will at a later date, but reviewing a 3k line diffstat in detail would be counterproductive anyway. I see a lot of tests here and therefore I'm just assuming they're broad enough to cover most cases. In a way, as long as outputs of the generator are good the details don't matter too much, functionally speaking. I did read the actual changes in DI though as they are the key part of this diff. I think I get what this is trying to do, and it makes sense to me (that said I didn't read it that long). When it comes to naming, I don't think it matters that much as I had much more trouble with deciphering the control flow at first rather than the naming, for two main reasons:
That said, when it comes to the names questioned above, my view is thus:
See what you think about these. I don't insist on these, they're just subjective feelings. Curious if you see those as any better than what's there right now. Running the code fixI'm not sure why, but was placed in to scope down to just this one code fix rather than fix absolutely every inspection reported by everything ever (which is what the After doing the above I'm getting the same results as your test branches, though (after enough iterations). Build timeTested on game-side.
Not sure whether I measured wrong, but this is basically not an issue for me because I can't really tell that anything's changed. Test procedure was basically Benchmark resultsRunning the benchmark attached in this PR on the test branch, I get:
That is pretty cut-and-dry. Wins across the board. Development & quick testing
So to sum all of the above up, aside from the naming suggestions above I don't have much to offer or object to. Good work on this 👍 |
|
Adjusted naming as proposed, favouring
I noticed this as well and it's something I want to refactor as a followup. |
|
I'm gonna need another nupkg after this is merged due to the naming changes. |
|
The new naming looks good to be, so I'll do that soon. |








This one's been brewing for a long time while I tried to figure out the best way to do things. It provides a structural foundation on top of which we can build other source generators.
Strategy
There are two primary goals that guided my decisions:
Before I explain further, let me mention that I'm not particularly proud of the naming in this PR and I understand it can be confusing. I'm open to alternatives.
For source generators to work at all for us, we unfortunately need to make every
Drawableclasspartial.To meet the above goals, the structure I've come up with is to add a
partialclass that implements a specific interface -ISourceGeneratedDependencyActivator. If the dependency activator detects that this interface has been implemented, it uses it instead of going via the reflection path.The
ISourceGeneratedDependencyActivatorinterface exposes one method in particular -RegisterDependencyActivator(IDependencyActivator). The source generator implements this method in the form of:Of importance,
Register()here generates twoFunc<>s which inject/cache dependencies for the current object. It does not do the whole activation process in-line. The reason for this is that I want this to be transparent and want both the reflection and SG modes to work concurrently regardless of the type hierarchy (e.g. if code hasn't been recompiled to support the SG).That is to say, as far as
DependencyActivator(the o!f static class orchestrating everything) is concerned, the returnedFunc<>s are just the normal callbacks generated by the reflection pathway.This is all placed in a file named
g_{class_name}_{guid}_Dependencies.cswhich you can get to by going to the definition of classes in Rider. Why the GUID? Because class names can conflict. There doesn't seem to be a proper solution to this offered by the Roslyn team.Analyser + codefix
I've created a pretty "basic" analyser for warning against non-
partialclasses when required. It attempts to discover:DependencyContainer.Inject().CachedModelDependencyContainer<T>.ITransformable,IDrawableorISourceGeneratedDependencyActivator.This covers all current cases while being as least invasive as possible (i.e. not analysing every member of every class ever).
A code fix has been provided for the diagnostic, however if you're planning to codefix the whole solution I suggest the running the following command until it outputs 0 changes:
The code fix seems to not fix all issues on the first attempt. This is potentially caused by my implementation, however I've tried everything that I could find and nothing's worked. I'll look into fixing this as a future effort, however for the most part it's a one-time thing.
The analyser and code fixer don't have tests at the moment. These are also planned in a future effort and will be structured similarly to the source generator tests and the
osu-localisation-analyserproject.Notes
IIncrementalGenerator, but there's a bit more reading to do for that.osu-localisation-analyser) and the tests are lacking structure. This will be improved in a future effort.Testing
You can test this branch with the following trees:
osu-framework: https://github.com/smoogipoo/osu-framework/tree/sg-dependency-activator-partial-classesosu: https://github.com/smoogipoo/osu/tree/sg-test (includes local-framework)I've tested:
osu-frameworkosu+ local framework.osu+osu-frameworkpackage.I have not tested VSCode compatibility, but I have no reason to believe it won't work, unlike
osu-localisation-analyserwhere there's differences in how IDEs add files to workspaces from analysers/codefixes.I'll leave it to @peppy to post profiling results.