#Reflection vs. Source Generators
1 messages ยท Page 1 of 1 (latest)
[Serializable]
public class LocalFloat
{
private FieldInfo FieldInfo => _fieldInfo ??= _getFieldInfo();
private FieldInfo _fieldInfo;
private Expression ControllerExpression => _controllerExpression ??= Expression.Constant(Controller);
private Expression _controllerExpression;
private Expression FieldExpression => _fieldExpression ??= Expression.Field(ControllerExpression, FieldInfo);
private Expression _fieldExpression;
[Tooltip("The controller that contains the float.")]
public MonoBehaviour Controller;
public string FieldName;
private Func<float> _getter;
private Func<float, float> _setter;
public float Get()
{
_getter ??= _createGetter();
return _getter();
}
public float Set(float value)
{
_setter ??= _createSetter();
return _setter(value);
}
private Func<float> _createGetter()
{
return Expression.Lambda<Func<float>>(FieldExpression).Compile();
}
private Func<float, float> _createSetter()
{
var param = Expression.Parameter(typeof(float), "newValue");
var expression = Expression.Assign(FieldExpression, param);
return Expression.Lambda<Func<float, float>>(expression, param).Compile();
}
private FieldInfo _getFieldInfo()
{
var type = Controller ? Controller.GetType() : throw new ArgumentException("No component selected.");
return type.GetField(FieldName, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) ?? throw new ArgumentException($"Unknown field {FieldName}.");
}
}```
tl;dr of the code:
It stores a reference to a float field in another component
And compiles a getter and setter for it
(It should be mentioned that there is a propertydrawer for this type not shown here)
I just don't see how source generators can accomplish something like this
what is your target to do with that?
It stores a reference to a float field?
hang on, ill show a slight demo
how does your IDE react to your funcky reflection?
does it find the getters and setters?
It doesn't need to
It compiles it into the delegates
Then I just call the delegates
Here's the usage
and what is the achievement ?
It integrates my code into the user's code which is unknown to me
So it basically allows the user to inject their controller's values into my tool
just make your game a serializer of json/xml
when you want your game to be modifyable
This isn't for a game
this is a system I'm selling on the asset store once it gets approved
so it allows the client to inject their values into the system
You don't have to do anything else - my code uses those values
The system itself is just built to remove boilerplate code in a typical combat system
The workflow of:
reflection is fast ok
get used to it
it's a 1 time thing to grab/generate the memberinfo/methodinfo
- Have damage values
- Have health values
- On collision, subtract damage from health
reflection is hard to read when you mix it with the game
we dont talk about generation
no it's not
timing
it can even be wrapped with a simple assignment to a delegate
In this case, I don't believe source generation can do the same thing
then use reflectino isntead of clean code oop
The invokation itself is even slower than native since because of type ambiguity
Can you explain that a bit? What type ambiguity? I'm lost
I've done profiles btw and there's little to no overhead
it has to check if the value is given
if its not there it cant decide what to call
What is it in this context?
Are you referring to the constant null checks?
Those are negligible
You can totally create this as a source generator
public void A(Box ob)
publi void A (NOBox ob)
xyz.A(null); <-- ambiguity conflict
the idea is to blend it within OOP ๐ as @narrow pelican did
I'm open to seeing how, but I'm not familiar enough with source generators to see how
he's using the wrapper as a field, if you've noticed
From what I understand, this would require a separate component for each class it references
Which simply is not viable
You are not adding any value here
No it does not
The added value is that I don't have to know about the field I'm referencing at the time of writing
I was replying to lyraxis
Did not say that
oh
I'm still confused about the ambiguity thing you mentioned
alright in that case, let's circle back to source generators
how could you do logic like this in a source generator?
and whats the problem of doing it with the OOP way ?
that's very OOP
I'm writing this as a tool for others to implement into their games
I don't know what code it will be interacting with
So I need reflection or source generation
It simply can't work without it
As code, yes. As logic, no
can be basically plugged into any component for anyone to work with
and can be instantiated and used by any other objects that may need it's functionality
It defers the value referenced to the end user (the other developer)
why do you not just let them subscribe with their actions?
Because it's not event-based
I would only complain how most of these methods are not static and once-per-runtime only lol
They're simply there to hold the runtime-compiled logic
Because each instance might refer to a different component or field
yea static.. with reflection.. wow
gj
lets go back and learn "how to create an object"
To give everyone more context, this is a system which handles combat logic, aimed at designers or devs with less experience. The purpose of the reflection here it to allow the user to select float fields from another component (that I don't know about) as damage and health values.
why do you not let them inherit from a dmgscript
which has those 2 values
just as a base class
and use that
Inheritance opens up room for roadblocks
a what
ECS > Inheritance in most cases
Sorry let me reword that
it's not an ECS issue
Composition > Inheritance in most cases
doesnt mean inheritance is bad
Also, my target audience is potentially people who don't know what inheritance is
if its necessary it has to be
The purpose of this discussion was to see if we can use source generators here
lul
better not make a base class but simply an interface. then you can even do simple dependency injection logic and presto
or that
that doesn't make it simpler though
Maybe on my end, but I can understand it fine
well for doing it via a source generator you could even manually write the string via StringBuilder
define write the string
and THAT's not OOP ๐
its fascinating how you all just ignore : SOLID principles, KISS, MVVM
it aint that hard to implement a fucking interface
the IDE tells you what to do
it'd take time
An interface can't implement collision logic
why do that when you just add a plug and play component to do that
I think this discussion has run its course
it never had one
? wrong
an interface can implement any logic
I was open to being converted to source generators, but I believe that my current approach is the best barring a possible source generator approach I don't know about
default methods ?
I guess a source generator would still need to use reflection huh
depends on language
sometimes yes, sometimes no
well if people would stop arguing about other things, maybe we could get back to source generators
true
it DOES NOT
I gave it some thought I couldn't see how
you need to get the fields of a target's type
which you can easily do from the syntax tree
..with reflection ๐
My current approach is incredibly simple for the user and only has overhead of one small compile to create getters and setters. The only possible improvement would be to move that compile from runtime to design time, aka to use a source generator instead
oh..
NO. from the syntax tree
oh.. yeah that makes sense
so how could I access a type's syntax tree? ๐
say, of my MonoBehaviour target
It's complicated, but I promise the syntax tree would give you anything reflection would in this context
I've written source generators before, but I don't think it fits this use case
well in that case it could become more performant, if you wrote methods via string builder
could even go a little wild on the if statements, wouldn't hurt
I'm going to need a concrete example
writing methods via string builder is fine and all, until it needs to be inside a property that can be in any monobehaviour, that needs to reference another monobehaviour's field
it absolutely does
through the source generator
was thinking of something like:
public MonoBehaviour target;
public string GetSourceCode() {
var sourceCode = "";
sourceCode += AddUsings();
sourceCode += AddClassDefinition();
sourceCode += AddProperties();
sourceCode += AddEOF();
return sourceCode;
}
... but with a StringBuilder
I know you believe that, but I'd like to see how so I can start to believe it
This is fine, but how does it allow me to reference code I don't yet know about?
well, you'd still have to generate it on the spot after you got the 'target'
i'll need some time
i'm not even on my pc rn
Even if you just give me a vague description, I can explore it myself
The problem I'm seeing with source generation is that each individual instance of the component uses different code internally, generated at runtime
So to replicate that in source generation, it would take outputting a different component for each instance
My approach stores the dynamic code as a delegate, but source generation doesn't have that luxury
It has to be concrete to get the benefits of source generation (no runtime overhead)
(leaving this channel for now, if anyone continues the conversation, please ping me)
repost so i can look at the code
[Serializable]
public class LocalFloat
{
private FieldInfo FieldInfo => _fieldInfo ??= _getFieldInfo();
private FieldInfo _fieldInfo;
private Expression ControllerExpression => _controllerExpression ??= Expression.Constant(Controller);
private Expression _controllerExpression;
private Expression FieldExpression => _fieldExpression ??= Expression.Field(ControllerExpression, FieldInfo);
private Expression _fieldExpression;
[Tooltip("The controller that contains the float.")]
public MonoBehaviour Controller;
public string FieldName;
private Func<float> _getter;
private Func<float, float> _setter;
public float Get()
{
_getter ??= _createGetter();
return _getter();
}
public float Set(float value)
{
_setter ??= _createSetter();
return _setter(value);
}
private Func<float> _createGetter()
{
return Expression.Lambda<Func<float>>(FieldExpression).Compile();
}
private Func<float, float> _createSetter()
{
var param = Expression.Parameter(typeof(float), "newValue");
var expression = Expression.Assign(FieldExpression, param);
return Expression.Lambda<Func<float, float>>(expression, param).Compile();
}
private FieldInfo _getFieldInfo()
{
var type = Controller ? Controller.GetType() : throw new ArgumentException("No component selected.");
return type.GetField(FieldName, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) ?? throw new ArgumentException($"Unknown field {FieldName}.");
}
}
@narrow pelican in your gif there's a dropdown, here there is only string fieldname
^
It finds all valid floats in the class and provides a dropdown to select one
ah. then why are you ?? throw new ArgumentException($"Unknown field {FieldName}."); on the last line?
but then it doesn't show up in the dropdown anymore either
But it will still be serialized as the old value
Or if the user doesn't select a value
ok