Jump to content

Get the value of a KSPField without knowing the object's name in the .cs file.


Recommended Posts

Let's say I know that I know these givens:

GIVENS:

p is a Part instance.

mod is a PartModule instance that is on that part.

"My Field A" is the guiName of a KSPField that the user can see in the rightclick menu for that part, and we know that "My Field A" came from the module mod (as opposed to from some other module that might be on part p).

RETRIEVE:

The value of the public member variable inside this mod that the KSPField called "My Field A" is actually attached to, assuming I cannot actually view the Csharp source file for the PartModule and I only know what I would know as an end-user - which is the guiName that appears on the rightclick menu for the part. I do NOT know (ahead of time) the actual member variable the KSPfield attribute is attached to.

The only solution I've seen looks *massively* slow - which is to perform the search backward, looking through *every single* member variable in the part module, to find which one happens to have a KSPfield attribute on it that fits the criteria given. There doesn't seem to be a way to go the other way around and find the KSPField first from PartModule.Fields, and then walk from there to the variable in question.

There is the method PartModule.GetValue(string fieldName), but I strongly fear that the fieldName in question is NOT the guiName, but is the csharp variable name.

WHY?

Because I'm trying to implement a feature for kOS where the intended audience includes people who aren't reading the Csharp code, but are reading the user-visible right-click menu. I want to give them the ability to query KSPfields of arbitrary modules, without them having to know anything they can't learn from the user experience (and thus why I want to use the guiName, not the csharp name for the variable.)

Link to comment
Share on other sites

Okay, I have not done this with Fields, but it works for actions so does this work if you swap them out?

I use this to pull all actions on a part


List<BaseAction> AllActions = new List<BaseAction>();
AllActions.AddRange(p.Actions);
foreach(PartModule pm in p.modules)
{
AllActions.AddRange(pm.Actions);
}

I also know there is a Fields list, can you use similar code to pull a list of all KSPfields that you could then query the guiName of? I'm assuming Fields have a similar class just like Actions have the BaseAction class for this to work.

I will state that partModule.GetValue is used to return the value of the field when you pass it the csharp variable name.

D.

Edited by Diazo
Link to comment
Share on other sites

I also know there is a Fields list, can you use similar code to pull a list of all KSPfields that you could then query the guiName of? I'm assuming Fields have a similar class just like Actions have the BaseAction class for this to work.

Sadly, PartModule.Fields is a list of KSPFields, which are Csharp Attributes, and as far as I can tell there is no way in Csharp to go from an Attribute TO the member variable is attached to. Only the opposite direction is supported - you can get the Attributes of a variable, but not the variable of an attribute.

Link to comment
Share on other sites

PartModule.Fields is not a list of KSPFields, it's a list of BaseFields. BaseField has a GetValue() function which probably does what you want. I'd guess something like this would work:


BaseField field = null;
foreach(BaseField f in mod.Fields) {
if(f.guiName == "My Field A") {
field = f;
break;
}
}

if(field != null) Debug.Log("value of the field is " + f.GetValue(mod));

Link to comment
Share on other sites

PartModule.Fields is not a list of KSPFields, it's a list of BaseFields. BaseField has a GetValue() function which probably does what you want. I'd guess something like this would work:


BaseField field = null;
foreach(BaseField f in mod.Fields) {
if(f.guiName == "My Field A") {
field = f;
break;
}
}

if(field != null) Debug.Log("value of the field is " + f.GetValue(mod));

Ah that does work. I was looking at BaseFields (plural) . GetValue(string), which takes the variable's name, not the KSPField's guiName as its argument so it's not useful to me.

It's still *basically* the same problem though. I'm walking the list to get to the thing, so I'll still probably want to cache the mapping once I find it the first time. It looks like an expensive operation to be re-doing each time someone accesses PART:MOD:FIELDNAME in the kOS code.

Edit: Thanks for the suggestion. It does work and it makes the code easier to read. (I had been doing essentially the same thing but more low-level, doing all the reflection calls myself. Given the messy stuff I had to do when I did that, I fear the BaseField.GetValue() might be doing the same expensive operations - walking the list of all member variables via reflection until it finds the one which has the Attribute 'f' on it.)

Edited by Steven Mading
Link to comment
Share on other sites

You can cache the reference to the BaseField if you want. But I doubt this is going to be a bottleneck.

That's not where I'm envisioning a bottleneck. I'm envisioning one secretly hidden inside BaseField.GetValue().

It's the mapping from guiName down to member variable that I'd want to cache, not the mapping from guiName down to BaseField.

In general, the act of going from Attribute to member variable is slow if you try to do it in your own reflection code, as you have to walk all the members to find the one that has the Attribute. Unless SQUAD themselves have done the caching I'm referring to internally inside BaseField (and they might have) they'd have the same problem. If only disassembly of the API wasn't expressly forbidden. Then I could find out the answer.

Edited by Steven Mading
Link to comment
Share on other sites

Almost certainly BaseField stores a reference to the FieldInfo for the field in question, and BaseField.GetValue() just calls FieldInfo.GetValue(). Presumably the list of BaseFields is created at startup by walking through the member variables looking for the [KSPField] attribute, and as each BaseField is created it is given the reference to the FieldInfo.

Link to comment
Share on other sites

Almost certainly BaseField stores a reference to the FieldInfo for the field in question, and BaseField.GetValue() just calls FieldInfo.GetValue().

It's impossible to know without doing the illegal, and disassembling the DLL to look at what the code for BaseField.GetValue() actually does. Maybe the class walked the member variables to find the reference just once up front and then stored it to use on each call to BaseField.GetValue(), or maybe it's walking the members to find it on the fly each time. You can't tell whether SQUAD felt the need to perform that optimization or not - it depends on whether they call BaseField.GetValue() lots and lots of times in loops in their own code. I know that the way *I* want to use it that's what I'd do, but I can't tell if they had the same use case and thus the same need for that optimization.

Link to comment
Share on other sites

This thread is quite old. Please consider starting a new thread rather than reviving this one.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...