1

I'm working on an OpenGL game engine as a passion project and im using the UI library "Dear ImGUI" to display and debug values similar to Unity's inspector. I'm having trouble thinking of a way to get a reference to the field I'm trying to debug.

Here is the code i have got at the moment but the problem is that its not a reference to the actual field, its just a reference to a local variable (value) and as such, it doesnt actually set the variable that I debug in the GUI. From what i've been able to see, there is no clean cut way to get the reference.

protected override void OnImGUIRender(FrameEventArgs e)
        {
            ImGui.PushFont(font);

            ImGui.ShowDemoWindow();

            //Scene Window
            {
                ImGui.Begin("Scene");

                ImGui.BeginTabBar("1");
                ImGui.BeginTabItem("Heirachy");

                if (ImGui.TreeNode("Scene"))
                {
                    foreach (var obj in (LayerStack.Layers.FirstOrDefault(x => x.GetType() == typeof(GameLayer)) as GameLayer).scene.GameObjects)
                    {
                        if (ImGui.Selectable(obj.Name))
                            selectedGameObject = obj;
                    }
                    ImGui.TreePop();
                }

                ImGui.EndTabItem();
                ImGui.EndTabBar();

                ImGui.Dummy(new System.Numerics.Vector2(0, 40));

                ImGui.BeginTabBar("Properties");

                ImGui.BeginTabItem("Properties");


                if (selectedGameObject != null)
                {
                    ImGui.Text(selectedGameObject.Name);

                    foreach (var c in selectedGameObject.components)
                    {
                        if (ImGui.TreeNode(c.GetType().Name))
                        {
                            var fields = c.GetType().GetFields();
                            foreach (var field in fields)
                            {
                                ImGui.DragFloat3(field.Name, field.refValue); <-- Focused line
                            }

                            ImGui.TreePop();
                        }
                    }
                }
                else
                    ImGui.Text("No Currently Selected Gameobject");

                ImGui.EndTabItem();

                ImGui.EndTabBar();

                ImGui.End();

                ImGui.Begin("Debug");

                ImGui.Text("Gameobjects: " + LayerStack.GameObjectCount);

                ImGui.End();
            }

            base.OnImGUIRender(e);
        }

Is there any way I can get a reference to the actual field that is being looped over in the foreach? In my head I would imagine it looking something like this:

ImGui.DragFloat3(field.Name, field.Reference);

Thanks!

Edit:

I found my personal solution to be in the code below but massive thanks to @pinkfloydx33 for helping me to understand the problem better and provide a high quality answer.

var fields = c.GetType().GetFields();
foreach (var field in fields)
{
    var value = (field.FieldType)field.GetValue(c);
    ImGui.DragFloat3(field.Name, field.refValue);
    field.SetValue(c, value);
}
Beckam White
  • 143
  • 10
  • Is the type of `c` a class or a struct? I assume it's probably a struct? Do you need to perform these actions just over the type of `c` or generically for any type? Also could you show what the method definition for this method looks like (how is it declared?) – pinkfloydx33 Jun 07 '20 at 10:16
  • Anyways... there's no built-in syntax. However you could accomplish this using Dynamic Methods or Expression Trees. I have a solution using the latter that's built on the assumption that A. the methods you call always have two parameters (name and `ref` value) and B. that you know the static type of `c` -- B is important if `c` is a struct. If it won't be a struct it makes things slightly easier. I have a method that can deal with either but it'd be good if you could clarify those two points and my question above – pinkfloydx33 Jun 07 '20 at 10:58
  • `c` is an inherited class from a list of type `Component` which is its base class. The type of c can be any of `Component`'s inherited classes. – Beckam White Jun 07 '20 at 11:02
  • BTW the reason you have the problem is that the fields you are trying to change are themselves structs, which you end up acting upon copies of. Can you please edit into the question the method declaration for the above--ie. `public void MyMethod(Component c)` [or however you call it]? And could you verify whether the methods you want to call (`ImGui.DragFloat3`) will always have *two parameters* only? – pinkfloydx33 Jun 07 '20 at 11:11
  • @pinkfloydx33 ive editied the method to show the full method. `ImGui.DragFloat()` takes in a `string` as the name, `ref System.Numerics.Vector3` for the object position. The ref is so the GUI can update and display the value every frame. `selectedGameobject.components` is a list of type `Component` which contains all of the objects components which can differ in type. – Beckam White Jun 07 '20 at 11:24

1 Answers1

1

Part of the problem you are experiencing is due to the fact those field values are structs. You only end up operating on copies of them. But we can get around this by building a delegate that accepts as its only parameter an object of the containing type (the type who's fields you are inspecting). This delegate will in turn call the method you are trying to invoke, passing the object's field under the hood with ref.

This solution below assumes that the methods you want to invoke (ImGui.Drag3, ImGui.Checkbox) always have two parameters -- string name and ref T value. In other words, a hypothetical method that operated on int fields would have to be declared as ImGui.DoSomethingToInt(string name, ref int value)

using System.Linq.Expressions;
using System.Reflection;
using System.Collection.Generic;

public static class ComponentHelpers
{
    // helper function to get the MethodInfo for the method we want to call
    private static MethodInfo GetStaticMethod(Expression<Action> expression)
    {
        if (expression.Body is MethodCallExpression body && body.Method.IsStatic)
            return body.Method;

        throw new InvalidOperationException("Expression must represent a static method");
    }

    // helper field we can use in calls to GetStaticMethod
    private static class Ref<T>
    {
        public static T Value;
    }

    // Define which method we want to call based on the field's type
    // each of these methods must take 2 parameters (string + ref T)
    private static readonly Dictionary<Type, MethodInfo> Methods = new Dictionary<Type, MethodInfo>
    {
        [typeof(Vector3)] = GetStaticMethod(() => ImGui.Drag3(default, ref Ref<Vector3>.Value)),
        [typeof(bool)] = GetStaticMethod(() => ImGui.Checkbox(default, ref Ref<bool>.Value))
    };

    // store the compiled delegates so that we only build/compile them once
    private static readonly Dictionary<FieldInfo, Action<Component>> Delegates = new Dictionary<FieldInfo, Action<Component>>();

    // this method will either build us a delegate, return one we've already built
    // or will return null if we have not defined a method for the specific type
    public static Action<Component> GetActionFor(FieldInfo field)
    {
        if (!Methods.TryGetValue(field.FieldType, out var method))
            return null;

        if (Delegates.TryGetValue(field, out var del))
            return del;

        // type the parameter as the base class Component
        var param = Expression.Parameter(typeof(Component), "x");

        var lambda = Expression.Lambda<Action<Component>>(
            Expression.Call(
                method,
                // Pass the field's name as the first parameter
                Expression.Constant(field.Name, typeof(string)),
                // pass the field as the second parameter
                Expression.Field(
                    // cast to the actual type so we can access fields of inherited types
                    Expression.Convert(param, field.ReflectedType),
                    field
                )
            ),
            param
        );

        return Delegates[field] = lambda.Compile();

    }
}

Once we've done that, we can update your main loop to look like the following:

var fields = c.GetType().GetFields();
foreach (var field in fields)
{
    var action = ComponentHelpers.GetActionFor(field);
    if (action == null) // no method defined
       continue;
    // invoke the function passing in the object itself
    action(c); 
}
pinkfloydx33
  • 11,863
  • 3
  • 46
  • 63
  • I really appreciate your help. You have helped me to understand my own problem. The way I personally solved this was by simply creating a local reference to the variable and then updating the actual variable after the GUI call. (Edited in question) – Beckam White Jun 08 '20 at 03:38