Browse Source

Optimized ScrollArea layout updates
Added isDestroyed checks to ScriptGUIElement to avoid crashing when accessing a destroyed GUIElement
Fixed some C# Inspectable type so they aren't constantly marked as modified

Marko Pintera 10 years ago
parent
commit
b2dcd9bf9f

+ 10 - 1
BansheeEngine/Include/BsGUIElementBase.h

@@ -198,12 +198,21 @@ namespace BansheeEngine
 		const GUIDimensions& _getDimensions() const { return mDimensions; }
 		const GUIDimensions& _getDimensions() const { return mDimensions; }
 
 
 		/**
 		/**
-		 * @brief	Returns element size range constrained by its layout options.
+		 * @brief	Calculates element size range constrained by its layout options.
 		 *
 		 *
 		 * @note	Internal method.
 		 * @note	Internal method.
 		 */
 		 */
 		virtual LayoutSizeRange _calculateLayoutSizeRange() const ;
 		virtual LayoutSizeRange _calculateLayoutSizeRange() const ;
 
 
+		/**
+		 * @brief	Returns element size range constrained by its layout options. This is
+		 *			different from ::_calculateLayoutSizeRange because this method may return
+		 *			cached size range.
+		 *
+		 * @note	Internal method.
+		 */
+		virtual LayoutSizeRange _getLayoutSizeRange() const;
+
 		/**
 		/**
 		 * @brief	Returns element padding that determines how far apart to space out this element
 		 * @brief	Returns element padding that determines how far apart to space out this element
 		 *			from other elements in a layout.
 		 *			from other elements in a layout.

+ 13 - 1
BansheeEngine/Include/BsGUILayout.h

@@ -65,10 +65,22 @@ namespace BansheeEngine
 		UINT32 getNumChildren() const { return (UINT32)mChildren.size(); }
 		UINT32 getNumChildren() const { return (UINT32)mChildren.size(); }
 
 
 		/**
 		/**
-		 * @brief	Returns a size range that was cached during the last "_updateOptimalLayoutSizes" call.
+		 * @copydoc		GUIElementBase::_getLayoutSizeRange
+		 */
+		LayoutSizeRange _getLayoutSizeRange() const override { return _getCachedSizeRange(); }
+
+		/**
+		 * @brief	Returns a size range that was cached during the last 
+		 *			GUIElementBase::_updateOptimalLayoutSizes call.
 		 */
 		 */
 		LayoutSizeRange _getCachedSizeRange() const { return mSizeRange; }
 		LayoutSizeRange _getCachedSizeRange() const { return mSizeRange; }
 
 
+		/**
+		 * @brief	Returns a size ranges for all children that was cached during the last 
+		 *			GUIElementBase::_updateOptimalLayoutSizes call.
+		 */
+		const Vector<LayoutSizeRange>& _getCachedChildSizeRanges() const { return mChildSizeRanges; }
+
 		/**
 		/**
 		 * @copydoc	GUIElementBase::_getOptimalSize
 		 * @copydoc	GUIElementBase::_getOptimalSize
 		 */
 		 */

+ 21 - 6
BansheeEngine/Include/BsGUILayoutUtility.h

@@ -19,12 +19,27 @@ namespace BansheeEngine
 		static Vector2I calcOptimalSize(const GUIElementBase* elem);
 		static Vector2I calcOptimalSize(const GUIElementBase* elem);
 
 
 		/**
 		/**
-		 * @brief	Calculates the actual size of the layout taken up by all of its elements.
-		 *			
-		 * @note	Actual size means the bounds might be smaller or larger than the layout area itself.
-		 *			If larger that means certain portions of the child elements will be clipped, and if
-		 *			smaller certain portions of the layout area will be empty.
+		 * @brief	Calculates the size of elements in a layout of the specified size.
+		 * 
+		 * @param	width				Width of the layout.
+		 * @param	height				Height of the layout.
+		 * @param	layout				Parent layout of the children to calculate the area for.
+		 * @param	updateOptimalSizes	Optimization (doesn't change the results). Set to false if
+		 *								GUIElementBase::_updateOptimalLayoutSizes was already called and optimal sizes are 
+		 *								up to date to avoid recalculating them. (Normally that is true if this is being 
+		 *								called during GUI layout update)
 		 */
 		 */
-		static Vector2I calcActualSize(UINT32 width, UINT32 height, const GUILayout* layout);
+		static Vector2I calcActualSize(UINT32 width, UINT32 height, GUILayout* layout, bool updateOptimalSizes = true);
+
+	private:
+		/**
+		 * @brief	Calculates the size of elements in a layout of the specified size. Assumes the layout and all its children
+		 *			have updated optimal sizes.
+		 * 
+		 * @param	width				Width of the layout.
+		 * @param	height				Height of the layout.
+		 * @param	layout				Parent layout of the children to calculate the area for.
+		 */
+		static Vector2I calcActualSizeInternal(UINT32 width, UINT32 height, GUILayout* layout);
 	};
 	};
 }
 }

+ 13 - 0
BansheeEngine/Include/BsGUIScrollArea.h

@@ -163,6 +163,11 @@ namespace BansheeEngine
 	protected:
 	protected:
 		~GUIScrollArea();
 		~GUIScrollArea();
 
 
+		/**
+		 * @copydoc	GUIElementContainer::_getLayoutSizeRange
+		 */
+		virtual LayoutSizeRange _getLayoutSizeRange() const override;
+
 		/**
 		/**
 		 * @copydoc GUIElementContainer::updateBounds
 		 * @copydoc GUIElementContainer::updateBounds
 		 */
 		 */
@@ -173,6 +178,11 @@ namespace BansheeEngine
 		 */
 		 */
 		LayoutSizeRange _calculateLayoutSizeRange() const override;
 		LayoutSizeRange _calculateLayoutSizeRange() const override;
 
 
+		/**
+		 * @copydoc	GUIElementBase::_updateOptimalLayoutSizes
+		 */
+		void _updateOptimalLayoutSizes() override;
+
 		/**
 		/**
 		 * @copydoc GUIElementContainer::_getOptimalSize
 		 * @copydoc GUIElementContainer::_getOptimalSize
 		 */
 		 */
@@ -233,6 +243,9 @@ namespace BansheeEngine
 		Vector2I mVisibleSize;
 		Vector2I mVisibleSize;
 		Vector2I mContentSize;
 		Vector2I mContentSize;
 
 
+		Vector<LayoutSizeRange> mChildSizeRanges;
+		LayoutSizeRange mSizeRange;
+
 		static const UINT32 MinHandleSize;
 		static const UINT32 MinHandleSize;
 		static const UINT32 WheelScrollAmount;
 		static const UINT32 WheelScrollAmount;
 	};
 	};

+ 6 - 0
BansheeEngine/Source/BsGUIElementBase.cpp

@@ -8,6 +8,7 @@
 #include "BsException.h"
 #include "BsException.h"
 #include "BsGUIWidget.h"
 #include "BsGUIWidget.h"
 #include "BsGUILayoutUtility.h"
 #include "BsGUILayoutUtility.h"
+#include "BsProfilerCPU.h"
 
 
 namespace BansheeEngine
 namespace BansheeEngine
 {
 {
@@ -260,6 +261,11 @@ namespace BansheeEngine
 		return dimensions.calculateSizeRange(_getOptimalSize());
 		return dimensions.calculateSizeRange(_getOptimalSize());
 	}
 	}
 
 
+	LayoutSizeRange GUIElementBase::_getLayoutSizeRange() const
+	{
+		return _calculateLayoutSizeRange();
+	}
+
 	void GUIElementBase::_getElementAreas(const Rect2I& layoutArea, Rect2I* elementAreas, UINT32 numElements,
 	void GUIElementBase::_getElementAreas(const Rect2I& layoutArea, Rect2I* elementAreas, UINT32 numElements,
 		const Vector<LayoutSizeRange>& sizeRanges, const LayoutSizeRange& mySizeRange) const
 		const Vector<LayoutSizeRange>& sizeRanges, const LayoutSizeRange& mySizeRange) const
 	{
 	{

+ 15 - 10
BansheeEngine/Source/BsGUILayoutUtility.cpp

@@ -6,6 +6,7 @@
 #include "BsGUIWidget.h"
 #include "BsGUIWidget.h"
 #include "BsViewport.h"
 #include "BsViewport.h"
 #include "BsGUIPanel.h"
 #include "BsGUIPanel.h"
+#include "BsProfilerCPU.h"
 
 
 namespace BansheeEngine
 namespace BansheeEngine
 {
 {
@@ -14,17 +15,17 @@ namespace BansheeEngine
 		return elem->_calculateLayoutSizeRange().optimal;
 		return elem->_calculateLayoutSizeRange().optimal;
 	}
 	}
 
 
-	Vector2I GUILayoutUtility::calcActualSize(UINT32 width, UINT32 height, const GUILayout* layout)
+	Vector2I GUILayoutUtility::calcActualSize(UINT32 width, UINT32 height, GUILayout* layout, bool updateOptimalSizes)
 	{
 	{
-		UINT32 numElements = (UINT32)layout->_getNumChildren();
+		if (updateOptimalSizes)
+			layout->_updateOptimalLayoutSizes();
 
 
-		Vector<LayoutSizeRange> sizeRanges;
-		for (UINT32 i = 0; i < numElements; i++)
-		{
-			GUIElementBase* child = layout->_getChild(i);
-			sizeRanges.push_back(child->_calculateLayoutSizeRange());
-		}
+		return calcActualSizeInternal(width, height, layout);
+	}
 
 
+	Vector2I GUILayoutUtility::calcActualSizeInternal(UINT32 width, UINT32 height, GUILayout* layout)
+	{
+		UINT32 numElements = (UINT32)layout->_getNumChildren();
 		Rect2I* elementAreas = nullptr;
 		Rect2I* elementAreas = nullptr;
 
 
 		if (numElements > 0)
 		if (numElements > 0)
@@ -34,9 +35,12 @@ namespace BansheeEngine
 		parentArea.width = width;
 		parentArea.width = width;
 		parentArea.height = height;
 		parentArea.height = height;
 
 
-		layout->_getElementAreas(parentArea, elementAreas, numElements, sizeRanges, layout->_calculateLayoutSizeRange());
+		gProfilerCPU().beginSample("actualSizeB");
+		layout->_getElementAreas(parentArea, elementAreas, numElements, layout->_getCachedChildSizeRanges(), layout->_getCachedSizeRange());
+		gProfilerCPU().endSample("actualSizeB");
 		Rect2I* actualAreas = elementAreas; // We re-use the same array
 		Rect2I* actualAreas = elementAreas; // We re-use the same array
 
 
+		gProfilerCPU().beginSample("actualSizeC");
 		for (UINT32 i = 0; i < numElements; i++)
 		for (UINT32 i = 0; i < numElements; i++)
 		{
 		{
 			GUIElementBase* child = layout->_getChild(i);
 			GUIElementBase* child = layout->_getChild(i);
@@ -44,7 +48,7 @@ namespace BansheeEngine
 
 
 			if (child->_getType() == GUIElementBase::Type::Layout || child->_getType() == GUIElementBase::Type::Panel)
 			if (child->_getType() == GUIElementBase::Type::Layout || child->_getType() == GUIElementBase::Type::Panel)
 			{
 			{
-				Vector2I childActualSize = calcActualSize(childArea.width, childArea.height, static_cast<GUILayout*>(child));
+				Vector2I childActualSize = calcActualSizeInternal(childArea.width, childArea.height, static_cast<GUILayout*>(child));
 				actualAreas[i].width = (UINT32)childActualSize.x;
 				actualAreas[i].width = (UINT32)childActualSize.x;
 				actualAreas[i].height = (UINT32)childActualSize.y;
 				actualAreas[i].height = (UINT32)childActualSize.y;
 			}
 			}
@@ -67,6 +71,7 @@ namespace BansheeEngine
 		if (elementAreas != nullptr)
 		if (elementAreas != nullptr)
 			bs_stack_free(elementAreas);
 			bs_stack_free(elementAreas);
 
 
+		gProfilerCPU().endSample("actualSizeC");
 		return actualSize;
 		return actualSize;
 	}
 	}
 }
 }

+ 10 - 12
BansheeEngine/Source/BsGUILayoutX.cpp

@@ -3,6 +3,7 @@
 #include "BsGUISpace.h"
 #include "BsGUISpace.h"
 #include "BsMath.h"
 #include "BsMath.h"
 #include "BsVector2I.h"
 #include "BsVector2I.h"
+#include "BsProfilerCPU.h"
 
 
 namespace BansheeEngine
 namespace BansheeEngine
 {
 {
@@ -46,6 +47,8 @@ namespace BansheeEngine
 		// Update all children first, otherwise we can't determine our own optimal size
 		// Update all children first, otherwise we can't determine our own optimal size
 		GUIElementBase::_updateOptimalLayoutSizes();
 		GUIElementBase::_updateOptimalLayoutSizes();
 
 
+		gProfilerCPU().beginSample("OptX");
+
 		if(mChildren.size() != mChildSizeRanges.size())
 		if(mChildren.size() != mChildSizeRanges.size())
 			mChildSizeRanges.resize(mChildren.size());
 			mChildSizeRanges.resize(mChildren.size());
 
 
@@ -57,23 +60,12 @@ namespace BansheeEngine
 		{
 		{
 			LayoutSizeRange& childSizeRange = mChildSizeRanges[childIdx];
 			LayoutSizeRange& childSizeRange = mChildSizeRanges[childIdx];
 
 
+			childSizeRange = child->_getLayoutSizeRange();
 			if (child->_getType() == GUIElementBase::Type::FixedSpace)
 			if (child->_getType() == GUIElementBase::Type::FixedSpace)
 			{
 			{
-				GUIFixedSpace* fixedSpace = static_cast<GUIFixedSpace*>(child);
-
-				childSizeRange = fixedSpace->_calculateLayoutSizeRange();
 				childSizeRange.optimal.y = 0;
 				childSizeRange.optimal.y = 0;
 				childSizeRange.min.y = 0;
 				childSizeRange.min.y = 0;
 			}
 			}
-			else if (child->_getType() == GUIElementBase::Type::Element)
-			{
-				childSizeRange = child->_calculateLayoutSizeRange();
-			}
-			else if (child->_getType() == GUIElementBase::Type::Layout || child->_getType() == GUIElementBase::Type::Panel)
-			{
-				GUILayout* layout = static_cast<GUILayout*>(child);
-				childSizeRange = layout->_getCachedSizeRange();
-			}
 
 
 			UINT32 paddingX = child->_getPadding().left + child->_getPadding().right;
 			UINT32 paddingX = child->_getPadding().left + child->_getPadding().right;
 			UINT32 paddingY = child->_getPadding().top + child->_getPadding().bottom;
 			UINT32 paddingY = child->_getPadding().top + child->_getPadding().bottom;
@@ -90,11 +82,15 @@ namespace BansheeEngine
 		mSizeRange = _getDimensions().calculateSizeRange(optimalSize);
 		mSizeRange = _getDimensions().calculateSizeRange(optimalSize);
 		mSizeRange.min.x = std::max(mSizeRange.min.x, minSize.x);
 		mSizeRange.min.x = std::max(mSizeRange.min.x, minSize.x);
 		mSizeRange.min.y = std::max(mSizeRange.min.y, minSize.y);
 		mSizeRange.min.y = std::max(mSizeRange.min.y, minSize.y);
+
+		gProfilerCPU().endSample("OptX");
 	}
 	}
 
 
 	void GUILayoutX::_getElementAreas(const Rect2I& layoutArea, Rect2I* elementAreas, UINT32 numElements,
 	void GUILayoutX::_getElementAreas(const Rect2I& layoutArea, Rect2I* elementAreas, UINT32 numElements,
 		const Vector<LayoutSizeRange>& sizeRanges, const LayoutSizeRange& mySizeRange) const
 		const Vector<LayoutSizeRange>& sizeRanges, const LayoutSizeRange& mySizeRange) const
 	{
 	{
+		gProfilerCPU().beginSample("areasX");
+
 		assert(mChildren.size() == numElements);
 		assert(mChildren.size() == numElements);
 
 
 		UINT32 totalOptimalSize = mySizeRange.optimal.x;
 		UINT32 totalOptimalSize = mySizeRange.optimal.x;
@@ -384,6 +380,8 @@ namespace BansheeEngine
 
 
 		if (processedElements != nullptr)
 		if (processedElements != nullptr)
 			bs_stack_free(processedElements);
 			bs_stack_free(processedElements);
+
+		gProfilerCPU().endSample("areasX");
 	}
 	}
 
 
 	void GUILayoutX::_updateLayoutInternal(const GUILayoutData& data)
 	void GUILayoutX::_updateLayoutInternal(const GUILayoutData& data)

+ 10 - 12
BansheeEngine/Source/BsGUILayoutY.cpp

@@ -3,6 +3,7 @@
 #include "BsGUISpace.h"
 #include "BsGUISpace.h"
 #include "BsMath.h"
 #include "BsMath.h"
 #include "BsVector2I.h"
 #include "BsVector2I.h"
+#include "BsProfilerCPU.h"
 
 
 namespace BansheeEngine
 namespace BansheeEngine
 {
 {
@@ -46,6 +47,8 @@ namespace BansheeEngine
 		// Update all children first, otherwise we can't determine our own optimal size
 		// Update all children first, otherwise we can't determine our own optimal size
 		GUIElementBase::_updateOptimalLayoutSizes();
 		GUIElementBase::_updateOptimalLayoutSizes();
 
 
+		gProfilerCPU().beginSample("OptY");
+
 		if(mChildren.size() != mChildSizeRanges.size())
 		if(mChildren.size() != mChildSizeRanges.size())
 			mChildSizeRanges.resize(mChildren.size());
 			mChildSizeRanges.resize(mChildren.size());
 
 
@@ -57,23 +60,12 @@ namespace BansheeEngine
 		{
 		{
 			LayoutSizeRange& childSizeRange = mChildSizeRanges[childIdx];
 			LayoutSizeRange& childSizeRange = mChildSizeRanges[childIdx];
 
 
+			childSizeRange = child->_getLayoutSizeRange();
 			if(child->_getType() == GUIElementBase::Type::FixedSpace)
 			if(child->_getType() == GUIElementBase::Type::FixedSpace)
 			{
 			{
-				GUIFixedSpace* fixedSpace = static_cast<GUIFixedSpace*>(child);
-
-				childSizeRange = fixedSpace->_calculateLayoutSizeRange();
 				childSizeRange.optimal.x = 0;
 				childSizeRange.optimal.x = 0;
 				childSizeRange.min.x = 0;
 				childSizeRange.min.x = 0;
 			}
 			}
-			else if(child->_getType() == GUIElementBase::Type::Element)
-			{
-				childSizeRange = child->_calculateLayoutSizeRange();
-			}
-			else if (child->_getType() == GUIElementBase::Type::Layout || child->_getType() == GUIElementBase::Type::Panel)
-			{
-				GUILayout* layout = static_cast<GUILayout*>(child);
-				childSizeRange = layout->_getCachedSizeRange();
-			}
 
 
 			UINT32 paddingX = child->_getPadding().left + child->_getPadding().right;
 			UINT32 paddingX = child->_getPadding().left + child->_getPadding().right;
 			UINT32 paddingY = child->_getPadding().top + child->_getPadding().bottom;
 			UINT32 paddingY = child->_getPadding().top + child->_getPadding().bottom;
@@ -90,11 +82,15 @@ namespace BansheeEngine
 		mSizeRange = _getDimensions().calculateSizeRange(optimalSize);
 		mSizeRange = _getDimensions().calculateSizeRange(optimalSize);
 		mSizeRange.min.x = std::max(mSizeRange.min.x, minSize.x);
 		mSizeRange.min.x = std::max(mSizeRange.min.x, minSize.x);
 		mSizeRange.min.y = std::max(mSizeRange.min.y, minSize.y);
 		mSizeRange.min.y = std::max(mSizeRange.min.y, minSize.y);
+
+		gProfilerCPU().endSample("OptY");
 	}
 	}
 
 
 	void GUILayoutY::_getElementAreas(const Rect2I& layoutArea, Rect2I* elementAreas, UINT32 numElements,
 	void GUILayoutY::_getElementAreas(const Rect2I& layoutArea, Rect2I* elementAreas, UINT32 numElements,
 		const Vector<LayoutSizeRange>& sizeRanges, const LayoutSizeRange& mySizeRange) const
 		const Vector<LayoutSizeRange>& sizeRanges, const LayoutSizeRange& mySizeRange) const
 	{
 	{
+		gProfilerCPU().beginSample("areasY");
+
 		assert(mChildren.size() == numElements);
 		assert(mChildren.size() == numElements);
 
 
 		UINT32 totalOptimalSize = mySizeRange.optimal.y;
 		UINT32 totalOptimalSize = mySizeRange.optimal.y;
@@ -382,6 +378,8 @@ namespace BansheeEngine
 			yOffset += elemHeight + child->_getPadding().bottom;
 			yOffset += elemHeight + child->_getPadding().bottom;
 			childIdx++;
 			childIdx++;
 		}
 		}
+
+		gProfilerCPU().endSample("areasY");
 	}
 	}
 
 
 	void GUILayoutY::_updateLayoutInternal(const GUILayoutData& data)
 	void GUILayoutY::_updateLayoutInternal(const GUILayoutData& data)

+ 10 - 11
BansheeEngine/Source/BsGUIPanel.cpp

@@ -3,6 +3,7 @@
 #include "BsGUISpace.h"
 #include "BsGUISpace.h"
 #include "BsMath.h"
 #include "BsMath.h"
 #include "BsVector2I.h"
 #include "BsVector2I.h"
+#include "BsProfilerCPU.h"
 
 
 namespace BansheeEngine
 namespace BansheeEngine
 {
 {
@@ -51,23 +52,14 @@ namespace BansheeEngine
 	{
 	{
 		if (element->_getType() == GUIElementBase::Type::FixedSpace || element->_getType() == GUIElementBase::Type::FlexibleSpace)
 		if (element->_getType() == GUIElementBase::Type::FixedSpace || element->_getType() == GUIElementBase::Type::FlexibleSpace)
 		{
 		{
-			LayoutSizeRange sizeRange = element->_calculateLayoutSizeRange();
+			LayoutSizeRange sizeRange = element->_getLayoutSizeRange();
 			sizeRange.optimal.x = 0;
 			sizeRange.optimal.x = 0;
 			sizeRange.optimal.y = 0;
 			sizeRange.optimal.y = 0;
 
 
 			return sizeRange;
 			return sizeRange;
 		}
 		}
-		else if (element->_getType() == GUIElementBase::Type::Element)
-		{
-			return element->_calculateLayoutSizeRange();
-		}
-		else if (element->_getType() == GUIElementBase::Type::Layout || element->_getType() == GUIElementBase::Type::Panel)
-		{
-			const GUILayout* layout = static_cast<const GUILayout*>(element);
-			return layout->_getCachedSizeRange();
-		}
 
 
-		return LayoutSizeRange();
+		return element->_getLayoutSizeRange();
 	}
 	}
 
 
 	void GUIPanel::_updateOptimalLayoutSizes()
 	void GUIPanel::_updateOptimalLayoutSizes()
@@ -75,6 +67,7 @@ namespace BansheeEngine
 		// Update all children first, otherwise we can't determine our own optimal size
 		// Update all children first, otherwise we can't determine our own optimal size
 		GUIElementBase::_updateOptimalLayoutSizes();
 		GUIElementBase::_updateOptimalLayoutSizes();
 
 
+		gProfilerCPU().beginSample("OptP");
 		if (mChildren.size() != mChildSizeRanges.size())
 		if (mChildren.size() != mChildSizeRanges.size())
 			mChildSizeRanges.resize(mChildren.size());
 			mChildSizeRanges.resize(mChildren.size());
 
 
@@ -100,6 +93,8 @@ namespace BansheeEngine
 		}
 		}
 
 
 		mSizeRange = _getDimensions().calculateSizeRange(optimalSize);
 		mSizeRange = _getDimensions().calculateSizeRange(optimalSize);
+
+		gProfilerCPU().endSample("OptP");
 	}
 	}
 
 
 	void GUIPanel::_getElementAreas(const Rect2I& layoutArea, Rect2I* elementAreas, UINT32 numElements,
 	void GUIPanel::_getElementAreas(const Rect2I& layoutArea, Rect2I* elementAreas, UINT32 numElements,
@@ -119,6 +114,8 @@ namespace BansheeEngine
 
 
 	Rect2I GUIPanel::_getElementArea(const Rect2I& layoutArea, const GUIElementBase* element, const LayoutSizeRange& sizeRange) const
 	Rect2I GUIPanel::_getElementArea(const Rect2I& layoutArea, const GUIElementBase* element, const LayoutSizeRange& sizeRange) const
 	{
 	{
+		gProfilerCPU().beginSample("areasP");
+
 		const GUIDimensions& dimensions = element->_getDimensions();
 		const GUIDimensions& dimensions = element->_getDimensions();
 
 
 		Rect2I area;
 		Rect2I area;
@@ -166,6 +163,8 @@ namespace BansheeEngine
 			area.height = modifiedHeight;
 			area.height = modifiedHeight;
 		}
 		}
 
 
+		gProfilerCPU().endSample("areasP");
+
 		return area;
 		return area;
 	}
 	}
 
 

+ 44 - 10
BansheeEngine/Source/BsGUIScrollArea.cpp

@@ -9,7 +9,9 @@
 #include "BsGUIScrollBarHorz.h"
 #include "BsGUIScrollBarHorz.h"
 #include "BsGUIMouseEvent.h"
 #include "BsGUIMouseEvent.h"
 #include "BsGUILayoutUtility.h"
 #include "BsGUILayoutUtility.h"
+#include "BsGUISpace.h"
 #include "BsException.h"
 #include "BsException.h"
+#include "BsProfilerCPU.h"
 
 
 using namespace std::placeholders;
 using namespace std::placeholders;
 
 
@@ -77,6 +79,33 @@ namespace BansheeEngine
 		return sizeRange;
 		return sizeRange;
 	}
 	}
 
 
+	LayoutSizeRange GUIScrollArea::_getLayoutSizeRange() const
+	{
+		return mSizeRange;
+	}
+
+	void GUIScrollArea::_updateOptimalLayoutSizes()
+	{
+		// Update all children first, otherwise we can't determine our own optimal size
+		GUIElementBase::_updateOptimalLayoutSizes();
+
+		gProfilerCPU().beginSample("OptS");
+
+		if (mChildren.size() != mChildSizeRanges.size())
+			mChildSizeRanges.resize(mChildren.size());
+
+		UINT32 childIdx = 0;
+		for (auto& child : mChildren)
+		{
+			mChildSizeRanges[childIdx] = child->_getLayoutSizeRange();
+			childIdx++;
+		}
+
+		mSizeRange = mContentLayout->_getLayoutSizeRange();
+
+		gProfilerCPU().endSample("OptS");
+	}
+
 	void GUIScrollArea::_getElementAreas(const Rect2I& layoutArea, Rect2I* elementAreas, UINT32 numElements,
 	void GUIScrollArea::_getElementAreas(const Rect2I& layoutArea, Rect2I* elementAreas, UINT32 numElements,
 		const Vector<LayoutSizeRange>& sizeRanges, const LayoutSizeRange& mySizeRange) const
 		const Vector<LayoutSizeRange>& sizeRanges, const LayoutSizeRange& mySizeRange) const
 	{
 	{
@@ -87,6 +116,8 @@ namespace BansheeEngine
 	void GUIScrollArea::_getElementAreas(const Rect2I& layoutArea, Rect2I* elementAreas, UINT32 numElements,
 	void GUIScrollArea::_getElementAreas(const Rect2I& layoutArea, Rect2I* elementAreas, UINT32 numElements,
 		const Vector<LayoutSizeRange>& sizeRanges, Vector2I& visibleSize, Vector2I& contentSize) const
 		const Vector<LayoutSizeRange>& sizeRanges, Vector2I& visibleSize, Vector2I& contentSize) const
 	{
 	{
+		gProfilerCPU().beginSample("areasS");
+
 		assert(mChildren.size() == numElements && numElements == 3);
 		assert(mChildren.size() == numElements && numElements == 3);
 
 
 		UINT32 layoutIdx = 0;
 		UINT32 layoutIdx = 0;
@@ -122,7 +153,7 @@ namespace BansheeEngine
 		UINT32 layoutWidth = std::max(optimalContentWidth, (UINT32)layoutArea.width);
 		UINT32 layoutWidth = std::max(optimalContentWidth, (UINT32)layoutArea.width);
 		UINT32 layoutHeight = std::max(optimalContentHeight, (UINT32)layoutArea.height);
 		UINT32 layoutHeight = std::max(optimalContentHeight, (UINT32)layoutArea.height);
 
 
-		contentSize = GUILayoutUtility::calcActualSize(layoutWidth, layoutHeight, mContentLayout);
+		contentSize = GUILayoutUtility::calcActualSize(layoutWidth, layoutHeight, mContentLayout, false);
 		visibleSize = Vector2I(layoutArea.width, layoutArea.height);
 		visibleSize = Vector2I(layoutArea.width, layoutArea.height);
 
 
 		bool addHorzScrollbar = (mHorzBarType == ScrollBarType::ShowIfDoesntFit && contentSize.x > visibleSize.x) ||
 		bool addHorzScrollbar = (mHorzBarType == ScrollBarType::ShowIfDoesntFit && contentSize.x > visibleSize.x) ||
@@ -140,7 +171,7 @@ namespace BansheeEngine
 			else
 			else
 				layoutHeight = std::max(optimalContentHeight, (UINT32)visibleSize.y); // Never go below optimal size
 				layoutHeight = std::max(optimalContentHeight, (UINT32)visibleSize.y); // Never go below optimal size
 
 
-			contentSize = GUILayoutUtility::calcActualSize(layoutWidth, layoutHeight, mContentLayout);
+			contentSize = GUILayoutUtility::calcActualSize(layoutWidth, layoutHeight, mContentLayout, false);
 			hasHorzScrollbar = true;
 			hasHorzScrollbar = true;
 		}
 		}
 
 
@@ -157,7 +188,7 @@ namespace BansheeEngine
 			else
 			else
 				layoutWidth = std::max(optimalContentWidth, (UINT32)visibleSize.x); // Never go below optimal size
 				layoutWidth = std::max(optimalContentWidth, (UINT32)visibleSize.x); // Never go below optimal size
 
 
-			contentSize = GUILayoutUtility::calcActualSize(layoutWidth, layoutHeight, mContentLayout);
+			contentSize = GUILayoutUtility::calcActualSize(layoutWidth, layoutHeight, mContentLayout, false);
 			hasVertScrollbar = true;
 			hasVertScrollbar = true;
 
 
 			if (!hasHorzScrollbar) // Since width has been reduced, we need to check if we require the horizontal scrollbar
 			if (!hasHorzScrollbar) // Since width has been reduced, we need to check if we require the horizontal scrollbar
@@ -174,7 +205,7 @@ namespace BansheeEngine
 					else
 					else
 						layoutHeight = std::max(optimalContentHeight, (UINT32)visibleSize.y); // Never go below optimal size
 						layoutHeight = std::max(optimalContentHeight, (UINT32)visibleSize.y); // Never go below optimal size
 
 
-					contentSize = GUILayoutUtility::calcActualSize(layoutWidth, layoutHeight, mContentLayout);
+					contentSize = GUILayoutUtility::calcActualSize(layoutWidth, layoutHeight, mContentLayout, false);
 					hasHorzScrollbar = true;
 					hasHorzScrollbar = true;
 				}
 				}
 			}
 			}
@@ -211,24 +242,26 @@ namespace BansheeEngine
 		{
 		{
 			elementAreas[horzScrollIdx] = Rect2I(layoutArea.x, layoutArea.y + layoutHeight, 0, 0);
 			elementAreas[horzScrollIdx] = Rect2I(layoutArea.x, layoutArea.y + layoutHeight, 0, 0);
 		}
 		}
+
+		gProfilerCPU().endSample("areasS");
 	}
 	}
 
 
 	void GUIScrollArea::_updateLayoutInternal(const GUILayoutData& data)
 	void GUIScrollArea::_updateLayoutInternal(const GUILayoutData& data)
 	{
 	{
+		gProfilerCPU().beginSample("layoutS");
+
 		UINT32 numElements = (UINT32)mChildren.size();
 		UINT32 numElements = (UINT32)mChildren.size();
 		Rect2I* elementAreas = nullptr;
 		Rect2I* elementAreas = nullptr;
 
 
 		if (numElements > 0)
 		if (numElements > 0)
 			elementAreas = bs_stack_new<Rect2I>(numElements);
 			elementAreas = bs_stack_new<Rect2I>(numElements);
 
 
-		Vector<LayoutSizeRange> sizeRanges;
 		UINT32 layoutIdx = 0;
 		UINT32 layoutIdx = 0;
 		UINT32 horzScrollIdx = 0;
 		UINT32 horzScrollIdx = 0;
 		UINT32 vertScrollIdx = 0;
 		UINT32 vertScrollIdx = 0;
 		for (UINT32 i = 0; i < numElements; i++)
 		for (UINT32 i = 0; i < numElements; i++)
 		{
 		{
 			GUIElementBase* child = _getChild(i);
 			GUIElementBase* child = _getChild(i);
-			sizeRanges.push_back(child->_calculateLayoutSizeRange());
 
 
 			if (child == mContentLayout)
 			if (child == mContentLayout)
 				layoutIdx = i;
 				layoutIdx = i;
@@ -240,7 +273,7 @@ namespace BansheeEngine
 				vertScrollIdx = i;
 				vertScrollIdx = i;
 		}
 		}
 
 
-		_getElementAreas(data.area, elementAreas, numElements, sizeRanges, mVisibleSize, mContentSize);
+		_getElementAreas(data.area, elementAreas, numElements, mChildSizeRanges, mVisibleSize, mContentSize);
 
 
 		Rect2I& layoutBounds = elementAreas[layoutIdx];
 		Rect2I& layoutBounds = elementAreas[layoutIdx];
 		Rect2I& horzScrollBounds = elementAreas[horzScrollIdx];
 		Rect2I& horzScrollBounds = elementAreas[horzScrollIdx];
@@ -266,9 +299,8 @@ namespace BansheeEngine
 			vertScrollData.clipRect = vertScrollBounds;
 			vertScrollData.clipRect = vertScrollBounds;
 			vertScrollData.clipRect.clip(data.clipRect);
 			vertScrollData.clipRect.clip(data.clipRect);
 
 
-			// This element is not a child of any layout so we treat it as a root element
 			mVertScroll->_setLayoutData(vertScrollData);
 			mVertScroll->_setLayoutData(vertScrollData);
-			mVertScroll->_updateLayout(vertScrollData);
+			mVertScroll->_updateLayoutInternal(vertScrollData);
 
 
 			// Set new handle size and update position to match the new size
 			// Set new handle size and update position to match the new size
 			UINT32 newHandleSize = (UINT32)Math::floorToInt(mVertScroll->getMaxHandleSize() * (vertScrollBounds.height / (float)mContentSize.y));
 			UINT32 newHandleSize = (UINT32)Math::floorToInt(mVertScroll->getMaxHandleSize() * (vertScrollBounds.height / (float)mContentSize.y));
@@ -293,7 +325,7 @@ namespace BansheeEngine
 			horzScrollData.clipRect.clip(data.clipRect);
 			horzScrollData.clipRect.clip(data.clipRect);
 
 
 			mHorzScroll->_setLayoutData(horzScrollData);
 			mHorzScroll->_setLayoutData(horzScrollData);
-			mHorzScroll->_updateLayout(horzScrollData);
+			mHorzScroll->_updateLayoutInternal(horzScrollData);
 
 
 			// Set new handle size and update position to match the new size
 			// Set new handle size and update position to match the new size
 			UINT32 newHandleSize = (UINT32)Math::floorToInt(mHorzScroll->getMaxHandleSize() * (horzScrollBounds.width / (float)mContentSize.x));
 			UINT32 newHandleSize = (UINT32)Math::floorToInt(mHorzScroll->getMaxHandleSize() * (horzScrollBounds.width / (float)mContentSize.x));
@@ -311,6 +343,8 @@ namespace BansheeEngine
 
 
 		if (elementAreas != nullptr)
 		if (elementAreas != nullptr)
 			bs_stack_free(elementAreas);
 			bs_stack_free(elementAreas);
+
+		gProfilerCPU().endSample("layoutS");
 	}
 	}
 
 
 	void GUIScrollArea::vertScrollUpdate(float scrollPos)
 	void GUIScrollArea::vertScrollUpdate(float scrollPos)

+ 2 - 2
BansheeEngine/Source/BsGUIWidget.cpp

@@ -17,6 +17,7 @@
 #include "BsRenderWindow.h"
 #include "BsRenderWindow.h"
 #include "BsGUIWidgetRTTI.h"
 #include "BsGUIWidgetRTTI.h"
 #include "BsProfilerCPU.h"
 #include "BsProfilerCPU.h"
+#include "BsDebug.h"
 
 
 namespace BansheeEngine
 namespace BansheeEngine
 {
 {
@@ -176,10 +177,9 @@ namespace BansheeEngine
 		else
 		else
 		{
 		{
 			GUILayoutData childLayoutData = updateParent->_getLayoutData();
 			GUILayoutData childLayoutData = updateParent->_getLayoutData();
-
 			updateParent->_updateLayout(childLayoutData);
 			updateParent->_updateLayout(childLayoutData);
 		}
 		}
-
+		
 		// Mark dirty contents
 		// Mark dirty contents
 		bs_frame_mark();
 		bs_frame_mark();
 		{
 		{

+ 1 - 1
MBansheeEditor/Inspector/InspectableArray.cs

@@ -73,7 +73,7 @@ namespace BansheeEditor
                 return true;
                 return true;
 
 
             object newPropertyValue = property.GetValue<object>();
             object newPropertyValue = property.GetValue<object>();
-            if (propertyValue != newPropertyValue)
+            if (!propertyValue.Equals(newPropertyValue))
                 return true;
                 return true;
 
 
             if (newPropertyValue != null)
             if (newPropertyValue != null)

+ 1 - 1
MBansheeEditor/Inspector/InspectableList.cs

@@ -73,7 +73,7 @@ namespace BansheeEditor
                 return true;
                 return true;
 
 
             object newPropertyValue = property.GetValue<object>();
             object newPropertyValue = property.GetValue<object>();
-            if (propertyValue != newPropertyValue)
+            if (!propertyValue.Equals(newPropertyValue))
                 return true;
                 return true;
 
 
             if (newPropertyValue != null)
             if (newPropertyValue != null)

+ 1 - 1
MBansheeEditor/Inspector/InspectableObject.cs

@@ -29,7 +29,7 @@ namespace BansheeEditor
                 return true;
                 return true;
 
 
             object newPropertyValue = property.GetValue<object>();
             object newPropertyValue = property.GetValue<object>();
-            if (propertyValue != newPropertyValue)
+            if (!propertyValue.Equals(newPropertyValue))
                 return true;
                 return true;
 
 
             return base.IsModified();
             return base.IsModified();

+ 2 - 0
MBansheeEditor/Inspector/InspectorWindow.cs

@@ -90,6 +90,8 @@ namespace BansheeEditor
             if (so == null)
             if (so == null)
                 return;
                 return;
 
 
+            Debug.Log("INSPECTOR REBUILD");
+
             currentType = InspectorType.SceneObject;
             currentType = InspectorType.SceneObject;
             activeSO = so;
             activeSO = so;
 
 

+ 36 - 0
SBansheeEngine/Source/BsScriptGUIElement.cpp

@@ -96,6 +96,9 @@ namespace BansheeEngine
 
 
 	void ScriptGUIElement::internal_setVisible(ScriptGUIElementBaseTBase* nativeInstance, bool visible)
 	void ScriptGUIElement::internal_setVisible(ScriptGUIElementBaseTBase* nativeInstance, bool visible)
 	{
 	{
+		if (nativeInstance->isDestroyed())
+			return;
+
 		if(visible)
 		if(visible)
 			nativeInstance->getGUIElement()->enableRecursively();
 			nativeInstance->getGUIElement()->enableRecursively();
 		else
 		else
@@ -104,6 +107,9 @@ namespace BansheeEngine
 
 
 	void ScriptGUIElement::internal_setFocus(ScriptGUIElementBaseTBase* nativeInstance, bool focus)
 	void ScriptGUIElement::internal_setFocus(ScriptGUIElementBaseTBase* nativeInstance, bool focus)
 	{
 	{
+		if (nativeInstance->isDestroyed())
+			return;
+
 		GUIElementBase* guiElemBase = nativeInstance->getGUIElement();
 		GUIElementBase* guiElemBase = nativeInstance->getGUIElement();
 		if (guiElemBase->_getType() == GUIElementBase::Type::Element)
 		if (guiElemBase->_getType() == GUIElementBase::Type::Element)
 		{
 		{
@@ -114,11 +120,17 @@ namespace BansheeEngine
 
 
 	Rect2I ScriptGUIElement::internal_getBounds(ScriptGUIElementBaseTBase* nativeInstance)
 	Rect2I ScriptGUIElement::internal_getBounds(ScriptGUIElementBaseTBase* nativeInstance)
 	{
 	{
+		if (nativeInstance->isDestroyed())
+			return Rect2I();
+
 		return nativeInstance->getGUIElement()->getBounds();
 		return nativeInstance->getGUIElement()->getBounds();
 	}
 	}
 
 
 	void ScriptGUIElement::internal_setBounds(ScriptGUIElementBaseTBase* nativeInstance, Rect2I bounds)
 	void ScriptGUIElement::internal_setBounds(ScriptGUIElementBaseTBase* nativeInstance, Rect2I bounds)
 	{
 	{
+		if (nativeInstance->isDestroyed())
+			return;
+
 		nativeInstance->getGUIElement()->setPosition(bounds.x, bounds.y);
 		nativeInstance->getGUIElement()->setPosition(bounds.x, bounds.y);
 		nativeInstance->getGUIElement()->setWidth(bounds.width);
 		nativeInstance->getGUIElement()->setWidth(bounds.width);
 		nativeInstance->getGUIElement()->setHeight(bounds.height);
 		nativeInstance->getGUIElement()->setHeight(bounds.height);
@@ -126,41 +138,65 @@ namespace BansheeEngine
 
 
 	Rect2I ScriptGUIElement::internal_getVisibleBounds(ScriptGUIElementBaseTBase* nativeInstance)
 	Rect2I ScriptGUIElement::internal_getVisibleBounds(ScriptGUIElementBaseTBase* nativeInstance)
 	{
 	{
+		if (nativeInstance->isDestroyed())
+			return Rect2I();
+
 		return nativeInstance->getGUIElement()->getVisibleBounds();
 		return nativeInstance->getGUIElement()->getVisibleBounds();
 	}
 	}
 
 
 	void ScriptGUIElement::internal_SetPosition(ScriptGUIElementBaseTBase* nativeInstance, INT32 x, INT32 y)
 	void ScriptGUIElement::internal_SetPosition(ScriptGUIElementBaseTBase* nativeInstance, INT32 x, INT32 y)
 	{
 	{
+		if (nativeInstance->isDestroyed())
+			return;
+
 		nativeInstance->getGUIElement()->setPosition(x, y);
 		nativeInstance->getGUIElement()->setPosition(x, y);
 	}
 	}
 
 
 	void ScriptGUIElement::internal_SetWidth(ScriptGUIElementBaseTBase* nativeInstance, UINT32 width)
 	void ScriptGUIElement::internal_SetWidth(ScriptGUIElementBaseTBase* nativeInstance, UINT32 width)
 	{
 	{
+		if (nativeInstance->isDestroyed())
+			return;
+
 		nativeInstance->getGUIElement()->setWidth(width);
 		nativeInstance->getGUIElement()->setWidth(width);
 	}
 	}
 
 
 	void ScriptGUIElement::internal_SetFlexibleWidth(ScriptGUIElementBaseTBase* nativeInstance, UINT32 minWidth, UINT32 maxWidth)
 	void ScriptGUIElement::internal_SetFlexibleWidth(ScriptGUIElementBaseTBase* nativeInstance, UINT32 minWidth, UINT32 maxWidth)
 	{
 	{
+		if (nativeInstance->isDestroyed())
+			return;
+
 		nativeInstance->getGUIElement()->setFlexibleWidth(minWidth, maxWidth);
 		nativeInstance->getGUIElement()->setFlexibleWidth(minWidth, maxWidth);
 	}
 	}
 
 
 	void ScriptGUIElement::internal_SetHeight(ScriptGUIElementBaseTBase* nativeInstance, UINT32 height)
 	void ScriptGUIElement::internal_SetHeight(ScriptGUIElementBaseTBase* nativeInstance, UINT32 height)
 	{
 	{
+		if (nativeInstance->isDestroyed())
+			return;
+
 		nativeInstance->getGUIElement()->setHeight(height);
 		nativeInstance->getGUIElement()->setHeight(height);
 	}
 	}
 
 
 	void ScriptGUIElement::internal_SetFlexibleHeight(ScriptGUIElementBaseTBase* nativeInstance, UINT32 minHeight, UINT32 maxHeight)
 	void ScriptGUIElement::internal_SetFlexibleHeight(ScriptGUIElementBaseTBase* nativeInstance, UINT32 minHeight, UINT32 maxHeight)
 	{
 	{
+		if (nativeInstance->isDestroyed())
+			return;
+
 		nativeInstance->getGUIElement()->setFlexibleHeight(minHeight, maxHeight);
 		nativeInstance->getGUIElement()->setFlexibleHeight(minHeight, maxHeight);
 	}
 	}
 
 
 	void ScriptGUIElement::internal_ResetDimensions(ScriptGUIElementBaseTBase* nativeInstance)
 	void ScriptGUIElement::internal_ResetDimensions(ScriptGUIElementBaseTBase* nativeInstance)
 	{
 	{
+		if (nativeInstance->isDestroyed())
+			return;
+
 		nativeInstance->getGUIElement()->resetDimensions();
 		nativeInstance->getGUIElement()->resetDimensions();
 	}
 	}
 
 
 	void ScriptGUIElement::internal_SetContextMenu(ScriptGUIElementBaseTBase* nativeInstance, ScriptContextMenu* contextMenu)
 	void ScriptGUIElement::internal_SetContextMenu(ScriptGUIElementBaseTBase* nativeInstance, ScriptContextMenu* contextMenu)
 	{
 	{
+		if (nativeInstance->isDestroyed())
+			return;
+
 		GUIElementBase* guiElemBase = nativeInstance->getGUIElement();
 		GUIElementBase* guiElemBase = nativeInstance->getGUIElement();
 		if (guiElemBase->_getType() == GUIElementBase::Type::Element)
 		if (guiElemBase->_getType() == GUIElementBase::Type::Element)
 		{
 		{

+ 53 - 6
TODO.txt

@@ -57,14 +57,61 @@ Code quality improvements:
 ----------------------------------------------------------------------
 ----------------------------------------------------------------------
 Polish stage 1
 Polish stage 1
 
 
-Test inspector selection, selecting a resource and adding/removing component updates
-Showing the inspector causes a considerable slowdown (maybe stuff gets refreshed too often?)
- - It seems to be rebuilding a lot of GUI just because I moused over an element. Can I avoid that?
-   - Layout update should only be called if I change element size or reposition it in some way
- - UpdateLayout seems to be taking a huge amount of time, and update meshes isn't much better
+UpdateMeshes is taking a long time to rebuild, try to optimize it
+
+Moving the mouse over inspector seems to queue a rebuild for the entire GUIWidget (meaning the whole main window)
+ - It seems the parent GUIPanel is marked as dirty
+ - Ensure that dirty changes don't propagate outside of EditorWidget
+ - Modify GUIElementBase::findUpdateParent
+	GUIElementBase* GUIElementBase::findUpdateParent()
+	{
+		GUIElementBase* currentElement = mParentElement;
+		while (currentElement != nullptr)
+		{
+			const GUIDimensions& parentDimensions = currentElement->_getDimensions();
+			bool boundsDependOnChildren = !parentDimensions.fixedHeight() || !parentDimensions.fixedWidth();
+
+			if (!boundsDependOnChildren)
+			{
+				// If parent is a panel then we can do an optimization and only update
+				// one child instead of all of them, so change parent to that child.
+				if (currentElement->_getType() == GUIElementBase::Type::Panel)
+				{
+					GUIElementBase* optimizedUpdateParent = this;
+					while (optimizedUpdateParent->_getParent() != currentElement)
+						optimizedUpdateParent = optimizedUpdateParent->_getParent();
+
+					currentElement = optimizedUpdateParent;
+				}
+
+				return currentElement;
+			}
+
+			currentElement = currentElement->mParentElement;
+		}
+
+		return nullptr;
+	}
+
+ScrollArea doesn't cache its own size range. This means whenever layouts (or other scroll areas) are performing optimal size updates I'm doing 
+another whole pass on all of the scroll area's children. While I'm modifying this I should modify how layouts determine whether to retrieve cached 
+size range or calculate it (right now I'm testing for exact type, but I need a more generic solution since ScrollArea doesn't qualify as its own type)
+
+Avoid fully recalculating layout for common operations like changing button states/toggles, and modifying input boxes.
+ - Layout should be fully recalculated only if:
+   - optimal size changes
+   - padding changes
+   - dimensions change (x/y position only relevant for for panels) (perhaps even only LayoutSizeRange)
+   - disabled state changes
+   - when initially created
+ - I should probably just handle this on a case by case basis (e.g. if changing the active button texture is the same size as previous one don't call contentDirty)
+
+
+Test scroll areas after optimization pass
+Get rid of all the profiling calls
 
 
+Test inspector selection, selecting a resource and adding/removing component updates
 Crash when showing the inspector (invalid index in Layout.InsertElement called from InspectableObject.Update)
 Crash when showing the inspector (invalid index in Layout.InsertElement called from InspectableObject.Update)
-
 Handle seems to lag behind the selected mesh
 Handle seems to lag behind the selected mesh
 ProjectLibrary seems to import some files on every start-up
 ProjectLibrary seems to import some files on every start-up
 Crash on shutdown in mono_gchandle_free
 Crash on shutdown in mono_gchandle_free