-
Notifications
You must be signed in to change notification settings - Fork 41
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add merged form of @Inject
and @ModifyVariable
#95
base: main
Are you sure you want to change the base?
Conversation
Allows capturing (and modifying) locals like `@ModifyVariable`, but with the callback and multiple local handling of `@Inject` Still needs the docs finishing and remapping logic adding
How is this likely to interact with Mumfrey's planned local variable capturing changes in 0.9? |
As an implementation it's really only tied to how
I don't have a crystal ball for how 0.9 will pan out, but using a new annotation rather than adding to |
Guess this must be a JDT exclusive inference
New things with tabs, old things with spaces
Got to make sure the index is only bumped when not going back
Quite particular about its generics J6
This seems very unnecessary yet 5ada553 says otherwise
Don't want to try pass in invalid locals into the error throwing method
We'll get there eventually
Player had mentioned this a few weeks ago, right now local capture with
@Inject
is a liability whenever the target code changes.@ModifyVariable
has a more ideal local targeting system, but only works one at a time, and doesn't allow the target method to be cancelled if desired. This PR adds an injector which merges the two systems together into a more ergonomic one.Take this rather artificial scenario:
If I wanted to inspect the values of
e
andf
just before the return concatenation, I could do so with an@Inject
:This is fine if I want to inspect them, but if I want to change them things start getting difficult
Now that works if I know the code isn't threaded, otherwise my
capturedE
might not be the right value. It also only lets me changef
, if I want to changee
too...All getting a bit hairy now. Say I might want to change the return value of the method altogether, otherwise change the value of
e
andf
.Starting to look like a wobbling tower of maintainability issues. If another local is added before
e
for example, the@Inject
goes wrong and needs some help:Of course this is all a forced example, but you see people who need to do one part of these situations every so often.
The top most problem is local capture being brittle. Needing to capture all the locals before the one(s) you want is no fun when that means a dozen or more extra parameters to your handler. Nor is the accompanying local capture related crash when trying to update your mixin to a newer Minecraft version. What we need is something more like
@ModifyVariable
:That's all well and good, but how do I know which
@Local
I need to do. For that printing will give you a helping hand:Running that gets us the following in the log:
Now I know
e
is the firstString
(ordinal 0), andf
is the second. I could also use the LVT index, then they'd be 2 and 3. I could even do them by name (although for Minecraft you wouldn't want to).What if I wanted
d
? Well that would be ordinal 0 forlong
, or index 0, or named
. As there is only onelong
local however, like@ModifyVariable
, we can use an implicit declaration:This is mainly to save typing out situations where the only local/s of it's/their type is/are being used. If an implicit search is used where there are multiple types, an error will be thrown (based on the
behaviour
):Running that gets us the following in the log:
The errors try to be useful where they can, each local is being found according to rules so it's easier to say what went wrong compared to
@Inject
's local capture which just knows the handler signature isn't what it wants.Back to our scenario, how could we write that now?
One handler is best, no threading problems now. But the direct changes to
e
andf
, how are they propagated back? That's where the final trick comes in,@InjectWithLocals.Modify
. That acts as a marker for any local which the changes should be applied to the target method. Mixin magic takes hold to make this happen, generating a specialCallbackInfo(Returnable)
which takes the locals in just before anyreturn
in the handler, then applying them to the target (if the call isn't cancelled of course). This process means minimal overhead as there is no boxing/putting into an array, and if theCallback
is used anyway there will be no new objects created at all compared to a normal injector.To demonstrate if that description is a little too abstract:
As many (or as few) captured locals as desired can be annotated with
@Modify
, this means changes to locals which you don't want to carry over don't have to.This PR isn't quite done; the docs have to be finished on
@InjectWithLocals
, the AP remapping logic needs doing (i.e. copy what@Inject
does), and there's a bug or two to fix with when the handler is wrong, but I thought it best check this is what you imagined.