0

I'm using IMGUI in Unity to make a custom variable debugger which allows me to send it varables of different primitive types and it will draw them on screen without much fuss. (by fuss I mean writing an OnGUI each time I want to check what a variable is doing, or hooking something up with the canvas system.) I know that obviously I can just look at these things in the inspector but with this system I can a) quickly add and remove things, and b) do it at runtime without having to do a development build.

I have a function working that takes in different types of data and does things with it based on its type. The issue is that it only seems to work if I pass the variable by value, and not by ref.

So for example if I send it an int or a float by value it works just fine but when I send it by ref I get this error:

error: CS1503: Argument 1: cannot convert from 'ref int' to 'ref object'

This occurs when AddDebugEntry is called (whole code is at the bottom of this question):

public void AddDebugEntry(ref object value, string name, DebugDrawStyle style = DebugDrawStyle.Label, float min = 0, float max = 0)
{
    DebugEntry newEntry = new DebugEntry(ref value, name, style, min, max);
    DebugEntries.Add(newEntry);
}

I beleive this may be something to do with Boxing: Is int (Int32) considered an object in .NET or a primitive (not int?)?

But I'm not sure how to get around it :(

using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using UnityEngine;

public class DebugEntry
{
    public object m_Value;
    public string m_name;
    public DebugDrawStyle m_Style;
    public float m_min;
    public float m_max;

    public DebugEntry(ref object mValue, string mName, DebugDrawStyle mStyle, float mMin, float mMax)
    {
        m_Value = mValue;
        m_name = mName;
        m_Style = mStyle;
        m_min = mMin;
        m_max = mMax;
    }
}

public enum DebugDrawStyle { Label, Slider }

public class DebugTool : MonoBehaviour
{
    #region -- Variable Declaration ------------------------------------------------------------------------------------

    public Project currentProject;
    public Vector2 DebugPosition;
    public Vector2 DebugSize;
    private int index = 0;

    [SerializeField]
    private List<DebugEntry> DebugEntries = new List<DebugEntry>();

    #endregion -- End Variable Declaration -----------------------------------------------------------------------------

    #region -- Initialisation ------------------------------------------------------------------------------------------

    // Start is called before the first frame update
    void Start()
    {
        currentProject.DebuggingTool = this;
    }

    #endregion -- End Initialisation -----------------------------------------------------------------------------------

    #region -- Adding / Removing Entries -------------------------------------------------------------------------------

    public void AddDebugEntry(ref object value, string name, DebugDrawStyle style = DebugDrawStyle.Label, float min = 0, float max = 0)
    {
        DebugEntry newEntry = new DebugEntry(ref value, name, style, min, max);
        DebugEntries.Add(newEntry);
    }

    public void RemoveDebugEntry(string name)
    {
        for (int i = 0; i < DebugEntries.Count; i++)
        {
            if (DebugEntries[i].m_name.GetHashCode() == name.GetHashCode())
            {
                DebugEntries.Remove(DebugEntries[i]);
            }
        }
    }

    #endregion -- End Adding / Removing Entries ------------------------------------------------------------------------

    #region -- GUI Updates ---------------------------------------------------------------------------------------------

    private void OnGUI()
    {
        index = 0;
        foreach (var entry in DebugEntries)
        {
            index++;
            switch (Type.GetTypeCode(entry.m_Value.GetType()))
            {
                case TypeCode.Int16:
                    switch (entry.m_Style)
                    {
                        case DebugDrawStyle.Label:
                            DrawLabel(entry);
                            break;
                        case DebugDrawStyle.Slider:
                            DrawSlider(entry);
                            break;
                        default:
                            throw new ArgumentOutOfRangeException();
                    }
                    break;
                case TypeCode.Int32:
                    switch (entry.m_Style)
                    {
                        case DebugDrawStyle.Label:
                            DrawLabel(entry);
                            break;
                        case DebugDrawStyle.Slider:
                            DrawSlider(entry);
                            break;
                        default:
                            throw new ArgumentOutOfRangeException();
                    }
                    break;
                case TypeCode.Int64:
                    switch (entry.m_Style)
                    {
                        case DebugDrawStyle.Label:
                            DrawLabel(entry);
                            break;
                        case DebugDrawStyle.Slider:
                            DrawSlider(entry);
                            break;
                        default:
                            throw new ArgumentOutOfRangeException();
                    }
                    break;
                case TypeCode.Decimal:
                    switch (entry.m_Style)
                    {
                        case DebugDrawStyle.Label:
                            DrawLabel(entry);
                            break;
                        case DebugDrawStyle.Slider:
                            DrawSlider(entry);
                            break;
                        default:
                            throw new ArgumentOutOfRangeException();
                    }
                    break;
                case TypeCode.Double:
                    switch (entry.m_Style)
                    {
                        case DebugDrawStyle.Label:
                            DrawLabel(entry);
                            break;
                        case DebugDrawStyle.Slider:
                            DrawSlider(entry);
                            break;
                        default:
                            throw new ArgumentOutOfRangeException();
                    }
                    break;
                case TypeCode.Single:
                    switch (entry.m_Style)
                    {
                        case DebugDrawStyle.Label:
                            DrawLabel(entry);
                            break;
                        case DebugDrawStyle.Slider:
                            DrawSlider(entry);
                            break;
                        default:
                            throw new ArgumentOutOfRangeException();
                    }
                    break;
                case TypeCode.Boolean:
                    DrawLabel(entry);
                    break;
                case TypeCode.String: 
                    DrawLabel(entry);
                    break;
                default:
                    Debug.Log("Type not found");
                    throw new ArgumentOutOfRangeException();
            }
        }
    }

    #endregion -- End GUI Updates --------------------------------------------------------------------------------------

    #region -- GUI Draw Functions --------------------------------------------------------------------------------------


    void DrawLabel(DebugEntry entry)
    {
        GUI.Label(new Rect(DebugPosition.x, DebugPosition.y + (DebugSize.y * index), DebugSize.x, DebugSize.y), entry.m_name.ToString());
        index++;
        GUI.Label(new Rect(DebugPosition.x, DebugPosition.y + (DebugSize.y * index), DebugSize.x, DebugSize.y), entry.m_Value.ToString());
    }

    void DrawSlider(DebugEntry entry)
    {
        GUI.Label(new Rect(DebugPosition.x, DebugPosition.y + (DebugSize.y * index), DebugSize.x, DebugSize.y), entry.m_name.ToString());
        index++;
        GUI.HorizontalSlider(new Rect(DebugPosition.x, DebugPosition.y + (DebugSize.y * index), DebugSize.x, DebugSize.y),
            (float) entry.m_Value, entry.m_min, entry.m_max);
    }

    #endregion -- End GUI Draw Functions -------------------------------------------------------------------------------

}
Ruzihm
  • 19,749
  • 5
  • 36
  • 48
  • 3
    Are you actually assigning to `value` within `AddDebugEntry`? If not you don't need to use `ref`. If you are you can just create an intermediate variable: `object o = myObj; AddDebugEntry(ref o)`. This isn't due to boxing though, just subtyping. – Lee Aug 01 '19 at 16:49
  • Think about it. If you call `AddDebugEntry` with a `ref someInteger`, then inside that function, set the value to an object of another type, when the function exits, your reference to a boxed integer is now a reference to an object of a different type - boom, bad things happen. If you check, the only thing you can pass to your function is a `ref` to a variable of type `object` – Flydog57 Aug 01 '19 at 16:52
  • Thanks. So if I assign the int to an intermediate variable then I have the same issue as if I'd passed it by value. i.e. when I update myObj, the one that has been added to AddDebugEntry() doesn't update as it's just holding the value that it was passed to it. What I'm trying to do is watch the variable. – Rob Homewood Aug 01 '19 at 17:18

1 Answers1

1

Looking at your code, you never assign to the reference value (or m_Value), so there is no reason to have it be a ref parameter in DebugEntry/AddDebugEntry.

Instead you can give it a Func<object> delegate that returns the current value in question when it is called, and call it whenever you need a fresh value:

public class DebugEntry
{
    public Func<object> m_getValue;
    public Type m_Type;
...

public DebugEntry(Func<object> getValue, string mName, DebugDrawStyle mStyle, float mMin, float mMax)
{
    m_getValue = getValue;
    m_Type = getValue().GetType();
    m_name = mName;
    m_Style = mStyle;
    m_min = mMin;
    m_max = mMax;
}

...

public void AddDebugEntry(Func<object> getValue, string name, DebugDrawStyle style = DebugDrawStyle.Label, float min = 0, float max = 0)
{
    DebugEntry newEntry = new DebugEntry(getValue, name, style, min, max);
    DebugEntries.Add(newEntry);
}

...

switch (Type.GetTypeCode(entry.m_Type))
{

...

void DrawLabel(DebugEntry entry)
{

    GUI.Label(new Rect(DebugPosition.x, DebugPosition.y + (DebugSize.y * index), DebugSize.x, DebugSize.y), entry.m_name.ToString());
    index++;
    GUI.Label(new Rect(DebugPosition.x, DebugPosition.y + (DebugSize.y * index), DebugSize.x, DebugSize.y), entry.m_getValue().ToString());
}

void DrawSlider(DebugEntry entry)
{
    GUI.Label(new Rect(DebugPosition.x, DebugPosition.y + (DebugSize.y * index), DebugSize.x, DebugSize.y), entry.m_name.ToString());
    index++;
    GUI.HorizontalSlider(new Rect(DebugPosition.x, DebugPosition.y + (DebugSize.y * index), DebugSize.x, DebugSize.y),
        (float) entry.m_getValue(), entry.m_min, entry.m_max);
}

And then when you call AddDebugEntry:

// add debug item for my x position
myDebugTool.AddDebugEntry( () => transform.position.x,
                           "My X Position",
                           min: -50f,
                           max: 50f);
Ruzihm
  • 19,749
  • 5
  • 36
  • 48