Announcement

Collapse
No announcement yet.

Is it possible to infer parameter types of a UFUNCTION in code

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

    Is it possible to infer parameter types of a UFUNCTION in code

    I have taken a dive into the reflection system source code alongside supplementary api references. I'd like to create certain custom editor tools, property viewers, or full plugins that rely on a key idea - serializing information about a UFUNCTION. Specifically, I am looking for the function name, return type, and parameter list.

    Here is what I'd like to verify:

    Based on the UFunction class, there is a property called "FirstPropertyToInit" - the description states: "Pointer to first local struct property in this UFunction that contains defaults".
    Can someone verify if this translates to the first parameter this function has - how does UFunction successfully serialize its signature in BP otherwise?

    The UProperty class has enough information for me to infer the function signature if the above is true. It would be even more helpful if someone attempted a similar problem; any recommendations and patterns are immensely helpful.
    The end result of what I'm trying to achieve is to be able to show the list of functions an object has - and depending on the signature, certain fields will be created (textbox, checkbox, numeric input) so that the user can invoke the function using these design time parameters. The idea is to bind functions to certain events during the DESIGN of the level - hence, a blueprint scripting workflow does not yield the correct results because:
    1. It assumes I know which objects and functions I need to bind before designing the level
    2. It forces that same behavior on all instances of the blueprint instead of being flexible on a per-instance level.

    #2
    You can create a UFunction iterator on any owner UObject;
    Once you have a pointer to the target UFunction it's possible to create a new "graph" object copying the exact signature of the function you've found.

    I've done that for my "Finite State Machine" plugin to auto generate callback Function Graphs on target Blueprint a user presses a "+ Functions" I added to Inspector. That helps dev quickly generate tens of functions with pre-determined signatures (including local variables, input, output, etc) from single button click.

    About serialization, UFunctions are UObjects, you can use Serialize() method on them.
    | Savior | USQLite | FSM | Object Pool | Sound Occlusion | Property Transfer | Magic Nodes | MORE |

    Comment


      #3
      Originally posted by BrUnO XaVIeR View Post
      You can create a UFunction iterator on any owner UObject;
      Once you have a pointer to the target UFunction it's possible to create a new "graph" object copying the exact signature of the function you've found.

      I've done that for my "Finite State Machine" plugin to auto generate callback Function Graphs on target Blueprint a user presses a "+ Functions" I added to Inspector. That helps dev quickly generate tens of functions with pre-determined signatures (including local variables, input, output, etc) from single button click.

      About serialization, UFunctions are UObjects, you can use Serialize() method on them.
      Hi, thanks for the response. I'd like to learn more about what you mean by a "graph" object as I have difficulty finding any resemblance of that. How do I copy the exact signature of the function I found with the iterator - because that seems to be the point of the question - I see no methods to query that. I would prefer saving you the trouble of detailing this, so if you can direct me to certain parts of the documentation or source code, that could be just as helpful!

      The closest I can find is the IsSignatureCompatibleWith method, but that assumes I know the function signature beforehand. The workflow I am trying to achieve is - the class is responsible for exposing certain functions that should appear in the property editor, and for that, the property editor needs to be able to determine the signature of the function to display it properly.

      For example, if I decide to expose a void SomeFunction(int, string), the property editor will display a list of functions - in this case, one of the entries being

      SomeFunc [numericBox] [Textbox]

      The user then sets these parameters, and an invocation to the function will use these parameters (which is another problem in its own rights). As far as I am concerned, I think assuming this feature will only handle void functions is accurate; otherwise it veers off to the side of being "unnecessarily complex".

      Comment


        #4
        Take a look at FBlueprintEditorUtils:
        https://api.unrealengine.com/INT/API...ils/index.html

        UEdGraph:
        https://api.unrealengine.com/INT/API...aph/index.html


        Some simple quick example:
        Code:
        const auto &BP = GetClassBlueprint(OBJ);
        
        UEdGraph* Graph = FBlueprintEditorUtils::CreateNewGraph(BP,*NID_OnBegin,UEdGraph::StaticClass(),UEdGraphSchema_K2::StaticClass());
        
        FBlueprintEditorUtils::AddFunctionGraph(BP,Graph,false,OBJ->FindFunctionChecked(TEXT("FSM_BeginArgs")));
        
        
        TArray<UK2Node_FunctionEntry*> UK2_Nodes;
        Graph->GetNodesOfClass(UK2_Nodes);
        
        for (const auto &Function : UK2_Nodes) {
            Function->GetNodeTitle(ENodeTitleType::MenuTitle);
            //... Read Function properties here
        }
        | Savior | USQLite | FSM | Object Pool | Sound Occlusion | Property Transfer | Magic Nodes | MORE |

        Comment


          #5
          It's actually pretty easy to achieve what you want.
          You can get the parameters like this:
          Code:
          // With some UFunction* Func
          for (auto It = TFieldIterator< UProperty >(Func); It; ++It)
          {
            auto Property = *It;
            // Call Property->HasAnyPropertyFlags(...) to determine if return value, input/ouput param, etc.
          }
          And to call the function, use UObject::ProcessEvent. If you search the engine code you should find a couple of uses showing how you can pass parameters to it, though you'd have to do a bit of low level manipulation with the property types/sizes in order to do so without knowing at compile time what the signature is.

          Comment


            #6
            Originally posted by kamrann View Post
            It's actually pretty easy to achieve what you want.
            You can get the parameters like this:
            Code:
            // With some UFunction* Func
            for (auto It = TFieldIterator< UProperty >(Func); It; ++It)
            {
            auto Property = *It;
            // Call Property->HasAnyPropertyFlags(...) to determine if return value, input/ouput param, etc.
            }
            And to call the function, use UObject::ProcessEvent. If you search the engine code you should find a couple of uses showing how you can pass parameters to it, though you'd have to do a bit of low level manipulation with the property types/sizes in order to do so without knowing at compile time what the signature is.
            That is so convenient! As for interpreting the type of the parameters of the function, reading the documentation - it seems one way I'll be able to do that is by casting the UProperty to an UEnumProperty, UObjectProperty, UNumericProperty, and so on. Is this the wrong way to go about it; perhaps there is a more explicit and direct way?

            If this approach is valid, then in the case of UObjectProperty, there is the method GetObjectPropertyValue which appears to return the object reference represented by the property. If that is the case, then using the Object's StaticClass method should return the specific sub-class of UObject if applicable - which I can later use to display properties in the editor.

            The method however expects an argument void* PropertyValueAddress. I imagined a property encapsulates one variable or object - why would I need to specify a specific address? Where does it come from?

            Comment


              #7
              Yeah the casting approach is correct.
              But properties are meta-level, they don't represent any particular instance. You generally combine a property along with the address of an instance of an object/struct on which that property resides. Then you can manipulate the value.

              In the case of a property representing a function parameter, there is no instance, so you can't call GetObjectPropertyValue. What you can do, is if you successfully cast to UObjectPropertyBase for example, that has a member PropertyClass (I think) which tells you the base class of objects compatible with that property. So in theory, a pointer to any object derived from that class can be passed for that parameter.

              Thinking a bit more, if you really want to be able to expose a UI for specifying arbitrary arguments to functions not known at compile time, it's going to involve creating temporary instances of properties and such. It's certainly doable, but it's going to need a pretty in depth knowledge of the reflection system. There's a lot to learn!

              Comment


                #8
                Oh so you want to avoid blueprint compiler?
                I think you would end up recreating a lot functionality BP Compiler already does for you through UEdGraphs
                | Savior | USQLite | FSM | Object Pool | Sound Occlusion | Property Transfer | Magic Nodes | MORE |

                Comment


                  #9
                  Originally posted by BrUnO XaVIeR View Post
                  Oh so you want to avoid blueprint compiler?
                  I think you would end up recreating a lot functionality BP Compiler already does for you through UEdGraphs
                  It will take me time to decide mainly because it is the first time I am exposed to the classes you showed me. When I had a quick read over the class hierarchy for the nodes, it almost feels like my ultimate goal is to inspect the UK2Node_CallFunction class (I didn't find the FunctionEntry node you pointed out yet) and then get my signature information from the pins. What I am mainly struggling with is lines 2 and 3 in your example -- what does the notion of creating a graph mean. Is the Function Graph the mechanism used to dictate the UFUNCTIONs associated with certain blueprint classes?

                  Originally posted by kamrann View Post
                  Yeah the casting approach is correct.
                  But properties are meta-level, they don't represent any particular instance. You generally combine a property along with the address of an instance of an object/struct on which that property resides. Then you can manipulate the value.

                  In the case of a property representing a function parameter, there is no instance, so you can't call GetObjectPropertyValue. What you can do, is if you successfully cast to UObjectPropertyBase for example, that has a member PropertyClass (I think) which tells you the base class of objects compatible with that property. So in theory, a pointer to any object derived from that class can be passed for that parameter.

                  Thinking a bit more, if you really want to be able to expose a UI for specifying arbitrary arguments to functions not known at compile time, it's going to involve creating temporary instances of properties and such. It's certainly doable, but it's going to need a pretty in depth knowledge of the reflection system. There's a lot to learn!
                  So if the value associated with a property is as low level as passing in void pointers, I would assume that's portion of the code none of us should be touching? This also means the UProperty class is mainly responsible for associating with a certain data in memory, which is what these Clear, Set, and Copy data methods are for. The ultimate question I am trying to reach here is, which aspect of the engine is responsible for doing these assignments; and would someone ever want to manipulate or access data values in this class.

                  Comment


                    #10
                    I use the stuff all the time. But yeah, it's low level and horrible, that's just the UE4 codebase for you.

                    You'll find disparate places all over with similar/duplicated code for doing certain jobs. Some can only be used in certain contexts, others are legacy but haven't been brought up to date, etc. Property handles are a nicer interface, but they're pretty much restricted to detail customizations and won't help you with what you want since you need to work with function parameter properties, not true properties.

                    Comment

                    Working...
                    X