- Home /
Unsafe code with Unity
Does Unity compile unsafe code. The reason I am asking is not for performance issues. I'm trying to implement something similar to AngryAnts CopyInspector but work on single values instead of all the properties which is looking in possible at the moment. I can't do reference comparisons because int float double ect get boxed. The only way I can see a way of doing what I want is to do pointer comparisons. I know this is unsafe but the memory locations should be the same at the point of comparison.
Edit: Thought I may aswell post my code. Its nothing special. The SaveValue methods are where I'm trying to implement my saving functionality.
CompoundGUIElement
/// <summary>
/// Base Class for CompoundGUIElements
/// </summary>
/// <typeparam name="T"></typeparam>
[System.Serializable]
public class CompoundGUIElement<T> : DraggableElement
{
#region ENUMS
/// <summary>
/// enum for specifying placement of GUI Elements within a CompoundGUIElement
/// </summary>
public enum CompoundGUIPlacement
{
Top,
Bottom,
Left,
Right
}
#endregion
#region PRIVATE MEMBERS
private T _initialValue;
private T _tempValue;
private T _value;
private Boolean _showSetButton;
private Boolean _showResetButton;
private CompoundGUIPlacement _setButtonPlacement = CompoundGUIPlacement.Left;
private CompoundGUIPlacement _resetButtonPlacement = CompoundGUIPlacement.Left;
private Boolean _setIsAppending = false;
private Boolean _saveIsAppending = false;
private Color NORMAL_TINT_COLOR = Color.white;
private Color SET_APPENDING_TINT_COLOR = Color.red;
private Color SAVE_APPENDING_TINT_COLOR = Color.green;
#region DRAGGABLE ELEMENT ATTRIBUTE IMPLEMENTATIONS
private Rect _dragRect;
private Dictionary<string, System.Object> _objectReferences;
#endregion
#endregion
#region PROPERTIES
/// <summary>
/// The value defined for the Element upon Initialization.
/// </summary>
public T InitialValue { get { return _initialValue; } }
/// <summary>
/// The value of the object before it is Set.
/// </summary>
public T TempValue { get { return _tempValue; } }
/// <summary>
/// The definate value of the Element.
/// </summary>
public T Value { get { return _value; } }
/// <summary>
/// Defines whether the set button should be displayed. The value can still be
/// set by using the return key when the Element is in focus.
/// </summary>
public Boolean ShowSetButton { get { return _showSetButton; } set { _showSetButton = value; } }
/// <summary>
/// Defines whether the Reset Button should be displayed.
/// </summary>
public Boolean ShowResetButton { get { return _showResetButton; } set { _showResetButton = value; } }
/// <summary>
/// Defines the placement of the Set Button. Use CompoundGUIPlacement instead of a
/// string for constistency.
/// </summary>
public CompoundGUIPlacement SetButtonPlacement { get { return _setButtonPlacement; } set { _setButtonPlacement = value; } }
/// <summary>
/// Defines the placement of the Reset Button. Use CompoundGUIPlacement instead of a
/// string for consistency.
/// </summary>
public CompoundGUIPlacement ResetButtonPlacement { get { return _resetButtonPlacement; } set { _resetButtonPlacement = value; } }
/// <summary>
/// Defines whether the Elements value is awaiting the user to set the value.
/// </summary>
public Boolean SetIsAppending { get { return _setIsAppending; } }
#region DRAGGABLE ELEMENT PROPERTY IMPLEMENTATIONS
/// <summary>
/// The Rectangle that defines where the Element responds to
/// Drag Events from the input device.
/// </summary>
public Rect DragRect
{
get { return _dragRect; }
set { _dragRect = value; }
}
/// <summary>
/// The array of object references that are being dragged.
/// </summary>
public Dictionary<string, System.Object> ObjectReferences
{
get { return _objectReferences; }
set { _objectReferences = value; }
}
#endregion
#endregion
#region DELEGATES & EVENTS
/// <summary>
/// Delegate Signature for CompoundGUI Events.
/// </summary>
/// <param name="e"></param>
public delegate void CompoundGUIEventDelegate(CompoundGUIEventArgs e);
/// <summary>
/// The event called when the Element Value is Set.
/// </summary>
public event CompoundGUIEventDelegate OnSetEventHandler;
/// <summary>
/// The event call when the Element Value is Reset.
/// </summary>
public event CompoundGUIEventDelegate OnResetEventHandler;
/// <summary>
/// The event called when the Element Value is Reverted back to the TempValue.
/// </summary>
public event CompoundGUIEventDelegate OnRevertEventHandler;
#endregion
#region INITIALIZATION
/// <summary>
/// Initializes the CompoundGUIElement. Should be called after instantiation or in Awake.
/// </summary>
/// <param name="value"></param>
public void Init(T value)
{
Init(value, true, CompoundGUIPlacement.Left);
}
/// <summary>
/// Initializes the CompoundGUIElement. Should be called after instantiation or in Awake.
/// </summary>
/// <param name="value"></param>
/// <param name="showSetButton"></param>
public void Init(T value, Boolean showSetButton)
{
Init(value, showSetButton, CompoundGUIPlacement.Left);
}
/// <summary>
/// Initializes the CompoundGUIElement. Should be called after instantiation or in Awake.
/// </summary>
/// <param name="value"></param>
/// <param name="setButtonPlacement"></param>
public void Init(T value, CompoundGUIPlacement setButtonPlacement)
{
Init(value, true, setButtonPlacement);
}
/// <summary>
/// Initializes the CompoundGUIElement. Should be called after instantiation or in Awake.
/// </summary>
/// <param name="value"></param>
/// <param name="showSetButton"></param>
/// <param name="setButtonPlacement"></param>
public void Init(T value, Boolean showSetButton, CompoundGUIPlacement setButtonPlacement)
{
_initialValue = value;
_setButtonPlacement = setButtonPlacement;
_showSetButton = showSetButton;
_tempValue = value;
}
#endregion
#region CUSTOM METHODS
protected bool CheckValueChanged()
{
return !Equals(_tempValue, _value);
}
/// <summary>
/// Sets and stores the Value stored in TempValue.
/// </summary>
protected virtual void SetValue()
{
_value = _tempValue;
_setIsAppending = false;
if (OnSetEventHandler != null)
OnSetEventHandler(new CompoundGUIEventArgs());
}
/// <summary>
/// Sets the Temp Value by using regular Unity GUI calls. This must be called within
/// OnGUI
/// </summary>
public void DrawGUI()
{
if (CheckValueChanged() == true)
{
_setIsAppending = true;
}
//create gui elements here
switch (_setButtonPlacement)
{
case CompoundGUIPlacement.Top:
{
GUILayout.BeginVertical("Toolbar", GUILayout.ExpandWidth(true));
DrawResetButton();
DrawSetButton();
DrawCustomGUI();
if (EditorApplication.isPlaying)
DrawSaveButton();
GUILayout.EndVertical();
break;
}
case CompoundGUIPlacement.Bottom:
{
GUILayout.BeginVertical("Toolbar", GUILayout.ExpandWidth(true));
DrawCustomGUI();
DrawResetButton();
DrawSetButton();
if (EditorApplication.isPlaying)
DrawSaveButton();
GUILayout.EndVertical();
break;
}
case CompoundGUIPlacement.Left:
{
GUILayout.BeginHorizontal("Toolbar", GUILayout.ExpandWidth(true));
DrawResetButton();
DrawSetButton();
if (EditorApplication.isPlaying)
DrawSaveButton();
DrawCustomGUI();
GUILayout.EndHorizontal();
break;
}
case CompoundGUIPlacement.Right:
{
GUILayout.BeginHorizontal("Toolbar", GUILayout.ExpandWidth(true));
DrawCustomGUI();
DrawResetButton();
DrawSetButton();
if (EditorApplication.isPlaying)
DrawSaveButton();
GUILayout.EndHorizontal();
break;
}
}
}
/// <summary>
/// Draws the custom elements for the Compound Element. Override and Implement
/// Custom GUI calls to create your own Compound Elements
/// </summary>
protected virtual void DrawCustomGUI()
{
}
/// <summary>
/// Draws the Set Button. Override this method to implement your own Set Button or Element.
/// </summary>
protected virtual void DrawSetButton()
{
if (_setIsAppending == true)
{
//GUI.backgroundColor = SET_APPENDING_TINT_COLOR;
GUI.contentColor = SET_APPENDING_TINT_COLOR;
}
if (GUILayout.Button("Set", "ToolbarButton", GUILayout.Width(50.0f), GUILayout.ExpandWidth(false)))
SetValue();
//GUI.backgroundColor = NORMAL_TINT_COLOR;
GUI.contentColor = NORMAL_TINT_COLOR;
}
/// <summary>
/// Draws the Reset Button. Override this method to implement your own Reset Button or Element.
/// </summary>
protected virtual void DrawResetButton()
{
if (GUILayout.Button("Reset", "ToolbarButton", GUILayout.Width(50.0f), GUILayout.ExpandWidth(false)))
ResetValue();
}
/// <summary>
/// Draws the Save Button. Override this method to implement your own Reset Button or Element.
/// </summary>
protected virtual void DrawSaveButton()
{
if (_saveIsAppending)
GUI.contentColor = SAVE_APPENDING_TINT_COLOR;
if (GUILayout.Button("Save", "ToolbarButton", GUILayout.Width(50.0f), GUILayout.ExpandWidth(false)))
SaveValue();
GUI.contentColor = NORMAL_TINT_COLOR;
}
/// <summary>
/// Resets the Elements Value to its initial value.
/// </summary>
protected virtual void ResetValue()
{
_value = _initialValue;
_tempValue = _initialValue;
_setIsAppending = false;
if(OnResetEventHandler != null)
OnResetEventHandler(new CompoundGUIEventArgs());
}
/// <summary>
/// Saves the Elements Value so it isn't reverted when exiting playmode.
/// </summary>
protected virtual void SaveValue()
{
_saveIsAppending = !_saveIsAppending;
//**TODO** Call CompoundDataManager.SaveValue() to save the value
}
/// <summary>
/// Sets the Element so that a Set is not appending.
/// </summary>
protected virtual void RevertValue()
{
_setIsAppending = false;
}
#endregion
#region DRAGGABLE ELEMENT METHOD IMPLEMENTATIONS
/// <summary>
/// Starts the drag Event
/// </summary>
public virtual void StartDrag()
{
}
/// <summary>
/// Called during a drag event
/// </summary>
public virtual void Drag()
{
}
/// <summary>
/// Called when a drag event ends
/// </summary>
public virtual void EndDrag()
{
}
/// <summary>
/// Adds a a reference to an Object for dragging.
/// </summary>
/// <param name="identifier">The string to identify the Object Reference</param>
/// <param name="objectReference">The Object Reference for storing</param>
/// <returns>System.Object</returns>
System.Object DraggableElement.AddObjectReference(string identifier, System.Object objectReference)
{
_objectReferences.Add(identifier, objectReference);
return _objectReferences[identifier];
}
/// <summary>
/// Removes an Object from being a draggable reference.
/// </summary>
/// <param name="identifier">The string that identifies the Object Reference that will be removed</param>
/// <returns>System.Object</returns>
System.Object DraggableElement.RemoveObjectReference(string identifier)
{
_objectReferences.Remove(identifier);
return _objectReferences[identifier];
}
/// <summary>
/// Removes all the draggable Object References.
/// </summary>
/// <returns>List of System.Objects</returns>
List<System.Object> DraggableElement.RemoveAllObjectReferences()
{
List<System.Object> objectRefs = new List<System.Object>(_objectReferences.Count);
foreach(KeyValuePair<string, System.Object> pair in _objectReferences)
{
objectRefs.Add(pair.Value);
}
_objectReferences.Clear();
return objectRefs;
}
#endregion
}
CompoundTextField
/// <summary>
/// Compound TextField
/// </summary>
class CompoundTextField:CompoundGUIElement<string>
{
protected override void DrawCustomGUI()
{
_tempValue = GUILayout.TextField(_tempValue, "ToolbarTextField", GUILayout.ExpandWidth(true));
}
}
CompoundDataManager
public static class CompoundDataManager
{
//Dictionary of some sort, here, to store values. Will use instanceID as primary key
/// <summary>
/// Saves the value from the passed in component
/// </summary>
/// <param name="component"></param>
/// <param name="value"></param>
public void SaveValue(Component component, System.Object value)
{
//**TODO** Find Solution for comparing value references
}
/// <summary>
/// Sets the saved values when exiting playmode
/// </summary>
/// <param name="component"></param>
public void SetValues(Component component)
{
}
}
Scrap this question! $$anonymous$$y brain isn't working. I still wouldn't be able to get around the boxing issues with using pointers. Unless anybody can think of another way I could compare references.
Answer by Bunny83 · Aug 06, 2011 at 10:31 AM
I'm still not sure what you want to achieve, but unsafe code won't work in Unity.
Actually it sounds like you don't need unsafe code but simple reflection...
With reflection you can process all properties / fields / what ever. There are only some problems you can bump into: If you read some of the properties of unity-objects like Renderer.material you will get a mem-leak with warning because you should not use .materials in the editor. only sharedMaterial can / should be accessed. There are several special cases you have to take case about. Beside that you should be able to "analyse" every component / class.
Can you explain your actual problem a bit more in detail? int, float, double... are value types so i don't get your reference comparison. For what reason do you want to compare what values / references?
Hi Thanks for the reply. I'll try to explain what I'm trying to do. Basically I am building a set of Compound GUI Elements that have reset, set and save(in play mode) buttons. AngryAnts' copy Inspector uses reflection to obtain all the properties of a class/component because this is how reflection works. I would like to ins$$anonymous$$d pass a reference value (as an object) and a component into a function which then some how tests to see if the passed in reference matches one of the components propertInfos value references. If it does that will be the value I need to store.
The problem is this will work for reference types but not value types. Value types can get passed in because they get cast to an object which is called boxing but because when they are cast they create a separate objects there is no way to compare the references.
For some reason I thought I could compare pointers but I wasn't thinking straight and this still wouldn't get round the boxing problems. So for now I can't see a solution. I think I will just implement the CopyInspector in a custom editor for each component but automatically save values when exiting play mode.
Yep, that won't work :D. Due to boxing the value gets copied into an object instance on the heap. There is no relation to the actual value anymore.
That's why Unitys animation system uses a string for the transform-path, a System.Type which has to be a Component and a string what field/property should be animated. Unity then just uses transform.Find() to get the right object then it uses the System.Type with GetComponent to get the component and finally uses reflection to get the fieldinfo / propertyinfo to be able to change the value.
You could pass a FieldInfo together with the component reference to address a certain field of that component, but i guess i still don't get what you want to do... xD
Oh Ok I haven't done any animation yet so didn't know that. $$anonymous$$akes sense. Thanks for the info. I've edited my post with my code. Following your comment I believe my best solution is to pass in the name of the property/field and the ref of the component that needs to be saved into the Init method. I wanted to try and avoid passing in more parameters but I don't think I can.
Your answer
Follow this Question
Related Questions
pointer in unity 0 Answers
Unsafe Code - Monodevelop Default 1 Answer
How to enable unsafe and use pointers 4 Answers
Is it impossible to use the code with keyword "unsafe" in C#? 2 Answers
Is 'unsafe' and 'fixed' keywords supported on mobile platforms? 0 Answers